diff --git a/crypto_libraries/inc/nx_crypto_rsa.h b/crypto_libraries/inc/nx_crypto_rsa.h index 587d98588..4d81a26f3 100644 --- a/crypto_libraries/inc/nx_crypto_rsa.h +++ b/crypto_libraries/inc/nx_crypto_rsa.h @@ -122,6 +122,12 @@ UINT _nx_crypto_rsa_pss_verify(const UCHAR *message_hash, UINT hash_length, VOID *hash_metadata, ULONG hash_metadata_size, UCHAR *scratch, UINT scratch_length); +UINT _nx_crypto_rsa_pss_sign(const UCHAR *message_hash, UINT hash_length, + UCHAR *em, UINT em_bits, + const NX_CRYPTO_METHOD *hash_method, + VOID *hash_metadata, ULONG hash_metadata_size, + UCHAR *scratch, UINT scratch_length); + #ifdef __cplusplus } #endif diff --git a/crypto_libraries/src/nx_crypto_rsa.c b/crypto_libraries/src/nx_crypto_rsa.c index 550fc66e8..b01fd516e 100644 --- a/crypto_libraries/src/nx_crypto_rsa.c +++ b/crypto_libraries/src/nx_crypto_rsa.c @@ -752,3 +752,208 @@ static const UCHAR _pss_zero8[8] = {0, 0, 0, 0, 0, 0, 0, 0}; return(NX_CRYPTO_SUCCESS); } + +/**************************************************************************/ +/* */ +/* FUNCTION RELEASE */ +/* */ +/* _nx_crypto_rsa_pss_sign PORTABLE C */ +/* 6.4.3 */ +/* DESCRIPTION */ +/* */ +/* Builds an EMSA-PSS-ENCODE octet string (RFC 8017 §9.1.1). */ +/* Caller is expected to run the RSA private-key operation on the */ +/* returned EM to produce the wire signature. */ +/* Assumes salt length == hash length (required by RFC 8446 §4.2.3). */ +/* Salt bytes are produced internally via NX_CRYPTO_RAND(). */ +/* */ +/* INPUT */ +/* */ +/* message_hash Pre-computed mHash over the signed content */ +/* hash_length hLen = byte length of mHash */ +/* em Output buffer; receives the encoded message */ +/* em_bits emBits = modulus_bits - 1 */ +/* hash_method Same hash used to build the PSS encoding */ +/* hash_metadata Scratch memory for hash operations */ +/* hash_metadata_size Size of hash_metadata in bytes */ +/* scratch Work buffer; must be >= db_len bytes */ +/* scratch_length Size of scratch in bytes */ +/* */ +/* OUTPUT */ +/* */ +/* NX_CRYPTO_SUCCESS EM built successfully */ +/* NX_CRYPTO_NOT_SUCCESSFUL em_len smaller than minimum */ +/* NX_CRYPTO_INVALID_BUFFER_SIZE Scratch too small */ +/* */ +/**************************************************************************/ +UINT _nx_crypto_rsa_pss_sign(const UCHAR *message_hash, UINT hash_length, + UCHAR *em, UINT em_bits, + const NX_CRYPTO_METHOD *hash_method, + VOID *hash_metadata, ULONG hash_metadata_size, + UCHAR *scratch, UINT scratch_length) +{ +UINT em_len; +UINT db_len; +UINT s_len; +UINT i; +UINT status; +UCHAR zero_bits; +UCHAR *db; +UCHAR *salt; +UCHAR *h; +VOID *handler = NX_CRYPTO_NULL; +static const UCHAR _pss_zero8[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + + /* emLen = ceil(emBits / 8). */ + em_len = (em_bits + 7u) >> 3; + + /* TLS 1.3 mandates salt length == hash length (RFC 8446 §4.2.3). */ + s_len = hash_length; + + if (em_len < (hash_length + s_len + 2u)) + { + return(NX_CRYPTO_NOT_SUCCESSFUL); + } + + db_len = em_len - hash_length - 1u; + + /* scratch layout: db[db_len] | salt[s_len]. The salt is also copied + * into DB at offset (db_len - s_len), so keeping a separate copy lets + * us hash it BEFORE building maskedDB. */ + if (scratch_length < (db_len + s_len)) + { + return(NX_CRYPTO_INVALID_BUFFER_SIZE); + } + + db = scratch; + salt = scratch + db_len; + h = em + db_len; /* H sits at em[db_len..em_len-2]. */ + + /* Step 4 – generate the random salt. NX_CRYPTO_RAND() is the same + * source ECDSA uses for its nonce, so the entropy is at least as good + * as the existing signature paths on this device. */ + for (i = 0u; i < s_len; i++) + { + salt[i] = (UCHAR)((UINT)NX_CRYPTO_RAND() & 0xFFu); + } + + /* Steps 5-6 – H = Hash(0x00^8 || mHash || salt). Write directly into + * em[db_len..] so we don't have to copy H around later. */ + if (hash_method -> nx_crypto_init) + { + status = hash_method -> nx_crypto_init((NX_CRYPTO_METHOD *)hash_method, + NX_CRYPTO_NULL, 0, + &handler, + hash_metadata, hash_metadata_size); + if (status != NX_CRYPTO_SUCCESS) + { + return(status); + } + } + + status = hash_method -> nx_crypto_operation(NX_CRYPTO_HASH_INITIALIZE, + handler, (NX_CRYPTO_METHOD *)hash_method, + NX_CRYPTO_NULL, 0, + NX_CRYPTO_NULL, 0, NX_CRYPTO_NULL, + NX_CRYPTO_NULL, 0, + hash_metadata, hash_metadata_size, + NX_CRYPTO_NULL, NX_CRYPTO_NULL); + if (status != NX_CRYPTO_SUCCESS) + { + return(status); + } + + /* Hash 8 zero bytes. */ + status = hash_method -> nx_crypto_operation(NX_CRYPTO_HASH_UPDATE, + handler, (NX_CRYPTO_METHOD *)hash_method, + NX_CRYPTO_NULL, 0, + (UCHAR *)_pss_zero8, 8, + NX_CRYPTO_NULL, + NX_CRYPTO_NULL, 0, + hash_metadata, hash_metadata_size, + NX_CRYPTO_NULL, NX_CRYPTO_NULL); + if (status != NX_CRYPTO_SUCCESS) + { + return(status); + } + + /* Hash mHash. */ + status = hash_method -> nx_crypto_operation(NX_CRYPTO_HASH_UPDATE, + handler, (NX_CRYPTO_METHOD *)hash_method, + NX_CRYPTO_NULL, 0, + (UCHAR *)message_hash, (ULONG)hash_length, + NX_CRYPTO_NULL, + NX_CRYPTO_NULL, 0, + hash_metadata, hash_metadata_size, + NX_CRYPTO_NULL, NX_CRYPTO_NULL); + if (status != NX_CRYPTO_SUCCESS) + { + return(status); + } + + /* Hash salt. */ + status = hash_method -> nx_crypto_operation(NX_CRYPTO_HASH_UPDATE, + handler, (NX_CRYPTO_METHOD *)hash_method, + NX_CRYPTO_NULL, 0, + salt, (ULONG)s_len, + NX_CRYPTO_NULL, + NX_CRYPTO_NULL, 0, + hash_metadata, hash_metadata_size, + NX_CRYPTO_NULL, NX_CRYPTO_NULL); + if (status != NX_CRYPTO_SUCCESS) + { + return(status); + } + + status = hash_method -> nx_crypto_operation(NX_CRYPTO_HASH_CALCULATE, + handler, (NX_CRYPTO_METHOD *)hash_method, + NX_CRYPTO_NULL, 0, + NX_CRYPTO_NULL, 0, NX_CRYPTO_NULL, + h, (ULONG)hash_length, + hash_metadata, hash_metadata_size, + NX_CRYPTO_NULL, NX_CRYPTO_NULL); + if (status != NX_CRYPTO_SUCCESS) + { + return(status); + } + + /* Steps 7-8 – DB = PS (zeros) || 0x01 || salt. */ + for (i = 0u; i < db_len - s_len - 1u; i++) + { + db[i] = 0x00u; + } + db[db_len - s_len - 1u] = 0x01u; + NX_CRYPTO_MEMCPY(&db[db_len - s_len], salt, s_len); /* Use case of memcpy is verified. */ + + /* Step 9 – dbMask = MGF1(H, db_len). Write directly into em[0..db_len-1]. */ + status = _nx_crypto_rsa_pss_mgf1(hash_method, hash_metadata, hash_metadata_size, + h, hash_length, em, db_len); + if (status != NX_CRYPTO_SUCCESS) + { + return(status); + } + + /* Step 10 – maskedDB = DB XOR dbMask (em[0..db_len-1] currently holds dbMask). */ + for (i = 0u; i < db_len; i++) + { + em[i] ^= db[i]; + } + + /* Step 11 – zero the top (8*emLen - emBits) bits of maskedDB[0]. */ + zero_bits = (UCHAR)(8u * em_len - em_bits); + if (zero_bits) + { + em[0] &= (UCHAR)(0xFFu >> zero_bits); + } + + /* Step 12 – EM = maskedDB || H || 0xBC. H is already in place; trailer last. */ + em[em_len - 1u] = 0xBCu; + +#ifdef NX_SECURE_KEY_CLEAR + NX_CRYPTO_MEMSET(db, 0, db_len); + NX_CRYPTO_MEMSET(salt, 0, s_len); +#endif + + return(NX_CRYPTO_SUCCESS); +} + diff --git a/nx_secure/src/nx_secure_tls_process_certificate_request.c b/nx_secure/src/nx_secure_tls_process_certificate_request.c index d62e4da46..8c03e9b6f 100644 --- a/nx_secure/src/nx_secure_tls_process_certificate_request.c +++ b/nx_secure/src/nx_secure_tls_process_certificate_request.c @@ -120,12 +120,18 @@ UINT extension_type; { tls_session -> nx_secure_tls_signature_algorithm = 0; - /* TLS1.3 uses RSASSA-PSS instead of RSASSA-PKCS. RSASSA-PSS is not supported now. */ + /* TLS 1.3 + RSA cert: announce RSASSA-PSS (RFC 8446 §4.2.3 — PKCS#1 v1.5 is banned for + * CertificateVerify in TLS 1.3). The send_certificate_verify path builds the EMSA-PSS-ENCODE + * via _nx_crypto_rsa_pss_sign. We pick rsa_pss_rsae_sha256 (0x0804) unconditionally — it's + * the only PSS sigalg our nx_crypto_rsa.c currently supports for signing, and any modern + * broker (mosquitto/OpenSSL) advertises it in CertificateRequest.sig_algs. */ if (local_certificate -> nx_secure_x509_public_algorithm == NX_SECURE_TLS_X509_TYPE_RSA) { - return(NX_SECURE_TLS_UNSUPPORTED_CERT_SIGN_TYPE); + expected_cert_type = NX_SECURE_TLS_CERT_TYPE_RSA_SIGN; + expected_sign_alg = 0x0804u; /* rsa_pss_rsae_sha256 */ } - + else + { /* In TLS 1.3, the signing curve is constrained. */ switch (local_certificate -> nx_secure_x509_private_key.ec_private_key.nx_secure_ec_named_curve) { @@ -141,6 +147,7 @@ UINT extension_type; default: return(NX_SECURE_TLS_UNSUPPORTED_CERT_SIGN_ALG); } + } } else #endif diff --git a/nx_secure/src/nx_secure_tls_send_certificate_verify.c b/nx_secure/src/nx_secure_tls_send_certificate_verify.c index 35c10355f..f71e8fedf 100644 --- a/nx_secure/src/nx_secure_tls_send_certificate_verify.c +++ b/nx_secure/src/nx_secure_tls_send_certificate_verify.c @@ -23,6 +23,7 @@ #define NX_SECURE_SOURCE_CODE #include "nx_secure_tls.h" +#include "nx_crypto_rsa.h" #ifdef NX_SECURE_ENABLE_DTLS #include "nx_secure_dtls.h" #endif /* NX_SECURE_ENABLE_DTLS */ @@ -30,6 +31,9 @@ #ifndef NX_SECURE_DISABLE_X509 static UCHAR handshake_hash[64 + 34 + 64]; /* We concatenate MD5 and SHA-1 hashes into this buffer, OR SHA-256/384/512. */ static UCHAR _nx_secure_padded_signature[600]; +#if (NX_SECURE_TLS_TLS_1_3_ENABLED) +static UCHAR _nx_secure_pss_scratch[600]; /* PSS encode: db[<=511 B] + salt[<=64 B] for RSA-4096+SHA-512 */ +#endif #if (NX_SECURE_TLS_TLS_1_2_ENABLED) static const UCHAR _NX_SECURE_OID_SHA256[] = {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}; @@ -174,7 +178,10 @@ NX_CRYPTO_EXTENDED_OUTPUT extended_output; #if (NX_SECURE_TLS_TLS_1_3_ENABLED) if (tls_session -> nx_secure_tls_1_3) { - /* Signature algorithm is set when processing CertificateRequest or SignatureAlgorithm extension. */ + /* Signature algorithm is set when processing CertificateRequest or SignatureAlgorithm extension. + * For RSA in TLS 1.3 the wire codes are rsa_pss_rsae_sha256/384/512 (0x0804/0805/0806). We reuse + * the existing RSA_SHA_* x509 cert types because the cipher/hash table lookup is shared with + * PKCS#1 — only the EMSA encoding step downstream switches to PSS. */ switch (tls_session -> nx_secure_tls_signature_algorithm) { case NX_SECURE_TLS_SIGNATURE_ECDSA_SHA256: @@ -186,6 +193,15 @@ NX_CRYPTO_EXTENDED_OUTPUT extended_output; case NX_SECURE_TLS_SIGNATURE_ECDSA_SHA512: signature_algorithm = NX_SECURE_TLS_X509_TYPE_ECDSA_SHA_512; break; + case 0x0804u: /* rsa_pss_rsae_sha256 */ + signature_algorithm = NX_SECURE_TLS_X509_TYPE_RSA_SHA_256; + break; + case 0x0805u: /* rsa_pss_rsae_sha384 */ + signature_algorithm = NX_SECURE_TLS_X509_TYPE_RSA_SHA_384; + break; + case 0x0806u: /* rsa_pss_rsae_sha512 */ + signature_algorithm = NX_SECURE_TLS_X509_TYPE_RSA_SHA_512; + break; default: return(NX_SECURE_TLS_UNSUPPORTED_CERT_SIGN_ALG); } @@ -528,6 +544,43 @@ NX_CRYPTO_EXTENDED_OUTPUT extended_output; #endif /* NX_SECURE_ENABLE_DTLS */ { +#if (NX_SECURE_TLS_TLS_1_3_ENABLED) + if (tls_session -> nx_secure_tls_1_3) + { + /* TLS 1.3 CertificateVerify wire format: SignatureScheme (2 bytes, big-endian) || length (2 bytes) || signature. + * nx_secure_tls_signature_algorithm carries the wire code (rsa_pss_rsae_sha256/384/512 = 0x0804/0805/0806) + * stored by process_certificate_request. The PSS-encoded EM is built directly into _nx_secure_padded_signature + * at full size — the PKCS#1 v1.5 padding loop further down is gated to skip when nx_secure_tls_1_3 is set. */ + current_buffer[length] = (UCHAR)((tls_session -> nx_secure_tls_signature_algorithm) >> 8); + current_buffer[length + 1] = (UCHAR)((tls_session -> nx_secure_tls_signature_algorithm) & 0xFFu); + current_buffer[length + 2] = (UCHAR)(data_size >> 8); + current_buffer[length + 3] = (UCHAR)(data_size); + length += 4; + + /* Sentinel: signature_length == data_size makes the PKCS#1 loop body below run zero iterations + * (i < data_size - data_size - 1 underflows; we explicitly skip it via the nx_secure_tls_1_3 guard). */ + signature_length = data_size; + + status = _nx_crypto_rsa_pss_sign(handshake_hash, handshake_hash_length, + _nx_secure_padded_signature, + (data_size << 3) - 1u, + hash_method, + tls_session -> nx_secure_tls_handshake_hash.nx_secure_tls_handshake_hash_scratch, + tls_session -> nx_secure_tls_handshake_hash.nx_secure_tls_handshake_hash_scratch_size, + _nx_secure_pss_scratch, sizeof(_nx_secure_pss_scratch)); + if (status != NX_CRYPTO_SUCCESS) + { +#ifdef NX_SECURE_KEY_CLEAR + NX_SECURE_MEMSET(handshake_hash, 0, sizeof(handshake_hash)); + NX_SECURE_MEMSET(_nx_secure_padded_signature, 0, sizeof(_nx_secure_padded_signature)); + NX_SECURE_MEMSET(_nx_secure_pss_scratch, 0, sizeof(_nx_secure_pss_scratch)); +#endif /* NX_SECURE_KEY_CLEAR */ + return(status); + } + } + else +#endif /* NX_SECURE_TLS_TLS_1_3_ENABLED */ + { /* Signature algorithm used. */ current_buffer[length] = NX_SECURE_TLS_HASH_ALGORITHM_SHA256; /* We only support SHA-256 right now. */ current_buffer[length + 1] = NX_SECURE_TLS_SIGNATURE_ALGORITHM_RSA; /* RSA */ @@ -560,6 +613,7 @@ NX_CRYPTO_EXTENDED_OUTPUT extended_output; /* Now put the data into the padded buffer - must be at the end. */ NX_SECURE_MEMCPY(&working_ptr[19], handshake_hash, 32); /* Use case of memcpy is verified. lgtm[cpp/banned-api-usage-required-any] */ + } } #endif @@ -609,10 +663,16 @@ NX_CRYPTO_EXTENDED_OUTPUT extended_output; then pad with 0xFF bytes (for signing) followed with a single 0 byte right before the payload, which comes at the end of the RSA block. */ - _nx_secure_padded_signature[1] = 0x1; /* Block type is 0x00, 0x01 for signatures */ - for (i = 2; i < (data_size - signature_length - 1); ++i) +#if (NX_SECURE_TLS_TLS_1_3_ENABLED) + /* TLS 1.3 uses RSA-PSS, which already wrote the full EM into _nx_secure_padded_signature above. */ + if (!tls_session -> nx_secure_tls_1_3) +#endif { - _nx_secure_padded_signature[i] = (UCHAR)0xFF; + _nx_secure_padded_signature[1] = 0x1; /* Block type is 0x00, 0x01 for signatures */ + for (i = 2; i < (data_size - signature_length - 1); ++i) + { + _nx_secure_padded_signature[i] = (UCHAR)0xFF; + } } /* Check for user-defined keys. */