diff options
Diffstat (limited to 'lib/x509/privkey_openssl.c')
-rw-r--r-- | lib/x509/privkey_openssl.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/lib/x509/privkey_openssl.c b/lib/x509/privkey_openssl.c new file mode 100644 index 0000000..9fc70e0 --- /dev/null +++ b/lib/x509/privkey_openssl.c @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2012 Free Software Foundation, Inc. + * + * Author: David Woodhouse + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/> + * + */ + +#include "gnutls_int.h" + +#include <datum.h> +#include <global.h> +#include "errors.h" +#include <common.h> +#include <x509.h> +#include <x509_b64.h> +#include "x509_int.h" +#include <algorithms.h> +#include <num.h> +#include <random.h> + +static int +openssl_hash_password(const char *_password, gnutls_datum_t * key, + gnutls_datum_t * salt) +{ + unsigned char md5[16]; + digest_hd_st hd; + unsigned int count = 0; + int ret; + char *password = NULL; + + if (_password != NULL) { + gnutls_datum_t pout; + ret = _gnutls_utf8_password_normalize(_password, strlen(_password), &pout, 1); + if (ret < 0) + return gnutls_assert_val(ret); + + password = (char*)pout.data; + } + + while (count < key->size) { + ret = _gnutls_hash_init(&hd, mac_to_entry(GNUTLS_MAC_MD5)); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + if (count) { + ret = _gnutls_hash(&hd, md5, sizeof(md5)); + if (ret < 0) { + hash_err: + _gnutls_hash_deinit(&hd, NULL); + gnutls_assert(); + goto cleanup; + } + } + + if (password) { + ret = _gnutls_hash(&hd, password, strlen(password)); + if (ret < 0) { + gnutls_assert(); + goto hash_err; + } + } + ret = _gnutls_hash(&hd, salt->data, 8); + if (ret < 0) { + gnutls_assert(); + goto hash_err; + } + + _gnutls_hash_deinit(&hd, md5); + + if (key->size - count <= sizeof(md5)) { + memcpy(&key->data[count], md5, key->size - count); + break; + } + + memcpy(&key->data[count], md5, sizeof(md5)); + count += sizeof(md5); + } + ret = 0; + + cleanup: + gnutls_free(password); + return ret; +} + +struct pem_cipher { + const char *name; + gnutls_cipher_algorithm_t cipher; +}; + +static const struct pem_cipher pem_ciphers[] = { + {"DES-CBC", GNUTLS_CIPHER_DES_CBC}, + {"DES-EDE3-CBC", GNUTLS_CIPHER_3DES_CBC}, + {"AES-128-CBC", GNUTLS_CIPHER_AES_128_CBC}, + {"AES-192-CBC", GNUTLS_CIPHER_AES_192_CBC}, + {"AES-256-CBC", GNUTLS_CIPHER_AES_256_CBC}, + {"CAMELLIA-128-CBC", GNUTLS_CIPHER_CAMELLIA_128_CBC}, + {"CAMELLIA-192-CBC", GNUTLS_CIPHER_CAMELLIA_192_CBC}, + {"CAMELLIA-256-CBC", GNUTLS_CIPHER_CAMELLIA_256_CBC}, +}; + +/** + * gnutls_x509_privkey_import_openssl: + * @key: The data to store the parsed key + * @data: The DER or PEM encoded key. + * @password: the password to decrypt the key (if it is encrypted). + * + * This function will convert the given PEM encrypted to + * the native gnutls_x509_privkey_t format. The + * output will be stored in @key. + * + * The @password should be in ASCII. If the password is not provided + * or wrong then %GNUTLS_E_DECRYPTION_FAILED will be returned. + * + * If the Certificate is PEM encoded it should have a header of + * "PRIVATE KEY" and the "DEK-Info" header. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a + * negative error value. + **/ +int +gnutls_x509_privkey_import_openssl(gnutls_x509_privkey_t key, + const gnutls_datum_t * data, + const char *password) +{ + gnutls_cipher_hd_t handle; + gnutls_cipher_algorithm_t cipher = GNUTLS_CIPHER_UNKNOWN; + gnutls_datum_t b64_data; + gnutls_datum_t salt, enc_key; + unsigned char *key_data; + size_t key_data_size; + const char *pem_header = (void *) data->data; + const char *pem_header_start = (void *) data->data; + ssize_t pem_header_size; + int ret; + unsigned int i, iv_size, l; + + pem_header_size = data->size; + + pem_header = + memmem(pem_header, pem_header_size, "PRIVATE KEY---", 14); + if (pem_header == NULL) { + gnutls_assert(); + return GNUTLS_E_PARSING_ERROR; + } + + pem_header_size -= (ptrdiff_t) (pem_header - pem_header_start); + + pem_header = memmem(pem_header, pem_header_size, "DEK-Info: ", 10); + if (pem_header == NULL) { + gnutls_assert(); + return GNUTLS_E_PARSING_ERROR; + } + + pem_header_size = + data->size - (ptrdiff_t) (pem_header - pem_header_start) - 10; + pem_header += 10; + + for (i = 0; i < sizeof(pem_ciphers) / sizeof(pem_ciphers[0]); i++) { + l = strlen(pem_ciphers[i].name); + if (!strncmp(pem_header, pem_ciphers[i].name, l) && + pem_header[l] == ',') { + pem_header += l + 1; + cipher = pem_ciphers[i].cipher; + break; + } + } + + if (cipher == GNUTLS_CIPHER_UNKNOWN) { + _gnutls_debug_log + ("Unsupported PEM encryption type: %.10s\n", + pem_header); + gnutls_assert(); + return GNUTLS_E_INVALID_REQUEST; + } + + iv_size = gnutls_cipher_get_iv_size(cipher); + salt.size = iv_size; + salt.data = gnutls_malloc(salt.size); + if (!salt.data) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + for (i = 0; i < salt.size * 2; i++) { + unsigned char x; + const char *c = &pem_header[i]; + + if (*c >= '0' && *c <= '9') + x = (*c) - '0'; + else if (*c >= 'A' && *c <= 'F') + x = (*c) - 'A' + 10; + else { + gnutls_assert(); + /* Invalid salt in encrypted PEM file */ + ret = GNUTLS_E_INVALID_REQUEST; + goto out_salt; + } + if (i & 1) + salt.data[i / 2] |= x; + else + salt.data[i / 2] = x << 4; + } + + pem_header += salt.size * 2; + if (*pem_header != '\r' && *pem_header != '\n') { + gnutls_assert(); + ret = GNUTLS_E_INVALID_REQUEST; + goto out_salt; + } + while (*pem_header == '\n' || *pem_header == '\r') + pem_header++; + + ret = + _gnutls_base64_decode((const void *) pem_header, + pem_header_size, &b64_data); + if (ret < 0) { + gnutls_assert(); + goto out_salt; + } + + if (b64_data.size < 16) { + /* Just to be sure our parsing is OK */ + gnutls_assert(); + ret = GNUTLS_E_PARSING_ERROR; + goto out_b64; + } + + enc_key.size = gnutls_cipher_get_key_size(cipher); + enc_key.data = gnutls_malloc(enc_key.size); + if (!enc_key.data) { + ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + goto out_b64; + } + + key_data_size = b64_data.size; + key_data = gnutls_malloc(key_data_size); + if (!key_data) { + ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + goto out_enc_key; + } + + while (1) { + memcpy(key_data, b64_data.data, key_data_size); + + ret = openssl_hash_password(password, &enc_key, &salt); + if (ret < 0) { + gnutls_assert(); + goto out; + } + + ret = gnutls_cipher_init(&handle, cipher, &enc_key, &salt); + if (ret < 0) { + gnutls_assert(); + gnutls_cipher_deinit(handle); + goto out; + } + + ret = + gnutls_cipher_decrypt(handle, key_data, key_data_size); + gnutls_cipher_deinit(handle); + + if (ret < 0) { + gnutls_assert(); + goto out; + } + + /* We have to strip any padding to accept it. + So a bit more ASN.1 parsing for us. */ + if (key_data[0] == 0x30) { + gnutls_datum_t key_datum; + unsigned int blocksize = + gnutls_cipher_get_block_size(cipher); + unsigned int keylen = key_data[1]; + unsigned int ofs = 2; + + if (keylen & 0x80) { + int lenlen = keylen & 0x7f; + keylen = 0; + + if (lenlen > 3) { + gnutls_assert(); + goto fail; + } + + while (lenlen) { + keylen <<= 8; + keylen |= key_data[ofs++]; + lenlen--; + } + } + keylen += ofs; + + /* If there appears to be more or less padding than required, fail */ + if (key_data_size - keylen > blocksize || key_data_size < keylen+1) { + gnutls_assert(); + goto fail; + } + + /* If the padding bytes aren't all equal to the amount of padding, fail */ + ofs = keylen; + while (ofs < key_data_size) { + if (key_data[ofs] != + key_data_size - keylen) { + gnutls_assert(); + goto fail; + } + ofs++; + } + + key_datum.data = key_data; + key_datum.size = keylen; + ret = + gnutls_x509_privkey_import(key, &key_datum, + GNUTLS_X509_FMT_DER); + if (ret == 0) + goto out; + } + fail: + ret = GNUTLS_E_DECRYPTION_FAILED; + goto out; + } + out: + zeroize_key(key_data, key_data_size); + gnutls_free(key_data); + out_enc_key: + _gnutls_free_key_datum(&enc_key); + out_b64: + gnutls_free(b64_data.data); + out_salt: + gnutls_free(salt.data); + return ret; +} |