diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2020-11-05 16:14:13 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-11-06 22:24:36 +0000 |
commit | dcdb25c472827dc6ccab23c15b14c415f74c5fb3 (patch) | |
tree | aef1abfa3ca931ed336eeeb0a4472275ff74b396 | |
parent | 56833933c266fd88dd4e6683d2dbd85616ffe673 (diff) | |
download | mongo-dcdb25c472827dc6ccab23c15b14c415f74c5fb3.tar.gz |
SERVER-52644 Update kms-message for gcp and azure support
40 files changed, 1705 insertions, 83 deletions
diff --git a/src/third_party/kms-message/README.md b/src/third_party/kms-message/README.md new file mode 100644 index 00000000000..b1242787f00 --- /dev/null +++ b/src/third_party/kms-message/README.md @@ -0,0 +1,43 @@ +This Repository is NOT a supported MongoDB product + +# kms-message +Library used to generate requests for: +- Amazon Web Services Key Management Service (KMS) +- Azure Key Vault + +This library is *not* a complete implementation of a KMS client, it only +implements the request format. + +## Testing kms-message +- `test_kms_request` tests HTTP request generation and response parsing, but does not require internet or use any live servers. +- `test_kms_azure_online` makes live requests, and has additional requirements (must have working credentials). + +### Requirements +- A complete installation of the C driver. (libbson is needed for parsing JSON, and libmongoc is used for creating TLS streams). See http://mongoc.org/libmongoc/current/installing.html for installation instructions. For macOS, `brew install mongo-c-driver` will suffice. +- An Azure key vault, and a service principal with an access policy allowing encrypt / decrypt key operations. The following environment variables must be set: + - AZURE_TENANT_ID + - AZURE_CLIENT_ID + - AZURE_CLIENT_SECRET + - AZURE_KEY_URL (e.g. `https://key-vault-kevinalbs.vault.azure.net/keys/test-key/9e1159e6ee5b447ba17e850b779bf652`) + +### Building +Configure and build with cmake: +``` +mkdir cmake-build +cd cmake-build +cmake .. +cmake --build . --target all +``` + +If the C driver is installed in a non-default location, specify the location with `-DCMAKE_PREFIX_PATH=...`. + +To build tests with verbose (and insecure) tracing, define `TEST_TRACING_INSECURE` in compiler flags by specifying `-DCMAKE_C_FLAGS="-DTEST_TRACING_INSECURE"` on cmake configuration. + +Recommended: compile tests with address sanitizer (use a relatively new gcc / clang compiler) by specifying `-fsanitize=address` in the C flags. This can be done by specifygin `-DCMAKE_C_FLAGS="-fsanitize=address"` as an option to cmake. Enable leak detection with the environment variable `ASAN_OPTIONS='detect_leaks=1'. Example: + +``` +cd cmake-build +cmake -DCMAKE_C_FLAGS="-fsanitize=address -DTEST_TRACING_INSECURE" +export ASAN_OPTIONS='detect_leaks=1' +./cmake-build/kms-message/test_kms_azure_online +``` diff --git a/src/third_party/kms-message/SConscript b/src/third_party/kms-message/SConscript index f1de7fda335..0ad6e82cfb2 100644 --- a/src/third_party/kms-message/SConscript +++ b/src/third_party/kms-message/SConscript @@ -9,7 +9,7 @@ def removeIfPresent(lst, item): except ValueError: pass -for to_remove in ['-Werror', "-Wsign-compare","-Wall","-Werror=unused-result"]: +for to_remove in ['-Werror', "-Wsign-compare", "-Wall", "-Werror=unused-result"]: removeIfPresent(env['CCFLAGS'], to_remove) removeIfPresent(env['CFLAGS'], to_remove) @@ -18,29 +18,35 @@ env.Append(CPPDEFINES=['KMS_MSG_STATIC']) additional_sources = [] if env.TargetOSIs('windows'): + env.Append(CPPDEFINES=['KMS_MESSAGE_ENABLE_CRYPTO_CNG']) additional_sources.append(['src/kms_crypto_windows.c']) # Disable warnings about deprecated functions env.Append(CFLAGS=['/wd4996']) elif env.TargetOSIs('darwin'): + env.Append(CPPDEFINES=['KMS_MESSAGE_ENABLE_CRYPTO_COMMON_CRYPTO']) additional_sources.append(['src/kms_crypto_apple.c']) else: - additional_sources.append(['src/kms_crypto_openssl.c']) + env.Append(CPPDEFINES=['KMS_MESSAGE_ENABLE_CRYPTO_LIBCRYPTO']) + additional_sources.append(['src/kms_crypto_libcrypto.c']) env.Library( target="kms-message", source=[ 'src/hexlify.c', + 'src/kms_azure_request.c', 'src/kms_b64.c', 'src/kms_caller_identity_request.c', 'src/kms_decrypt_request.c', 'src/kms_encrypt_request.c', + 'src/kms_gcp_request.c', 'src/kms_kv_list.c', 'src/kms_message.c', - 'src/kms_request.c', + 'src/kms_port.c', 'src/kms_request_opt.c', 'src/kms_request_str.c', - 'src/kms_response.c', + 'src/kms_request.c', 'src/kms_response_parser.c', + 'src/kms_response.c', 'src/sort.c', ] + additional_sources, LIBDEPS_TAGS=[ diff --git a/src/third_party/kms-message/THIRD_PARTY_NOTICES b/src/third_party/kms-message/THIRD_PARTY_NOTICES index 3fc095170c5..4110c1b91e0 100644 --- a/src/third_party/kms-message/THIRD_PARTY_NOTICES +++ b/src/third_party/kms-message/THIRD_PARTY_NOTICES @@ -1,4 +1,4 @@ -License notice for common-b64.c +License notice for kms_b64.c ------------------------------------------------------------------------------- ISC License diff --git a/src/third_party/kms-message/src/hexlify.c b/src/third_party/kms-message/src/hexlify.c index be9ee030b93..2d70927148c 100644 --- a/src/third_party/kms-message/src/hexlify.c +++ b/src/third_party/kms-message/src/hexlify.c @@ -24,6 +24,8 @@ char * hexlify (const uint8_t *buf, size_t len) { char *hex_chars = malloc (len * 2 + 1); + KMS_ASSERT (hex_chars); + char *p = hex_chars; size_t i; @@ -36,21 +38,30 @@ hexlify (const uint8_t *buf, size_t len) return hex_chars; } -uint8_t * -unhexlify (const char *hex_chars, size_t *len) +/* Returns -1 on error. */ +int +unhexlify (const char *in, size_t len) { - uint8_t *buf; - uint8_t *pos; + int i; + int byte; + int total = 0; + int multiplier = 1; - *len = strlen (hex_chars) / 2; - buf = malloc (*len); - pos = buf; + for (i = (int) len - 1; i >= 0; i--) { + char c = *(in + i); - while (*hex_chars) { - KMS_ASSERT (1 == sscanf (hex_chars, "%2hhx", pos)); - pos++; - hex_chars += 2; - } + if (c >= '0' && c <= '9') { + byte = c - 48; + } else if (c >= 'a' && c <= 'f') { + byte = c - 97 + 10; + } else if (c >= 'A' && c <= 'F') { + byte = c - 65 + 10; + } else { + return -1; + } - return buf; + total += byte * multiplier; + multiplier *= 16; + } + return total; } diff --git a/src/third_party/kms-message/src/hexlify.h b/src/third_party/kms-message/src/hexlify.h index e0096eb6ca4..60bc93ea7fc 100644 --- a/src/third_party/kms-message/src/hexlify.h +++ b/src/third_party/kms-message/src/hexlify.h @@ -19,5 +19,6 @@ char * hexlify (const uint8_t *buf, size_t len); -uint8_t * -unhexlify (const char *hex_chars, size_t *len); + +int +unhexlify (const char *in, size_t len);
\ No newline at end of file diff --git a/src/third_party/kms-message/src/kms_azure_request.c b/src/third_party/kms-message/src/kms_azure_request.c new file mode 100644 index 00000000000..5ce7488ff3d --- /dev/null +++ b/src/third_party/kms-message/src/kms_azure_request.c @@ -0,0 +1,219 @@ +/* + * Copyright 2020-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "kms_message/kms_azure_request.h" + +#include "kms_message/kms_b64.h" +#include "kms_message_private.h" +#include "kms_request_opt_private.h" +#include "kms_request_str.h" + +/* + * Request has the following form: + * + * POST /{tenant ID}/oauth2/v2.0/token HTTP/1.1 + * Host: {host of identify platform URL} + * Content-Type: application/x-www-form-urlencoded + * + * client_id={client ID} + * &scope=https%3A%2F%2Fvault.azure.net%2F.default + * &client_secret={client secret} + * &grant_type=client_credentials +*/ +kms_request_t * +kms_azure_request_oauth_new (const char *host, + const char *scope, + const char *tenant_id, + const char *client_id, + const char *client_secret, + const kms_request_opt_t *opt) +{ + char *path_and_query = NULL; + char *payload = NULL; + kms_request_t *req; + kms_request_str_t *str; + + str = kms_request_str_new (); + kms_request_str_appendf (str, "/%s/oauth2/v2.0/token", tenant_id); + path_and_query = kms_request_str_detach (str); + str = kms_request_str_new (); + kms_request_str_appendf ( + str, + "client_id=%s&scope=%s&client_secret=%s&grant_type=client_credentials", + client_id, + scope, + client_secret); + payload = kms_request_str_detach (str); + + req = kms_request_new ("POST", path_and_query, opt); + + if (opt->provider != KMS_REQUEST_PROVIDER_AZURE) { + KMS_ERROR (req, "Expected KMS request with provider type: Azure"); + goto done; + } + + if (kms_request_get_error (req)) { + goto done; + } + + if (!kms_request_add_header_field ( + req, "Content-Type", "application/x-www-form-urlencoded")) { + goto done; + } + if (!kms_request_add_header_field (req, "Host", host)) { + goto done; + } + if (!kms_request_add_header_field (req, "Accept", "application/json")) { + goto done; + } + + if (!kms_request_append_payload (req, payload, strlen (payload))) { + goto done; + } + +done: + kms_request_free_string (path_and_query); + kms_request_free_string (payload); + return req; +} + +static kms_request_t * +_wrap_unwrap_common (const char *wrap_unwrap, + const char *host, + const char *access_token, + const char *key_name, + const char *key_version, + const uint8_t *value, + size_t value_len, + const kms_request_opt_t *opt) +{ + char *path_and_query = NULL; + char *payload = NULL; + char *bearer_token_value = NULL; + char *value_base64url = NULL; + kms_request_t *req; + kms_request_str_t *str; + + str = kms_request_str_new (); + /* {vaultBaseUrl}/keys/{key-name}/{key-version}/wrapkey?api-version=7.1 */ + kms_request_str_appendf (str, + "/keys/%s/%s/%s?api-version=7.1", + key_name, + key_version ? key_version : "", + wrap_unwrap); + path_and_query = kms_request_str_detach (str); + + req = kms_request_new ("POST", path_and_query, opt); + + if (opt->provider != KMS_REQUEST_PROVIDER_AZURE) { + KMS_ERROR (req, "Expected KMS request with provider type: Azure"); + goto done; + } + + if (kms_request_get_error (req)) { + goto done; + } + + value_base64url = kms_message_raw_to_b64url (value, value_len); + if (!value_base64url) { + KMS_ERROR (req, "Could not bases64url-encode plaintext"); + goto done; + } + + str = kms_request_str_new (); + kms_request_str_appendf ( + str, "{\"alg\": \"RSA-OAEP-256\", \"value\": \"%s\"}", value_base64url); + payload = kms_request_str_detach (str); + str = kms_request_str_new (); + kms_request_str_appendf (str, "Bearer %s", access_token); + bearer_token_value = kms_request_str_detach (str); + if (!kms_request_add_header_field ( + req, "Authorization", bearer_token_value)) { + goto done; + } + if (!kms_request_add_header_field ( + req, "Content-Type", "application/json")) { + goto done; + } + if (!kms_request_add_header_field (req, "Host", host)) { + goto done; + } + if (!kms_request_add_header_field (req, "Accept", "application/json")) { + goto done; + } + + if (!kms_request_append_payload (req, payload, strlen (payload))) { + goto done; + } + +done: + kms_request_free_string (path_and_query); + kms_request_free_string (payload); + kms_request_free_string (bearer_token_value); + kms_request_free_string (value_base64url); + return req; +} + +/* + * Request has the following form: + * + * POST /keys/{key-name}/{key-version}/wrapkey?api-version=7.1 + * Host: {host of key vault endpoint} + * Authentication: Bearer {token} + * Content-Type: application/json + * + * { + * "alg": "RSA-OAEP-256" + * "value": "base64url encoded data" + * } + */ +kms_request_t * +kms_azure_request_wrapkey_new (const char *host, + const char *access_token, + const char *key_name, + const char *key_version, + const uint8_t *plaintext, + size_t plaintext_len, + const kms_request_opt_t *opt) +{ + return _wrap_unwrap_common ("wrapkey", + host, + access_token, + key_name, + key_version, + plaintext, + plaintext_len, + opt); +} + +kms_request_t * +kms_azure_request_unwrapkey_new (const char *host, + const char *access_token, + const char *key_name, + const char *key_version, + const uint8_t *ciphertext, + size_t ciphertext_len, + const kms_request_opt_t *opt) +{ + return _wrap_unwrap_common ("unwrapkey", + host, + access_token, + key_name, + key_version, + ciphertext, + ciphertext_len, + opt); +}
\ No newline at end of file diff --git a/src/third_party/kms-message/src/kms_b64.c b/src/third_party/kms-message/src/kms_b64.c index 713545d90e4..b7df107b6f0 100644 --- a/src/third_party/kms-message/src/kms_b64.c +++ b/src/third_party/kms-message/src/kms_b64.c @@ -41,8 +41,12 @@ */ #include <ctype.h> -#include "kms_message/kms_message.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + #include "kms_message/kms_b64.h" +#include "kms_message/kms_message.h" #define Assert(Cond) \ if (!(Cond)) \ @@ -505,3 +509,157 @@ kms_message_b64_pton (char const *src, uint8_t *target, size_t targsize) else return b64_pton_len (src); } + +int +kms_message_b64_to_b64url (const char *src, + size_t srclength, + char *target, + size_t targsize) +{ + size_t i; + + for (i = 0; i < srclength; i++) { + if (src[i] == '=') { + break; + } + + if (i >= targsize) { + return -1; + } + + target[i] = src[i]; + if (target[i] == '+') { + target[i] = '-'; + } else if (target[i] == '/') { + target[i] = '_'; + } + } + + /* NULL terminate if room. */ + if (i < targsize) { + target[i] = '\0'; + } + + return (int) i; +} + +int +kms_message_b64url_to_b64 (const char *src, + size_t srclength, + char *target, + size_t targsize) +{ + size_t i; + size_t boundary; + + for (i = 0; i < srclength; i++) { + if (src[i] == '=') { + break; + } + + if (i >= targsize) { + return -1; + } + + target[i] = src[i]; + if (target[i] == '-') { + target[i] = '+'; + } else if (target[i] == '_') { + target[i] = '/'; + } + } + + /* Pad to four byte boundary. */ + boundary = 4 * ((i + 3) / 4); + for (; i < boundary; i++) { + if (i >= targsize) { + return -1; + } + target[i] = '='; + } + + /* NULL terminate if room. */ + if (i < targsize) { + target[i] = '\0'; + } + + return (int) i; +} + +char * +kms_message_raw_to_b64 (const uint8_t *raw, size_t raw_len) +{ + char *b64; + size_t b64_len; + + b64_len = (raw_len / 3 + 1) * 4 + 1; + b64 = malloc (b64_len); + memset (b64, 0, b64_len); + if (-1 == kms_message_b64_ntop (raw, raw_len, b64, b64_len)) { + free (b64); + return NULL; + } + return b64; +} + +uint8_t * +kms_message_b64_to_raw (const char *b64, size_t *out) +{ + uint8_t *raw; + int ret; + size_t b64len; + + b64len = strlen (b64); + raw = (uint8_t *) malloc (b64len + 1); + memset (raw, 0, b64len + 1); + ret = kms_message_b64_pton (b64, raw, b64len); + if (ret > 0) { + *out = (size_t) ret; + return raw; + } + free (raw); + return NULL; +} + +char * +kms_message_raw_to_b64url (const uint8_t *raw, size_t raw_len) +{ + char *b64; + size_t b64len; + + b64 = kms_message_raw_to_b64 (raw, raw_len); + if (!b64) { + return NULL; + } + + b64len = strlen (b64); + if (-1 == kms_message_b64_to_b64url (b64, b64len, b64, b64len)) { + free (b64); + return NULL; + } + + return b64; +} + +uint8_t * +kms_message_b64url_to_raw (const char *b64url, size_t *out) +{ + char *b64; + size_t capacity; + uint8_t *raw; + size_t b64urllen; + + b64urllen = strlen(b64url); + /* Add four for padding '=' characters. */ + capacity = b64urllen + 4; + b64 = malloc (capacity); + memset (b64, 0, capacity); + if (-1 == + kms_message_b64url_to_b64 (b64url, b64urllen, b64, capacity)) { + free (b64); + return NULL; + } + raw = kms_message_b64_to_raw (b64, out); + free (b64); + return raw; +}
\ No newline at end of file diff --git a/src/third_party/kms-message/src/kms_caller_identity_request.c b/src/third_party/kms-message/src/kms_caller_identity_request.c index d4c12188445..371d2d7bad2 100644 --- a/src/third_party/kms-message/src/kms_caller_identity_request.c +++ b/src/third_party/kms-message/src/kms_caller_identity_request.c @@ -40,7 +40,10 @@ kms_caller_identity_request_new (const kms_request_opt_t *opt) payload = kms_request_str_new (); kms_request_str_appendf (payload, "Action=GetCallerIdentity&Version=2011-06-15"); - kms_request_append_payload (request, payload->str, payload->len); + if (!kms_request_append_payload (request, payload->str, payload->len)) { + KMS_ERROR (request, "Could not append payload"); + goto done; + } done: kms_request_str_destroy (payload); diff --git a/src/third_party/kms-message/src/kms_crypto.h b/src/third_party/kms-message/src/kms_crypto.h index 0da50b7a12b..a9789451bd5 100644 --- a/src/third_party/kms-message/src/kms_crypto.h +++ b/src/third_party/kms-message/src/kms_crypto.h @@ -31,7 +31,14 @@ typedef struct { const char *input, size_t len, unsigned char *hash_out); + bool (*sign_rsaes_pkcs1_v1_5) (void *sign_ctx, + const char *private_key, + size_t private_key_len, + const char *input, + size_t input_len, + unsigned char *signature_out); void *ctx; + void *sign_ctx; } _kms_crypto_t; int @@ -51,4 +58,13 @@ kms_sha256_hmac (void *ctx, size_t len, unsigned char *hash_out); +/* signature_out must be a preallocated buffer of 256 bytes (or greater). */ +bool +kms_sign_rsaes_pkcs1_v1_5 (void *sign_ctx, + const char *private_key, + size_t private_key_len, + const char *input, + size_t input_len, + unsigned char *signature_out); + #endif /* KMS_MESSAGE_KMS_CRYPTO_H */ diff --git a/src/third_party/kms-message/src/kms_crypto_apple.c b/src/third_party/kms-message/src/kms_crypto_apple.c index 61da0a62887..c9212f10098 100644 --- a/src/third_party/kms-message/src/kms_crypto_apple.c +++ b/src/third_party/kms-message/src/kms_crypto_apple.c @@ -16,8 +16,14 @@ #include "kms_crypto.h" +#ifdef KMS_MESSAGE_ENABLE_CRYPTO_COMMON_CRYPTO + #include <CommonCrypto/CommonDigest.h> #include <CommonCrypto/CommonHMAC.h> +#include <CoreFoundation/CFArray.h> +#include <Security/SecKey.h> +#include <Security/SecItem.h> +#include <Security/SecImportExport.h> int kms_crypto_init () @@ -54,3 +60,96 @@ kms_sha256_hmac (void *unused_ctx, CCHmac (kCCHmacAlgSHA256, key_input, key_len, input, len, hash_out); return true; } + +static void +safe_CFRelease (CFTypeRef ptr) +{ + if (ptr) { + CFRelease (ptr); + } +} + +bool +kms_sign_rsaes_pkcs1_v1_5 (void *unused_ctx, + const char *private_key, + size_t private_key_len, + const char *input, + size_t input_len, + unsigned char *signature_out) +{ + CFDataRef key_data_ref = NULL; + CFDataRef pass_ref = NULL; + SecItemImportExportKeyParameters import_params; + OSStatus status; + /* TODO: I think the expected format should be kSecFormatWrappedPKCS8, but + * GCP keys appear to only load for kSecFormatBSAFE. */ + SecExternalFormat format = kSecFormatUnknown; + SecExternalItemType type = kSecItemTypePrivateKey; + CFArrayRef out_ref = NULL; + SecKeyRef key_ref = NULL; + CFDataRef data_to_sign_ref = NULL; + CFErrorRef error_ref; + CFDataRef signature_ref = NULL; + bool ret = false; + + key_data_ref = CFDataCreate (NULL /* default allocator */, + (const uint8_t *) private_key, + (CFIndex) private_key_len); + if (!key_data_ref) { + goto cleanup; + } + memset (&import_params, 0, sizeof (SecItemImportExportKeyParameters)); + import_params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + + /* Give an empty password. SecItemImport returns an error expecting a + * password. */ + pass_ref = CFDataCreate (NULL, NULL, 0); + if (!pass_ref) { + goto cleanup; + } + import_params.passphrase = (CFTypeRef) pass_ref; + + status = SecItemImport (key_data_ref, + NULL /* extension. */, + &format, + &type, + 0, + &import_params, + NULL /* keychain */, + &out_ref); + if (status != errSecSuccess) { + goto cleanup; + } + if (1 != CFArrayGetCount (out_ref)) { + goto cleanup; + } + + key_ref = (SecKeyRef) CFArrayGetValueAtIndex (out_ref, 0); + data_to_sign_ref = CFDataCreate (NULL, (const uint8_t *) input, input_len); + if (!data_to_sign_ref) { + goto cleanup; + } + error_ref = NULL; + signature_ref = + SecKeyCreateSignature (key_ref, + kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256, + data_to_sign_ref, + &error_ref); + if (!signature_ref) { + goto cleanup; + } + memcpy (signature_out, + CFDataGetBytePtr (signature_ref), + CFDataGetLength (signature_ref)); + + ret = true; +cleanup: + safe_CFRelease (key_data_ref); + safe_CFRelease (pass_ref); + safe_CFRelease (out_ref); + safe_CFRelease (data_to_sign_ref); + safe_CFRelease (signature_ref); + return ret; +} + +#endif /* KMS_MESSAGE_ENABLE_CRYPTO_COMMON_CRYPTO */ diff --git a/src/third_party/kms-message/src/kms_crypto_openssl.c b/src/third_party/kms-message/src/kms_crypto_libcrypto.c index f6202906eb5..52f6ef713c4 100644 --- a/src/third_party/kms-message/src/kms_crypto_openssl.c +++ b/src/third_party/kms-message/src/kms_crypto_libcrypto.c @@ -16,6 +16,8 @@ #include "kms_crypto.h" +#ifdef KMS_MESSAGE_ENABLE_CRYPTO_LIBCRYPTO + #include <openssl/sha.h> #include <openssl/evp.h> #include <openssl/hmac.h> @@ -88,3 +90,49 @@ kms_sha256_hmac (void *unused_ctx, hash_out, NULL) != NULL; } + +bool +kms_sign_rsaes_pkcs1_v1_5 (void *unused_ctx, + const char *private_key, + size_t private_key_len, + const char *input, + size_t input_len, + unsigned char *signature_out) +{ + EVP_MD_CTX *ctx; + EVP_PKEY *pkey = NULL; + bool ret = false; + size_t signature_out_len = 256; + + ctx = EVP_MD_CTX_new (); + pkey = d2i_PrivateKey (EVP_PKEY_RSA, + NULL, + (const unsigned char **) &private_key, + private_key_len); + if (!pkey) { + goto cleanup; + } + + ret = EVP_DigestSignInit (ctx, NULL, EVP_sha256 (), NULL /* engine */, pkey); + if (ret != 1) { + goto cleanup; + } + + ret = EVP_DigestSignUpdate (ctx, input, input_len); + if (ret != 1) { + goto cleanup; + } + + ret = EVP_DigestSignFinal (ctx, signature_out, &signature_out_len); + if (ret != 1) { + goto cleanup; + } + + ret = true; +cleanup: + EVP_MD_CTX_free (ctx); + EVP_PKEY_free (pkey); + return ret; +} + +#endif /* KMS_MESSAGE_ENABLE_CRYPTO_LIBCRYPTO */ diff --git a/src/third_party/kms-message/src/kms_crypto_none.c b/src/third_party/kms-message/src/kms_crypto_none.c index 9ef2147687f..dee69ffe0a7 100644 --- a/src/third_party/kms-message/src/kms_crypto_none.c +++ b/src/third_party/kms-message/src/kms_crypto_none.c @@ -16,6 +16,8 @@ #include "kms_crypto.h" +#ifndef KMS_MESSAGE_ENABLE_CRYPTO + int kms_crypto_init () { @@ -48,3 +50,16 @@ kms_sha256_hmac (void *unused_ctx, /* only gets called if hooks were mistakenly not set */ return false; } + +bool +kms_sign_rsaes_pkcs1_v1_5 (void *unused_ctx, + const char *private_key, + size_t private_key_len, + const char *input, + size_t input_len, + unsigned char *signature_out) { + /* only gets called if hooks were mistakenly not set */ + return false; +} + +#endif /* KMS_MESSAGE_ENABLE_CRYPTO */ diff --git a/src/third_party/kms-message/src/kms_crypto_windows.c b/src/third_party/kms-message/src/kms_crypto_windows.c index ccdc7e095d4..5d41f7fd81f 100644 --- a/src/third_party/kms-message/src/kms_crypto_windows.c +++ b/src/third_party/kms-message/src/kms_crypto_windows.c @@ -16,6 +16,8 @@ #include "kms_crypto.h" +#ifdef KMS_MESSAGE_ENABLE_CRYPTO_CNG + // tell windows.h not to include a bunch of headers we don't need: #define WIN32_LEAN_AND_MEAN @@ -36,9 +38,13 @@ #include <ntstatus.h> #include <bcrypt.h> +#include <wincrypt.h> static BCRYPT_ALG_HANDLE _algoSHA256 = 0; static BCRYPT_ALG_HANDLE _algoSHA256Hmac = 0; +static BCRYPT_ALG_HANDLE _algoRSA = 0; + +#define SHA_256_HASH_LEN 32 int kms_crypto_init () @@ -57,6 +63,12 @@ kms_crypto_init () return 2; } + if (BCryptOpenAlgorithmProvider ( + &_algoRSA, BCRYPT_RSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0) != + STATUS_SUCCESS) { + return 3; + } + return 0; } @@ -65,6 +77,7 @@ kms_crypto_cleanup () { (void) BCryptCloseAlgorithmProvider (_algoSHA256, 0); (void) BCryptCloseAlgorithmProvider (_algoSHA256Hmac, 0); + (void) BCryptCloseAlgorithmProvider (_algoRSA, 0); } bool @@ -130,3 +143,126 @@ cleanup: return status == STATUS_SUCCESS ? 1 : 0; } + +bool +kms_sign_rsaes_pkcs1_v1_5 (void *unused_ctx, + const char *private_key, + size_t private_key_len, + const char *input, + size_t input_len, + unsigned char *signature_out) +{ + bool success = false; + bool ret = false; + LPBYTE blob_private = NULL; + DWORD blob_private_len = 0; + LPBYTE raw_private = NULL; + DWORD raw_private_len = 0; + + NTSTATUS status; + BCRYPT_KEY_HANDLE hKey = NULL; + BCRYPT_PKCS1_PADDING_INFO padding_PKCS1; + + unsigned char *hash_value = NULL; + DWORD hash_length = 256; + + success = CryptDecodeObjectEx (X509_ASN_ENCODING, + PKCS_PRIVATE_KEY_INFO, + private_key, + (DWORD) private_key_len, + 0, + NULL, + NULL, + &blob_private_len); + if (!success) { + goto cleanup; + } + + blob_private = (LPBYTE) calloc (1, blob_private_len); + + success = CryptDecodeObjectEx (X509_ASN_ENCODING, + PKCS_PRIVATE_KEY_INFO, + private_key, + (DWORD) private_key_len, + 0, + NULL, + blob_private, + &blob_private_len); + if (!success) { + goto cleanup; + } + + CRYPT_PRIVATE_KEY_INFO *privateKeyInfo = + (CRYPT_PRIVATE_KEY_INFO *) blob_private; + + success = CryptDecodeObjectEx (X509_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + privateKeyInfo->PrivateKey.pbData, + (DWORD) privateKeyInfo->PrivateKey.cbData, + 0, + NULL, + NULL, + &raw_private_len); + if (!success) { + goto cleanup; + } + + raw_private = (LPBYTE) calloc (1, raw_private_len); + + success = CryptDecodeObjectEx (X509_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + privateKeyInfo->PrivateKey.pbData, + (DWORD) privateKeyInfo->PrivateKey.cbData, + 0, + NULL, + raw_private, + &raw_private_len); + if (!success) { + goto cleanup; + } + + status = BCryptImportKeyPair ( + _algoRSA, + NULL, + LEGACY_RSAPRIVATE_BLOB, + &hKey, + raw_private, + raw_private_len, + 0); + if (!NT_SUCCESS (status)) { + goto cleanup; + } + + hash_value = calloc (1, SHA_256_HASH_LEN); + + if(!kms_sha256 (NULL, input, input_len, hash_value)) { + goto cleanup; + } + + padding_PKCS1.pszAlgId = BCRYPT_SHA256_ALGORITHM; + + status = + BCryptSignHash (hKey, + &padding_PKCS1, + hash_value, + SHA_256_HASH_LEN, + signature_out, + hash_length, + &hash_length, + BCRYPT_PAD_PKCS1); + if (!NT_SUCCESS (status)) { + goto cleanup; + } + + ret = true; + +cleanup: + BCryptDestroyKey(hKey); + free (blob_private); + free (raw_private); + free (hash_value); + + return ret; +} + +#endif /* KMS_MESSAGE_ENABLE_CRYPTO_CNG */ diff --git a/src/third_party/kms-message/src/kms_decrypt_request.c b/src/third_party/kms-message/src/kms_decrypt_request.c index 06faa431195..25cbecad237 100644 --- a/src/third_party/kms-message/src/kms_decrypt_request.c +++ b/src/third_party/kms-message/src/kms_decrypt_request.c @@ -48,7 +48,7 @@ kms_decrypt_request_new (const uint8_t *ciphertext_blob, if (!(b64 = malloc (b64_len))) { KMS_ERROR (request, "Could not allocate %d bytes for base64-encoding payload", - b64_len); + (int) b64_len); goto done; } @@ -59,7 +59,10 @@ kms_decrypt_request_new (const uint8_t *ciphertext_blob, payload = kms_request_str_new (); kms_request_str_appendf (payload, "{\"CiphertextBlob\": \"%s\"}", b64); - kms_request_append_payload (request, payload->str, payload->len); + if (!kms_request_append_payload (request, payload->str, payload->len)) { + KMS_ERROR (request, "Could not append payload"); + goto done; + } done: free (b64); diff --git a/src/third_party/kms-message/src/kms_encrypt_request.c b/src/third_party/kms-message/src/kms_encrypt_request.c index b5f4d6436e6..3f922abc3a8 100644 --- a/src/third_party/kms-message/src/kms_encrypt_request.c +++ b/src/third_party/kms-message/src/kms_encrypt_request.c @@ -47,7 +47,7 @@ kms_encrypt_request_new (const uint8_t *plaintext, if (!(b64 = malloc (b64_len))) { KMS_ERROR (request, "Could not allocate %d bytes for base64-encoding payload", - b64_len); + (int) b64_len); goto done; } @@ -60,7 +60,10 @@ kms_encrypt_request_new (const uint8_t *plaintext, payload = kms_request_str_new (); kms_request_str_appendf ( payload, "{\"Plaintext\": \"%s\", \"KeyId\": \"%s\"}", b64, key_id); - kms_request_append_payload (request, payload->str, payload->len); + if (!kms_request_append_payload (request, payload->str, payload->len)) { + KMS_ERROR (request, "Could not append payload"); + goto done; + } done: free (b64); diff --git a/src/third_party/kms-message/src/kms_gcp_request.c b/src/third_party/kms-message/src/kms_gcp_request.c new file mode 100644 index 00000000000..564cacc6113 --- /dev/null +++ b/src/third_party/kms-message/src/kms_gcp_request.c @@ -0,0 +1,286 @@ +/* + * Copyright 2020-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "kms_message/kms_gcp_request.h" + +#include "kms_message/kms_b64.h" +#include "kms_message_private.h" +#include "kms_request_opt_private.h" + +/* Set a default expiration of 5 minutes for JSON Web Tokens (GCP allows up to + * one hour) */ +#define JWT_EXPIRATION_SECS 5 * 60 +#define SIGNATURE_LEN 256 + +kms_request_t * +kms_gcp_request_oauth_new (const char *host, + const char *email, + const char *audience, + const char *scope, + const char *private_key_data, + size_t private_key_len, + const kms_request_opt_t *opt) +{ + kms_request_t *req = NULL; + kms_request_str_t *str = NULL; + time_t issued_at; + /* base64 encoding of {"alg":"RS256","typ":"JWT"} */ + const char *jwt_header_b64url = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"; + char *jwt_claims_b64url = NULL; + char *jwt_header_and_claims_b64url = NULL; + uint8_t *jwt_signature = NULL; + char *jwt_signature_b64url = NULL; + char *jwt_assertion_b64url = NULL; + char *payload = NULL; + + req = kms_request_new ("POST", "/token", opt); + if (opt->provider != KMS_REQUEST_PROVIDER_GCP) { + KMS_ERROR (req, "Expected KMS request with provider type: GCP"); + goto done; + } + + if (kms_request_get_error (req)) { + goto done; + } + + /* Produce the signed JWT <base64url header>.<base64url claims>.<base64url + * signature> */ + issued_at = time (NULL); + str = kms_request_str_new (); + kms_request_str_appendf (str, + "{\"iss\": \"%s\", \"aud\": \"%s\", \"scope\": " + "\"%s\", \"iat\": %lu, \"exp\": %lu}", + email, + audience, + scope, + (unsigned long) issued_at, + (unsigned long) issued_at + JWT_EXPIRATION_SECS); + jwt_claims_b64url = + kms_message_raw_to_b64url ((const uint8_t *) str->str, str->len); + kms_request_str_destroy (str); + if (!jwt_claims_b64url) { + KMS_ERROR (req, "Failed to base64url encode JWT claims"); + goto done; + } + + str = kms_request_str_new (); + kms_request_str_appendf (str, "%s.%s", jwt_header_b64url, jwt_claims_b64url); + jwt_header_and_claims_b64url = kms_request_str_detach (str); + + /* Produce the signature of <base64url header>.<base64url claims> */ + req->crypto.sign_rsaes_pkcs1_v1_5 = kms_sign_rsaes_pkcs1_v1_5; + if (opt->crypto.sign_rsaes_pkcs1_v1_5) { + req->crypto.sign_rsaes_pkcs1_v1_5 = opt->crypto.sign_rsaes_pkcs1_v1_5; + req->crypto.sign_ctx = opt->crypto.sign_ctx; + } + + jwt_signature = malloc (SIGNATURE_LEN); + if (!req->crypto.sign_rsaes_pkcs1_v1_5 ( + req->crypto.sign_ctx, + private_key_data, + private_key_len, + jwt_header_and_claims_b64url, + strlen (jwt_header_and_claims_b64url), + jwt_signature)) { + KMS_ERROR (req, "Failed to create GCP oauth request signature"); + goto done; + } + + jwt_signature_b64url = + kms_message_raw_to_b64url (jwt_signature, SIGNATURE_LEN); + if (!jwt_signature_b64url) { + KMS_ERROR (req, "Failed to base64url encode JWT signature"); + goto done; + } + str = kms_request_str_new (); + kms_request_str_appendf (str, + "%s.%s.%s", + jwt_header_b64url, + jwt_claims_b64url, + jwt_signature_b64url); + jwt_assertion_b64url = kms_request_str_detach (str); + + str = + kms_request_str_new_from_chars ("grant_type=urn%3Aietf%3Aparams%3Aoauth%" + "3Agrant-type%3Ajwt-bearer&assertion=", + -1); + kms_request_str_append_chars (str, jwt_assertion_b64url, -1); + payload = kms_request_str_detach (str); + + if (!kms_request_add_header_field ( + req, "Content-Type", "application/x-www-form-urlencoded")) { + goto done; + } + if (!kms_request_add_header_field (req, "Host", host)) { + goto done; + } + if (!kms_request_add_header_field (req, "Accept", "application/json")) { + goto done; + } + + if (!kms_request_append_payload (req, payload, strlen (payload))) { + goto done; + } + +done: + free (jwt_signature); + free (jwt_signature_b64url); + free (jwt_claims_b64url); + free (jwt_header_and_claims_b64url); + free (jwt_assertion_b64url); + free (payload); + return req; +} + +static kms_request_t * +_encrypt_decrypt_common (const char *encrypt_decrypt, + const char *host, + const char *access_token, + const char *project_id, + const char *location, + const char *key_ring_name, + const char *key_name, + const char *key_version, + const uint8_t *value, + size_t value_len, + const kms_request_opt_t *opt) +{ + char *path_and_query = NULL; + char *payload = NULL; + char *bearer_token_value = NULL; + char *value_base64 = NULL; + kms_request_t *req; + kms_request_str_t *str; + + str = kms_request_str_new (); + /* /v1/projects/{project-id}/locations/{location}/keyRings/{key-ring-name}/cryptoKeys/{key-name} + */ + kms_request_str_appendf ( + str, + "/v1/projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", + project_id, + location, + key_ring_name, + key_name); + if (key_version && strlen (key_version) > 0) { + kms_request_str_appendf (str, "/cryptoKeyVersions/%s", key_version); + } + kms_request_str_appendf (str, ":%s", encrypt_decrypt); + path_and_query = kms_request_str_detach (str); + + req = kms_request_new ("POST", path_and_query, opt); + + if (opt->provider != KMS_REQUEST_PROVIDER_GCP) { + KMS_ERROR (req, "Expected KMS request with provider type: GCP"); + goto done; + } + + if (kms_request_get_error (req)) { + goto done; + } + + value_base64 = kms_message_raw_to_b64 (value, value_len); + if (!value_base64) { + KMS_ERROR (req, "Could not bases64-encode plaintext"); + goto done; + } + + str = kms_request_str_new (); + if (0 == strcmp ("encrypt", encrypt_decrypt)) { + kms_request_str_appendf (str, "{\"plaintext\": \"%s\"}", value_base64); + } else { + kms_request_str_appendf (str, "{\"ciphertext\": \"%s\"}", value_base64); + } + + payload = kms_request_str_detach (str); + str = kms_request_str_new (); + kms_request_str_appendf (str, "Bearer %s", access_token); + bearer_token_value = kms_request_str_detach (str); + if (!kms_request_add_header_field ( + req, "Authorization", bearer_token_value)) { + goto done; + } + if (!kms_request_add_header_field ( + req, "Content-Type", "application/json")) { + goto done; + } + if (!kms_request_add_header_field (req, "Host", host)) { + goto done; + } + if (!kms_request_add_header_field (req, "Accept", "application/json")) { + goto done; + } + + if (!kms_request_append_payload (req, payload, strlen (payload))) { + goto done; + } + +done: + kms_request_free_string (path_and_query); + kms_request_free_string (payload); + kms_request_free_string (bearer_token_value); + kms_request_free_string (value_base64); + return req; +} + +kms_request_t * +kms_gcp_request_encrypt_new (const char *host, + const char *access_token, + const char *project_id, + const char *location, + const char *key_ring_name, + const char *key_name, + const char *key_version, + const uint8_t *plaintext, + size_t plaintext_len, + const kms_request_opt_t *opt) +{ + return _encrypt_decrypt_common ("encrypt", + host, + access_token, + project_id, + location, + key_ring_name, + key_name, + key_version, + plaintext, + plaintext_len, + opt); +} + +kms_request_t * +kms_gcp_request_decrypt_new (const char *host, + const char *access_token, + const char *project_id, + const char *location, + const char *key_ring_name, + const char *key_name, + const uint8_t *ciphertext, + size_t ciphertext_len, + const kms_request_opt_t *opt) +{ + return _encrypt_decrypt_common ("decrypt", + host, + access_token, + project_id, + location, + key_ring_name, + key_name, + NULL /* key_version */, + ciphertext, + ciphertext_len, + opt); +}
\ No newline at end of file diff --git a/src/third_party/kms-message/src/kms_kv_list.c b/src/third_party/kms-message/src/kms_kv_list.c index 2d6845a1aa0..0cff3dc2c64 100644 --- a/src/third_party/kms-message/src/kms_kv_list.c +++ b/src/third_party/kms-message/src/kms_kv_list.c @@ -17,6 +17,7 @@ #include "kms_kv_list.h" #include "kms_message/kms_message.h" +#include "kms_message_private.h" #include "kms_request_str.h" #include "kms_port.h" #include "sort.h" @@ -39,9 +40,12 @@ kms_kv_list_t * kms_kv_list_new (void) { kms_kv_list_t *lst = malloc (sizeof (kms_kv_list_t)); + KMS_ASSERT (lst); lst->size = 16; lst->kvs = malloc (lst->size * sizeof (kms_kv_t)); + KMS_ASSERT (lst->kvs); + lst->len = 0; return lst; @@ -72,6 +76,7 @@ kms_kv_list_add (kms_kv_list_t *lst, if (lst->len == lst->size) { lst->size *= 2; lst->kvs = realloc (lst->kvs, lst->size * sizeof (kms_kv_t)); + KMS_ASSERT (lst->kvs); } kv_init (&lst->kvs[lst->len], key, value); @@ -84,7 +89,7 @@ kms_kv_list_find (const kms_kv_list_t *lst, const char *key) size_t i; for (i = 0; i < lst->len; i++) { - if (0 == strcasecmp (lst->kvs[i].key->str, key)) { + if (0 == kms_strcasecmp (lst->kvs[i].key->str, key)) { return &lst->kvs[i]; } } @@ -119,8 +124,12 @@ kms_kv_list_dup (const kms_kv_list_t *lst) } dup = malloc (sizeof (kms_kv_list_t)); + KMS_ASSERT (dup); + dup->size = dup->len = lst->len; dup->kvs = malloc (lst->len * sizeof (kms_kv_t)); + KMS_ASSERT (dup->kvs); + for (i = 0; i < lst->len; i++) { kv_init (&dup->kvs[i], lst->kvs[i].key, lst->kvs[i].value); diff --git a/src/third_party/kms-message/src/kms_message/kms_azure_request.h b/src/third_party/kms-message/src/kms_message/kms_azure_request.h new file mode 100644 index 00000000000..2e9af68fd03 --- /dev/null +++ b/src/third_party/kms-message/src/kms_message/kms_azure_request.h @@ -0,0 +1,110 @@ +/* + * Copyright 2020-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef KMS_AZURE_REQUEST_H +#define KMS_AZURE_REQUEST_H + +#include "kms_message_defines.h" +#include "kms_request.h" +#include "kms_request_opt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Constructs an oauth client credentials grant request for Azure. + * See + * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#get-a-token. + * + * Parameters: + * All parameters must be NULL terminated strings. + * - host: The value of the Host header. This should be a custom host or + * "login.microsoftonline.com". + * - scope: The oauth scope. This should be a custom scope or + * "https%3A%2F%2Fvault.azure.net%2F.default". Must be URL encoded. + * - tenant_id: The Azure tenant ID. + * - client_id: The client ID to authenticate. + * - client_secret: The client secret to authenticate. + * - opt: Additional options. This must have the Azure provider set via + * kms_request_opt_set_provider. + * + * Returns: A new kms_request_t. + * Always returns a new kms_request_t, even on error. + * Caller must check if an error occurred by calling kms_request_get_error. + */ +KMS_MSG_EXPORT (kms_request_t *) +kms_azure_request_oauth_new (const char *host, + const char *scope, + const char *tenant_id, + const char *client_id, + const char *client_secret, + const kms_request_opt_t *opt); + +/* Constructs a wrapkey request for Azure. + * See https://docs.microsoft.com/en-us/rest/api/keyvault/wrapkey/wrapkey + * + * Parameters: + * All parameters must be NULL terminated strings. + * - host: The value of the Host header, like "mykeyvault.vault.azure.net". + * - access_token: The access_token obtained from an oauth response as a + * base64url encoded string. + * - key_name: The azure key name. + * - key_version: An optional key version. May be NULL or empty string. + * - plaintext: The plaintext key to encrypt. + * - plaintext_len: The number of bytes of plaintext. + * - opt: Additional options. This must have the Azure provider set via + * kms_request_opt_set_provider. + */ + +KMS_MSG_EXPORT (kms_request_t *) +kms_azure_request_wrapkey_new (const char *host, + const char *access_token, + const char *key_name, + const char *key_version, + const uint8_t *plaintext, + size_t plaintext_len, + const kms_request_opt_t *opt); + +/* Constructs an unwrapkey request for Azure. + * See https://docs.microsoft.com/en-us/rest/api/keyvault/unwrapkey/unwrapkey + * + * Parameters: + * All parameters must be NULL terminated strings. + * - host: The value of the Host header, like "mykeyvault.vault.azure.net". + * - access_token: The access_token obtained from an oauth response as a + * base64url encoded string. + * - key_name: The azure key name. + * - key_version: An optional key version. May be NULL or empty string. + * - ciphertext: The ciphertext key to decrypt. + * - ciphertext_len: The number of bytes of ciphertext. + * - opt: Additional options. This must have the Azure provider set via + * kms_request_opt_set_provider. + */ + +KMS_MSG_EXPORT (kms_request_t *) +kms_azure_request_unwrapkey_new (const char *host, + const char *access_token, + const char *key_name, + const char *key_version, + const uint8_t *ciphertext, + size_t ciphertext_len, + const kms_request_opt_t *opt); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* KMS_AZURE_REQUEST_H */ diff --git a/src/third_party/kms-message/src/kms_message/kms_b64.h b/src/third_party/kms-message/src/kms_message/kms_b64.h index a55c1ec9f6d..f0845cd331e 100644 --- a/src/third_party/kms-message/src/kms_message/kms_b64.h +++ b/src/third_party/kms-message/src/kms_message/kms_b64.h @@ -38,6 +38,31 @@ kms_message_b64_ntop (uint8_t const *src, KMS_MSG_EXPORT (int) kms_message_b64_pton (char const *src, uint8_t *target, size_t targsize); +/* src and target may be the same string. Assumes no whitespace in src. */ +KMS_MSG_EXPORT (int) +kms_message_b64_to_b64url (const char *src, + size_t srclength, + char *target, + size_t targsize); +KMS_MSG_EXPORT (int) +kms_message_b64url_to_b64 (const char *src, + size_t srclength, + char *target, + size_t targsize); + +/* Convenience conversions which return copies. */ +char * +kms_message_raw_to_b64 (const uint8_t *raw, size_t raw_len); + +uint8_t * +kms_message_b64_to_raw (const char *b64, size_t *out); + +char * +kms_message_raw_to_b64url (const uint8_t *raw, size_t raw_len); + +uint8_t * +kms_message_b64url_to_raw (const char *b64url, size_t *out); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/src/third_party/kms-message/src/kms_message/kms_caller_identity_request.h b/src/third_party/kms-message/src/kms_message/kms_caller_identity_request.h index 9af8e2ccdd9..9f48e534235 100644 --- a/src/third_party/kms-message/src/kms_message/kms_caller_identity_request.h +++ b/src/third_party/kms-message/src/kms_message/kms_caller_identity_request.h @@ -17,7 +17,9 @@ #ifndef KMS_CALLER_IDENTITY_REQUEST_H #define KMS_CALLER_IDENTITY_REQUEST_H -#include "kms_message.h" +#include "kms_message_defines.h" +#include "kms_request.h" +#include "kms_request_opt.h" #ifdef __cplusplus extern "C" { diff --git a/src/third_party/kms-message/src/kms_message/kms_decrypt_request.h b/src/third_party/kms-message/src/kms_message/kms_decrypt_request.h index b27a96d9917..db18d5f5e1c 100644 --- a/src/third_party/kms-message/src/kms_message/kms_decrypt_request.h +++ b/src/third_party/kms-message/src/kms_message/kms_decrypt_request.h @@ -17,7 +17,9 @@ #ifndef KMS_DECRYPT_REQUEST_H #define KMS_DECRYPT_REQUEST_H -#include "kms_message.h" +#include "kms_message_defines.h" +#include "kms_request.h" +#include "kms_request_opt.h" #ifdef __cplusplus extern "C" { diff --git a/src/third_party/kms-message/src/kms_message/kms_encrypt_request.h b/src/third_party/kms-message/src/kms_message/kms_encrypt_request.h index ac30f1a3675..601ee36297f 100644 --- a/src/third_party/kms-message/src/kms_message/kms_encrypt_request.h +++ b/src/third_party/kms-message/src/kms_message/kms_encrypt_request.h @@ -17,7 +17,9 @@ #ifndef KMS_ENCRYPT_REQUEST_H #define KMS_ENCRYPT_REQUEST_H -#include "kms_message.h" +#include "kms_message_defines.h" +#include "kms_request.h" +#include "kms_request_opt.h" #ifdef __cplusplus extern "C" { diff --git a/src/third_party/kms-message/src/kms_message/kms_gcp_request.h b/src/third_party/kms-message/src/kms_message/kms_gcp_request.h new file mode 100644 index 00000000000..1d1555fb0c6 --- /dev/null +++ b/src/third_party/kms-message/src/kms_message/kms_gcp_request.h @@ -0,0 +1,124 @@ +/* + * Copyright 2020-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef KMS_GCP_REQUEST_H +#define KMS_GCP_REQUEST_H + +#include "kms_message_defines.h" +#include "kms_request.h" +#include "kms_request_opt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Constructs an oauth client credentials request for GCP. + * See https://developers.google.com/identity/protocols/oauth2/service-account + * + * Parameters: + * - host: The host header, like "oauth2.googleapis.com". + * - email: The email for the service account to authenticate. + * - audience: The "aud" field in the JSON Web Token (JWT). Should be a URL + * like "https://oauth2.googleapis.com/token" + * - scope: The "scope" field in the JSON Web Token (JWT). Should be a URL + * like "https://www.googleapis.com/auth/cloudkms". + * - private_key_data: Bytes pointing to a PKCS#8 private key. + * - private_key_len: The length of private_key_data. + * - opt: Request options. The provider must be set to KMS_REQUEST_PROVIDER_GCP + * with kms_request_opt_set_provider. Callers that want to use a custom crypto + * callback to sign the request should set the callback on opt with + * kms_request_opt_set_crypto_hook_rsaes_pkcs1_v1_5. + * + * Returns: A new kms_request_t. + * Always returns a new kms_request_t, even on error. + * Caller must check if an error occurred by calling kms_request_get_error. + */ +KMS_MSG_EXPORT (kms_request_t *) +kms_gcp_request_oauth_new (const char *host, + const char *email, + const char *audience, + const char *scope, + const char *private_key_data, + size_t private_key_len, + const kms_request_opt_t *opt); + +/* Constructs the encrypt request for GCP. + * See + * https://cloud.google.com/kms/docs/encrypt-decrypt#kms-encrypt-symmetric-api + * + * Parameters: + * - host: The value of the Host header, like "cloudkms.googleapis.com". + * - project_id: The project id. + * - location: The location id, like "global". + * - key_ring_name: The key ring name. + * - key_name: The key name. + * - key_version: The optional key version. May be NULL. + * - plaintext: The plaintext key to encrypt. + * - plaintext_len: The number of bytes of plaintext. + * - opt: Request options. The provider must be set to KMS_REQUEST_PROVIDER_GCP + * with kms_request_opt_set_provider. + * + * Returns: A new kms_request_t. + * Always returns a new kms_request_t, even on error. + * Caller must check if an error occurred by calling kms_request_get_error. + */ +KMS_MSG_EXPORT (kms_request_t *) +kms_gcp_request_encrypt_new (const char *host, + const char *access_token, + const char *project_id, + const char *location, + const char *key_ring_name, + const char *key_name, + const char *key_version, + const uint8_t *plaintext, + size_t plaintext_len, + const kms_request_opt_t *opt); + +/* Constructs the decrypt request for GCP. + * See + * https://cloud.google.com/kms/docs/encrypt-decrypt#kms-decrypt-symmetric-api + * + * Parameters: + * - host: The value of the Host header, like "cloudkms.googleapis.com". + * - project_id: The project id. + * - location: The location id, like "global". + * - key_ring_name: The key ring name. + * - key_name: The key name. + * - ciphertext: The ciphertext key to encrypt. + * - ciphertext_len: The number of bytes of ciphertext. + * - opt: Request options. The provider must be set to KMS_REQUEST_PROVIDER_GCP + * with kms_request_opt_set_provider. + * + * Returns: A new kms_request_t. + * Always returns a new kms_request_t, even on error. + * Caller must check if an error occurred by calling kms_request_get_error. + */ +KMS_MSG_EXPORT (kms_request_t *) +kms_gcp_request_decrypt_new (const char *host, + const char *access_token, + const char *project_id, + const char *location, + const char *key_ring_name, + const char *key_name, + const uint8_t *ciphertext, + size_t ciphertext_len, + const kms_request_opt_t *opt); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* KMS_GCP_REQUEST_H */ diff --git a/src/third_party/kms-message/src/kms_message/kms_message.h b/src/third_party/kms-message/src/kms_message/kms_message.h index 6ea95dd04ca..8048528f2e0 100644 --- a/src/third_party/kms-message/src/kms_message/kms_message.h +++ b/src/third_party/kms-message/src/kms_message/kms_message.h @@ -17,6 +17,8 @@ #ifndef KMS_MESSAGE_H #define KMS_MESSAGE_H +#include <sys/types.h> + #include "kms_message_defines.h" #include "kms_request_opt.h" #include "kms_request.h" diff --git a/src/third_party/kms-message/src/kms_message/kms_message_defines.h b/src/third_party/kms-message/src/kms_message/kms_message_defines.h index a4d019bd774..a539d531ef6 100644 --- a/src/third_party/kms-message/src/kms_message/kms_message_defines.h +++ b/src/third_party/kms-message/src/kms_message/kms_message_defines.h @@ -53,4 +53,14 @@ kms_message_cleanup (void); } /* extern "C" */ #endif +#ifdef _MSC_VER +#include <basetsd.h> +#pragma warning(disable : 4142) +#ifndef _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED +typedef SSIZE_T ssize_t; +#endif +#pragma warning(default : 4142) +#endif + #endif /* KMS_MESSAGE_DEFINES_H */ diff --git a/src/third_party/kms-message/src/kms_message/kms_request.h b/src/third_party/kms-message/src/kms_message/kms_request.h index cf49f0a31a9..0428c813491 100644 --- a/src/third_party/kms-message/src/kms_message/kms_request.h +++ b/src/third_party/kms-message/src/kms_message/kms_request.h @@ -17,7 +17,8 @@ #ifndef KMS_REQUEST_H #define KMS_REQUEST_H -#include "kms_message.h" +#include "kms_message_defines.h" +#include "kms_request_opt.h" #include <stdbool.h> #include <stdint.h> @@ -28,6 +29,8 @@ extern "C" { #endif +/* A KMS request is general enough to create arbitrary HTTP requests, but also + * supports generating AWS signature v4. */ typedef struct _kms_request_t kms_request_t; KMS_MSG_EXPORT (kms_request_t *) @@ -38,6 +41,8 @@ KMS_MSG_EXPORT (void) kms_request_destroy (kms_request_t *request); KMS_MSG_EXPORT (const char *) kms_request_get_error (kms_request_t *request); + +/* Begin: AWS specific */ KMS_MSG_EXPORT (bool) kms_request_set_date (kms_request_t *request, const struct tm *tm); KMS_MSG_EXPORT (bool) @@ -48,6 +53,8 @@ KMS_MSG_EXPORT (bool) kms_request_set_access_key_id (kms_request_t *request, const char *akid); KMS_MSG_EXPORT (bool) kms_request_set_secret_key (kms_request_t *request, const char *key); +/* End: AWS specific */ + KMS_MSG_EXPORT (bool) kms_request_add_header_field (kms_request_t *request, const char *field_name, @@ -60,6 +67,8 @@ KMS_MSG_EXPORT (bool) kms_request_append_payload (kms_request_t *request, const char *payload, size_t len); + +/* Begin: AWS specific */ KMS_MSG_EXPORT (char *) kms_request_get_canonical (kms_request_t *request); @@ -74,9 +83,14 @@ KMS_MSG_EXPORT (char *) kms_request_get_signature (kms_request_t *request); KMS_MSG_EXPORT (char *) kms_request_get_signed (kms_request_t *request); +/* End: AWS specific */ + KMS_MSG_EXPORT (void) kms_request_free_string (char *ptr); +/* Finalize and obtain a plain HTTP request (no signing). */ +KMS_MSG_EXPORT (char *) kms_request_to_string (kms_request_t *request); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/src/third_party/kms-message/src/kms_message/kms_request_opt.h b/src/third_party/kms-message/src/kms_message/kms_request_opt.h index f38652e9e74..74a3fb69771 100644 --- a/src/third_party/kms-message/src/kms_message/kms_request_opt.h +++ b/src/third_party/kms-message/src/kms_message/kms_request_opt.h @@ -28,13 +28,26 @@ extern "C" { typedef struct _kms_request_opt_t kms_request_opt_t; +typedef size_t kms_request_provider_t; + +#define KMS_REQUEST_PROVIDER_AWS 0 +#define KMS_REQUEST_PROVIDER_AZURE 1 +#define KMS_REQUEST_PROVIDER_GCP 2 + KMS_MSG_EXPORT (kms_request_opt_t *) kms_request_opt_new (void); + +/* The default provider is AWS. This will automatically set extra headers. + * Returns false if provider is invalid. */ +KMS_MSG_EXPORT (bool) +kms_request_opt_set_provider (kms_request_opt_t *opt, + kms_request_provider_t provider); KMS_MSG_EXPORT (void) kms_request_opt_destroy (kms_request_opt_t *request); KMS_MSG_EXPORT (void) kms_request_opt_set_connection_close (kms_request_opt_t *opt, bool connection_close); + KMS_MSG_EXPORT (void) kms_request_opt_set_crypto_hooks (kms_request_opt_t *opt, bool (*sha256) (void *ctx, @@ -49,6 +62,16 @@ kms_request_opt_set_crypto_hooks (kms_request_opt_t *opt, unsigned char *hash_out), void *ctx); +KMS_MSG_EXPORT (void) +kms_request_opt_set_crypto_hook_sign_rsaes_pkcs1_v1_5 ( + kms_request_opt_t *opt, + bool (*sign_rsaes_pkcs1_v1_5) (void *ctx, + const char *private_key, + size_t private_key_len, + const char *input, + size_t input_len, + unsigned char *signature_out), + void *ctx); #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/src/third_party/kms-message/src/kms_message/kms_response.h b/src/third_party/kms-message/src/kms_message/kms_response.h index 545476d40bc..d270f248826 100644 --- a/src/third_party/kms-message/src/kms_message/kms_response.h +++ b/src/third_party/kms-message/src/kms_message/kms_response.h @@ -17,7 +17,9 @@ #ifndef KMS_RESPONSE_H #define KMS_RESPONSE_H -#include "kms_message.h" +#include "kms_message_defines.h" + +#include <sys/types.h> #ifdef __cplusplus extern "C" { @@ -25,9 +27,11 @@ extern "C" { typedef struct _kms_response_t kms_response_t; +KMS_MSG_EXPORT (int) +kms_response_get_status (kms_response_t *response); KMS_MSG_EXPORT (const char *) -kms_response_get_body (kms_response_t *reply, size_t *len); -KMS_MSG_EXPORT (void) kms_response_destroy (kms_response_t *reply); +kms_response_get_body (kms_response_t *response, size_t *len); +KMS_MSG_EXPORT (void) kms_response_destroy (kms_response_t *response); #ifdef __cplusplus } /* extern "C" */ diff --git a/src/third_party/kms-message/src/kms_message/kms_response_parser.h b/src/third_party/kms-message/src/kms_message/kms_response_parser.h index c355ff1cdb5..0bdf0809a00 100644 --- a/src/third_party/kms-message/src/kms_message/kms_response_parser.h +++ b/src/third_party/kms-message/src/kms_message/kms_response_parser.h @@ -17,9 +17,13 @@ #ifndef KMS_RESPONSE_PARSER_H #define KMS_RESPONSE_PARSER_H -#include "kms_message.h" +#include "kms_message_defines.h" #include "kms_response.h" +#include <sys/types.h> +#include <stdbool.h> +#include <stdint.h> + #ifdef __cplusplus extern "C" { #endif diff --git a/src/third_party/kms-message/src/kms_message_private.h b/src/third_party/kms-message/src/kms_message_private.h index 6bd3b891de3..b41b56836ae 100644 --- a/src/third_party/kms-message/src/kms_message_private.h +++ b/src/third_party/kms-message/src/kms_message_private.h @@ -28,21 +28,24 @@ struct _kms_request_t { char error[512]; bool failed; bool finalized; + /* Begin: AWS specific */ kms_request_str_t *region; kms_request_str_t *service; kms_request_str_t *access_key_id; kms_request_str_t *secret_key; + kms_request_str_t *datetime; + kms_request_str_t *date; + /* End: AWS specific */ kms_request_str_t *method; kms_request_str_t *path; kms_request_str_t *query; kms_request_str_t *payload; - kms_request_str_t *datetime; - kms_request_str_t *date; kms_kv_list_t *query_params; kms_kv_list_t *header_fields; /* turn off for tests only, not in public kms_request_opt_t API */ bool auto_content_length; _kms_crypto_t crypto; + kms_request_provider_t provider; }; struct _kms_response_t { @@ -55,6 +58,8 @@ typedef enum { PARSING_STATUS_LINE, PARSING_HEADER, PARSING_BODY, + PARSING_CHUNK_LENGTH, + PARSING_CHUNK, PARSING_DONE } kms_response_parser_state_t; @@ -65,6 +70,14 @@ struct _kms_response_parser_t { kms_request_str_t *raw_response; int content_length; int start; /* start of the current thing getting parsed. */ + + /* Support two types of HTTP 1.1 responses. + * - "Content-Length: x" header is present, indicating the body length. + * - "Transfer-Encoding: chunked" header is present, indicating a stream of + * chunks. + */ + bool transfer_encoding_chunked; + int chunk_size; kms_response_parser_state_t state; }; @@ -84,10 +97,10 @@ set_error (char *error, size_t size, const char *fmt, ...); set_error (obj->error, sizeof (obj->error), __VA_ARGS__); \ } while (0) -#define KMS_ASSERT(stmt) \ -if (!(stmt)) { \ - fprintf (stderr, "%s failed\n", #stmt); \ - abort (); \ -} +#define KMS_ASSERT(stmt) \ + if (!(stmt)) { \ + fprintf (stderr, "%s failed\n", #stmt); \ + abort (); \ + } #endif /* KMS_MESSAGE_PRIVATE_H */ diff --git a/src/third_party/kms-message/src/kms_port.c b/src/third_party/kms-message/src/kms_port.c new file mode 100644 index 00000000000..ee9e6ed9c90 --- /dev/null +++ b/src/third_party/kms-message/src/kms_port.c @@ -0,0 +1,33 @@ +/* + * Copyright 2020-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "kms_port.h" +#if defined(_WIN32) +#include <stdlib.h> +#include <string.h> +char * kms_strndup (const char *src, size_t len) +{ + char *dst = (char *) malloc (len + 1); + if (!dst) { + return 0; + } + + memcpy (dst, src, len); + dst[len] = '\0'; + + return dst; +} +#endif
\ No newline at end of file diff --git a/src/third_party/kms-message/src/kms_port.h b/src/third_party/kms-message/src/kms_port.h index c3cbbac3693..2123a99dc95 100644 --- a/src/third_party/kms-message/src/kms_port.h +++ b/src/third_party/kms-message/src/kms_port.h @@ -15,21 +15,18 @@ * limitations under the License. */ -#if defined(_WIN32) -#define strcasecmp _stricmp - -inline char * -strndup (const char *src, size_t len) -{ - char *dst = (char *) malloc (len + 1); - if (!dst) { - return 0; - } - - memcpy (dst, src, len); - dst[len] = '\0'; +#ifndef KMS_PORT_H +#define KMS_PORT_H - return dst; -} +#include <stddef.h> +#if defined(_WIN32) +#define kms_strcasecmp _stricmp +char * +kms_strndup (const char *src, size_t len); +#else +#define kms_strndup strndup +#define kms_strcasecmp strcasecmp #endif + +#endif /* KMS_PORT_H */
\ No newline at end of file diff --git a/src/third_party/kms-message/src/kms_request.c b/src/third_party/kms-message/src/kms_request.c index fa2d487123b..58bfb990b7d 100644 --- a/src/third_party/kms-message/src/kms_request.c +++ b/src/third_party/kms-message/src/kms_request.c @@ -61,6 +61,12 @@ kms_request_new (const char *method, kms_request_t *request = calloc (1, sizeof (kms_request_t)); const char *question_mark; + KMS_ASSERT (request); + if (opt && opt->provider) { + request->provider = opt->provider; + } else { + request->provider = KMS_REQUEST_PROVIDER_AWS; + } /* parsing may set failed to true */ request->failed = false; @@ -92,10 +98,16 @@ kms_request_new (const char *method, request->header_fields = kms_kv_list_new (); request->auto_content_length = true; - kms_request_set_date (request, NULL); + /* For AWS KMS requests, add a X-Amz-Date header. */ + if (request->provider == KMS_REQUEST_PROVIDER_AWS && + !kms_request_set_date (request, NULL)) { + return request; + } if (opt && opt->connection_close) { - kms_request_add_header_field (request, "Connection", "close"); + if (!kms_request_add_header_field (request, "Connection", "close")) { + return request; + } } if (opt && opt->crypto.sha256) { @@ -164,7 +176,9 @@ kms_request_set_date (kms_request_t *request, const struct tm *tm) kms_request_str_set_chars (request->date, buf, sizeof "YYYYmmDD" - 1); kms_request_str_set_chars (request->datetime, buf, sizeof AMZ_DT_FORMAT - 1); kms_kv_list_del (request->header_fields, "X-Amz-Date"); - kms_request_add_header_field (request, "X-Amz-Date", buf); + if (!kms_request_add_header_field (request, "X-Amz-Date", buf)) { + return false; + } return true; } @@ -309,7 +323,8 @@ append_canonical_headers (kms_kv_list_t *lst, kms_request_str_t *str) * values in headers that have multiple values." */ for (i = 0; i < lst->len; i++) { kv = &lst->kvs[i]; - if (previous_key && 0 == strcasecmp (previous_key->str, kv->key->str)) { + if (previous_key && + 0 == kms_strcasecmp (previous_key->str, kv->key->str)) { /* duplicate header */ kms_request_str_append_char (str, ','); kms_request_str_append_stripped (str, kv->value); @@ -339,12 +354,13 @@ append_signed_headers (kms_kv_list_t *lst, kms_request_str_t *str) for (i = 0; i < lst->len; i++) { kv = &lst->kvs[i]; - if (previous_key && 0 == strcasecmp (previous_key->str, kv->key->str)) { + if (previous_key && + 0 == kms_strcasecmp (previous_key->str, kv->key->str)) { /* duplicate header */ continue; } - if (0 == strcasecmp (kv->key->str, "connection")) { + if (0 == kms_strcasecmp (kv->key->str, "connection")) { continue; } @@ -376,10 +392,13 @@ finalize (kms_request_t *request) lst = request->header_fields; - /* By default, if no explicit Host was set, it is derived from region + - * service */ if (!kms_kv_list_find (lst, "Host")) { - /* like "kms.us-east-1.amazonaws.com" */ + if (request->provider != KMS_REQUEST_PROVIDER_AWS) { + KMS_ERROR (request, "Required Host header not set"); + return false; + } + /* For AWS requests, derive a default Host header from region + service. + * E.g. "kms.us-east-1.amazonaws.com" */ k = kms_request_str_new_from_chars ("Host", -1); v = kms_request_str_dup (request->service); kms_request_str_append_char (v, '.'); @@ -412,7 +431,8 @@ finalize (kms_request_t *request) static int cmp_header_field_names (const void *a, const void *b) { - return strcasecmp (((kms_kv_t *) a)->key->str, ((kms_kv_t *) b)->key->str); + return kms_strcasecmp (((kms_kv_t *) a)->key->str, + ((kms_kv_t *) b)->key->str); } static kms_kv_list_t * @@ -447,6 +467,7 @@ kms_request_get_canonical (kms_request_t *request) kms_request_str_append_newline (canonical); normalized = kms_request_str_path_normalized (request->path); kms_request_str_append_escaped (canonical, normalized, false); + kms_request_str_destroy (normalized); kms_request_str_append_newline (canonical); append_canonical_query (request, canonical); kms_request_str_append_newline (canonical); @@ -454,12 +475,14 @@ kms_request_get_canonical (kms_request_t *request) append_canonical_headers (lst, canonical); kms_request_str_append_newline (canonical); append_signed_headers (lst, canonical); - kms_request_str_append_newline (canonical); - kms_request_str_append_hashed ( - &request->crypto, canonical, request->payload); - - kms_request_str_destroy (normalized); kms_kv_list_destroy (lst); + kms_request_str_append_newline (canonical); + if (!kms_request_str_append_hashed ( + &request->crypto, canonical, request->payload)) { + KMS_ERROR (request, "could not generate hash"); + kms_request_str_destroy (canonical); + return NULL; + } return kms_request_str_detach (canonical); } @@ -514,6 +537,10 @@ kms_request_get_string_to_sign (kms_request_t *request) kms_request_str_append_chars (sts, "/aws4_request\n", -1); creq = kms_request_str_wrap (kms_request_get_canonical (request), -1); + if (!creq) { + goto done; + } + if (!kms_request_str_append_hashed (&request->crypto, sts, creq)) { goto done; } @@ -739,6 +766,51 @@ done: return kms_request_str_detach (sreq); } +char * +kms_request_to_string (kms_request_t *request) +{ + kms_kv_list_t *lst = NULL; + kms_request_str_t *sreq = NULL; + size_t i; + + if (!finalize (request)) { + return false; + } + + sreq = kms_request_str_new (); + /* like "POST / HTTP/1.1" */ + kms_request_str_append (sreq, request->method); + kms_request_str_append_char (sreq, ' '); + kms_request_str_append (sreq, request->path); + if (request->query->len) { + kms_request_str_append_char (sreq, '?'); + kms_request_str_append (sreq, request->query); + } + + kms_request_str_append_chars (sreq, " HTTP/1.1", -1); + kms_request_str_append_newline (sreq); + + /* headers */ + lst = kms_kv_list_dup (request->header_fields); + kms_kv_list_sort (lst, cmp_header_field_names); + for (i = 0; i < lst->len; i++) { + kms_request_str_append (sreq, lst->kvs[i].key); + kms_request_str_append_char (sreq, ':'); + kms_request_str_append (sreq, lst->kvs[i].value); + kms_request_str_append_newline (sreq); + } + + kms_request_str_append_newline (sreq); + + /* body */ + if (request->payload->len) { + kms_request_str_append (sreq, request->payload); + } + + kms_kv_list_destroy (lst); + return kms_request_str_detach (sreq); +} + void kms_request_free_string (char *ptr) { diff --git a/src/third_party/kms-message/src/kms_request_opt.c b/src/third_party/kms-message/src/kms_request_opt.c index f17bf3c3343..b0a184fad7a 100644 --- a/src/third_party/kms-message/src/kms_request_opt.c +++ b/src/third_party/kms-message/src/kms_request_opt.c @@ -56,4 +56,32 @@ kms_request_opt_set_crypto_hooks (kms_request_opt_t *opt, opt->crypto.sha256 = sha256; opt->crypto.sha256_hmac = sha256_hmac; opt->crypto.ctx = ctx; +} + +bool +kms_request_opt_set_provider (kms_request_opt_t *opt, + kms_request_provider_t provider) +{ + if (provider != KMS_REQUEST_PROVIDER_AWS && + provider != KMS_REQUEST_PROVIDER_AZURE && + provider != KMS_REQUEST_PROVIDER_GCP) { + return false; + } + opt->provider = provider; + return true; +} + +void +kms_request_opt_set_crypto_hook_sign_rsaes_pkcs1_v1_5 ( + kms_request_opt_t *opt, + bool (*sign_rsaes_pkcs1_v1_5) (void *sign_ctx, + const char *private_key, + size_t private_key_len, + const char *input, + size_t input_len, + unsigned char *signature_out), + void *sign_ctx) +{ + opt->crypto.sign_rsaes_pkcs1_v1_5 = sign_rsaes_pkcs1_v1_5; + opt->crypto.sign_ctx = sign_ctx; }
\ No newline at end of file diff --git a/src/third_party/kms-message/src/kms_request_opt_private.h b/src/third_party/kms-message/src/kms_request_opt_private.h index 5e971a9d890..8c25bdf2801 100644 --- a/src/third_party/kms-message/src/kms_request_opt_private.h +++ b/src/third_party/kms-message/src/kms_request_opt_private.h @@ -26,6 +26,7 @@ struct _kms_request_opt_t { bool connection_close; _kms_crypto_t crypto; + kms_request_provider_t provider; }; #endif /* KMS_REQUEST_OPT_PRIVATE_H */ diff --git a/src/third_party/kms-message/src/kms_request_str.c b/src/third_party/kms-message/src/kms_request_str.c index 0f7c19c9726..65207d2f4fa 100644 --- a/src/third_party/kms-message/src/kms_request_str.c +++ b/src/third_party/kms-message/src/kms_request_str.c @@ -51,10 +51,13 @@ kms_request_str_t * kms_request_str_new (void) { kms_request_str_t *s = malloc (sizeof (kms_request_str_t)); + KMS_ASSERT (s); s->len = 0; s->size = 16; s->str = malloc (s->size); + KMS_ASSERT (s->str); + s->str[0] = '\0'; return s; @@ -64,11 +67,15 @@ kms_request_str_t * kms_request_str_new_from_chars (const char *chars, ssize_t len) { kms_request_str_t *s = malloc (sizeof (kms_request_str_t)); + KMS_ASSERT (s); + size_t actual_len; actual_len = len < 0 ? strlen (chars) : (size_t) len; s->size = actual_len + 1; s->str = malloc (s->size); + KMS_ASSERT (s->str); + memcpy (s->str, chars, actual_len); s->str[actual_len] = '\0'; s->len = actual_len; @@ -86,6 +93,8 @@ kms_request_str_wrap (char *chars, ssize_t len) } s = malloc (sizeof (kms_request_str_t)); + KMS_ASSERT (s); + s->str = chars; s->len = len < 0 ? strlen (chars) : (size_t) len; @@ -148,8 +157,10 @@ kms_request_str_t * kms_request_str_dup (kms_request_str_t *str) { kms_request_str_t *dup = malloc (sizeof (kms_request_str_t)); + KMS_ASSERT (dup); + - dup->str = strndup (str->str, str->len); + dup->str = kms_strndup (str->str, str->len); dup->len = str->len; dup->size = str->len + 1; diff --git a/src/third_party/kms-message/src/kms_request_str.h b/src/third_party/kms-message/src/kms_request_str.h index f053a595aa7..4e33faa175f 100644 --- a/src/third_party/kms-message/src/kms_request_str.h +++ b/src/third_party/kms-message/src/kms_request_str.h @@ -25,11 +25,6 @@ #include <stdint.h> #include <string.h> -#if defined(_WIN32) -#include <basetsd.h> -typedef SSIZE_T ssize_t; -#endif // _WIN32 - typedef struct { char *str; size_t len; @@ -72,6 +67,8 @@ kms_request_str_append_lowercase (kms_request_str_t *str, KMS_MSG_EXPORT (void) kms_request_str_appendf (kms_request_str_t *str, const char *format, ...); KMS_MSG_EXPORT (void) +kms_request_strdupf (kms_request_str_t *str, const char *format, ...); +KMS_MSG_EXPORT (void) kms_request_str_append_escaped (kms_request_str_t *str, kms_request_str_t *appended, bool escape_slash); diff --git a/src/third_party/kms-message/src/kms_response.c b/src/third_party/kms-message/src/kms_response.c index 593e39fd3d9..c90e772b14f 100644 --- a/src/third_party/kms-message/src/kms_response.c +++ b/src/third_party/kms-message/src/kms_response.c @@ -37,4 +37,10 @@ kms_response_get_body (kms_response_t *response, size_t *len) *len = response->body->len; } return response->body->str; +} + +int +kms_response_get_status (kms_response_t *response) +{ + return response->status; }
\ No newline at end of file diff --git a/src/third_party/kms-message/src/kms_response_parser.c b/src/third_party/kms-message/src/kms_response_parser.c index 31e4868a68e..6f0c0487864 100644 --- a/src/third_party/kms-message/src/kms_response_parser.c +++ b/src/third_party/kms-message/src/kms_response_parser.c @@ -1,11 +1,13 @@ #include "kms_message/kms_response_parser.h" #include "kms_message_private.h" -#include "kms_message_private.h" +#include <errno.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> +#include "hexlify.h" + /* destroys the members of parser, but not the parser itself. */ static void _parser_destroy (kms_response_parser_t *parser) @@ -24,16 +26,21 @@ _parser_init (kms_response_parser_t *parser) parser->raw_response = kms_request_str_new (); parser->content_length = -1; parser->response = calloc (1, sizeof (kms_response_t)); + KMS_ASSERT (parser->response); parser->response->headers = kms_kv_list_new (); parser->state = PARSING_STATUS_LINE; parser->start = 0; parser->failed = false; + parser->chunk_size = 0; + parser->transfer_encoding_chunked = false; } kms_response_parser_t * kms_response_parser_new (void) { kms_response_parser_t *parser = malloc (sizeof (kms_response_parser_t)); + KMS_ASSERT (parser); + _parser_init (parser); return parser; } @@ -47,6 +54,12 @@ kms_response_parser_wants_bytes (kms_response_parser_t *parser, int32_t max) case PARSING_STATUS_LINE: case PARSING_HEADER: return max; + case PARSING_CHUNK_LENGTH: + return max; + case PARSING_CHUNK: + /* add 2 for trailing \r\n */ + return (parser->chunk_size + 2) - + ((int) parser->raw_response->len - parser->start); case PARSING_BODY: KMS_ASSERT (parser->content_length != -1); return parser->content_length - @@ -59,11 +72,26 @@ static bool _parse_int (const char *str, int *result) { char *endptr = NULL; + int64_t long_result; - *result = (int) strtol (str, &endptr, 10); - if (*endptr) { + errno = 0; + long_result = strtol (str, &endptr, 10); + if (endptr == str) { + /* No digits were parsed. Consider this an error */ + return false; + } + if (endptr != NULL && *endptr != '\0') { + /* endptr points to the first invalid character. */ + return false; + } + if (errno == EINVAL || errno == ERANGE) { + return false; + } + if (long_result > INT32_MAX || long_result < INT32_MIN) { return false; } + *result = (int) long_result; + return true; } @@ -72,6 +100,8 @@ static bool _parse_int_from_view (const char *str, int start, int end, int *result) { char *num_str = malloc (end - start + 1); + KMS_ASSERT (num_str); + bool ret; strncpy (num_str, str + start, end - start); @@ -81,6 +111,16 @@ _parse_int_from_view (const char *str, int start, int end, int *result) return ret; } +static bool +_parse_hex_from_view (const char *str, int len, int *result) +{ + *result = unhexlify (str, len); + if (*result < 0) { + return false; + } + return true; +} + /* returns true if char is "linear white space". This *ignores* the folding case * of CRLF followed by WSP. See https://stackoverflow.com/a/21072806/774658 */ static bool @@ -134,6 +174,9 @@ _parse_line (kms_response_parser_t *parser, int end) if (i == end) { /* empty line, this signals the start of the body. */ + if (parser->transfer_encoding_chunked) { + return PARSING_CHUNK_LENGTH; + } return PARSING_BODY; } @@ -181,9 +224,29 @@ _parse_line (kms_response_parser_t *parser, int end) return PARSING_DONE; } } + + if (0 == strcmp (key->str, "Transfer-Encoding")) { + if (0 == strcmp (val->str, "chunked")) { + parser->transfer_encoding_chunked = true; + } else { + KMS_ERROR (parser, "Unsupported Transfer-Encoding: %s", val->str); + kms_request_str_destroy (key); + kms_request_str_destroy (val); + return PARSING_DONE; + } + } kms_request_str_destroy (key); kms_request_str_destroy (val); return PARSING_HEADER; + } else if (parser->state == PARSING_CHUNK_LENGTH) { + int result = 0; + + if (!_parse_hex_from_view (raw + i, end - i, &result)) { + KMS_ERROR (parser, "Failed to parse hex chunk length."); + return PARSING_DONE; + } + parser->chunk_size = result; + return PARSING_CHUNK; } return PARSING_DONE; } @@ -194,7 +257,7 @@ kms_response_parser_feed (kms_response_parser_t *parser, uint32_t len) { kms_request_str_t *raw = parser->raw_response; - int curr, body_read; + int curr, body_read, chunk_read; curr = (int) raw->len; kms_request_str_append_chars (raw, (char *) buf, len); @@ -203,6 +266,7 @@ kms_response_parser_feed (kms_response_parser_t *parser, switch (parser->state) { case PARSING_STATUS_LINE: case PARSING_HEADER: + case PARSING_CHUNK_LENGTH: /* find the next \r\n. */ if (curr && strncmp (raw->str + (curr - 1), "\r\n", 2) == 0) { parser->state = _parse_line (parser, curr - 1); @@ -234,6 +298,28 @@ kms_response_parser_feed (kms_response_parser_t *parser, curr = (int) raw->len; break; + case PARSING_CHUNK: + chunk_read = (int) raw->len - parser->start; + /* check if we've read the full chunk and the trailing \r\n */ + if (chunk_read >= parser->chunk_size + 2) { + if (!parser->response->body) { + parser->response->body = kms_request_str_new (); + } + kms_request_str_append_chars (parser->response->body, + raw->str + parser->start, + parser->chunk_size); + curr = parser->start + parser->chunk_size + 2; + parser->start = curr; + if (parser->chunk_size == 0) { + /* last chunk. */ + parser->state = PARSING_DONE; + } else { + parser->state = PARSING_CHUNK_LENGTH; + } + } else { + curr = (int) raw->len; + } + break; case PARSING_DONE: KMS_ERROR (parser, "Unexpected extra HTTP content"); return false; diff --git a/src/third_party/scripts/kms_message_get_sources.sh b/src/third_party/scripts/kms_message_get_sources.sh index 6ad2fbb0e67..9567d9dde2a 100755 --- a/src/third_party/scripts/kms_message_get_sources.sh +++ b/src/third_party/scripts/kms_message_get_sources.sh @@ -18,7 +18,7 @@ if grep -q Microsoft /proc/version; then fi NAME=libmongocrypt -REVISION=59c8c17bbdfa1cf0fdec60cfdde73a437a868221 +REVISION=ebddc00f74874b8dcad8661f77d8ff51ded0318f if grep -q Microsoft /proc/version; then SRC_ROOT=$(wslpath -u $(powershell.exe -Command "Get-ChildItem Env:TEMP | Get-Content | Write-Host")) |