diff --git a/include/aws/nitro_enclaves/kms.h b/include/aws/nitro_enclaves/kms.h index d5fa7ed..66f1f25 100644 --- a/include/aws/nitro_enclaves/kms.h +++ b/include/aws/nitro_enclaves/kms.h @@ -48,6 +48,25 @@ enum aws_key_spec { AWS_KS_AES_128, }; +enum aws_message_type { + AWS_MT_UNINITIALIZED = -1, + AWS_MT_RAW, + AWS_MT_DIGEST, +}; + +enum aws_signing_algorithm { + AWS_SA_UNINITIALIZED = -1, + AWS_SA_RSASSA_PSS_SHA_256, + AWS_SA_RSASSA_PSS_SHA_384, + AWS_SA_RSASSA_PSS_SHA_512, + AWS_SA_RSASSA_PKCS1_V1_5_SHA_256, + AWS_SA_RSASSA_PKCS1_V1_5_SHA_384, + AWS_SA_RSASSA_PKCS1_V1_5_SHA_512, + AWS_SA_ECDSA_SHA_256, + AWS_SA_ECDSA_SHA_384, + AWS_SA_ECDSA_SHA_512, +}; + struct aws_recipient { struct aws_byte_buf public_key; @@ -335,6 +354,108 @@ struct aws_kms_generate_random_response { struct aws_allocator *const allocator; }; +struct aws_kms_sign_request { + /** + * Specifies the message or message digest to sign. Messages can be 0-4096 bytes. + * To sign a larger message, provide the message digest. + * + * If you provide a message, AWS KMS generates a hash digest of the message + * and then signs it. + * + * Required: Yes. + */ + struct aws_byte_buf message; + + /** + * Tells AWS KMS whether the value of the Message parameter is a message or + * message digest. The default value, RAW, indicates a message. To indicate + * a message digest, enter DIGEST. + * + * Required: No. + */ + enum aws_message_type message_type; + + /** + * A list of grant tokens. + * + * For more information, see + * Grant Tokens + * in the AWS Key Management Service Developer Guide. + * + * Required: No. + */ + struct aws_array_list grant_tokens; + + /** + * Identifies an asymmetric CMK. AWS KMS uses the private key in the asymmetric + * CMK to sign the message. The KeyUsage type of the CMK must be SIGN_VERIFY. + * To find the KeyUsage of a CMK, use the DescribeKey operation. + * + * Required: Yes. + */ + struct aws_string *key_id; + + /** + * Specifies the signing algorithm to use when signing the message. + * + * Choose an algorithm that is compatible with the type and size of the specified + * asymmetric CMK. + * + * Required: Yes. + */ + enum aws_signing_algorithm signing_algorithm; + + /** + * Allocator used for memory management of associated resources. + * + * Note that this is not part of the request. + */ + struct aws_allocator *const allocator; +}; + +struct aws_kms_sign_response { + /** + * ARN of the asymmetric CMK that was used to sign the message. This value is returned if no errors are + * encountered during the operation. + * + * Required: Yes. + */ + struct aws_string *key_id; + + /** + * The cryptographic signature that was generated for the message. + * + * When used with the supported RSA signing algorithms, the encoding of + * this value is defined by PKCS #1 in RFC 8017 (https://tools.ietf.org/html/rfc8017). + * + * When used with the ECDSA_SHA_256, ECDSA_SHA_384, or ECDSA_SHA_512 signing + * algorithms, this value is a DER-encoded object as defined by ANS X9.62–2005 + * and RFC 3279 Section 2.2.3 (https://tools.ietf.org/html/rfc3279#section-2.2.3). + * This is the most commonly used signature format and is appropriate for + * most uses. + * + * When you use the HTTP API or the AWS CLI, the value is Base64-encoded. Otherwise, + * it is not Base64-encoded. + * + * Required: Yes. + */ + struct aws_byte_buf signature; + + /** + * The signing algorithm that was used to sign the message. + * + * Required: No. + */ + enum aws_signing_algorithm signing_algorithm; + + /** + * Allocator used for memory management of associated resources. + * + * Note that this is not part of the response. + */ + struct aws_allocator *const allocator; +}; + struct aws_nitro_enclaves_kms_client_configuration { /* Optional. Will default to library allocator if NULL. */ struct aws_allocator *allocator; @@ -656,6 +777,90 @@ struct aws_kms_generate_random_response *aws_kms_generate_random_response_from_j AWS_NITRO_ENCLAVES_API void aws_kms_generate_random_response_destroy(struct aws_kms_generate_random_response *res); +/** + * Creates an aws_kms_sign_request structure. + * + * @param[in] allocator The allocator used for initialization. NULL for default. + * + * @return A new aws_kms_sign_request structure. + */ +AWS_NITRO_ENCLAVES_API +struct aws_kms_sign_request *aws_kms_sign_request_new(struct aws_allocator *allocator); + +/** + * Deallocate all internal data for a KMS Sign Request. + * + * @param[in] req The KMS Sign Request. + */ +AWS_NITRO_ENCLAVES_API +void aws_kms_sign_request_destroy(struct aws_kms_sign_request *req); + +/** + * Creates an aws_kms_sign_response structure. + * + * @param[in] allocator The allocator used for initialization. + * + * @return A new aws_kms_sign_response structure. + */ +AWS_NITRO_ENCLAVES_API +struct aws_kms_sign_response *aws_kms_sign_response_new(struct aws_allocator *allocator); + +/** + * Deallocate all internal data for a KMS Sign Response. + * + * @param[in] res The KMS Sign Response. + */ +AWS_NITRO_ENCLAVES_API +void aws_kms_sign_response_destroy(struct aws_kms_sign_response *res); + +/** + * Serializes a KMS Sign Request @ref aws_kms_sign_request to json. + * + * @note The request must contain the required @ref aws_kms_sign_request::ciphertext_blob parameter. + * + * @param[in] req The KMS Sign Request that is to be serialized. + * + * @return The serialized KMS Sign Request. + */ +AWS_NITRO_ENCLAVES_API +struct aws_string *aws_kms_sign_request_to_json(const struct aws_kms_sign_request *req); + +/** + * Deserializes a KMS Sign Request @ref aws_kms_sign_request from json. + * + * @param[in] allocator The allocator used for managing resource creation. NULL for default. + * @param[in] json The serialized json KMS Sign Request. + * + * @return A new aws_kms_sign_request structure. + */ +AWS_NITRO_ENCLAVES_API +struct aws_kms_sign_request *aws_kms_sign_request_from_json( + struct aws_allocator *allocator, + const struct aws_string *json); + +/** + * Serializes a KMS Sign Response @ref aws_kms_sign_response to json. + * + * @param[in] res The KMS Sign Response that is to be serialized. + * + * @return The serialized KMS Sign Response. + */ +AWS_NITRO_ENCLAVES_API +struct aws_string *aws_kms_sign_response_to_json(const struct aws_kms_sign_response *res); + +/** + * Deserializes a KMS Sign Response @ref aws_kms_sign_response from json. + * + * @param[in] allocator The allocator used for managing resource creation. NULL for default. + * @param[in] json The serialized json KMS Sign Response. + * + * @return A new aws_kms_sign_response structure. + */ +AWS_NITRO_ENCLAVES_API +struct aws_kms_sign_response *aws_kms_sign_response_from_json( + struct aws_allocator *allocator, + const struct aws_string *json); + /** * Create a default KMS client configuration. * Uses the library default allocator. @@ -728,6 +933,15 @@ int aws_kms_generate_random_blocking( uint32_t number_of_bytes, struct aws_byte_buf *plaintext /* TODO: err_reason */); +AWS_NITRO_ENCLAVES_API +int aws_kms_sign_blocking( + struct aws_nitro_enclaves_kms_client *client, + const struct aws_string *key_id, + enum aws_signing_algorithm signing_algorithm, + const struct aws_byte_buf *message, + enum aws_message_type message_type, + struct aws_byte_buf *signature /* TODO: err_reason */); + AWS_EXTERN_C_END #endif /* AWS_NITRO_ENCLAVES_KMS_H */ diff --git a/source/kms.c b/source/kms.c index b263ef2..ff8be0d 100644 --- a/source/kms.c +++ b/source/kms.c @@ -27,6 +27,10 @@ #define KMS_NUMBER_OF_BYTES "NumberOfBytes" #define KMS_KEY_SPEC "KeySpec" #define KMS_CUSTOM_KEY_STORE_ID "CustomKeyStoreId" +#define KMS_MESSAGE "Message" +#define KMS_MESSAGE_TYPE "MessageType" +#define KMS_SIGNING_ALGORITHM "SigningAlgorithm" +#define KMS_SIGNATURE "Signature" /** * Helper macro for safe comparing a C string with a C string literal. @@ -54,6 +58,25 @@ AWS_STATIC_STRING_FROM_LITERAL(s_aws_kea_rsaes_oaep_sha_256, "RSAES_OAEP_SHA_256 AWS_STATIC_STRING_FROM_LITERAL(s_aws_ks_aes_256, "AES_256"); AWS_STATIC_STRING_FROM_LITERAL(s_aws_ks_aes_128, "AES_128"); +/** + * Aws string values for the AWS Signing Algorithm used by KMS. + */ +AWS_STATIC_STRING_FROM_LITERAL(s_sa_rsassa_pss_sha_256, "RSASSA_PSS_SHA_256"); +AWS_STATIC_STRING_FROM_LITERAL(s_sa_rsassa_pss_sha_384, "RSASSA_PSS_SHA_384"); +AWS_STATIC_STRING_FROM_LITERAL(s_sa_rsassa_pss_sha_512, "RSASSA_PSS_SHA_512"); +AWS_STATIC_STRING_FROM_LITERAL(s_sa_rsassa_pkcs1_v1_5_sha_256, "RSASSA_PKCS1_V1_5_SHA_256"); +AWS_STATIC_STRING_FROM_LITERAL(s_sa_rsassa_pkcs1_v1_5_sha_384, "RSASSA_PKCS1_V1_5_SHA_384"); +AWS_STATIC_STRING_FROM_LITERAL(s_sa_rsassa_pkcs1_v1_5_sha_512, "RSASSA_PKCS1_V1_5_SHA_512"); +AWS_STATIC_STRING_FROM_LITERAL(s_sa_ecdsa_sha_256, "ECDSA_SHA_256"); +AWS_STATIC_STRING_FROM_LITERAL(s_sa_ecdsa_sha_384, "ECDSA_SHA_384"); +AWS_STATIC_STRING_FROM_LITERAL(s_sa_ecdsa_sha_512, "ECDSA_SHA_512"); + +/** + * Aws string values for the AWS Message Type used by KMS. + */ +AWS_STATIC_STRING_FROM_LITERAL(s_mt_raw, "RAW"); +AWS_STATIC_STRING_FROM_LITERAL(s_mt_digest, "DIGEST"); + /** * Initializes a @ref aws_encryption_algorithm from string. * @@ -2045,6 +2068,553 @@ void aws_kms_generate_random_response_destroy(struct aws_kms_generate_random_res aws_mem_release(res->allocator, res); } +/** + * Initializes a @ref aws_signing_algorithm from string. + * + * @param[in] str The string used to initialize the signing algorithm. + * @param[out] signing_algorithm The initialized signing algorithm. + * + * @return True if the string is valid, false otherwise. + */ +static bool s_aws_signing_algorithm_from_aws_string( + const struct aws_string *str, + enum aws_signing_algorithm *signing_algorithm) { + + AWS_PRECONDITION(aws_string_c_str(str)); + AWS_PRECONDITION(signing_algorithm); + + if (aws_string_compare(str, s_sa_rsassa_pss_sha_256) == 0) { + *signing_algorithm = AWS_SA_RSASSA_PSS_SHA_256; + return true; + } + + if (aws_string_compare(str, s_sa_rsassa_pss_sha_384) == 0) { + *signing_algorithm = AWS_SA_RSASSA_PSS_SHA_384; + return true; + } + + if (aws_string_compare(str, s_sa_rsassa_pss_sha_512) == 0) { + *signing_algorithm = AWS_SA_RSASSA_PSS_SHA_512; + return true; + } + + if (aws_string_compare(str, s_sa_rsassa_pkcs1_v1_5_sha_256) == 0) { + *signing_algorithm = AWS_SA_RSASSA_PKCS1_V1_5_SHA_256; + return true; + } + + if (aws_string_compare(str, s_sa_rsassa_pkcs1_v1_5_sha_384) == 0) { + *signing_algorithm = AWS_SA_RSASSA_PKCS1_V1_5_SHA_384; + return true; + } + + if (aws_string_compare(str, s_sa_rsassa_pkcs1_v1_5_sha_512) == 0) { + *signing_algorithm = AWS_SA_RSASSA_PKCS1_V1_5_SHA_512; + return true; + } + + if (aws_string_compare(str, s_sa_ecdsa_sha_256) == 0) { + *signing_algorithm = AWS_SA_ECDSA_SHA_256; + return true; + } + + if (aws_string_compare(str, s_sa_ecdsa_sha_384) == 0) { + *signing_algorithm = AWS_SA_ECDSA_SHA_384; + return true; + } + + if (aws_string_compare(str, s_sa_ecdsa_sha_512) == 0) { + *signing_algorithm = AWS_SA_ECDSA_SHA_512; + return true; + } + + return false; +} + +/** + * Obtains the string representation of a @ref aws_signing_algorithm. + * + * @param[int] signing_algorithm The signing algorithm that is converted to string. + * + * @return A string representing the encryption algorithm. + */ +static const struct aws_string *s_aws_signing_algorithm_to_aws_string( + enum aws_signing_algorithm signing_algorithm) { + + switch (signing_algorithm) { + case AWS_SA_RSASSA_PSS_SHA_256: + return s_sa_rsassa_pss_sha_256; + case AWS_SA_RSASSA_PSS_SHA_384: + return s_sa_rsassa_pss_sha_384; + case AWS_SA_RSASSA_PSS_SHA_512: + return s_sa_rsassa_pss_sha_512; + case AWS_SA_RSASSA_PKCS1_V1_5_SHA_256: + return s_sa_rsassa_pkcs1_v1_5_sha_256; + case AWS_SA_RSASSA_PKCS1_V1_5_SHA_384: + return s_sa_rsassa_pkcs1_v1_5_sha_384; + case AWS_SA_RSASSA_PKCS1_V1_5_SHA_512: + return s_sa_rsassa_pkcs1_v1_5_sha_512; + case AWS_SA_ECDSA_SHA_256: + return s_sa_ecdsa_sha_256; + case AWS_SA_ECDSA_SHA_384: + return s_sa_ecdsa_sha_384; + case AWS_SA_ECDSA_SHA_512: + return s_sa_ecdsa_sha_512; + + case AWS_SA_UNINITIALIZED: + default: + return NULL; + } +} + +/** + * Initializes a @ref aws_message_type from string. + * + * @param[in] str The string used to initialize the message type. + * @param[out] mt The initialized message type. + * + * @return True if the string is valid, false otherwise. + */ +static bool s_aws_message_type_from_aws_string(const struct aws_string *str, enum aws_message_type *mt) { + AWS_PRECONDITION(aws_string_c_str(str)); + AWS_PRECONDITION(mt); + + if (aws_string_compare(str, s_mt_raw) == 0) { + *mt = AWS_MT_RAW; + return true; + } + + if (aws_string_compare(str, s_mt_digest) == 0) { + *mt = AWS_MT_DIGEST; + return true; + } + + return false; +} + +/** + * Obtains the string representation of a @ref aws_message_type. + * + * @param[int] message_type The message type that is converted to string. + * + * @return A string representing the message type. + */ +static const struct aws_string *s_aws_message_type_to_aws_string( + enum aws_message_type message_type) { + + switch (message_type) { + case AWS_MT_RAW: + return s_mt_raw; + case AWS_MT_DIGEST: + return s_mt_digest; + + case AWS_MT_UNINITIALIZED: + default: + return NULL; + } +} + +struct aws_kms_sign_request *aws_kms_sign_request_new(struct aws_allocator *allocator) { + if (allocator == NULL) { + allocator = aws_nitro_enclaves_get_allocator(); + } + + AWS_PRECONDITION(aws_allocator_is_valid(allocator)); + + struct aws_kms_sign_request *request = aws_mem_calloc(allocator, 1, sizeof(struct aws_kms_sign_request)); + if (request == NULL) { + return NULL; + } + + request->signing_algorithm = AWS_SA_UNINITIALIZED; + request->message_type = AWS_MT_UNINITIALIZED; + + /* Ensure allocator constness for customer usage. Utilize the @ref aws_string pattern. */ + *(struct aws_allocator **)(&request->allocator) = allocator; + + return request; +} + +void aws_kms_sign_request_destroy(struct aws_kms_sign_request *req) { + if (req == NULL) { + return; + } + AWS_PRECONDITION(req); + AWS_PRECONDITION(aws_allocator_is_valid(req->allocator)); + + if (aws_byte_buf_is_valid(&req->message)) { + aws_byte_buf_clean_up_secure(&req->message); + } + + if (aws_string_is_valid(req->key_id)) { + aws_string_destroy(req->key_id); + } + + if (aws_array_list_is_valid(&req->grant_tokens)) { + for (size_t i = 0; i < aws_array_list_length(&req->grant_tokens); i++) { + struct aws_string *elem = NULL; + AWS_FATAL_ASSERT(aws_array_list_get_at(&req->grant_tokens, &elem, i) == AWS_OP_SUCCESS); + + aws_string_destroy(elem); + } + + aws_array_list_clean_up(&req->grant_tokens); + } + + aws_mem_release(req->allocator, req); +} + +struct aws_kms_sign_response *aws_kms_sign_response_new(struct aws_allocator *allocator) { + if (allocator == NULL) { + allocator = aws_nitro_enclaves_get_allocator(); + } + + AWS_PRECONDITION(aws_allocator_is_valid(allocator)); + + struct aws_kms_sign_response *response = aws_mem_calloc(allocator, 1, sizeof(struct aws_kms_sign_response)); + if (response == NULL) { + return NULL; + } + + response->signing_algorithm = AWS_SA_UNINITIALIZED; + + /* Ensure allocator constness for customer usage. Utilize the @ref aws_string pattern. */ + *(struct aws_allocator **)(&response->allocator) = allocator; + + return response; +} + +void aws_kms_sign_response_destroy(struct aws_kms_sign_response *res) { + if (res == NULL) { + return; + } + AWS_PRECONDITION(res); + AWS_PRECONDITION(aws_allocator_is_valid(res->allocator)); + + if (aws_string_is_valid(res->key_id)) { + aws_string_destroy(res->key_id); + } + + if (aws_byte_buf_is_valid(&res->signature)) { + aws_byte_buf_clean_up_secure(&res->signature); + } + + aws_mem_release(res->allocator, res); +} + +struct aws_string *aws_kms_sign_request_to_json(const struct aws_kms_sign_request *req) { + AWS_PRECONDITION(req); + AWS_PRECONDITION(aws_allocator_is_valid(req->allocator)); + AWS_PRECONDITION(aws_byte_buf_is_valid(&req->message)); + + struct json_object *obj = json_object_new_object(); + if (obj == NULL) { + return NULL; + } + + /* Required parameters. */ + if (req->message.buffer == NULL) { + goto clean_up; + } + if (s_aws_byte_buf_to_base64_json(req->allocator, obj, KMS_MESSAGE, &req->message) != AWS_OP_SUCCESS) { + goto clean_up; + } + + const struct aws_string *signing_algorithm = s_aws_signing_algorithm_to_aws_string(req->signing_algorithm); + if (signing_algorithm == NULL) { + goto clean_up; + } + if (s_string_to_json(obj, KMS_SIGNING_ALGORITHM, aws_string_c_str(signing_algorithm)) != AWS_OP_SUCCESS) { + goto clean_up; + } + + if (req->key_id == NULL) { + goto clean_up; + } + if (s_string_to_json(obj, KMS_KEY_ID, aws_string_c_str(req->key_id)) != AWS_OP_SUCCESS) { + goto clean_up; + } + + /* Optional parameters. */ + if (req->message_type != AWS_MT_UNINITIALIZED) { + const struct aws_string *message_type = s_aws_message_type_to_aws_string(req->message_type); + if (message_type == NULL) { + goto clean_up; + } + if (s_string_to_json(obj, KMS_MESSAGE_TYPE, aws_string_c_str(message_type)) != AWS_OP_SUCCESS) { + goto clean_up; + } + } + + if (aws_array_list_is_valid(&req->grant_tokens) && aws_array_list_length(&req->grant_tokens) != 0) { + if (s_aws_array_list_to_json(obj, KMS_GRANT_TOKENS, &req->grant_tokens) != AWS_OP_SUCCESS) { + goto clean_up; + } + } + + struct aws_string *json = s_aws_string_from_json(req->allocator, obj); + if (json == NULL) { + goto clean_up; + } + + json_object_put(obj); + + return json; + +clean_up: + json_object_put(obj); + + return NULL; +} + +struct aws_kms_sign_request *aws_kms_sign_request_from_json( + struct aws_allocator *allocator, + const struct aws_string *json) { + + if (allocator == NULL) { + allocator = aws_nitro_enclaves_get_allocator(); + } + + AWS_PRECONDITION(aws_allocator_is_valid(allocator)); + AWS_PRECONDITION(aws_string_is_valid(json)); + + struct json_object *obj = s_json_object_from_string(json); + if (obj == NULL) { + return NULL; + } + + struct aws_kms_sign_request *req = aws_kms_sign_request_new(allocator); + if (req == NULL) { + json_object_put(obj); + return NULL; + } + + struct json_object_iterator it_end = json_object_iter_end(obj); + for (struct json_object_iterator it = json_object_iter_begin(obj); !json_object_iter_equal(&it, &it_end); + json_object_iter_next(&it)) { + const char *key = json_object_iter_peek_name(&it); + struct json_object *value = json_object_iter_peek_value(&it); + int value_type = json_object_get_type(value); + + if (value_type == json_type_string) { + if (AWS_SAFE_COMPARE(key, KMS_MESSAGE)) { + if (s_aws_byte_buf_from_base64_json(allocator, value, &req->message) != AWS_OP_SUCCESS) { + goto clean_up; + } + continue; + } + + if (AWS_SAFE_COMPARE(key, KMS_KEY_ID)) { + req->key_id = s_aws_string_from_json(allocator, value); + if (req->key_id == NULL) { + goto clean_up; + } + continue; + } + + if (AWS_SAFE_COMPARE(key, KMS_SIGNING_ALGORITHM)) { + struct aws_string *str = s_aws_string_from_json(allocator, value); + if (str == NULL) { + goto clean_up; + } + + if (!s_aws_signing_algorithm_from_aws_string(str, &req->signing_algorithm)) { + aws_string_destroy(str); + goto clean_up; + } + + aws_string_destroy(str); + continue; + } + + if (AWS_SAFE_COMPARE(key, KMS_MESSAGE_TYPE)) { + struct aws_string *str = s_aws_string_from_json(allocator, value); + if (str == NULL) { + goto clean_up; + } + + if (!s_aws_message_type_from_aws_string(str, &req->message_type)) { + aws_string_destroy(str); + goto clean_up; + } + + aws_string_destroy(str); + continue; + } + + /* Unexpected key for string type. */ + goto clean_up; + } + + if (value_type == json_type_array) { + if (AWS_SAFE_COMPARE(key, KMS_GRANT_TOKENS)) { + if (s_aws_array_list_from_json(allocator, value, &req->grant_tokens) != AWS_OP_SUCCESS) { + goto clean_up; + } + continue; + } + + /* Unexpected key for array type. */ + goto clean_up; + } + + /* Unexpected value type. */ + goto clean_up; + } + + /* Validate required parameters. */ + if (req->message.buffer == NULL || !aws_byte_buf_is_valid(&req->message)) { + goto clean_up; + } + if (req->signing_algorithm == AWS_SA_UNINITIALIZED) { + goto clean_up; + } + if (req->key_id == NULL) { + goto clean_up; + } + + json_object_put(obj); + + return req; + +clean_up: + json_object_put(obj); + aws_kms_sign_request_destroy(req); + + return NULL; +} + +struct aws_string *aws_kms_sign_response_to_json(const struct aws_kms_sign_response *res) { + AWS_PRECONDITION(res); + AWS_PRECONDITION(aws_allocator_is_valid(res->allocator)); + AWS_PRECONDITION(aws_string_is_valid(res->key_id)); + + struct json_object *obj = json_object_new_object(); + if (obj == NULL) { + return NULL; + } + + /* Required parameters. */ + if (res->key_id == NULL){ + goto clean_up; + } + if (s_string_to_json(obj, KMS_KEY_ID, aws_string_c_str(res->key_id)) != AWS_OP_SUCCESS) { + goto clean_up; + } + + if (res->signature.buffer != NULL) { + if (s_aws_byte_buf_to_base64_json(res->allocator, obj, KMS_SIGNATURE, &res->signature) != AWS_OP_SUCCESS) { + goto clean_up; + } + } + + /* Optional parameter. */ + if (res->signing_algorithm != AWS_SA_UNINITIALIZED) + { + const struct aws_string *signing_algorithm = s_aws_signing_algorithm_to_aws_string(res->signing_algorithm); + if (signing_algorithm == NULL) { + goto clean_up; + } + if (s_string_to_json(obj, KMS_SIGNING_ALGORITHM, aws_string_c_str(signing_algorithm)) != AWS_OP_SUCCESS) { + goto clean_up; + } + } + + struct aws_string *json = s_aws_string_from_json(res->allocator, obj); + if (json == NULL) { + goto clean_up; + } + + json_object_put(obj); + return json; + +clean_up: + json_object_put(obj); + return NULL; +} + +struct aws_kms_sign_response *aws_kms_sign_response_from_json( + struct aws_allocator *allocator, + const struct aws_string *json) { + + if (allocator == NULL) { + allocator = aws_nitro_enclaves_get_allocator(); + } + + AWS_PRECONDITION(aws_allocator_is_valid(allocator)); + AWS_PRECONDITION(aws_string_is_valid(json)); + + struct json_object *obj = s_json_object_from_string(json); + if (obj == NULL) { + return NULL; + } + + struct aws_kms_sign_response *response = aws_kms_sign_response_new(allocator); + if (response == NULL) { + json_object_put(obj); + return NULL; + } + + struct json_object_iterator it_end = json_object_iter_end(obj); + for (struct json_object_iterator it = json_object_iter_begin(obj); !json_object_iter_equal(&it, &it_end); + json_object_iter_next(&it)) { + const char *key = json_object_iter_peek_name(&it); + struct json_object *value = json_object_iter_peek_value(&it); + int value_type = json_object_get_type(value); + + if (value_type != json_type_string) { + goto clean_up; + } + + if (AWS_SAFE_COMPARE(key, KMS_KEY_ID)) { + response->key_id = s_aws_string_from_json(allocator, value); + if (response->key_id == NULL) { + goto clean_up; + } + continue; + } + + if (AWS_SAFE_COMPARE(key, KMS_SIGNATURE)) { + if (s_aws_byte_buf_from_base64_json(allocator, value, &response->signature) != AWS_OP_SUCCESS) { + goto clean_up; + } + continue; + } + + if (AWS_SAFE_COMPARE(key, KMS_SIGNING_ALGORITHM)) { + struct aws_string *str = s_aws_string_from_json(allocator, value); + if (str == NULL) { + goto clean_up; + } + + if (!s_aws_signing_algorithm_from_aws_string(str, &response->signing_algorithm)) { + aws_string_destroy(str); + goto clean_up; + } + + aws_string_destroy(str); + continue; + } + + goto clean_up; + } + + /* Validate required parameters. */ + if (!aws_string_is_valid(response->key_id)) { + goto clean_up; + } + + json_object_put(obj); + + return response; + +clean_up: + json_object_put(obj); + aws_kms_sign_response_destroy(response); + + return NULL; +} + AWS_STATIC_STRING_FROM_LITERAL(s_kms_string, "kms"); struct aws_nitro_enclaves_kms_client_configuration *aws_nitro_enclaves_kms_client_config_default( @@ -2237,6 +2807,7 @@ static struct aws_byte_cursor kms_target_generate_data_key = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("TrentService.GenerateDataKey"); static struct aws_byte_cursor kms_target_generate_random = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("TrentService.GenerateRandom"); +static struct aws_byte_cursor kms_target_sign = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("TrentService.Sign"); int aws_kms_decrypt_blocking( struct aws_nitro_enclaves_kms_client *client, @@ -2441,3 +3012,70 @@ int aws_kms_generate_random_blocking( aws_string_destroy(response); return AWS_OP_ERR; } + +int aws_kms_sign_blocking( + struct aws_nitro_enclaves_kms_client *client, + const struct aws_string *key_id, + enum aws_signing_algorithm signing_algorithm, + const struct aws_byte_buf *message, + enum aws_message_type message_type, + struct aws_byte_buf *signature /* TODO: err_reason */) { + AWS_PRECONDITION(client != NULL); + AWS_PRECONDITION(key_id != NULL); + AWS_PRECONDITION(signing_algorithm != AWS_SA_UNINITIALIZED); + AWS_PRECONDITION(message != NULL); + AWS_PRECONDITION(signature != NULL); + + struct aws_string *response = NULL; + struct aws_string *request = NULL; + struct aws_kms_sign_response *response_structure = NULL; + struct aws_kms_sign_request *request_structure = NULL; + int rc = 0; + + request_structure = aws_kms_sign_request_new(client->allocator); + if (request_structure == NULL) { + return AWS_OP_ERR; + } + + aws_byte_buf_init_copy(&request_structure->message, client->allocator, message); + request_structure->key_id = aws_string_clone_or_reuse(client->allocator, key_id); + request_structure->signing_algorithm = signing_algorithm; + request_structure->message_type = message_type; + + request = aws_kms_sign_request_to_json(request_structure); + if (request == NULL) { + goto err_clean; + } + + rc = s_aws_nitro_enclaves_kms_client_call_blocking(client, kms_target_sign, request, &response); + if (rc != 200) { + fprintf(stderr, "Got non-200 answer from KMS: %d\n", rc); + goto err_clean; + } + + response_structure = aws_kms_sign_response_from_json(client->allocator, response); + if (response_structure == NULL) { + fprintf(stderr, "Could not read response from KMS: %d\n", rc); + goto err_clean; + } + + /* + rc = s_decrypt_ciphertext_for_recipient( + client->allocator, &response_structure->ciphertext_for_recipient, client->keypair, signature); + */ + rc = AWS_OP_SUCCESS; + + aws_byte_buf_init_copy(signature, client->allocator, &response_structure->signature); + aws_kms_sign_request_destroy(request_structure); + aws_kms_sign_response_destroy(response_structure); + aws_string_destroy(request); + aws_string_destroy(response); + + return rc; +err_clean: + aws_kms_sign_request_destroy(request_structure); + aws_kms_sign_response_destroy(response_structure); + aws_string_destroy(request); + aws_string_destroy(response); + return AWS_OP_ERR; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0712af6..fe40b39 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,6 +29,10 @@ add_test_case(test_kms_generate_random_request_to_json) add_test_case(test_kms_generate_random_request_from_json) add_test_case(test_kms_generate_random_response_to_json) add_test_case(test_kms_generate_random_response_from_json) +add_test_case(test_kms_sign_request_to_json) +add_test_case(test_kms_sign_request_from_json) +add_test_case(test_kms_sign_response_to_json) +add_test_case(test_kms_sign_response_from_json) add_test_case(test_basic_rest_client) add_test_case(test_rest_call_blocking) add_test_case(test_cms_parsing_correctness) diff --git a/tests/kms_test.c b/tests/kms_test.c index 731f269..e5ec467 100644 --- a/tests/kms_test.c +++ b/tests/kms_test.c @@ -15,6 +15,10 @@ #define ENCRYPTION_ALGORITHM_SHA_256 "RSAES_OAEP_SHA_256" #define CIPHERTEXT_BLOB_DATA "Hello" #define CIPHERTEXT_BLOB_BASE64 "SGVsbG8=" +#define MESSAGE_DATA "Hello" +#define MESSAGE_BASE64 "SGVsbG8=" +#define SIGNATURE_DATA "Hello" +#define SIGNATURE_BASE64 "SGVsbG8=" #define TOKEN_FIRST "TokenFirst" #define TOKEN_SECOND "TokenSecond" #define ENCRYPTION_CONTEXT_KEY "EncryptionContextKey" @@ -23,6 +27,8 @@ #define KEA_RSAES_PKCS1_V1_5 "RSAES_PKCS1_V1_5" #define KEA_RSAES_OAEP_SHA_1 "RSAES_OAEP_SHA_1" #define KEA_RSAES_OAEP_SHA_256 "RSAES_OAEP_SHA_256" +#define SIGNING_ALGORITHM "RSASSA_PKCS1_V1_5_SHA_256" +#define MESSAGE_TYPE "RAW" #define KS_AES_256 "AES_256" AWS_TEST_CASE(test_kms_decrypt_request_cipher_to_json, s_test_kms_decrypt_request_cipher_to_json) @@ -1233,3 +1239,152 @@ static int s_test_kms_generate_random_response_from_json(struct aws_allocator *a return SUCCESS; } + +AWS_TEST_CASE(test_kms_sign_request_to_json, s_test_kms_sign_request_to_json) +static int s_test_kms_sign_request_to_json(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_kms_sign_request *request = aws_kms_sign_request_new(allocator); + ASSERT_NOT_NULL(request); + + ASSERT_SUCCESS(aws_byte_buf_init_copy_from_cursor( + &request->message, allocator, aws_byte_cursor_from_c_str(MESSAGE_DATA))); + + request->key_id = aws_string_new_from_c_str(allocator, KEY_ID); + ASSERT_NOT_NULL(request->key_id); + + request->message_type = AWS_MT_RAW; + request->signing_algorithm = AWS_SA_RSASSA_PKCS1_V1_5_SHA_256; + + ASSERT_SUCCESS(aws_array_list_init_dynamic(&request->grant_tokens, allocator, 2, sizeof(struct aws_string *))); + struct aws_string *token_first = aws_string_new_from_c_str(allocator, TOKEN_FIRST); + ASSERT_NOT_NULL(token_first); + struct aws_string *token_second = aws_string_new_from_c_str(allocator, TOKEN_SECOND); + ASSERT_NOT_NULL(token_second); + ASSERT_SUCCESS(aws_array_list_push_back(&request->grant_tokens, &token_first)); + ASSERT_SUCCESS(aws_array_list_push_back(&request->grant_tokens, &token_second)); + + struct aws_string *json = aws_kms_sign_request_to_json(request); + ASSERT_NOT_NULL(json); + + struct aws_string *expected = aws_string_new_from_c_str( + allocator, + "{ \"Message\": \"" MESSAGE_BASE64 "\", " + "\"SigningAlgorithm\": \"" SIGNING_ALGORITHM "\", " + "\"KeyId\": \"" KEY_ID "\", " + "\"MessageType\": \"" MESSAGE_TYPE "\", " + "\"GrantTokens\": [ \"" TOKEN_FIRST "\", \"" TOKEN_SECOND "\" ] }"); + ASSERT_NOT_NULL(expected); + ASSERT_STR_EQUALS(aws_string_c_str(expected), aws_string_c_str(json)); + aws_string_destroy(expected); + aws_string_destroy(json); + aws_kms_sign_request_destroy(request); + + return SUCCESS; +} + +AWS_TEST_CASE(test_kms_sign_request_from_json, s_test_kms_sign_request_from_json) +static int s_test_kms_sign_request_from_json(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + /* Add Key ID to the JSON. */ + struct aws_string *json = aws_string_new_from_c_str( + allocator, + "{ \"Message\": \"" MESSAGE_BASE64 "\", " + "\"SigningAlgorithm\": \"" SIGNING_ALGORITHM "\", " + "\"KeyId\": \"" KEY_ID "\", " + "\"MessageType\": \"" MESSAGE_TYPE "\", " + "\"GrantTokens\": [ \"" TOKEN_FIRST "\", \"" TOKEN_SECOND "\" ] }"); + struct aws_kms_sign_request *request = aws_kms_sign_request_from_json(allocator, json); + ASSERT_NOT_NULL(request); + + ASSERT_BIN_ARRAYS_EQUALS( + MESSAGE_DATA, + sizeof(MESSAGE_DATA) - 1, + (char *)request->message.buffer, + request->message.len); + ASSERT_INT_EQUALS(request->message_type, AWS_MT_RAW); + ASSERT_INT_EQUALS(request->signing_algorithm, AWS_SA_RSASSA_PKCS1_V1_5_SHA_256); + ASSERT_INT_EQUALS(2, aws_array_list_length(&request->grant_tokens)); + struct aws_string *elem = NULL; + AWS_FATAL_ASSERT(aws_array_list_get_at(&request->grant_tokens, &elem, 0) == AWS_OP_SUCCESS); + ASSERT_STR_EQUALS(TOKEN_FIRST, aws_string_c_str(elem)); + AWS_FATAL_ASSERT(aws_array_list_get_at(&request->grant_tokens, &elem, 1) == AWS_OP_SUCCESS); + ASSERT_STR_EQUALS(TOKEN_SECOND, aws_string_c_str(elem)); + ASSERT_STR_EQUALS(KEY_ID, aws_string_c_str(request->key_id)); + + /* Ensure we can serialize back to a JSON. */ + struct aws_string *json_second = aws_kms_sign_request_to_json(request); + ASSERT_NOT_NULL(json_second); + ASSERT_STR_EQUALS(aws_string_c_str(json), aws_string_c_str(json_second)); + + aws_string_destroy(json); + aws_string_destroy(json_second); + aws_kms_sign_request_destroy(request); + + return SUCCESS; +} + +AWS_TEST_CASE(test_kms_sign_response_to_json, s_test_kms_sign_response_to_json) +static int s_test_kms_sign_response_to_json(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_kms_sign_response *response = aws_kms_sign_response_new(allocator); + ASSERT_NOT_NULL(response); + + response->key_id = aws_string_new_from_c_str(allocator, KEY_ID); + ASSERT_NOT_NULL(response->key_id); + + ASSERT_SUCCESS(aws_byte_buf_init_copy_from_cursor( + &response->signature, allocator, aws_byte_cursor_from_c_str(SIGNATURE_DATA))); + + response->signing_algorithm = AWS_SA_RSASSA_PKCS1_V1_5_SHA_256; + + struct aws_string *json = aws_kms_sign_response_to_json(response); + ASSERT_NOT_NULL(json); + + struct aws_string *expected = aws_string_new_from_c_str( + allocator, + "{ \"KeyId\": \"" KEY_ID "\", " + "\"Signature\": \"" SIGNATURE_BASE64 "\", " + "\"SigningAlgorithm\": \"" SIGNING_ALGORITHM "\" }"); + ASSERT_NOT_NULL(expected); + ASSERT_STR_EQUALS(aws_string_c_str(expected), aws_string_c_str(json)); + aws_string_destroy(expected); + aws_string_destroy(json); + aws_kms_sign_response_destroy(response); + + return SUCCESS; +} + +AWS_TEST_CASE(test_kms_sign_response_from_json, s_test_kms_sign_response_from_json) +static int s_test_kms_sign_response_from_json(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + struct aws_string *json = aws_string_new_from_c_str( + allocator, + "{ \"KeyId\": \"" KEY_ID "\", " + "\"Signature\": \"" SIGNATURE_BASE64 "\", " + "\"SigningAlgorithm\": \"" SIGNING_ALGORITHM "\" }"); + ASSERT_NOT_NULL(json); + + struct aws_kms_sign_response *response = aws_kms_sign_response_from_json(allocator, json); + ASSERT_NOT_NULL(response); + ASSERT_BIN_ARRAYS_EQUALS( + SIGNATURE_DATA, + sizeof(SIGNATURE_DATA) - 1, + (char *)response->signature.buffer, + response->signature.len); + ASSERT_INT_EQUALS(response->signing_algorithm, AWS_SA_RSASSA_PKCS1_V1_5_SHA_256); + + /* Ensure we can serialize back to a JSON. */ + struct aws_string *json_second = aws_kms_sign_response_to_json(response); + ASSERT_NOT_NULL(json_second); + ASSERT_STR_EQUALS(aws_string_c_str(json), aws_string_c_str(json_second)); + + aws_string_destroy(json); + aws_string_destroy(json_second); + aws_kms_sign_response_destroy(response); + + return SUCCESS; +} \ No newline at end of file