diff --git a/deps/picotest b/deps/picotest index a99858e0c..f390562fd 160000 --- a/deps/picotest +++ b/deps/picotest @@ -1 +1 @@ -Subproject commit a99858e0c33b97b24cd09ceae729f2be33ec01e1 +Subproject commit f390562fd4d6919807441721ec05b08f6d8c8d9c diff --git a/include/picotls/mbedtls.h b/include/picotls/mbedtls.h index 62fd382d3..b18b90109 100644 --- a/include/picotls/mbedtls.h +++ b/include/picotls/mbedtls.h @@ -60,9 +60,21 @@ extern ptls_key_exchange_algorithm_t *ptls_mbedtls_key_exchanges[]; void ptls_mbedtls_random_bytes(void *buf, size_t len); +int ptls_mbedtls_load_file(char const* file_name, unsigned char** buf, size_t* n); + int ptls_mbedtls_load_private_key(ptls_context_t *ctx, char const *pem_fname); void ptls_mbedtls_dispose_sign_certificate(ptls_sign_certificate_t *_self); +int ptls_mbedtls_sign_certificate(ptls_sign_certificate_t* _self, ptls_t* tls, ptls_async_job_t** async, + uint16_t* selected_algorithm, ptls_buffer_t* outbuf, ptls_iovec_t input, + const uint16_t* algorithms, size_t num_algorithms); + +int picoquic_mbedtls_get_certs_from_file(char const* pem_fname, ptls_iovec_t** vec, size_t* count); +int ptls_mbedtls_init_verify_certificate(ptls_context_t* ptls_ctx, char const* pem_fname); +void ptls_mbedtls_dispose_verify_certificate(ptls_context_t* ptls_ctx); + + + #ifdef __cplusplus } #endif diff --git a/lib/mbedtls_sign.c b/lib/mbedtls_sign.c index d2097c9aa..d49553761 100644 --- a/lib/mbedtls_sign.c +++ b/lib/mbedtls_sign.c @@ -33,6 +33,7 @@ #include #include #include +#include "mbedtls/x509_crt.h" #include #include #include @@ -50,9 +51,21 @@ typedef struct st_ptls_mbedtls_sign_certificate_t { const ptls_mbedtls_signature_scheme_t *schemes; } ptls_mbedtls_sign_certificate_t; + +typedef struct st_ptls_mbedtls_verify_certificate_t { + ptls_verify_certificate_t super; + mbedtls_x509_crt *trust_ca; + mbedtls_x509_crl *trust_crl; + int (*f_vrfy)(void*, mbedtls_x509_crt*, int, uint32_t*); + void* p_vrfy; +} ptls_mbedtls_verify_certificate_t; + static const unsigned char ptls_mbedtls_oid_ec_key[] = {0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01}; static const unsigned char ptls_mbedtls_oid_rsa_key[] = {0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01}; +#if 0 +/* Commented out for now, as EDDSA is not yet supported by MbedTLS */ static const unsigned char ptls_mbedtls_oid_ed25519[] = {0x2b, 0x65, 0x70}; +#endif static const ptls_mbedtls_signature_scheme_t rsa_signature_schemes[] = {{PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256, PSA_ALG_SHA_256}, {PTLS_SIGNATURE_RSA_PSS_RSAE_SHA384, PSA_ALG_SHA_384}, @@ -65,8 +78,11 @@ static const ptls_mbedtls_signature_scheme_t secp384r1_signature_schemes[] = { {PTLS_SIGNATURE_ECDSA_SECP384R1_SHA384, PSA_ALG_SHA_384}, {UINT16_MAX, PSA_ALG_NONE}}; static const ptls_mbedtls_signature_scheme_t secp521r1_signature_schemes[] = { {PTLS_SIGNATURE_ECDSA_SECP521R1_SHA512, PSA_ALG_SHA_512}, {UINT16_MAX, PSA_ALG_NONE}}; +#if 0 +/* Commented out for now, as EDDSA is not yet supported by MbedTLS */ static const ptls_mbedtls_signature_scheme_t ed25519_signature_schemes[] = {{PTLS_SIGNATURE_ED25519, PSA_ALG_NONE}, {UINT16_MAX, PSA_ALG_NONE}}; +#endif #if defined(MBEDTLS_PEM_PARSE_C) @@ -149,6 +165,9 @@ static int ptls_mbedtls_parse_ecdsa_field(const unsigned char *pem_buf, size_t p return ret; } +#if 0 +/* Commenting out, as EDDSA is no yet supported in MbedTLS */ + /* On input, key_index points at the "key information" in a * "private key" message. For EDDSA, this contains an * octet string carrying the key itself. On return, key index @@ -174,6 +193,7 @@ static int ptls_mbedtls_parse_eddsa_key(const unsigned char *pem_buf, size_t pem } return ret; } +#endif /* If using PKCS8 encoding, the "private key" field contains the * same "ecdsa field" found in PEM "EC PRIVATE KEY" files. We @@ -452,6 +472,8 @@ int ptls_mbedtls_sign_certificate(ptls_sign_certificate_t *_self, ptls_t *tls, p size_t hash_length = 0; + *selected_algorithm = scheme->scheme_id; + if (scheme->hash_algo == PSA_ALG_NONE) { hash_value = input.base; hash_length = input.len; @@ -725,6 +747,8 @@ int ptls_mbedtls_load_private_key(ptls_context_t *ctx, char const *pem_fname) if (ret == 0) { ret = ptls_mbedtls_set_ec_key_attributes(signer, key_length); } +#if 0 + /* Commenting out, as EDDSA is no yet supported in MbedTLS */ } else if (oid_length == sizeof(ptls_mbedtls_oid_ed25519) && memcmp(pem.private_buf + oid_index, ptls_mbedtls_oid_ed25519, sizeof(ptls_mbedtls_oid_ed25519)) == 0) { /* We recognized ED25519 -- PSA_ECC_FAMILY_TWISTED_EDWARDS -- PSA_ALG_ED25519PH */ @@ -732,6 +756,7 @@ int ptls_mbedtls_load_private_key(ptls_context_t *ctx, char const *pem_fname) psa_set_key_type(&signer->attributes, PSA_ECC_FAMILY_TWISTED_EDWARDS); ret = ptls_mbedtls_parse_eddsa_key(pem.private_buf, pem.private_buflen, &key_index, &key_length); psa_set_key_bits(&signer->attributes, 256); +#endif } else if (oid_length == sizeof(ptls_mbedtls_oid_rsa_key) && memcmp(pem.private_buf + oid_index, ptls_mbedtls_oid_rsa_key, sizeof(ptls_mbedtls_oid_rsa_key)) == 0) { /* We recognized RSA */ @@ -767,3 +792,459 @@ int ptls_mbedtls_load_private_key(ptls_context_t *ctx, char const *pem_fname) } return ret; } + + +/* Handling of certificates. +* Certificates in picotls are used both at the client and the server side. +* +* The server is programmed with a copy of the certificate chain linking +* the local key and identity to a certificate authority. Picotls formats +* that key and sends it as part of the "server hello". It is signed with +* the server key. +* +* The client is programmed with a list of trusted certificates. It should +* process the list received from the server and verifies that it does +* correctly link the server certificate to one of the certificates in the +* root list. +* +* Mbedtls documents a series of certificate related API in `x509_crt.h`. +* +* On the server side, we read the certificates from a PEM encoded +* file, and provide it to the server. +* +* For verify certificate, picotls uses a two phase API: +* +* - During initialization, prepare a "verify certificate callback" +* - During the handshake, picotls executes the callback. +* +* Picotls verifies certificates using the "verify_certificate" callback. +* +* if ((ret = tls->ctx->verify_certificate->cb(tls->ctx->verify_certificate, +* tls, server_name, &tls->certificate_verify.cb, +* &tls->certificate_verify.verify_ctx, certs, num_certs)) != 0) +* goto Exit; +* +* This is implemented using the function mbedtls_verify_certificate, +* documented during the initialization of the "cb" structure, +* ptls_mbedtls_verify_certificate_t. The function pointer is +* the first member of that structure, followed by other arguments. +* The callback structure is passed as the first argument in the +* callback, with type "self". +* +* The callback should return 0 if the certificate is good. The call may +* also set the value of tls->certificate_verify.cb and +* tls->certificate_verify.verify_ctx. If these are set, picotls will +* then use tls->certificate_verify.cb to verify that the TLS messages +* are properly signed using that certificate. +* +* The function mbedtls_verify_certificate is implemented using the +* function "mbedtls_x509_crt_verify", which has the following arguments: +* +* - A chain of certificates, starting from the server certificate and hopefully +* going all the way to one of the root certificates. In our code, this +* is obtained by parsing the "certs" argument provided by picotls, which +* is an iovec vector of length numcerts, with one entry per certificate in +* the CERTS parameter received from the server. +* - The chain of trusted certificate authorities. In our case, that list is +* initialized during the call to `ptls_mbedssl_init_verify_certificate`, +* loading certificates from a `root` file. +* - A certificate revocation list. We leave that parameter NULL for now. +* - The expected server name, a NULL terminated string. +* - A "verify" function pointer, and its argument. +* +* The call returns 0 (and flags set to 0) if the chain was verified and valid, +* MBEDTLS_ERR_X509_CERT_VERIFY_FAILED if the chain was verified but found to +* be invalid, in which case *flags will have one or more MBEDTLS_X509_BADCERT_XXX +* or MBEDTLS_X509_BADCRL_XXX flags set, or another error +* (and flags set to 0xffffffff) in case of a fatal error encountered +* during the verification process. +* +* The verify callback is a user-supplied callback that can clear / modify / add +* flags for a certificate. If set, the verification callback is called for each +* certificate in the chain (from the trust-ca down to the presented crt). +* The parameters for the callback are: (void *parameter, mbedtls_x509_crt *crt, +* int certificate_depth, int *flags). With the flags representing current flags +* for that specific certificate and the certificate depth from the bottom +* (Peer cert depth = 0). Function pointer and parameters can be set in the call +* to `ptls_mbedssl_init_verify_certificate`. +* +* If the certificate verification is successfull, the code sets the pointer +* and the context for the certificate_verify callback: +* +* struct { +* int (*cb)(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_iovec_t signature); +* void *verify_ctx; +* } certificate_verify; +* +* The structure "certificate_verify" is allocated as part of the PTLS context. We will +* allcoate a a "ptls_mbetls_certificate_verify_ctx_t" ctx as part of the +* +* The verify callback is implemented using `psa_verify_message`, which takes the following +* arguments: +* +* psa_status_t psa_verify_message(psa_key_id_t key, +* psa_algorithm_t alg, +* const uint8_t * input, +* size_t input_length, +* const uint8_t * signature, +* size_t signature_length); +* +* The public key ID will be set from the certificate proposed by the server. The +* input and length of the data to be signed are derived from the data parameter +* in the callback, and the signature and length from the signature parameter of +* the callback. The "alg" parameter of type "psa_algorithm_t" will have to +* be derived from the algo parameter of the callback, which is a 16 bit +* "signature scheme" (see +* https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-signaturescheme). +* +* Picotls will use that callback exactly once, then reset the callback +* pointer to NULL. It does not reset of free the "verify_ctx" -- if necessary, +* the value there should be reset after the first call. +*/ + +typedef struct st_mbedtls_message_verify_ctx_t { + psa_key_id_t key_id; +} mbedtls_message_verify_ctx_t; + +uint16_t mbedtls_verify_sign_algos[] = { + 0x0201, 0x0203, 0x0401, 0x0403, 0x501, 0x0503, 0x0601, 0x0603, + 0x0804, 0x0805, 0x0806, + 0xFFFF +}; + +static int mbedtls_verify_sign(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_iovec_t signature) +{ + /* Obtain the key parameters, etc. */ + int ret = 0; + psa_algorithm_t alg = PSA_ALG_NONE; + mbedtls_message_verify_ctx_t * message_verify_ctx = (mbedtls_message_verify_ctx_t*)verify_ctx; + + if (message_verify_ctx == NULL) { + ret = PTLS_ERROR_LIBRARY; + } else if (data.base != NULL) { + /* Picotls will call verify_sign with data.base == NULL when it + * only wants to clear the memory. This is not an error condition. */ + /* Find the PSA_ALG for the signature scheme is supported */ + switch (algo) { + case 0x0201: /* PTLS_SIGNATURE_RSA_PKCS1_SHA1 */ + alg = PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_1); + break; + case 0x0203: /* ecdsa_sha1 */ + alg = PSA_ALG_ECDSA(PSA_ALG_SHA_1); + break; + case 0x401: /* PTLS_SIGNATURE_RSA_PKCS1_SHA256 */ + alg = PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256); + break; + case 0x0403: /* PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256 */ + alg = PSA_ALG_ECDSA(PSA_ALG_SHA_256); + break; +#if 0 + /* For further study. These two algorithms might be available in MbedTLS */ + case 0x0420: /* rsa_pkcs1_sha256_legacy */ + break; + case 0x0520: /* rsa_pkcs1_sha384_legacy */ + break; +#endif + case 0x501: /* rsa_pkcs1_sha384 */ + alg = PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_384); + break; + case 0x0503: /* PTLS_SIGNATURE_ECDSA_SECP384R1_SHA384 */ + alg = PSA_ALG_ECDSA(PSA_ALG_SHA_384); + break; + case 0x0601: /* rsa_pkcs1_sha512 */ + alg = PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_512); + break; + case 0x0603: /* PTLS_SIGNATURE_ECDSA_SECP521R1_SHA512 */ + alg = PSA_ALG_ECDSA(PSA_ALG_SHA_512); + break; + case 0x0804: /* PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256 */ + alg = PSA_ALG_RSA_PSS(PSA_ALG_SHA_256); + break; + case 0x0805: /* PTLS_SIGNATURE_RSA_PSS_RSAE_SHA384 */ + alg = PSA_ALG_RSA_PSS(PSA_ALG_SHA_384); + break; + case 0x0806: /* PTLS_SIGNATURE_RSA_PSS_RSAE_SHA512 */ + alg = PSA_ALG_RSA_PSS(PSA_ALG_SHA_512); + break; +#if 0 + /* Commented out, as EDDSA is not supported yet in MbedTLS*/ + case 0x0807: /* PTLS_SIGNATURE_ED25519 */ + alg = PSA_ALG_ED25519PH; + break; + case 0x0808: /* PTLS_SIGNATURE_ED448 */ + alg = PSA_ALG_ED448PH; + break; +#endif + default: + break; + } + + if (alg == PSA_ALG_NONE) { + ret = PTLS_ALERT_ILLEGAL_PARAMETER; + } + else { + psa_status_t status = psa_verify_message(message_verify_ctx->key_id, alg, data.base, data.len, signature.base, signature.len); + + if (status != PSA_SUCCESS) { + switch (status) { + case PSA_ERROR_NOT_PERMITTED: /* The key does not have the PSA_KEY_USAGE_SIGN_MESSAGE flag, or it does not permit the requested algorithm. */ + ret = PTLS_ERROR_INCOMPATIBLE_KEY; + break; + case PSA_ERROR_INVALID_SIGNATURE: /* The calculation was performed successfully, but the passed signature is not a valid signature. */ + ret = PTLS_ALERT_DECRYPT_ERROR; + break; + case PSA_ERROR_NOT_SUPPORTED: + ret = PTLS_ALERT_ILLEGAL_PARAMETER; + break; + case PSA_ERROR_INSUFFICIENT_MEMORY: + ret = PTLS_ERROR_NO_MEMORY; + break; + default: + ret = PTLS_ERROR_LIBRARY; + break; + } + } + } + } + /* destroy the key because it is used only once. */ + if (message_verify_ctx != NULL) { + psa_destroy_key(message_verify_ctx->key_id); + free(message_verify_ctx); + } + return ret; +} + +static int mbedtls_verify_certificate(ptls_verify_certificate_t *_self, ptls_t *tls, const char *server_name, + int (**verifier)(void *, uint16_t, ptls_iovec_t, ptls_iovec_t), void **verify_data, ptls_iovec_t *certs, + size_t num_certs) +{ + size_t i; + int ret = 0; + ptls_mbedtls_verify_certificate_t *self = (ptls_mbedtls_verify_certificate_t *)_self; + mbedtls_x509_crt chain_head = { 0 }; + + *verifier = NULL; + *verify_data = NULL; + + /* If any certs are given, convert them to MbedTLS representation, then verify the cert chain. If no certs are given, just give + * the override_callback to see if we want to stay fail open. */ + if (num_certs == 0) { + ret = PTLS_ALERT_CERTIFICATE_REQUIRED; + } else { + mbedtls_x509_crt* previous_chain = &chain_head; + mbedtls_x509_crt_init(&chain_head); + + for (i = 0; i != num_certs; ++i) { + ret = mbedtls_x509_crt_parse_der(previous_chain, certs[i].base, certs[i].len); + if (i != 0) { + if (previous_chain->next == NULL) { + ret = PTLS_ALERT_BAD_CERTIFICATE; + break; + } + previous_chain = previous_chain->next; + } + } + + if (ret == 0) { + uint32_t flags = 0; + + int verify_ret = mbedtls_x509_crt_verify(&chain_head, self->trust_ca, self->trust_crl, server_name, &flags, + self->f_vrfy, self->p_vrfy); + + if (verify_ret != 0) { + switch (verify_ret) { + case MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: + /* if the chain was verified but found to be invalid, in which case + * flags will have one or more MBEDTLS_X509_BADCERT_XXX + * or MBEDTLS_X509_BADCRL_XXX flags set, or another + * error(and flags set to 0xffffffff) in case of a fatal error + * encountered during the verification process. */ + ret = PTLS_ALERT_BAD_CERTIFICATE; + break; + default: + ret = PTLS_ERROR_LIBRARY; + break; + } + } + } + + if (ret == 0) { + mbedtls_message_verify_ctx_t* message_verify_ctx = (mbedtls_message_verify_ctx_t*) + malloc(sizeof(mbedtls_message_verify_ctx_t)); + if (message_verify_ctx == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + } + else { + psa_status_t status; + psa_key_attributes_t attributes; + memset(&attributes, 0, sizeof(attributes)); + memset(message_verify_ctx, 0, sizeof(mbedtls_message_verify_ctx_t)); + + status = mbedtls_pk_get_psa_attributes(&chain_head.next->pk, PSA_KEY_USAGE_VERIFY_MESSAGE, &attributes); + if (status == PSA_SUCCESS) { + status = mbedtls_pk_import_into_psa(&chain_head.next->pk, &attributes, &message_verify_ctx->key_id); + } + if (status != PSA_SUCCESS) { + ret = PTLS_ERROR_LIBRARY; + free(message_verify_ctx); + } + else { + *verifier = mbedtls_verify_sign; + *verify_data = message_verify_ctx; + } + } + } + } + + if (chain_head.next != NULL) { + mbedtls_x509_crt_free(chain_head.next); + } + return ret; +} + +/* Read certificates from a file using MbedTLS functions. +* We only use the PEM function to parse PEM files, find +* up to 16 certificates, and convert the base64 encoded +* data to DER encoded binary. No attempt is made to verify +* that these actually are certificates. +* +* Discuss: picotls has a built in function for this. +* Is it really necessary to program an alternative? +*/ +int picoquic_mbedtls_get_certs_from_file(char const * pem_fname, ptls_iovec_t** pvec, size_t * count) +{ + int ret = 0; + *pvec = (ptls_iovec_t*)malloc(sizeof(ptls_iovec_t) * 16); + + *count = 0; + if (*pvec == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + } else { + size_t buf_length; + unsigned char* buf = NULL; + /* The load file function simply loads the file content in memory */ + if ((ret = ptls_mbedtls_load_file(pem_fname, &buf, &buf_length)) == 0) { + size_t length_already_read = 0; + + while (ret == 0 && *count < 16 && length_already_read < (size_t)buf_length) { + mbedtls_pem_context pem = { 0 }; + size_t length_read = 0; + + /* PEM context setup. */ + mbedtls_pem_init(&pem); + /* Read a buffer for PEM information and store the resulting data into the specified context buffers. */ + ret = mbedtls_pem_read_buffer(&pem, + "-----BEGIN CERTIFICATE-----", + "-----END CERTIFICATE-----", + buf + length_already_read, NULL, 0, &length_read); + if (ret == 0) { + /* Certificate was read successfully. PEM buffer contains the base64 value */ + uint8_t* cert = (uint8_t*)malloc(pem.private_buflen); + if (cert == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + } + else { + memcpy(cert, pem.private_buf, pem.private_buflen); + (*pvec)[*count].base = cert; + (*pvec)[*count].len = pem.private_buflen; + *count += 1; + } + } + mbedtls_pem_free(&pem); + length_already_read += length_read; + } + + free(buf); + } + } + return ret; +} + +int ptls_mbedtls_load_certificates(ptls_context_t *ctx, char const *cert_pem_file) +{ + return picoquic_mbedtls_get_certs_from_file(cert_pem_file, &ctx->certificates.list, + &ctx->certificates.count); +} + +/* Creating the call back. This API is not described by picotls. The "backend" +* merely has to provide a "certicate verifier" callback. We consider two ways of +* providing this callback: an API very close to the details of the MBedTLS code, +* with a list of explicit parameters, and a "portable" API whose only +* parameter is a file name for the list of trusted certificates. +*/ + +int ptls_mbedssl_init_verify_certificate_complete(ptls_context_t * ptls_ctx, + mbedtls_x509_crt* trust_ca, mbedtls_x509_crl* trust_crl, + int (*f_vrfy)(void*, mbedtls_x509_crt*, int, uint32_t*), void* p_vrfy) +{ + int ret = 0; + ptls_mbedtls_verify_certificate_t* verifier = + (ptls_mbedtls_verify_certificate_t*)malloc(sizeof(ptls_mbedtls_verify_certificate_t)); + if (verifier == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + } + else { + memset(verifier, 0, sizeof(ptls_mbedtls_verify_certificate_t)); + verifier->super.cb = mbedtls_verify_certificate; + verifier->super.algos = mbedtls_verify_sign_algos; /* list of supported algorithms, end with 0xFFFF */ + verifier->trust_ca = trust_ca; + verifier->trust_crl = trust_crl; + verifier->f_vrfy = f_vrfy; + verifier->p_vrfy = p_vrfy; + ptls_ctx->verify_certificate = &verifier->super; + } + return ret; +} + +int ptls_mbedtls_init_verify_certificate(ptls_context_t* ptls_ctx, char const* pem_fname) +{ + int ret = 0; + mbedtls_x509_crt* chain_head = (mbedtls_x509_crt*)malloc(sizeof(mbedtls_x509_crt)); + + if (chain_head == NULL) { + ret = PTLS_ERROR_NO_MEMORY; + } + else { + int psa_ret; + mbedtls_x509_crt_init(chain_head); + + psa_ret = mbedtls_x509_crt_parse_file(chain_head, pem_fname); + if (psa_ret == 0) { + ret = ptls_mbedssl_init_verify_certificate_complete(ptls_ctx, + chain_head, NULL, NULL, NULL); + } + else if (psa_ret > 0) { + /* some of the certificates could not parsed */ + ret = PTLS_ALERT_BAD_CERTIFICATE; + } + else if (psa_ret == PSA_ERROR_INSUFFICIENT_MEMORY) { + ret = PTLS_ERROR_NO_MEMORY; + } + else { + ret = PTLS_ERROR_LIBRARY; + } + + if (ret != 0 && chain_head != NULL) { + mbedtls_x509_crt_free(chain_head); + } + } + return ret; +} + +void ptls_mbedtls_dispose_verify_certificate(ptls_context_t* ptls_ctx) +{ + ptls_mbedtls_verify_certificate_t* verifier = + (ptls_mbedtls_verify_certificate_t*)ptls_ctx->verify_certificate; + if (verifier != NULL) { + if (verifier->trust_ca != NULL) { + mbedtls_x509_crt_free(verifier->trust_ca); + verifier->trust_ca = NULL; + } + if (verifier->trust_crl != NULL) { + mbedtls_x509_crl_free(verifier->trust_crl); + } + memset(verifier, 0, sizeof(ptls_mbedtls_verify_certificate_t)); + free(verifier); + ptls_ctx->verify_certificate = NULL; + } +} \ No newline at end of file diff --git a/t/assets/not_a_valid_pem_file.pem b/t/assets/not_a_valid_pem_file.pem new file mode 100644 index 000000000..db0784d5a --- /dev/null +++ b/t/assets/not_a_valid_pem_file.pem @@ -0,0 +1,736 @@ +#ifdef _WINDOWS +#include "wincompat.h" +#endif + +#include +#include +#include +#include +#include "mbedtls/build_info.h" +#if 0 +#include "psa/crypto.h" +#include "psa/crypto_struct.h" +#endif +#include "ptls_mbedtls.h" +#include "picotls/minicrypto.h" +#include "mbedtls/pk.h" +#include "mbedtls/pem.h" +#include "mbedtls/error.h" + +int test_random(); +int test_hash(ptls_hash_algorithm_t* algo, ptls_hash_algorithm_t* ref); +int test_label(ptls_hash_algorithm_t* hash, ptls_hash_algorithm_t* ref); +int test_cipher(ptls_cipher_algorithm_t* cipher, ptls_cipher_algorithm_t* cipher_ref); +int test_aead(ptls_aead_algorithm_t* algo, ptls_hash_algorithm_t* hash, ptls_aead_algorithm_t* ref, ptls_hash_algorithm_t* hash_ref); +int test_key_exchange(ptls_key_exchange_algorithm_t* client, ptls_key_exchange_algorithm_t* server); +int test_load_file(); +int test_load_key(); +int test_load_key_fail(); + +int main(int argc, char ** argv) +{ + ptls_cipher_algorithm_t* cipher_test[5] = { + &ptls_mbedtls_aes128ecb, + &ptls_mbedtls_aes128ctr, + &ptls_mbedtls_aes256ecb, + &ptls_mbedtls_aes256ctr, + &ptls_mbedtls_chacha20 + }; + ptls_cipher_algorithm_t* cipher_ref[5] = { + &ptls_minicrypto_aes128ecb, + &ptls_minicrypto_aes128ctr, + &ptls_minicrypto_aes256ecb, + &ptls_minicrypto_aes256ctr, + &ptls_minicrypto_chacha20 + }; + int ret = 0; +#ifdef _WINDOWS + printf("testing on WIndows.\n"); +#else + printf("Testing on Unix.\n"); +#endif + + /* Initialize the PSA crypto library. */ + if ((ret = ptls_mbedtls_init()) != 0) { + fprintf(stdout, "psa_crypto_init fails.\n"); + } + else { + ret = test_random(); + printf("test random returns: %d\n", ret); + + if (ret == 0) { + ret = test_hash(&ptls_mbedtls_sha256, &ptls_minicrypto_sha256); + printf("test hash returns: %d\n", ret); + } + + if (ret == 0) { + ret = test_label(&ptls_mbedtls_sha256, &ptls_minicrypto_sha256); + printf("test label returns: %d\n", ret); + } + + if (ret == 0) { + for (int i = 0; i < 5; i++) { + if (test_cipher(cipher_test[i], cipher_ref[i]) != 0) { + printf("test cipher %d fails\n", i); + ret = -1; + } + } + printf("test ciphers returns: %d\n", ret); + } + + if (ret == 0) { + ret = test_aead(&ptls_mbedtls_aes128gcm, &ptls_mbedtls_sha256, &ptls_minicrypto_aes128gcm, &ptls_minicrypto_sha256); + printf("test aeads returns: %d\n", ret); + } + + if (ret == 0) { + ret = test_key_exchange(&ptls_mbedtls_secp256r1, &ptls_minicrypto_secp256r1); + if (ret != 0) { + printf("test key exchange secp256r1 mbedtls to minicrypto fails\n"); + } + else { + ret = test_key_exchange(&ptls_minicrypto_secp256r1, &ptls_mbedtls_secp256r1); + if (ret != 0) { + printf("test key exchange secp256r1 minicrypto to mbedtls fails\n"); + } + } + ret = test_key_exchange(&ptls_mbedtls_x25519, &ptls_minicrypto_x25519); + if (ret != 0) { + printf("test key exchange x25519 mbedtls to minicrypto fails\n"); + } + else { + ret = test_key_exchange(&ptls_minicrypto_x25519, &ptls_mbedtls_x25519); + if (ret != 0) { + printf("test key exchange x25519 minicrypto to mbedtls fails\n"); + } + } + printf("test key exchange returns: %d\n", ret); + } + + if (ret == 0) { + ret = test_load_file(); + printf("test load file returns: %d\n", ret); + } + + if (ret == 0) { + ret = test_load_key(); + printf("test load key returns:0x%x\n", ret); + if (ret < 0 && ret != -1) { + char buf[256]; + buf[0] = 0; + mbedtls_strerror(ret, buf, sizeof(buf)); + printf("MbedTLS error -0x%x, %s\n", ret, buf); + } + } + + if (ret == 0) { + ret = test_load_key_fail(); + printf("test load key fail returns: %d\n", ret); + } + + /* Deinitialize the PSA crypto library. */ + ptls_mbedtls_free(); + } + + + return (ret == 0) ? 0 : -1; +} + +#define PTLS_MBEDTLS_RANDOM_TEST_LENGTH 1021 + +int test_random() +{ + /* The random test is just trying to check that we call the API properly. + * This is done by getting a vector of 1021 bytes, computing the sum of + * all values, and comparing to theoretical min and max, + * computed as average +- 8*standard deviation for sum of 1021 terms. + * 8 random deviations results in an extremely low probability of random + * failure. + * Note that this does not actually test the random generator. + */ + + uint8_t buf[PTLS_MBEDTLS_RANDOM_TEST_LENGTH]; + uint64_t sum = 0; + const uint64_t max_sum_1021 = 149505; + const uint64_t min_sum_1021 = 110849; + int ret = 0; + + ptls_mbedtls_random_bytes(buf, PTLS_MBEDTLS_RANDOM_TEST_LENGTH); + for (size_t i = 0; i < PTLS_MBEDTLS_RANDOM_TEST_LENGTH; i++) { + sum += buf[i]; + } + if (sum > max_sum_1021 || sum < min_sum_1021) { + ret = -1; + } + + return ret; +} + +int hash_trial(ptls_hash_algorithm_t* algo, const uint8_t* input, size_t len1, size_t len2, uint8_t* final_hash) +{ + int ret = 0; + ptls_hash_context_t* hash_ctx = algo->create(); + + hash_ctx->update(hash_ctx, input, len1); + if (len2 > 0) { + hash_ctx->update(hash_ctx, input + len1, len2); + } + hash_ctx->final(hash_ctx, final_hash, PTLS_HASH_FINAL_MODE_FREE); + + return ret; +} + +int hash_reset_trial(ptls_hash_algorithm_t* algo, const uint8_t* input, size_t len1, size_t len2, + uint8_t* hash1, uint8_t* hash2) +{ + int ret = 0; + ptls_hash_context_t* hash_ctx = algo->create(); + + hash_ctx->update(hash_ctx, input, len1); + hash_ctx->final(hash_ctx, hash1, PTLS_HASH_FINAL_MODE_RESET); + hash_ctx->update(hash_ctx, input + len1, len2); + hash_ctx->final(hash_ctx, hash2, PTLS_HASH_FINAL_MODE_FREE); + + return ret; +} + +int test_hash(ptls_hash_algorithm_t* algo, ptls_hash_algorithm_t* ref) +{ + int ret = 0; + uint8_t input[1234]; + uint8_t final_hash[32]; + uint8_t final_ref[32]; + uint8_t hash1[32], hash2[32], href1[32], href2[32]; + + memset(input, 0xba, sizeof(input)); + + ret = hash_trial(algo, input, sizeof(input), 0, final_hash); + if (ret == 0) { + ret = hash_trial(ref, input, sizeof(input), 0, final_ref); + } + if (ret == 0) { + if (memcmp(final_hash, final_ref, ref->digest_size) != 0) { + ret = -1; + } + } + if (ret == 0) { + ret = hash_trial(algo, input, sizeof(input) - 17, 17, final_hash); + } + if (ret == 0) { + if (memcmp(final_hash, final_ref, ref->digest_size) != 0) { + ret = -1; + } + } + + if (ret == 0) { + ret = hash_reset_trial(algo, input, sizeof(input) - 126, 126, hash1, hash2); + } + if (ret == 0) { + ret = hash_reset_trial(ref, input, sizeof(input) - 126, 126, href1, href2); + } + if (ret == 0) { + if (memcmp(hash1, href1, ref->digest_size) != 0) { + ret = -1; + } + else if (memcmp(hash2, href2, ref->digest_size) != 0) { + ret = -1; + } + } + + return ret; +} + +int cipher_trial(ptls_cipher_algorithm_t * cipher, const uint8_t * key, const uint8_t * iv, int is_enc, const uint8_t * v_in, uint8_t * v_out1, uint8_t * v_out2, size_t len) +{ + int ret = 0; + ptls_cipher_context_t* test_cipher = ptls_cipher_new(cipher, is_enc, key); + if (test_cipher == NULL) { + ret = -1; + } else { + if (test_cipher->do_init != NULL) { + ptls_cipher_init(test_cipher, iv); + } + ptls_cipher_encrypt(test_cipher, v_out1, v_in, len); + if (test_cipher->do_init != NULL) { + ptls_cipher_init(test_cipher, iv); + } + ptls_cipher_encrypt(test_cipher, v_out2, v_out1, len); + ptls_cipher_free(test_cipher); + } + + return ret; +} + +int test_cipher(ptls_cipher_algorithm_t * cipher, ptls_cipher_algorithm_t * cipher_ref) +{ + uint8_t key[32]; + uint8_t iv[16]; + uint8_t v_in[16]; + uint8_t v_out_1a[16], v_out_2a[16], v_out_1b[16], v_out_2b[16], v_out_1d[16], v_out_2d[16]; + int ret = 0; + + /* Set initial values */ + memset(key, 0x55, sizeof(key)); + memset(iv, 0x33, sizeof(iv)); + memset(v_in, 0xaa, sizeof(v_in)); + + /* Encryption test */ + ret = cipher_trial(cipher, key, iv, 1, v_in, v_out_1a, v_out_2a, 16); + if (ret == 0) { + ret = cipher_trial(cipher_ref, key, iv, 1, v_in, v_out_1b, v_out_2b, 16); + } + if (ret == 0) { + if (memcmp(v_out_1a, v_out_1b, 16) != 0) { + ret = -1; + } + else if (memcmp(v_out_2a, v_out_2b, 16) != 0) { + ret = -1; + } + } + /* decryption test */ + if (ret == 0) { + ret = cipher_trial(cipher, key, iv, 0, v_out_2a, v_out_1d, v_out_2d, 16); + } + if (ret == 0) { + if (memcmp(v_out_1a, v_out_1d, 16) != 0) { + ret = -1; + } + else if (memcmp(v_out_2d, v_in, 16) != 0) { + ret = -1; + } + } + + return ret; +} + +int label_test(ptls_hash_algorithm_t * hash, uint8_t * v_out, size_t o_len, const uint8_t * secret, + char const * label, char const * label_prefix) +{ + uint8_t h_val_v[32]; + ptls_iovec_t h_val = { 0 }; + ptls_iovec_t s_vec = { 0 }; + s_vec.base = (uint8_t *)secret; + s_vec.len = 32; + h_val.base = h_val_v; + h_val.len = 32; + memset(h_val_v, 0, sizeof(h_val_v)); + + ptls_hkdf_expand_label(hash, v_out, o_len, s_vec, label, h_val, label_prefix); + return 0; +} + +int test_label(ptls_hash_algorithm_t* hash, ptls_hash_algorithm_t* ref) +{ + int ret = 0; + uint8_t v_out[16], v_ref[16]; + uint8_t secret[32]; + char const* label = "label"; + char const* label_prefix = "label_prefix"; + memset(secret, 0x5e, sizeof(secret)); + + ret = label_test(hash, v_out, 16, secret, label, label_prefix); + + if (ret == 0) { + ret = label_test(ref, v_ref, 16, secret, label, label_prefix); + } + + if (ret == 0 && memcmp(v_out, v_ref, 16) != 0) { + ret = -1; + } + + return ret; +} + + +int aead_trial(ptls_aead_algorithm_t * algo, ptls_hash_algorithm_t * hash, const uint8_t * secret, int is_enc, + const uint8_t * v_in, size_t len, uint8_t * aad, size_t aad_len, uint64_t seq, uint8_t * v_out, size_t * o_len) +{ + int ret = 0; + ptls_aead_context_t* aead = ptls_aead_new(algo, hash, is_enc, secret, "test_aead"); + + if (aead == NULL) { + ret = -1; + } + else{ + if (is_enc) { + *o_len = ptls_aead_encrypt(aead, v_out, v_in, len, seq, aad, aad_len); + if (*o_len != len + algo->tag_size) { + ret = -1; + } + } + else { + *o_len = ptls_aead_decrypt(aead, v_out, v_in, len, seq, aad, aad_len); + if (*o_len != len - algo->tag_size) { + ret = -1; + } + } + ptls_aead_free(aead); + } + return ret; +} + +int test_aead(ptls_aead_algorithm_t* algo, ptls_hash_algorithm_t* hash, ptls_aead_algorithm_t* ref, ptls_hash_algorithm_t* hash_ref) +{ + uint8_t secret[32]; + uint8_t v_in[1234]; + uint8_t aad[17]; + uint8_t v_out_a[1250], v_out_b[1250], v_out_r[1250]; + size_t olen_a, olen_b, olen_r; + uint64_t seq = 12345; + int ret = 0; + + memset(secret, 0x58, sizeof(secret)); + memset(v_in, 0x12, sizeof(v_in)); + memset(aad, 0xaa, sizeof(aad)); + + ret = aead_trial(algo, hash, secret, 1, v_in, sizeof(v_in), aad, sizeof(aad), seq, v_out_a, &olen_a); + if (ret == 0) { + ret = aead_trial(ref, hash_ref, secret, 1, v_in, sizeof(v_in), aad, sizeof(aad), seq, v_out_b, &olen_b); + } + if (ret == 0 && (olen_a != olen_b || memcmp(v_out_a, v_out_b, olen_a) != 0)) { + ret = -1; + } + if (ret == 0) { + ret = aead_trial(ref, hash_ref, secret, 0, v_out_a, olen_a, aad, sizeof(aad), seq, v_out_r, &olen_r); + } + if (ret == 0 && (olen_r != sizeof(v_in) || memcmp(v_in, v_out_r, sizeof(v_in)) != 0)) { + ret = -1; + } + return ret; +} + +/* Test key exchanges. We copy paste the code from "test.h". +* in production, we should reuse this code. + */ + +int test_key_exchange(ptls_key_exchange_algorithm_t *client, ptls_key_exchange_algorithm_t *server) +{ + ptls_key_exchange_context_t *ctx; + ptls_iovec_t client_secret, server_pubkey, server_secret; + int f_ret; + int ret = 0; + + /* fail */ + if ((f_ret = server->exchange(server, &server_pubkey, &server_secret, (ptls_iovec_t) { NULL })) == 0) { + ret = -1; + } + if (ret == 0) { + /* perform ecdh */ + ret = client->create(client, &ctx); + if (ret == 0) { + ret = server->exchange(server, &server_pubkey, &server_secret, ctx->pubkey); + } + if (ret == 0) { + ret = ctx->on_exchange(&ctx, 1, &client_secret, server_pubkey); + } + if (ret == 0) { + if (client_secret.len != server_secret.len || + memcmp(client_secret.base, server_secret.base, client_secret.len) != 0) { + ret = -1; + } + } + } + + free(client_secret.base); + free(server_pubkey.base); + free(server_secret.base); + + if (ret == 0) { + /* client abort */ + ret = client->create(client, &ctx); + if (ret == 0) { + ret = ctx->on_exchange(&ctx, 1, NULL, ptls_iovec_init(NULL, 0)); + } + if (ctx != NULL) { + ret = -1; + } + } + + return ret; +} + +/* +Sign certificate has to implement a callback: + +if ((ret = tls->ctx->sign_certificate->cb( +tls->ctx->sign_certificate, tls, tls->is_server ? &tls->server.async_job : NULL, &algo, sendbuf, +ptls_iovec_init(data, datalen), signature_algorithms != NULL ? signature_algorithms->list : NULL, +signature_algorithms != NULL ? signature_algorithms->count : 0)) != 0) { + +or: + +static int sign_certificate(ptls_sign_certificate_t *_self, ptls_t *tls, ptls_async_job_t **async, uint16_t *selected_algorithm, +ptls_buffer_t *outbuf, ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms) + +The callback "super" type is ptls_sign_certificate_t, defined by the macro: +PTLS_CALLBACK_TYPE(int, sign_certificate, ptls_t *tls, ptls_async_job_t **async, uint16_t *selected_algorithm, +ptls_buffer_t *output, ptls_iovec_t input, const uint16_t *algorithms, size_t num_algorithms); + +The notation is simple: input buffer and supported algorithms as input, selected algo and output buffer as output. +Output buffer is already partially filled. + +For PSA/MbedTLS, see: +https://mbed-tls.readthedocs.io/en/latest/getting_started/psa/ +Using PSA, Signing a message with RSA provides the following sequence: + +-- Set key attributes -- +psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_HASH); +psa_set_key_algorithm(&attributes, PSA_ALG_RSA_PKCS1V15_SIGN_RAW); +psa_set_key_type(&attributes, PSA_KEY_TYPE_RSA_KEY_PAIR); +psa_set_key_bits(&attributes, 1024); + +-- Import the key -- +status = psa_import_key(&attributes, key, key_len, &key_id); +if (status != PSA_SUCCESS) { + printf("Failed to import key\n"); + return; +} + +-- Sign message using the key -- +status = psa_sign_hash(key_id, PSA_ALG_RSA_PKCS1V15_SIGN_RAW, + hash, sizeof(hash), + signature, sizeof(signature), + &signature_length); + +TODO: verify that Picotls does compute the hash before calling sign. +TODO: verify that there are "sign raw" implementations for ECDSA, EDDSA + +-- Verify hash: +psa_status_t psa_verify_hash(mbedtls_svc_key_id_t key, psa_algorithm_t alg, const uint8_t *hash, size_t hash_length, const uint8_t *signature, size_t signature_length) + +Load a key in memory + +int mbedtls_pk_parse_keyfile(mbedtls_pk_context* ctx, + const char* path, const char* pwd, + int (*f_rng)(void*, unsigned char*, size_t), void* p_rng); + +But before using the psa API, the key must be imported. That means the key has to +be expressed in the proper x509/DER format. + +*/ +#ifdef _WINDOWS +#define ASSET_DIR ..\\..\\data +#define ASSET_RSA_KEY "..\\..\\data\\rsa\\key.pem" +#define ASSET_RSA_PKCS8_KEY "..\\..\\data\\rsa-pkcs8\\key.pem" +#define ASSET_SECP256R1_KEY "..\\..\\data\\secp256r1\\key.pem" +#define ASSET_SECP384R1_KEY "..\\..\\data\\secp384r1\\key.pem" +#define ASSET_SECP521R1_KEY "..\\..\\data\\secp521r1\\key.pem" +#define ASSET_SECP256R1_PKCS8_KEY "..\\..\\data\\secp256r1-pkcs8\\key.pem" +#define ASSET_ED25519_KEY "..\\..\\data\\ed25519\\key.pem" +#define ASSET_NO_SUCH_FILE "..\\..\\data\\no_such_file.pem" +#define ASSET_NOT_A_PEM_FILE "..\\..\\data\\not_a_pem_file.pem" +#define ASSET_RSA_CERT "..\\..\\data\\rsa\\cert.pem" +#define ASSET_RSA_PKCS8_CERT "..\\..\\data\\rsa-pkcs8\\cert.pem" +#define ASSET_SECP256R1_CERT "..\\..\\data\\secp256r1\\cert.pem" +#define ASSET_SECP384R1_CERT "..\\..\\data\\secp384r1\\cert.pem" +#define ASSET_SECP521R1_CERT "..\\..\\data\\secp521r1\\cert.pem" +#define ASSET_SECP256R1_PKCS8_CERT "..\\..\\data\\secp256r1-pkcs8\\cert.pem" +#define ASSET_ED25519_CERT "..\\..\\data\\ed25519\\cert.pem" +#else +#define ASSET_DIR data +#define ASSET_RSA_KEY "data/rsa/key.pem" +#define ASSET_RSA_PKCS8_KEY "data/rsa-pkcs8/key.pem" +#define ASSET_SECP256R1_KEY "data/secp256r1/key.pem" +#define ASSET_SECP384R1_KEY "data/secp384r1/key.pem" +#define ASSET_SECP521R1_KEY "data/secp521r1/key.pem" +#define ASSET_SECP256R1_PKCS8_KEY "data/secp256r1-pkcs8/key.pem" +#define ASSET_ED25519_KEY "data/ed25519/key.pem" +#define ASSET_NO_SUCH_FILE "data/no_such_file.pem" +#define ASSET_NOT_A_PEM_FILE "data/not_a_pem_file.pem" +#endif + +int test_load_one_file(char const* path) +{ + size_t n; + unsigned char *buf; + int ret = ptls_mbedtls_load_file(path, &buf, &n); + if (ret != 0) { + printf("Cannot load file from: %s, ret = %d (0x%x, -0x%x)\n", path, ret, ret, (int16_t)-ret); + } + else if (n == 0) { + printf("File %s is empty\n", path); + ret = -1; + } + else if (buf[n] != 0) { + printf("Buffer from %s is not null terminated\n", path); + ret = -1; + } + if (buf != NULL) { + free(buf); + } + return ret; +} + +int test_load_file() +{ + int ret = 0; + if (ret == 0) { + ret = test_load_one_file(ASSET_RSA_KEY); + } + + if (ret == 0) { + ret = test_load_one_file(ASSET_SECP256R1_KEY); + } + + if (ret == 0) { + ret = test_load_one_file(ASSET_SECP384R1_KEY); + } + + if (ret == 0) { + ret = test_load_one_file(ASSET_SECP521R1_KEY); + } + + if (ret == 0) { + ret = test_load_one_file(ASSET_SECP256R1_PKCS8_KEY); + } + + if (ret == 0) { + ret = test_load_one_file(ASSET_RSA_PKCS8_KEY); + } + return ret; +} + +int test_load_one_key(char const* path) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + unsigned char hash[32]; + const unsigned char h0[32] = { + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32 + }; + ptls_context_t ctx = { 0 }; + + ret = ptls_mbedtls_load_private_key(&ctx, path); + if (ret != 0) { + printf("Cannot create load private key from: %s, ret = %d (0x%x, -0x%x)\n", path, ret, ret, (int16_t)-ret); + } + else if (ctx.sign_certificate == NULL) { + printf("Sign_certificate not set in ptls context for: %s\n", path); + ret = -1; + } + else { + /* Try to sign something */ + int ret; + ptls_mbedtls_sign_certificate_t* signer = (ptls_mbedtls_sign_certificate_t*) + (((unsigned char*)ctx.sign_certificate) - offsetof(struct st_ptls_mbedtls_sign_certificate_t, super)); + ptls_buffer_t outbuf; + uint8_t outbuf_smallbuf[256]; + ptls_iovec_t input = { hash, sizeof(hash) }; + uint16_t selected_algorithm = 0; + int num_algorithms = 0; + uint16_t algorithms[16]; + memcpy(hash, h0, 32); + while (signer->schemes[num_algorithms].scheme_id != UINT16_MAX && num_algorithms < 16) { + algorithms[num_algorithms] = signer->schemes[num_algorithms].scheme_id; + num_algorithms++; + } + + ptls_buffer_init(&outbuf, outbuf_smallbuf, sizeof(outbuf_smallbuf)); + + ret = ptls_mbedtls_sign_certificate(ctx.sign_certificate, NULL, NULL, &selected_algorithm, + &outbuf, input, algorithms, num_algorithms); + if (ret == 0) { + printf("Signed a message, key: %s, scheme: %x, signature size: %zu\n", path, selected_algorithm, outbuf.off); + } + else { + printf("Sign failed, key: %s, scheme: %x, signature size: %zu\n", path, selected_algorithm, outbuf.off); + } + ptls_buffer_dispose(&outbuf); + ptls_mbedtls_dispose_sign_certificate(&signer->super); + } + + return ret; +} + +int test_load_key() +{ + int ret = 0; + if (ret == 0) { + ret = test_load_one_key(ASSET_RSA_KEY); + } + + if (ret == 0) { + ret = test_load_one_key(ASSET_SECP256R1_KEY); + } + + if (ret == 0) { + ret = test_load_one_key(ASSET_SECP384R1_KEY); + } + + if (ret == 0) { + ret = test_load_one_key(ASSET_SECP521R1_KEY); + } + + if (ret == 0) { + ret = test_load_one_key(ASSET_SECP256R1_PKCS8_KEY); + } + + if (ret == 0) { + ret = test_load_one_key(ASSET_RSA_PKCS8_KEY); + } + +#if 0 + /* Commenting out ED25519 for now, not supported yet in MBEDTLS/PSA */ + if (ret == 0) { + ret = test_load_one_key(ASSET_ED25519_KEY); + } +#endif + return ret; +} + +/* +* Testing of failure modes. +* +* Testing the various reasons why loading of key should fail: +* - key file does not exist +* - key file is empty, no PEM keyword +* - key file does not contain a key (we use a cert file for that) +* - key file is for ED25559, which is not supported +*/ +int test_load_key_fail() +{ + int ret = 0; + + if (ret == 0 && test_load_one_key(ASSET_NO_SUCH_FILE) == 0) + { + ret = -1; + } + + if (ret == 0 && test_load_one_key(ASSET_NOT_A_PEM_FILE) == 0) + { + ret = -1; + } + + if (ret == 0 && test_load_one_key(ASSET_RSA_CERT) == 0) + { + ret = -1; + } + + if (ret == 0 && test_load_one_key(ASSET_ED25519_KEY) == 0) + { + ret = -1; + } + + return ret; +} + +/* +* End to end testing of signature and verifiers: +* The general scenario is: +* - prepare a signature of a test string using a simulated +* server programmed with a private key and a certificate +* list. +* - verify the signature in a simulated client programmed +* with a list of trusted certificates. +* +* The test has several configuration parameters: +* - the file names for the key, certificate list, and +* trusted certificates. +* - the implementation tested. One of the peers will +* be using MbedTLS, the other peer may be MbedTLS, +* openssl, or maybe minicrypto (for some scenario). +* +* This test only checks the "good" scenarios. +*/ diff --git a/t/mbedtls.c b/t/mbedtls.c index 1ea3416bf..d78855650 100644 --- a/t/mbedtls.c +++ b/t/mbedtls.c @@ -133,6 +133,66 @@ Output buffer is already partially filled. #define ASSET_SECP384R1_KEY "t/assets/secp384r1/key.pem" #define ASSET_SECP521R1_KEY "t/assets/secp521r1/key.pem" #define ASSET_SECP256R1_PKCS8_KEY "t/assets/secp256r1-pkcs8/key.pem" +#define ASSET_ED25519_KEY "t/assets/ed25519/key.pem" +#define ASSET_NO_SUCH_FILE "t/assets/no_such_file.pem" +#define ASSET_NOT_A_PEM_FILE "t/assets/not_a_valid_pem_file.pem" +#define ASSET_RSA_CERT "t/assets/rsa/cert.pem" +#define ASSET_RSA_PKCS8_CERT "t/assets/rsa-pkcs8/cert.pem" +#define ASSET_SECP256R1_CERT "t/assets/secp256r1/cert.pem" +#define ASSET_SECP384R1_CERT "t/assets/secp384r1/cert.pem" +#define ASSET_SECP521R1_CERT "t/assets/secp521r1/cert.pem" +#define ASSET_SECP256R1_PKCS8_CERT "t/assets/secp256r1-pkcs8/cert.pem" +#define ASSET_ED25519_CERT "t/assets/ed25519/cert.pem" +#define ASSET_TEST_CA "data/test-ca.crt" +#endif + +int test_load_one_file(char const* path) +{ + size_t n; + unsigned char *buf; + int ret = ptls_mbedtls_load_file(path, &buf, &n); + if (ret != 0) { + printf("Cannot load file from: %s, ret = %d (0x%x, -0x%x)\n", path, ret, ret, (int16_t)-ret); + } + else if (n == 0) { + printf("File %s is empty\n", path); + ret = -1; + } + else if (buf[n] != 0) { + printf("Buffer from %s is not null terminated\n", path); + ret = -1; + } + if (buf != NULL) { + free(buf); + } + return ret; +} + +static void test_load_file_key() +{ + int ret = test_load_file_key(ASSET_RSA_KEY); + if (ret != 0) { + ok(!"fail"); + return; + } + ok(!!"success"); +} + +static void test_load_file_cert() +{ + int ret = test_load_file_key(ASSET_SECP256R1_PKCS8_CERT); + if (ret != 0) { + ok(!"fail"); + return; + } + ok(!!"success"); +} + +void test_load_file() +{ + subtest("load file key", test_load_file_key()); + subtest("load file cert", test_load_file_cert); +} int test_load_one_der_key(char const *path) { @@ -244,7 +304,7 @@ static void test_load_rsa_pkcs8_key() ok(!!"success"); } -void test_sign_certificate(void) +void test_load_keys(void) { subtest("load rsa key", test_load_rsa_key); subtest("load secp256r1 key", test_load_secp256r1_key); @@ -256,6 +316,396 @@ void test_sign_certificate(void) /* we do not test EDDSA keys, because they are not yet supported */ } +static void test_load_key_no_such_file() +{ + int ret = test_load_one_der_key(ASSET_NO_SUCH_FILE); + if (ret == 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +static void test_load_key_not_a_pem_file() +{ + int ret = test_load_one_der_key(ASSET_NOT_A_PEM_FILE); + if (ret == 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +static void test_load_key_not_a_key_file() +{ + int ret = test_load_one_der_key(ASSET_RSA_CERT); + if (ret == 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +static void test_load_key_not_supported() +{ + int ret = test_load_one_der_key(ASSET_ED25519_KEY); + if (ret == 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +/* +* Testing of failure modes. +* +* Testing the various reasons why loading of key should fail: +* - key file does not exist +* - key file is empty, no PEM keyword +* - key file does not contain a key (we use a cert file for that) +* - key file is for ED25559, which is not supported +*/ + +static void test_load_key_fail() +{ + subtest("load key no such file", test_load_key_no_such_file); + subtest("load key not a PEM file", test_load_key_not_a_pem_file); + subtest("load key not a key file", test_load_key_not_a_key_file); + subtest("load key not supported", test_load_key_not_supported); +} + +/* +* End to end testing of signature and verifiers: +* The general scenario is: +* - prepare a signature of a test string using a simulated +* server programmed with a private key and a certificate +* list. +* - verify the signature in a simulated client programmed +* with a list of trusted certificates. +* +* The test is configured with the file names for the key, +* certificate list, and trusted certificates. +* +* Ideally, we should be able to run the test by mixing and +* matching mbedtls server or clients with other backends. +* However, using openssl will require some plumbing, +* which will be done when integrating this code in +* picotls. For now, we will only do self tests, and test with +* minicrypto if the key is supported. +* +* Consider breaking out parts of this test in separate subtests, +* e.g., load certificate chain, verify certificate chain, +* extract key from certificare chain, verify signature. +*/ + +static const unsigned char test_sign_verify_message[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 , 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64 +}; +const size_t test_sign_verify_message_size = sizeof(test_sign_verify_message); + +static uint16_t test_sign_signature_algorithms[] = { + 0x0401, 0x0403, 0x501, 0x0503, 0x0601, 0x0603, + 0x0804, 0x0805, 0x0806, 0x0807, 0x0808 +}; + +static size_t num_test_sign_signature_algorithms = sizeof(test_sign_signature_algorithms) / sizeof(uint16_t); + +char const* test_sign_server_name = "test.example.com"; + +static int test_sign_init_server_mbedtls(ptls_context_t* ctx, char const* key_path, char const* cert_path) +{ + int ret = ptls_mbedtls_load_private_key(ctx, key_path); + if (ret == 0) { + ret = picoquic_mbedtls_get_certs_from_file(cert_path, &ctx->certificates.list, &ctx->certificates.count); + } + return ret; +} + +static int test_sign_init_server_minicrypto(ptls_context_t* ctx, char const* key_path, char const* cert_path) +{ + int ret = ptls_minicrypto_load_private_key(ctx, key_path); + if (ret == 0) { + ret = ptls_load_certificates(ctx, cert_path); + } + return ret; +} + +static void test_sign_free_certificates(ptls_context_t* ctx) +{ + if (ctx->certificates.list != NULL) { + for (int i = 0; i < ctx->certificates.count; i++) { + free(ctx->certificates.list[i].base); + } + free(ctx->certificates.list); + } + ctx->certificates.list = NULL; + ctx->certificates.count = 0; +} + +static void test_sign_free_context(ptls_context_t* ctx, int config) +{ + /* Free the server context */ + if (ctx == NULL) { + return; + } + test_sign_free_certificates(ctx); + if (ctx->sign_certificate != NULL) { + switch (config) { + case 0: + ptls_mbedtls_dispose_sign_certificate(ctx->sign_certificate); + break; + case 1: + default: + free(ctx->sign_certificate); + ctx->sign_certificate = NULL; + } + } + + if (ctx->verify_certificate != NULL) { + switch (config) { + case 0: + ptls_mbedtls_dispose_verify_certificate(ctx); + break; + default: + break; + } + } + + free(ctx); +} + +static ptls_context_t* test_sign_set_ptls_context(char const* key_path, char const* cert_path, char const* trusted_path, int is_server, int config) +{ + int ret = 0; + ptls_context_t* ctx = (ptls_context_t*)malloc(sizeof(ptls_context_t)); + if (ctx == NULL) { + return NULL; + } + + memset(ctx, 0, sizeof(ptls_context_t)); + ctx->get_time = &ptls_get_time; + + switch (config) { + case 0: + ctx->random_bytes = ptls_mbedtls_random_bytes; + case 1: + default: + break; + } + + if (is_server) { + /* First, create the "signer" plug-in */ + switch (config) { + case 0: /* MbedTLS */ + ret = test_sign_init_server_mbedtls(ctx, key_path, cert_path); + break; + case 1: /* Minicrypto */ + ret = test_sign_init_server_minicrypto(ctx, key_path, cert_path); + break; + default: + ret = -1; + break; + } + } + else { + /* Initialize the client verify context */ + switch (config) { + case 0: /* MbedTLS */ + ret = ptls_mbedtls_init_verify_certificate(ctx, trusted_path); + break; + default: + ret = -1; + break; + } + } + + if (ret != 0) { + /* Release and return NULL */ + test_sign_free_context(ctx, config); + ctx = NULL; + } + return ctx; +} + +static int test_sign_verify_one(char const* key_path, char const * cert_path, char const * trusted_path, int server_config, int client_config) +{ + int ret = 0; + ptls_context_t* server_ctx = test_sign_set_ptls_context(key_path, cert_path, trusted_path, 1, server_config); + ptls_context_t* client_ctx = test_sign_set_ptls_context(key_path, cert_path, trusted_path, 0, client_config); + ptls_t* client_tls = NULL; + ptls_t* server_tls = NULL; + uint16_t selected_algorithm = 0; + uint8_t signature_smallbuf[256]; + ptls_buffer_t signature; + struct { + int (*cb)(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_iovec_t signature); + void *verify_ctx; + } certificate_verify; + ptls_iovec_t input; + input.base = (uint8_t *)test_sign_verify_message; + input.len = test_sign_verify_message_size; + + ptls_buffer_init(&signature, signature_smallbuf, sizeof(signature_smallbuf)); + + if (server_ctx == NULL || client_ctx == NULL) { + ret = -1; + } + + if (ret == 0) { + /* Then, create a tls context for the server. */ + server_tls = ptls_new(server_ctx, 1); + if (server_tls == NULL) { + ret = -1; + } + } + + if (ret == 0) { + /* Then, create the signature messages */ + ret = server_ctx->sign_certificate->cb(server_ctx->sign_certificate, server_tls, NULL, + &selected_algorithm, &signature, input, + test_sign_signature_algorithms, num_test_sign_signature_algorithms); + if (ret != 0) { + printf("sign_certificate (%s) returns 0x%x\n", key_path, ret); + } + } + + if (ret == 0) { + /* Then, create a tls context for the client. */ + client_tls = ptls_new(client_ctx, 0); + if (client_tls == NULL) { + ret = -1; + } + } + + if (ret == 0) { + /* verify the certificates */ + ret = client_ctx->verify_certificate->cb(client_ctx->verify_certificate, client_tls, test_sign_server_name, + &certificate_verify.cb, &certificate_verify.verify_ctx, + server_ctx->certificates.list, server_ctx->certificates.count); + if (ret != 0) { + printf("verify_certificate (%s) returns 0x%x\n", cert_path, ret); + } + /* verify the signature */ + if (ret == 0) { + ptls_iovec_t sig; + sig.base = signature.base; + sig.len = signature.off; + + ret = certificate_verify.cb(certificate_verify.verify_ctx, selected_algorithm, input, sig); + if (ret != 0) { + printf("verify_signature (%s) returns 0x%x\n", key_path, ret); + } + } + else if (certificate_verify.cb != NULL) { + ptls_iovec_t empty; + empty.base = NULL; + empty.len = 0; + (void)certificate_verify.cb(certificate_verify.verify_ctx, 0, empty, empty); + } + } + if (ret == 0) { + printf("verify_signature (%s) and cert (%s) succeeds\n", key_path, cert_path); + } + + ptls_buffer_dispose(&signature); + + if (client_tls != NULL) { + ptls_free(client_tls); + } + if (server_tls != NULL) { + ptls_free(server_tls); + } + + test_sign_free_context(server_ctx, server_config); + test_sign_free_context(client_ctx, client_config); + + return ret; +} + +static void test_sign_verify_rsa_mbedtls_mbedtls() +{ + int ret = test_sign_verify_one(ASSET_RSA_KEY, ASSET_RSA_CERT, ASSET_TEST_CA, 0, 0); + if (ret != 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +static void test_sign_verify_rsa_mbedtls_mbedtls() +{ + int ret = test_sign_verify_one(ASSET_RSA_KEY, ASSET_RSA_CERT, ASSET_TEST_CA, 0, 0); + if (ret != 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +static void test_sign_verify_secp256r1_mbedtls_mbedtls() +{ + ret = test_sign_verify_one(ASSET_SECP256R1_KEY, ASSET_SECP256R1_CERT, ASSET_SECP256R1_CERT, 0, 0); + if (ret != 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +static void test_sign_verify_secp384r1_mbedtls_mbedtls() +{ + int ret = test_sign_verify_one(ASSET_SECP384R1_KEY, ASSET_SECP384R1_CERT, ASSET_SECP384R1_CERT, 0, 0); + if (ret != 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +static void test_sign_verify_secp521r1_mbedtls_mbedtls() +{ + ret = test_sign_verify_one(ASSET_SECP521R1_KEY, ASSET_SECP521R1_CERT, ASSET_SECP521R1_CERT, 0, 0); + if (ret != 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +static void test_sign_verify_secp256r1_pkcs8_mbedtls_mbedtls() +{ + ret = test_sign_verify_one(ASSET_SECP256R1_PKCS8_KEY, ASSET_SECP256R1_PKCS8_CERT, ASSET_SECP256R1_PKCS8_CERT, 0, 0); + if (ret != 0){ + ok(!"fail"); + return; + } + ok(!!"success"); +} + +/* TODO: all these tests are failing, because we do not have the +* proper combination of hostname and certificate. Fix that, then +* enable the test. +* +* TODO: add tests of minicrypto server and mbedtls client. +* TODO: add tests of mbedtls versus openssl. +* TODO: add negative testing. + */ + +static void test_sign_verify() +{ + subtest("sign verify rsa mbedtls mbedtls", test_sign_verify_rsa_mbedtls_mbedtls); + subtest("sign verify secp256r1 mbedtls mbedtls", test_sign_verify_secp256r1_mbedtls_mbedtls); + subtest("sign verify secp384r1 mbedtls mbedtls", test_sign_verify_secp384r1_mbedtls_mbedtls); + subtest("sign verify secp521r1 mbedtls mbedtls", test_sign_verify_secp521r1_mbedtls_mbedtls); + subtest("sign verify secp256r1 pkcs8 mbedtls mbedtls", test_sign_verify_secp256r1_pkcs8_mbedtls_mbedtls); + + return ret; +} + + DEFINE_FFX_AES128_ALGORITHMS(mbedtls); DEFINE_FFX_CHACHA20_ALGORITHMS(mbedtls); @@ -310,8 +760,17 @@ int main(int argc, char **argv) ctx_peer = &mbedtls_ctx; subtest("minicrypto vs.", test_picotls); - /* test the sign certificate */ - subtest("sign certificate", test_sign_certificate); + /* Test loading file in memory */ + subtest("test load file", test_load_file); + + /* test loading of keys in memory and capability to sign */ + subtest("test load keys", test_load_keys); + + /* Test that loading bad files or bad keys fails */ + subtest("test load key failures", test_load_key_fail); + + /* End to end test of signing and verifying certicates */ + subtest("test sign verify certiciates", test_load_key_fail); /* Deinitialize the PSA crypto library. */ mbedtls_psa_crypto_free();