/* * 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 3 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 * */ #include #include #include #include #include #include #include #include #include "x509_int.h" #include #include #include #include static int openssl_hash_password (const char *pass, gnutls_datum_t * key, gnutls_datum_t * salt) { unsigned char md5[16]; gnutls_hash_hd_t hash; unsigned int count = 0; int err; while (count < key->size) { err = gnutls_hash_init (&hash, GNUTLS_DIG_MD5); if (err) { gnutls_assert (); return err; } if (count) { err = gnutls_hash (hash, md5, sizeof (md5)); if (err) { hash_err: gnutls_hash_deinit (hash, NULL); gnutls_assert(); return err; } } if (pass) { err = gnutls_hash (hash, pass, strlen (pass)); if (err) goto hash_err; } err = gnutls_hash (hash, salt->data, 8); if (err) goto hash_err; gnutls_hash_deinit (hash, 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); } return 0; } static const struct pem_cipher { const char *name; gnutls_cipher_algorithm_t 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 structure 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 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; const char *pem_header = (void*)data->data; const char *pem_header_start = (void*)data->data; ssize_t pem_header_size; int ret, err; 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_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; } ret = GNUTLS_E_MEMORY_ERROR; enc_key.size = gnutls_cipher_get_key_size (cipher); enc_key.data = gnutls_malloc (enc_key.size); if (!enc_key.data) goto out_b64; key_data = gnutls_malloc (b64_data.size); if (!key_data) goto out_enc_key; while (1) { memcpy (key_data, b64_data.data, b64_data.size); ret = openssl_hash_password (password, &enc_key, &salt); if (ret) goto out; err = gnutls_cipher_init (&handle, cipher, &enc_key, &salt); if (err) { gnutls_assert(); gnutls_cipher_deinit (handle); ret = err; goto out; } err = gnutls_cipher_decrypt (handle, key_data, b64_data.size); gnutls_cipher_deinit (handle); if (err) { gnutls_assert(); ret = -err; 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) goto fail; while (lenlen) { keylen <<= 8; keylen |= key_data[ofs++]; lenlen--; } } keylen += ofs; /* If there appears to be more padding than required, fail */ if (b64_data.size - keylen >= blocksize) goto fail; /* If the padding bytes aren't all equal to the amount of padding, fail */ ofs = keylen; while (ofs < b64_data.size) { if (key_data[ofs] != b64_data.size - keylen) goto fail; ofs++; } key_datum.data = key_data; key_datum.size = keylen; err = gnutls_x509_privkey_import (key, &key_datum, GNUTLS_X509_FMT_DER); if (!err) { ret = 0; goto out; } } fail: ret = GNUTLS_E_DECRYPTION_FAILED; goto out; } out: gnutls_free (key_data); out_enc_key: gnutls_free (enc_key.data); out_b64: gnutls_free (b64_data.data); out_salt: gnutls_free (salt.data); return ret; }