/** * Copyright 2020-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include #include #include #include #include #include /* Expected CMS content versions. These versions denote * the current CMS structure so it is mandatory that we * stick with these two versions */ #define ENVELOPED_DATA_VERSION (2) #define ENVELOPED_DATA_RECIPIENT_VERSION (2) /* A helper wrapper function to facilitate function calls made to * CBS_get_any_ber_asn1_element. */ static inline int get_any_ber_asn1_element( CBS *cbs, CBS *out, CBS_ASN1_TAG *out_tag, size_t *out_header_len) { /* TODO: out_indefinite is dereferenced by CBS_get_any_ber_asn1_element, * but we don't need *out_indefinite right now. When we update * the parser's code, it needs to be refactored to use *out_indefinite * to distinguish indefinite from empty elements. */ int indefinite_temp; return CBS_get_any_ber_asn1_element(cbs, out, out_tag, out_header_len, NULL, &indefinite_temp); } /** * A highly specialized function that parses a BER-encoded CMS Enveloped Data * content stream and outputs specific entries required for decrypting content * in the KMS client side. * * NOTE: Always assumes RecipentInfo to have RSA-OAEP with SHA256 for envelope * encryption and AES-256-CBC for content encryption. * * @param[in] in_ber The serialized CMS content. * @param[out] cipherkey The symmetric key received in the RecipientInfo structure. * Caller has to decrypt this with the asymmetric public key. * @param[out] iv The IV received in the EncryptedContent structure. * @param[out] ciphertext The actual ciphertext present in the Encrypted Content. * Caller has to decrypt this with the symmetric @cipherkey and @iv. * * @return Returns the error code. If SUCCESS, the above parameters are valid. */ int aws_cms_parse_enveloped_data( struct aws_byte_buf *in_ber, struct aws_byte_buf *cipherkey, struct aws_byte_buf *iv, struct aws_byte_buf *ciphertext) { AWS_PRECONDITION(aws_byte_buf_is_valid(in_ber)); /* This function consumes BER encoded tags and gets relevant inner values * in accordance with the CMS General Syntax. Since we currently do not * have a usecase where KMS responses with multiple recipient keys, this * whole code assumes the RecipientInfo is a SET of MAX_SIZE = 1. * * CMS General Syntax * https://tools.ietf.org/html/rfc5652#section-3 */ CBS cms; CBS_init(&cms, in_ber->buffer, in_ber->len); /* Validate that this is PKCS#7 Enveloped Data type and version we support. * CMS PKCS#7 Enveloped Data * See https://tools.ietf.org/html/rfc5652#section-6.1 */ unsigned tag; size_t tag_size; CBS content_type; if (!get_any_ber_asn1_element(&cms, NULL, &tag, &tag_size) || /* ASN1_SEQ */ (tag != CBS_ASN1_SEQUENCE) || !CBS_get_asn1(&cms, &content_type, CBS_ASN1_OBJECT)) { goto err; } if (NID_pkcs7_enveloped != OBJ_cbs2nid(&content_type)) { goto err; } /* Validate the version */ CBS version; if (!get_any_ber_asn1_element(&cms, NULL, &tag, &tag_size) || /* ASN1_ENUM */ !get_any_ber_asn1_element(&cms, NULL, &tag, &tag_size) || (tag != CBS_ASN1_SEQUENCE) || /* ASN1_SEQ */ !CBS_get_asn1(&cms, &version, CBS_ASN1_INTEGER)) { goto err; } uint8_t env_ver = 0; if (!CBS_get_u8(&version, &env_ver) || env_ver != ENVELOPED_DATA_VERSION) { goto err; } /* CMS PKCS#7 Enveloped Data * See https://tools.ietf.org/html/rfc5652#section-6.1 */ CBS enveloped_data; if (!get_any_ber_asn1_element(&cms, &enveloped_data, &tag, &tag_size) || tag != CBS_ASN1_SET) { goto err; } /* Originator Info. Optional, but if present, consume it. * We currently have no interest in its contents. */ int has_originator; CBS originator_info; if (!CBS_get_optional_asn1(&enveloped_data, &originator_info, &has_originator, CBS_ASN1_SEQUENCE)) { goto err; } (void)has_originator; /* Recipient Info. Type: KeyTransRecipientInfo * See https://tools.ietf.org/html/rfc5652#section-6.2.1 */ CBS recipient_infos, recipient_info_data; uint64_t recipient_ver; if (!CBS_get_asn1(&enveloped_data, &recipient_infos, CBS_ASN1_SET) || !CBS_get_asn1(&recipient_infos, &recipient_info_data, CBS_ASN1_SEQUENCE) || !CBS_get_asn1_uint64(&recipient_info_data, &recipient_ver)) { goto err; } if (recipient_ver != ENVELOPED_DATA_RECIPIENT_VERSION) { /* Only subjectKeyIdentifier is supported */ goto err; } CBS recipient_encrypted_key; if (!CBS_get_any_asn1_element(&recipient_info_data, NULL, NULL, NULL) || /* RID */ !CBS_get_asn1(&recipient_info_data, NULL, CBS_ASN1_SEQUENCE) || /* Asymmetric ALGO. RSA-OAEP in this case. */ !CBS_get_asn1(&recipient_info_data, &recipient_encrypted_key, CBS_ASN1_OCTETSTRING)) { goto err; } const uint8_t *symmetric_key = CBS_data(&recipient_encrypted_key); size_t symmetric_key_len = CBS_len(&recipient_encrypted_key); /* Construct the encrypted symmetric key output buffer. */ struct aws_byte_cursor cursor = aws_byte_cursor_from_array(symmetric_key, symmetric_key_len); if (AWS_OP_SUCCESS != aws_byte_buf_init_copy_from_cursor(cipherkey, aws_nitro_enclaves_get_allocator(), cursor)) { goto err; } /* EncryptedContentInfo * See https://tools.ietf.org/html/rfc5652#section-6.1 */ CBS encrypted_content_type; if (!get_any_ber_asn1_element(&cms, NULL, &tag, &tag_size) || (tag != CBS_ASN1_SEQUENCE) || !CBS_get_asn1(&cms, &encrypted_content_type, CBS_ASN1_OBJECT)) { goto err; } /* Validate that this is PKCS#7 Data type */ if (NID_pkcs7_data != OBJ_cbs2nid(&encrypted_content_type)) { goto err; } /* Fetch the IV. * See https://tools.ietf.org/html/rfc5652#section-6.3 */ CBS content_encryption_algo, algo, iv_string; if (!get_any_ber_asn1_element(&cms, &content_encryption_algo, &tag, &tag_size) || tag != CBS_ASN1_SEQUENCE || !CBS_skip(&content_encryption_algo, tag_size) || !CBS_get_asn1(&content_encryption_algo, &algo, CBS_ASN1_OBJECT) || !CBS_get_asn1(&content_encryption_algo, &iv_string, CBS_ASN1_OCTETSTRING)) { goto err; } /* Validate that we have AES256-CBC in the Content */ if (NID_aes_256_cbc != OBJ_cbs2nid(&algo)) { goto err; } const uint8_t *iv_data = CBS_data(&iv_string); size_t iv_data_len = CBS_len(&iv_string); cursor = aws_byte_cursor_from_array(iv_data, iv_data_len); if (AWS_OP_SUCCESS != aws_byte_buf_init_copy_from_cursor(iv, aws_nitro_enclaves_get_allocator(), cursor)) { goto err; } CBB encrypted_content; /* Grow as much as needed. Do not limit KMS encrypted content size from here. */ if (!CBB_init(&encrypted_content, 0)) { goto err; } CBS wrapped_encrypted_content; /* Consume all the entries in the scattered list */ if (CBS_peek_asn1_tag(&cms, CBS_ASN1_CONTEXT_SPECIFIC) == 1) { /* Fixed length context specific IMPLICIT OCTETSTRING content. Not explicitly marked as a CBS_ASN1_OCTETSTRING. */ while (CBS_get_any_asn1(&cms, &wrapped_encrypted_content, &tag) == 1) /* ASN1_CONTEXT_SPECIFIC */ { if (!CBB_add_bytes( &encrypted_content, CBS_data(&wrapped_encrypted_content), CBS_len(&wrapped_encrypted_content))) { CBB_cleanup(&encrypted_content); goto err; } } } else { /* Indefinite-length explicit scattered OCTETSTRING content. Aggregate them if more than one. */ if (!get_any_ber_asn1_element(&cms, NULL, &tag, &tag_size)) { /* ASN1_ENUM */ CBB_cleanup(&encrypted_content); goto err; } /* Consume all the entries in the scattered list */ while (get_any_ber_asn1_element(&cms, &wrapped_encrypted_content, &tag, &tag_size) == 1 && tag == CBS_ASN1_OCTETSTRING) { CBS encrypted_content_part; if (!CBS_get_asn1(&wrapped_encrypted_content, &encrypted_content_part, CBS_ASN1_OCTETSTRING) || !CBB_add_bytes( &encrypted_content, CBS_data(&encrypted_content_part), CBS_len(&encrypted_content_part))) { CBB_cleanup(&encrypted_content); goto err; } } } /* Guaranteed to have at least one OCTETSTRING, so we should always have a valid CBB here */ const uint8_t *cipher_content = CBB_data(&encrypted_content); size_t cipher_content_len = CBB_len(&encrypted_content); cursor = aws_byte_cursor_from_array(cipher_content, cipher_content_len); if (AWS_OP_SUCCESS != aws_byte_buf_init_copy_from_cursor(ciphertext, aws_nitro_enclaves_get_allocator(), cursor)) { CBB_cleanup(&encrypted_content); goto err; } CBB_cleanup(&encrypted_content); return AWS_OP_SUCCESS; err: if (aws_byte_buf_is_valid(cipherkey)) { aws_byte_buf_clean_up_secure(cipherkey); } if (aws_byte_buf_is_valid(iv)) { aws_byte_buf_clean_up_secure(iv); } if (aws_byte_buf_is_valid(ciphertext)) { aws_byte_buf_clean_up_secure(ciphertext); } return AWS_OP_ERR; } /** * Symmetric one-shot decryption function. * * NOTE: Implicitly assumes AES-256-CBC cipher. * * @param[in] The ciphertext to decrypt. * @param[in] The symmetric key. * @param[in] The IV used initially for encryption. * @param[out] The plaintext. * * @return Returns the error code. If SUCCESS, the output plaintext is valid. */ int aws_cms_cipher_decrypt( struct aws_byte_buf *ciphertext, struct aws_byte_buf *key, struct aws_byte_buf *iv, struct aws_byte_buf *plaintext) { AWS_PRECONDITION(aws_byte_buf_is_valid(ciphertext)); AWS_PRECONDITION(aws_byte_buf_is_valid(key)); AWS_PRECONDITION(aws_byte_buf_is_valid(iv)); if (key->len != EVP_CIPHER_key_length(EVP_aes_256_cbc())) { return AWS_OP_ERR; } EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) { return AWS_OP_ERR; } /* Setup the decryption context */ if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key->buffer, iv->buffer)) { EVP_CIPHER_CTX_free(ctx); return AWS_OP_ERR; } /* Output: ciphertext_len + the block length minus one */ int ulen, flen; uint8_t out_text[ciphertext->len + EVP_CIPHER_CTX_block_size(ctx)]; if (!EVP_DecryptUpdate(ctx, out_text, &ulen, ciphertext->buffer, ciphertext->len) || !EVP_DecryptFinal_ex(ctx, &out_text[ulen], &flen)) { EVP_CIPHER_CTX_free(ctx); return AWS_OP_ERR; } /* Construct the plaintext output buffer. */ struct aws_byte_cursor cursor = aws_byte_cursor_from_array(out_text, ulen + flen); if (AWS_OP_SUCCESS != aws_byte_buf_init_copy_from_cursor(plaintext, aws_nitro_enclaves_get_allocator(), cursor)) { EVP_CIPHER_CTX_free(ctx); return AWS_OP_ERR; } EVP_CIPHER_CTX_free(ctx); return AWS_OP_SUCCESS; }