diff --git a/.gitignore b/.gitignore index 24dfbfed..8894d595 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ compile_commands.json.bak /serial-file-test /rand-file-test manpages/*.1.gz +tests/x509/cert_setup_unit_test diff --git a/Makefile.am b/Makefile.am index 6e8e0a53..e11625b6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -90,6 +90,7 @@ endif include src/include.am include wolfclu/include.am include tests/tools/include.am +include tests/x509/unit_include.am if HAVE_PYTHON include tests/dh/include.am include tests/dsa/include.am diff --git a/configure.ac b/configure.ac index b125a366..1b729d18 100644 --- a/configure.ac +++ b/configure.ac @@ -135,6 +135,9 @@ AC_CHECK_FUNC([wolfSSL_X509_REQ_print], AC_CHECK_FUNC([wc_EncodeObjectId], [], [AM_CFLAGS="$AM_CFLAGS -DNO_WC_ENCODE_OBJECT_ID"]) +AC_CHECK_FUNC([wc_SetAltNamesFromList], + [AM_CFLAGS="$AM_CFLAGS -DHAVE_WC_SET_ALT_NAMES_FROM_LIST"], + []) ############################################### diff --git a/src/crypto/clu_crypto_setup.c b/src/crypto/clu_crypto_setup.c index 5ed6f67e..45f1ecb2 100644 --- a/src/crypto/clu_crypto_setup.c +++ b/src/crypto/clu_crypto_setup.c @@ -317,8 +317,7 @@ int wolfCLU_setup(int argc, char** argv, char action) case WOLFCLU_PASSWORD_SOURCE: passwordSz = keySize; ret = wolfCLU_GetPassword((char*)pwdKey, &passwordSz, optarg); - /* On an unsupported source wolfCLU_GetPassword zeroes the buffer - * and fails. Bail out so we do not encrypt under an empty key. */ + /* bail on unsupported password source to avoid empty key */ if (ret != WOLFCLU_SUCCESS) { wolfCLU_freeBins(pwdKey, iv, key, (byte*)mode, NULL); return ret; @@ -610,43 +609,6 @@ int wolfCLU_setup(int argc, char** argv, char action) } } - if (pwdKeyChk == 0 && keyCheck == 0) { - if (decCheck == 1) { - WOLFCLU_LOG(WOLFCLU_L0, "\nDECRYPT ERROR:"); - wolfCLU_LogError("no key or passphrase set"); - WOLFCLU_LOG(WOLFCLU_L0, - "Please type \"wolfssl -decrypt -help\" for decryption" - " usage \n"); - wolfCLU_freeBins(pwdKey, iv, key, (byte*)mode, NULL); - return WOLFCLU_FATAL_ERROR; - } - /* if no pwdKey is provided */ - else { - /* Pass the pwdKey buffer capacity, NOT &keySize: - * wolfCLU_GetStdinPassword writes the entered length back through - * this pointer, and keySize (the algorithm key size in bits) is - * still needed for key derivation and cleanup below. */ - word32 pwdBufSz = (word32)(keySize + block); - WOLFCLU_LOG(WOLFCLU_L0, - "No -pwd flag set, please enter a password to use for" - " encrypting."); - ret = wolfCLU_GetStdinPassword(pwdKey, &pwdBufSz); - pwdKeyChk = 1; - } - } - - if (inCheck == 0 && encCheck == 1) { - ret = wolfCLU_readFilename(inName, sizeof(inName), - "-in flag was not set, please enter a string or" - " file name to be encrypted: "); - if (ret != WOLFCLU_SUCCESS) { - wolfCLU_freeBins(pwdKey, iv, key, (byte*)mode, NULL); - return WOLFCLU_FATAL_ERROR; - } - WOLFCLU_LOG(WOLFCLU_L0, "Encrypting :\"%s\"", inName); - inCheck = 1; - } - if (encCheck == 1 && decCheck == 1) { WOLFCLU_LOG(WOLFCLU_E0, "Encrypt and decrypt simultaneously is invalid"); @@ -683,8 +645,47 @@ int wolfCLU_setup(int argc, char** argv, char action) return WOLFCLU_FATAL_ERROR; } + if (pwdKeyChk == 0 && keyCheck == 0) { + if (decCheck == 1) { + WOLFCLU_LOG(WOLFCLU_L0, "\nDECRYPT ERROR:"); + wolfCLU_LogError("no key or passphrase set"); + WOLFCLU_LOG(WOLFCLU_L0, + "Please type \"wolfssl -decrypt -help\" for decryption" + " usage \n"); + wolfCLU_freeBins(pwdKey, iv, key, (byte*)mode, NULL); + return WOLFCLU_FATAL_ERROR; + } + /* if no pwdKey is provided */ + else { + /* use separate pwdBufSz since GetStdinPassword overwrites it */ + word32 pwdBufSz = (word32)(keySize + block); + WOLFCLU_LOG(WOLFCLU_L0, + "No -pwd flag set, please enter a password to use for" + " encrypting."); + ret = wolfCLU_GetStdinPassword(pwdKey, &pwdBufSz); + if (ret != WOLFCLU_SUCCESS) { + wolfCLU_freeBins(pwdKey, iv, key, (byte*)mode, NULL); + return WOLFCLU_FATAL_ERROR; + } + pwdKeyChk = 1; + } + } + + if (inCheck == 0 && encCheck == 1) { + ret = wolfCLU_readFilename(inName, sizeof(inName), + "-in flag was not set, please enter a string or" + " file name to be encrypted: "); + if (ret != WOLFCLU_SUCCESS) { + wolfCLU_freeBins(pwdKey, iv, key, (byte*)mode, NULL); + return WOLFCLU_FATAL_ERROR; + } + WOLFCLU_LOG(WOLFCLU_L0, "Encrypting :\"%s\"", inName); + inCheck = 1; + } + + if (pwdKeyChk == 1 && keyCheck == 1) { - XMEMSET(pwdKey, 0, keySize + block); + wolfCLU_ForceZero(pwdKey, keySize + block); } /* encryption function call */ diff --git a/src/ecparam/clu_ecparam.c b/src/ecparam/clu_ecparam.c index 25694f0c..b149e74a 100644 --- a/src/ecparam/clu_ecparam.c +++ b/src/ecparam/clu_ecparam.c @@ -260,7 +260,7 @@ int wolfCLU_ecparam(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS && key != NULL) { - wolfCLU_EcparamPrintOID(bioOut, key, outForm); + ret = wolfCLU_EcparamPrintOID(bioOut, key, outForm); } if (ret == WOLFCLU_SUCCESS && key != NULL && genKey) { diff --git a/src/genkey/clu_genkey.c b/src/genkey/clu_genkey.c index de361729..0ee1e399 100644 --- a/src/genkey/clu_genkey.c +++ b/src/genkey/clu_genkey.c @@ -26,13 +26,16 @@ #if defined(WOLFSSL_KEY_GEN) && !defined(NO_ASN) +#include +#include /* strerror */ + /* Each key-generation routine below writes its result out through the wolfCLU * secure file helpers, which are only declared and compiled when a stdio * filesystem is available, so each is additionally conditioned on * !WOLFCLU_NO_FILESYSTEM and falls back to its NOT_COMPILED_IN branch. - * The BIO-based helpers (wolfCLU_GenKeyECC, wolfCLU_EcparamPrintOID, - * wolfCLU_KeyDerToPem) open no files and stay available: ecparam still - * generates keys to stdout without a filesystem. */ + * The BIO-based helpers (wolfCLU_GenKeyECC, wolfCLU_EcparamPrintOID) open no + * files and stay available: ecparam still generates keys to stdout without a + * filesystem. */ #include #include #include @@ -417,7 +420,8 @@ static int wolfCLU_ECC_write_priv_der(WOLFSSL_BIO* out, WOLFSSL_EC_KEY* key) } #endif /* !WOLFCLU_NO_FILESYSTEM */ -void wolfCLU_EcparamPrintOID(WOLFSSL_BIO* out, WOLFSSL_EC_KEY* key, +/* returns WOLFCLU_SUCCESS on success, WOLFCLU_FATAL_ERROR on failure */ +int wolfCLU_EcparamPrintOID(WOLFSSL_BIO* out, WOLFSSL_EC_KEY* key, int fmt) { int ret = WOLFCLU_SUCCESS; @@ -440,6 +444,12 @@ void wolfCLU_EcparamPrintOID(WOLFSSL_BIO* out, WOLFSSL_EC_KEY* key, } } + /* the length is emitted below as a single DER short-form byte */ + if (ret == WOLFCLU_SUCCESS && oidSz >= ASN_LONG_LENGTH) { + wolfCLU_LogError("Curve OID too large to encode"); + ret = WOLFCLU_FATAL_ERROR; + } + if (ret == WOLFCLU_SUCCESS) { objOIDSz = oidSz + 2; objOID = (byte*)XMALLOC(oidSz + 2, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -451,7 +461,7 @@ void wolfCLU_EcparamPrintOID(WOLFSSL_BIO* out, WOLFSSL_EC_KEY* key, /* set object ID tag and internal oid section */ if (ret == WOLFCLU_SUCCESS) { objOID[0] = ASN_OBJECT_ID; - objOID[1] = oidSz; + objOID[1] = (byte)oidSz; XMEMCPY(objOID + 2, oid, oidSz); } @@ -507,7 +517,8 @@ void wolfCLU_EcparamPrintOID(WOLFSSL_BIO* out, WOLFSSL_EC_KEY* key, if (objOID != NULL) { XFREE(objOID, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); } - (void)ret; + + return ret; } WOLFSSL_EC_KEY* wolfCLU_GenKeyECC(char* name) @@ -759,42 +770,6 @@ int wolfCLU_GenAndOutput_ECC(WC_RNG* rng, char* fName, int directive, } -#if !defined(NO_RSA) || defined(HAVE_ED25519) -/* helper function to convert a key to PEM format. Creates new 'out' buffer on - * success. - * returns size of PEM buffer created on success - * returns 0 or negative value on failure */ -int wolfCLU_KeyDerToPem(const byte* der, int derSz, byte** out, int pemType, - int heapType) -{ - int pemBufSz; - byte* pemBuf = NULL; - - if (out == NULL || der == NULL || derSz <= 0) { - return 0; - } - - pemBufSz = wc_DerToPemEx(der, derSz, NULL, 0, NULL, pemType); - if (pemBufSz > 0) { - pemBuf = (byte*)XMALLOC(pemBufSz, HEAP_HINT, heapType); - if (pemBuf == NULL) { - pemBufSz = 0; - } - else { - pemBufSz = wc_DerToPemEx(der, derSz, pemBuf, pemBufSz, NULL, - pemType); - } - } - - if (pemBufSz <= 0 && pemBuf != NULL) { - XFREE(pemBuf, HEAP_HINT, heapType); - pemBuf = NULL; - } - *out = pemBuf; - return pemBufSz; -} -#endif /* !NO_RSA || HAVE_ED25519*/ - /* return WOLFCLU_SUCCESS on success */ int wolfCLU_genKey_RSA(WC_RNG* rng, char* fName, int directive, int fmt, int @@ -1099,7 +1074,7 @@ int wolfCLU_genKey_Dilithium(WC_RNG* rng, char* fName, int directive, int fmt, } /* set the level of the dilithium key */ - if (wc_dilithium_set_level(key, level) != 0) { + if (wc_dilithium_set_level(key, (byte)level) != 0) { wc_dilithium_free(key); #ifdef WOLFSSL_SMALL_STACK XFREE(key, HEAP_HINT, DYNAMIC_TYPE_DILITHIUM); @@ -1150,8 +1125,10 @@ int wolfCLU_genKey_Dilithium(WC_RNG* rng, char* fName, int directive, int fmt, /* Private key to der */ derBufSz = wc_Dilithium_PrivateKeyToDer(key, derBuf, (word32)keySz); - if (derBufSz < 0) { - ret = derBufSz; + /* a zero-length encoding would otherwise be written out as a + * valid empty key file */ + if (derBufSz <= 0) { + ret = (derBufSz < 0) ? derBufSz : OUTPUT_FILE_ERROR; } else { outBuf = derBuf; @@ -1171,7 +1148,7 @@ int wolfCLU_genKey_Dilithium(WC_RNG* rng, char* fName, int directive, int fmt, } } - /* open file and write Private key */ + /* open file and write Private key with owner-only perms */ if (ret == WOLFCLU_SUCCESS) { file = wolfCLU_OpenKeyFile(fOutNameBuf); if (file == NULL) { @@ -1180,7 +1157,8 @@ int wolfCLU_genKey_Dilithium(WC_RNG* rng, char* fName, int directive, int fmt, } if (ret == WOLFCLU_SUCCESS) { - if ((int)XFWRITE(outBuf, 1, outBufSz, file) <= 0) { + if (XFWRITE(outBuf, 1, outBufSz, file) != + (size_t)outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1196,7 +1174,7 @@ int wolfCLU_genKey_Dilithium(WC_RNG* rng, char* fName, int directive, int fmt, derBuf = NULL; if (pemBuf != NULL) { wolfCLU_ForceZero(pemBuf, pemBufSz); - XFREE(pemBuf, HEAP_HINT, DYNAMIC_TYPE_PRIVATE_KEY); + XFREE(pemBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); pemBuf = NULL; } @@ -1215,8 +1193,10 @@ int wolfCLU_genKey_Dilithium(WC_RNG* rng, char* fName, int directive, int fmt, derBufSz = wc_Dilithium_PublicKeyToDer(key, derBuf, (word32)keySz, withAlg); - if (derBufSz < 0) { - ret = derBufSz; + /* a zero-length encoding would otherwise be written out as a + * valid empty key file */ + if (derBufSz <= 0) { + ret = (derBufSz < 0) ? derBufSz : OUTPUT_FILE_ERROR; } else { outBuf = derBuf; @@ -1245,7 +1225,8 @@ int wolfCLU_genKey_Dilithium(WC_RNG* rng, char* fName, int directive, int fmt, } if (ret == WOLFCLU_SUCCESS) { - if ((int)XFWRITE(outBuf, 1, outBufSz, file) <= 0) { + if (XFWRITE(outBuf, 1, outBufSz, file) != + (size_t)outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1267,7 +1248,7 @@ int wolfCLU_genKey_Dilithium(WC_RNG* rng, char* fName, int directive, int fmt, if (pemBuf != NULL) { wolfCLU_ForceZero(pemBuf, pemBufSz); - XFREE(pemBuf, HEAP_HINT, DYNAMIC_TYPE_PRIVATE_KEY); + XFREE(pemBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); } if (fOutNameBuf != NULL) { @@ -1341,7 +1322,7 @@ int wolfCLU_genKey_ML_DSA(WC_RNG* rng, char* fName, int directive, int fmt, } /* set the level of the ML-DSA key */ - if (wc_MlDsaKey_SetParams(key, level) != 0) { + if (wc_MlDsaKey_SetParams(key, (byte)level) != 0) { wolfCLU_LogError("Failed to set ML-DSA Key parameters"); wc_MlDsaKey_Free(key); #ifdef WOLFSSL_SMALL_STACK @@ -1394,8 +1375,10 @@ int wolfCLU_genKey_ML_DSA(WC_RNG* rng, char* fName, int directive, int fmt, /* Private key to der */ derBufSz = wc_MlDsaKey_PrivateKeyToDer(key, derBuf, (word32)keySz); - if (derBufSz < 0) { - ret = derBufSz; + /* a zero-length encoding would otherwise be written out as a + * valid empty key file */ + if (derBufSz <= 0) { + ret = (derBufSz < 0) ? derBufSz : OUTPUT_FILE_ERROR; } else { outBuf = derBuf; @@ -1415,7 +1398,7 @@ int wolfCLU_genKey_ML_DSA(WC_RNG* rng, char* fName, int directive, int fmt, } } - /* open file and write Private key */ + /* open file and write Private key with owner-only perms */ if (ret == WOLFCLU_SUCCESS) { file = wolfCLU_OpenKeyFile(fOutNameBuf); if (file == NULL) { @@ -1424,7 +1407,8 @@ int wolfCLU_genKey_ML_DSA(WC_RNG* rng, char* fName, int directive, int fmt, } if (ret == WOLFCLU_SUCCESS) { - if ((int)XFWRITE(outBuf, 1, outBufSz, file) <= 0) { + if (XFWRITE(outBuf, 1, outBufSz, file) != + (size_t)outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1440,7 +1424,7 @@ int wolfCLU_genKey_ML_DSA(WC_RNG* rng, char* fName, int directive, int fmt, derBuf = NULL; if (pemBuf != NULL) { wolfCLU_ForceZero(pemBuf, pemBufSz); - XFREE(pemBuf, HEAP_HINT, DYNAMIC_TYPE_PRIVATE_KEY); + XFREE(pemBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); pemBuf = NULL; } @@ -1463,8 +1447,10 @@ int wolfCLU_genKey_ML_DSA(WC_RNG* rng, char* fName, int directive, int fmt, if (ret == WOLFCLU_SUCCESS) { derBufSz = wc_MlDsaKey_PublicKeyToDer(key, derBuf, (word32)keySz, withAlg); - if (derBufSz < 0) { - ret = derBufSz; + /* a zero-length encoding would otherwise be written + * out as a valid empty key file */ + if (derBufSz <= 0) { + ret = (derBufSz < 0) ? derBufSz : OUTPUT_FILE_ERROR; } else { outBuf = derBuf; @@ -1494,7 +1480,8 @@ int wolfCLU_genKey_ML_DSA(WC_RNG* rng, char* fName, int directive, int fmt, } if (ret == WOLFCLU_SUCCESS) { - if ((int)XFWRITE(outBuf, 1, outBufSz, file) <= 0) { + if (XFWRITE(outBuf, 1, outBufSz, file) != + (size_t)outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1517,7 +1504,7 @@ int wolfCLU_genKey_ML_DSA(WC_RNG* rng, char* fName, int directive, int fmt, if (pemBuf != NULL) { wolfCLU_ForceZero(pemBuf, pemBufSz); - XFREE(pemBuf, HEAP_HINT, DYNAMIC_TYPE_PRIVATE_KEY); + XFREE(pemBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); } if (fOutNameBuf != NULL) { @@ -1559,7 +1546,7 @@ enum wc_XmssRc wolfCLU_XmssKey_WriteCb(const byte * priv, int err = 0; if (priv == NULL || context == NULL || privSz == 0) { - fprintf(stderr, "error: invalid write args\n"); + XFPRINTF(stderr, "error: invalid write args\n"); return WC_XMSS_RC_BAD_ARG; } @@ -1567,76 +1554,83 @@ enum wc_XmssRc wolfCLU_XmssKey_WriteCb(const byte * priv, /* This is the XMSS private key, including the signing state that is * rewritten after every signature, so it gets the same owner-only, - * no-symlink treatment as every other private key wolfCLU writes. */ + * no-symlink treatment as every other private key wolfCLU writes. + * Recreate only on ENOENT, to preserve that state. */ + errno = 0; file = wolfCLU_OpenExistingSecureFile(filename, "rb+", 1); - if (!file) { - /* Create the file if it didn't exist. */ - file = wolfCLU_OpenKeyFile(filename); - if (!file) { - fprintf(stderr, "error: could not open %s for writing.\n", - filename); + if (file == XBADFILE) { + if (errno != ENOENT) { + XFPRINTF(stderr, "error: open existing (%s, \"rb+\") failed: %s\n", + filename, strerror(errno)); + return WC_XMSS_RC_WRITE_FAIL; + } + + /* First write: lock down perms on creation. */ + file = wolfCLU_CreateSecureFile(filename, "wb+", 1); + if (file == XBADFILE) { + XFPRINTF(stderr, "error: fopen(%s, \"w+\") failed.\n", filename); return WC_XMSS_RC_WRITE_FAIL; } } - n_write = fwrite(priv, 1, privSz, file); + n_write = XFWRITE(priv, 1, privSz, file); if (n_write != privSz) { - fprintf(stderr, "error: wrote %zu, expected %d: %d\n", n_write, privSz, + XFPRINTF(stderr, "error: wrote %zu, expected %d: %d\n", n_write, privSz, ferror(file)); - fclose(file); + XFCLOSE(file); return WC_XMSS_RC_WRITE_FAIL; } - err = fclose(file); + err = XFCLOSE(file); if (err) { - fprintf(stderr, "error: fclose returned %d\n", err); + XFPRINTF(stderr, "error: fclose returned %d\n", err); return WC_XMSS_RC_WRITE_FAIL; } /* Verify private key data has actually been written to persistent * storage correctly. */ file = wolfCLU_OpenExistingSecureFile(filename, "rb", 1); - if (!file) { - fprintf(stderr, "error: could not reopen %s to verify.\n", filename); + if (file == XBADFILE) { + XFPRINTF(stderr, "error: reopen (%s, \"rb\") failed.\n", filename); return WC_XMSS_RC_WRITE_FAIL; } - buff = malloc(privSz); + buff = (byte*)XMALLOC(privSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); if (buff == NULL) { - fprintf(stderr, "error: malloc(%d) failed\n", privSz); - fclose(file); + XFPRINTF(stderr, "error: malloc(%d) failed\n", privSz); + XFCLOSE(file); return WC_XMSS_RC_WRITE_FAIL; } XMEMSET(buff, 0, n_write); - n_read = fread(buff, 1, n_write, file); + n_read = XFREAD(buff, 1, n_write, file); if (n_read != n_write) { - fprintf(stderr, "error: read %zu, expected %zu: %d\n", n_read, n_write, + XFPRINTF(stderr, "error: read %zu, expected %zu: %d\n", n_read, n_write, ferror(file)); - wolfCLU_ForceZero(buff, (unsigned int)privSz); - free(buff); - fclose(file); + wolfCLU_ForceZero(buff, privSz); + XFREE(buff, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFCLOSE(file); return WC_XMSS_RC_WRITE_FAIL; } n_cmp = XMEMCMP(buff, priv, n_write); /* buff holds a copy of the private key read back from disk. */ - wolfCLU_ForceZero(buff, (unsigned int)privSz); - free(buff); + wolfCLU_ForceZero(buff, privSz); + XFREE(buff, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); buff = NULL; if (n_cmp != 0) { - fprintf(stderr, "error: write data was corrupted: %d\n", n_cmp); - fclose(file); + XFPRINTF(stderr, "error: write data was corrupted: %d\n", n_cmp); + XFCLOSE(file); return WC_XMSS_RC_WRITE_FAIL; } - err = fclose(file); + err = XFCLOSE(file); if (err) { - fprintf(stderr, "error: fclose returned %d\n", err); + XFPRINTF(stderr, "error: fclose returned %d\n", err); return WC_XMSS_RC_WRITE_FAIL; } @@ -1651,7 +1645,7 @@ enum wc_XmssRc wolfCLU_XmssKey_ReadCb(byte * priv, size_t n_read = 0; if (priv == NULL || context == NULL || privSz == 0) { - fprintf(stderr, "error: invalid read args\n"); + XFPRINTF(stderr, "error: invalid read args\n"); return WC_XMSS_RC_BAD_ARG; } @@ -1664,21 +1658,22 @@ enum wc_XmssRc wolfCLU_XmssKey_ReadCb(byte * priv, * account must stay usable for signing without having its mode rewritten * underneath the owner. */ file = wolfCLU_OpenExistingSecureFile(filename, "rb", 0); - if (!file) { - fprintf(stderr, "error: could not open %s for reading\n", filename); + if (file == XBADFILE) { + XFPRINTF(stderr, "error: open existing (%s, \"rb\") failed\n", + filename); return WC_XMSS_RC_READ_FAIL; } - n_read = fread(priv, 1, privSz, file); + n_read = XFREAD(priv, 1, privSz, file); if (n_read != privSz) { - fprintf(stderr, "error: read %zu, expected %d: %d\n", n_read, privSz, + XFPRINTF(stderr, "error: read %zu, expected %d: %d\n", n_read, privSz, ferror(file)); - fclose(file); + XFCLOSE(file); return WC_XMSS_RC_READ_FAIL; } - fclose(file); + XFCLOSE(file); return WC_XMSS_RC_READ_TO_MEMORY; } diff --git a/src/ocsp/clu_ocsp.c b/src/ocsp/clu_ocsp.c index dd807d3c..a874d6b1 100644 --- a/src/ocsp/clu_ocsp.c +++ b/src/ocsp/clu_ocsp.c @@ -865,12 +865,11 @@ static int ocspResponder(OcspResponderConfig* config) if (transportSendResponse(clientfd, transportType, respBuffer, (int)respSz) != 0) goto continue_loop; - if (ocspStatus == OCSP_SUCCESSFUL) { - /* Only count successfully processed requests toward the -nrequest - * limit. Failed reads/sends jump to continue_loop above, so a - * misbehaving client cannot exhaust the budget. */ - requestsProcessed++; - } + /* Count every response sent, including malformedRequest and + * internalError ones, to match OpenSSL's accept_count. Connections + * that never produced a response jump to continue_loop above and so + * are not counted. */ + requestsProcessed++; /* Check if we've hit the request limit */ if (config->nrequest > 0 && requestsProcessed >= config->nrequest) { diff --git a/src/pkcs/clu_pkcs12.c b/src/pkcs/clu_pkcs12.c index c3b41553..4ea86330 100644 --- a/src/pkcs/clu_pkcs12.c +++ b/src/pkcs/clu_pkcs12.c @@ -62,6 +62,9 @@ int wolfCLU_PKCS12(int argc, char** argv) #if defined(HAVE_PKCS12) && !defined(WOLFCLU_NO_FILESYSTEM) char password[MAX_PASSWORD_SIZE] = ""; int passwordSz = MAX_PASSWORD_SIZE; + char passOut[MAX_PASSWORD_SIZE] = ""; + int passOutSz = MAX_PASSWORD_SIZE; + int hasPassOut = 0; int ret = WOLFCLU_SUCCESS; int useDES = 1; /* default to yes */ int printCerts = 1; /* default to yes*/ @@ -74,7 +77,7 @@ int wolfCLU_PKCS12(int argc, char** argv) WOLF_STACK_OF(WOLFSSL_X509) *extra = NULL; WOLFSSL_BIO *bioIn = NULL; WOLFSSL_BIO *bioOut = NULL; - char *outPath = NULL; + const char *outPath = NULL; opterr = 0; /* do not display unrecognized options */ optind = 0; /* start at indent 0 */ @@ -100,10 +103,19 @@ int wolfCLU_PKCS12(int argc, char** argv) case WOLFCLU_PASSWORD: passwordSz = MAX_PASSWORD_SIZE; - ret = wolfCLU_GetPassword(password, &passwordSz, optarg); + if (wolfCLU_GetPassword(password, &passwordSz, optarg) + != WOLFCLU_SUCCESS) { + ret = WOLFCLU_FATAL_ERROR; + } break; case WOLFCLU_PASSWORD_OUT: + passOutSz = MAX_PASSWORD_SIZE; + hasPassOut = (wolfCLU_GetPassword(passOut, &passOutSz, + optarg) == WOLFCLU_SUCCESS); + if (!hasPassOut) { + ret = WOLFCLU_FATAL_ERROR; + } break; case WOLFCLU_INFILE: @@ -124,6 +136,9 @@ int wolfCLU_PKCS12(int argc, char** argv) case WOLFCLU_HELP: wolfCLU_pKeyHelp(); + wolfCLU_ForceZero(password, MAX_PASSWORD_SIZE); + wolfCLU_ForceZero(passOut, MAX_PASSWORD_SIZE); + wolfSSL_BIO_free(bioIn); return WOLFCLU_SUCCESS; case ':': @@ -173,7 +188,11 @@ int wolfCLU_PKCS12(int argc, char** argv) } else { pkcs12 = wc_PKCS12_new(); - if (wc_d2i_PKCS12(buf, bufSz, pkcs12) < 0) { + if (pkcs12 == NULL) { + wolfCLU_LogError("Error allocating pkcs12 struct"); + ret = WOLFCLU_FATAL_ERROR; + } + else if (wc_d2i_PKCS12(buf, bufSz, pkcs12) < 0) { wolfCLU_LogError("Error reading pkcs12 file"); ret = WOLFCLU_FATAL_ERROR; } @@ -195,6 +214,15 @@ int wolfCLU_PKCS12(int argc, char** argv) } } + /* deferred until it's known whether a key will actually be written */ + if (ret == WOLFCLU_SUCCESS && outPath != NULL) { + bioOut = wolfCLU_OpenOutOrKeyFileBio(outPath, + printKeys && pkey != NULL); + if (bioOut == NULL) { + ret = WOLFCLU_FATAL_ERROR; + } + } + /* setup output bio to stdout if not already set */ if (ret == WOLFCLU_SUCCESS && bioOut == NULL) { bioOut = wolfSSL_BIO_new(wolfSSL_BIO_s_file()); @@ -234,12 +262,22 @@ int wolfCLU_PKCS12(int argc, char** argv) /* print out the key */ if (ret == WOLFCLU_SUCCESS && pkey != NULL && printKeys) { if (useDES) { - passwordSz = MAX_PASSWORD_SIZE; - wolfCLU_GetStdinPassword((byte*)password, (word32*)&passwordSz); - ret = wolfCLU_pKeyPEMtoPriKeyEnc(bioOut, pkey, DES3b, - (byte*)password, passwordSz); + if (!hasPassOut) { + passOutSz = MAX_PASSWORD_SIZE; + ret = wolfCLU_GetStdinPassword((byte*)passOut, + (word32*)&passOutSz); + } + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_pKeyPEMtoPriKeyEnc(bioOut, pkey, DES3b, + (byte*)passOut, passOutSz); + } } else { + if (hasPassOut) { + wolfCLU_Log(WOLFCLU_L0, + "Warning: -passout ignored because -nodes was set; " + "key will be written unencrypted"); + } ret = wolfCLU_pKeyPEMtoPriKey(bioOut, pkey); } if (ret != WOLFCLU_SUCCESS) { @@ -249,6 +287,7 @@ int wolfCLU_PKCS12(int argc, char** argv) } wolfCLU_ForceZero(password, MAX_PASSWORD_SIZE); + wolfCLU_ForceZero(passOut, MAX_PASSWORD_SIZE); wolfSSL_BIO_free(bioIn); wolfSSL_BIO_free(bioOut); wolfSSL_EVP_PKEY_free(pkey); diff --git a/src/pkey/clu_pkey.c b/src/pkey/clu_pkey.c index 1bf7c6b5..7c98967a 100644 --- a/src/pkey/clu_pkey.c +++ b/src/pkey/clu_pkey.c @@ -96,7 +96,9 @@ static int _ECCpKeyPEMtoKey(WOLFSSL_BIO* bio, WOLFSSL_EVP_PKEY* pkey, } if (der != NULL) { - wolfCLU_ForceZero(der, derSz); + if (derSz > 0) { + wolfCLU_ForceZero(der, derSz); + } XFREE(der, NULL, DYNAMIC_TYPE_OPENSSL); } } @@ -426,7 +428,7 @@ int wolfCLU_pKeySetup(int argc, char** argv) WOLFSSL_EVP_PKEY *pkey = NULL; WOLFSSL_BIO *bioIn = NULL; WOLFSSL_BIO *bioOut = NULL; - char *outPath = NULL; + const char *outPath = NULL; optind = 0; /* start at indent 0 */ while ((option = wolfCLU_GetOpt(argc, argv, "", pkey_options, @@ -518,6 +520,14 @@ int wolfCLU_pKeySetup(int argc, char** argv) } } + if (ret == WOLFCLU_SUCCESS && outPath != NULL) { + /* bio holds secret material exactly when pubOut is false */ + bioOut = wolfCLU_OpenOutOrKeyFileBio(outPath, !pubOut); + if (bioOut == NULL) { + ret = WOLFCLU_FATAL_ERROR; + } + } + if (ret == WOLFCLU_SUCCESS && bioOut == NULL) { bioOut = wolfSSL_BIO_new(wolfSSL_BIO_s_file()); if (bioOut == NULL) { diff --git a/src/pkey/clu_rsa.c b/src/pkey/clu_rsa.c index 9ab8aad0..12dc749e 100644 --- a/src/pkey/clu_rsa.c +++ b/src/pkey/clu_rsa.c @@ -80,7 +80,7 @@ int wolfCLU_RSA(int argc, char** argv) WOLFSSL_BIO *bioIn = NULL; WOLFSSL_BIO *bioOut = NULL; WOLFSSL_RSA *rsa = NULL; - char *outPath = NULL; + const char *outPath = NULL; opterr = 0; /* do not display unrecognized options */ optind = 0; /* start at indent 0 */ @@ -209,6 +209,14 @@ int wolfCLU_RSA(int argc, char** argv) } } + if (ret == WOLFCLU_SUCCESS && outPath != NULL) { + /* bio holds secret material exactly when pubOut is false */ + bioOut = wolfCLU_OpenOutOrKeyFileBio(outPath, !pubOut); + if (bioOut == NULL) { + ret = WOLFCLU_FATAL_ERROR; + } + } + /* print to stdout if no -out was used */ if (ret == WOLFCLU_SUCCESS && bioOut == NULL) { bioOut = wolfSSL_BIO_new(wolfSSL_BIO_s_file()); @@ -229,6 +237,8 @@ int wolfCLU_RSA(int argc, char** argv) unsigned char *pt; /* use pt with i2d to handle potential pointer increment */ int derSz = 0; + /* separate from derSz: a failed second i2d call overwrites derSz */ + int allocSz = 0; int pemType; int heapType; @@ -242,6 +252,7 @@ int wolfCLU_RSA(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS) { + allocSz = derSz; der = (unsigned char*)XMALLOC(derSz, HEAP_HINT, heapType); if (der == NULL) { ret = WOLFCLU_FATAL_ERROR; @@ -251,6 +262,9 @@ int wolfCLU_RSA(int argc, char** argv) if (ret == WOLFCLU_SUCCESS) { pt = der; derSz = wolfSSL_i2d_RSAPublicKey(rsa, &pt); + if (derSz < 0) { + ret = WOLFCLU_FATAL_ERROR; + } } } else { @@ -263,6 +277,7 @@ int wolfCLU_RSA(int argc, char** argv) } if (ret == WOLFCLU_SUCCESS) { + allocSz = derSz; der = (unsigned char*)XMALLOC(derSz, HEAP_HINT, heapType); if (der == NULL) { ret = WOLFCLU_FATAL_ERROR; @@ -272,18 +287,25 @@ int wolfCLU_RSA(int argc, char** argv) if (ret == WOLFCLU_SUCCESS) { pt = der; derSz = wolfSSL_i2d_RSAPrivateKey(rsa, &pt); + if (derSz < 0) { + ret = WOLFCLU_FATAL_ERROR; + } } } - if (outForm == PEM_FORM) { - ret = wolfCLU_printDer(bioOut, der, derSz, pemType, heapType); - } - else { - wolfSSL_BIO_write(bioOut, der, derSz); + if (ret == WOLFCLU_SUCCESS) { + if (outForm == PEM_FORM) { + ret = wolfCLU_printDer(bioOut, der, derSz, pemType, heapType); + } + else if (wolfSSL_BIO_write(bioOut, der, derSz) != derSz) { + ret = WOLFCLU_FATAL_ERROR; + } } if (der != NULL) { - wolfCLU_ForceZero(der, derSz); + if (allocSz > 0) { + wolfCLU_ForceZero(der, allocSz); + } XFREE(der, HEAP_HINT, heapType); } } diff --git a/src/sign-verify/clu_crl_verify.c b/src/sign-verify/clu_crl_verify.c index 4d00e117..47d1fb2e 100644 --- a/src/sign-verify/clu_crl_verify.c +++ b/src/sign-verify/clu_crl_verify.c @@ -74,7 +74,7 @@ int wolfCLU_CRLVerify(int argc, char** argv) char* out = NULL; WOLFSSL_BIO* bioIn = NULL; WOLFSSL_BIO* bioOut = NULL; - WOLFSSL_X509_CRL* test; + WOLFSSL_X509_CRL* test = NULL; opterr = 0; /* do not display unrecognized options */ optind = 0; /* start at indent 0 */ @@ -121,7 +121,8 @@ int wolfCLU_CRLVerify(int argc, char** argv) case WOLFCLU_HELP: wolfCLU_CRLVerifyHelp(); - return WOLFCLU_SUCCESS; + ret = WOLFCLU_SUCCESS; + goto cleanup; case ':': case '?': @@ -295,6 +296,7 @@ int wolfCLU_CRLVerify(int argc, char** argv) wolfSSL_CertManagerFree(cm); } +cleanup: if (der != NULL) { XFREE(der, HEAP_HINT, DYNAMIC_TYPE_CRL); } diff --git a/src/sign-verify/clu_dgst_setup.c b/src/sign-verify/clu_dgst_setup.c index dc34e6b2..c9e1da4f 100644 --- a/src/sign-verify/clu_dgst_setup.c +++ b/src/sign-verify/clu_dgst_setup.c @@ -179,9 +179,7 @@ static int ExtractKey(void* key, WOLFSSL_EVP_PKEY* pkey, int* keySz, } -/* compute an HMAC over the data in dataBio and output the resulting MAC. When - * outFile is set the raw MAC bytes are written there, otherwise the MAC is - * printed to stdout as hex. +/* HMACs dataBio; writes raw bytes to outFile, or hex to stdout if NULL. * return WOLFCLU_SUCCESS on success */ static int wolfCLU_dgstHmac(WOLFSSL_BIO* dataBio, char* hmacKey, enum wc_HashType hashType, char* outFile) @@ -201,9 +199,8 @@ static int wolfCLU_dgstHmac(WOLFSSL_BIO* dataBio, char* hmacKey, return WOLFCLU_FATAL_ERROR; } - /* Split the key on the FIRST ':' only, so a plaintext value may itself - * contain ':' (matching OpenSSL's key:/hexkey: forms). Everything before - * the colon is the type, everything after is the verbatim value. */ + /* Split on the first ':' only, so a plaintext value may itself + * contain ':' (matches OpenSSL's key:/hexkey: forms). */ sep = XSTRSTR(hmacKey, ":"); if (sep == NULL) { wolfCLU_LogError("Malformed Hmac key %s", hmacKey); @@ -228,9 +225,8 @@ static int wolfCLU_dgstHmac(WOLFSSL_BIO* dataBio, char* hmacKey, ret = WOLFCLU_FATAL_ERROR; } - /* The key is supplied as a hex string (matching OpenSSL's "hexkey:" - * form), so decode it to raw bytes before keying the HMAC. Using the - * ASCII text directly would key with the wrong bytes and length. */ + /* "hexkey:" values must be decoded to raw bytes, else the HMAC is + * keyed with the wrong bytes and length. */ if (ret == WOLFCLU_SUCCESS) { if (hex) { ret = wolfCLU_hexToBin(macKeyVal, &keyBin, &keyBinSz, @@ -264,12 +260,11 @@ static int wolfCLU_dgstHmac(WOLFSSL_BIO* dataBio, char* hmacKey, } } - /* output the resulting MAC */ if (ret == WOLFCLU_SUCCESS) { WOLFSSL_BIO* outBio = NULL; if (outFile != NULL) { - outBio = wolfSSL_BIO_new_file(outFile, "wb"); + outBio = wolfCLU_OpenOutFileBio(outFile); } else { outBio = wolfSSL_BIO_new_fp(stdout, WOLFSSL_BIO_NOCLOSE); @@ -298,13 +293,12 @@ static int wolfCLU_dgstHmac(WOLFSSL_BIO* dataBio, char* hmacKey, } } - /* clean up */ if (hmacCtx != NULL) { wolfSSL_HMAC_CTX_cleanup(hmacCtx); wolfSSL_HMAC_CTX_free(hmacCtx); } - /* wolfCLU_hexToBin allocates keyBin with a NULL heap hint; zero the - * key material and free it with the matching hint. */ + /* keyBin was allocated with a NULL heap hint; free with the matching + * hint. */ if (keyBin != NULL) { wolfCLU_ForceZero(keyBin, keyBinSz); XFREE(keyBin, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -314,9 +308,7 @@ static int wolfCLU_dgstHmac(WOLFSSL_BIO* dataBio, char* hmacKey, } -/* create or verify a signature over the data in dataBio using the key in - * pubKeyBio. When signing the signature is written to outFile, otherwise the - * signature is read from sigFile and verified. +/* signs data in dataBio to outFile, or verifies it against sigFile. * return WOLFCLU_SUCCESS on success */ static int wolfCLU_dgstSignVerify(WOLFSSL_BIO* dataBio, WOLFSSL_BIO* pubKeyBio, char* sigFile, char* outFile, enum wc_HashType hashType, int inForm, @@ -341,6 +333,10 @@ static int wolfCLU_dgstSignVerify(WOLFSSL_BIO* dataBio, WOLFSSL_BIO* pubKeyBio, /* Stream the data file through a hash to produce a digest, then pass * the digest to wc_Signature{Generate,Verify}Hash below. */ if (ret == WOLFCLU_SUCCESS) { + /* digest[] is MAX_DER_DIGEST_SZ so it can also hold the DigestInfo + * wrapper added below, but the raw digest itself must fit + * WC_MAX_DIGEST_SIZE: wc_EncodeSignature() copies its input into a + * WC_MAX_DIGEST_SIZE local when encoding in place. */ digestSz = WC_MAX_DIGEST_SIZE; ret = wolfCLU_streamHashBio(dataBio, hashType, digest, &digestSz); } @@ -436,9 +432,20 @@ static int wolfCLU_dgstSignVerify(WOLFSSL_BIO* dataBio, WOLFSSL_BIO* pubKeyBio, wolfCLU_LogError("Unable to get hash OID for DER encoding"); ret = WOLFCLU_FATAL_ERROR; } + else if (digestSz > WC_MAX_DIGEST_SIZE) { + /* Checked before the call, not after: wc_EncodeSignature() stages + * the input through a WC_MAX_DIGEST_SIZE local when out == digest, + * so an oversized digest would overflow inside wolfSSL before any + * check on the result could run. */ + wolfCLU_LogError("Digest too large to DER-encode"); + ret = WOLFCLU_FATAL_ERROR; + } else { + /* MAX_DER_DIGEST_SZ already accounts for the DigestInfo ASN.1 + * overhead (MAX_ALGO_SZ + MAX_SEQ_SZ) on top of the largest + * supported digest, so the output cannot overrun digest[]. */ enc = wc_EncodeSignature(digest, digest, digestSz, oid); - if (enc == 0) { + if (enc == 0 || enc > (word32)MAX_DER_DIGEST_SZ) { wolfCLU_LogError("Unable to DER-encode digest"); ret = WOLFCLU_FATAL_ERROR; } @@ -459,7 +466,8 @@ static int wolfCLU_dgstSignVerify(WOLFSSL_BIO* dataBio, WOLFSSL_BIO* pubKeyBio, } else { wolfCLU_LogError("Verification failure"); - if (hashType == WC_HASH_TYPE_MD5 && verifyRet == BAD_FUNC_ARG) { + if (hashType == WC_HASH_TYPE_MD5 && + verifyRet == WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { WOLFCLU_LOG(WOLFCLU_L0, "Note: MD5 below default min sig hash on wolfSSL > 5.9.1"); } @@ -502,7 +510,8 @@ static int wolfCLU_dgstSignVerify(WOLFSSL_BIO* dataBio, WOLFSSL_BIO* pubKeyBio, digest, digestSz, sig, &sigSz, key, keySz, &rng); if (signRet != 0) { wolfCLU_LogError("Error getting signature"); - if (hashType == WC_HASH_TYPE_MD5 && signRet == BAD_FUNC_ARG) { + if (hashType == WC_HASH_TYPE_MD5 && + signRet == WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { WOLFCLU_LOG(WOLFCLU_L0, "Note: MD5 below default min sig hash on wolfSSL > 5.9.1"); } @@ -512,10 +521,8 @@ static int wolfCLU_dgstSignVerify(WOLFSSL_BIO* dataBio, WOLFSSL_BIO* pubKeyBio, /* write out the signature */ if (ret == WOLFCLU_SUCCESS) { - sigBio = wolfSSL_BIO_new_file(outFile, "wb"); + sigBio = wolfCLU_OpenOutFileBio(outFile); if (sigBio == NULL) { - wolfCLU_LogError("Unable to create signature file %s", - outFile); ret = WOLFCLU_FATAL_ERROR; } } @@ -590,12 +597,14 @@ int wolfCLU_dgst_setup(int argc, char** argv) int j; for (j = 0; dgst_options[j].name != NULL; j++) { + /* An option name always wins over a same-named file on disk; + * wolfCLU_GetOpt below parses it as that flag either way. */ if (XSTRCMP(lastArg, dgst_options[j].name) == 0) { isPositional = 0; /* last token is itself an option */ break; } - if (argc >= 2 && dgst_options[j].has_arg == required_argument && - XSTRCMP(argv[argc-2], dgst_options[j].name) == 0) { + if (argc >= 2 && dgst_options[j].has_arg == required_argument + && XSTRCMP(argv[argc-2], dgst_options[j].name) == 0) { isPositional = 0; /* last token is that option's value */ break; } @@ -659,9 +668,23 @@ int wolfCLU_dgst_setup(int argc, char** argv) break; case WOLFCLU_SIGN: - signing = 1; - FALL_THROUGH; case WOLFCLU_VERIFY: + /* Both select the key, so taking them together would leak + * the first BIO and silently run in whichever mode the flag + * order happened to leave behind. */ + if (pubKeyBio != NULL) { + wolfCLU_LogError("-sign and -verify are mutually " + "exclusive"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + if (optarg == NULL) { + wolfCLU_LogError("No key passed to %s", + (option == WOLFCLU_SIGN) ? "-sign" : "-verify"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + signing = (option == WOLFCLU_SIGN) ? 1 : 0; pubKeyBio = wolfSSL_BIO_new_file(optarg, "rb"); if (pubKeyBio == NULL) { wolfCLU_LogError("Unable to open key file %s", @@ -689,6 +712,13 @@ int wolfCLU_dgst_setup(int argc, char** argv) case ARG_FOUND_TWICE: ret = WOLFCLU_FATAL_ERROR; break; + case WOLFCLU_HELP: + wolfCLU_dgstHelp(); + wolfSSL_BIO_free(dataBio); + wolfSSL_BIO_free(pubKeyBio); + /* 'ret' may already hold an error from an earlier option, + * and -help does not make a bad option line valid */ + return ret; case ':': case '?': @@ -705,9 +735,17 @@ int wolfCLU_dgst_setup(int argc, char** argv) ret = WOLFCLU_FATAL_ERROR; } - /* the sign/verify paths need a file to read/write; validate before - * dispatch so a NULL is never handed to wolfSSL_BIO_new_file/LogError. - * Also check that we have a pubkey*/ + /* -hmac silently wins dispatch below, so reject it combined with + * -sign/-verify/-signature instead of discarding those flags. */ + if (ret == WOLFCLU_SUCCESS && hmac == 1 && + (pubKeyBio != NULL || sigFile != NULL)) { + wolfCLU_LogError( + "-hmac cannot be combined with -sign, -verify, or -signature"); + ret = WOLFCLU_FATAL_ERROR; + } + + /* validate before dispatch so a NULL file is never handed to + * wolfSSL_BIO_new_file/LogError */ if (ret == WOLFCLU_SUCCESS && hmac == 0) { if (pubKeyBio == NULL) { wolfCLU_LogError("No key provided, use -sign or -verify "); @@ -723,7 +761,6 @@ int wolfCLU_dgst_setup(int argc, char** argv) } } - /* dispatch to the HMAC or sign/verify handler */ if (ret == WOLFCLU_SUCCESS) { if (hmac == 1) { ret = wolfCLU_dgstHmac(dataBio, hmacKey, hashType, outFile); diff --git a/src/sign-verify/clu_sign.c b/src/sign-verify/clu_sign.c index 8bcbf12a..52db7ed1 100644 --- a/src/sign-verify/clu_sign.c +++ b/src/sign-verify/clu_sign.c @@ -25,9 +25,8 @@ #include #include /* for xmss callback functions */ -#include - -/* Upper bound on DER size out of wolfCLU_KeyPemToDer, covering all key types. */ +/* Upper bound on DER size out of wolfCLU_KeyPemToDer, covering all key + * types. */ #ifndef WOLFCLU_MAX_KEY_PEM_DER_SZ #define WOLFCLU_MAX_KEY_PEM_DER_SZ 65536 #endif /* WOLFCLU_MAX_KEY_PEM_DER_SZ */ @@ -113,46 +112,58 @@ int wolfCLU_KeyPemToDer(unsigned char** pkeyBuf, int pkeySz, int pubIn) { return ret; } -int wolfCLU_sign_data(char* in, char* out, char* privKey, int keyType, - int inForm) +/* Treats ASN_NO_PEM_HEADER as "already DER" (returns 0, buffer untouched) + * instead of an error, logging either way. Returns the new positive size on a + * successful conversion. Deliberately not exported: two entry points for the + * same operation with different return conventions is a trap, so callers use + * wolfCLU_KeyPemToDerFallback_ex() below. */ +static int wolfCLU_KeyPemToDerFallback(unsigned char** pkeyBuf, int pkeySz, + int pubIn) +{ + int ret = wolfCLU_KeyPemToDer(pkeyBuf, pkeySz, pubIn); + + if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { + WOLFCLU_LOG(WOLFCLU_L0, "No PEM header found, treating as DER."); + ret = 0; + } + else if (ret < 0) { + wolfCLU_LogError("Failed to convert PEM to DER.\nRET: %d", ret); + } + + return ret; +} + +/* Prefer at call sites: 0 on success, updates *pkeySz when converted. */ +int wolfCLU_KeyPemToDerFallback_ex(unsigned char** pkeyBuf, int* pkeySz, + int pubIn) { int ret; - int fSz; - long fTell; - XFILE f; - byte *data = NULL; - f = XFOPEN(in, "rb"); - if (f == NULL) { - wolfCLU_LogError("unable to open file %s", in); + if (pkeyBuf == NULL || pkeySz == NULL) { return BAD_FUNC_ARG; } - if (XFSEEK(f, 0, SEEK_END) != 0) { - wolfCLU_LogError("Failed to seek to end of file."); - XFCLOSE(f); - return WOLFCLU_FATAL_ERROR; + + ret = wolfCLU_KeyPemToDerFallback(pkeyBuf, *pkeySz, pubIn); + if (ret < 0) { + return ret; } - fTell = XFTELL(f); - if (fTell <= 0 || fTell > INT_MAX) { - wolfCLU_LogError("Incorrect input file size: %ld", fTell); - XFCLOSE(f); - return WOLFCLU_FATAL_ERROR; + if (ret > 0) { + *pkeySz = ret; } - fSz = (int)fTell; + return 0; +} - data = (byte*)XMALLOC((size_t)fSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (data == NULL) { - XFCLOSE(f); - return MEMORY_E; - } +int wolfCLU_sign_data(char* in, char* out, char* privKey, int keyType, + int inForm) +{ + int ret; + int fSz; + byte *data = NULL; - if (XFSEEK(f, 0, SEEK_SET) != 0 || - XFREAD(data, 1, (size_t)fSz, f) != (size_t)fSz) { - XFREE(data, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - XFCLOSE(f); + if (wolfCLU_ReadFileToBuffer(in, WOLFCLU_MAX_FILE_SIZE, &data, &fSz) != + WOLFCLU_SUCCESS) { return WOLFCLU_FATAL_ERROR; } - XFCLOSE(f); switch(keyType) { @@ -202,7 +213,6 @@ int wolfCLU_sign_data_rsa(byte* data, char* out, word32 dataSz, char* privKey, int privFileSz = 0; word32 index = 0; - XFILE privKeyFile = NULL; byte* keyBuf = NULL; RsaKey key; @@ -240,59 +250,14 @@ int wolfCLU_sign_data_rsa(byte* data, char* out, word32 dataSz, char* privKey, /* open, read, and store RSA key */ if (ret == 0) { - /* Private key read: refuse to follow a symlink, but don't rewrite - * the mode of a key that may be provisioned by another account. */ - privKeyFile = wolfCLU_OpenExistingSecureFile(privKey, "rb", 0); - if (privKeyFile == NULL) { - wolfCLU_LogError("unable to open file %s", privKey); - ret = BAD_FUNC_ARG; - } - } - if (ret == 0) { - if (XFSEEK(privKeyFile, 0, SEEK_END) != 0) { - wolfCLU_LogError("Failed to seek to end of file."); - ret = WOLFCLU_FATAL_ERROR; - } - } - if (ret == 0) { - privFileSz = (int)XFTELL(privKeyFile); - if (privFileSz > 0 && privFileSz <= (RSA_MAX_SIZE / 8 * 16)) { - keyBuf = (byte*)XMALLOC(privFileSz+1, HEAP_HINT, - DYNAMIC_TYPE_TMP_BUFFER); - if (keyBuf == NULL) { - ret = MEMORY_E; - } - } - else { - wolfCLU_LogError("Incorrect private key file size: %d", privFileSz); - ret = WOLFCLU_FATAL_ERROR; - } - } - if (ret == 0) { - XMEMSET(keyBuf, 0, privFileSz+1); - if (XFSEEK(privKeyFile, 0, SEEK_SET) != 0 || - (int)XFREAD(keyBuf, 1, privFileSz, privKeyFile) != privFileSz) { - ret = WOLFCLU_FATAL_ERROR; - } + int rfRet = wolfCLU_ReadKeyFileToBuffer(privKey, + (long)(RSA_MAX_SIZE / 8 * 16), &keyBuf, &privFileSz); + ret = (rfRet == WOLFCLU_SUCCESS) ? 0 : WOLFCLU_FATAL_ERROR; } - /* convert PEM to DER if necessary */ + /* convert PEM to DER if necessary; negative ret propagates */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&keyBuf, privFileSz, 0); - if (ret < 0) { - if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { - WOLFCLU_LOG(WOLFCLU_L0, - "No PEM header found, treating as DER."); - ret = 0; - } - else { - wolfCLU_LogError("Failed to convert PEM to DER.\nRET: %d", ret); - } - } - else { - privFileSz = ret; - ret = 0; - } + ret = wolfCLU_KeyPemToDerFallback_ex(&keyBuf, &privFileSz, 0); } /* retrieving private key and storing in the RsaKey */ @@ -345,10 +310,6 @@ int wolfCLU_sign_data_rsa(byte* data, char* out, word32 dataSz, char* privKey, } /* cleanup allocated resources */ - if (privKeyFile != NULL) { - XFCLOSE(privKeyFile); - } - if (keyBuf!= NULL) { wolfCLU_ForceZero(keyBuf, (unsigned int)privFileSz); XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -377,7 +338,6 @@ int wolfCLU_sign_data_ecc(byte* data, char* out, word32 fSz, char* privKey, word32 outLen = 0; byte* keyBuf = NULL; - XFILE privKeyFile = NULL; ecc_key key; WC_RNG rng; @@ -404,59 +364,14 @@ int wolfCLU_sign_data_ecc(byte* data, char* out, word32 fSz, char* privKey, /* open, read, and store ecc key */ if (ret == 0) { - /* Private key read: refuse to follow a symlink, but don't rewrite - * the mode of a key that may be provisioned by another account. */ - privKeyFile = wolfCLU_OpenExistingSecureFile(privKey, "rb", 0); - if (privKeyFile == NULL) { - wolfCLU_LogError("unable to open file %s", privKey); - ret = BAD_FUNC_ARG; - } - } - if (ret == 0) { - if (XFSEEK(privKeyFile, 0, SEEK_END) != 0) { - wolfCLU_LogError("Failed to seek to end of file."); - ret = WOLFCLU_FATAL_ERROR; - } - } - if (ret == 0) { - privFileSz = (int)XFTELL(privKeyFile); - if (privFileSz > 0 && privFileSz <= (MAX_ECC_BITS_NEEDED / 8 * 16)) { - keyBuf = (byte*)XMALLOC(privFileSz+1, HEAP_HINT, - DYNAMIC_TYPE_TMP_BUFFER); - if (keyBuf == NULL) { - ret = MEMORY_E; - } - } - else { - wolfCLU_LogError("Incorrect private key file size: %d", privFileSz); - ret = WOLFCLU_FATAL_ERROR; - } - } - if (ret == 0) { - XMEMSET(keyBuf, 0, privFileSz+1); - if (XFSEEK(privKeyFile, 0, SEEK_SET) != 0 || - (int)XFREAD(keyBuf, 1, privFileSz, privKeyFile) != privFileSz) { - ret = WOLFCLU_FATAL_ERROR; - } + int rfRet = wolfCLU_ReadKeyFileToBuffer(privKey, + (long)(MAX_ECC_BITS_NEEDED / 8 * 16), &keyBuf, &privFileSz); + ret = (rfRet == WOLFCLU_SUCCESS) ? 0 : WOLFCLU_FATAL_ERROR; } - /* convert PEM to DER if necessary */ + /* convert PEM to DER if necessary; negative ret propagates */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&keyBuf, privFileSz, 0); - if (ret < 0) { - if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { - WOLFCLU_LOG(WOLFCLU_L0, - "No PEM header found, treating as DER."); - ret = 0; - } - else { - wolfCLU_LogError("Failed to convert PEM to DER.\nRET: %d", ret); - } - } - else { - privFileSz = ret; - ret = 0; - } + ret = wolfCLU_KeyPemToDerFallback_ex(&keyBuf, &privFileSz, 0); } /* retrieving private key and storing in the Ecc Key */ @@ -533,10 +448,6 @@ int wolfCLU_sign_data_ecc(byte* data, char* out, word32 fSz, char* privKey, } /* cleanup allocated resources */ - if (privKeyFile != NULL) { - XFCLOSE(privKeyFile); - } - if (keyBuf!= NULL) { wolfCLU_ForceZero(keyBuf, (unsigned int)privFileSz); XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -564,7 +475,6 @@ int wolfCLU_sign_data_ed25519 (byte* data, char* out, word32 fSz, char* privKey, word32 index = 0; word32 outLen = 0; - XFILE privKeyFile = NULL; byte* keyBuf = NULL; byte* outBuf = NULL; int outBufSz = 0; @@ -591,59 +501,14 @@ int wolfCLU_sign_data_ed25519 (byte* data, char* out, word32 fSz, char* privKey, /* open, read, and store ED25519 key */ if (ret == 0) { - /* Private key read: refuse to follow a symlink, but don't rewrite - * the mode of a key that may be provisioned by another account. */ - privKeyFile = wolfCLU_OpenExistingSecureFile(privKey, "rb", 0); - if (privKeyFile == NULL) { - wolfCLU_LogError("unable to open file %s", privKey); - ret = BAD_FUNC_ARG; - } - } - if (ret == 0) { - if (XFSEEK(privKeyFile, 0, SEEK_END) != 0) { - wolfCLU_LogError("Failed to seek to end of file."); - ret = WOLFCLU_FATAL_ERROR; - } - } - if (ret == 0) { - privFileSz = (int)XFTELL(privKeyFile); - if (privFileSz > 0 && privFileSz <= (ED25519_PRV_KEY_SIZE * 16)) { - keyBuf = (byte*)XMALLOC(privFileSz+1, HEAP_HINT, - DYNAMIC_TYPE_TMP_BUFFER); - if (keyBuf == NULL) { - ret = MEMORY_E; - } - } - else { - wolfCLU_LogError("Incorrect private key file size: %d", privFileSz); - ret = WOLFCLU_FATAL_ERROR; - } - } - if (ret == 0) { - XMEMSET(keyBuf, 0, privFileSz+1); - if (XFSEEK(privKeyFile, 0, SEEK_SET) != 0 || - (int)XFREAD(keyBuf, 1, privFileSz, privKeyFile) != privFileSz) { - ret = WOLFCLU_FATAL_ERROR; - } + int rfRet = wolfCLU_ReadKeyFileToBuffer(privKey, + (long)(ED25519_PRV_KEY_SIZE * 16), &keyBuf, &privFileSz); + ret = (rfRet == WOLFCLU_SUCCESS) ? 0 : WOLFCLU_FATAL_ERROR; } - /* convert PEM to DER if necessary */ + /* convert PEM to DER if necessary; negative ret propagates */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&keyBuf, privFileSz, 0); - if (ret < 0) { - if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { - WOLFCLU_LOG(WOLFCLU_L0, - "No PEM header found, treating as DER."); - ret = 0; - } - else { - wolfCLU_LogError("Failed to convert PEM to DER.\nRET: %d", ret); - } - } - else { - privFileSz = ret; - ret = 0; - } + ret = wolfCLU_KeyPemToDerFallback_ex(&keyBuf, &privFileSz, 0); } /* retrieve RAW private key and store in the ED25519 Key */ @@ -709,10 +574,6 @@ int wolfCLU_sign_data_ed25519 (byte* data, char* out, word32 fSz, char* privKey, } /* cleanup allocated resources */ - if (privKeyFile != NULL) { - XFCLOSE(privKeyFile); - } - if (keyBuf!= NULL) { wolfCLU_ForceZero(keyBuf, (unsigned int)privFileSz); XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -739,7 +600,6 @@ int wolfCLU_sign_data_dilithium (byte* data, char* out, word32 dataSz, char* pri int privFileSz = 0; word32 index = 0; - XFILE privKeyFile = NULL; byte* privBuf = NULL; word32 privBufSz = 0; @@ -783,62 +643,19 @@ int wolfCLU_sign_data_dilithium (byte* data, char* out, word32 dataSz, char* pri /* open and read private key */ if (ret == 0) { - /* Private key read: refuse to follow a symlink, but don't rewrite - * the mode of a key that may be provisioned by another account. */ - privKeyFile = wolfCLU_OpenExistingSecureFile(privKey, "rb", 0); - if (privKeyFile == NULL) { - wolfCLU_LogError("Failed to open Private key FILE."); - ret = BAD_FUNC_ARG; - } - } - if (ret == 0) { - if (XFSEEK(privKeyFile, 0, SEEK_END) != 0) { - wolfCLU_LogError("Failed to seek to end of file."); - ret = WOLFCLU_FATAL_ERROR; - } + int rfRet = wolfCLU_ReadKeyFileToBuffer(privKey, + (long)WOLFCLU_MAX_PQ_KEY_PEM_SIZE, &privBuf, &privFileSz); + ret = (rfRet == WOLFCLU_SUCCESS) ? 0 : WOLFCLU_FATAL_ERROR; if (ret == 0) { - privFileSz = (int)XFTELL(privKeyFile); - if (privFileSz > 0 && - privFileSz <= DILITHIUM_MAX_BOTH_KEY_PEM_SIZE) { - privBuf = (byte*)XMALLOC(privFileSz+1, HEAP_HINT, - DYNAMIC_TYPE_TMP_BUFFER); - if (privBuf == NULL) { - ret = MEMORY_E; - } - } else { - wolfCLU_LogError("Incorrect private key file size: %d", - privFileSz); - ret = WOLFCLU_FATAL_ERROR; - } - } - } - if (ret == 0) { - XMEMSET(privBuf, 0, privFileSz+1); - privBufSz = privFileSz; - if (XFSEEK(privKeyFile, 0, SEEK_SET) != 0 || - (int)XFREAD(privBuf, 1, privFileSz, privKeyFile) != privFileSz) { - wolfCLU_LogError("Failed to read private key file."); - ret = WOLFCLU_FATAL_ERROR; + privBufSz = (word32)privFileSz; } } - /* convert PEM to DER if necessary */ + /* convert PEM to DER if necessary; negative ret propagates */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&privBuf, privFileSz, 0); - if (ret < 0) { - if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { - WOLFCLU_LOG(WOLFCLU_L0, - "No PEM header found, treating as DER."); - ret = 0; - } - else { - wolfCLU_LogError("Failed to convert PEM to DER.\nRET: %d", ret); - } - } - else { - /* update privBuf and privFileSz with the converted DER data */ - privBufSz = privFileSz = ret; - ret = 0; + ret = wolfCLU_KeyPemToDerFallback_ex(&privBuf, &privFileSz, 0); + if (ret == 0) { + privBufSz = (word32)privFileSz; } } @@ -888,9 +705,6 @@ int wolfCLU_sign_data_dilithium (byte* data, char* out, word32 dataSz, char* pri } /* cleanup allocated resources */ - if (privKeyFile != NULL) - XFCLOSE(privKeyFile); - if (privBuf != NULL) { wolfCLU_ForceZero(privBuf, (unsigned int)privBufSz); XFREE(privBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -901,8 +715,8 @@ int wolfCLU_sign_data_dilithium (byte* data, char* out, word32 dataSz, char* pri } wc_dilithium_free(key); - /* rng zeroed via XMEMSET before wc_InitRng, so even if wc_InitRng failed: - * wolfSSL checks rng->drbg internally before freeing. */ + /* rng is zeroed before wc_InitRng, so wc_FreeRng is safe even on init + * failure. */ wc_FreeRng(&rng); #ifdef WOLFSSL_SMALL_STACK XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/src/sign-verify/clu_sign_verify_setup.c b/src/sign-verify/clu_sign_verify_setup.c index 7c52c8e8..c0092171 100644 --- a/src/sign-verify/clu_sign_verify_setup.c +++ b/src/sign-verify/clu_sign_verify_setup.c @@ -246,8 +246,7 @@ int wolfCLU_sign_verify_setup(int argc, char** argv) int helpCheck = 0; int inForm = DER_FORM; /* the key input format */ - /* The algorithm name is a positional mode selector (rsa, ecc, ...). - * checkForArg doesn't look for "-" here, as it would have been + /* checkForArg doesn't look for "-" here, as it would have been * removed in clu_main.c if present. */ if (wolfCLU_checkForArg("rsa", 3, argc, argv) > 0) { algCheck = RSA_SIG_VER; diff --git a/src/sign-verify/clu_verify.c b/src/sign-verify/clu_verify.c index ae4c0ff0..4857871a 100644 --- a/src/sign-verify/clu_verify.c +++ b/src/sign-verify/clu_verify.c @@ -27,117 +27,90 @@ * and ED25519_SIG_VER */ #ifndef WOLFCLU_NO_FILESYSTEM -/* Upper bound (in bytes) on a signature, hash, or message file the verify path - * will read into memory. Files larger than this are rejected rather than - * allocated. */ -#define WOLFCLU_MAX_FILE_SIZE 0xFFFFFFF - int wolfCLU_verify_signature(char* sig, char* hashFile, char* out, char* keyPath, int keyType, int pubIn, int inForm) { - long hSz = 0; - long fSz; - int dataSz = 0; + int hSz = 0; + int fSz; int ret = WOLFCLU_FATAL_ERROR; byte* hash = NULL; byte* data = NULL; - if (sig == NULL) { return BAD_FUNC_ARG; } - ret = wolfCLU_ReadFileToBuffer(sig, WOLFCLU_MAX_FILE_SIZE, &data, &dataSz); - if (ret != WOLFCLU_SUCCESS) { - return ret; + if (wolfCLU_ReadFileToBuffer(sig, WOLFCLU_MAX_FILE_SIZE, &data, &fSz) != + WOLFCLU_SUCCESS) { + return WOLFCLU_FATAL_ERROR; } - fSz = (long)dataSz; - ret = WOLFCLU_FATAL_ERROR; switch(keyType) { case RSA_SIG_VER: - ret = wolfCLU_verify_signature_rsa(data, out, (int)fSz, - keyPath, pubIn, inForm); + ret = wolfCLU_verify_signature_rsa(data, out, fSz, keyPath, + pubIn, inForm); break; case ECC_SIG_VER: - { - int hSzInt = 0; - int hRet = wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, &hash, &hSzInt); - if (hRet != WOLFCLU_SUCCESS) { - ret = hRet; - break; - } - hSz = hSzInt; + if (wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, + &hash, &hSz) != WOLFCLU_SUCCESS) { + ret = WOLFCLU_FATAL_ERROR; + break; } - ret = wolfCLU_verify_signature_ecc(data, (int)fSz, hash, (int)hSz, - keyPath, pubIn, inForm); + ret = wolfCLU_verify_signature_ecc(data, fSz, hash, hSz, keyPath, + pubIn, inForm); break; case ED25519_SIG_VER: #ifdef HAVE_ED25519 - { - int hSzInt = 0; - int hRet = wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, &hash, &hSzInt); - if (hRet != WOLFCLU_SUCCESS) { - ret = hRet; - break; - } - hSz = hSzInt; + if (wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, + &hash, &hSz) != WOLFCLU_SUCCESS) { + ret = WOLFCLU_FATAL_ERROR; + break; } - ret = wolfCLU_verify_signature_ed25519(data, (int)fSz, hash, - (int)hSz, keyPath, pubIn, inForm); + ret = wolfCLU_verify_signature_ed25519(data, fSz, hash, hSz, + keyPath, pubIn, inForm); #endif break; #ifdef HAVE_DILITHIUM case DILITHIUM_SIG_VER: - { - int hSzInt = 0; - int hRet = wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, &hash, &hSzInt); - if (hRet != WOLFCLU_SUCCESS) { - ret = hRet; - break; - } - hSz = hSzInt; + /* hashFile means msgFile, hSz means msgLen, hash means msg */ + if (wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, + &hash, &hSz) != WOLFCLU_SUCCESS) { + ret = WOLFCLU_FATAL_ERROR; + break; } - ret = wolfCLU_verify_signature_dilithium(data, (int)fSz, hash, - (int)hSz, keyPath, inForm); + ret = wolfCLU_verify_signature_dilithium(data, fSz, hash, hSz, + keyPath, inForm); break; #endif #ifdef WOLFSSL_HAVE_XMSS case XMSS_SIG_VER: - { - int hSzInt = 0; - int hRet = wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, &hash, &hSzInt); - if (hRet != WOLFCLU_SUCCESS) { - ret = hRet; - break; - } - hSz = hSzInt; + /* hashFile means msgFile, hSz means msgLen, hash means msg */ + if (wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, + &hash, &hSz) != WOLFCLU_SUCCESS) { + ret = WOLFCLU_FATAL_ERROR; + break; } - ret = wolfCLU_verify_signature_xmss(data, (int)fSz, hash, (int)hSz, - keyPath); + ret = wolfCLU_verify_signature_xmss(data, fSz, hash, hSz, keyPath); break; case XMSSMT_SIG_VER: - { - int hSzInt = 0; - int hRet = wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, &hash, &hSzInt); - if (hRet != WOLFCLU_SUCCESS) { - ret = hRet; - break; - } - hSz = hSzInt; + /* hashFile means msgFile, hSz means msgLen, hash means msg */ + if (wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, + &hash, &hSz) != WOLFCLU_SUCCESS) { + ret = WOLFCLU_FATAL_ERROR; + break; } - ret = wolfCLU_verify_signature_xmssmt(data, (int)fSz, hash, - (int)hSz, keyPath); + ret = wolfCLU_verify_signature_xmssmt(data, fSz, hash, hSz, + keyPath); break; #endif default: @@ -160,9 +133,8 @@ int wolfCLU_verify_signature_rsa(byte* sig, char* out, int sigSz, char* keyPath, #ifndef NO_RSA int ret; - long keyFileSz = 0; + int keyFileSz = 0; word32 index = 0; - XFILE keyPathFile = NULL; RsaKey key; byte* keyBuf = NULL; byte* outBuf = NULL; @@ -178,56 +150,15 @@ int wolfCLU_verify_signature_rsa(byte* sig, char* out, int sigSz, char* keyPath, /* open, read, and store RSA key */ if (ret == 0) { - keyPathFile = XFOPEN(keyPath, "rb"); - if (keyPathFile == NULL) { - wolfCLU_LogError("unable to open file %s", keyPath); - ret = BAD_FUNC_ARG; - } - } - if (ret == 0) { - XFSEEK(keyPathFile, 0, SEEK_END); - keyFileSz = XFTELL(keyPathFile); - if (keyFileSz < 0) { - wolfCLU_LogError("Unable to Get Size of Key File %s.", keyPath); - ret = BAD_FUNC_ARG; - } - else if (keyFileSz > WOLFCLU_MAX_FILE_SIZE) { - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", keyPath, (unsigned)WOLFCLU_MAX_FILE_SIZE); - ret = WOLFCLU_FATAL_ERROR; - } - } - if (ret == 0) { - keyBuf = (byte*)XMALLOC(keyFileSz+1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (keyBuf == NULL) { - ret = MEMORY_E; - } - } - if (ret == 0) { - XMEMSET(keyBuf, 0, keyFileSz+1); - if (XFSEEK(keyPathFile, 0, SEEK_SET) != 0 || - (int)XFREAD(keyBuf, 1, keyFileSz, keyPathFile) != keyFileSz) { - ret = WOLFCLU_FATAL_ERROR; - } + ret = (wolfCLU_ReadFileToBuffer(keyPath, + WOLFCLU_KEY_FILE_CAP(RSA_MAX_SIZE / 8 * 16), + &keyBuf, &keyFileSz) == WOLFCLU_SUCCESS) ? 0 : + WOLFCLU_FATAL_ERROR; } - /* convert PEM to DER if necessary */ + /* convert PEM to DER if necessary; negative ret propagates */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&keyBuf, (int)keyFileSz, pubIn); - if (ret < 0) { - if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { - WOLFCLU_LOG(WOLFCLU_L0, - "No PEM header found, treating as DER."); - ret = 0; - } - else { - wolfCLU_LogError("Failed to convert PEM to DER.\nRET: %d", ret); - } - } - else { - keyFileSz = ret; - ret = 0; - } + ret = wolfCLU_KeyPemToDerFallback_ex(&keyBuf, &keyFileSz, pubIn); } if (pubIn == 1) { @@ -292,10 +223,6 @@ int wolfCLU_verify_signature_rsa(byte* sig, char* out, int sigSz, char* keyPath, } /* Cleanup allocated resources */ - if (keyPathFile != NULL) { - XFCLOSE(keyPathFile); - } - if (outBuf != NULL) { XFREE(outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); } @@ -318,11 +245,10 @@ int wolfCLU_verify_signature_ecc(byte* sig, int sigSz, byte* hash, int hashSz, #ifdef HAVE_ECC int ret; - long keyFileSz = 0; + int keyFileSz = 0; int stat = 0; word32 index = 0; - XFILE keyPathFile = NULL; ecc_key key; byte* keyBuf = NULL; byte* outBuf = NULL; @@ -338,56 +264,15 @@ int wolfCLU_verify_signature_ecc(byte* sig, int sigSz, byte* hash, int hashSz, /* open, read, and store Ecc key */ if (ret == 0) { - keyPathFile = XFOPEN(keyPath, "rb"); - if (keyPathFile == NULL) { - wolfCLU_LogError("unable to open file %s", keyPath); - ret = BAD_FUNC_ARG; - } - } - if (ret == 0) { - XFSEEK(keyPathFile, 0, SEEK_END); - keyFileSz = XFTELL(keyPathFile); - if (keyFileSz < 0) { - wolfCLU_LogError("Unable to Get Size of Key File %s.", keyPath); - ret = BAD_FUNC_ARG; - } - else if (keyFileSz > WOLFCLU_MAX_FILE_SIZE) { - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", keyPath, (unsigned)WOLFCLU_MAX_FILE_SIZE); - ret = WOLFCLU_FATAL_ERROR; - } - } - if (ret == 0) { - keyBuf = (byte*)XMALLOC(keyFileSz+1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (keyBuf == NULL) { - ret = MEMORY_E; - } - } - if (ret == 0) { - XMEMSET(keyBuf, 0, keyFileSz+1); - if (XFSEEK(keyPathFile, 0, SEEK_SET) != 0 || - (int)XFREAD(keyBuf, 1, keyFileSz, keyPathFile) != keyFileSz) { - ret = WOLFCLU_FATAL_ERROR; - } + ret = (wolfCLU_ReadFileToBuffer(keyPath, + WOLFCLU_KEY_FILE_CAP(MAX_ECC_BITS_NEEDED / 8 * 16), + &keyBuf, &keyFileSz) == WOLFCLU_SUCCESS) ? 0 : + WOLFCLU_FATAL_ERROR; } - /* convert PEM to DER if necessary */ + /* convert PEM to DER if necessary; negative ret propagates */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&keyBuf, (int)keyFileSz, pubIn); - if (ret < 0) { - if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { - WOLFCLU_LOG(WOLFCLU_L0, - "No PEM header found, treating as DER."); - ret = 0; - } - else { - wolfCLU_LogError("Failed to convert PEM to DER.\nRET: %d", ret); - } - } - else { - keyFileSz = ret; - ret = 0; - } + ret = wolfCLU_KeyPemToDerFallback_ex(&keyBuf, &keyFileSz, pubIn); } if (pubIn == 1) { @@ -470,10 +355,6 @@ int wolfCLU_verify_signature_ecc(byte* sig, int sigSz, byte* hash, int hashSz, } /* cleanup allocated resources */ - if (keyPathFile != NULL) { - XFCLOSE(keyPathFile); - } - if (outBuf != NULL) { XFREE(outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); } @@ -497,9 +378,8 @@ int wolfCLU_verify_signature_ed25519(byte* sig, int sigSz, int ret; int stat = 0; word32 index = 0; - long keyFileSz = 0; + int keyFileSz = 0; - XFILE keyPathFile = NULL; ed25519_key key; byte* keyBuf = NULL; @@ -513,56 +393,15 @@ int wolfCLU_verify_signature_ed25519(byte* sig, int sigSz, /* open, read, and store ED25519 key */ if (ret == 0) { - keyPathFile = XFOPEN(keyPath, "rb"); - if (keyPathFile == NULL) { - wolfCLU_LogError("unable to open file %s", keyPath); - ret = BAD_FUNC_ARG; - } - } - if (ret == 0) { - XFSEEK(keyPathFile, 0, SEEK_END); - keyFileSz = XFTELL(keyPathFile); - if (keyFileSz < 0) { - wolfCLU_LogError("Unable to Get Size of Key File %s.", keyPath); - ret = BAD_FUNC_ARG; - } - else if (keyFileSz > WOLFCLU_MAX_FILE_SIZE) { - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", keyPath, (unsigned)WOLFCLU_MAX_FILE_SIZE); - ret = WOLFCLU_FATAL_ERROR; - } - } - if (ret == 0) { - keyBuf = (byte*)XMALLOC(keyFileSz+1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (keyBuf == NULL) { - ret = MEMORY_E; - } - } - if (ret == 0) { - XMEMSET(keyBuf, 0, keyFileSz+1); - if (XFSEEK(keyPathFile, 0, SEEK_SET) != 0 || - (int)XFREAD(keyBuf, 1, keyFileSz, keyPathFile) != keyFileSz) { - ret = WOLFCLU_FATAL_ERROR; - } + ret = (wolfCLU_ReadFileToBuffer(keyPath, + WOLFCLU_KEY_FILE_CAP(ED25519_PRV_KEY_SIZE * 16), + &keyBuf, &keyFileSz) == WOLFCLU_SUCCESS) ? 0 : + WOLFCLU_FATAL_ERROR; } - /* convert PEM to DER if necessary */ + /* convert PEM to DER if necessary; negative ret propagates */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&keyBuf, (int)keyFileSz, pubIn); - if (ret < 0) { - if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { - WOLFCLU_LOG(WOLFCLU_L0, - "No PEM header found, treating as DER."); - ret = 0; - } - else { - wolfCLU_LogError("Failed to convert PEM to DER.\nRET: %d", ret); - } - } - else { - keyFileSz = ret; - ret = 0; - } + ret = wolfCLU_KeyPemToDerFallback_ex(&keyBuf, &keyFileSz, pubIn); } if (pubIn == 1 && ret == 0) { @@ -626,10 +465,6 @@ int wolfCLU_verify_signature_ed25519(byte* sig, int sigSz, } /* cleanup allocated resources */ - if (keyPathFile != NULL) { - XFCLOSE(keyPathFile); - } - if (keyBuf != NULL) { XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); } @@ -649,9 +484,8 @@ int wolfCLU_verify_signature_dilithium(byte* sig, int sigSz, byte* msg, #ifdef HAVE_DILITHIUM int ret = 0; - XFILE keyFile = NULL; byte* keyBuf = NULL; - long keyFileSz = 0; + int keyFileSz = 0; word32 keyBufSz = 0; word32 index = 0; int res = 0; @@ -681,54 +515,8 @@ int wolfCLU_verify_signature_dilithium(byte* sig, int sigSz, byte* msg, } /* open and read public key */ - keyFile = XFOPEN(keyPath, "rb"); - if (keyFile == NULL) { - wolfCLU_LogError("Failed to open public key FILE."); - wc_dilithium_free(key); - #ifdef WOLFSSL_SMALL_STACK - XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - #endif - return BAD_FUNC_ARG; - } - - XFSEEK(keyFile, 0, SEEK_END); - keyFileSz = XFTELL(keyFile); - if (keyFileSz <= 0) { - wolfCLU_LogError("Failed to get valid size of public key FILE."); - XFCLOSE(keyFile); - wc_dilithium_free(key); - #ifdef WOLFSSL_SMALL_STACK - XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - #endif - return BAD_FUNC_ARG; - } - if (keyFileSz > DILITHIUM_MAX_BOTH_KEY_PEM_SIZE) { - wolfCLU_LogError("Incorrect public key file size: %ld", keyFileSz); - XFCLOSE(keyFile); - wc_dilithium_free(key); - #ifdef WOLFSSL_SMALL_STACK - XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - #endif - return WOLFCLU_FATAL_ERROR; - } - - keyBuf = (byte*)XMALLOC(keyFileSz + 1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (keyBuf == NULL) { - wolfCLU_LogError("Failed to malloc key buffer."); - XFCLOSE(keyFile); - wc_dilithium_free(key); - #ifdef WOLFSSL_SMALL_STACK - XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - #endif - return MEMORY_E; - } - XMEMSET(keyBuf, 0, keyFileSz + 1); - - if (XFSEEK(keyFile, 0, SEEK_SET) != 0 || - (int)XFREAD(keyBuf, 1, keyFileSz, keyFile) != keyFileSz) { - wolfCLU_LogError("Failed to read public key.\nRET: %d", ret); - XFCLOSE(keyFile); - XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (wolfCLU_ReadFileToBuffer(keyPath, (long)WOLFCLU_MAX_PQ_KEY_PEM_SIZE, + &keyBuf, &keyFileSz) != WOLFCLU_SUCCESS) { wc_dilithium_free(key); #ifdef WOLFSSL_SMALL_STACK XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -736,30 +524,19 @@ int wolfCLU_verify_signature_dilithium(byte* sig, int sigSz, byte* msg, return WOLFCLU_FATAL_ERROR; } keyBufSz = (word32)keyFileSz; - XFCLOSE(keyFile); /* convert PEM to DER if necessary */ if (inForm == PEM_FORM) { - ret = wolfCLU_KeyPemToDer(&keyBuf, (int)keyFileSz, 1); - if (ret < 0) { - if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { - WOLFCLU_LOG(WOLFCLU_L0, - "No PEM header found, treating as DER."); - ret = 0; - } - else { - wolfCLU_LogError("Failed to convert PEM to DER.\nRET: %d", ret); - XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - wc_dilithium_free(key); - #ifdef WOLFSSL_SMALL_STACK - XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - #endif - return ret; - } - } - else { - keyBufSz = ret; + ret = wolfCLU_KeyPemToDerFallback_ex(&keyBuf, &keyFileSz, 1); + if (ret != 0) { + XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_dilithium_free(key); + #ifdef WOLFSSL_SMALL_STACK + XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + #endif + return ret; } + keyBufSz = (word32)keyFileSz; } /* retrieving public key and storing in the dilithium key */ @@ -818,9 +595,8 @@ int wolfCLU_verify_signature_xmss(byte* sig, int sigSz, { #ifdef WOLFSSL_HAVE_XMSS int ret = 0; - XFILE keyFile = NULL; /* public key file */ byte* keyBuf = NULL; /* public key buffer */ - long keyFileSz = 0; /* public key buffer size */ + int keyFileSz = 0; /* public key buffer size */ word32 oid = 0x0; /* OID of the XMSS parameter */ char* paramStr = NULL; /* XMSS parameter string */ int paramLen = XMSS_NAME_LEN + 1; /* XMSS parameter string length */ @@ -845,48 +621,15 @@ int wolfCLU_verify_signature_xmss(byte* sig, int sigSz, /* open and read public key */ if (ret == 0) { - keyFile = XFOPEN(pubKey, "rb"); - if (keyFile == NULL) { - ret = OUTPUT_FILE_ERROR; - wolfCLU_LogError("Failed to open Public key FILE."); - } - } - - if (ret == 0) { - XFSEEK(keyFile, 0, SEEK_END); - keyFileSz = XFTELL(keyFile); - if (keyFileSz < 0) { + if (wolfCLU_ReadFileToBuffer(pubKey, (long)WOLFCLU_MAX_PQ_KEY_PEM_SIZE, + &keyBuf, &keyFileSz) != WOLFCLU_SUCCESS) { ret = WOLFCLU_FATAL_ERROR; - wolfCLU_LogError("Failed to get size of public key FILE."); } - else if (keyFileSz > WOLFCLU_MAX_FILE_SIZE) { + else if (keyFileSz < (int)XMSS_OID_LEN) { + /* The OID is read straight off the front of the file below. */ + wolfCLU_LogError("XMSS public key file is too small to hold an " + "OID"); ret = WOLFCLU_FATAL_ERROR; - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", pubKey, (unsigned)WOLFCLU_MAX_FILE_SIZE); - } - } - - if (ret == 0) { - keyBuf = (byte*)XMALLOC(keyFileSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (keyBuf == NULL) { - ret = MEMORY_E; - wolfCLU_LogError("Failed to malloc key buffer.\nRET: %d", ret); - } - else { - XMEMSET(keyBuf, 0, keyFileSz); - } - } - - if (ret == 0) { - if (XFSEEK(keyFile, 0, SEEK_SET) != 0 || - (int)XFREAD(keyBuf, 1, keyFileSz, keyFile) != keyFileSz) { - ret = WOLFCLU_FATAL_ERROR; - wolfCLU_LogError("Failed to read public key." - "\nRET: %d", ret); - } - else { - XFCLOSE(keyFile); - keyFile = NULL; } } @@ -954,9 +697,6 @@ int wolfCLU_verify_signature_xmss(byte* sig, int sigSz, } /* cleanup allocated resources */ - if (keyFile != NULL) { - XFCLOSE(keyFile); - } if (keyBuf != NULL) { XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); } @@ -986,9 +726,8 @@ int wolfCLU_verify_signature_xmssmt(byte* sig, int sigSz, { #ifdef WOLFSSL_HAVE_XMSS int ret = 0; - XFILE keyFile = NULL; /* public key file */ byte* keyBuf = NULL; /* public key buffer */ - long keyFileSz = 0; /* public key buffer size */ + int keyFileSz = 0; /* public key buffer size */ word32 oid = 0x0; /* OID of the XMSS parameter */ char* paramStr = NULL; /* XMSS parameter string */ int paramLen = XMSSMT_NAME_MAX_LEN + 1; /* XMSS parameter string length */ @@ -1013,47 +752,15 @@ int wolfCLU_verify_signature_xmssmt(byte* sig, int sigSz, /* open and read public key */ if (ret == 0) { - keyFile = XFOPEN(pubKey, "rb"); - if (keyFile == NULL) { - ret = OUTPUT_FILE_ERROR; - wolfCLU_LogError("Failed to open Public key FILE."); - } - } - - if (ret == 0) { - XFSEEK(keyFile, 0, SEEK_END); - keyFileSz = XFTELL(keyFile); - if (keyFileSz < 0) { + if (wolfCLU_ReadFileToBuffer(pubKey, (long)WOLFCLU_MAX_PQ_KEY_PEM_SIZE, + &keyBuf, &keyFileSz) != WOLFCLU_SUCCESS) { ret = WOLFCLU_FATAL_ERROR; - wolfCLU_LogError("Failed to get size of public key FILE."); } - else if (keyFileSz > WOLFCLU_MAX_FILE_SIZE) { + else if (keyFileSz < (int)XMSS_OID_LEN) { + /* The OID is read straight off the front of the file below. */ + wolfCLU_LogError("XMSS^MT public key file is too small to hold " + "an OID"); ret = WOLFCLU_FATAL_ERROR; - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", pubKey, (unsigned)WOLFCLU_MAX_FILE_SIZE); - } - } - - if (ret == 0) { - keyBuf = (byte*)XMALLOC(keyFileSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (keyBuf == NULL) { - ret = MEMORY_E; - wolfCLU_LogError("Failed to malloc key buffer.\nRET: %d", ret); - } - else { - XMEMSET(keyBuf, 0, keyFileSz); - } - } - - if (ret == 0) { - if (XFSEEK(keyFile, 0, SEEK_SET) != 0 || - (int)XFREAD(keyBuf, 1, keyFileSz, keyFile) != keyFileSz) { - ret = WOLFCLU_FATAL_ERROR; - wolfCLU_LogError("Failed to read public key.\nRET: %d", ret); - } - else { - XFCLOSE(keyFile); - keyFile = NULL; } } @@ -1136,9 +843,6 @@ int wolfCLU_verify_signature_xmssmt(byte* sig, int sigSz, } /* cleanup allocated resources */ - if (keyFile != NULL) { - XFCLOSE(keyFile); - } if (keyBuf != NULL) { XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); } diff --git a/src/sign-verify/clu_x509_verify.c b/src/sign-verify/clu_x509_verify.c index 81c1f8b1..3f302ce9 100644 --- a/src/sign-verify/clu_x509_verify.c +++ b/src/sign-verify/clu_x509_verify.c @@ -172,13 +172,6 @@ int wolfCLU_x509Verify(int argc, char** argv) } } - if (ret == WOLFCLU_SUCCESS && caCert != NULL && !partialChain && - (XSTRCMP(caCert, verifyCert) == 0)) { - wolfCLU_LogError("Unless -partial_chain is passed as an argument " - "-CAFile cannot be the same as the cert to verify"); - ret = WOLFCLU_FATAL_ERROR; - } - if (ret == WOLFCLU_SUCCESS) { cert = load_cert_from_file(verifyCert); if (!cert) { @@ -218,6 +211,14 @@ int wolfCLU_x509Verify(int argc, char** argv) } } + if (ret == WOLFCLU_SUCCESS && caCert != NULL) { + if (!partialChain && wolfCLU_PathsRefEqual(caCert, verifyCert)) { + wolfCLU_LogError("Cannot verify a certificate against itself " + "as a CA without -partial_chain"); + ret = WOLFCLU_FATAL_ERROR; + } + } + /* Confirm CA file is root CA unless partialChain enabled */ if (ret == WOLFCLU_SUCCESS){ if (!partialChain && caCert != NULL){ diff --git a/src/tools/clu_funcs.c b/src/tools/clu_funcs.c index 24869abf..e0b3f685 100644 --- a/src/tools/clu_funcs.c +++ b/src/tools/clu_funcs.c @@ -1158,6 +1158,35 @@ word32 wolfCLU_DerSetLength(word32 length, byte* output) return sz; } +/* Parse decimal days string into [1, WOLFCLU_MAX_CERT_DAYS]. + * Returns WOLFCLU_SUCCESS or USER_INPUT_ERROR. Avoids strtol/errno. */ +int wolfCLU_ParseDaysArg(const char* arg, int* daysOut) +{ + word32 days = 0; + const char* cur; + + if (arg == NULL || daysOut == NULL || arg[0] == '\0') { + return USER_INPUT_ERROR; + } + + for (cur = arg; *cur != '\0'; cur++) { + if (*cur < '0' || *cur > '9') { + return USER_INPUT_ERROR; + } + days = (days * 10) + (word32)(*cur - '0'); + if (days > (word32)WOLFCLU_MAX_CERT_DAYS) { + return USER_INPUT_ERROR; + } + } + + if (days < 1) { + return USER_INPUT_ERROR; + } + + *daysOut = (int)days; + return WOLFCLU_SUCCESS; +} + void wolfCLU_ForceZero(void* mem, unsigned int len) { #ifndef WOLFSSL_NO_FORCE_ZERO @@ -2644,3 +2673,96 @@ int wolfCLU_hmacHash(WOLFSSL_HMAC_CTX *ctx, void* key, word32 len, wolfCLU_ForceZero(digest, sizeof(digest)); return ret; } + +/* helper function to convert a key to PEM format. Creates new 'out' buffer on + * success. + * returns size of PEM buffer created on success + * returns 0 or negative value on failure */ +int wolfCLU_KeyDerToPem(const byte* der, int derSz, byte** out, int pemType, + int heapType) +{ + int pemBufSz; + byte* pemBuf = NULL; + + if (out == NULL || der == NULL || derSz <= 0) { + return 0; + } + + pemBufSz = wc_DerToPemEx(der, derSz, NULL, 0, NULL, pemType); + if (pemBufSz > 0) { + pemBuf = (byte*)XMALLOC(pemBufSz, HEAP_HINT, heapType); + if (pemBuf == NULL) { + pemBufSz = 0; + } + else { + pemBufSz = wc_DerToPemEx(der, derSz, pemBuf, pemBufSz, NULL, + pemType); + } + } + + if (pemBufSz <= 0 && pemBuf != NULL) { + XFREE(pemBuf, HEAP_HINT, heapType); + pemBuf = NULL; + } + *out = pemBuf; + return pemBufSz; +} + +int wolfCLU_DerToPemBuf(const byte* der, int derSz, int pemType, + byte** outBuf, int* outBufSz) +{ + int pemSz; + byte* pemBuf = NULL; + + if (der == NULL || derSz <= 0 || outBuf == NULL || outBufSz == NULL) { + return BAD_FUNC_ARG; + } + + pemSz = wolfCLU_KeyDerToPem(der, derSz, &pemBuf, pemType, + DYNAMIC_TYPE_TMP_BUFFER); + if (pemSz <= 0) { + wolfCLU_LogError("DER to PEM conversion failed: %d", pemSz); + return (pemSz < 0) ? pemSz : WOLFCLU_FATAL_ERROR; + } + + *outBuf = pemBuf; + *outBufSz = pemSz; + return WOLFCLU_SUCCESS; +} + +/* Write signed cert DER to bioOut; returns WOLFCLU_SUCCESS or an error code. */ +int wolfCLU_WriteCertBio(WOLFSSL_BIO* bioOut, int outForm, + const byte* certBuf, int certDerSz, int pemType) +{ + int ret = WOLFCLU_SUCCESS; + int pemOutSz = 0; + byte* pemBuf = NULL; + + if (bioOut == NULL || certBuf == NULL || certDerSz <= 0) { + return BAD_FUNC_ARG; + } + + if (outForm == DER_FORM) { + if (wolfSSL_BIO_write(bioOut, certBuf, certDerSz) != certDerSz) { + ret = WOLFCLU_FATAL_ERROR; + } + return ret; + } + + ret = wolfCLU_DerToPemBuf(certBuf, certDerSz, pemType, &pemBuf, + &pemOutSz); + if (ret != WOLFCLU_SUCCESS) { + return ret; + } + + if (wolfSSL_BIO_write(bioOut, pemBuf, pemOutSz) != pemOutSz) { + ret = WOLFCLU_FATAL_ERROR; + } + else { + ret = WOLFCLU_SUCCESS; + } + + wolfCLU_ForceZero(pemBuf, pemOutSz); + XFREE(pemBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} diff --git a/src/x509/clu_ca_setup.c b/src/x509/clu_ca_setup.c index 566cc3ae..29ff1caa 100644 --- a/src/x509/clu_ca_setup.c +++ b/src/x509/clu_ca_setup.c @@ -105,11 +105,13 @@ int wolfCLU_CASetup(int argc, char** argv) int days = 0; int selfSigned = 0; int altSign = 0; + int x509Owned = 0; opterr = 0; /* do not display unrecognized options */ optind = 0; /* start at indent 0 */ - while ((option = wolfCLU_GetOpt(argc, argv, "", ca_options, - &longIndex )) != END_OF_ARGS) { + while (ret == WOLFCLU_SUCCESS && + (option = wolfCLU_GetOpt(argc, argv, "", ca_options, + &longIndex)) != END_OF_ARGS) { switch (option) { case WOLFCLU_INFILE: @@ -205,7 +207,14 @@ int wolfCLU_CASetup(int argc, char** argv) break; case WOLFCLU_DAYS: - days = XATOI(optarg); + /* #5879: validate -days to prevent RFC 5280 notAfter + * overflow. */ + if (wolfCLU_ParseDaysArg(optarg, &days) != WOLFCLU_SUCCESS) { + wolfCLU_LogError("-days must be a positive integer " + "in [1, %d], got: %s", WOLFCLU_MAX_CERT_DAYS, + optarg); + ret = USER_INPUT_ERROR; + } break; case WOLFCLU_EXTENSIONS: @@ -214,7 +223,8 @@ int wolfCLU_CASetup(int argc, char** argv) case WOLFCLU_HELP: wolfCLU_CAHelp(); - return WOLFCLU_SUCCESS; + ret = WOLFCLU_SUCCESS; + goto cleanup; case ARG_FOUND_TWICE: wolfCLU_LogError("Found duplicate argument"); @@ -234,7 +244,7 @@ int wolfCLU_CASetup(int argc, char** argv) } } - if (reqIn == NULL && !altSign) { + if (ret == WOLFCLU_SUCCESS && reqIn == NULL && !altSign) { wolfCLU_LogError("Expecting CSR input"); ret = WOLFCLU_FATAL_ERROR; } @@ -242,10 +252,10 @@ int wolfCLU_CASetup(int argc, char** argv) if (ret == WOLFCLU_SUCCESS && config != NULL) { signer = wolfCLU_readSignConfig(config, (char*)"ca"); } - else { + else if (ret == WOLFCLU_SUCCESS) { signer = wolfCLU_CertSignNew(); } - if (signer == NULL) { + if (ret == WOLFCLU_SUCCESS && signer == NULL) { wolfCLU_LogError("Unable to create a signer struct"); ret = WOLFCLU_FATAL_ERROR; } @@ -289,11 +299,7 @@ int wolfCLU_CASetup(int argc, char** argv) if (ret == WOLFCLU_SUCCESS && (pkey != NULL || ca != NULL || altKey != NULL || altKeyPub != NULL)) { - if (selfSigned) { - wolfCLU_CertSignSetCA(signer, x509, pkey, - wolfCLU_GetTypeFromPKEY(pkey)); - } - else if (altSign) { + if (altSign) { char* subjName = wolfSSL_X509_NAME_oneline( wolfSSL_X509_get_subject_name(x509), 0, 0); if (subjName != NULL) { @@ -306,8 +312,17 @@ int wolfCLU_CASetup(int argc, char** argv) } } else { - wolfCLU_CertSignSetCA(signer, ca, pkey, - wolfCLU_GetTypeFromPKEY(pkey)); + int pkeyType = wolfCLU_GetTypeFromPKEY(pkey); + wolfCLU_CertSignSetCA(signer, selfSigned ? x509 : ca, pkey, + pkeyType); + if (selfSigned) { + /* ownership of x509 was transferred to signer */ + x509Owned = 1; + } + if (pkeyType == RSAk || pkeyType == ECDSAk) { + /* ownership of pkey was transferred to signer */ + pkey = NULL; + } } } @@ -327,6 +342,7 @@ int wolfCLU_CASetup(int argc, char** argv) ret = wolfCLU_CertSign(signer, x509); } +cleanup: wolfSSL_BIO_free(reqIn); wolfSSL_BIO_free(keyIn); if (altKey != NULL) { @@ -338,12 +354,15 @@ int wolfCLU_CASetup(int argc, char** argv) if (subjKey != NULL) { wolfSSL_BIO_free(subjKey); } - if (!selfSigned) { + if (!x509Owned) { wolfSSL_X509_free(x509); } if ((selfSigned || altSign) && ca != NULL) { wolfSSL_X509_free(ca); } + if (pkey != NULL) { + wolfSSL_EVP_PKEY_free(pkey); + } /* check for success on signer free since random data is output */ if (wolfCLU_CertSignFree(signer) != WOLFCLU_SUCCESS) { diff --git a/src/x509/clu_cert_setup.c b/src/x509/clu_cert_setup.c index a7cda803..e51e34f3 100644 --- a/src/x509/clu_cert_setup.c +++ b/src/x509/clu_cert_setup.c @@ -19,6 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#include + #include #include #include @@ -110,6 +112,79 @@ static const struct option cert_options[] = { }; #endif +int wolfCLU_FreeKeyCtx(CLU_KEY_CTX* ctx) +{ + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + if (ctx->evp != NULL) { + wolfSSL_EVP_PKEY_free(ctx->evp); + ctx->evp = NULL; + } + if (ctx->key != NULL && ctx->keyFree != NULL) { + ctx->keyFree(&ctx->key); + } + ctx->key = NULL; + ctx->keyFree = NULL; + ctx->keyType = 0; + ctx->level = 0; + return WOLFCLU_SUCCESS; +} + +int wolfCLU_LoadKey(const char* file, CLU_KEY_CTX* ctx) +{ + WOLFSSL_BIO* keyBio = NULL; + + if (file == NULL || ctx == NULL) { + return BAD_FUNC_ARG; + } + + XMEMSET(ctx, 0, sizeof(*ctx)); + + keyBio = wolfSSL_BIO_new_file(file, "rb"); + if (keyBio != NULL) { + ctx->evp = wolfSSL_PEM_read_bio_PrivateKey(keyBio, NULL, NULL, NULL); + wolfSSL_BIO_free(keyBio); + } + + if (ctx->evp == NULL) { + /* Algorithms EVP cannot represent hook in ahead of this by filling + * ctx->key themselves; nothing here accepted the file. */ + wolfCLU_LogError("Failed to load key from %s", file); + return USER_INPUT_ERROR; + } + return WOLFCLU_SUCCESS; +} + +/* Calls wolfCLU_extenstionGetObjectNID(), which is only compiled when a + * filesystem is available; the sole caller (wolfCLU_CertSign) is behind the + * same guard. */ +#if defined(WOLFSSL_CERT_EXT) && !defined(WOLFCLU_NO_FILESYSTEM) +/* Set (non-critical) basicConstraints CA:TRUE or CA:FALSE on x509. + * Returns WOLFCLU_SUCCESS or WOLFCLU_FATAL_ERROR. */ +int wolfCLU_SetBasicConstraintsCA(WOLFSSL_X509* x509, int ca) +{ + WOLFSSL_X509_EXTENSION* bcExt = wolfSSL_X509_EXTENSION_new(); + WOLFSSL_ASN1_OBJECT* obj = wolfCLU_extenstionGetObjectNID(bcExt, + NID_basic_constraints, 0); + + /* NULL means bcExt was already freed internally; only free on success. */ + if (obj == NULL) { + wolfCLU_LogError("Failed to set basicConstraints"); + return WOLFCLU_FATAL_ERROR; + } + + obj->ca = ca ? 1 : 0; + if (wolfSSL_X509_add_ext(x509, bcExt, -1) != WOLFSSL_SUCCESS) { + wolfCLU_LogError("Failed to set basicConstraints"); + wolfSSL_X509_EXTENSION_free(bcExt); + return WOLFCLU_FATAL_ERROR; + } + wolfSSL_X509_EXTENSION_free(bcExt); + return WOLFCLU_SUCCESS; +} +#endif /* WOLFSSL_CERT_EXT && !WOLFCLU_NO_FILESYSTEM */ + /* return WOLFCLU_SUCCESS on success */ int wolfCLU_certSetup(int argc, char **argv) { @@ -373,7 +448,7 @@ int wolfCLU_certSetup(int argc, char **argv) ret = WOLFCLU_FATAL_ERROR; } else { - inBufSz -= inBufCertBegin - inBufRaw; + inBufSz -= (int)(inBufCertBegin - inBufRaw); inBuf = inBufCertBegin; } } @@ -442,17 +517,25 @@ int wolfCLU_certSetup(int argc, char **argv) long line = 0; conf = wolfSSL_NCONF_new(NULL); - wolfSSL_NCONF_load(conf, extFile, &line); - if (wolfSSL_NCONF_get_section(conf, ext) == NULL) { - wolfCLU_LogError("Unable to find certificate extension " - "section %s", - ext); + /* #5971: NCONF alloc can fail; propagate rather than passing NULL to + * subsequent NCONF helpers which may crash or silently no-op. */ + if (conf == NULL) { + wolfCLU_LogError("Failed to allocate config context for -extfile"); ret = WOLFCLU_FATAL_ERROR; } else { - ret = wolfCLU_setExtensions(x509, conf, ext); + wolfSSL_NCONF_load(conf, extFile, &line); + if (wolfSSL_NCONF_get_section(conf, ext) == NULL) { + wolfCLU_LogError("Unable to find certificate extension " + "section %s", + ext); + ret = WOLFCLU_FATAL_ERROR; + } + else { + ret = wolfCLU_setExtensions(x509, conf, ext); + } + wolfSSL_NCONF_free(conf); } - wolfSSL_NCONF_free(conf); } /*default to version 3 which supports extensions */ @@ -844,3 +927,1379 @@ int wolfCLU_certSetup(int argc, char **argv) return WOLFCLU_FATAL_ERROR; #endif } + + +#ifdef WOLFSSL_CERT_GEN + +/* Extensions are read directly off the DER here to avoid requiring the + * full OPENSSL_EXTRA/OPENSSL_ALL compatibility layer. */ +#ifdef WOLFSSL_CERT_EXT + +/* DER content bytes (tag/length stripped) of OIDs this file recognizes. */ +/* 2.5.29.19 */ +static const byte kOidBasicConstraints[] = {0x55, 0x1D, 0x13}; +/* 2.5.29.15 */ +static const byte kOidKeyUsage[] = {0x55, 0x1D, 0x0F}; +/* 2.5.29.37 */ +static const byte kOidExtKeyUsage[] = {0x55, 0x1D, 0x25}; +/* 2.5.29.14 */ +static const byte kOidSubjectKeyIdentifier[] = {0x55, 0x1D, 0x0E}; +/* 2.5.29.35 */ +static const byte kOidAuthorityKeyIdentifier[] = {0x55, 0x1D, 0x23}; +/* 2.5.29.17 */ +static const byte kOidSubjectAltName[] = {0x55, 0x1D, 0x11}; + +/* One decoded Extension entry; oid/val point into the caller-owned DER + * buffer, nothing here allocates. */ +typedef struct WOLFCLU_X509_EXT { + const byte* oid; + word32 oidLen; + int critical; + const byte* val; + word32 valLen; +} WOLFCLU_X509_EXT; + +static int wolfCLU_OidEquals(const byte* oid, word32 oidLen, + const byte* known, word32 knownLen) +{ + return oid != NULL && oidLen == knownLen && + XMEMCMP(oid, known, knownLen) == 0; +} + +/* Decode a DER tag+length header at buf[*idx]. On success *idx is advanced + * past the header, *tag is the raw tag byte, and *len is the content + * length (already bounds-checked against bufSz). */ +static int wolfCLU_DerGetHeader(const byte* buf, word32 bufSz, word32* idx, + byte* tag, word32* len) +{ + word32 i = *idx; + byte lenByte; + + if (i >= bufSz) { + return BUFFER_E; + } + *tag = buf[i++]; + + if (i >= bufSz) { + return BUFFER_E; + } + lenByte = buf[i++]; + + if ((lenByte & 0x80) == 0) { + *len = lenByte; + } + else { + int nBytes = lenByte & 0x7F; + word32 l = 0; + int j; + + if (nBytes == 0 || nBytes > (int)sizeof(word32) || + i + (word32)nBytes > bufSz) { + return ASN_PARSE_E; + } + for (j = 0; j < nBytes; j++) { + l = (l << 8) | buf[i++]; + } + *len = l; + } + + if (*len > bufSz - i) { + return BUFFER_E; + } + *idx = i; + return WOLFCLU_SUCCESS; +} + +/* Decode one Extension SEQUENCE entry at buf[*idx]. Advances *idx past the + * entry only on full success. Returns WOLFCLU_SUCCESS or ASN_PARSE_E. */ +static int wolfCLU_DerGetExtension(const byte* buf, word32 bufSz, + word32* idx, WOLFCLU_X509_EXT* ext) +{ + byte tag; + word32 len; + word32 i; + word32 seqIdx; + word32 seqEnd; + int ret; + + if (buf == NULL || idx == NULL || ext == NULL) { + return BAD_FUNC_ARG; + } + + i = *idx; + ret = wolfCLU_DerGetHeader(buf, bufSz, &i, &tag, &len); + if (ret != WOLFCLU_SUCCESS || tag != (ASN_SEQUENCE | ASN_CONSTRUCTED)) { + return ASN_PARSE_E; + } + seqIdx = i; + seqEnd = seqIdx + len; + + ret = wolfCLU_DerGetHeader(buf, seqEnd, &seqIdx, &tag, &len); + if (ret != WOLFCLU_SUCCESS || tag != ASN_OBJECT_ID) { + return ASN_PARSE_E; + } + ext->oid = buf + seqIdx; + ext->oidLen = len; + seqIdx += len; + + ext->critical = 0; + if (seqIdx < seqEnd && buf[seqIdx] == ASN_BOOLEAN) { + ret = wolfCLU_DerGetHeader(buf, seqEnd, &seqIdx, &tag, &len); + if (ret != WOLFCLU_SUCCESS || len != 1) { + return ASN_PARSE_E; + } + ext->critical = (buf[seqIdx] != 0); + seqIdx += len; + } + + ret = wolfCLU_DerGetHeader(buf, seqEnd, &seqIdx, &tag, &len); + if (ret != WOLFCLU_SUCCESS || tag != ASN_OCTET_STRING) { + return ASN_PARSE_E; + } + ext->val = buf + seqIdx; + ext->valLen = len; + + *idx = seqEnd; + return WOLFCLU_SUCCESS; +} + +/* Skip optional `[3] EXPLICIT Extensions` tag and inner SEQUENCE header + * so *extensions points directly at the entries. + * Exposed non-static so the unwrap logic can be unit tested directly. */ +int wolfCLU_UnwrapX509Extensions(const byte** extensions, int* extensionsSz) +{ + const byte* buf; + word32 bufSz; + word32 idx = 0; + byte tag; + word32 len; + + if (extensions == NULL || *extensions == NULL || extensionsSz == NULL) { + return BAD_FUNC_ARG; + } + buf = *extensions; + bufSz = (word32)*extensionsSz; + + if (wolfCLU_DerGetHeader(buf, bufSz, &idx, &tag, &len) != + WOLFCLU_SUCCESS || tag != ASN_EXTENSIONS) { + idx = 0; /* not [3]-wrapped; try a bare SEQUENCE at offset 0 */ + } + + if (wolfCLU_DerGetHeader(buf, bufSz, &idx, &tag, &len) == + WOLFCLU_SUCCESS && tag == (ASN_SEQUENCE | ASN_CONSTRUCTED)) { + *extensions = buf + idx; + *extensionsSz = (int)len; + } + /* Leaving the buffer as-is is a valid outcome, not a failure: the caller + * asked for the wrapper to be stripped if one is present. */ + return WOLFCLU_SUCCESS; +} + +/* Parse x509's raw DER into dCert to access Extensions. + * Caller must wc_FreeDecodedCert(dCert). dCert points into x509's buffer. + * Must use CERT_TYPE (x509 is a placeholder cert, not a CSR) because + * downstream SAN parsing (wc_SetAltNamesBuffer) hardcodes CERT_TYPE. */ +static int wolfCLU_GetX509RawExtensions(WOLFSSL_X509* x509, + DecodedCert* dCert) +{ + const byte* der; + int derSz = 0; + int ret; + + der = wolfSSL_X509_get_der(x509, &derSz); + if (der == NULL || derSz <= 0) { + wolfCLU_LogError("Could not get CSR's raw DER"); + return WOLFCLU_FATAL_ERROR; + } + + wc_InitDecodedCert(dCert, der, (word32)derSz, NULL); + ret = wc_ParseCert(dCert, CERT_TYPE, NO_VERIFY, NULL); + if (ret != 0) { + wolfCLU_LogError("Could not parse CSR's DER to read extensions"); + wc_FreeDecodedCert(dCert); + return WOLFCLU_FATAL_ERROR; + } + + + if (dCert->extensions != NULL && dCert->extensionsSz > 0) { + (void)wolfCLU_UnwrapX509Extensions(&dCert->extensions, + &dCert->extensionsSz); + } + + return WOLFCLU_SUCCESS; +} + +/* Find the first extension in an already-parsed CSR's extensions matching + * the given OID. Returns WOLFCLU_SUCCESS with *found set, or a fatal error + * on a malformed CSR. dCert must already be parsed via + * wolfCLU_GetX509RawExtensions(). */ +static int wolfCLU_FindX509Ext(DecodedCert* dCert, const byte* oid, + word32 oidLen, WOLFCLU_X509_EXT* ext, int* found) +{ + word32 idx = 0; + + *found = 0; + + while (idx < (word32)dCert->extensionsSz) { + WOLFCLU_X509_EXT cur; + + if (wolfCLU_DerGetExtension(dCert->extensions, + (word32)dCert->extensionsSz, &idx, &cur) != WOLFCLU_SUCCESS) { + wolfCLU_LogError("Malformed extension in CSR extensions"); + return WOLFCLU_FATAL_ERROR; + } + if (wolfCLU_OidEquals(cur.oid, cur.oidLen, oid, oidLen)) { + *ext = cur; + *found = 1; + break; + } + } + + return WOLFCLU_SUCCESS; +} + +/* DER content bytes of the standard extKeyUsage purpose OIDs + * (id-kp-* under 1.3.6.1.5.5.7.3.* and anyExtendedKeyUsage). */ +static const byte kOidEkuServerAuth[] = + {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01}; +static const byte kOidEkuClientAuth[] = + {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02}; +static const byte kOidEkuCodeSigning[] = + {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03}; +static const byte kOidEkuEmailProt[] = + {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04}; +static const byte kOidEkuTimeStamping[] = + {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x08}; +static const byte kOidEkuOcspSigning[] = + {0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x09}; +/* 2.5.29.37.0 */ +static const byte kOidEkuAny[] = {0x55, 0x1D, 0x25, 0x00}; + +/* Decode an extKeyUsage extnValue (`ExtKeyUsageSyntax ::= SEQUENCE OF + * KeyPurposeId`) into wolfcrypt's EXTKEYUSE_* bitmask. Unrecognized + * purpose OIDs (e.g. SGC, DVCS) are silently skipped, matching how they + * had no EXTKEYUSE_* bit to map to previously either. */ +static byte wolfCLU_DerGetExtKeyUsageBits(const byte* val, word32 valLen) +{ + byte eku = 0; + byte tag; + word32 len; + word32 idx = 0; + word32 seqEnd; + + if (wolfCLU_DerGetHeader(val, valLen, &idx, &tag, &len) != + WOLFCLU_SUCCESS || + tag != (ASN_SEQUENCE | ASN_CONSTRUCTED)) { + return 0; + } + seqEnd = idx + len; + + while (idx < seqEnd) { + word32 oidIdx = idx; + const byte* oid; + + if (wolfCLU_DerGetHeader(val, seqEnd, &oidIdx, &tag, &len) != + WOLFCLU_SUCCESS || + tag != ASN_OBJECT_ID) { + break; + } + oid = val + oidIdx; + + if (wolfCLU_OidEquals(oid, len, kOidEkuServerAuth, + (word32)sizeof(kOidEkuServerAuth))) { + eku |= EXTKEYUSE_SERVER_AUTH; + } + else if (wolfCLU_OidEquals(oid, len, kOidEkuClientAuth, + (word32)sizeof(kOidEkuClientAuth))) { + eku |= EXTKEYUSE_CLIENT_AUTH; + } + else if (wolfCLU_OidEquals(oid, len, kOidEkuEmailProt, + (word32)sizeof(kOidEkuEmailProt))) { + eku |= EXTKEYUSE_EMAILPROT; + } + else if (wolfCLU_OidEquals(oid, len, kOidEkuCodeSigning, + (word32)sizeof(kOidEkuCodeSigning))) { + eku |= EXTKEYUSE_CODESIGN; + } + else if (wolfCLU_OidEquals(oid, len, kOidEkuOcspSigning, + (word32)sizeof(kOidEkuOcspSigning))) { + eku |= EXTKEYUSE_OCSP_SIGN; + } + else if (wolfCLU_OidEquals(oid, len, kOidEkuTimeStamping, + (word32)sizeof(kOidEkuTimeStamping))) { + eku |= EXTKEYUSE_TIMESTAMP; + } + else if (wolfCLU_OidEquals(oid, len, kOidEkuAny, + (word32)sizeof(kOidEkuAny))) { + eku |= EXTKEYUSE_ANY; + } + + idx = oidIdx + len; + } + + return eku; +} +#endif /* WOLFSSL_CERT_EXT */ + +int wolfCLU_SetCertNameFieldByNid(CertName* dst, int nid, const char* val, + int valLen) +{ + char* field = NULL; + + if (dst == NULL || val == NULL || valLen <= 0) { + return BAD_FUNC_ARG; + } + + switch (nid) { + case NID_countryName: + field = dst->country; + break; + case NID_stateOrProvinceName: + field = dst->state; + break; + case NID_localityName: + field = dst->locality; + break; + case NID_organizationName: + field = dst->org; + break; + case NID_organizationalUnitName: + field = dst->unit; + break; + case NID_commonName: + field = dst->commonName; + break; + case NID_emailAddress: + field = dst->email; + break; + case NID_streetAddress: + field = dst->street; + break; + case NID_surname: + field = dst->sur; + break; + case NID_serialNumber: + field = dst->serialDev; + break; + case NID_userId: + field = dst->userId; + break; + case NID_postalCode: + field = dst->postalCode; + break; +#ifdef WOLFSSL_CERT_NAME_ALL + case NID_givenName: + field = dst->givenName; + break; + case NID_initials: + field = dst->initials; + break; + case NID_dnQualifier: + field = dst->dnQualifier; + break; +#endif +#ifdef WOLFSSL_CERT_EXT + case NID_businessCategory: + field = dst->busCat; + break; +#endif + default: + /* Reject rather than drop, for the same reason oversized values + * are rejected below: dropping a whole RDN issues a certificate + * whose subject differs from the one that was requested. */ + wolfCLU_LogError("DN field (nid %d) has no destination in the " + "issued certificate", nid); + return WOLFCLU_FATAL_ERROR; + } + + /* Reject rather than silently truncate: the old XSTRLCPY-based behavior + * cut oversized DN values short and signed anyway, issuing a certificate + * with a corrupted identity field. */ + if (valLen > CTC_NAME_SIZE - 1) { + wolfCLU_LogError("DN field (nid %d) exceeds %d-byte limit", + nid, CTC_NAME_SIZE - 1); + return WOLFCLU_FATAL_ERROR; + } + XMEMCPY(field, val, (size_t)valLen); + field[valLen] = '\0'; + + return WOLFCLU_SUCCESS; +} + +int wolfCLU_CopyX509NameToCert(WOLFSSL_X509_NAME* name, CertName* dst) +{ + int i; + + if (name == NULL || dst == NULL) { + return BAD_FUNC_ARG; + } + + for (i = 0; i < wolfSSL_X509_NAME_entry_count(name); i++) { + WOLFSSL_X509_NAME_ENTRY* e; + WOLFSSL_ASN1_OBJECT* obj; + WOLFSSL_ASN1_STRING* str; + const char* val; + int nid; + int valLen; + int ret; + + e = wolfSSL_X509_NAME_get_entry(name, i); + if (e == NULL) { + continue; + } + obj = wolfSSL_X509_NAME_ENTRY_get_object(e); + str = wolfSSL_X509_NAME_ENTRY_get_data(e); + if (obj == NULL || str == NULL) { + continue; + } + + nid = wolfSSL_OBJ_obj2nid(obj); + val = (const char*)wolfSSL_ASN1_STRING_data(str); + valLen = wolfSSL_ASN1_STRING_length(str); + if (val == NULL || valLen <= 0) { + continue; + } + + ret = wolfCLU_SetCertNameFieldByNid(dst, nid, val, valLen); + if (ret != WOLFCLU_SUCCESS) { + return ret; + } + } + + return WOLFCLU_SUCCESS; +} + +/* Re-encode a WOLFSSL_ASN1_TIME as a DER tag+length+value suitable for + * Cert->beforeDate/afterDate. Returns the encoded length or a negative + * error code. */ +int wolfCLU_Asn1TimeToCertDate(byte* out, int outSz, + const WOLFSSL_ASN1_TIME* t) +{ + int sz, i; + + /* Sanity bound on t->length vs ASN1_TIME's own buffer; not the output + * capacity check -- that's the t->length + 2 > outSz check below. */ + if (out == NULL || t == NULL || t->length <= 0 || + t->length > CTC_DATE_SIZE) { + return BUFFER_E; + } + /* Validate DER tag: UTCTime (23) or GeneralizedTime (24) expected. */ + if (t->type != V_ASN1_UTCTIME && t->type != V_ASN1_GENERALIZEDTIME) { + return BUFFER_E; + } + if (outSz <= 0) { + return BUFFER_E; + } + /* t->length <= 32 always DER-encodes with a 1-byte tag + 1-byte + * short-form length; this is the real output-capacity check. */ + if (t->length + 2 > outSz) { + return BUFFER_E; + } + + sz = (int)wolfCLU_DerSetLength((word32)t->length, NULL) + 1; + if (sz + t->length > outSz) { + return BUFFER_E; + } + wolfCLU_DerSetLength((word32)t->length, out + 1); + + out[0] = (byte)t->type; + for (i = 0; i < t->length; i++) { + out[sz + i] = t->data[i]; + } + return t->length + sz; +} + +/* Copy subjectAltName from an already-parsed dCert onto cert. + * Avoids redundant DER parsing by reusing the existing dCert structure. */ +#if defined(WOLFSSL_ALT_NAMES) && defined(HAVE_WC_SET_ALT_NAMES_FROM_LIST) +static int wolfCLU_CopyX509SanToCertFromDCert(DecodedCert* dCert, Cert* cert) +{ + if (dCert == NULL || cert == NULL) { + return BAD_FUNC_ARG; + } + if (cert->altNamesSz > 0) { + wolfCLU_Log(WOLFCLU_L0, "Warning: wolfCLU_CopyX509SanToCert called " + "on a Cert that already has altNames; skipping to avoid " + "double-population"); + return WOLFCLU_SUCCESS; + } + + if (wc_SetAltNamesFromList(cert, dCert->altNames) != 0) { + wolfCLU_LogError("Error copying subjectAltName from CSR"); + return WOLFCLU_FATAL_ERROR; + } + + return WOLFCLU_SUCCESS; +} +#endif /* WOLFSSL_ALT_NAMES && HAVE_WC_SET_ALT_NAMES_FROM_LIST */ + +/* Copy subjectAltName from CSR to cert. Returns WOLFCLU_SUCCESS or error. */ +#if defined(WOLFSSL_ALT_NAMES) +int wolfCLU_CopyX509SanToCert(WOLFSSL_X509* x509, Cert* cert) +{ +#if defined(HAVE_WC_SET_ALT_NAMES_FROM_LIST) && defined(WOLFSSL_CERT_EXT) + /* Parse just for dCert->altNames -- avoids wc_SetAltNamesBuffer()'s + * own internal, CERT_TYPE-only re-parse of x509's whole raw DER. */ + DecodedCert dCert; + int ret; + + if (x509 == NULL || cert == NULL) { + return BAD_FUNC_ARG; + } + + ret = wolfCLU_GetX509RawExtensions(x509, &dCert); + if (ret != WOLFCLU_SUCCESS) { + return ret; + } + + ret = wolfCLU_CopyX509SanToCertFromDCert(&dCert, cert); + wc_FreeDecodedCert(&dCert); + return ret; +#else + /* Fall back to wc_SetAltNamesBuffer() to re-parse the raw DER, + * avoiding manual SAN/GeneralName parsing without OPENSSL_EXTRA. */ + const byte* der; + int derSz = 0; + + if (x509 == NULL || cert == NULL) { + return BAD_FUNC_ARG; + } + if (cert->altNamesSz > 0) { + wolfCLU_Log(WOLFCLU_L0, "Warning: wolfCLU_CopyX509SanToCert called " + "on a Cert that already has altNames; skipping to avoid " + "double-population"); + return WOLFCLU_SUCCESS; + } + + der = wolfSSL_X509_get_der(x509, &derSz); + if (der == NULL || derSz <= 0) { + wolfCLU_LogError("Could not get CSR's raw DER"); + return WOLFCLU_FATAL_ERROR; + } + + if (wc_SetAltNamesBuffer(cert, der, derSz) != 0) { + wolfCLU_LogError("Error copying subjectAltName from CSR"); + return WOLFCLU_FATAL_ERROR; + } + + return WOLFCLU_SUCCESS; +#endif /* HAVE_WC_SET_ALT_NAMES_FROM_LIST && WOLFSSL_CERT_EXT */ +} +#endif /* WOLFSSL_ALT_NAMES */ + +#ifdef WOLFSSL_CERT_EXT +/* Extensions that wolfCLU_X509FillCert already handles explicitly. */ +typedef struct { + int nid; + const byte* oid; + word32 oidLen; +} wolfCLU_HandledExt; + +static const wolfCLU_HandledExt kHandledExts[] = { + { NID_basic_constraints, kOidBasicConstraints, + (word32)sizeof(kOidBasicConstraints) }, + { NID_key_usage, kOidKeyUsage, + (word32)sizeof(kOidKeyUsage) }, + { NID_ext_key_usage, kOidExtKeyUsage, + (word32)sizeof(kOidExtKeyUsage) }, + { NID_subject_key_identifier, kOidSubjectKeyIdentifier, + (word32)sizeof(kOidSubjectKeyIdentifier) }, + { NID_authority_key_identifier, kOidAuthorityKeyIdentifier, + (word32)sizeof(kOidAuthorityKeyIdentifier) }, +#if defined(WOLFSSL_ALT_NAMES) + /* Only claim SAN as handled when wolfCLU_CopyX509SanToCert actually + * runs to copy it (guarded the same way, see clu_cert.h). */ + { NID_subject_alt_name, kOidSubjectAltName, + (word32)sizeof(kOidSubjectAltName) }, +#endif +}; +#define WOLFCLU_NUM_HANDLED_EXTS \ + (sizeof(kHandledExts) / sizeof(kHandledExts[0])) + +int wolfCLU_ExtHandledNid(int nid) +{ + size_t i; + + for (i = 0; i < WOLFCLU_NUM_HANDLED_EXTS; i++) { + if (kHandledExts[i].nid == nid) { + return 1; + } + } + return 0; +} + +/* Look up handled extensions by raw OID DER. */ +static int wolfCLU_ExtHandledOid(const byte* oid, word32 oidLen) +{ + size_t i; + + for (i = 0; i < WOLFCLU_NUM_HANDLED_EXTS; i++) { + if (wolfCLU_OidEquals(oid, oidLen, kHandledExts[i].oid, + kHandledExts[i].oidLen)) { + return 1; + } + } + return 0; +} + +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_CUSTOM_OID) && \ + defined(HAVE_OID_ENCODING) +/* Decode DER-encoded OID content bytes into a NUL-terminated dotted-decimal + * string. */ +static int wolfCLU_OidDerToDotted(const byte* oid, word32 oidLen, + char* out, size_t outSz) +{ + word32 i; + int written; + int firstArc; + unsigned long arc; + size_t len; + + if (oid == NULL || oidLen == 0 || out == NULL || outSz == 0) { + return BAD_FUNC_ARG; + } + /* last base-128 group must be complete (continuation bit clear). */ + if ((oid[oidLen - 1] & 0x80) != 0) { + return ASN_PARSE_E; + } + + /* The first identifier byte encodes the first two arcs as 40*X+Y but is + * still base-128 continued; a single-byte read mis-decodes any OID + * whose first two arcs combine to >= 128 (e.g. 2.100.3). */ + arc = 0; + firstArc = -1; + i = 0; + while (i < oidLen) { + /* reject a run of continuation bytes long enough to shift bits + * out of arc, rather than silently wrapping into a bogus arc + * value. */ + if (arc > (ULONG_MAX >> 7)) { + return ASN_PARSE_E; + } + arc = (arc << 7) | (unsigned long)(oid[i] & 0x7F); + if ((oid[i] & 0x80) == 0) { + firstArc = 1; + i++; + break; + } + i++; + } + if (firstArc < 0) { + return ASN_PARSE_E; + } + + if (arc < 40) { + written = XSNPRINTF(out, outSz, "0.%lu", arc); + } + else if (arc < 80) { + written = XSNPRINTF(out, outSz, "1.%lu", arc - 40); + } + else { + written = XSNPRINTF(out, outSz, "2.%lu", arc - 80); + } + if (written < 0 || (size_t)written >= outSz) { + return BUFFER_E; + } + + arc = 0; + for (; i < oidLen; i++) { + if (arc > (ULONG_MAX >> 7)) { + return ASN_PARSE_E; + } + arc = (arc << 7) | (unsigned long)(oid[i] & 0x7F); + if ((oid[i] & 0x80) == 0) { + len = XSTRLEN(out); + written = XSNPRINTF(out + len, outSz - len, ".%lu", arc); + if (written < 0 || (size_t)written >= outSz - len) { + return BUFFER_E; + } + arc = 0; + } + } + return WOLFCLU_SUCCESS; +} +#endif /* WOLFSSL_ASN_TEMPLATE && WOLFSSL_CUSTOM_OID && HAVE_OID_ENCODING */ + +/* Carry unhandled CSR extensions onto the wolfcrypt Cert. */ +static int wolfCLU_CopyX509ExtsToCertFromDCert(DecodedCert* dCert, Cert* cert, + int* extsDropped) +{ + int ret = WOLFCLU_SUCCESS; + int uncopied = 0; + word32 idx = 0; + + if (extsDropped != NULL) { + *extsDropped = 0; + } + if (dCert == NULL || cert == NULL) { + return BAD_FUNC_ARG; + } + + while (ret == WOLFCLU_SUCCESS && idx < (word32)dCert->extensionsSz) { + WOLFCLU_X509_EXT ext; + + if (wolfCLU_DerGetExtension(dCert->extensions, + (word32)dCert->extensionsSz, &idx, &ext) != WOLFCLU_SUCCESS) { + wolfCLU_LogError("Malformed extension in CSR extensions"); + ret = WOLFCLU_FATAL_ERROR; + continue; + } + if (wolfCLU_ExtHandledOid(ext.oid, ext.oidLen)) { + continue; /* already copied explicitly by wolfCLU_X509FillCert */ + } + +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_CUSTOM_OID) && \ + defined(HAVE_OID_ENCODING) + { + char oid[80]; + + if (wolfCLU_OidDerToDotted(ext.oid, ext.oidLen, oid, + sizeof(oid)) != WOLFCLU_SUCCESS) { + if (ext.critical) { + wolfCLU_LogError("Could not encode a critical " + "extension's OID; refusing to issue"); + ret = WOLFCLU_FATAL_ERROR; + continue; + } + wolfCLU_Log(WOLFCLU_L0, + "Warning: could not encode an extension " + "OID; not copied to the certificate"); + uncopied = 1; + continue; + } + /* wc_SetCustomExtension keeps these pointers as-is, and both + * point into x509's DER buffer; heap-copy so cert doesn't + * depend on x509 outliving encoding. */ + { + char* oidHeap = (char*)XMALLOC(XSTRLEN(oid) + 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + byte* valHeap = NULL; + + if (oidHeap == NULL) { + wolfCLU_LogError("Out of memory copying extension OID"); + ret = WOLFCLU_FATAL_ERROR; + } + else { + /* +1 to avoid a 0-byte XMALLOC(), which can return NULL + * and be mistaken for an out-of-memory failure. */ + valHeap = (byte*)XMALLOC((size_t)ext.valLen + 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (valHeap == NULL) { + XFREE(oidHeap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wolfCLU_LogError( + "Out of memory copying extension value"); + ret = WOLFCLU_FATAL_ERROR; + } + } + + if (ret == WOLFCLU_SUCCESS) { + XMEMCPY(oidHeap, oid, XSTRLEN(oid) + 1); + if (ext.valLen > 0) { + XMEMCPY(valHeap, ext.val, (size_t)ext.valLen); + } + if (wc_SetCustomExtension(cert, ext.critical, oidHeap, + valHeap, ext.valLen) < 0) { + XFREE(oidHeap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(valHeap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (ext.critical) { + wolfCLU_LogError("Failed to copy a critical " + "extension (OID %s); refusing to issue", + oid); + ret = WOLFCLU_FATAL_ERROR; + } + else { + wolfCLU_Log(WOLFCLU_L0, + "Warning: failed to copy extension " + "(OID %s) to the certificate", oid); + uncopied = 1; + } + } + } + } + } +#else + if (ext.critical) { + wolfCLU_LogError("This build cannot copy a critical CSR " + "extension; refusing to issue"); + ret = WOLFCLU_FATAL_ERROR; + continue; + } + uncopied = 1; /* this build cannot copy arbitrary extensions */ +#endif /* WOLFSSL_ASN_TEMPLATE && WOLFSSL_CUSTOM_OID && HAVE_OID_ENCODING */ + } + + if (ret == WOLFCLU_SUCCESS && uncopied) { + wolfCLU_Log(WOLFCLU_L0, + "Warning: this build only carries basicConstraints, " + "keyUsage, extKeyUsage, subjectKeyIdentifier, " + "authorityKeyIdentifier and subjectAltName; other CSR " + "extensions were not copied (build wolfSSL with " + "WOLFSSL_CUSTOM_OID + HAVE_OID_ENCODING to carry arbitrary " + "extensions)"); + if (extsDropped != NULL) { + *extsDropped = 1; + } + } + + return ret; +} + +/* Carry CSR extensions that wolfCLU_X509FillCert does not handle explicitly + * onto the wolfcrypt Cert. */ +int wolfCLU_CopyX509ExtsToCert(WOLFSSL_X509* x509, Cert* cert, + int* extsDropped) +{ + int ret; + DecodedCert dCert; + + if (x509 == NULL || cert == NULL) { + return BAD_FUNC_ARG; + } + + ret = wolfCLU_GetX509RawExtensions(x509, &dCert); + if (ret != WOLFCLU_SUCCESS) { + return ret; + } + + ret = wolfCLU_CopyX509ExtsToCertFromDCert(&dCert, cert, extsDropped); + if (ret != WOLFCLU_SUCCESS) { + /* Free any custom-extension buffers a partial copy already + * attached to cert before returning the failure. */ + (void)wolfCLU_FreeCertCustomExts(cert); + } + wc_FreeDecodedCert(&dCert); + return ret; +} + +/* Frees the oid/val buffers allocated by wolfCLU_CopyX509ExtsToCert; call + * once the Cert is done being used (after signing/encoding). */ +int wolfCLU_FreeCertCustomExts(Cert* cert) +{ +#ifdef WOLFSSL_CUSTOM_OID + int i; + + if (cert == NULL) { + return BAD_FUNC_ARG; + } + for (i = 0; i < cert->customCertExtCount; i++) { + if (cert->customCertExt[i].oid != NULL) { + XFREE((void*)cert->customCertExt[i].oid, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + cert->customCertExt[i].oid = NULL; + } + if (cert->customCertExt[i].val != NULL) { + XFREE((void*)cert->customCertExt[i].val, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + cert->customCertExt[i].val = NULL; + } + } + cert->customCertExtCount = 0; +#else + if (cert == NULL) { + return BAD_FUNC_ARG; + } +#endif /* WOLFSSL_CUSTOM_OID */ + return WOLFCLU_SUCCESS; +} +#endif /* WOLFSSL_CERT_EXT */ + +#ifdef WOLFSSL_CERT_EXT +/* Default leaf-cert keyUsage for a subject key type (the CertType space, + * e.g. RSA_TYPE/ECC_TYPE/ED25519_TYPE/...). Only RSA is also used for key + * encipherment; every other type here (including ML-DSA once wired up) is + * signature-only. */ +static word16 wolfCLU_LeafKeyUsageDefault(int subjWcKeyType) +{ + switch (subjWcKeyType) { + case RSA_TYPE: + return KU_DIGITAL_SIGNATURE | KU_KEY_ENCIPHERMENT; + default: + return KU_DIGITAL_SIGNATURE; + } +} +#endif /* WOLFSSL_CERT_EXT */ + +#ifdef WOLFSSL_CERT_EXT +/* Set keyUsage and extKeyUsage on cert. ku is what the CSR asked for and + * dCert is that same CSR, parsed; isCSR builds a request rather than an + * issued certificate. + * return WOLFCLU_SUCCESS on success */ +static int wolfCLU_SetCertKeyUsage(Cert* cert, DecodedCert* dCert, word16 ku, + int subjWcKeyType, int isCA, int isCSR) +{ + int ret = WOLFCLU_SUCCESS; + + if (isCSR) { + /* A request carries what the requester asked for. Issuance policy is + * the signer's job and is applied when the request is signed, not + * when it is created. */ + cert->keyUsage = ku; + } + else if (isCA) { + /* A CA cert's keyUsage is always exactly keyCertSign/cRLSign; the + * CSR's requested keyUsage (ku) is intentionally ignored here so a + * CSR cannot grant itself extra key usages on a CA certificate. */ + cert->keyUsage = KU_KEY_CERT_SIGN | KU_CRL_SIGN; + } + else { + /* subjWcKeyType is the CertType space (RSA_TYPE/ECC_TYPE/...) also + * used by wc_SetSubjectKeyIdFromPublicKey_ex(). */ + cert->keyUsage = wolfCLU_LeafKeyUsageDefault(subjWcKeyType); + /* CSR keyUsage can only add bits on top of the default, never + * narrow it; CA-only bits are masked out so a leaf CSR can't grant + * itself keyCertSign/cRLSign. wolfSSL_X509_get_keyUsage() returns 0 + * both when the extension is absent and when it is empty, and either + * way there is nothing to merge. */ + cert->keyUsage |= (word16)(ku & ~(KU_KEY_CERT_SIGN | KU_CRL_SIGN)); + } + + cert->extKeyUsage = 0; + if (isCSR || !isCA) { + /* wolfSSL_X509_get_extended_key_usage() needs full OPENSSL_EXTRA; + * read the extKeyUsage extension natively instead. An issued CA cert + * never gets the CSR's extKeyUsage: same rationale as the keyUsage + * lock-down above -- a CSR shouldn't be able to grant itself + * extended key usages on a CA certificate. */ + WOLFCLU_X509_EXT ext; + int found = 0; + + ret = wolfCLU_FindX509Ext(dCert, kOidExtKeyUsage, + (word32)sizeof(kOidExtKeyUsage), &ext, &found); + if (ret == WOLFCLU_SUCCESS && found) { + cert->extKeyUsage = wolfCLU_DerGetExtKeyUsageBits(ext.val, + ext.valLen); + } + } + + return ret; +} + +/* Set the subject and authority key identifiers on cert. dCert is the parsed + * CSR the certificate is being built from. + * return WOLFCLU_SUCCESS on success */ +static int wolfCLU_SetCertKeyIds(Cert* cert, DecodedCert* dCert, + void* subjWcKey, int subjWcKeyType, void* caWcKey, int caWcKeyType, + int isCSR) +{ + int ret = WOLFCLU_SUCCESS; + int emitSkid = 1; + + if (isCSR) { + /* In a request the SKID is something the requester asked for, so + * only carry it over when it is actually present. */ + WOLFCLU_X509_EXT ext; + int found = 0; + + ret = wolfCLU_FindX509Ext(dCert, kOidSubjectKeyIdentifier, + (word32)sizeof(kOidSubjectKeyIdentifier), &ext, &found); + emitSkid = (ret == WOLFCLU_SUCCESS && found); + } + /* An issued certificate always gets one: RFC 5280 section 4.2.1.2 + * requires it for CA certificates and recommends it for end entities, + * and path building depends on it. A CSR never carries one, so gating on + * the input's extensions would mean no issued certificate ever had a + * SKID. */ + if (ret == WOLFCLU_SUCCESS && emitSkid) { + /* subjWcKey != NULL is enforced by the caller's parameter + * validation. */ + if (wc_SetSubjectKeyIdFromPublicKey_ex(cert, subjWcKeyType, + subjWcKey) < 0) { + wolfCLU_LogError("Error setting subject key identifier"); + ret = WOLFCLU_FATAL_ERROR; + } + } + + /* The AKID identifies the issuer's key, so it is information only the + * issuer can supply and is meaningless in a request. RFC 5280 section + * 4.2.1.1 requires conforming CAs to include it. */ + if (ret == WOLFCLU_SUCCESS && !isCSR && caWcKey != NULL) { + if (wc_SetAuthKeyIdFromPublicKey_ex(cert, caWcKeyType, + caWcKey) < 0) { + wolfCLU_LogError("Error setting authority key identifier"); + ret = WOLFCLU_FATAL_ERROR; + } + } + + return ret; +} +#endif /* WOLFSSL_CERT_EXT */ + +/* Populate a wolfcrypt Cert from a CSR. isCSR selects between the two very + * different jobs this does: building a certificate to be issued, where CA + * policy decides the key usages and the issuer contributes an authority key + * identifier, and building a certificate request, which must carry exactly + * what the requester asked for. + * return WOLFCLU_SUCCESS on success */ +static int wolfCLU_X509FillCert_ex(WOLFSSL_X509* x509, Cert* cert, + int sigType, void* subjWcKey, int subjWcKeyType, + void* caWcKey, int caWcKeyType, WOLFSSL_X509* caCert, + int policySanitized, int isCSR, int* extsDropped) +{ + int ret = WOLFCLU_SUCCESS; + int ku; + int isCA; + int selfSigned; + WOLFSSL_X509_NAME* name; + const WOLFSSL_ASN1_TIME* nb; + const WOLFSSL_ASN1_TIME* na; +#ifdef WOLFSSL_CERT_EXT + DecodedCert dCert; + int dCertValid = 0; +#endif + + if (extsDropped != NULL) { + *extsDropped = 0; + } + + if (x509 == NULL || cert == NULL || subjWcKey == NULL) { + return BAD_FUNC_ARG; + } + + /* x509's basicConstraints/keyUsage can be attacker-controlled CSR content; + * refuse to sign unless policySanitized says it's safe. */ + if (!policySanitized) { + wolfCLU_LogError("CSR policy not sanitized; refusing to sign"); + return WOLFCLU_FATAL_ERROR; + } + + ku = wolfSSL_X509_get_keyUsage(x509); + /* Use get_isCA() to read the in-memory isCA field, which correctly + * reflects any config overrides applied via wolfCLU_setExtensions(). + * Untrusted CA:TRUE claims from CSRs have already been rejected by the + * policy sanitizer in wolfCLU_CertSign before reaching here. */ + selfSigned = (caCert == NULL || caCert == x509); + isCA = wolfSSL_X509_get_isCA(x509); + + if (wc_InitCert(cert) != 0) { + return WOLFCLU_FATAL_ERROR; + } + cert->version = 2; /* X.509 v3; wc_InitCert default */ + cert->sigType = sigType; + + cert->isCA = isCA ? 1 : 0; + cert->pathLen = 0; + cert->pathLenSet = 0; + /* Propagate the CSR/config's pathLenConstraint if set. */ + if (isCA && wolfSSL_X509_get_isSet_pathLength(x509)) { + unsigned int plen = wolfSSL_X509_get_pathLength(x509); + cert->pathLen = (plen > 255) ? 255 : (byte)plen; + cert->pathLenSet = 1; + } + +#ifdef WOLFSSL_CERT_EXT + /* Parse the CSR's DER once and reuse it for every wolfCLU_FindX509Ext() + * lookup below, instead of each lookup re-parsing the same DER. */ + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_GetX509RawExtensions(x509, &dCert); + dCertValid = (ret == WOLFCLU_SUCCESS); + } + + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_SetCertKeyUsage(cert, &dCert, (word16)ku, + subjWcKeyType, isCA, isCSR); + } +#else + (void)isCA; + (void)ku; + (void)isCSR; +#endif /* WOLFSSL_CERT_EXT */ + + /* wolfSSL_X509_get_notBefore()/_notAfter() always return a pointer to + * the embedded ASN1_TIME field, even when it was never set (e.g. a + * freshly-created X509 or one built from a CSR, which carries no + * validity dates at all) -- length == 0 in that case. Only convert + * when a date was actually set; leaving *DateSz at 0 lets callers like + * wolfCLU_MakeAndSignCertDer fall back to a days-valid default. A date + * that IS set but fails to convert is a real error. */ + nb = wolfSSL_X509_get_notBefore(x509); + na = wolfSSL_X509_get_notAfter(x509); + if (ret == WOLFCLU_SUCCESS && nb != NULL && nb->length > 0) { + cert->beforeDateSz = wolfCLU_Asn1TimeToCertDate(cert->beforeDate, + CTC_DATE_SIZE, nb); + if (cert->beforeDateSz <= 0) { + wolfCLU_LogError("Error converting notBefore date"); + ret = WOLFCLU_FATAL_ERROR; + } + } + if (ret == WOLFCLU_SUCCESS && na != NULL && na->length > 0) { + cert->afterDateSz = wolfCLU_Asn1TimeToCertDate(cert->afterDate, + CTC_DATE_SIZE, na); + if (cert->afterDateSz <= 0) { + wolfCLU_LogError("Error converting notAfter date"); + ret = WOLFCLU_FATAL_ERROR; + } + } + + if (ret == WOLFCLU_SUCCESS) { + byte serial[EXTERNAL_SERIAL_SIZE]; + int serialSz = EXTERNAL_SERIAL_SIZE; + + if (wolfSSL_X509_get_serial_number(x509, serial, &serialSz) != + WOLFSSL_SUCCESS || serialSz <= 0) { + wolfCLU_LogError("Error reading serial number"); + ret = WOLFCLU_FATAL_ERROR; + } + else if (serialSz > CTC_SERIAL_SIZE) { + wolfCLU_LogError("Serial number too large"); + ret = WOLFCLU_FATAL_ERROR; + } + else { + XMEMCPY(cert->serial, serial, (size_t)serialSz); + cert->serialSz = serialSz; + } + } + + if (ret == WOLFCLU_SUCCESS) { + name = wolfSSL_X509_get_subject_name(x509); + if (name == NULL) { + wolfCLU_LogError("CSR has no subject name"); + ret = BAD_FUNC_ARG; + } + else { + ret = wolfCLU_CopyX509NameToCert(name, &cert->subject); + } + } + + if (ret == WOLFCLU_SUCCESS) { + /*CA-signed: issuer is CA's subject. */ + name = (caCert != NULL) + ? wolfSSL_X509_get_subject_name(caCert) + : wolfSSL_X509_get_subject_name(x509); + cert->selfSigned = selfSigned ? 1 : 0; + if (name != NULL) { + ret = wolfCLU_CopyX509NameToCert(name, &cert->issuer); + } + else if (caCert != NULL) { + wolfCLU_LogError("CA certificate has no subject name"); + ret = BAD_FUNC_ARG; + } + } + +#ifdef WOLFSSL_CERT_EXT + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_SetCertKeyIds(cert, &dCert, subjWcKey, subjWcKeyType, + caWcKey, caWcKeyType, isCSR); + } + + /* Carry any remaining CSR extensions (or warn that they were dropped), + * reusing the dCert parsed above instead of paying for a second + * wc_ParseCert() over the identical CSR DER. */ + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_CopyX509ExtsToCertFromDCert(&dCert, cert, extsDropped); + } + +#if defined(WOLFSSL_ALT_NAMES) && defined(HAVE_WC_SET_ALT_NAMES_FROM_LIST) + /* Also reuse dCert for SAN copying, avoiding wc_SetAltNamesBuffer()'s + * own independent CERT_TYPE-only re-parse of the same CSR DER. */ + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_CopyX509SanToCertFromDCert(&dCert, cert); + } +#endif + + if (dCertValid) { + wc_FreeDecodedCert(&dCert); + dCertValid = 0; + } +#endif /* WOLFSSL_CERT_EXT */ + +#if defined(WOLFSSL_ALT_NAMES) && \ + !(defined(WOLFSSL_CERT_EXT) && defined(HAVE_WC_SET_ALT_NAMES_FROM_LIST)) + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_CopyX509SanToCert(x509, cert); + } +#endif + +#ifdef WOLFSSL_CERT_EXT + /* On failure, free any buffers a partial wolfCLU_CopyX509ExtsToCert() + * already handed to cert. On success, the caller owns cert and must + * call wolfCLU_FreeCertCustomExts() itself. */ + if (ret != WOLFCLU_SUCCESS) { + (void)wolfCLU_FreeCertCustomExts(cert); + } +#endif /* WOLFSSL_CERT_EXT */ + + return ret; +} + +/* Populate a wolfcrypt Cert from a CSR for CA signing. + * return WOLFCLU_SUCCESS on success */ +int wolfCLU_X509FillCert(WOLFSSL_X509* x509, Cert* cert, int sigType, + void* subjWcKey, int subjWcKeyType, + void* caWcKey, int caWcKeyType, WOLFSSL_X509* caCert, + int policySanitized, int* extsDropped) +{ + return wolfCLU_X509FillCert_ex(x509, cert, sigType, subjWcKey, + subjWcKeyType, caWcKey, caWcKeyType, caCert, policySanitized, 0, + extsDropped); +} +#endif /* WOLFSSL_CERT_GEN */ + +#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) +int wolfCLU_MakeAndSignCertDer(WOLFSSL_X509* x509, int isCSR, int sigType, + int bufSz, void* subjKey, int subjKeyType, void* caKey, int caKeyType, + WOLFSSL_X509* caCert, int policySanitized, int days, + byte** outDer, int* outDerSz) +{ + int ret = WOLFCLU_SUCCESS; + int rngInit = 0; + int certSz = 0; + + WC_RNG rng; + Cert* cert = NULL; + byte* certBuf = NULL; + + if (x509 == NULL || subjKey == NULL || caKey == NULL || + outDer == NULL || outDerSz == NULL || bufSz <= 0) { + return BAD_FUNC_ARG; + } + if (isCSR) { +#ifndef WOLFSSL_CERT_REQ + wolfCLU_LogError("Certificate requests require wolfSSL built with " + "WOLFSSL_CERT_REQ"); + return WOLFCLU_FATAL_ERROR; +#else + /* A CSR carries a self-signature that proves possession of the + * subject key, so it cannot be signed with a separate CA key. */ + if (caKey != subjKey || caKeyType != subjKeyType) { + wolfCLU_LogError("A certificate request must be signed with the " + "subject's own key"); + return BAD_FUNC_ARG; + } +#endif + } + *outDer = NULL; + *outDerSz = 0; + + XMEMSET(&rng, 0, sizeof(rng)); + cert = (Cert*)XMALLOC(sizeof(Cert), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (cert == NULL) { + return MEMORY_E; + } + XMEMSET(cert, 0, sizeof(*cert)); + + ret = wc_InitRng(&rng); + if (ret != 0) { + wolfCLU_LogError("Failed to init RNG: %d", ret); + ret = WOLFCLU_FATAL_ERROR; + } + else { + rngInit = 1; + ret = WOLFCLU_SUCCESS; + } + + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_X509FillCert_ex(x509, cert, sigType, subjKey, + subjKeyType, caKey, caKeyType, caCert, policySanitized, isCSR, + NULL); + if (ret == WOLFCLU_SUCCESS && !isCSR && days >= 0 && + cert->beforeDateSz == 0 && cert->afterDateSz == 0) { + cert->daysValid = (days > 0) ? days : WOLFCLU_CERT_DAYS_DEFAULT; + } + } + + if (ret == WOLFCLU_SUCCESS) { + certBuf = (byte*)XMALLOC(bufSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (certBuf == NULL) { + ret = MEMORY_E; + } + else { + XMEMSET(certBuf, 0, bufSz); + } + } + + if (ret == WOLFCLU_SUCCESS) { + if (isCSR) { +#ifdef WOLFSSL_CERT_REQ + ret = wc_MakeCertReq_ex(cert, certBuf, (word32)bufSz, + subjKeyType, subjKey); +#else + ret = WOLFCLU_FATAL_ERROR; /* rejected in the argument check */ +#endif + } + else { + ret = wc_MakeCert_ex(cert, certBuf, (word32)bufSz, subjKeyType, + subjKey, &rng); + } + if (ret < 0) { + wolfCLU_LogError("%s failed: %d", + isCSR ? "wc_MakeCertReq_ex" : "wc_MakeCert_ex", ret); + ret = WOLFCLU_FATAL_ERROR; + } + else { + cert->bodySz = (word32)ret; + ret = WOLFCLU_SUCCESS; + } + } + + if (ret == WOLFCLU_SUCCESS) { + ret = wc_SignCert_ex(cert->bodySz, cert->sigType, certBuf, + (word32)bufSz, caKeyType, caKey, &rng); + if (ret < 0) { + wolfCLU_LogError("wc_SignCert_ex failed: %d", ret); + ret = WOLFCLU_FATAL_ERROR; + } + else { + certSz = ret; + if (certSz <= 0 || certSz > bufSz) { + wolfCLU_LogError("%s has invalid size", isCSR ? + "Certificate request" : "Signed certificate"); + ret = WOLFCLU_FATAL_ERROR; + } + else { + ret = WOLFCLU_SUCCESS; + } + } + } + + if (ret == WOLFCLU_SUCCESS) { + *outDer = certBuf; + *outDerSz = certSz; + certBuf = NULL; + } + + if (certBuf != NULL) { + wolfCLU_ForceZero(certBuf, bufSz); + XFREE(certBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + if (rngInit) { + wc_FreeRng(&rng); + } + if (cert != NULL) { + (void)wolfCLU_FreeCertCustomExts(cert); + XFREE(cert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + + return ret; +} + +int wolfCLU_BuildAndSignNative(void* key, int keyType, int sigType, int bufSz, + WOLFSSL_X509* x509, int days, int isCSR, int outForm, + WOLFSSL_BIO* bioOut, int noOut) +{ + int ret; + byte* certBuf = NULL; + int certSz = 0; + + if (key == NULL || x509 == NULL) { + return BAD_FUNC_ARG; + } + if ((!noOut) && (bioOut == NULL)) { + return BAD_FUNC_ARG; + } + + ret = wolfCLU_MakeAndSignCertDer(x509, isCSR, sigType, bufSz, key, + keyType, key, keyType, NULL, 1, days, &certBuf, &certSz); + + if (ret == WOLFCLU_SUCCESS && !noOut) { + ret = wolfCLU_WriteCertBio(bioOut, outForm, certBuf, certSz, + isCSR ? CERTREQ_TYPE : CERT_TYPE); + } + + if (certBuf != NULL) { + wolfCLU_ForceZero(certBuf, (unsigned int)bufSz); + XFREE(certBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + + return ret; +} +#endif /* WOLFSSL_CERT_GEN && WOLFSSL_CERT_EXT */ diff --git a/src/x509/clu_config.c b/src/x509/clu_config.c index 780e56db..1e8c50f3 100644 --- a/src/x509/clu_config.c +++ b/src/x509/clu_config.c @@ -124,7 +124,7 @@ static WOLFSSL_X509_EXTENSION* wolfCLU_parseBasicConstraint(char* in, int crit) wordSz = (int)XSTRLEN(word); for (z = 0; z < wordSz; z++) - word[z] = toupper(word[z]); + word[z] = (char)toupper((unsigned char)word[z]); if (XSTRCMP(word, "TRUE") == 0) { obj->ca = 1; } @@ -275,6 +275,13 @@ static WOLFSSL_X509_EXTENSION* wolfCLU_parseKeyUsage(char* str, int crit, } } + /* #5880: reject all-zero keyUseFlag (unrecognised / misspelled tokens) */ + if (keyUseFlag == 0) { + wolfCLU_LogError("keyUsage: no recognised usage bits; " + "check token spelling (e.g. 'keyCertSign')"); + return NULL; + } + data = wolfSSL_ASN1_STRING_new(); if (data != NULL) { if (wolfSSL_ASN1_STRING_set(data, (byte*)&keyUseFlag, sizeof(word16)) @@ -295,7 +302,12 @@ static int wolfCLU_parseExtension(WOLFSSL_X509* x509, char* str, int nid, int* idx) { WOLFSSL_X509_EXTENSION *ext = NULL; - int ret, crit = 0; + int ret = WOLFCLU_SUCCESS; + int crit = 0; + /* Set when nid names an extension we know how to build, so that a NULL + * ext back from the builder means "the configured value was rejected" + * rather than "there was nothing to add". */ + int handled = 1; if (XSTRSTR(str, "critical") != NULL) { crit = 1; @@ -309,6 +321,7 @@ static int wolfCLU_parseExtension(WOLFSSL_X509* x509, char* str, int nid, break; case NID_authority_key_identifier: /* @TODO */ + handled = 0; break; case NID_key_usage: ext = wolfCLU_parseKeyUsage(str, crit, x509); @@ -317,17 +330,25 @@ static int wolfCLU_parseExtension(WOLFSSL_X509* x509, char* str, int nid, default: WOLFCLU_LOG(WOLFCLU_L0, "unknown / supported nid %d value for extension", nid); + handled = 0; } if (ext != NULL) { - ret = wolfSSL_X509_add_ext(x509, ext, -1); - if (ret != WOLFSSL_SUCCESS) { - wolfCLU_LogError("error %d adding extension", ret); + if (wolfSSL_X509_add_ext(x509, ext, -1) != WOLFSSL_SUCCESS) { + wolfCLU_LogError("error adding extension for nid %d", nid); + ret = WOLFCLU_FATAL_ERROR; + } + else { + *idx = *idx + 1; } - *idx = *idx + 1; wolfSSL_X509_EXTENSION_free(ext); } - return WOLFCLU_SUCCESS; + else if (handled) { + /* Fail rather than issue a certificate that silently omits an + * extension the configuration asked for. */ + ret = WOLFCLU_FATAL_ERROR; + } + return ret; } @@ -545,10 +566,8 @@ static int wolfCLU_setAltNames(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, #ifdef WOLFSSL_ALT_NAMES /* Apply an inline subjectAltName list to x509, e.g. - * "DNS:example.com,IP:10.0.0.1". Leading whitespace per entry is skipped. - * Buffer is tokenized in place, so callers pass a writable string. Returns - * WOLFCLU_SUCCESS, or WOLFCLU_FATAL_ERROR on a malformed entry so a bad SAN is - * never silently ignored. */ + * "DNS:example.com,IP:10.0.0.1". Tokenizes val in place, so callers must + * pass a writable buffer. */ static int wolfCLU_setInlineAltNames(WOLFSSL_X509* x509, char* val) { int ret = WOLFCLU_SUCCESS; @@ -565,7 +584,6 @@ static int wolfCLU_setInlineAltNames(WOLFSSL_X509* x509, char* val) char* value; size_t len; - /* trim whitespace around entries and trailing whitespace */ while (*token == ' ' || *token == '\t' || *token == '\r' || *token == '\n') { token++; } @@ -582,15 +600,13 @@ static int wolfCLU_setInlineAltNames(WOLFSSL_X509* x509, char* val) break; } *colon = '\0'; - /* The trailing-whitespace trim above already NUL-terminated the token - * at the correct boundary, so value needs no second trailing trim. */ - /* drop whitespace between the colon and the value */ + /* Token already NUL-terminated by the trailing trim above; value + * needs no second trailing trim. */ value = colon + 1; while (*value == ' ' || *value == '\t' || *value == '\r' || *value == '\n') { value++; } - /* Check for empty type or value after trimming */ if (XSTRLEN(token) == 0) { wolfCLU_LogError("bad subjectAltName entry: empty type prefix"); ret = WOLFCLU_FATAL_ERROR; @@ -626,39 +642,38 @@ int wolfCLU_setExtensions(WOLFSSL_X509* x509, WOLFSSL_CONF* conf, char* sect) current = wolfSSL_NCONF_get_string(conf, sect, "basicConstraints"); if (current != NULL) { - wolfCLU_parseExtension(x509, current, NID_basic_constraints, &idx); + ret = wolfCLU_parseExtension(x509, current, NID_basic_constraints, + &idx); } current = wolfSSL_NCONF_get_string(conf, sect, "subjectKeyIdentifier"); - if (current != NULL) { - wolfCLU_parseExtension(x509, current, NID_subject_key_identifier, &idx); + if (ret == WOLFCLU_SUCCESS && current != NULL) { + ret = wolfCLU_parseExtension(x509, current, + NID_subject_key_identifier, &idx); } current = wolfSSL_NCONF_get_string(conf, sect, "authorityKeyIdentifier"); - if (current != NULL) { - wolfCLU_parseExtension(x509, current, NID_authority_key_identifier, - &idx); + if (ret == WOLFCLU_SUCCESS && current != NULL) { + ret = wolfCLU_parseExtension(x509, current, + NID_authority_key_identifier, &idx); } current = wolfSSL_NCONF_get_string(conf, sect, "keyUsage"); - if (current != NULL) { - wolfCLU_parseExtension(x509, current, NID_key_usage, &idx); + if (ret == WOLFCLU_SUCCESS && current != NULL) { + ret = wolfCLU_parseExtension(x509, current, NID_key_usage, &idx); } current = wolfSSL_NCONF_get_string(conf, sect, "subjectAltName"); - if (current != NULL) { + if (ret == WOLFCLU_SUCCESS && current != NULL) { if (current[0] == '@') { ret = wolfCLU_setAltNames(x509, conf, current + 1); } else { /* Accept inline form for config compatibility. */ #ifndef WOLFSSL_ALT_NAMES - /* Intentional: mirror the pre-existing silent-skip behaviour of - * the @section form (wolfCLU_setAltNames is also a no-op when - * WOLFSSL_ALT_NAMES is not defined). We log the skip but do NOT - * promote ret to WOLFCLU_FATAL_ERROR so that a config containing - * a subjectAltName line is still usable in builds where alt-name - * support was compiled out. */ + /* Mirrors the @section form's silent-skip: logs but doesn't + * fail, so a config with subjectAltName still works in builds + * without alt-name support. */ WOLFCLU_LOG(WOLFCLU_L0, "Skipping alt names, recompile wolfSSL " "with WOLFSSL_ALT_NAMES..."); #else @@ -996,6 +1011,10 @@ int wolfCLU_readConfig(WOLFSSL_X509* x509, char* config, char* sect, char* ext) char *curnt; conf = wolfSSL_NCONF_new(NULL); + if (conf == NULL) { + wolfCLU_LogError("Failed to allocate config context"); + return WOLFCLU_FATAL_ERROR; + } wolfSSL_NCONF_load(conf, config, &line); /* check if no prompting */ @@ -1010,10 +1029,9 @@ int wolfCLU_readConfig(WOLFSSL_X509* x509, char* config, char* sect, char* ext) wolfCLU_setAttributes(x509, conf, wolfSSL_NCONF_get_string(conf, sect, "attributes")); if (ext == NULL) { - /* Note: we capture this return code because the !WOLFSSL_CERT_EXT stub - * of wolfCLU_setExtensions gracefully returns SUCCESS when the string - * is NULL, but fails loudly if an extension section IS requested and - * WOLFSSL_CERT_EXT is disabled. These two behaviors are coupled. */ + /* Capture the return: the !WOLFSSL_CERT_EXT stub of + * wolfCLU_setExtensions succeeds when sect is NULL but fails loudly + * if a section was requested without WOLFSSL_CERT_EXT. */ ret = wolfCLU_setExtensions(x509, conf, wolfSSL_NCONF_get_string(conf, sect, "x509_extensions")); } diff --git a/src/x509/clu_parse.c b/src/x509/clu_parse.c index 0784f961..3065891c 100644 --- a/src/x509/clu_parse.c +++ b/src/x509/clu_parse.c @@ -50,22 +50,9 @@ int wolfCLU_printDer(WOLFSSL_BIO* bio, unsigned char* der, int derSz, ret = WOLFCLU_FATAL_ERROR; } - /* get pem size alloc buffer and convert to pem format */ if (ret == WOLFCLU_SUCCESS) { - pemSz = wc_DerToPemEx(der, derSz, NULL, 0, NULL, pemType); - if (pemSz > 0) { - pem = (unsigned char*)XMALLOC(pemSz, NULL, heapType); - if (pem == NULL) { - ret = WOLFCLU_FATAL_ERROR; - } - else { - if (wc_DerToPemEx(der, derSz, pem, pemSz, NULL, pemType) - <= 0) { - ret = WOLFCLU_FATAL_ERROR; - } - } - } - else { + pemSz = wolfCLU_KeyDerToPem(der, derSz, &pem, pemType, heapType); + if (pemSz <= 0) { ret = WOLFCLU_FATAL_ERROR; } } @@ -78,7 +65,7 @@ int wolfCLU_printDer(WOLFSSL_BIO* bio, unsigned char* der, int derSz, if (pem != NULL) { wolfCLU_ForceZero(pem, pemSz); - XFREE(pem, NULL, heapType); + XFREE(pem, HEAP_HINT, heapType); } return ret; diff --git a/src/x509/clu_request_setup.c b/src/x509/clu_request_setup.c index 47264800..357bbe2a 100644 --- a/src/x509/clu_request_setup.c +++ b/src/x509/clu_request_setup.c @@ -714,7 +714,8 @@ int wolfCLU_requestSetup(int argc, char** argv) case WOLFCLU_HELP: wolfCLU_certgenHelp(); - return WOLFCLU_SUCCESS; + ret = WOLFCLU_SUCCESS; + goto cleanup; case WOLFCLU_RSA: algCheck = 1; @@ -729,7 +730,12 @@ int wolfCLU_requestSetup(int argc, char** argv) break; case WOLFCLU_DAYS: - days = XATOI(optarg); + if (wolfCLU_ParseDaysArg(optarg, &days) != WOLFCLU_SUCCESS) { + wolfCLU_LogError("-days must be a positive integer " + "in [1, %d], got: %s", WOLFCLU_MAX_CERT_DAYS, + optarg); + ret = USER_INPUT_ERROR; + } break; case WOLFCLU_CERT_SHA: @@ -806,6 +812,18 @@ int wolfCLU_requestSetup(int argc, char** argv) } } + /* -out and -keyout opening the same path would silently discard + * whichever file was written first. wolfCLU_PathsRefEqual() also + * catches equivalent paths (symlinks, relative vs. absolute). + * Only applies when -keyout is actually written, i.e. -newkey was + * given (keyType/keyInfo set); otherwise -keyout is inert. */ + if (ret == WOLFCLU_SUCCESS && keyType != NULL && keyInfo != NULL && + out != NULL && keyOut != NULL && + wolfCLU_PathsRefEqual(out, keyOut)) { + wolfCLU_LogError("-out and -keyout must not be the same file"); + ret = USER_INPUT_ERROR; + } + /* default to sha256 if not set */ if (ret == WOLFCLU_SUCCESS && md == NULL) { md = wolfSSL_EVP_sha256(); @@ -1124,7 +1142,9 @@ int wolfCLU_requestSetup(int argc, char** argv) } if (keyOutBio == NULL) { - wolfCLU_LogError("Error opening keyout file %s", keyOut); + if (keyOut == NULL) { + wolfCLU_LogError("Error opening keyout file for stdout"); + } ret = WOLFCLU_FATAL_ERROR; } @@ -1155,6 +1175,7 @@ int wolfCLU_requestSetup(int argc, char** argv) wolfSSL_BIO_free(keyOutBio); } +cleanup: (void)algCheck; (void)in; (void)oid; diff --git a/src/x509/clu_x509_sign.c b/src/x509/clu_x509_sign.c index 75d5b382..53b077a6 100644 --- a/src/x509/clu_x509_sign.c +++ b/src/x509/clu_x509_sign.c @@ -206,14 +206,51 @@ void wolfCLU_CertSignSetCA(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* ca, break; default: + /* ownership of 'key' is NOT taken here, unlike the + * RSA/ECDSA cases above -- caller must free it. */ WOLFCLU_LOG(WOLFCLU_E0, "keytype needs added to wolfCLU_CertSignSetCA"); + break; } csign->keyType = keyType; } } } +#if defined(WOLFSSL_DUAL_ALG_CERTS) && defined(HAVE_DILITHIUM) +/* Read BIO's underlying file into buf. Returns WOLFCLU_SUCCESS on success + * (sets *outSz), BAD_FUNC_ARG on bad args, or WOLFCLU_FATAL_ERROR. */ +static int _ReadBioToBuf(WOLFSSL_BIO *bio, byte *buf, int bufSz, int *outSz) +{ + int ret; + XFILE fp = NULL; + + if (bio == NULL || buf == NULL || outSz == NULL || bufSz <= 0) { + return BAD_FUNC_ARG; + } + + ret = (int)wolfSSL_BIO_get_fp(bio, &fp); + if (ret != WOLFSSL_SUCCESS) { + wolfCLU_LogError("Error getting fp from BIO"); + return WOLFCLU_FATAL_ERROR; + } + + if (XFSEEK(fp, 0, XSEEK_SET) != 0) { + wolfCLU_LogError("Error seeking to start of file"); + return WOLFCLU_FATAL_ERROR; + } + + ret = (int)XFREAD(buf, 1, bufSz, fp); + if (ret <= 0) { + wolfCLU_LogError("Error reading from file"); + return WOLFCLU_FATAL_ERROR; + } + + *outSz = ret; + return WOLFCLU_SUCCESS; +} +#endif + /* ref: https://github.com/wolfssl/wolfssl-examples/X9.146/gen_ecdsa_mldsa_dual_keysig_cert.c */ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, WOLFSSL_BIO *bioAltSubjPubKey, WOLFSSL_BIO *bioSubjKey, @@ -240,10 +277,6 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, int initAltCaKey = 0; int initPreTBS = 0; - XFILE caKeyFp = NULL; - XFILE altCaKeyFp = NULL; - XFILE altCaPubKeyFp = NULL; - XFILE serverKeyFp = NULL; WOLFSSL_BIO *out = NULL; char *token = NULL; @@ -317,13 +350,10 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, altSigValBuf = (byte*)XMALLOC(LARGE_TEMP_SZ, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); derBuf = (byte*)XMALLOC(LARGE_TEMP_SZ, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); outBuf = (byte*)XMALLOC(LARGE_TEMP_SZ, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - caCertBuf = (byte*)XMALLOC(LARGE_TEMP_SZ, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - serverKeyBuf = (byte*)XMALLOC(LARGE_TEMP_SZ, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); if (caKeyBuf == NULL || altCaKeyBuf == NULL || sapkiBuf == NULL || altSigAlgBuf == NULL || scratchBuf == NULL || preTbsBuf == NULL || - altSigValBuf == NULL || derBuf == NULL || outBuf == NULL || - caCertBuf == NULL || serverKeyBuf == NULL) { + altSigValBuf == NULL || derBuf == NULL || outBuf == NULL) { ret = MEMORY_E; } else { @@ -336,6 +366,20 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, XMEMSET(altSigValBuf, 0, LARGE_TEMP_SZ); XMEMSET(derBuf, 0, LARGE_TEMP_SZ); XMEMSET(outBuf, 0, LARGE_TEMP_SZ); + } + } + + /* caCertBuf/serverKeyBuf are only needed on the server-cert path */ + if (ret == WOLFCLU_SUCCESS && !isCA) { + caCertBuf = (byte*)XMALLOC(LARGE_TEMP_SZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + serverKeyBuf = (byte*)XMALLOC(LARGE_TEMP_SZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + + if (caCertBuf == NULL || serverKeyBuf == NULL) { + ret = MEMORY_E; + } + else { XMEMSET(caCertBuf, 0, LARGE_TEMP_SZ); XMEMSET(serverKeyBuf, 0, LARGE_TEMP_SZ); } @@ -360,6 +404,10 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, wolfCLU_LogError("Error getting DER from CA cert"); ret = WOLFCLU_FATAL_ERROR; } + else if (caCertSz > LARGE_TEMP_SZ) { + wolfCLU_LogError("CA cert DER too large for temp buffer"); + ret = WOLFCLU_FATAL_ERROR; + } else { XMEMCPY(caCertBuf, tmpBuf, caCertSz); ret = WOLFCLU_SUCCESS; @@ -368,44 +416,13 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, /* open CA ecc private key */ if (ret == WOLFCLU_SUCCESS) { - ret = (int)wolfSSL_BIO_get_fp(bioCaKey, &caKeyFp); - if (ret != WOLFCLU_SUCCESS) { - wolfCLU_LogError("Error cannot get CA key fd"); - ret = WOLFCLU_FATAL_ERROR; - } - else { - XFSEEK(caKeyFp, 0, SEEK_SET); - ret = (int)XFREAD(caKeyBuf, 1, caKeySz, caKeyFp); - if (ret <= 0) { - wolfCLU_LogError("Error reading CA key"); - ret = WOLFCLU_FATAL_ERROR; - } - else { - caKeySz = ret; - ret = WOLFCLU_SUCCESS; - } - } + ret = _ReadBioToBuf(bioCaKey, caKeyBuf, caKeySz, &caKeySz); } /* open server ecc private key */ if (ret == WOLFCLU_SUCCESS && !isCA) { - ret = (int)wolfSSL_BIO_get_fp(bioSubjKey, &serverKeyFp); - if (ret != WOLFCLU_SUCCESS) { - wolfCLU_LogError("Error cannot get server key fd"); - ret = WOLFCLU_FATAL_ERROR; - } - else { - XFSEEK(serverKeyFp, 0, SEEK_SET); - ret = (int)XFREAD(serverKeyBuf, 1, serverKeySz, serverKeyFp); - if (ret <= 0) { - wolfCLU_LogError("Error reading server key"); - ret = WOLFCLU_FATAL_ERROR; - } - else { - serverKeySz = ret; - ret = WOLFCLU_SUCCESS; - } - } + ret = _ReadBioToBuf(bioSubjKey, serverKeyBuf, serverKeySz, + &serverKeySz); } /* open CA ecc private key */ @@ -424,11 +441,18 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, } if (ret == 0) { - XMEMSET(caKeyBuf, 0, caKeySz); /* clear original buffer */ - caKeySz = derObj->length; - XMEMCPY(caKeyBuf, derObj->buffer, caKeySz); - wc_FreeDer(&derObj); - ret = WOLFCLU_SUCCESS; + if (derObj->length > LARGE_TEMP_SZ) { + wolfCLU_LogError("CA key DER too large for temp buffer"); + wc_FreeDer(&derObj); + ret = WOLFCLU_FATAL_ERROR; + } + else { + XMEMSET(caKeyBuf, 0, caKeySz); /* clear original buffer */ + caKeySz = derObj->length; + XMEMCPY(caKeyBuf, derObj->buffer, caKeySz); + wc_FreeDer(&derObj); + ret = WOLFCLU_SUCCESS; + } } } @@ -471,11 +495,19 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, } if (ret == 0) { - XMEMSET(serverKeyBuf, 0, serverKeySz); /* clear original buffer */ - serverKeySz = derObj->length; - XMEMCPY(serverKeyBuf, derObj->buffer, serverKeySz); - wc_FreeDer(&derObj); - ret = WOLFCLU_SUCCESS; + if (derObj->length > LARGE_TEMP_SZ) { + wolfCLU_LogError("Server key DER too large for temp buffer"); + wc_FreeDer(&derObj); + ret = WOLFCLU_FATAL_ERROR; + } + else { + /* clear original buffer */ + XMEMSET(serverKeyBuf, 0, serverKeySz); + serverKeySz = derObj->length; + XMEMCPY(serverKeyBuf, derObj->buffer, serverKeySz); + wc_FreeDer(&derObj); + ret = WOLFCLU_SUCCESS; + } } } @@ -506,23 +538,7 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, /* load alternative CA public key */ if (ret == WOLFCLU_SUCCESS) { - ret = (int)wolfSSL_BIO_get_fp(bioAltSubjPubKey, &altCaPubKeyFp); - if (ret != WOLFCLU_SUCCESS) { - wolfCLU_LogError("Error get AltCAkey fd"); - ret = WOLFCLU_FATAL_ERROR; - } - else { - XFSEEK(altCaPubKeyFp, 0, SEEK_SET); - ret = (int)XFREAD(sapkiBuf, 1, sapkiSz, altCaPubKeyFp); - if (ret <= 0) { - wolfCLU_LogError("Error cannot read ML-DSA key"); - ret = WOLFCLU_FATAL_ERROR; - } - else { - sapkiSz = ret; - ret = WOLFCLU_SUCCESS; - } - } + ret = _ReadBioToBuf(bioAltSubjPubKey, sapkiBuf, sapkiSz, &sapkiSz); } if (ret == WOLFCLU_SUCCESS) { @@ -532,6 +548,11 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, wolfCLU_LogError("Error convert file pem to der"); ret = WOLFCLU_FATAL_ERROR; } + else if (derObj->length > LARGE_TEMP_SZ) { + wolfCLU_LogError("Alt public key DER too large for temp buffer"); + wc_FreeDer(&derObj); + ret = WOLFCLU_FATAL_ERROR; + } else { XMEMSET(sapkiBuf, 0, sapkiSz); /* clear original buffer */ sapkiSz = derObj->length; @@ -555,23 +576,8 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, } if (ret == WOLFCLU_SUCCESS) { - ret = (int)wolfSSL_BIO_get_fp(bioAltCaKey, &altCaKeyFp); - if (ret != WOLFCLU_SUCCESS) { - wolfCLU_LogError("Error cannot get AltCA key fd"); - ret = WOLFCLU_FATAL_ERROR; - } - else { - XFSEEK(altCaKeyFp, 0, SEEK_SET); - ret = (int)XFREAD(altCaKeyBuf, 1, altCaKeySz, altCaKeyFp); - if (ret <= 0) { - wolfCLU_LogError("Error reading alternative CA key"); - ret = WOLFCLU_FATAL_ERROR; - } - else { - altCaKeySz = ret; - ret = WOLFCLU_SUCCESS; - } - } + ret = _ReadBioToBuf(bioAltCaKey, altCaKeyBuf, altCaKeySz, + &altCaKeySz); } if (ret == WOLFCLU_SUCCESS) { @@ -581,6 +587,11 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, wolfCLU_LogError("Error convert pem to der"); ret = WOLFCLU_FATAL_ERROR; } + else if (derObj->length > LARGE_TEMP_SZ) { + wolfCLU_LogError("Alt CA key DER too large for temp buffer"); + wc_FreeDer(&derObj); + ret = WOLFCLU_FATAL_ERROR; + } else { XMEMSET(altCaKeyBuf, 0, altCaKeySz); /* clear original buffer */ altCaKeySz = derObj->length; @@ -664,26 +675,23 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, break; } - if (XSTRCMP(key, "C") == 0) { - XSTRLCPY(newCert.subject.country, value, CTC_NAME_SIZE); - } - else if (XSTRCMP(key, "ST") == 0) { - XSTRLCPY(newCert.subject.state, value, CTC_NAME_SIZE); - } - else if (XSTRCMP(key, "L") == 0) { - XSTRLCPY(newCert.subject.locality, value, CTC_NAME_SIZE); - } - else if (XSTRCMP(key, "O") == 0) { - XSTRLCPY(newCert.subject.org, value, CTC_NAME_SIZE); - } - else if (XSTRCMP(key, "OU") == 0) { - XSTRLCPY(newCert.subject.unit, value, CTC_NAME_SIZE); - } - else if (XSTRCMP(key, "CN") == 0) { - XSTRLCPY(newCert.subject.commonName, value, CTC_NAME_SIZE); - } - else if (XSTRCMP(key, "emailAddress") == 0) { - XSTRLCPY(newCert.subject.email, value, CTC_NAME_SIZE); + { + int subjNid = wolfSSL_OBJ_sn2nid(key); + + if (subjNid == 0) { + /* Reject rather than skip: silently ignoring a + * component would sign a subject the caller did not + * ask for. */ + wolfCLU_LogError("Unknown subject component \"%s\" " + "in -subj", key); + ret = WOLFCLU_FATAL_ERROR; + break; + } + ret = wolfCLU_SetCertNameFieldByNid(&newCert.subject, + subjNid, value, (int)XSTRLEN(value)); + if (ret != WOLFCLU_SUCCESS) { + break; + } } token = XSTRTOK(NULL, "/", &slash); @@ -903,9 +911,8 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, } if (ret == WOLFCLU_SUCCESS) { - out = wolfSSL_BIO_new_file(outFileName, "wb"); + out = wolfCLU_OpenOutFileBio(outFileName); if (out == NULL) { - wolfCLU_LogError("Unable to open out file %s", outFileName); ret = WOLFCLU_FATAL_ERROR; } else { @@ -1148,6 +1155,120 @@ int wolfCLU_CertSignAppendOut(WOLFCLU_CERT_SIGN* csign, char* out) } +/* Amount the record buffer grows by, and so the most one wolfSSL_BIO_gets() + * can return, per read. */ +#ifndef WOLFCLU_DB_READ_CHUNK_SZ +#define WOLFCLU_DB_READ_CHUNK_SZ 256 +#endif + +/* Upper bound on one certificate database record, so a corrupt or hostile + * database cannot drive an unbounded allocation. */ +#ifndef WOLFCLU_MAX_DB_RECORD_SZ +#define WOLFCLU_MAX_DB_RECORD_SZ 16384 +#endif + +/* Read one newline-terminated record from the certificate database. A DN is + * only bounded by the number of RDNs it carries, so a record can be longer + * than any single read; accumulate until the newline rather than truncating, + * which would leave the continuation looking like a fresh (tab-less) record. + * Reads land directly in the growing record so no bounce buffer sits on the + * stack. On success *out holds the record and must be freed by the caller, + * or is NULL at end of input. + * return WOLFCLU_SUCCESS on success */ +static int wolfCLU_ReadDbRecord(WOLFSSL_BIO* bio, char** out) +{ + char* record = NULL; + int recordSz = 0; + int ret = WOLFCLU_SUCCESS; + + if (bio == NULL || out == NULL) { + return BAD_FUNC_ARG; + } + *out = NULL; + + for (;;) { + char* bigger; + int readSz; + + if (recordSz > WOLFCLU_MAX_DB_RECORD_SZ - WOLFCLU_DB_READ_CHUNK_SZ) { + wolfCLU_LogError("Certificate database record exceeds %d bytes", + WOLFCLU_MAX_DB_RECORD_SZ); + ret = WOLFCLU_FATAL_ERROR; + break; + } + bigger = (char*)XREALLOC(record, + (size_t)recordSz + WOLFCLU_DB_READ_CHUNK_SZ, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (bigger == NULL) { + ret = MEMORY_E; + break; + } + record = bigger; + + if (wolfSSL_BIO_gets(bio, record + recordSz, + WOLFCLU_DB_READ_CHUNK_SZ) <= 0) { + break; + } + readSz = (int)XSTRLEN(record + recordSz); + if (readSz == 0) { + break; + } + recordSz += readSz; + + if (record[recordSz - 1] == '\n') { + break; + } + } + + if (ret != WOLFCLU_SUCCESS || recordSz == 0) { + /* *out stays NULL: either an error, or the end of the database. */ + XFREE(record, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return ret; + } + + *out = record; + return WOLFCLU_SUCCESS; +} + + +/* #5749/#5751: a DN attribute holding a TAB, CR or LF splits the flat-file + * database record wolfCLU_CertSignLog() writes, and the split record no + * longer round-trips back to an equal WOLFSSL_X509_NAME. Rewriting those + * characters would only change the stored copy, leaving the unique_subject + * comparison -- which runs against the live name -- unable to ever match, so + * refuse the DN instead and keep both sides in the same form. + * return WOLFCLU_SUCCESS when the subject is safe to record */ +static int wolfCLU_CheckSubjectRecordable(WOLFSSL_X509_NAME* name) +{ + char* oneline; + int i; + int ret = WOLFCLU_SUCCESS; + + if (name == NULL) { + wolfCLU_LogError("Unable to get subject name"); + return WOLFCLU_FATAL_ERROR; + } + + oneline = wolfSSL_X509_NAME_oneline(name, NULL, 0); + if (oneline == NULL) { + wolfCLU_LogError("Unable to get subject name"); + return WOLFCLU_FATAL_ERROR; + } + + for (i = 0; oneline[i] != '\0'; i++) { + if (oneline[i] == '\t' || oneline[i] == '\r' || oneline[i] == '\n') { + wolfCLU_LogError("Subject name contains a control character and " + "cannot be recorded in the certificate database"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + } + + XFREE(oneline, NULL, DYNAMIC_TYPE_OPENSSL); + return ret; +} + + static int wolfCLU_CertSignLog(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* x509) { int ret = WOLFCLU_SUCCESS; @@ -1177,6 +1298,10 @@ static int wolfCLU_CertSignLog(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* x509) ret = WOLFCLU_FATAL_ERROR; } + /* #5749/#5751: the DN is known to be free of the TAB/CR/LF field + * separators here -- wolfCLU_CheckSubjectRecordable() rejected the + * certificate before this point if it was not. */ + if (ret == WOLFCLU_SUCCESS && wolfSSL_BIO_write(csign->dataBase, subject, (int)XSTRLEN(subject)) <= 0) { @@ -1221,26 +1346,27 @@ static int _checkPolicy(WOLFSSL_X509_NAME* issuer, WOLFSSL_X509_NAME* subject, return WOLFCLU_FAILURE; } - current = (char*)XMALLOC(currentSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); - expected = (char*)XMALLOC(expectedSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + current = (char*)XMALLOC(currentSz + 1, NULL, DYNAMIC_TYPE_TMP_BUFFER); + expected = (char*)XMALLOC(expectedSz + 1, NULL, + DYNAMIC_TYPE_TMP_BUFFER); if (current == NULL || expected == NULL) { ret = WOLFCLU_FAILURE; } if (ret == WOLFCLU_SUCCESS && wolfSSL_X509_NAME_get_text_by_NID(subject, nid, current, - currentSz) <= 0) { + currentSz + 1) <= 0) { ret = WOLFCLU_FAILURE; } if (ret == WOLFCLU_SUCCESS && wolfSSL_X509_NAME_get_text_by_NID(issuer, nid, expected, - expectedSz) <= 0) { + expectedSz + 1) <= 0) { ret = WOLFCLU_FAILURE; } if (ret == WOLFCLU_SUCCESS && - XSTRNCMP(expected, current, currentSz) != 0) { + XSTRNCMP(expected, current, currentSz + 1) != 0) { WOLFSSL_MSG("Policy mismatch with subject and issuer"); ret = WOLFCLU_FAILURE; } @@ -1275,6 +1401,15 @@ int wolfCLU_CertSign(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* x509) ret = WOLFCLU_FATAL_ERROR; } + /* Validate the subject before anything is written to the database or + * compared against it, so the recorded form and the live name are always + * the same form. */ + if (ret == WOLFCLU_SUCCESS && + (csign->dataBase != NULL || csign->unique == 1)) { + ret = wolfCLU_CheckSubjectRecordable( + wolfSSL_X509_get_subject_name(x509)); + } + /* set cert date */ if (ret == WOLFCLU_SUCCESS) { ret = _wolfCLU_CertSetDate(x509, csign->days); @@ -1375,45 +1510,87 @@ int wolfCLU_CertSign(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* x509) wolfSSL_ASN1_INTEGER_free(s); } + /* A CSR's basicConstraints/CA:TRUE claim is untrusted content: issuance + * policy is the signer's call, not the requester's. Force CA:FALSE + * before extensions are applied, so the operator's own -extensions + * config can still assert CA:TRUE afterwards and win. -selfsign + * (csign->ca == x509) defaults to CA:TRUE instead, matching req -x509: + * there the operator supplies both the request and the signing key. + * + * Neutralizing rather than refusing keeps `ca` usable for batch signing + * and matches OpenSSL, which ignores CSR extensions by default + * (copy_extensions = none) rather than failing on them. */ +#if defined(WOLFSSL_CERT_EXT) + if (ret == WOLFCLU_SUCCESS) { + if (csign->ca != x509 && wolfSSL_X509_get_isCA(x509)) { + WOLFCLU_LOG(WOLFCLU_L0, "CSR requested basicConstraints CA:TRUE; " + "issuing as CA:FALSE (use -extensions to override)"); + } + ret = wolfCLU_SetBasicConstraintsCA(x509, csign->ca == x509); + } +#else + /* No WOLFSSL_CERT_EXT API to neutralize a CSR's basicConstraints, so a + * CA:TRUE claim can only be refused here. */ + if (ret == WOLFCLU_SUCCESS && csign->ca != x509 && + wolfSSL_X509_get_isCA(x509)) { + wolfCLU_LogError("wolfSSL built without WOLFSSL_CERT_EXT cannot " + "neutralize a CSR's basicConstraints; refusing to sign a " + "CSR that asserts CA:TRUE"); + ret = WOLFCLU_FATAL_ERROR; + } +#endif /* WOLFSSL_CERT_EXT */ + /* set extensions */ if (ret == WOLFCLU_SUCCESS && csign->ext != NULL) { ret = wolfCLU_setExtensions(x509, csign->config, csign->ext); } + /* sign the certificate */ - if (ret == WOLFCLU_SUCCESS && - (csign->keyType == RSAk || csign->keyType == ECDSAk)) { - if (wolfSSL_X509_check_private_key(csign->ca, csign->caKey.pkey) != - WOLFSSL_SUCCESS) { - wolfCLU_LogError("Private key does not match with CA"); - ret = WOLFCLU_FATAL_ERROR; - } + if (ret == WOLFCLU_SUCCESS) { + if (csign->keyType == RSAk || csign->keyType == ECDSAk) { + if (wolfSSL_X509_check_private_key(csign->ca, csign->caKey.pkey) != + WOLFSSL_SUCCESS) { + wolfCLU_LogError("Private key does not match with CA"); + ret = WOLFCLU_FATAL_ERROR; + } - if (ret == WOLFCLU_SUCCESS && - wolfSSL_X509_sign(x509, csign->caKey.pkey, md) <= 0) { - wolfCLU_LogError("Error signing certificate"); + if (ret == WOLFCLU_SUCCESS && + wolfSSL_X509_sign(x509, csign->caKey.pkey, md) <= 0) { + wolfCLU_LogError("Error signing certificate"); + ret = WOLFCLU_FATAL_ERROR; + } + } + else { + /* ML-DSA/PQC pure-cert signing isn't implemented yet; fail + * instead of writing out an unsigned certificate. */ + wolfCLU_LogError("Unsupported key type for CA signing"); ret = WOLFCLU_FATAL_ERROR; } - } /* @TODO else case here could get the tbs buffer or just the der of the - * x509 struct and use a different method for signing and creating the - * certificate */ + } /* check if unique subject name is required */ if (ret == WOLFCLU_SUCCESS && csign->unique == 1) { - char line[MAX_TERM_WIDTH]; WOLFSSL_X509_NAME* subject; + char* record = NULL; + subject = wolfSSL_X509_get_subject_name(x509); /* for now using a dumb brute force approach */ wolfSSL_BIO_reset(csign->dataBase); - while (wolfSSL_BIO_gets(csign->dataBase, line, MAX_TERM_WIDTH) > 0) { + for (;;) { int i = 0; char* word, *end; char* deli = (char*)"\t"; char* subj = NULL; WOLFSSL_X509_NAME* current = NULL; - for (word = strtok_r(line, deli, &end); word != NULL; + ret = wolfCLU_ReadDbRecord(csign->dataBase, &record); + if (ret != WOLFCLU_SUCCESS || record == NULL) { + break; /* error, or end of database */ + } + + for (word = strtok_r(record, deli, &end); word != NULL; word = strtok_r(NULL, deli, &end)) { if (i == 1) { subj = word; @@ -1441,7 +1618,11 @@ int wolfCLU_CertSign(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* x509) break; } wolfSSL_X509_NAME_free(current); + + XFREE(record, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + record = NULL; } + XFREE(record, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); } /* check policy constraints */ @@ -1517,10 +1698,8 @@ int wolfCLU_CertSign(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* x509) /* create WOLFSSL_BIO for output */ if (ret == WOLFCLU_SUCCESS) { - out = wolfSSL_BIO_new_file(csign->outDir, "wb"); + out = wolfCLU_OpenOutFileBio(csign->outDir); if (out == NULL) { - wolfCLU_LogError("Could not open output file %s", - csign->outDir); ret = WOLFCLU_FATAL_ERROR; } } @@ -1756,7 +1935,17 @@ WOLFCLU_CERT_SIGN* wolfCLU_readSignConfig(char* config, char* sect) if (wolfSSL_NCONF_get_number(conf, CAsection, "default_days", &defaultDays) == WOLFSSL_SUCCESS) { - wolfCLU_CertSignSetDate(ret, (int)defaultDays); + /* #5685: guard against long->int narrowing UB and RFC 5280 + * year-overflow for absurdly large values. */ + if (defaultDays > 0 && + defaultDays <= (long)WOLFCLU_MAX_CERT_DAYS) { + wolfCLU_CertSignSetDate(ret, (int)defaultDays); + } + else { + wolfCLU_LogError("default_days value %ld is out of valid range " + "[1, %d]; using built-in default", defaultDays, + WOLFCLU_MAX_CERT_DAYS); + } } defaultMD = wolfSSL_NCONF_get_string(conf, CAsection, "default_md"); @@ -1813,15 +2002,88 @@ WOLFCLU_CERT_SIGN* wolfCLU_readSignConfig(char* config, char* sect) keyType = wolfCLU_GetTypeFromPKEY(caKey); } + /* Ownership after SetCA: + * - ca: taken when ret != NULL (stored in csign->ca) + * - caKey: taken only when ret != NULL and keyType is RSAk/ECDSAk + * - on ret == NULL, SetCA is a no-op; free both locals below + * - on unsupported keyType with ret != NULL, free caKey (ca stays) */ wolfCLU_CertSignSetCA(ret, ca, caKey, keyType); + if (ret == NULL || (keyType != RSAk && keyType != ECDSAk)) { + wolfSSL_EVP_PKEY_free(caKey); + } + /* in fail case free up memory */ if (ret == NULL) { wolfSSL_NCONF_free(conf); wolfSSL_X509_free(ca); - wolfSSL_EVP_PKEY_free(caKey); } return ret; } #endif /* WOLFCLU_NO_FILESYSTEM */ + +#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) +int wolfCLU_CertSignNative(WOLFSSL_X509* x509, void* caKey, int caKeyType, + int sigType, int bufSz, WOLFSSL_X509* caCert, int outForm, + byte** outData, int* outDataSz, int policySanitized, + void* subjKey, int subjKeyType) +{ + int ret; + byte* certBuf = NULL; + byte* outBuf = NULL; + int certSz = 0; + int outBufSz = 0; + + if (outData != NULL) { + *outData = NULL; + } + if (outDataSz != NULL) { + *outDataSz = 0; + } + if (x509 == NULL || caKey == NULL || outData == NULL || outDataSz == NULL || + subjKey == NULL) { + return BAD_FUNC_ARG; + } + + ret = wolfCLU_MakeAndSignCertDer(x509, 0, sigType, bufSz, subjKey, + subjKeyType, caKey, caKeyType, caCert, policySanitized, -1, + &certBuf, &certSz); + + if (ret == WOLFCLU_SUCCESS && outForm == PEM_FORM) { + ret = wolfCLU_DerToPemBuf(certBuf, certSz, CERT_TYPE, &outBuf, + &outBufSz); + } + if (ret == WOLFCLU_SUCCESS) { + if (outForm == PEM_FORM) { + *outData = outBuf; + *outDataSz = outBufSz; + outBuf = NULL; + } + else if (outForm == DER_FORM) { + if (bufSz > certSz) { + wolfCLU_ForceZero(certBuf + certSz, + (unsigned int)(bufSz - certSz)); + } + *outData = certBuf; + *outDataSz = certSz; + certBuf = NULL; + } + else { + wolfCLU_LogError("Invalid outForm specified"); + ret = WOLFCLU_FATAL_ERROR; + } + } + + if (certBuf != NULL) { + wolfCLU_ForceZero(certBuf, (unsigned int)bufSz); + XFREE(certBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + if (outBuf != NULL) { + wolfCLU_ForceZero(outBuf, (unsigned int)outBufSz); + XFREE(outBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + + return ret; +} +#endif /* WOLFSSL_CERT_GEN && WOLFSSL_CERT_EXT */ diff --git a/tests/dgst/dgst-test.py b/tests/dgst/dgst-test.py index a9a1953d..b68ccf6f 100644 --- a/tests/dgst/dgst-test.py +++ b/tests/dgst/dgst-test.py @@ -109,6 +109,56 @@ def test_sign_verify_all_hash_algs(self): "-signature", sig_file, input_file) self.assertEqual(r.returncode, 0, r.stderr) + def test_verify_stdin_data_with_existing_signature_file(self): + """A trailing -signature file must not be misread as the positional + data file just because it exists on disk; with none given, data + must still come from stdin.""" + sig_file = "dgst-stdin-verify-test.sig" + self.addCleanup(lambda: os.remove(sig_file) + if os.path.exists(sig_file) else None) + data = "stdin verify regression test data" + + r = run_wolfssl("dgst", "-sha256", "-sign", + os.path.join(CERTS_DIR, "server-key.pem"), + "-out", sig_file, stdin_data=data) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dgst", "-sha256", "-verify", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-signature", sig_file, stdin_data=data) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_trailing_token_matching_option_name_is_treated_as_flag(self): + """A trailing token that names a known option (e.g. a file called + "-sha256") is always parsed as that option, never as positional + data, even if a same-named file exists on disk; data must fall + back to stdin instead.""" + real_content = "ground truth data for the -sha256 filename test" + decoy_file_content = "decoy on-disk data that must NOT be hashed" + + normal_named_file = "dgst-option-collision-src.txt" + collision_named_file = "-sha256" + sig_file = "dgst-option-collision-test.sig" + for f in (normal_named_file, collision_named_file, sig_file): + self.addCleanup(lambda p=f: os.remove(p) + if os.path.exists(p) else None) + + with open(normal_named_file, "w", encoding="utf-8") as f: + f.write(real_content) + with open(collision_named_file, "w", encoding="utf-8") as f: + f.write(decoy_file_content) + + r = run_wolfssl("dgst", "-sha256", "-sign", + os.path.join(CERTS_DIR, "server-key.pem"), + "-out", sig_file, normal_named_file) + self.assertEqual(r.returncode, 0, r.stderr) + + r = run_wolfssl("dgst", "-verify", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-signature", sig_file, collision_named_file, + stdin_data=real_content) + self.assertEqual(r.returncode, 0, r.stderr) + def test_dgst_out_roundtrip(self): """dgst -out creates the signature file; -signature round-trips.""" sig_file = "dgst-out-test.sig" @@ -129,14 +179,8 @@ def test_dgst_out_roundtrip(self): self.assertEqual(r.returncode, 0, r.stderr) def test_missing_data_file_detected(self): - """Omitting the trailing data file must be detected, not misread. - - clu_dgst_setup.c passes argc-1 to wolfCLU_GetOpt so the trailing - data file is excluded from option scanning, then checks whether the - last option consumed it as a value. With the data file absent here, - the .sig path is the trailing argument and the malformed-argument - check must reject the invocation rather than hashing the .sig file. - """ + """Omitting the trailing data file must be rejected, not hash the + .sig path as if it were the data.""" r = run_wolfssl("dgst", "-sha256", "-verify", os.path.join(CERTS_DIR, "server-keyPub.pem"), "-signature", os.path.join(DGST_DIR, "sha256-rsa.sig")) @@ -174,8 +218,9 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - for f in [cls.LARGE_FILE, "large-test.txt.enc", "large-test.txt.dec", - "5000-server-key.sig"]: + # "5000-server-key.sig" excluded: it's a checked-in fixture for + # test_verify_large_file, not test output. + for f in [cls.LARGE_FILE, "large-test.txt.enc", "large-test.txt.dec"]: if os.path.exists(f): os.remove(f) @@ -188,7 +233,9 @@ def test_verify_large_file(self): self.assertEqual(r.returncode, 0, r.stderr) def test_sign_and_verify_large_file(self): - sig_file = "5000-server-key.sig" + # Must not collide with the checked-in "5000-server-key.sig" + # fixture that test_verify_large_file reads. + sig_file = "5000-server-key-roundtrip.sig" self.addCleanup(lambda: os.remove(sig_file) if os.path.exists(sig_file) else None) @@ -389,15 +436,9 @@ def test_ecc_sign_verify_roundtrip(self): @unittest.skipIf(no_filesystem(), "filesystem support disabled") class DgstHmacTest(unittest.TestCase): - """HMAC test vectors for `dgst -mac HMAC`. - - Mirrors the wolfCrypt hmac_*_test functions in - wolfssl/wolfcrypt/test/test.c. The hex-key vectors come from RFC 4231 - Test Case 1 (a 20-byte 0x0b key over "Hi There"). The plaintext-key - vectors use a 16-byte key over 50 bytes of 0xdd, generated with OpenSSL - and confirmed with wolfSSL. Both keys are at or above the FIPS minimum - HMAC key length (112 bits), so these run under FIPS too. - """ + """HMAC test vectors for `dgst -mac HMAC` (RFC 4231 Test Case 1 hex key; + OpenSSL-generated plaintext key). Both keys meet the FIPS HMAC minimum + (112 bits), so these run under FIPS too.""" HEXKEY = "hexkey:0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b" HEXKEY_DATA = "Hi There" @@ -416,8 +457,8 @@ class DgstHmacTest(unittest.TestCase): "be9d914eeb61f1702e696c203a126854") } - # Plaintext 16-byte key (>= FIPS minimum) over 50 bytes of 0xdd. The - # expected values were generated with OpenSSL and confirmed with wolfSSL. + # Plaintext key (>= FIPS minimum); vectors below were generated with + # OpenSSL and confirmed with wolfSSL. KEY = "key:thisisthelongkey" DATA = b"\xdd" * 50 @@ -470,8 +511,7 @@ def test_hmac_vectors(self): def test_hmac_wrong_key(self): """A different key must not produce the reference HMAC.""" - # Use a different 20-byte key (FIPS-valid length) than the reference - # plaintext key used to generate VECTORS. + # Different 20-byte (FIPS-valid) key than the one used for VECTORS. r = run_wolfssl("dgst", "-sha256", "-hmac", "-mackey", "hexkey:" + "bb" * 20, self.data_file) self.assertEqual(r.returncode, 0, r.stderr) @@ -492,9 +532,8 @@ def test_hmac_out_file(self): def test_hmac_plaintext_key_with_colon(self): """A plaintext key containing ':' is used verbatim, not truncated.""" - # Both keys are >= the FIPS minimum length so this runs under FIPS. - # "key:aaaaaaaaaaaaaa:bb" -> the HMAC key is "aaaaaaaaaaaaaa:bb", - # not just "aaaaaaaaaaaaaa". + # "key:aaaaaaaaaaaaaa:bb" -> key is "aaaaaaaaaaaaaa:bb", not + # truncated at the colon; both keys meet the FIPS HMAC minimum. r_full = run_wolfssl("dgst", "-sha256", "-hmac", "-mackey", "key:aaaaaaaaaaaaaa:bb", self.data_file) self.assertEqual(r_full.returncode, 0, r_full.stderr) @@ -533,6 +572,31 @@ def test_hmac_no_hash_algorithm(self): "-mackey", self.KEY, self.data_file) self.assertNotEqual(r.returncode, 0) + def test_hmac_with_verify_flag_rejected(self): + """-hmac combined with -verify must fail, not silently ignore -verify.""" + r = run_wolfssl("dgst", "-sha256", "-hmac", + "-mackey", self.KEY, "-verify", + os.path.join(CERTS_DIR, "server-keyPub.pem"), + "-signature", os.path.join(DGST_DIR, "sha256-rsa.sig"), + self.data_file) + self.assertNotEqual(r.returncode, 0) + + def test_hmac_with_sign_flag_rejected(self): + """-hmac combined with -sign must fail, not silently ignore -sign.""" + r = run_wolfssl("dgst", "-sha256", "-hmac", + "-mackey", self.KEY, "-sign", + os.path.join(CERTS_DIR, "server-key.pem"), + self.data_file) + self.assertNotEqual(r.returncode, 0) + + def test_hmac_with_bare_signature_flag_rejected(self): + """-hmac combined with bare -signature (no -verify) must fail.""" + r = run_wolfssl("dgst", "-sha256", "-hmac", + "-mackey", self.KEY, "-signature", + os.path.join(DGST_DIR, "sha256-rsa.sig"), + self.data_file) + self.assertNotEqual(r.returncode, 0) + if __name__ == "__main__": test_main() diff --git a/tests/encrypt/enc-test.py b/tests/encrypt/enc-test.py index c0f8d520..6b5c50c4 100644 --- a/tests/encrypt/enc-test.py +++ b/tests/encrypt/enc-test.py @@ -15,9 +15,8 @@ no_filesystem, CERTS_DIR, WOLFSSL_BIN, run_wolfssl, test_main ) -# The interactive password prompt only reads from stdin when stdin is a real -# terminal (wolfCLU_GetStdinPassword -> tcgetattr fails on a pipe), so driving -# it requires a pseudo-terminal. The pty module is POSIX-only. +# wolfCLU_GetStdinPassword only reads from a real terminal (tcgetattr +# fails on a pipe), so testing it needs a pty. POSIX-only. try: import pty as _pty HAVE_PTY = True @@ -202,7 +201,7 @@ def test_enc_to_stdout(self): self.assertGreater(len(r.stdout), 0) def test_explicit_hex_key_iv(self): - """Regression: explicit --key/--iv hex strings must be copied correctly.""" + """Regression: explicit -key/-iv hex strings must be copied correctly.""" src = "enc_hex_test.txt" enc = "enc_hex_test.enc" self._cleanup(src, enc) @@ -212,8 +211,8 @@ def test_explicit_hex_key_iv(self): r = run_wolfssl("enc", "-aes-128-cbc", "-nosalt", "-in", src, "-out", enc, - "--key", "00112233445566778899aabbccddeeff", - "--iv", "00112233445566778899aabb0011aab7") + "-key", "00112233445566778899aabbccddeeff", + "-iv", "00112233445566778899aabb0011aab7") self.assertEqual(r.returncode, 0, "encrypt with explicit hex key/iv failed: " "{}".format(r.stderr)) @@ -378,14 +377,9 @@ def test_pbkdf2_wolfssl_pass_flag(self): @unittest.skipIf(no_filesystem(), "filesystem support disabled") class EncPassSourceTest(unittest.TestCase): - """Regression tests for issue 6133. - - wolfCLU_GetPassword only supports the "stdin" and "pass:" password - sources. Any other source (env:, file:, fd:, ...) must make the tool - fail loudly. Previously -pass parsing errors were ignored and the file - was silently encrypted under an empty/zeroed password while the tool - reported success. - """ + """wolfCLU_GetPassword supports only the "stdin" and "pass:" sources; + any other -pass source (env:, file:, fd:, ...) must fail loudly, not + silently encrypt under an empty password.""" @classmethod def setUpClass(cls): @@ -419,13 +413,12 @@ def test_unsupported_pass_sources_fail(self): os.remove(enc) r = self._enc_with_pass(src, enc) - # The tool must report failure for an unsupported source. self.assertNotEqual(r.returncode, 0, "unsupported -pass source %r encrypted with returncode 0; " "output may be under an empty password" % src) - # Defence in depth: if an output file was produced anyway, it - # must not be the plaintext encrypted under an empty password. + # Defence in depth: any output produced must not decrypt under + # an empty password. if os.path.exists(enc): d = subprocess.run( [WOLFSSL_BIN, "enc", "-d", "-aes-256-cbc", @@ -948,23 +941,18 @@ def test_rand_hex_to_inkey_workflow(self): @unittest.skipUnless(HAVE_PTY, "pty not available (non-POSIX)") @unittest.skipIf(no_filesystem(), "filesystem support disabled") class EncStdinPasswordTest(unittest.TestCase): - """Interactive stdin-password path of `encrypt` (F-5970). - - Running `encrypt` without -pwd/-pass/-key prompts for a password on the - terminal via wolfCLU_GetStdinPassword, which writes the typed length back - through its size pointer. The caller passed &keySize (the algorithm key - size in bits), so keySize was overwritten by the password length. With - -pbkdf2 the EVP derivation length is (keySize+7)/8, collapsing the derived - key to a couple of bytes. These tests drive the prompt over a pty. - """ + """Interactive stdin-password path of `encrypt`. - # >= 14 chars to satisfy the FIPS HMAC minimum (HMAC_FIPS_MIN_KEY) for the - # PBKDF2 path, while still truncating the bit-size keySize far below a real - # cipher key, so the buggy derivation collapses to a few key bytes. + wolfCLU_GetStdinPassword writes the typed password length back through + its size pointer, which aliases the algorithm keySize (bits); this + corrupted the -pbkdf2 derived key length. Driven over a pty since the + prompt only reads from a real terminal.""" + + # >= 14 chars for the FIPS HMAC minimum on the PBKDF2 path, while still + # short enough that the aliased-keySize bug would collapse the key. PASSWORD = "correcthorsebatterystaple" PLAINTEXT = b"F-5970 interactive password regression payload\n" - # AES-256 key length in bytes. - FULL_KEY_BYTES = 32 + FULL_KEY_BYTES = 32 # AES-256 key length in bytes. @classmethod def setUpClass(cls): @@ -989,13 +977,11 @@ def _encrypt_with_pty_password(self, password, *args, timeout=30): import select import signal - # fgets() reads a line, so the password must be newline-terminated or - # the child blocks forever waiting for end-of-line. + # fgets() needs a newline or the child blocks waiting for it. line = (password + "\n").encode() argv = [WOLFSSL_BIN, "encrypt"] + list(args) pid, fd = _pty.fork() if pid == 0: - # Child: become the encrypt process with the pty as its stdin. try: os.execvpe(argv[0], argv, os.environ) except Exception: @@ -1054,10 +1040,9 @@ def _encrypt_with_pty_password(self, password, *args, timeout=30): return code, output.decode(errors="replace") def test_pbkdf2_full_key_derivation(self): - """A typed password with -pbkdf2 must derive the full cipher key. - - Before the fix keySize was overwritten by the password length, so the - `-p` debug print reported a few key bytes instead of 32.""" + """A typed password with -pbkdf2 must derive the full cipher key, + not a truncated one (keySize used to be overwritten by the + password length).""" plain = "f5970_keylen_in.txt" cipher = "f5970_keylen.bin" self._cleanup(plain, cipher) @@ -1076,8 +1061,8 @@ def test_pbkdf2_full_key_derivation(self): m.group(1), self.FULL_KEY_BYTES)) def test_pbkdf2_stdin_decrypts_with_pass(self): - """A file encrypted with a typed password + -pbkdf2 must decrypt with - the same password supplied via -pass (interoperability, F-5970).""" + """A file encrypted with a typed password + -pbkdf2 must decrypt + with the same password supplied via -pass.""" plain = "f5970_interop_in.txt" cipher = "f5970_interop.bin" dec = "f5970_interop_out.txt" @@ -1102,9 +1087,8 @@ def test_pbkdf2_stdin_decrypts_with_pass(self): "decrypted plaintext mismatch") def test_default_kdf_stdin_decrypts_with_pass(self): - """Regression guard: the default (BytesToKey) path already interops - because the key length comes from the cipher; the fix must keep it - working.""" + """The default (BytesToKey) path already interops since the key + length comes from the cipher, not the aliased keySize pointer.""" plain = "f5970_def_in.txt" cipher = "f5970_def.bin" dec = "f5970_def_out.txt" diff --git a/tests/ocsp/ocsp-test.py b/tests/ocsp/ocsp-test.py index 124d3100..c7c31ece 100644 --- a/tests/ocsp/ocsp-test.py +++ b/tests/ocsp/ocsp-test.py @@ -309,6 +309,39 @@ def test_12_graceful_shutdown(self): log = resp.read_log() self.assertIn("wolfssl exiting gracefully", log) + def test_13_malformed_request_counts_toward_nrequest(self): + """A malformed (non-OCSP) request must still count toward + -nrequest, or a client sending only malformed requests could keep + the responder running indefinitely.""" + if self.RESPONDER_BIN != WOLFSSL_BIN: + self.skipTest("nrequest counting only checked for wolfssl") + + resp = self._start_responder(INDEX_VALID, nrequest=1) + + body = b"not a valid OCSP request" + request = ( + b"POST / HTTP/1.0\r\n" + b"Content-Type: application/ocsp-request\r\n" + b"Content-Length: " + str(len(body)).encode() + b"\r\n" + b"\r\n" + body + ) + import socket + with socket.create_connection(("127.0.0.1", self.PORT), + timeout=5) as s: + s.sendall(request) + s.recv(4096) + + # poll for up to 2 seconds for graceful shutdown log + for _ in range(20): + time.sleep(0.1) + log = resp.read_log() + if "wolfssl exiting gracefully" in log: + break + self.assertIn("wolfssl exiting gracefully", log, + "responder did not shut down after a single malformed " + "request with -nrequest 1 -- malformed requests must " + "still count toward the request budget") + # Concrete test classes for each client/responder combination. # Each gets a dynamically assigned port in setUpClass to avoid conflicts. diff --git a/tests/pkcs/pkcs12-test.py b/tests/pkcs/pkcs12-test.py index 7d067f1c..cd2a875e 100644 --- a/tests/pkcs/pkcs12-test.py +++ b/tests/pkcs/pkcs12-test.py @@ -85,6 +85,36 @@ def test_out_bad_path_fails(self): "-out", os.path.join("no-such-dir", "out.pem")) self.assertNotEqual(r.returncode, 0) + def test_passout_encrypts_key_with_supplied_password(self): + """-passout must actually be used to DES-encrypt the extracted + private key, not silently ignored: the output must be an + encrypted PEM block, decryptable with that exact password and + not with a different one.""" + out = "pkcs12-passout-test.pem" + self.addCleanup(lambda: os.remove(out) if os.path.exists(out) else None) + + r = run_wolfssl("pkcs12", "-nocerts", "-passin", 'pass:wolfSSL test', + "-passout", "pass:my-passout-secret", + "-in", P12_FILE, "-out", out) + self.assertEqual(r.returncode, 0, r.stderr) + + with open(out, "r") as f: + content = f.read() + self.assertIn("ENCRYPTED", content, + "-passout key output was not encrypted") + + r = run_wolfssl("rsa", "-in", out, "-noout", + "-passin", "pass:my-passout-secret") + self.assertEqual(r.returncode, 0, + "failed to decrypt -passout key with the correct " + "password: {}".format(r.stderr)) + + r = run_wolfssl("rsa", "-in", out, "-noout", + "-passin", "pass:wrong-password") + self.assertNotEqual(r.returncode, 0, + "decrypting -passout key with the wrong " + "password should have failed") + def test_nocerts_with_passout(self): r = subprocess.run( [WOLFSSL_BIN, "pkcs12", "-passin", "stdin", "-passout", "pass:", diff --git a/tests/x509/cert_setup_unit_test.c b/tests/x509/cert_setup_unit_test.c new file mode 100644 index 00000000..d685cd13 --- /dev/null +++ b/tests/x509/cert_setup_unit_test.c @@ -0,0 +1,1908 @@ +/* cert_setup_unit_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Unit test for the Cert <- WOLFSSL_X509 helpers in clu_cert_setup.c. + * No CLI entry point reaches them yet (only the unwired CSR->cert + * ML-DSA CA-signing path does), so call them directly. */ + +#include +#include +#ifdef _WIN32 + #include + #define GETPID _getpid +#else + #include + #include + #define GETPID getpid +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* skip tests on builds without WOLFSSL_CERT_GEN */ +#ifdef WOLFSSL_CERT_GEN + +static int fail = 0; + +#define CHECK(cond, msg) \ + do { \ + if (!(cond)) { \ + printf("FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); \ + fail++; \ + } \ + } while (0) + +/* build and parse a self-signed DER cert to exercise getters */ +static WOLFSSL_X509* buildFixtureX509Ex(RsaKey* key, WC_RNG* rng, + byte* derBuf, int derBufSz, int* outDerSz, int isCA, + word16 keyUsage) +{ + Cert cert; + int ret; + int certSz; + + if (wc_InitRsaKey(key, HEAP_HINT) != 0) { + printf("FAIL: wc_InitRsaKey\n"); + return NULL; + } + + if (wc_MakeRsaKey(key, 2048, 65537, rng) != 0) { + printf("FAIL: wc_MakeRsaKey\n"); + wc_FreeRsaKey(key); + return NULL; + } + + if (wc_InitCert(&cert) != 0) { + printf("FAIL: wc_InitCert\n"); + wc_FreeRsaKey(key); + return NULL; + } + + XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.state, "Washington", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.locality, "Seattle", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.org, "wolfSSL", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.unit, "Testing", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.commonName, "wolfCLU Cert Setup Test", + CTC_NAME_SIZE - 1); + + cert.isCA = isCA; + cert.keyUsage = keyUsage; + cert.sigType = CTC_SHA256wRSA; + + ret = wc_SetSubjectKeyIdFromPublicKey_ex(&cert, RSA_TYPE, key); + if (ret < 0) { + printf("FAIL: wc_SetSubjectKeyIdFromPublicKey_ex: %d\n", ret); + wc_FreeRsaKey(key); + return NULL; + } + + certSz = wc_MakeCert(&cert, derBuf, derBufSz, key, NULL, rng); + if (certSz <= 0) { + printf("FAIL: wc_MakeCert: %d\n", certSz); + wc_FreeRsaKey(key); + return NULL; + } + + certSz = wc_SignCert(cert.bodySz, cert.sigType, derBuf, derBufSz, key, + NULL, rng); + if (certSz <= 0) { + printf("FAIL: wc_SignCert: %d\n", certSz); + wc_FreeRsaKey(key); + return NULL; + } + + *outDerSz = certSz; + + { + const byte* p = derBuf; + WOLFSSL_X509* x509 = wolfSSL_d2i_X509(NULL, &p, certSz); + if (x509 == NULL) { + printf("FAIL: wolfSSL_d2i_X509\n"); + wc_FreeRsaKey(key); + } + return x509; + } +} + +static WOLFSSL_X509* buildFixtureX509(RsaKey* key, WC_RNG* rng, + byte* derBuf, int derBufSz, int* outDerSz) +{ + return buildFixtureX509Ex(key, rng, derBuf, derBufSz, outDerSz, 1, + KU_KEY_CERT_SIGN | KU_CRL_SIGN); +} + +static void testSetCertNameFieldByNid(void) +{ + CertName name; + int ret; + char longVal[CTC_NAME_SIZE + 10]; + + XMEMSET(&name, 0, sizeof(name)); + + /* valid nid/value populates the field and NUL-terminates */ + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_commonName, "wolfSSL", 7); + CHECK(ret == WOLFCLU_SUCCESS, "SetCertNameFieldByNid valid CN"); + CHECK(XSTRCMP(name.commonName, "wolfSSL") == 0, + "SetCertNameFieldByNid CN value"); + + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_countryName, "US", 2); + CHECK(ret == WOLFCLU_SUCCESS, "SetCertNameFieldByNid valid C"); + CHECK(XSTRCMP(name.country, "US") == 0, "SetCertNameFieldByNid C value"); + + /* NULL dst */ + ret = wolfCLU_SetCertNameFieldByNid(NULL, NID_commonName, "wolfSSL", 7); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), + "SetCertNameFieldByNid NULL dst"); + + /* NULL val */ + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_commonName, NULL, 7); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), + "SetCertNameFieldByNid NULL val"); + + /* valLen <= 0 */ + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_commonName, "wolfSSL", 0); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), + "SetCertNameFieldByNid valLen 0"); + + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_commonName, "wolfSSL", -1); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), + "SetCertNameFieldByNid valLen -1"); + + /* value too long */ + XMEMSET(longVal, 'A', sizeof(longVal)); + longVal[sizeof(longVal) - 1] = '\0'; + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_organizationName, longVal, + CTC_NAME_SIZE); + CHECK(ret == WOLFCLU_FATAL_ERROR, "SetCertNameFieldByNid too long"); + CHECK(name.org[0] == '\0', "SetCertNameFieldByNid too-long org untouched"); + + /* recognized NID with no CertName destination: rejected, because + * dropping an RDN would issue a subject other than the one requested */ + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_pkcs9_contentType, + "1.2.3.4", 7); + CHECK(ret == WOLFCLU_FATAL_ERROR, "SetCertNameFieldByNid unmapped nid"); + +#ifdef WOLFSSL_CERT_NAME_ALL + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_initials, "AB", 2); + CHECK(ret == WOLFCLU_SUCCESS, "SetCertNameFieldByNid initials"); + CHECK(XSTRCMP(name.initials, "AB") == 0, + "SetCertNameFieldByNid initials value"); +#endif +} + +#ifdef WOLFSSL_CERT_EXT +static void testExtHandledNid(void) +{ + CHECK(wolfCLU_ExtHandledNid(NID_basic_constraints) == 1, + "ExtHandledNid basic_constraints"); + CHECK(wolfCLU_ExtHandledNid(NID_key_usage) == 1, + "ExtHandledNid key_usage"); + CHECK(wolfCLU_ExtHandledNid(NID_ext_key_usage) == 1, + "ExtHandledNid ext_key_usage"); + CHECK(wolfCLU_ExtHandledNid(NID_subject_key_identifier) == 1, + "ExtHandledNid subject_key_identifier"); + CHECK(wolfCLU_ExtHandledNid(NID_authority_key_identifier) == 1, + "ExtHandledNid authority_key_identifier"); +#ifdef WOLFSSL_ALT_NAMES + /* SAN handling uses wc_SetAltNamesBuffer natively */ + CHECK(wolfCLU_ExtHandledNid(NID_subject_alt_name) == 1, + "ExtHandledNid subject_alt_name"); +#endif + CHECK(wolfCLU_ExtHandledNid(NID_commonName) == 0, + "ExtHandledNid commonName not handled"); +} + +/* Exercises wolfCLU_UnwrapX509Extensions() on synthetic buffers. */ +static const byte kOneExt[] = { + 0x30, 0x09, 0x06, 0x03, 0x55, 0x1D, 0x13, 0x04, 0x02, 0x30, 0x00 +}; + +static void checkUnwrapLandsOnExtension(const byte* buf, int bufSz, + const char* label) +{ + const byte* extensions = buf; + int extensionsSz = bufSz; + char msg[128]; + + (void)wolfCLU_UnwrapX509Extensions(&extensions, &extensionsSz); + + XSNPRINTF(msg, sizeof(msg), "UnwrapX509Extensions %s: size", label); + CHECK(extensionsSz == (int)sizeof(kOneExt), msg); + + XSNPRINTF(msg, sizeof(msg), "UnwrapX509Extensions %s: bytes match " + "(first extension's OID, not the wrapper's tag bytes)", label); + CHECK(extensionsSz == (int)sizeof(kOneExt) && + XMEMCMP(extensions, kOneExt, sizeof(kOneExt)) == 0, msg); +} + +static void testUnwrapX509Extensions(void) +{ + /* bare "SEQUENCE OF Extension" (no [3] wrapper): 30 0B */ + byte bare[2 + sizeof(kOneExt)]; + /* "[3] EXPLICIT Extensions" wrapping the same bare form: A3 0D */ + byte wrapped[2 + sizeof(bare)]; + /* Ensure untouched if no [3] tag or SEQUENCE at offset 0 */ + static const byte garbage[] = { 0x02, 0x01, 0x00 }; /* INTEGER 0 */ + const byte* extensions; + int extensionsSz; + + bare[0] = 0x30; + bare[1] = (byte)sizeof(kOneExt); + XMEMCPY(bare + 2, kOneExt, sizeof(kOneExt)); + checkUnwrapLandsOnExtension(bare, (int)sizeof(bare), "bare SEQUENCE"); + + wrapped[0] = 0xA3; /* ASN_EXTENSIONS, [3] EXPLICIT constructed */ + wrapped[1] = (byte)sizeof(bare); + XMEMCPY(wrapped + 2, bare, sizeof(bare)); + checkUnwrapLandsOnExtension(wrapped, (int)sizeof(wrapped), + "[3]-wrapped"); + + extensions = garbage; + extensionsSz = (int)sizeof(garbage); + (void)wolfCLU_UnwrapX509Extensions(&extensions, &extensionsSz); + CHECK(extensions == garbage && extensionsSz == (int)sizeof(garbage), + "UnwrapX509Extensions: non-SEQUENCE/non-[3] input left " + "untouched"); +} +#endif /* WOLFSSL_CERT_EXT */ + +static void testAsn1TimeToCertDate(WOLFSSL_X509* x509) +{ + const WOLFSSL_ASN1_TIME* t; + byte buf[CTC_DATE_SIZE]; + int ret; + WOLFSSL_ASN1_TIME bad; + + t = wolfSSL_X509_get_notBefore(x509); + CHECK(t != NULL, "Asn1TimeToCertDate fixture notBefore present"); + if (t == NULL) { + return; + } + + XMEMSET(buf, 0, sizeof(buf)); + ret = wolfCLU_Asn1TimeToCertDate(buf, (int)sizeof(buf), t); + CHECK(ret > 0, "Asn1TimeToCertDate round trip success"); + if (ret > 0) { + int lenPrefixSz = ret - t->length; + CHECK(lenPrefixSz >= 2, "Asn1TimeToCertDate sane length prefix"); + CHECK(buf[0] == (byte)t->type, "Asn1TimeToCertDate tag byte"); + CHECK(XMEMCMP(buf + lenPrefixSz, t->data, (size_t)t->length) == 0, + "Asn1TimeToCertDate value bytes"); + } + + /* bad tag */ + XMEMSET(&bad, 0, sizeof(bad)); + bad.type = 99; /* not UTCTime or GeneralizedTime */ + bad.length = 13; + XMEMSET(bad.data, '0', 12); + bad.data[12] = 'Z'; + ret = wolfCLU_Asn1TimeToCertDate(buf, (int)sizeof(buf), &bad); + CHECK(ret < 0, "Asn1TimeToCertDate bad tag rejected"); + + /* outSz too small */ + bad.type = V_ASN1_UTCTIME; + ret = wolfCLU_Asn1TimeToCertDate(buf, 2, &bad); + CHECK(ret < 0, "Asn1TimeToCertDate outSz too small rejected"); +} + +static void testCopyX509NameToCert(WOLFSSL_X509* x509) +{ + WOLFSSL_X509_NAME* name; + CertName dst; + int ret; + + XMEMSET(&dst, 0, sizeof(dst)); + name = wolfSSL_X509_get_subject_name(x509); + CHECK(name != NULL, "CopyX509NameToCert fixture subject present"); + if (name == NULL) { + return; + } + + ret = wolfCLU_CopyX509NameToCert(name, &dst); + CHECK(ret == WOLFCLU_SUCCESS, "CopyX509NameToCert success"); + CHECK(XSTRCMP(dst.commonName, "wolfCLU Cert Setup Test") == 0, + "CopyX509NameToCert commonName matches"); + CHECK(XSTRCMP(dst.country, "US") == 0, + "CopyX509NameToCert country matches"); + CHECK(XSTRCMP(dst.org, "wolfSSL") == 0, "CopyX509NameToCert org matches"); + + /* NULL args */ + ret = wolfCLU_CopyX509NameToCert(NULL, &dst); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "CopyX509NameToCert NULL name"); + ret = wolfCLU_CopyX509NameToCert(name, NULL); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "CopyX509NameToCert NULL dst"); +} + +#ifdef WOLFSSL_ALT_NAMES +static void testCopyX509SanToCert(WOLFSSL_X509* x509) +{ + Cert cert; + int ret; + + if (wc_InitCert(&cert) != 0) { + CHECK(0, "CopyX509SanToCert wc_InitCert"); + return; + } + + ret = wolfCLU_CopyX509SanToCert(NULL, &cert); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "CopyX509SanToCert NULL x509"); + ret = wolfCLU_CopyX509SanToCert(x509, NULL); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "CopyX509SanToCert NULL cert"); + + ret = wolfCLU_CopyX509SanToCert(x509, &cert); + CHECK(ret == WOLFCLU_SUCCESS, "CopyX509SanToCert no-SAN success"); + CHECK(cert.altNamesSz == 0, "CopyX509SanToCert no-SAN leaves altNamesSz 0"); +} + +/* tests SAN reading from parsed DER */ +static void testCopyX509SanToCertWithSan(void) +{ + RsaKey key; + WC_RNG rng; + WOLFSSL_X509* x509 = NULL; + byte* derBuf = NULL; + int derBufSz = 8192; + int certSz; + Cert cert; + int ret; + WOLFSSL_X509_EXTENSION* ext; + WOLFSSL_ASN1_STRING* sanData; + /* GeneralNames SEQUENCE with one dNSName entry */ + static const byte sanDer[] = { + 0x30, 0x12, 0x82, 0x10, + 't', 'e', 's', 't', '.', 'w', 'o', 'l', 'f', 's', 's', 'l', '.', + 'c', 'o', 'm' + }; + + if (wc_InitRng(&rng) != 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_InitRng"); + return; + } + derBuf = (byte*)XMALLOC((size_t)derBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (derBuf == NULL) { + CHECK(0, "CopyX509SanToCertWithSan: malloc derBuf"); + wc_FreeRng(&rng); + return; + } + + if (wc_InitRsaKey(&key, HEAP_HINT) != 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_InitRsaKey"); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + if (wc_MakeRsaKey(&key, 2048, 65537, &rng) != 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_MakeRsaKey"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + if (wc_InitCert(&cert) != 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_InitCert (fixture)"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.commonName, "wolfCLU Cert Setup Test SAN", + CTC_NAME_SIZE - 1); + cert.isCA = 0; + cert.keyUsage = KU_DIGITAL_SIGNATURE; + cert.sigType = CTC_SHA256wRSA; + XMEMCPY(cert.altNames, sanDer, sizeof(sanDer)); + cert.altNamesSz = (int)sizeof(sanDer); + + if (wc_SetSubjectKeyIdFromPublicKey_ex(&cert, RSA_TYPE, &key) < 0) { + CHECK(0, "CopyX509SanToCertWithSan: " + "wc_SetSubjectKeyIdFromPublicKey_ex"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + certSz = wc_MakeCert(&cert, derBuf, (word32)derBufSz, &key, NULL, &rng); + if (certSz <= 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_MakeCert"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + certSz = wc_SignCert(cert.bodySz, cert.sigType, derBuf, (word32)derBufSz, + &key, NULL, &rng); + if (certSz <= 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_SignCert"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + { + const byte* p = derBuf; + x509 = wolfSSL_d2i_X509(NULL, &p, certSz); + } + if (x509 == NULL) { + CHECK(0, "CopyX509SanToCertWithSan: wolfSSL_d2i_X509"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + /* fresh output Cert distinct from the fixture-building 'cert' above */ + { + Cert outCert; + + if (wc_InitCert(&outCert) != 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_InitCert (output)"); + } + else { + ret = wolfCLU_CopyX509SanToCert(x509, &outCert); + CHECK(ret == WOLFCLU_SUCCESS, + "CopyX509SanToCertWithSan: copy success"); + CHECK(outCert.altNamesSz > 0, + "CopyX509SanToCertWithSan: altNamesSz populated"); + + ext = wolfSSL_X509_get_ext(x509, + wolfSSL_X509_get_ext_by_NID(x509, NID_subject_alt_name, + -1)); + CHECK(ext != NULL, + "CopyX509SanToCertWithSan: SAN ext present on x509"); + if (ext != NULL) { + sanData = wolfSSL_X509_EXTENSION_get_data(ext); + CHECK(sanData != NULL, + "CopyX509SanToCertWithSan: SAN ext data present"); + if (sanData != NULL) { + CHECK(outCert.altNamesSz == sanData->length, + "CopyX509SanToCertWithSan: altNamesSz matches " + "source extension length"); + CHECK(XMEMCMP(outCert.altNames, sanData->data, + (size_t)sanData->length) == 0, + "CopyX509SanToCertWithSan: altNames bytes match " + "source extension"); + } + } + } + } + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); +} +#endif /* WOLFSSL_ALT_NAMES */ + +#ifdef WOLFSSL_CERT_EXT +static void testCopyX509ExtsToCert(WOLFSSL_X509* x509) +{ + Cert cert; + int ret; + int extsDropped = 1; + + if (wc_InitCert(&cert) != 0) { + CHECK(0, "CopyX509ExtsToCert wc_InitCert"); + return; + } + + ret = wolfCLU_CopyX509ExtsToCert(x509, &cert, &extsDropped); + CHECK(ret == WOLFCLU_SUCCESS, "CopyX509ExtsToCert success/no-crash"); + CHECK(extsDropped == 0, + "CopyX509ExtsToCert: no extensions dropped for this fixture"); + + /* no-crash smoke check; nothing custom was added for this fixture */ + (void)wolfCLU_FreeCertCustomExts(&cert); +} + +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_CUSTOM_OID) && \ + defined(HAVE_OID_ENCODING) +/* Non-standard OID extensions must fall back to the generic + * custom-extension copy. */ +static void testCopyX509ExtsToCertCustomExt(void) +{ + WC_RNG rng; + RsaKey key; + WOLFSSL_X509* x509 = NULL; + byte* derBuf = NULL; + int derBufSz = 8192; + int outDerSz = 0; + Cert cert; + int ret; + int extsDropped = 1; + /* Arbitrary, non-standard OID: 1.2.3.4.5 */ + static const char customOid[] = "1.2.3.4.5"; + static const byte customVal[] = { 0x04, 0x03, 'a', 'b', 'c' }; + int i; + + if (wc_InitRng(&rng) != 0) { + CHECK(0, "CopyX509ExtsToCert custom ext: wc_InitRng"); + return; + } + + derBuf = (byte*)XMALLOC((size_t)derBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (derBuf == NULL) { + CHECK(0, "CopyX509ExtsToCert custom ext: malloc derBuf"); + wc_FreeRng(&rng); + return; + } + + if (wc_InitRsaKey(&key, HEAP_HINT) != 0) { + CHECK(0, "CopyX509ExtsToCert custom ext: wc_InitRsaKey"); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + if (wc_MakeRsaKey(&key, 2048, 65537, &rng) != 0) { + CHECK(0, "CopyX509ExtsToCert custom ext: wc_MakeRsaKey"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + { + Cert fixture; + + if (wc_InitCert(&fixture) != 0) { + CHECK(0, "CopyX509ExtsToCert custom ext: wc_InitCert (fixture)"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + XSTRNCPY(fixture.subject.commonName, "wolfCLU Cert Setup Test", + CTC_NAME_SIZE - 1); + fixture.isCA = 0; + fixture.keyUsage = KU_DIGITAL_SIGNATURE; + fixture.sigType = CTC_SHA256wRSA; + + ret = wc_SetCustomExtension(&fixture, 0, customOid, customVal, + (word32)sizeof(customVal)); + if (ret < 0) { + CHECK(0, "CopyX509ExtsToCert custom ext: wc_SetCustomExtension"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + outDerSz = wc_MakeCert(&fixture, derBuf, derBufSz, &key, NULL, &rng); + if (outDerSz <= 0) { + CHECK(0, "CopyX509ExtsToCert custom ext: wc_MakeCert"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + outDerSz = wc_SignCert(fixture.bodySz, fixture.sigType, derBuf, + derBufSz, &key, NULL, &rng); + if (outDerSz <= 0) { + CHECK(0, "CopyX509ExtsToCert custom ext: wc_SignCert"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + } + + { + const byte* p = derBuf; + x509 = wolfSSL_d2i_X509(NULL, &p, outDerSz); + } + if (x509 == NULL) { + CHECK(0, "CopyX509ExtsToCert custom ext: wolfSSL_d2i_X509"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + if (wc_InitCert(&cert) != 0) { + CHECK(0, "CopyX509ExtsToCert custom ext: wc_InitCert"); + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + ret = wolfCLU_CopyX509ExtsToCert(x509, &cert, &extsDropped); + CHECK(ret == WOLFCLU_SUCCESS, + "CopyX509ExtsToCert custom ext: success"); + CHECK(extsDropped == 0, + "CopyX509ExtsToCert custom ext: not dropped"); + CHECK(cert.customCertExtCount == 1, + "CopyX509ExtsToCert custom ext: exactly one custom ext copied"); + + { + int found = 0; + + for (i = 0; i < cert.customCertExtCount; i++) { + if (cert.customCertExt[i].oid != NULL && + XSTRCMP((const char*)cert.customCertExt[i].oid, + customOid) == 0 && + cert.customCertExt[i].valSz == sizeof(customVal) && + XMEMCMP(cert.customCertExt[i].val, customVal, + sizeof(customVal)) == 0) { + found = 1; + break; + } + } + CHECK(found, "CopyX509ExtsToCert custom ext: OID and value match"); + } + + (void)wolfCLU_FreeCertCustomExts(&cert); + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); +} +#endif /* WOLFSSL_ASN_TEMPLATE && WOLFSSL_CUSTOM_OID && HAVE_OID_ENCODING */ +#endif /* WOLFSSL_CERT_EXT */ + +static void testX509FillCert(WOLFSSL_X509* x509, RsaKey* key) +{ + Cert outCert; + int ret; + + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, key, RSA_TYPE, + NULL, 0, NULL, 1, NULL); + CHECK(ret == WOLFCLU_SUCCESS, "X509FillCert success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.isCA == 1, "X509FillCert isCA"); + CHECK(outCert.keyUsage == (KU_KEY_CERT_SIGN | KU_CRL_SIGN), + "X509FillCert keyUsage carries CA bits verbatim"); + CHECK(XSTRCMP(outCert.subject.commonName, + "wolfCLU Cert Setup Test") == 0, + "X509FillCert subject commonName"); + CHECK(outCert.selfSigned == 1, "X509FillCert selfSigned (no caCert)"); +#ifdef WOLFSSL_CERT_EXT + (void)wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + /* Test with caCert == x509 (self-signing via the CA-signing path) */ + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, key, RSA_TYPE, + key, RSA_TYPE, x509, 1, NULL); + CHECK(ret == WOLFCLU_SUCCESS, "X509FillCert with caCert==x509 success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.selfSigned == 1, + "X509FillCert with caCert==x509 is treated as self-signed"); + CHECK(XSTRCMP(outCert.issuer.commonName, "wolfCLU Cert Setup Test") == + 0, + "X509FillCert with caCert==x509 issuer commonName"); +#ifdef WOLFSSL_CERT_EXT + (void)wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + /* Test with a distinct caCert (real CA-signing case) */ + { + WC_RNG caRng; + RsaKey caKey; + WOLFSSL_X509* caX509 = NULL; + byte* caDerBuf = NULL; + int caDerBufSz = 8192; + int caOutDerSz = 0; + + if (wc_InitRng(&caRng) != 0) { + CHECK(0, "X509FillCert distinct caCert: wc_InitRng"); + } + else { + caDerBuf = (byte*)XMALLOC((size_t)caDerBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (caDerBuf == NULL) { + CHECK(0, "X509FillCert distinct caCert: malloc derBuf"); + } + else { + caX509 = buildFixtureX509Ex(&caKey, &caRng, caDerBuf, + caDerBufSz, &caOutDerSz, 1, + KU_KEY_CERT_SIGN | KU_CRL_SIGN); + if (caX509 == NULL) { + CHECK(0, "X509FillCert distinct caCert: fixture CA X509"); + } + else { + ret = wolfCLU_X509FillCert(x509, &outCert, + CTC_SHA256wRSA, key, RSA_TYPE, &caKey, RSA_TYPE, + caX509, 1, NULL); + CHECK(ret == WOLFCLU_SUCCESS, + "X509FillCert with distinct caCert success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.selfSigned == 0, + "X509FillCert with distinct caCert " + "selfSigned == 0"); + CHECK(outCert.isCA == 1, + "X509FillCert with distinct caCert: CA:TRUE " + "is preserved (rejection logic moved " + "upstream)"); +#ifdef WOLFSSL_CERT_EXT + (void)wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + wolfSSL_X509_free(caX509); + wc_FreeRsaKey(&caKey); + } + XFREE(caDerBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + wc_FreeRng(&caRng); + } + } + + /* policySanitized == 0 must be refused */ + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, key, RSA_TYPE, + NULL, 0, NULL, 0, NULL); + CHECK(ret == WOLFCLU_FATAL_ERROR, + "X509FillCert refuses unsanitized policy"); + + /* NULL x509 */ + ret = wolfCLU_X509FillCert(NULL, &outCert, CTC_SHA256wRSA, key, RSA_TYPE, + NULL, 0, NULL, 1, NULL); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "X509FillCert NULL x509"); + + /* NULL subjWcKey must be rejected */ + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, NULL, RSA_TYPE, + NULL, 0, NULL, 1, NULL); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "X509FillCert NULL subjWcKey"); +} + +/* Non-CA CSR must retain RSA defaults and extra bits */ +static void testX509FillCertLeafKeyUsageMerge(void) +{ + WC_RNG rng; + RsaKey key; + WOLFSSL_X509* x509 = NULL; + byte* derBuf = NULL; + int derBufSz = 8192; + int outDerSz = 0; + Cert outCert; + int ret; + + if (wc_InitRng(&rng) != 0) { + CHECK(0, "leaf keyUsage merge: wc_InitRng"); + return; + } + + derBuf = (byte*)XMALLOC((size_t)derBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (derBuf == NULL) { + CHECK(0, "leaf keyUsage merge: malloc derBuf"); + wc_FreeRng(&rng); + return; + } + + x509 = buildFixtureX509Ex(&key, &rng, derBuf, derBufSz, &outDerSz, 0, + KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION); + if (x509 == NULL) { + CHECK(0, "leaf keyUsage merge: could not build fixture X509"); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, &key, + RSA_TYPE, NULL, 0, NULL, 1, NULL); + CHECK(ret == WOLFCLU_SUCCESS, "leaf keyUsage merge: X509FillCert success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.keyUsage == + (KU_DIGITAL_SIGNATURE | KU_KEY_ENCIPHERMENT | + KU_NON_REPUDIATION), + "leaf keyUsage merge: RSA default keyEncipherment kept, " + "CSR nonRepudiation added"); +#ifdef WOLFSSL_CERT_EXT + (void)wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); +} + +#ifdef WOLFSSL_CERT_EXT +/* ExtKeyUsage extension must carry over EXTKEYUSE_* bits. */ +static void testX509FillCertExtKeyUsage(void) +{ + WC_RNG rng; + RsaKey key; + WOLFSSL_X509* x509 = NULL; + byte* derBuf = NULL; + int derBufSz = 8192; + int outDerSz = 0; + Cert fixture; + Cert outCert; + int ret; + + if (wc_InitRng(&rng) != 0) { + CHECK(0, "extKeyUsage: wc_InitRng"); + return; + } + + derBuf = (byte*)XMALLOC((size_t)derBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (derBuf == NULL) { + CHECK(0, "extKeyUsage: malloc derBuf"); + wc_FreeRng(&rng); + return; + } + + if (wc_InitRsaKey(&key, HEAP_HINT) != 0) { + CHECK(0, "extKeyUsage: wc_InitRsaKey"); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + if (wc_MakeRsaKey(&key, 2048, 65537, &rng) != 0) { + CHECK(0, "extKeyUsage: wc_MakeRsaKey"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + if (wc_InitCert(&fixture) != 0) { + CHECK(0, "extKeyUsage: wc_InitCert (fixture)"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + XSTRNCPY(fixture.subject.commonName, "wolfCLU Cert Setup Test", + CTC_NAME_SIZE - 1); + fixture.isCA = 0; + fixture.keyUsage = KU_DIGITAL_SIGNATURE; + fixture.sigType = CTC_SHA256wRSA; + + ret = wc_SetExtKeyUsage(&fixture, "serverAuth,clientAuth"); + if (ret != 0) { + CHECK(0, "extKeyUsage: wc_SetExtKeyUsage"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + outDerSz = wc_MakeCert(&fixture, derBuf, derBufSz, &key, NULL, &rng); + if (outDerSz <= 0) { + CHECK(0, "extKeyUsage: wc_MakeCert"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + outDerSz = wc_SignCert(fixture.bodySz, fixture.sigType, derBuf, derBufSz, + &key, NULL, &rng); + if (outDerSz <= 0) { + CHECK(0, "extKeyUsage: wc_SignCert"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + { + const byte* p = derBuf; + x509 = wolfSSL_d2i_X509(NULL, &p, outDerSz); + } + if (x509 == NULL) { + CHECK(0, "extKeyUsage: wolfSSL_d2i_X509"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, &key, + RSA_TYPE, NULL, 0, NULL, 1, NULL); + CHECK(ret == WOLFCLU_SUCCESS, "extKeyUsage: X509FillCert success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.extKeyUsage == + (EXTKEYUSE_SERVER_AUTH | EXTKEYUSE_CLIENT_AUTH), + "extKeyUsage: serverAuth+clientAuth bits carried onto cert"); + (void)wolfCLU_FreeCertCustomExts(&outCert); + } + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); +} + +/* CA branch must drop CSR extKeyUsage (same lockdown as keyUsage). */ +static void testX509FillCertCaExtKeyUsageDropped(void) +{ + WC_RNG rng; + RsaKey key; + WOLFSSL_X509* x509 = NULL; + byte* derBuf = NULL; + int derBufSz = 8192; + int outDerSz = 0; + Cert fixture; + Cert outCert; + int ret; + + if (wc_InitRng(&rng) != 0) { + CHECK(0, "CA extKeyUsage: wc_InitRng"); + return; + } + + derBuf = (byte*)XMALLOC((size_t)derBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (derBuf == NULL) { + CHECK(0, "CA extKeyUsage: malloc derBuf"); + wc_FreeRng(&rng); + return; + } + + if (wc_InitRsaKey(&key, HEAP_HINT) != 0) { + CHECK(0, "CA extKeyUsage: wc_InitRsaKey"); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + if (wc_MakeRsaKey(&key, 2048, 65537, &rng) != 0) { + CHECK(0, "CA extKeyUsage: wc_MakeRsaKey"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + if (wc_InitCert(&fixture) != 0) { + CHECK(0, "CA extKeyUsage: wc_InitCert (fixture)"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + XSTRNCPY(fixture.subject.commonName, "wolfCLU Cert Setup Test CA", + CTC_NAME_SIZE - 1); + fixture.isCA = 1; + fixture.keyUsage = KU_KEY_CERT_SIGN | KU_CRL_SIGN; + fixture.sigType = CTC_SHA256wRSA; + + ret = wc_SetExtKeyUsage(&fixture, "serverAuth,clientAuth"); + if (ret != 0) { + CHECK(0, "CA extKeyUsage: wc_SetExtKeyUsage"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + outDerSz = wc_MakeCert(&fixture, derBuf, derBufSz, &key, NULL, &rng); + if (outDerSz <= 0) { + CHECK(0, "CA extKeyUsage: wc_MakeCert"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + outDerSz = wc_SignCert(fixture.bodySz, fixture.sigType, derBuf, derBufSz, + &key, NULL, &rng); + if (outDerSz <= 0) { + CHECK(0, "CA extKeyUsage: wc_SignCert"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + { + const byte* p = derBuf; + x509 = wolfSSL_d2i_X509(NULL, &p, outDerSz); + } + if (x509 == NULL) { + CHECK(0, "CA extKeyUsage: wolfSSL_d2i_X509"); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, &key, + RSA_TYPE, NULL, 0, NULL, 1, NULL); + CHECK(ret == WOLFCLU_SUCCESS, "CA extKeyUsage: X509FillCert success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.isCA == 1, "CA extKeyUsage: isCA set"); + CHECK(outCert.extKeyUsage == 0, + "CA extKeyUsage: CSR EKU bits dropped on CA cert"); + (void)wolfCLU_FreeCertCustomExts(&outCert); + } + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); +} +#endif /* WOLFSSL_CERT_EXT */ + +/* CA CSR must only retain keyCertSign/cRLSign on CA branch. */ +static void testX509FillCertCaKeyUsageMask(void) +{ + WC_RNG rng; + RsaKey key; + WOLFSSL_X509* x509 = NULL; + byte* derBuf = NULL; + int derBufSz = 8192; + int outDerSz = 0; + Cert outCert; + int ret; + + if (wc_InitRng(&rng) != 0) { + CHECK(0, "CA keyUsage mask: wc_InitRng"); + return; + } + + derBuf = (byte*)XMALLOC((size_t)derBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (derBuf == NULL) { + CHECK(0, "CA keyUsage mask: malloc derBuf"); + wc_FreeRng(&rng); + return; + } + + x509 = buildFixtureX509Ex(&key, &rng, derBuf, derBufSz, &outDerSz, 1, + KU_KEY_CERT_SIGN | KU_CRL_SIGN | KU_DIGITAL_SIGNATURE | + KU_DATA_ENCIPHERMENT); + if (x509 == NULL) { + CHECK(0, "CA keyUsage mask: could not build fixture X509"); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, &key, + RSA_TYPE, NULL, 0, NULL, 1, NULL); + CHECK(ret == WOLFCLU_SUCCESS, "CA keyUsage mask: X509FillCert success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.keyUsage == (KU_KEY_CERT_SIGN | KU_CRL_SIGN), + "CA keyUsage mask: non-CA CSR keyUsage bits dropped, only " + "keyCertSign/cRLSign carried onto issued CA cert"); +#ifdef WOLFSSL_CERT_EXT + (void)wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); +} + +/* Non-RSA leaf keys get digitalSignature, not keyEncipherment */ +static void testX509FillCertLeafKeyUsageNonRsa(void) +{ + WC_RNG rng; + ecc_key key; + WOLFSSL_X509* x509 = NULL; + byte* derBuf = NULL; + int derBufSz = 8192; + int certSz; + Cert cert; + Cert outCert; + int ret; + + if (wc_InitRng(&rng) != 0) { + CHECK(0, "leaf keyUsage non-RSA: wc_InitRng"); + return; + } + + derBuf = (byte*)XMALLOC((size_t)derBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (derBuf == NULL) { + CHECK(0, "leaf keyUsage non-RSA: malloc derBuf"); + wc_FreeRng(&rng); + return; + } + + if (wc_ecc_init(&key) != 0) { + CHECK(0, "leaf keyUsage non-RSA: wc_ecc_init"); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + if (wc_ecc_make_key(&rng, 32, &key) != 0) { + CHECK(0, "leaf keyUsage non-RSA: wc_ecc_make_key"); + wc_ecc_free(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + if (wc_InitCert(&cert) != 0) { + CHECK(0, "leaf keyUsage non-RSA: wc_InitCert"); + wc_ecc_free(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.commonName, "wolfCLU Cert Setup Test ECC", + CTC_NAME_SIZE - 1); + cert.isCA = 0; + cert.keyUsage = KU_DIGITAL_SIGNATURE; + cert.sigType = CTC_SHA256wECDSA; + + if (wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &key) < 0) { + CHECK(0, "leaf keyUsage non-RSA: wc_SetSubjectKeyIdFromPublicKey_ex"); + wc_ecc_free(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + certSz = wc_MakeCert(&cert, derBuf, (word32)derBufSz, NULL, &key, &rng); + if (certSz <= 0) { + CHECK(0, "leaf keyUsage non-RSA: wc_MakeCert"); + wc_ecc_free(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + certSz = wc_SignCert(cert.bodySz, cert.sigType, derBuf, (word32)derBufSz, + NULL, &key, &rng); + if (certSz <= 0) { + CHECK(0, "leaf keyUsage non-RSA: wc_SignCert"); + wc_ecc_free(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + { + const byte* p = derBuf; + x509 = wolfSSL_d2i_X509(NULL, &p, certSz); + } + if (x509 == NULL) { + CHECK(0, "leaf keyUsage non-RSA: wolfSSL_d2i_X509"); + wc_ecc_free(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return; + } + + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wECDSA, &key, + ECC_TYPE, NULL, 0, NULL, 1, NULL); + CHECK(ret == WOLFCLU_SUCCESS, + "leaf keyUsage non-RSA: X509FillCert success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.keyUsage == KU_DIGITAL_SIGNATURE, + "leaf keyUsage non-RSA: plain digitalSignature, no " + "RSA-only keyEncipherment"); +#ifdef WOLFSSL_CERT_EXT + (void)wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + wolfSSL_X509_free(x509); + wc_ecc_free(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); +} + +#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) +/* Portable byte-buffer substring search (memmem() isn't available on all + * of this project's target platforms). */ +static int containsBytes(const byte* haystack, size_t haystackSz, + const char* needle) +{ + size_t needleSz = XSTRLEN(needle); + size_t i; + + if (needleSz == 0 || needleSz > haystackSz) { + return 0; + } + for (i = 0; i <= haystackSz - needleSz; i++) { + if (XMEMCMP(haystack + i, needle, needleSz) == 0) { + return 1; + } + } + return 0; +} + +/* Confirms a DER buffer parses as a structurally valid cert/CSR. */ +static void checkParsesAsDer(const byte* der, int derSz, int certType, + const char* label) +{ + struct DecodedCert dCert; + int ret; + char msg[128]; + + wc_InitDecodedCert(&dCert, der, (word32)derSz, HEAP_HINT); + ret = wc_ParseCert(&dCert, certType, NO_VERIFY, NULL); + wc_FreeDecodedCert(&dCert); + + XSNPRINTF(msg, sizeof(msg), "%s: parses as valid DER", label); + CHECK(ret == 0, msg); +} + +/* Confirms certDer's signature verifies against caDer as a trusted root. */ +static void checkSignatureVerifies(const byte* caDer, int caDerSz, + const byte* certDer, int certDerSz, const char* label) +{ + WOLFSSL_CERT_MANAGER* cm; + char msg[128]; + + cm = wolfSSL_CertManagerNew(); + if (cm == NULL) { + CHECK(0, "checkSignatureVerifies: wolfSSL_CertManagerNew"); + return; + } + + XSNPRINTF(msg, sizeof(msg), "%s: CA loads as trust anchor", label); + CHECK(wolfSSL_CertManagerLoadCABuffer(cm, caDer, (long)caDerSz, + WOLFSSL_FILETYPE_ASN1) == WOLFSSL_SUCCESS, msg); + + XSNPRINTF(msg, sizeof(msg), "%s: signature verifies against CA", label); + CHECK(wolfSSL_CertManagerVerifyBuffer(cm, certDer, (long)certDerSz, + WOLFSSL_FILETYPE_ASN1) == WOLFSSL_SUCCESS, msg); + + wolfSSL_CertManagerFree(cm); +} + +/* A CSR-derived (or freshly-created) X509 carries no notBefore/notAfter -- + * wolfSSL_X509_get_notBefore()/_notAfter() still return a non-NULL pointer + * to the zeroed embedded ASN1_TIME in that case (length == 0), which must + * NOT be treated as a fatal date-conversion error. */ +static void testX509FillCertDateUnset(WOLFSSL_X509* x509, RsaKey* key) +{ + Cert outCert; + int ret; + WOLFSSL_ASN1_TIME* nb; + WOLFSSL_ASN1_TIME* na; + WOLFSSL_ASN1_TIME savedNb; + WOLFSSL_ASN1_TIME savedNa; + byte* outDer = NULL; + int outDerSz = 0; + + nb = wolfSSL_X509_get_notBefore(x509); + na = wolfSSL_X509_get_notAfter(x509); + if (nb == NULL || na == NULL) { + CHECK(0, "X509FillCert date-unset: fixture missing notBefore/notAfter"); + return; + } + savedNb = *nb; + savedNa = *na; + nb->length = 0; + na->length = 0; + + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, key, RSA_TYPE, + NULL, 0, NULL, 1, NULL); + CHECK(ret == WOLFCLU_SUCCESS, + "X509FillCert date-unset does not fail"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.beforeDateSz == 0, + "X509FillCert date-unset leaves beforeDateSz at 0"); + CHECK(outCert.afterDateSz == 0, + "X509FillCert date-unset leaves afterDateSz at 0"); +#ifdef WOLFSSL_CERT_EXT + (void)wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + +#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) + /* MakeAndSignCertDer must fall back to a days-valid default rather + * than failing when the source x509 has no dates set. */ + ret = wolfCLU_MakeAndSignCertDer(x509, 0, CTC_SHA256wRSA, 8192, key, + RSA_TYPE, key, RSA_TYPE, NULL, 1, 365, &outDer, &outDerSz); + CHECK(ret == WOLFCLU_SUCCESS, + "MakeAndSignCertDer date-unset falls back to daysValid"); + if (ret == WOLFCLU_SUCCESS) { + checkParsesAsDer(outDer, outDerSz, CERT_TYPE, + "MakeAndSignCertDer date-unset"); + XFREE(outDer, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } +#else + (void)outDer; + (void)outDerSz; +#endif /* WOLFSSL_CERT_GEN && WOLFSSL_CERT_EXT */ + + *nb = savedNb; + *na = savedNa; +} + +static void testMakeAndSignCertDer(WOLFSSL_X509* x509, RsaKey* key) +{ + byte* outDer = NULL; + int outDerSz = 0; + int ret; + + /* self-signed cert */ + ret = wolfCLU_MakeAndSignCertDer(x509, 0, CTC_SHA256wRSA, 8192, key, + RSA_TYPE, key, RSA_TYPE, NULL, 1, 365, &outDer, &outDerSz); + CHECK(ret == WOLFCLU_SUCCESS, "MakeAndSignCertDer self-signed success"); + if (ret == WOLFCLU_SUCCESS) { + checkParsesAsDer(outDer, outDerSz, CERT_TYPE, + "MakeAndSignCertDer self-signed"); + checkSignatureVerifies(outDer, outDerSz, outDer, outDerSz, + "MakeAndSignCertDer self-signed"); + XFREE(outDer, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + outDer = NULL; + } + + /* CSR */ + ret = wolfCLU_MakeAndSignCertDer(x509, 1, CTC_SHA256wRSA, 8192, key, + RSA_TYPE, key, RSA_TYPE, NULL, 1, -1, &outDer, &outDerSz); + CHECK(ret == WOLFCLU_SUCCESS, "MakeAndSignCertDer CSR success"); + if (ret == WOLFCLU_SUCCESS) { + checkParsesAsDer(outDer, outDerSz, CERTREQ_TYPE, + "MakeAndSignCertDer CSR"); + XFREE(outDer, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + outDer = NULL; + } + + /* arg validation */ + ret = wolfCLU_MakeAndSignCertDer(NULL, 0, CTC_SHA256wRSA, 8192, key, + RSA_TYPE, key, RSA_TYPE, NULL, 1, 365, &outDer, &outDerSz); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "MakeAndSignCertDer NULL x509"); + + ret = wolfCLU_MakeAndSignCertDer(x509, 0, CTC_SHA256wRSA, 8192, NULL, + RSA_TYPE, key, RSA_TYPE, NULL, 1, 365, &outDer, &outDerSz); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), + "MakeAndSignCertDer NULL subjKey"); + + ret = wolfCLU_MakeAndSignCertDer(x509, 0, CTC_SHA256wRSA, 8192, key, + RSA_TYPE, NULL, RSA_TYPE, NULL, 1, 365, &outDer, &outDerSz); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), + "MakeAndSignCertDer NULL caKey"); + + ret = wolfCLU_MakeAndSignCertDer(x509, 0, CTC_SHA256wRSA, 8192, key, + RSA_TYPE, key, RSA_TYPE, NULL, 1, 365, NULL, &outDerSz); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), + "MakeAndSignCertDer NULL outDer"); +} + +static void testBuildAndSignNative(WOLFSSL_X509* x509, RsaKey* key) +{ + WOLFSSL_BIO* bio; + byte* data = NULL; + int dataSz; + int ret; + + /* self-signed cert, DER form, via BIO */ + bio = wolfSSL_BIO_new(wolfSSL_BIO_s_mem()); + CHECK(bio != NULL, "BuildAndSignNative: BIO_new"); + if (bio != NULL) { + ret = wolfCLU_BuildAndSignNative(key, RSA_TYPE, CTC_SHA256wRSA, 8192, + x509, 365, 0, DER_FORM, bio, 0); + CHECK(ret == WOLFCLU_SUCCESS, "BuildAndSignNative self-signed success"); + if (ret == WOLFCLU_SUCCESS) { + dataSz = wolfSSL_BIO_get_mem_data(bio, &data); + CHECK(dataSz > 0 && data != NULL, + "BuildAndSignNative self-signed: BIO has data"); + if (dataSz > 0 && data != NULL) { + checkParsesAsDer(data, dataSz, CERT_TYPE, + "BuildAndSignNative self-signed"); + checkSignatureVerifies(data, dataSz, data, dataSz, + "BuildAndSignNative self-signed"); + } + } + wolfSSL_BIO_free(bio); + } + + /* CSR, PEM form, via BIO */ + bio = wolfSSL_BIO_new(wolfSSL_BIO_s_mem()); + CHECK(bio != NULL, "BuildAndSignNative CSR: BIO_new"); + if (bio != NULL) { + ret = wolfCLU_BuildAndSignNative(key, RSA_TYPE, CTC_SHA256wRSA, 8192, + x509, 0, 1, PEM_FORM, bio, 0); + CHECK(ret == WOLFCLU_SUCCESS, "BuildAndSignNative CSR success"); + if (ret == WOLFCLU_SUCCESS) { + dataSz = wolfSSL_BIO_get_mem_data(bio, &data); + CHECK(dataSz > 0 && data != NULL && + containsBytes(data, (size_t)dataSz, + "CERTIFICATE REQUEST"), + "BuildAndSignNative CSR: PEM contains CSR header"); + } + wolfSSL_BIO_free(bio); + } + + /* noOut path: build+sign without writing anywhere */ + ret = wolfCLU_BuildAndSignNative(key, RSA_TYPE, CTC_SHA256wRSA, 8192, + x509, 365, 0, DER_FORM, NULL, 1); + CHECK(ret == WOLFCLU_SUCCESS, "BuildAndSignNative noOut success"); + + /* arg validation */ + ret = wolfCLU_BuildAndSignNative(NULL, RSA_TYPE, CTC_SHA256wRSA, 8192, + x509, 365, 0, DER_FORM, NULL, 1); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "BuildAndSignNative NULL key"); + + ret = wolfCLU_BuildAndSignNative(key, RSA_TYPE, CTC_SHA256wRSA, 8192, + NULL, 365, 0, DER_FORM, NULL, 1); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "BuildAndSignNative NULL x509"); + + ret = wolfCLU_BuildAndSignNative(key, RSA_TYPE, CTC_SHA256wRSA, 8192, + x509, 365, 0, DER_FORM, NULL, 0); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), + "BuildAndSignNative NULL bioOut with noOut=0"); +} + +static void testCertSignNative(WOLFSSL_X509* x509, RsaKey* key) +{ + WC_RNG caRng; + RsaKey caKey; + WOLFSSL_X509* caX509 = NULL; + byte* caDerBuf = NULL; + int caDerBufSz = 8192; + int caOutDerSz = 0; + byte* outData = NULL; + int outDataSz = 0; + int ret; + + if (wc_InitRng(&caRng) != 0) { + CHECK(0, "CertSignNative: wc_InitRng"); + return; + } + + caDerBuf = (byte*)XMALLOC((size_t)caDerBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (caDerBuf == NULL) { + CHECK(0, "CertSignNative: malloc caDerBuf"); + wc_FreeRng(&caRng); + return; + } + + caX509 = buildFixtureX509Ex(&caKey, &caRng, caDerBuf, caDerBufSz, + &caOutDerSz, 1, KU_KEY_CERT_SIGN | KU_CRL_SIGN); + if (caX509 == NULL) { + CHECK(0, "CertSignNative: fixture CA X509"); + XFREE(caDerBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&caRng); + return; + } + + /* CA-signed cert, DER form */ + ret = wolfCLU_CertSignNative(x509, &caKey, RSA_TYPE, CTC_SHA256wRSA, 8192, + caX509, DER_FORM, &outData, &outDataSz, 1, key, RSA_TYPE); + CHECK(ret == WOLFCLU_SUCCESS, "CertSignNative DER success"); + if (ret == WOLFCLU_SUCCESS) { + checkParsesAsDer(outData, outDataSz, CERT_TYPE, "CertSignNative DER"); + checkSignatureVerifies(caDerBuf, caOutDerSz, outData, outDataSz, + "CertSignNative DER"); + XFREE(outData, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + outData = NULL; + } + + /* CA-signed cert, PEM form */ + ret = wolfCLU_CertSignNative(x509, &caKey, RSA_TYPE, CTC_SHA256wRSA, 8192, + caX509, PEM_FORM, &outData, &outDataSz, 1, key, RSA_TYPE); + CHECK(ret == WOLFCLU_SUCCESS, "CertSignNative PEM success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outDataSz > 0 && outData != NULL && + containsBytes(outData, (size_t)outDataSz, + "BEGIN CERTIFICATE"), + "CertSignNative PEM: output has cert header"); + XFREE(outData, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + outData = NULL; + } + + /* arg validation */ + ret = wolfCLU_CertSignNative(NULL, &caKey, RSA_TYPE, CTC_SHA256wRSA, 8192, + caX509, DER_FORM, &outData, &outDataSz, 1, key, RSA_TYPE); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "CertSignNative NULL x509"); + + ret = wolfCLU_CertSignNative(x509, NULL, RSA_TYPE, CTC_SHA256wRSA, 8192, + caX509, DER_FORM, &outData, &outDataSz, 1, key, RSA_TYPE); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "CertSignNative NULL caKey"); + + ret = wolfCLU_CertSignNative(x509, &caKey, RSA_TYPE, CTC_SHA256wRSA, 8192, + caX509, DER_FORM, NULL, &outDataSz, 1, key, RSA_TYPE); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "CertSignNative NULL outData"); + + ret = wolfCLU_CertSignNative(x509, &caKey, RSA_TYPE, CTC_SHA256wRSA, 8192, + caX509, DER_FORM, &outData, &outDataSz, 1, NULL, RSA_TYPE); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "CertSignNative NULL subjKey"); + + wolfSSL_X509_free(caX509); + wc_FreeRsaKey(&caKey); + XFREE(caDerBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&caRng); +} +#endif /* WOLFSSL_CERT_GEN && WOLFSSL_CERT_EXT */ + +static void testReadFileToBuffer(void) +{ + byte* buf = NULL; + int bufSz = 0; + int ret; + char testFile[64]; + FILE* f; + + XSNPRINTF(testFile, sizeof(testFile), "test_read_file_%d.tmp", + (int)GETPID()); + + /* NULL args */ + ret = wolfCLU_ReadFileToBuffer(NULL, 100, &buf, &bufSz); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "ReadFileToBuffer NULL path"); + ret = wolfCLU_ReadFileToBuffer(testFile, 100, NULL, &bufSz); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "ReadFileToBuffer NULL outBuf"); + ret = wolfCLU_ReadFileToBuffer(testFile, 100, &buf, NULL); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "ReadFileToBuffer NULL outSz"); + ret = wolfCLU_ReadFileToBuffer(testFile, 0, &buf, &bufSz); + CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "ReadFileToBuffer maxSz <= 0"); + + /* Missing file */ + remove(testFile); /* Ensure it doesn't exist */ + ret = wolfCLU_ReadFileToBuffer(testFile, 100, &buf, &bufSz); + CHECK(ret == WOLFCLU_FATAL_ERROR, "ReadFileToBuffer missing file"); + + /* Empty file */ + f = fopen(testFile, "wb"); + if (f) { + fclose(f); + ret = wolfCLU_ReadFileToBuffer(testFile, 100, &buf, &bufSz); + CHECK(ret == WOLFCLU_FATAL_ERROR, "ReadFileToBuffer empty file"); + remove(testFile); + } else { + CHECK(0, "ReadFileToBuffer empty file: fopen failed"); + } + + /* File exceeds maxSz */ + f = fopen(testFile, "wb"); + if (f) { + if (fwrite("12345", 1, 5, f) == 5) { + fclose(f); + ret = wolfCLU_ReadFileToBuffer(testFile, 4, &buf, &bufSz); + CHECK(ret == WOLFCLU_FATAL_ERROR, "ReadFileToBuffer exceeds maxSz"); + } else { + fclose(f); + CHECK(0, "ReadFileToBuffer exceeds maxSz: fwrite failed"); + } + remove(testFile); + } else { + CHECK(0, "ReadFileToBuffer exceeds maxSz: fopen failed"); + } + + /* Valid read */ + f = fopen(testFile, "wb"); + if (f) { + if (fwrite("12345", 1, 5, f) == 5) { + fclose(f); + ret = wolfCLU_ReadFileToBuffer(testFile, 10, &buf, &bufSz); + CHECK(ret == WOLFCLU_SUCCESS, "ReadFileToBuffer valid read"); + CHECK(bufSz == 5, "ReadFileToBuffer size"); + if (buf) { + CHECK(XMEMCMP(buf, "12345", 5) == 0, + "ReadFileToBuffer content"); + CHECK(buf[5] == '\0', "ReadFileToBuffer null terminated"); + XFREE(buf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + } else { + fclose(f); + CHECK(0, "ReadFileToBuffer valid read: fwrite failed"); + } + remove(testFile); + } else { + CHECK(0, "ReadFileToBuffer valid read: fopen failed"); + } +} + +static void testPathsRefEqual(void) +{ + FILE* f; + char relPath[64]; + char dotRelPath[80]; + + CHECK(wolfCLU_PathsRefEqual(NULL, NULL) == 0, "PathsRefEqual NULLs"); + CHECK(wolfCLU_PathsRefEqual("a", NULL) == 0, "PathsRefEqual one NULL"); + CHECK(wolfCLU_PathsRefEqual("same.txt", "same.txt") == 1, + "PathsRefEqual identical"); + CHECK(wolfCLU_PathsRefEqual("a.txt", "b.txt") == 0, + "PathsRefEqual different"); + + XSNPRINTF(relPath, sizeof(relPath), "test_ref_equal_%d.tmp", + (int)GETPID()); + XSNPRINTF(dotRelPath, sizeof(dotRelPath), "./%s", relPath); + + f = fopen(relPath, "wb"); + if (f) { + fclose(f); + CHECK(wolfCLU_PathsRefEqual(relPath, dotRelPath) == 1, + "PathsRefEqual absolute/relative"); + remove(relPath); + } +} + +#ifndef _WIN32 +/* wolfCLU_OpenOutFile() must stay usable for the -out targets fopen() has + * always accepted, while wolfCLU_OpenKeyFile() must never write key material + * through a symlink nor destroy the link. */ +static void testOpenOutAndKeyFile(void) +{ + char target[64]; + char link[64]; + FILE* f; + struct stat st; + + XSNPRINTF(target, sizeof(target), "test_openfile_%d.tmp", (int)GETPID()); + XSNPRINTF(link, sizeof(link), "test_openlink_%d.tmp", (int)GETPID()); + remove(target); + remove(link); + + /* Non-secret output writes through a symlink and leaves it in place. */ + f = fopen(target, "wb"); + if (f == NULL) { + CHECK(0, "OpenOutFile fixture create target"); + return; + } + fclose(f); + if (symlink(target, link) != 0) { + /* Filesystem without symlink support; nothing to assert. */ + remove(target); + return; + } + + f = wolfCLU_OpenOutFile(link); + CHECK(f != NULL, "OpenOutFile follows symlink"); + if (f != NULL) { + fputs("data", f); + fclose(f); + } + CHECK(lstat(link, &st) == 0 && S_ISLNK(st.st_mode), + "OpenOutFile leaves symlink intact"); + CHECK(stat(target, &st) == 0 && st.st_size == 4, + "OpenOutFile wrote through symlink"); + + /* Key output refuses the same symlink rather than following it. */ + f = wolfCLU_OpenKeyFile(link); + CHECK(f == NULL, "OpenKeyFile refuses symlink"); + if (f != NULL) { + fclose(f); + } + CHECK(lstat(link, &st) == 0 && S_ISLNK(st.st_mode), + "OpenKeyFile leaves symlink intact"); + CHECK(stat(target, &st) == 0 && st.st_size == 4, + "OpenKeyFile did not truncate symlink target"); + + remove(link); + + /* Key output re-tightens permissions on an existing loose file. */ + CHECK(chmod(target, 0666) == 0, "OpenKeyFile fixture chmod"); + f = wolfCLU_OpenKeyFile(target); + CHECK(f != NULL, "OpenKeyFile plain path"); + if (f != NULL) { + fclose(f); + CHECK(stat(target, &st) == 0 && + (st.st_mode & (S_IRWXG | S_IRWXO)) == 0, + "OpenKeyFile is owner-only"); + } + + remove(target); +} +#endif /* !_WIN32 */ + +static void testParseDaysArg(void) +{ + int days = -1; + + CHECK(wolfCLU_ParseDaysArg(NULL, &days) == USER_INPUT_ERROR, + "ParseDaysArg NULL s"); + CHECK(wolfCLU_ParseDaysArg("1", NULL) == USER_INPUT_ERROR, + "ParseDaysArg NULL out"); + CHECK(wolfCLU_ParseDaysArg("", &days) == USER_INPUT_ERROR, + "ParseDaysArg empty"); + CHECK(wolfCLU_ParseDaysArg("0", &days) == USER_INPUT_ERROR, + "ParseDaysArg zero"); + CHECK(wolfCLU_ParseDaysArg("-1", &days) == USER_INPUT_ERROR, + "ParseDaysArg negative"); + CHECK(wolfCLU_ParseDaysArg("abc", &days) == USER_INPUT_ERROR, + "ParseDaysArg non-numeric"); + CHECK(wolfCLU_ParseDaysArg("12x", &days) == USER_INPUT_ERROR, + "ParseDaysArg trailing junk"); + CHECK(wolfCLU_ParseDaysArg("36501", &days) == USER_INPUT_ERROR, + "ParseDaysArg above max"); + + days = -1; + CHECK(wolfCLU_ParseDaysArg("1", &days) == WOLFCLU_SUCCESS && days == 1, + "ParseDaysArg 1"); + days = -1; + CHECK(wolfCLU_ParseDaysArg("365", &days) == WOLFCLU_SUCCESS && + days == 365, "ParseDaysArg 365"); + days = -1; + CHECK(wolfCLU_ParseDaysArg("36500", &days) == WOLFCLU_SUCCESS && + days == WOLFCLU_MAX_CERT_DAYS, "ParseDaysArg max"); + + days = -1; + CHECK(wolfCLU_ParseDaysArg("01", &days) == WOLFCLU_SUCCESS && days == 1, + "ParseDaysArg leading zero"); + CHECK(wolfCLU_ParseDaysArg(" 365", &days) == USER_INPUT_ERROR, + "ParseDaysArg leading space"); + CHECK(wolfCLU_ParseDaysArg("365 ", &days) == USER_INPUT_ERROR, + "ParseDaysArg trailing space"); +} + +static void testDerSetLength(void) +{ + byte out[8]; + word32 sz; + + /* size-only mode (output == NULL) */ + CHECK(wolfCLU_DerSetLength(0, NULL) == 1, "DerSetLength size-only 0"); + CHECK(wolfCLU_DerSetLength(127, NULL) == 1, "DerSetLength size-only 127"); + CHECK(wolfCLU_DerSetLength(128, NULL) == 2, "DerSetLength size-only 128"); + CHECK(wolfCLU_DerSetLength(255, NULL) == 2, "DerSetLength size-only 255"); + CHECK(wolfCLU_DerSetLength(256, NULL) == 3, "DerSetLength size-only 256"); + CHECK(wolfCLU_DerSetLength(65535, NULL) == 3, + "DerSetLength size-only 65535"); + CHECK(wolfCLU_DerSetLength(65536, NULL) == 4, + "DerSetLength size-only 65536"); + + /* short-form: length < 0x80 encodes as a single byte */ + XMEMSET(out, 0, sizeof(out)); + sz = wolfCLU_DerSetLength(0, out); + CHECK(sz == 1 && out[0] == 0x00, "DerSetLength encode 0"); + + XMEMSET(out, 0, sizeof(out)); + sz = wolfCLU_DerSetLength(127, out); + CHECK(sz == 1 && out[0] == 0x7F, "DerSetLength encode 127"); + + /* long-form boundary: 128 requires 0x81 0x80 */ + XMEMSET(out, 0, sizeof(out)); + sz = wolfCLU_DerSetLength(128, out); + CHECK(sz == 2 && out[0] == 0x81 && out[1] == 0x80, + "DerSetLength encode 128"); + + XMEMSET(out, 0, sizeof(out)); + sz = wolfCLU_DerSetLength(255, out); + CHECK(sz == 2 && out[0] == 0x81 && out[1] == 0xFF, + "DerSetLength encode 255"); + + /* long-form boundary: 256 requires 0x82 0x01 0x00 */ + XMEMSET(out, 0, sizeof(out)); + sz = wolfCLU_DerSetLength(256, out); + CHECK(sz == 3 && out[0] == 0x82 && out[1] == 0x01 && out[2] == 0x00, + "DerSetLength encode 256"); + + XMEMSET(out, 0, sizeof(out)); + sz = wolfCLU_DerSetLength(65535, out); + CHECK(sz == 3 && out[0] == 0x82 && out[1] == 0xFF && out[2] == 0xFF, + "DerSetLength encode 65535"); + + /* long-form boundary: 65536 requires 0x83 0x01 0x00 0x00 */ + XMEMSET(out, 0, sizeof(out)); + sz = wolfCLU_DerSetLength(65536, out); + CHECK(sz == 4 && out[0] == 0x83 && out[1] == 0x01 && + out[2] == 0x00 && out[3] == 0x00, "DerSetLength encode 65536"); +} + +int main(void) +{ + WC_RNG rng; + RsaKey key; + WOLFSSL_X509* x509 = NULL; + byte* derBuf = NULL; + int derBufSz = 8192; + int outDerSz = 0; + + if (wolfCrypt_Init() != 0) { + printf("FAIL: wolfCrypt_Init\n"); + return 1; + } + + /* FIPS builds require the RNG seed source to be registered explicitly; + * wolfSSL_Init() does this for the main wolfCLU binary, but this test + * only calls wolfCrypt_Init(). */ +#ifdef WC_RNG_SEED_CB + wc_SetSeed_Cb(WC_GENERATE_SEED_DEFAULT); +#endif + + if (wc_InitRng(&rng) != 0) { + printf("FAIL: wc_InitRng\n"); + return 1; + } + + derBuf = (byte*)XMALLOC((size_t)derBufSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (derBuf == NULL) { + printf("FAIL: malloc derBuf\n"); + wc_FreeRng(&rng); + return 1; + } + + x509 = buildFixtureX509(&key, &rng, derBuf, derBufSz, &outDerSz); + if (x509 == NULL) { + printf("FAIL: could not build fixture X509\n"); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + return 1; + } + + testSetCertNameFieldByNid(); +#ifdef WOLFSSL_CERT_EXT + testExtHandledNid(); + testUnwrapX509Extensions(); +#endif + testAsn1TimeToCertDate(x509); + testCopyX509NameToCert(x509); +#ifdef WOLFSSL_ALT_NAMES + testCopyX509SanToCert(x509); + testCopyX509SanToCertWithSan(); +#endif +#ifdef WOLFSSL_CERT_EXT + testCopyX509ExtsToCert(x509); +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_CUSTOM_OID) && \ + defined(HAVE_OID_ENCODING) + testCopyX509ExtsToCertCustomExt(); +#endif +#endif + testX509FillCert(x509, &key); + testX509FillCertDateUnset(x509, &key); + testX509FillCertLeafKeyUsageMerge(); +#ifdef WOLFSSL_CERT_EXT + testX509FillCertExtKeyUsage(); + testX509FillCertCaExtKeyUsageDropped(); +#endif + testX509FillCertCaKeyUsageMask(); + testX509FillCertLeafKeyUsageNonRsa(); +#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) + testMakeAndSignCertDer(x509, &key); + testBuildAndSignNative(x509, &key); + testCertSignNative(x509, &key); +#endif + testReadFileToBuffer(); + testPathsRefEqual(); +#ifndef _WIN32 + testOpenOutAndKeyFile(); +#endif + testParseDaysArg(); + testDerSetLength(); + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); + + wolfCrypt_Cleanup(); + + if (fail == 0) { + printf("All cert_setup_unit_test tests passed.\n"); + } + else { + printf("%d cert_setup_unit_test test(s) FAILED.\n", fail); + } + + return fail ? 1 : 0; +} + +#else /* !WOLFSSL_CERT_GEN */ + +int main(void) +{ + printf("Skipping cert_setup_unit_test: WOLFSSL_CERT_GEN not enabled.\n"); + return 0; +} + +#endif /* WOLFSSL_CERT_GEN */ diff --git a/tests/x509/unit_include.am b/tests/x509/unit_include.am new file mode 100644 index 00000000..960cb60c --- /dev/null +++ b/tests/x509/unit_include.am @@ -0,0 +1,18 @@ +# vim:ft=automake +# included from top level Makefile.am +# All paths should be given relative to root directory +# +# Native C unit tests for tests/x509. Unlike tests/x509/include.am (Python +# CLI tests, only built if HAVE_PYTHON), these do not depend on Python and +# so are included unconditionally. + +check_PROGRAMS += tests/x509/cert_setup_unit_test + +tests_x509_cert_setup_unit_test_SOURCES = \ + tests/x509/cert_setup_unit_test.c \ + src/clu_log.c \ + src/x509/clu_cert_setup.c \ + src/x509/clu_parse.c \ + src/x509/clu_config.c \ + src/x509/clu_x509_sign.c \ + src/tools/clu_funcs.c diff --git a/tests/x509/x509-ca-test.py b/tests/x509/x509-ca-test.py index 50b7ed3c..8ac4550d 100644 --- a/tests/x509/x509-ca-test.py +++ b/tests/x509/x509-ca-test.py @@ -225,6 +225,28 @@ def test_ca_help(self): r = run_wolfssl("ca", "-help") self.assertEqual(r.returncode, 0, r.stderr) +class TestCAInvalidDays(unittest.TestCase): + """Negative tests for the -days argument on the ca command.""" + + def test_days_zero(self): + r = run_wolfssl("ca", "-days", "0") + self.assertNotEqual(r.returncode, 0) + self.assertIn("-days must be a positive integer", r.stderr + r.stdout) + + def test_days_negative(self): + r = run_wolfssl("ca", "-days", "-1") + self.assertNotEqual(r.returncode, 0) + self.assertIn("-days must be a positive integer", r.stderr + r.stdout) + + def test_days_non_numeric(self): + r = run_wolfssl("ca", "-days", "abc") + self.assertNotEqual(r.returncode, 0) + self.assertIn("-days must be a positive integer", r.stderr + r.stdout) + + def test_days_out_of_bounds(self): + r = run_wolfssl("ca", "-days", "9999999999") + self.assertNotEqual(r.returncode, 0) + self.assertIn("-days must be a positive integer", r.stderr + r.stdout) @unittest.skipIf(no_filesystem(), "filesystem support disabled") @@ -312,6 +334,74 @@ def test_selfsign_verify_fails_wrong_ca(self): +CA_TRUE_REQ_CONF = """\ +[ req ] +distinguished_name = req_distinguished_name +prompt = no +[ req_distinguished_name ] +countryName = US +commonName = testing +[ v3_alt_ca ] +basicConstraints = CA:TRUE +keyUsage = digitalSignature +""" + + +class TestCARejectsCsrCaTrue(unittest.TestCase): + """A CSR asserting CA:TRUE must not be able to grant itself CA status + when signed by a distinct CA with no -extensions override.""" + + @classmethod + def setUpClass(cls): + cls.ca_conf = _tmp("ca_catrue.conf") + with open(cls.ca_conf, "w", encoding="utf-8", newline="\n") as f: + f.write(CA_CONF) + cls.req_conf = _tmp("ca_catrue_req.conf") + with open(cls.req_conf, "w", encoding="utf-8", newline="\n") as f: + f.write(CA_TRUE_REQ_CONF) + _touch(_tmp("index.txt")) + cls.csr = _tmp("ca_catrue.csr") + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-config", cls.req_conf, "-extensions", "v3_alt_ca", + "-out", cls.csr) + assert r.returncode == 0, "CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.ca_conf, cls.req_conf, cls.csr, _tmp("index.txt")) + + def test_catrue_csr_neutralized_without_extensions(self): + """CA:TRUE CSR signed by a distinct CA, no -extensions: the request + is signed, but issued as CA:FALSE rather than refused.""" + out_name = "tmp_catrue_neutralized.pem" + out = _tmp(out_name) + self.addCleanup(lambda: _cleanup(out)) + r = run_wolfssl("ca", "-config", self.ca_conf, + "-in", self.csr, "-out", out_name, "-md", "sha256", + "-keyfile", os.path.join(CERTS_DIR, "ca-key.pem"), + "-cert", os.path.join(CERTS_DIR, "ca-cert.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + rt = run_wolfssl("x509", "-in", out, "-text", "-noout") + self.assertEqual(rt.returncode, 0, rt.stderr) + self.assertNotIn("CA:TRUE", rt.stdout + rt.stderr, + "a CSR's CA:TRUE must not survive signing without " + "an explicit -extensions override") + + def test_catrue_csr_accepted_with_selfsign(self): + """CA:TRUE CSR is trusted when the operator supplies both the + request and the signing key (-selfsign).""" + out_name = "tmp_catrue_selfsign.pem" + out = _tmp(out_name) + self.addCleanup(lambda: _cleanup(out)) + r = run_wolfssl("ca", "-config", self.ca_conf, + "-in", self.csr, "-out", out_name, "-md", "sha256", + "-selfsign", + "-keyfile", os.path.join(CERTS_DIR, "server-key.pem")) + self.assertEqual(r.returncode, 0, r.stderr) + + @unittest.skipIf(no_filesystem(), "filesystem support disabled") class TestCACreateAndVerify(unittest.TestCase): """ca certificate creation and verification.""" @@ -354,6 +444,46 @@ def test_create_and_verify(self): +BAD_KEYUSAGE_CONF = CA_CONF.replace( + "[ usr_cert ]\n", + "[ bad_ku ]\n\nbasicConstraints=CA:FALSE\n" + "keyUsage=digitalSignatureX\n\n[ usr_cert ]\n") + + +class TestCABadKeyUsage(unittest.TestCase): + """An unparsable keyUsage must fail rather than silently issuing a + certificate with no keyUsage at all.""" + + @classmethod + def setUpClass(cls): + cls.conf = _tmp("ca_bad_ku.conf") + with open(cls.conf, "w", encoding="utf-8", newline="\n") as f: + f.write(BAD_KEYUSAGE_CONF) + _cleanup(_tmp("index.txt")) + _touch(_tmp("index.txt")) + cls.csr = _tmp("ca_bad_ku.csr") + r = run_wolfssl("req", "-key", + os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", + "/O=wolfSSL/C=US/ST=MT/L=Bozeman/CN=wolfSSL/OU=org-unit", + "-out", cls.csr) + assert r.returncode == 0, "CSR creation failed: " + r.stderr + + @classmethod + def tearDownClass(cls): + _cleanup(cls.conf, cls.csr, _tmp("index.txt")) + + def test_unparsable_keyusage_fails(self): + out_name = "test_ca_bad_ku.pem" + out = _tmp(out_name) + self.addCleanup(lambda: _cleanup(out)) + r = run_wolfssl("ca", "-config", self.conf, "-in", self.csr, + "-out", out_name, "-extensions", "bad_ku") + self.assertNotEqual(r.returncode, 0, + "unparsable keyUsage was accepted; the issued " + "certificate would carry no keyUsage at all") + + @unittest.skipIf(no_filesystem(), "filesystem support disabled") class TestCAOverrideConfig(unittest.TestCase): """Override config options with command-line flags.""" @@ -397,6 +527,22 @@ def test_override_extensions_md_days_cert_keyfile(self): os.path.join(CERTS_DIR, "ca-ecc-key.pem")) self.assertEqual(r.returncode, 0, r.stderr) + def test_ca_days_validation(self): + """ca -days rejects anything outside [1, 36500] instead of using 0.""" + for bad in ("0", "-1", "abc", "12x", "36501", ""): + out_name = "test_ca_days.pem" + out = _tmp(out_name) + self._clean(out) + r = run_wolfssl("ca", "-config", self.conf, + "-in", self.csr, "-out", out_name, + "-days", bad, + "-cert", + os.path.join(CERTS_DIR, "ca-ecc-cert.pem"), + "-keyfile", + os.path.join(CERTS_DIR, "ca-ecc-key.pem")) + self.assertNotEqual(r.returncode, 0, + "-days {!r} should be rejected".format(bad)) + class TestCAKeyMismatch(unittest.TestCase): """ca with mismatched key should fail.""" @@ -735,6 +881,42 @@ def test_chimera_cert(self): "-cert", ca_chimera, "-out", server_chimera_name) self.assertEqual(r.returncode, 0, r.stderr) + def test_chimera_cert_oversized_subj_field(self): + """altextend on a CA cert whose subject DN field exceeds + CTC_NAME_SIZE must fail cleanly, not silently truncate.""" + ca_cert_name = "tmp_chimera_ca_oversized.pem" + ca_cert = _tmp(ca_cert_name) + self._clean(ca_cert) + + # 200 bytes exceeds CTC_NAME_SIZE (64 or 128, build-dependent); + # -subj reparsing must reject rather than truncate. + oversized_o = "O" * 200 + + r = run_wolfssl("req", "-new", "-x509", + "-key", os.path.join(CERTS_DIR, "ca-ecc-key.pem"), + "-subj", + "O={}/C=US/ST=WA/L=Seattle/CN=A/OU=org-unit-A" + .format(oversized_o), + "-out", ca_cert, "-outform", "PEM") + if r.returncode != 0: + # Some builds (e.g. Windows CI) enforce CTC_NAME_SIZE in the + # compat layer and reject earlier in req; fine too, it didn't + # truncate. + return + + r = run_wolfssl("ca", "-altextend", "-in", ca_cert, + "-keyfile", os.path.join(CERTS_DIR, "ca-ecc-key.pem"), + "-altkey", + os.path.join(CERTS_DIR, "ca-mldsa44-key.pem"), + "-altpub", + os.path.join(CERTS_DIR, "ca-mldsa44-keyPub.pem"), + "-out", "tmp_chimera_ca_oversized_chimera.pem") + self.addCleanup(lambda: _cleanup( + _tmp("tmp_chimera_ca_oversized_chimera.pem"))) + self.assertNotEqual(r.returncode, 0, + "altextend should reject an oversized subject DN field " + "instead of silently truncating it") + @unittest.skipIf(no_filesystem(), "filesystem support disabled") diff --git a/tests/x509/x509-req-test.py b/tests/x509/x509-req-test.py index e9839b43..a346fcbf 100644 --- a/tests/x509/x509-req-test.py +++ b/tests/x509/x509-req-test.py @@ -104,12 +104,9 @@ def _cleanup(*files): def _flip_last_der_byte(src, dst): - """Copy a DER file to dst with its final byte flipped. - - A CSR's DER encoding ends with the signature BIT STRING, so flipping - the last byte corrupts the signature value while leaving every ASN.1 - length intact: the request still parses, but the signature no longer - verifies.""" + """Copy src to dst with its final DER byte flipped -- corrupts the + trailing signature BIT STRING while leaving ASN.1 lengths intact, so + the CSR still parses but no longer verifies.""" with open(src, "rb") as f: data = bytearray(f.read()) assert len(data) > 0, "empty DER file" @@ -281,14 +278,10 @@ def _get_san_line(self, stdout): return None def test_req_inline_subjectaltname_openssl_compat(self): - """A config subjectAltName in the OpenSSL inline form (TYPE:value list, - not the @section indirection) is accepted and applied to the cert. - - Pins the behavior in wolfCLU_setExtensions (clu_config.c): the inline - form is parsed via the same path as -addext (wolfCLU_setInlineAltNames), - so an OpenSSL-style config is neither silently dropped nor rejected. - Whitespace after the comma must be tolerated. Skipped on builds without - cert extensions, where the parsing path is absent.""" + """A config subjectAltName in OpenSSL inline form (TYPE:value list, + not @section indirection) is parsed via the same path as -addext + (wolfCLU_setInlineAltNames in clu_config.c), not dropped or + rejected. Skipped where cert extensions aren't compiled in.""" conf = _tmp("test_req_inline_san.conf") out = _tmp("test_req_inline_san.crt") self._clean(conf, out) @@ -316,7 +309,6 @@ def test_req_inline_subjectaltname_openssl_compat(self): "accepted: " + combined) self.assertTrue(os.path.isfile(out) and os.path.getsize(out) > 0, "certificate should be written") - # The SAN must actually be present in the issued cert. r2 = run_wolfssl("x509", "-in", out, "-text", "-noout") self.assertEqual(r2.returncode, 0, r2.stderr) san_line = self._get_san_line(r2.stdout) @@ -327,10 +319,9 @@ def test_req_inline_subjectaltname_openssl_compat(self): "IP SAN not applied from inline config form") def test_req_inline_subjectaltname_trims_whitespace(self): - """Inline subjectAltName entries are trimmed of surrounding whitespace - like OpenSSL: whitespace BEFORE the comma (a trailing space on the - value) and AFTER the colon must not end up in the stored name. Pins the - trailing/leading trim in wolfCLU_setInlineAltNames (clu_config.c).""" + """Inline subjectAltName values are trimmed like OpenSSL: whitespace + before the comma and after the colon must not end up in the stored + name (wolfCLU_setInlineAltNames, clu_config.c).""" conf = _tmp("test_req_inline_san_ws.conf") out = _tmp("test_req_inline_san_ws.crt") self._clean(conf, out) @@ -362,8 +353,7 @@ def test_req_inline_subjectaltname_trims_whitespace(self): self.assertEqual(r2.returncode, 0, r2.stderr) san_line = self._get_san_line(r2.stdout) self.assertIsNotNone(san_line, "SAN not found in cert output") - # Extract the exact DNS value; a trailing space would make this - # "trim.example.com " and fail the equality (assertIn would not). + # assertEqual, not assertIn: a trailing space would still match assertIn. m = re.search(r"DNS:([^,]*)", san_line) self.assertIsNotNone(m, "DNS SAN missing: " + san_line) self.assertEqual(m.group(1), "trim.example.com", @@ -483,6 +473,18 @@ def test_req_addext_unsupported_alt_type_fails(self): self._addext_fails("subjectAltName=otherName:foo", "test_req_addext_badtype.crt") + def test_req_days_validation(self): + """req -days rejects anything outside [1, 36500] instead of using 0.""" + for bad in ("0", "-1", "abc", "12x", "36501", ""): + tmp = _tmp("test_req_days.cert") + self._clean(tmp) + r = run_wolfssl("req", "-new", "-days", bad, + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-subj", "O=wolfSSL/C=US/CN=wolfSSL", + "-out", tmp, "-x509") + self.assertNotEqual(r.returncode, 0, + "-days {!r} should be rejected".format(bad)) + @unittest.skipIf(no_filesystem(), "filesystem support disabled") class TestReqPemDerRoundTrip(unittest.TestCase): @@ -530,7 +532,7 @@ def test_pem_to_der_to_pem(self): @unittest.skipIf(no_filesystem(), "filesystem support disabled") class TestReqVerify(unittest.TestCase): - """Test req -verify, including that a tampered CSR fails (F-5363).""" + """Test req -verify, including that a tampered CSR fails.""" @classmethod def setUpClass(cls): @@ -560,7 +562,7 @@ def test_verify_good_csr(self): self.assertIn("verify OK", r.stdout + r.stderr) def test_verify_tampered_csr_der_fails(self): - """A CSR with a corrupted signature must fail verification (F-5363).""" + """A CSR with a corrupted signature must fail verification.""" bad = _tmp("test_req_verify_bad.der") self._clean(bad) _flip_last_der_byte(self.csr_der, bad) @@ -574,10 +576,9 @@ def test_verify_tampered_csr_der_fails(self): "{}".format(r.returncode)) def test_verify_tampered_csr_no_output(self): - """A failed verify must not emit the CSR even without -noout (F-5363). - - Output handling is gated on the verify result, so a tampered CSR - produces no PEM body on stdout.""" + """A failed verify must not emit the CSR even without -noout: + output is gated on the verify result, so a tampered CSR prints no + PEM body.""" bad = _tmp("test_req_verify_bad2.der") self._clean(bad) _flip_last_der_byte(self.csr_der, bad) @@ -880,6 +881,49 @@ def test_newkey_with_passout_keyout(self): stdin_data="long test password\n") self.assertEqual(r.returncode, 0, r.stderr) + def test_newkey_keyout_same_as_out_rejected(self): + """-out and -keyout naming the same file is rejected, not silently + overwritten (the second secure-file open would otherwise destroy + what the first just wrote).""" + if is_fips(): + self.skipTest("FIPS build") + same = _tmp("test_req_fips_same_out_keyout.pem") + self._clean(same) + r = run_wolfssl("req", "-newkey", "rsa:2048", "-keyout", same, + "-config", self.conf_file, "-out", same) + self.assertNotEqual(r.returncode, 0) + self.assertIn("-out and -keyout must not be the same file", + r.stderr + r.stdout) + + def test_newkey_keyout_same_as_out_relative_path_rejected(self): + """Same collision, caught even via a differently-spelled path + (relative vs. absolute) to the same existing file.""" + if is_fips(): + self.skipTest("FIPS build") + name = "test_req_fips_relpath_same.pem" + abs_path = _tmp(name) + with open(abs_path, "w", encoding="utf-8") as f: + f.write("placeholder\n") + self._clean(abs_path) + r = run_wolfssl("req", "-newkey", "rsa:2048", "-keyout", name, + "-config", self.conf_file, "-out", abs_path) + self.assertNotEqual(r.returncode, 0) + self.assertIn("-out and -keyout must not be the same file", + r.stderr + r.stdout) + + def test_keyout_same_as_out_without_newkey_accepted(self): + """-keyout is inert without -newkey (no key is ever written through + it), so naming the same path as -out is harmless and must not be + rejected by the -out/-keyout collision check.""" + if is_fips(): + self.skipTest("FIPS build") + same = _tmp("test_req_fips_same_out_keyout_no_newkey.pem") + self._clean(same) + r = run_wolfssl("req", "-new", "-x509", + "-key", os.path.join(CERTS_DIR, "server-key.pem"), + "-keyout", same, "-config", self.conf_file, + "-out", same) + self.assertEqual(r.returncode, 0, r.stderr) @unittest.skipIf(no_filesystem(), "filesystem support disabled") diff --git a/tests/x509/x509-verify-test.py b/tests/x509/x509-verify-test.py index 220badd3..fc04489d 100644 --- a/tests/x509/x509-verify-test.py +++ b/tests/x509/x509-verify-test.py @@ -114,11 +114,9 @@ def test_last_arg_not_misread(self): self.assertIn("Malformed argument", r.stdout + r.stderr) def test_partial_chain_no_cafile_no_crash(self): - """-partial_chain with -untrusted but no -CAfile must not crash. - - caCert is NULL in this path; the malformed-argument check must - tolerate that rather than dereferencing it. - """ + """-partial_chain with -untrusted but no -CAfile must not crash: + caCert is NULL here, and the malformed-argument check must + tolerate that rather than dereference it.""" r = run_wolfssl("verify", "-partial_chain", "-untrusted", os.path.join(CERTS_DIR, "server-cert.pem"), os.path.join(CERTS_DIR, "server-cert.pem")) diff --git a/wolfclu/clu_header_main.h b/wolfclu/clu_header_main.h index 8ac5c846..f309a5f9 100644 --- a/wolfclu/clu_header_main.h +++ b/wolfclu/clu_header_main.h @@ -620,6 +620,17 @@ void wolfCLU_ForceZero(void* mem, unsigned int len); */ word32 wolfCLU_DerSetLength(word32 length, byte* output); +/* RFC 5280-oriented upper bound for -days / default_days (~100 years). */ +#ifndef WOLFCLU_MAX_CERT_DAYS +#define WOLFCLU_MAX_CERT_DAYS 36500 +#endif + +/** + * @brief Parse a decimal -days argument into [1, WOLFCLU_MAX_CERT_DAYS]. + * Returns WOLFCLU_SUCCESS or USER_INPUT_ERROR. + */ +int wolfCLU_ParseDaysArg(const char* arg, int* daysOut); + /* * These helpers deliberately work in terms of FILE* and POSIX/Win32 file * descriptors rather than wolfSSL's XFILE/XFOPEN porting macros: the @@ -956,6 +967,43 @@ int wolfCLU_ReadCertDer(const char* filename, byte** outDer); */ int wolfCLU_GetStdinPassword(byte* password, word32* passwordSz); +/** + * @brief PEM-encode a DER buffer into a newly allocated buffer + * @param der DER input + * @param derSz size of der + * @param type wolfSSL PEM type, e.g. CERT_TYPE + * @param outBuf receives the PEM buffer; caller must XFREE it + * @param outBufSz receives the PEM size + * @return WOLFCLU_SUCCESS on success + */ +int wolfCLU_DerToPemBuf(const byte* der, int derSz, int type, byte** outBuf, + int* outBufSz); + +/** + * @brief PEM-encode a DER key into a newly allocated buffer + * @param der DER input + * @param derSz size of der + * @param out receives the PEM buffer; caller must XFREE it + * @param pemType wolfSSL PEM type, e.g. PRIVATEKEY_TYPE + * @param heapType dynamic type used for the allocation + * @return size of the PEM buffer on success, 0 or negative on failure + */ +int wolfCLU_KeyDerToPem(const byte* der, int derSz, byte** out, int pemType, + int heapType); + +/** + * @brief Write a DER buffer to a BIO, PEM-encoding it first when outForm is + * PEM_FORM + * @param bioOut destination BIO + * @param outForm PEM_FORM or DER_FORM + * @param derBuf DER input + * @param derBufSz size of derBuf + * @param type wolfSSL PEM type used when outForm is PEM_FORM + * @return WOLFCLU_SUCCESS on success + */ +int wolfCLU_WriteCertBio(WOLFSSL_BIO* bioOut, int outForm, const byte* derBuf, + int derBufSz, int type); + #ifdef __cplusplus } #endif diff --git a/wolfclu/genkey/clu_genkey.h b/wolfclu/genkey/clu_genkey.h index a670c5ef..bf9d2573 100644 --- a/wolfclu/genkey/clu_genkey.h +++ b/wolfclu/genkey/clu_genkey.h @@ -100,13 +100,14 @@ int wolfCLU_genKey_RSA(WC_RNG* rng, char* fName, int directive, int fmt, int wolfCLU_genKey_PWDBASED(WC_RNG* rng, byte* pwdKey, int size, byte* salt, int pad); -void wolfCLU_EcparamPrintOID(WOLFSSL_BIO* out, WOLFSSL_EC_KEY* key, +/* returns WOLFCLU_SUCCESS on success, WOLFCLU_FATAL_ERROR on failure */ +int wolfCLU_EcparamPrintOID(WOLFSSL_BIO* out, WOLFSSL_EC_KEY* key, int fmt); WOLFSSL_EC_KEY* wolfCLU_GenKeyECC(char* name); -int wolfCLU_KeyDerToPem(const byte* der, int derSz, byte** out, int pemType, - int heapType); +/* wolfCLU_KeyDerToPem() lives in clu_funcs.c and is declared in + * clu_header_main.h. */ /** * generate a Dilithium key diff --git a/wolfclu/sign-verify/clu_sign.h b/wolfclu/sign-verify/clu_sign.h index 86ff9205..1ed9dd4e 100644 --- a/wolfclu/sign-verify/clu_sign.h +++ b/wolfclu/sign-verify/clu_sign.h @@ -35,7 +35,8 @@ #ifdef HAVE_DILITHIUM #include /* Fallback for older wolfSSL builds predating this constant; 10267 is - * the largest possible key PEM size (DILITHIUM_LEVEL5_BOTH_KEY_PEM_SIZE). */ + * the largest possible key PEM size + * (DILITHIUM_LEVEL5_BOTH_KEY_PEM_SIZE). */ #ifndef DILITHIUM_MAX_BOTH_KEY_PEM_SIZE #define DILITHIUM_MAX_BOTH_KEY_PEM_SIZE 10267 #endif @@ -44,6 +45,35 @@ #include #endif +/* Upper bound (256MB) on file size */ +#ifndef WOLFCLU_MAX_FILE_SIZE +#define WOLFCLU_MAX_FILE_SIZE 0xFFFFFFF +#endif /* WOLFCLU_MAX_FILE_SIZE */ + +/* Cap for a classical key file read. The key-type-derived bound can come out + * very small (e.g. 512 bytes when MAX_ECC_BITS_NEEDED resolves to 256), which + * would reject a valid PEM carrying surrounding text, such as the output of + * `openssl pkey -text`. Never cap below WOLFCLU_MIN_KEY_FILE_SIZE. */ +#ifndef WOLFCLU_MIN_KEY_FILE_SIZE +#define WOLFCLU_MIN_KEY_FILE_SIZE 4096 +#endif +#define WOLFCLU_KEY_FILE_CAP(sz) \ + ((long)((sz) > WOLFCLU_MIN_KEY_FILE_SIZE ? (sz) : WOLFCLU_MIN_KEY_FILE_SIZE)) + +/* Shared cap for PQ key file reads (Dilithium/ML-DSA, XMSS/XMSSMT). */ +#ifndef WOLFCLU_MAX_PQ_KEY_PEM_SIZE + /* WC_MLDSA_87_* is the newer spelling and is checked first: the + * DILITHIUM_* arm below always matches under HAVE_DILITHIUM, since it + * has a fallback definition above. */ + #ifdef WC_MLDSA_87_BOTH_KEY_PEM_SIZE + #define WOLFCLU_MAX_PQ_KEY_PEM_SIZE WC_MLDSA_87_BOTH_KEY_PEM_SIZE + #elif defined(DILITHIUM_MAX_BOTH_KEY_PEM_SIZE) + #define WOLFCLU_MAX_PQ_KEY_PEM_SIZE DILITHIUM_MAX_BOTH_KEY_PEM_SIZE + #else + #define WOLFCLU_MAX_PQ_KEY_PEM_SIZE 16384 + #endif +#endif /* WOLFCLU_MAX_PQ_KEY_PEM_SIZE */ + enum { RSA_SIG_VER, ECC_SIG_VER, @@ -53,14 +83,25 @@ enum { XMSSMT_SIG_VER, }; -int wolfCLU_sign_data(char*, char*, char*, int, int); +int wolfCLU_sign_data(char* in, char* out, char* privKey, int keyType, + int inForm); -int wolfCLU_sign_data_rsa(byte*, char*, word32, char*, int); -int wolfCLU_sign_data_ecc(byte*, char*, word32, char*, int); -int wolfCLU_sign_data_ed25519(byte*, char*, word32, char*, int); -int wolfCLU_sign_data_dilithium (byte*, char*, word32, char*, int); -int wolfCLU_sign_data_xmss(byte*, char*, int, char*); -int wolfCLU_sign_data_xmssmt(byte*, char*, int, char*); +int wolfCLU_sign_data_rsa(byte* data, char* out, word32 dataSz, char* privKey, + int inForm); +int wolfCLU_sign_data_ecc(byte* data, char* out, word32 fSz, char* privKey, + int inForm); +int wolfCLU_sign_data_ed25519(byte* data, char* out, word32 fSz, + char* privKey, int inForm); +int wolfCLU_sign_data_dilithium(byte* data, char* out, word32 dataSz, + char* privKey, int inForm); +int wolfCLU_sign_data_xmss(byte* data, char* out, int fSz, char* privKey); +int wolfCLU_sign_data_xmssmt(byte* data, char* out, int fSz, char* privKey); int wolfCLU_KeyPemToDer(unsigned char** pkeyBuf, int pkeySz, int pubIn); + +/* Same as wolfCLU_KeyPemToDer, but treats ASN_NO_PEM_HEADER as "already DER" + * instead of an error, logging either way. Returns 0 on success and updates + * *pkeySz when a conversion resized the buffer. */ +int wolfCLU_KeyPemToDerFallback_ex(unsigned char** pkeyBuf, int* pkeySz, + int pubIn); diff --git a/wolfclu/x509/clu_cert.h b/wolfclu/x509/clu_cert.h index 5256ae17..271a2c90 100644 --- a/wolfclu/x509/clu_cert.h +++ b/wolfclu/x509/clu_cert.h @@ -19,6 +19,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#include +#include #include #include @@ -26,9 +28,80 @@ #define DER_FORM 2 #define RAW_FORM 3 +/* Unified key context for X.509 operations. + * + * wolfSSL's EVP_PKEY cannot represent every key type wolfCLU signs with, so + * the context carries either an EVP_PKEY or a raw wolfCrypt key together + * with its type and, for algorithms that have one, its parameter set. The + * raw slot is deliberately opaque: an algorithm adds support by loading into + * 'key'/'keyType' and supplying 'keyFree', without this header growing a + * per-algorithm member. */ +typedef struct CLU_KEY_CTX { + WOLFSSL_EVP_PKEY* evp; /* set when the key fits the EVP API */ + void* key; /* raw wolfCrypt key, when evp is NULL */ + int keyType; /* *k OID enum describing 'key' */ + byte level; /* parameter set, where the algorithm has one */ + void (*keyFree)(void** key);/* frees 'key'; set by whoever loaded it */ +} CLU_KEY_CTX; + +/* Load a private key from 'file' into 'ctx'. Tries the EVP path first. + * Returns WOLFCLU_SUCCESS, or USER_INPUT_ERROR when no loader accepts it. */ +int wolfCLU_LoadKey(const char* file, CLU_KEY_CTX* ctx); + +/* Release whatever 'ctx' holds. Safe to call on a zeroed context. */ +int wolfCLU_FreeKeyCtx(CLU_KEY_CTX* ctx); + +#if defined(WOLFSSL_CERT_EXT) +/* Set (non-critical) basicConstraints CA:TRUE or CA:FALSE on x509. */ +int wolfCLU_SetBasicConstraintsCA(WOLFSSL_X509* x509, int ca); +#endif + /* handles incoming arguments for certificate generation */ int wolfCLU_certSetup(int argc, char** argv); /* print help info */ void wolfCLU_certHelp(void); + +#ifdef WOLFSSL_CERT_GEN +int wolfCLU_CopyX509NameToCert(WOLFSSL_X509_NAME* name, CertName* dst); +int wolfCLU_SetCertNameFieldByNid(CertName* dst, int nid, const char* val, + int valLen); +int wolfCLU_Asn1TimeToCertDate(byte* out, int outSz, + const WOLFSSL_ASN1_TIME* t); + +#if defined(WOLFSSL_ALT_NAMES) +int wolfCLU_CopyX509SanToCert(WOLFSSL_X509* x509, Cert* cert); +#endif /* WOLFSSL_ALT_NAMES */ + +#ifdef WOLFSSL_CERT_EXT +int wolfCLU_ExtHandledNid(int nid); +int wolfCLU_CopyX509ExtsToCert(WOLFSSL_X509* x509, Cert* cert, + int* extsDropped); +int wolfCLU_FreeCertCustomExts(Cert* cert); + +/* Extracts raw Extensions DER for testing. */ +int wolfCLU_UnwrapX509Extensions(const byte** extensions, + int* extensionsSz); +#endif /* WOLFSSL_CERT_EXT */ + +int wolfCLU_X509FillCert(WOLFSSL_X509* x509, Cert* cert, int sigType, + void* subjWcKey, int subjWcKeyType, + void* caWcKey, int caWcKeyType, WOLFSSL_X509* caCert, + int policySanitized, int* extsDropped); +#define WOLFCLU_CERT_DAYS_DEFAULT 365 + +#ifdef WOLFSSL_CERT_EXT +int wolfCLU_BuildAndSignNative(void* key, int keyType, int sigType, int bufSz, + WOLFSSL_X509* x509, int days, int isCSR, int outForm, + WOLFSSL_BIO* bioOut, int noOut); + +/* Shared build+sign core for wolfCLU_BuildAndSignNative()/CertSignNative(). + * days < 0 leaves cert->daysValid untouched. */ +int wolfCLU_MakeAndSignCertDer(WOLFSSL_X509* x509, int isCSR, int sigType, + int bufSz, void* subjKey, int subjKeyType, void* caKey, int caKeyType, + WOLFSSL_X509* caCert, int policySanitized, int days, + byte** outDer, int* outDerSz); +#endif /* WOLFSSL_CERT_EXT */ + +#endif /* WOLFSSL_CERT_GEN */ diff --git a/wolfclu/x509/clu_x509_sign.h b/wolfclu/x509/clu_x509_sign.h index 67ab5d1a..fe04afce 100644 --- a/wolfclu/x509/clu_x509_sign.h +++ b/wolfclu/x509/clu_x509_sign.h @@ -36,6 +36,12 @@ int wolfCLU_GenChimeraCertSign(WOLFSSL_BIO *bioCaKey, WOLFSSL_BIO *bioAltCaKey, void wolfCLU_CertSignSetHash(WOLFCLU_CERT_SIGN* csign, enum wc_HashType hashType); void wolfCLU_CertSignSetDate(WOLFCLU_CERT_SIGN* csign, int d); +#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_CERT_EXT) +int wolfCLU_CertSignNative(WOLFSSL_X509* x509, void* caKey, int caKeyType, + int sigType, int bufSz, WOLFSSL_X509* caCert, int outForm, + byte** outData, int* outDataSz, int policySanitized, + void* subjKey, int subjKeyType); +#endif /* WOLFSSL_CERT_GEN && WOLFSSL_CERT_EXT */ int wolfCLU_CertSign(WOLFCLU_CERT_SIGN* csign, WOLFSSL_X509* x509); WOLFCLU_CERT_SIGN* wolfCLU_readSignConfig(char* config, char* sect); int wolfCLU_CertSignAppendOut(WOLFCLU_CERT_SIGN* csign, char* out);