int _gnutls_fbase64_decode (const char *header, const uint8_t * data, size_t data_size, gnutls_datum_t* result) { int ret; static const char top[] = "-----BEGIN "; static const char bottom[] = "-----END "; uint8_t *rdata, *kdata; int rdata_size; char pem_header[128]; _gnutls_str_cpy (pem_header, sizeof (pem_header), top); if (header != NULL) _gnutls_str_cat (pem_header, sizeof (pem_header), header); rdata = memmem (data, data_size, pem_header, strlen (pem_header)); if (rdata == NULL) { gnutls_assert (); _gnutls_debug_log ("Could not find '%s'\n", pem_header); return GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR; } data_size -= (unsigned long int) rdata - (unsigned long int) data; if (data_size < 4 + strlen (bottom)) { gnutls_assert (); return GNUTLS_E_BASE64_DECODING_ERROR; } kdata = memmem (rdata + 1, data_size - 1, ENDSTR, sizeof (ENDSTR) - 1); /* allow CR as well. */ if (kdata == NULL) { gnutls_assert (); _gnutls_debug_log ("Could not find '%s'\n", ENDSTR); return GNUTLS_E_BASE64_DECODING_ERROR; } data_size -= strlen (ENDSTR); data_size -= (unsigned long int) kdata - (unsigned long int) rdata; rdata = kdata + strlen (ENDSTR); /* position is now after the ---BEGIN--- headers */ kdata = memmem (rdata, data_size, bottom, strlen (bottom)); if (kdata == NULL) { gnutls_assert (); return GNUTLS_E_BASE64_DECODING_ERROR; } /* position of kdata is before the ----END--- footer */ rdata_size = (unsigned long int) kdata - (unsigned long int) rdata; if (rdata_size < 4) { gnutls_assert (); return GNUTLS_E_BASE64_DECODING_ERROR; } if ((ret = _gnutls_base64_decode (rdata, rdata_size, result)) < 0) { gnutls_assert (); return GNUTLS_E_BASE64_DECODING_ERROR; } return ret; }
/** * 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 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; } 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) { 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 padding than required, fail */ if (key_data_size - keylen > blocksize) { 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; }