From 8be5533d3be8fb92e3d74e51c1676ab2ce129b16 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Wed, 15 Jul 2026 16:00:29 -0600 Subject: [PATCH] Extract generic X.509 certificate helpers and secure file utilities --- .gitignore | 1 + Makefile.am | 1 + src/crypto/clu_crypto_setup.c | 79 +- src/genkey/clu_genkey.c | 133 ++- src/ocsp/clu_ocsp.c | 6 +- src/pkcs/clu_pkcs12.c | 20 +- src/pkey/clu_pkey.c | 4 +- src/pkey/clu_rsa.c | 22 +- src/sign-verify/clu_dgst_setup.c | 16 + src/sign-verify/clu_sign.c | 163 +--- src/tools/clu_funcs.c | 163 +++- src/x509/clu_cert_setup.c | 569 +++++++++++++ tests/dgst/dgst-test.py | 17 + tests/encrypt/enc-test.py | 6 +- tests/genkey_sign_ver/genkey-sign-ver-test.py | 76 ++ tests/x509/cert_setup_unit_test.c | 806 ++++++++++++++++++ tests/x509/unit_include.am | 17 + wolfclu/clu_header_main.h | 8 + wolfclu/x509/clu_cert.h | 23 + 19 files changed, 1870 insertions(+), 260 deletions(-) create mode 100644 tests/x509/cert_setup_unit_test.c create mode 100644 tests/x509/unit_include.am diff --git a/.gitignore b/.gitignore index 33595f5b..06024e00 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ CLAUDE.md /serial-file-test /rand-file-test manpages/*.1.gz +tests/x509/cert_setup_unit_test diff --git a/Makefile.am b/Makefile.am index 6ef17b86..c1454fe9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -89,6 +89,7 @@ endif include src/include.am include wolfclu/include.am +include tests/x509/unit_include.am if HAVE_PYTHON include tests/dh/include.am include tests/dsa/include.am diff --git a/src/crypto/clu_crypto_setup.c b/src/crypto/clu_crypto_setup.c index 37be03c2..7ac217ad 100644 --- a/src/crypto/clu_crypto_setup.c +++ b/src/crypto/clu_crypto_setup.c @@ -528,6 +528,42 @@ int wolfCLU_setup(int argc, char** argv, char action) } } + if (encCheck == 1 && decCheck == 1) { + WOLFCLU_LOG(WOLFCLU_E0, + "Encrypt and decrypt simultaneously is invalid"); + wolfCLU_freeBins(pwdKey, iv, key, NULL, NULL); + return WOLFCLU_FATAL_ERROR; + } + + if (inCheck == 0 && decCheck == 1) { + wolfCLU_LogError("File/string to decrypt needed"); + wolfCLU_freeBins(pwdKey, iv, key, NULL, NULL); + return WOLFCLU_FATAL_ERROR; + } + + if (ivCheck == 1) { + if (keyCheck == 0) { + WOLFCLU_LOG(WOLFCLU_E0, + "-iv was explicitly set, but no -key or -inkey was" + " provided. A non-password based key must be supplied" + " when setting the -iv flag."); + wolfCLU_freeBins(pwdKey, iv, key, NULL, NULL); + return WOLFCLU_FATAL_ERROR; + } + } + + /* When the user supplies an explicit -key/-inkey, no salt-based + * key/iv derivation runs. The cipher therefore needs an explicit -iv: + * silently using the all-zero buffer would produce ciphertext that no + * one (including this tool on a later run) can decrypt safely. */ + if (keyCheck == 1 && ivCheck == 0) { + WOLFCLU_LOG(WOLFCLU_E0, + "-key/-inkey requires -iv to be set: an IV must be" + " supplied alongside an explicit key."); + wolfCLU_freeBins(pwdKey, iv, key, NULL, NULL); + return WOLFCLU_FATAL_ERROR; + } + if (pwdKeyChk == 0 && keyCheck == 0) { if (decCheck == 1) { WOLFCLU_LOG(WOLFCLU_L0, "\nDECRYPT ERROR:"); @@ -535,6 +571,9 @@ int wolfCLU_setup(int argc, char** argv, char action) WOLFCLU_LOG(WOLFCLU_L0, "Please type \"wolfssl -decrypt -help\" for decryption" " usage \n"); + wolfCLU_freeBins(pwdKey, iv, key, NULL, NULL); + if (mode != NULL) + XFREE(mode, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); return WOLFCLU_FATAL_ERROR; } /* if no pwdKey is provided */ @@ -548,6 +587,12 @@ int wolfCLU_setup(int argc, char** argv, char action) "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, NULL, NULL); + if (mode != NULL) + XFREE(mode, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return WOLFCLU_FATAL_ERROR; + } pwdKeyChk = 1; } } @@ -566,41 +611,7 @@ int wolfCLU_setup(int argc, char** argv, char action) inCheck = 1; } - if (encCheck == 1 && decCheck == 1) { - WOLFCLU_LOG(WOLFCLU_E0, - "Encrypt and decrypt simultaneously is invalid"); - wolfCLU_freeBins(pwdKey, iv, key, NULL, NULL); - return WOLFCLU_FATAL_ERROR; - } - - if (inCheck == 0 && decCheck == 1) { - wolfCLU_LogError("File/string to decrypt needed"); - wolfCLU_freeBins(pwdKey, iv, key, NULL, NULL); - return WOLFCLU_FATAL_ERROR; - } - - if (ivCheck == 1) { - if (keyCheck == 0) { - WOLFCLU_LOG(WOLFCLU_E0, - "-iv was explicitly set, but no -key or -inkey was" - " provided. A non-password based key must be supplied" - " when setting the -iv flag."); - wolfCLU_freeBins(pwdKey, iv, key, NULL, NULL); - return WOLFCLU_FATAL_ERROR; - } - } - /* When the user supplies an explicit -key/-inkey, no salt-based - * key/iv derivation runs. The cipher therefore needs an explicit -iv: - * silently using the all-zero buffer would produce ciphertext that no - * one (including this tool on a later run) can decrypt safely. */ - if (keyCheck == 1 && ivCheck == 0) { - WOLFCLU_LOG(WOLFCLU_E0, - "-key/-inkey requires -iv to be set: an IV must be" - " supplied alongside an explicit key."); - wolfCLU_freeBins(pwdKey, iv, key, NULL, NULL); - return WOLFCLU_FATAL_ERROR; - } if (pwdKeyChk == 1 && keyCheck == 1) { XMEMSET(pwdKey, 0, keySize + block); diff --git a/src/genkey/clu_genkey.c b/src/genkey/clu_genkey.c index e129096d..a920e24f 100644 --- a/src/genkey/clu_genkey.c +++ b/src/genkey/clu_genkey.c @@ -31,6 +31,70 @@ #include #include #include /* PER_FORM/DER_FORM */ +/* Opens path for writing owner-only, replacing any existing file. */ +#ifdef _WIN32 +#include +#include +#include +#include +#pragma comment(lib, "advapi32.lib") + +static FILE* wolfCLU_OpenKeyFile(const char* path) +{ + SECURITY_ATTRIBUTES sa; + PSECURITY_DESCRIPTOR pSD = NULL; + HANDLE hFile; + int fd; + FILE* f = NULL; + const char* sddl = "D:P(A;;FA;;;OW)"; + + (void)_unlink(path); + if (ConvertStringSecurityDescriptorToSecurityDescriptorA(sddl, SDDL_REVISION_1, &pSD, NULL)) { + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = FALSE; + sa.lpSecurityDescriptor = pSD; + + hFile = CreateFileA(path, GENERIC_WRITE, 0, &sa, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + fd = _open_osfhandle((intptr_t)hFile, _O_WRONLY | _O_CREAT | _O_TRUNC); + if (fd != -1) { + f = _fdopen(fd, "wb"); + } + if (f == NULL) { + if (fd != -1) _close(fd); + else CloseHandle(hFile); + (void)_unlink(path); + } + } + LocalFree(pSD); + } + return f; +} +#else +#include +#include +#ifndef O_NOFOLLOW + #define O_NOFOLLOW 0 +#endif +static FILE* wolfCLU_OpenKeyFile(const char* path) +{ + int fd; + FILE* f; + /* Ignore the result (including ENOENT if the file doesn't exist yet); + * this just ensures the O_EXCL open below gets a fresh inode. */ + (void)unlink(path); + fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW, 0600); + if (fd < 0) + return NULL; + f = fdopen(fd, "wb"); + if (f == NULL) { + close(fd); + (void)unlink(path); /* remove the stray empty file we just created */ + } + return f; +} +#endif /* _WIN32 */ + #ifdef HAVE_ED25519 /* return WOLFCLU_SUCCESS on success */ @@ -121,7 +185,7 @@ int wolfCLU_genKey_ED25519(WC_RNG* rng, char* fOutNm, int directive, int format) /* open the file for writing the private key */ if (ret == 0) { - file = XFOPEN(finalOutFNm, "wb"); + file = wolfCLU_OpenKeyFile(finalOutFNm); if (!file) { ret = OUTPUT_FILE_ERROR; } @@ -663,11 +727,22 @@ int wolfCLU_GenAndOutput_ECC(WC_RNG* rng, char* fName, int directive, fOutNameBuf[fNameSz + fExtSz] = '\0'; WOLFCLU_LOG(WOLFCLU_L0, "Private key file = %s", fOutNameBuf); - bioPri = wolfSSL_BIO_new_file(fOutNameBuf, "wb"); - if (bioPri == NULL) { - wolfCLU_LogError("unable to read outfile %s", - fOutNameBuf); - ret = MEMORY_E; + { + FILE* f = wolfCLU_OpenKeyFile(fOutNameBuf); + if (f == NULL) { + wolfCLU_LogError("unable to read outfile %s", + fOutNameBuf); + ret = MEMORY_E; + } + else { + bioPri = wolfSSL_BIO_new_fp(f, BIO_CLOSE); + if (bioPri == NULL) { + XFCLOSE(f); + wolfCLU_LogError("unable to read outfile %s", + fOutNameBuf); + ret = MEMORY_E; + } + } } } @@ -849,7 +924,7 @@ int wolfCLU_genKey_RSA(WC_RNG* rng, char* fName, int directive, int fmt, int /* open the file for writing the private key */ if (ret == WOLFCLU_SUCCESS) { - file = XFOPEN(fOutNameBuf, "wb"); + file = wolfCLU_OpenKeyFile(fOutNameBuf); if (!file) { ret = OUTPUT_FILE_ERROR; } @@ -1160,10 +1235,10 @@ 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 = XFOPEN(fOutNameBuf, "wb"); - if (file == XBADFILE) { + file = wolfCLU_OpenKeyFile(fOutNameBuf); + if (file == NULL) { wolfCLU_LogError("unable to open file %s", fOutNameBuf); ret = OUTPUT_FILE_ERROR; @@ -1171,7 +1246,7 @@ 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 ((int)XFWRITE(outBuf, 1, outBufSz, file) != outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1187,7 +1262,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; } @@ -1238,7 +1313,7 @@ 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 ((int)XFWRITE(outBuf, 1, outBufSz, file) != outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1260,7 +1335,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) { @@ -1408,10 +1483,10 @@ 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 = XFOPEN(fOutNameBuf, "wb"); - if (file == XBADFILE) { + file = wolfCLU_OpenKeyFile(fOutNameBuf); + if (file == NULL) { wolfCLU_LogError("unable to open file %s", fOutNameBuf); ret = OUTPUT_FILE_ERROR; @@ -1419,7 +1494,7 @@ 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 ((int)XFWRITE(outBuf, 1, outBufSz, file) != outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1435,7 +1510,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; } @@ -1491,7 +1566,7 @@ 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 ((int)XFWRITE(outBuf, 1, outBufSz, file) != outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1514,7 +1589,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) { @@ -1563,8 +1638,22 @@ enum wc_XmssRc wolfCLU_XmssKey_WriteCb(const byte * priv, /* Open file for read and write. */ file = fopen(filename, "rb+"); if (!file) { - /* Create the file if it didn't exist. */ + /* Create the file if it didn't exist. XMSS repeatedly reopens and + * updates this state file in place, so it can't go through + * wolfCLU_OpenKeyFile's O_EXCL create-and-replace pattern; lock + * down permissions right after creation instead. */ +#ifndef _WIN32 + { + int fd = open(filename, O_CREAT | O_EXCL | O_RDWR | O_NOFOLLOW, 0600); + if (fd >= 0) { + file = fdopen(fd, "wb+"); + if (!file) + close(fd); + } + } +#else file = fopen(filename, "wb+"); +#endif if (!file) { fprintf(stderr, "error: fopen(%s, \"w+\") failed.\n", filename); return WC_XMSS_RC_WRITE_FAIL; diff --git a/src/ocsp/clu_ocsp.c b/src/ocsp/clu_ocsp.c index 48c8d420..67dadb8e 100644 --- a/src/ocsp/clu_ocsp.c +++ b/src/ocsp/clu_ocsp.c @@ -858,10 +858,10 @@ static int ocspResponder(OcspResponderConfig* config) if (transportSendResponse(clientfd, transportType, respBuffer, (int)respSz) != 0) goto continue_loop; + /* Count only successful OCSP requests (not OCSP-level errors like + * malformed requests) to prevent a single bad client from easily + * exhausting the -nrequest budget. */ 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++; } diff --git a/src/pkcs/clu_pkcs12.c b/src/pkcs/clu_pkcs12.c index 7da29c5c..4f458e12 100644 --- a/src/pkcs/clu_pkcs12.c +++ b/src/pkcs/clu_pkcs12.c @@ -61,6 +61,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*/ @@ -97,6 +100,9 @@ int wolfCLU_PKCS12(int argc, char** argv) break; case WOLFCLU_PASSWORD_OUT: + passOutSz = MAX_PASSWORD_SIZE; + ret = wolfCLU_GetPassword(passOut, &passOutSz, optarg); + hasPassOut = 1; break; case WOLFCLU_INFILE: @@ -220,10 +226,15 @@ 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 { ret = wolfCLU_pKeyPEMtoPriKey(bioOut, pkey); @@ -235,6 +246,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 63018647..eb2608aa 100644 --- a/src/pkey/clu_pkey.c +++ b/src/pkey/clu_pkey.c @@ -95,7 +95,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); } } diff --git a/src/pkey/clu_rsa.c b/src/pkey/clu_rsa.c index 27d8c013..5c989a19 100644 --- a/src/pkey/clu_rsa.c +++ b/src/pkey/clu_rsa.c @@ -238,6 +238,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 { @@ -259,18 +262,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 { + wolfSSL_BIO_write(bioOut, der, derSz); + } } if (der != NULL) { - wolfCLU_ForceZero(der, derSz); + if (derSz > 0) { + wolfCLU_ForceZero(der, derSz); + } XFREE(der, HEAP_HINT, heapType); } } diff --git a/src/sign-verify/clu_dgst_setup.c b/src/sign-verify/clu_dgst_setup.c index 91e2d707..6c8ba633 100644 --- a/src/sign-verify/clu_dgst_setup.c +++ b/src/sign-verify/clu_dgst_setup.c @@ -686,6 +686,12 @@ int wolfCLU_dgst_setup(int argc, char** argv) } break; + case WOLFCLU_HELP: + wolfCLU_dgstHelp(); + wolfSSL_BIO_free(dataBio); + wolfSSL_BIO_free(pubKeyBio); + return WOLFCLU_SUCCESS; + case ':': case '?': break; @@ -701,6 +707,16 @@ int wolfCLU_dgst_setup(int argc, char** argv) ret = WOLFCLU_FATAL_ERROR; } + /* -hmac and -sign/-verify/-signature are mutually exclusive; -hmac + * silently wins dispatch below, so reject the combination instead of + * discarding those flags without telling the user. */ + 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; + } + /* 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*/ diff --git a/src/sign-verify/clu_sign.c b/src/sign-verify/clu_sign.c index 27201250..411258b2 100644 --- a/src/sign-verify/clu_sign.c +++ b/src/sign-verify/clu_sign.c @@ -180,7 +180,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; @@ -218,38 +217,9 @@ int wolfCLU_sign_data_rsa(byte* data, char* out, word32 dataSz, char* privKey, /* open, read, and store RSA key */ if (ret == 0) { - privKeyFile = XFOPEN(privKey, "rb"); - 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_ReadFileToBuffer(privKey, + (long)(RSA_MAX_SIZE / 8 * 16), &keyBuf, &privFileSz); + ret = (rfRet == WOLFCLU_SUCCESS) ? 0 : WOLFCLU_FATAL_ERROR; } /* convert PEM to DER if necessary */ @@ -312,10 +282,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, privFileSz); XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -344,7 +310,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; @@ -371,38 +336,9 @@ int wolfCLU_sign_data_ecc(byte* data, char* out, word32 fSz, char* privKey, /* open, read, and store ecc key */ if (ret == 0) { - privKeyFile = XFOPEN(privKey, "rb"); - 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_ReadFileToBuffer(privKey, + (long)(MAX_ECC_BITS_NEEDED / 8 * 16), &keyBuf, &privFileSz); + ret = (rfRet == WOLFCLU_SUCCESS) ? 0 : WOLFCLU_FATAL_ERROR; } /* convert PEM to DER if necessary */ @@ -489,10 +425,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, privFileSz); XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -520,7 +452,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; @@ -547,38 +478,9 @@ int wolfCLU_sign_data_ed25519 (byte* data, char* out, word32 fSz, char* privKey, /* open, read, and store ED25519 key */ if (ret == 0) { - privKeyFile = XFOPEN(privKey, "rb"); - 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_ReadFileToBuffer(privKey, + (long)(ED25519_PRV_KEY_SIZE * 16), &keyBuf, &privFileSz); + ret = (rfRet == WOLFCLU_SUCCESS) ? 0 : WOLFCLU_FATAL_ERROR; } /* convert PEM to DER if necessary */ @@ -654,10 +556,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, privFileSz); XFREE(keyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -684,7 +582,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; @@ -729,41 +626,10 @@ int wolfCLU_sign_data_dilithium (byte* data, char* out, word32 dataSz, char* pri /* open and read private key */ if (ret == 0) { - privKeyFile = XFOPEN(privKey, "rb"); - 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; - } - 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; - } + int rfRet = wolfCLU_ReadFileToBuffer(privKey, + (long)DILITHIUM_MAX_BOTH_KEY_PEM_SIZE, &privBuf, &privFileSz); + ret = (rfRet == WOLFCLU_SUCCESS) ? 0 : WOLFCLU_FATAL_ERROR; + privBufSz = (word32)privFileSz; } /* convert PEM to DER if necessary */ @@ -823,9 +689,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, privBufSz); XFREE(privBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/src/tools/clu_funcs.c b/src/tools/clu_funcs.c index 2ffb7d61..72e222da 100644 --- a/src/tools/clu_funcs.c +++ b/src/tools/clu_funcs.c @@ -1579,6 +1579,70 @@ void wolfCLU_ForceZero(void* mem, unsigned int len) while (len--) *z++ = 0; } +int wolfCLU_ReadFileToBuffer(const char* path, long maxSz, byte** outBuf, + int* outSz) +{ + int sz; + long fsz; + byte* buf = NULL; + XFILE f; + + if (path == NULL || outBuf == NULL || outSz == NULL || maxSz <= 0) { + return BAD_FUNC_ARG; + } + *outBuf = NULL; + *outSz = 0; + + f = XFOPEN(path, "rb"); + if (f == XBADFILE) { + wolfCLU_LogError("unable to open file %s", path); + return BAD_FUNC_ARG; + } + + if (XFSEEK(f, 0, XSEEK_END) != 0) { + XFCLOSE(f); + return WOLFCLU_FATAL_ERROR; + } + fsz = XFTELL(f); + if (XFSEEK(f, 0, XSEEK_SET) != 0) { + XFCLOSE(f); + return WOLFCLU_FATAL_ERROR; + } + if (fsz <= 0) { + wolfCLU_LogError("%s: file is empty or unreadable", path); + XFCLOSE(f); + return WOLFCLU_FATAL_ERROR; + } + if (fsz > maxSz || fsz > (long)INT_MAX) { + wolfCLU_LogError("%s: size %ld exceeds %ld-byte file limit", + path, fsz, maxSz); + XFCLOSE(f); + return WOLFCLU_FATAL_ERROR; + } + sz = (int)fsz; + + /* +1/NUL-terminate: matches other PEM-buffer readers in this codebase. */ + buf = (byte*)XMALLOC(sz + 1, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (buf == NULL) { + XFCLOSE(f); + return MEMORY_E; + } + + /* short/long read here catches a file that changed size after XFTELL. */ + if (XFREAD(buf, 1, (size_t)sz, f) != (size_t)sz) { + XFCLOSE(f); + wolfCLU_ForceZero(buf, sz); + XFREE(buf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return WOLFCLU_FAILURE; + } + buf[sz] = '\0'; + XFCLOSE(f); + + *outBuf = buf; + *outSz = sz; + return WOLFCLU_SUCCESS; +} + #ifndef WOLFCLU_NO_TERM_SUPPORT int wolfCLU_GetPassword(char* password, int* passwordSz, char* arg) @@ -1770,18 +1834,67 @@ int wolfCLU_GetOpt(int argc, char** argv, const char *options, } +/* Reads bioIn in MAX_IO_CHUNK_SZ chunks, calling update(updateCtx, chunk, + * bytesRead) on each one, until EOF or an error. Shared by + * wolfCLU_streamHashBio and wolfCLU_hmacHash so the read/error-handling + * logic isn't duplicated per hashing primitive. */ +static int wolfCLU_bioReadUpdate(WOLFSSL_BIO* bioIn, + int (*update)(void* updateCtx, const byte* data, word32 sz), + void* updateCtx) +{ + byte chunk[MAX_IO_CHUNK_SZ]; + int bytesRead; + int ret = WOLFCLU_SUCCESS; + + while (ret == WOLFCLU_SUCCESS) { + bytesRead = wolfSSL_BIO_read(bioIn, chunk, sizeof(chunk)); + if (bytesRead < 0) { + wolfCLU_LogError("Error reading data"); + ret = WOLFCLU_FATAL_ERROR; + break; + } + else if (bytesRead == 0) { + break; + } + if (update(updateCtx, chunk, (word32)bytesRead) != 0) { + wolfCLU_LogError("Hash update failed"); + ret = WOLFCLU_FATAL_ERROR; + } + } + + wolfCLU_ForceZero(chunk, sizeof(chunk)); + return ret; +} + +struct wolfCLU_hashUpdateCtx { + wc_HashAlg* hashAlg; + enum wc_HashType hashType; +}; + +static int wolfCLU_hashUpdateCb(void* updateCtx, const byte* data, word32 sz) +{ + struct wolfCLU_hashUpdateCtx* ctx = + (struct wolfCLU_hashUpdateCtx*)updateCtx; + return wc_HashUpdate(ctx->hashAlg, ctx->hashType, data, sz); +} + +static int wolfCLU_hmacUpdateCb(void* updateCtx, const byte* data, word32 sz) +{ + return (wolfSSL_HMAC_Update((WOLFSSL_HMAC_CTX*)updateCtx, data, sz) + == WOLFSSL_SUCCESS) ? 0 : WOLFCLU_FATAL_ERROR; +} + /* Stream-hash data read from bioIn using hashType and write the digest to * outDigest. On entry *outDigestSz is the capacity of outDigest; on success * it is updated to the actual digest length. */ int wolfCLU_streamHashBio(WOLFSSL_BIO* bioIn, enum wc_HashType hashType, byte* outDigest, word32* outDigestSz) { - byte chunk[MAX_IO_CHUNK_SZ]; wc_HashAlg hashAlg; + struct wolfCLU_hashUpdateCtx updateCtx; int hashInit = 0; - int bytesRead; int dsz; - int ret = WOLFCLU_SUCCESS; + int ret; if (bioIn == NULL || outDigest == NULL || outDigestSz == NULL) { return BAD_FUNC_ARG; @@ -1799,21 +1912,9 @@ int wolfCLU_streamHashBio(WOLFSSL_BIO* bioIn, enum wc_HashType hashType, } hashInit = 1; - while (ret == WOLFCLU_SUCCESS) { - bytesRead = wolfSSL_BIO_read(bioIn, chunk, sizeof(chunk)); - if (bytesRead < 0) { - wolfCLU_LogError("Error reading data"); - ret = WOLFCLU_FATAL_ERROR; - break; - } - else if (bytesRead == 0) { - break; - } - if (wc_HashUpdate(&hashAlg, hashType, chunk, (word32)bytesRead) != 0) { - wolfCLU_LogError("Hash update failed"); - ret = WOLFCLU_FATAL_ERROR; - } - } + updateCtx.hashAlg = &hashAlg; + updateCtx.hashType = hashType; + ret = wolfCLU_bioReadUpdate(bioIn, wolfCLU_hashUpdateCb, &updateCtx); if (ret == WOLFCLU_SUCCESS) { if (wc_HashFinal(&hashAlg, hashType, outDigest) != 0) { @@ -1849,7 +1950,12 @@ int wolfCLU_hmacHash(WOLFSSL_HMAC_CTX *ctx, void* key, word32 len, * Cast to int so unrelated hash types don't trip -Wswitch-enum. */ switch ((int)alg) { case WC_HASH_TYPE_MD5: + #ifndef NO_MD5 md = wolfSSL_EVP_md5(); + #else + wolfCLU_LogError("MD5 not compiled in"); + ret = WOLFCLU_FATAL_ERROR; + #endif break; case WC_HASH_TYPE_SHA: md = wolfSSL_EVP_sha1(); @@ -1879,24 +1985,7 @@ int wolfCLU_hmacHash(WOLFSSL_HMAC_CTX *ctx, void* key, word32 len, } if (ret == WOLFCLU_SUCCESS) { - int bytesRead = 0; - while (ret == WOLFCLU_SUCCESS) { - bytesRead = wolfSSL_BIO_read(in, chunk, sizeof(chunk)); - if (bytesRead < 0) { - wolfCLU_LogError("Error reading data"); - ret = WOLFCLU_FATAL_ERROR; - break; - } - else if (bytesRead == 0) { - break; - } - if (wolfSSL_HMAC_Update(ctx, chunk, (word32)bytesRead) - != WOLFSSL_SUCCESS) { - wolfCLU_LogError("Hash update failed"); - ret = WOLFCLU_FATAL_ERROR; - } - } - wc_ForceZero(chunk, sizeof(chunk)); + ret = wolfCLU_bioReadUpdate(in, wolfCLU_hmacUpdateCb, ctx); } if (ret == WOLFCLU_SUCCESS) { @@ -1917,6 +2006,6 @@ int wolfCLU_hmacHash(WOLFSSL_HMAC_CTX *ctx, void* key, word32 len, } } - wc_ForceZero(chunk, sizeof(chunk)); + wolfCLU_ForceZero(chunk, sizeof(chunk)); return ret; } diff --git a/src/x509/clu_cert_setup.c b/src/x509/clu_cert_setup.c index 6feab56a..456d5b15 100644 --- a/src/x509/clu_cert_setup.c +++ b/src/x509/clu_cert_setup.c @@ -874,3 +874,572 @@ int wolfCLU_certSetup(int argc, char** argv) return WOLFCLU_FATAL_ERROR; #endif } + + +#ifdef WOLFSSL_CERT_GEN + +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; + default: + break; + } + + if (field != NULL) { + 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; + + 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 <= CTC_DATE_SIZE (32), so t->length + 6 cannot overflow int. */ + if (t->length + 6 > outSz) { + return BUFFER_E; + } + + sz = (int)SetLength((word32)t->length, out + 1) + 1; + if (sz + t->length > outSz) { + return BUFFER_E; + } + + 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 CSR to cert. Returns WOLFCLU_SUCCESS or error. */ +#if defined(WOLFSSL_ALT_NAMES) +int wolfCLU_CopyX509SanToCert(WOLFSSL_X509* x509, Cert* cert) +{ + int extIdx; + + 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; + } + + extIdx = wolfSSL_X509_get_ext_by_NID(x509, NID_subject_alt_name, -1); + if (extIdx < 0) { + return WOLFCLU_SUCCESS; + } + +#if defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL) || defined(WOLFSSL_QT) + { + WOLFSSL_X509_EXTENSION* ext; + WOLFSSL_ASN1_STRING* sanData; + + ext = wolfSSL_X509_get_ext(x509, extIdx); + if (ext == NULL) { + wolfCLU_LogError("Failed to get subjectAltName extension"); + return WOLFCLU_FATAL_ERROR; + } + + sanData = wolfSSL_X509_EXTENSION_get_data(ext); + if (sanData == NULL || sanData->data == NULL || sanData->length <= 0) { + return WOLFCLU_SUCCESS; + } + + if (sanData->length > (int)sizeof(cert->altNames)) { + wolfCLU_LogError( + "subjectAltName extension too large for cert buffer"); + return WOLFCLU_FATAL_ERROR; + } + + XMEMCPY(cert->altNames, sanData->data, (size_t)sanData->length); + cert->altNamesSz = sanData->length; + } +#else + (void)extIdx; + /* wolfSSL_X509_get_ext requires OPENSSL_EXTRA. */ + wolfCLU_Log(WOLFCLU_L0, "Warning: subjectAltName not copied; build with " + "OPENSSL_EXTRA to preserve SANs in ML-DSA CA-signed certs"); +#endif /* OPENSSL_EXTRA || OPENSSL_ALL || WOLFSSL_QT */ + + return WOLFCLU_SUCCESS; +} +#endif /* WOLFSSL_ALT_NAMES */ + +#ifdef WOLFSSL_CERT_EXT +/* NIDs that wolfCLU_X509FillCert already transfers to the Cert explicitly, so + * the generic copy below must skip them to avoid duplicating an extension. */ +int wolfCLU_ExtHandledNid(int nid) +{ + switch (nid) { + case NID_basic_constraints: + case NID_key_usage: + case NID_ext_key_usage: + case NID_subject_key_identifier: + case NID_authority_key_identifier: + return 1; +#if defined(WOLFSSL_ALT_NAMES) && \ + (defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL) || defined(WOLFSSL_QT)) + /* wolfCLU_CopyX509SanToCert's actual copy logic is gated behind this + * same condition; only claim the SAN NID as "handled" when it can + * really be copied, so builds with WOLFSSL_ALT_NAMES but without + * OPENSSL_EXTRA/OPENSSL_ALL/WOLFSSL_QT fall through to the generic + * copier below instead of silently dropping the SAN. */ + case NID_subject_alt_name: + return 1; +#endif + default: + return 0; + } +} + +/* Carry CSR extensions that wolfCLU_X509FillCert does not handle explicitly + * onto the wolfcrypt Cert. */ +int wolfCLU_CopyX509ExtsToCert(WOLFSSL_X509* x509, Cert* cert) +{ + int ret = WOLFCLU_SUCCESS; + int count = wolfSSL_X509_get_ext_count(x509); + int i; + int uncopied = 0; + + for (i = 0; ret == WOLFCLU_SUCCESS && i < count; i++) { + WOLFSSL_X509_EXTENSION* ext = wolfSSL_X509_get_ext(x509, i); + WOLFSSL_ASN1_OBJECT* obj; + int nid; + + if (ext == NULL) { + continue; + } + obj = wolfSSL_X509_EXTENSION_get_object(ext); + if (obj == NULL) { + continue; + } + nid = wolfSSL_OBJ_obj2nid(obj); + if (wolfCLU_ExtHandledNid(nid)) { + continue; /* already copied explicitly by wolfCLU_X509FillCert */ + } + +#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_CUSTOM_OID) && \ + defined(HAVE_OID_ENCODING) + { + char oid[80]; + WOLFSSL_ASN1_STRING* data; + const unsigned char* val; + int valSz; + int crit; + + /* numerical (dotted-decimal) OID, the form wc_SetCustomExtension + * expects */ + if (wolfSSL_OBJ_obj2txt(oid, (int)sizeof(oid), obj, 1) <= 0) { + wolfCLU_Log(WOLFCLU_L0, + "Warning: could not encode an extension " + "OID; not copied to the certificate"); + uncopied = 1; + continue; + } + data = wolfSSL_X509_EXTENSION_get_data(ext); + if (data == NULL) { + uncopied = 1; + continue; + } + val = wolfSSL_ASN1_STRING_get0_data(data); + valSz = wolfSSL_ASN1_STRING_length(data); + if (val == NULL || valSz <= 0) { + uncopied = 1; + continue; + } + crit = wolfSSL_X509_EXTENSION_get_critical(ext); + /* wc_SetCustomExtension stores the oid/der pointers directly + * without copying, and "val" here points into x509's internal + * ASN1_STRING buffer. Heap-copy both so the resulting cert does + * not depend on x509 (the source CSR) outliving cert encoding; + * wolfCLU_FreeCertCustomExts() frees these once the caller is + * done with cert. */ + { + char* oidHeap = (char*)XMALLOC(XSTRLEN(oid) + 1, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + byte* valHeap = NULL; + + if (oidHeap == NULL) { + ret = MEMORY_E; + } + else { + valHeap = (byte*)XMALLOC((size_t)valSz, HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (valHeap == NULL) { + XFREE(oidHeap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + ret = MEMORY_E; + } + } + + if (ret == WOLFCLU_SUCCESS) { + XMEMCPY(oidHeap, oid, XSTRLEN(oid) + 1); + XMEMCPY(valHeap, val, (size_t)valSz); + if (wc_SetCustomExtension(cert, crit, oidHeap, valHeap, + (word32)valSz) < 0) { + wolfCLU_LogError("Failed to copy extension (OID %s) to the " + "ML-DSA certificate", oid); + XFREE(oidHeap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(valHeap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + ret = WOLFCLU_FATAL_ERROR; + } + } + } + } +#else + uncopied = 1; /* this build cannot copy arbitrary extensions */ +#endif /* WOLFSSL_ASN_TEMPLATE && WOLFSSL_CUSTOM_OID && \ */ + } + + if (ret == WOLFCLU_SUCCESS && uncopied) { + wolfCLU_Log(WOLFCLU_L0, + "Warning: the ML-DSA CA-sign path carries only " + "basicConstraints, keyUsage, extKeyUsage, " + "subjectKeyIdentifier, authorityKeyIdentifier and " + "subjectAltName; other CSR extensions were not copied to the " + "issued certificate (build wolfSSL with WOLFSSL_CUSTOM_OID + " + "HAVE_OID_ENCODING to carry arbitrary extensions)"); + } + + return ret; +} + +/* frees the oid/val buffers allocated by wolfCLU_CopyX509ExtsToCert; + * wc_SetCustomExtension stores both pointers directly without copying or + * freeing them, so the caller must invoke this once the Cert is done being + * used (after signing/encoding). */ +void wolfCLU_FreeCertCustomExts(Cert* cert) +{ +#ifdef WOLFSSL_CUSTOM_OID + int i; + + if (cert == NULL) { + return; + } + 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; + } + } +#else + (void)cert; +#endif /* WOLFSSL_CUSTOM_OID */ +} +#endif /* WOLFSSL_CERT_EXT */ + +/*Populate a wolfcrypt Cert from a CSR for ML-DSA CA signing. */ +int wolfCLU_X509FillCert(WOLFSSL_X509* x509, Cert* cert, int sigType, + void* subjWcKey, int subjWcKeyType, + void* caWcKey, int caWcKeyType, WOLFSSL_X509* caCert, + int policySanitized) +{ + int ret = WOLFCLU_SUCCESS; + int ku; + int isCA; + WOLFSSL_X509_NAME* name; + const WOLFSSL_ASN1_TIME* nb; + const WOLFSSL_ASN1_TIME* na; + + if (x509 == NULL || cert == 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 wolfSSL_X509_add_ext(). */ + 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)) { + cert->pathLen = wolfSSL_X509_get_pathLength(x509); + cert->pathLenSet = 1; + } + +#ifdef WOLFSSL_CERT_EXT + if (isCA) { + cert->keyUsage = KU_KEY_CERT_SIGN | KU_CRL_SIGN; + /* CSR keyUsage can only add bits on top of the CA-only default; it + * can never narrow or replace it, so a CSR cannot use this to grant + * itself extra key usages beyond what the CA default provides. */ + if (ku >= 0) { + cert->keyUsage |= (word16)ku; + } + } + else { + /* Only RSA keys can meaningfully be used for key encipherment as + * well as signing; every other supported key type here (ECDSA, + * Ed25519/Ed448, ML-DSA/Dilithium, Falcon) is signature-only. */ + /* subjWcKeyType is in wc_SetSubjectKeyIdFromPublicKey_ex()'s + * CertType space (RSA_TYPE/ECC_TYPE/...), not the RSAk OID-sum + * space, since it is also passed directly to that call below. */ + cert->keyUsage = (subjWcKeyType == RSA_TYPE) ? + (KU_DIGITAL_SIGNATURE | KU_KEY_ENCIPHERMENT) : + KU_DIGITAL_SIGNATURE; + /* CSR keyUsage can only add bits on top of the type-appropriate + * default (e.g. RSA always keeps keyEncipherment for compatibility, + * even if the CSR only asked for digitalSignature); it can never + * narrow it. CA-only bits are masked out so a leaf CSR cannot use + * this to grant itself keyCertSign/cRLSign. */ + if (ku >= 0) { + int leafKu = ku & ~(KU_KEY_CERT_SIGN | KU_CRL_SIGN); + if (leafKu > 0) + cert->keyUsage |= (word16)leafKu; + } + } + + { + /* wolfSSL_X509_get_extended_key_usage() returns WOLFSSL_XKU_* bits + * (x509v3). */ + unsigned int xku = wolfSSL_X509_get_extended_key_usage(x509); + byte eku = 0; + if (xku & WOLFSSL_XKU_SSL_SERVER) eku |= EXTKEYUSE_SERVER_AUTH; + if (xku & WOLFSSL_XKU_SSL_CLIENT) eku |= EXTKEYUSE_CLIENT_AUTH; + if (xku & WOLFSSL_XKU_SMIME) eku |= EXTKEYUSE_EMAILPROT; + if (xku & WOLFSSL_XKU_CODE_SIGN) eku |= EXTKEYUSE_CODESIGN; + if (xku & WOLFSSL_XKU_OCSP_SIGN) eku |= EXTKEYUSE_OCSP_SIGN; + if (xku & WOLFSSL_XKU_TIMESTAMP) eku |= EXTKEYUSE_TIMESTAMP; + if (xku & WOLFSSL_XKU_ANYEKU) eku |= EXTKEYUSE_ANY; + if (xku & (WOLFSSL_XKU_SGC | WOLFSSL_XKU_DVCS)) { + /* wolfcrypt's EXTKEYUSE_* has no SGC/DVCS equivalent. */ + wolfCLU_Log(WOLFCLU_L0, + "Warning: CSR extendedKeyUsage SGC/DVCS purpose " + "dropped, not supported on issued cert"); + } + cert->extKeyUsage = eku; + } +#else + (void)isCA; + (void)ku; +#endif /* WOLFSSL_CERT_EXT */ + + nb = wolfSSL_X509_get_notBefore(x509); + na = wolfSSL_X509_get_notAfter(x509); + if (nb != NULL) { + 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) { + 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) { + 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 = (caCert == NULL) ? 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 && + wolfSSL_X509_get_ext_by_NID(x509, NID_subject_key_identifier, + -1) >= 0) { + /* subjWcKey is always non-NULL here: the caller validates subjKey != NULL + * before calling this function. */ + if (subjWcKey == NULL || + wc_SetSubjectKeyIdFromPublicKey_ex(cert, subjWcKeyType, + subjWcKey) < 0) { + wolfCLU_LogError("Error setting subject key identifier"); + ret = WOLFCLU_FATAL_ERROR; + } + } + + if (ret == WOLFCLU_SUCCESS && caWcKey != NULL && + wolfSSL_X509_get_ext_by_NID(x509, NID_authority_key_identifier, + -1) >= 0) { + if (wc_SetAuthKeyIdFromPublicKey_ex(cert, caWcKeyType, + caWcKey) < 0) { + wolfCLU_LogError("Error setting authority key identifier"); + ret = WOLFCLU_FATAL_ERROR; + } + } +#endif /* WOLFSSL_CERT_EXT */ + +#if defined(WOLFSSL_ALT_NAMES) + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_CopyX509SanToCert(x509, cert); + } +#endif /* WOLFSSL_ALT_NAMES */ + +#ifdef WOLFSSL_CERT_EXT + /* Carry any remaining CSR extensions (or warn that they were dropped). */ + if (ret == WOLFCLU_SUCCESS) { + ret = wolfCLU_CopyX509ExtsToCert(x509, cert); + } + + /* On failure, free any oid/val buffers already handed to cert by a + * partially-successful wolfCLU_CopyX509ExtsToCert() so ownership of + * those allocations doesn't leak out to the caller. On success the + * caller owns cert and must call wolfCLU_FreeCertCustomExts() itself + * once done with it (after signing/encoding). */ + if (ret != WOLFCLU_SUCCESS) { + wolfCLU_FreeCertCustomExts(cert); + } +#endif /* WOLFSSL_CERT_EXT */ + + return ret; +} +#endif /* WOLFSSL_CERT_GEN */ diff --git a/tests/dgst/dgst-test.py b/tests/dgst/dgst-test.py index 76a5699c..c97ff485 100644 --- a/tests/dgst/dgst-test.py +++ b/tests/dgst/dgst-test.py @@ -519,6 +519,23 @@ 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) + if __name__ == "__main__": test_main() diff --git a/tests/encrypt/enc-test.py b/tests/encrypt/enc-test.py index aa30cfbd..5fa48c7f 100644 --- a/tests/encrypt/enc-test.py +++ b/tests/encrypt/enc-test.py @@ -163,7 +163,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) @@ -173,8 +173,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)) diff --git a/tests/genkey_sign_ver/genkey-sign-ver-test.py b/tests/genkey_sign_ver/genkey-sign-ver-test.py index 615fe385..b90fc0bf 100644 --- a/tests/genkey_sign_ver/genkey-sign-ver-test.py +++ b/tests/genkey_sign_ver/genkey-sign-ver-test.py @@ -327,6 +327,82 @@ def test_rsa_sign_invalid_key_fails(self): "RSA signing with empty key should have failed") +@unittest.skipIf(os.name == "nt", "POSIX file permissions only") +class KeyFilePermissionsTest(unittest.TestCase): + """wolfCLU_OpenKeyFile must write private keys with owner-only (0600) + permissions, and must replace (not append to) a pre-existing file.""" + + @classmethod + def tearDownClass(cls): + _cleanup_files(_TEMP_FILES) + _TEMP_FILES.clear() + + def _priv_mode(self, keybase): + priv = keybase + ".priv" + pub = keybase + ".pub" + _TEMP_FILES.extend([priv, pub]) + r = run_wolfssl("-genkey", "rsa", "-size", "2048", "-out", keybase, + "-outform", "der", "-output", "KEYPAIR") + self.assertEqual(r.returncode, 0, r.stderr) + return priv, os.stat(priv).st_mode & 0o777 + + def test_rsa_priv_key_mode_is_owner_only(self): + _, mode = self._priv_mode("rsakey-perm-test") + self.assertEqual(mode, 0o600, + "RSA private key file mode is {:o}, expected 600" + .format(mode)) + + def test_ecc_priv_key_mode_is_owner_only(self): + priv = "ecckey-perm-test.priv" + pub = "ecckey-perm-test.pub" + _TEMP_FILES.extend([priv, pub]) + r = run_wolfssl("-genkey", "ecc", "-out", "ecckey-perm-test", + "-outform", "der", "-output", "KEYPAIR") + self.assertEqual(r.returncode, 0, r.stderr) + mode = os.stat(priv).st_mode & 0o777 + self.assertEqual(mode, 0o600, + "ECC private key file mode is {:o}, expected 600" + .format(mode)) + + def test_ed25519_priv_key_mode_is_owner_only(self): + priv = "edkey-perm-test.priv" + pub = "edkey-perm-test.pub" + _TEMP_FILES.extend([priv, pub]) + r = run_wolfssl("-genkey", "ed25519", "-out", "edkey-perm-test", + "-outform", "der", "-output", "KEYPAIR") + self.assertEqual(r.returncode, 0, r.stderr) + mode = os.stat(priv).st_mode & 0o777 + self.assertEqual(mode, 0o600, + "Ed25519 private key file mode is {:o}, expected " + "600".format(mode)) + + def test_preexisting_priv_file_is_replaced(self): + """A stale file at the target path must be replaced, not appended + to or left with mixed content, and must end up owner-only.""" + keybase = "rsakey-replace-test" + priv = keybase + ".priv" + pub = keybase + ".pub" + _TEMP_FILES.extend([priv, pub]) + + with open(priv, "wb") as f: + f.write(b"stale placeholder content") + os.chmod(priv, 0o644) + + r = run_wolfssl("-genkey", "rsa", "-size", "2048", "-out", keybase, + "-outform", "der", "-output", "KEYPAIR") + self.assertEqual(r.returncode, 0, r.stderr) + + with open(priv, "rb") as f: + content = f.read() + self.assertNotIn(b"stale placeholder content", content, + "stale content survived key generation") + + mode = os.stat(priv).st_mode & 0o777 + self.assertEqual(mode, 0o600, + "replaced private key file mode is {:o}, expected " + "600".format(mode)) + + @unittest.skipUnless(_has_algorithm("dilithium"), "dilithium not available") class DilithiumTest(_GenkeySignVerifyBase): diff --git a/tests/x509/cert_setup_unit_test.c b/tests/x509/cert_setup_unit_test.c new file mode 100644 index 00000000..02b08ecc --- /dev/null +++ b/tests/x509/cert_setup_unit_test.c @@ -0,0 +1,806 @@ +/* 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 + */ + +/* Native C unit test for the wolfcrypt Cert <- WOLFSSL_X509 helper + * functions in src/x509/clu_cert_setup.c. These helpers are currently only + * reachable from the (not-yet-wired) CSR->cert ML-DSA CA-signing path, so + * there is no CLI entry point to exercise them from a Python test; this test + * calls them directly. */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* wolfCLU_X509FillCert and friends are only declared/defined under + * WOLFSSL_CERT_GEN (see wolfclu/x509/clu_cert.h); skip this test entirely + * on builds without it rather than failing to compile. */ +#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 a self-signed RSA X.509 DER cert in memory using wolfCrypt directly, + * mirroring the shape used in src/x509/clu_x509_sign.c (wc_InitCert / + * wc_MakeCert / wc_SignCert) and src/genkey/clu_genkey.c + * (wc_InitRsaKey / wc_MakeRsaKey), then parse it back into a WOLFSSL_X509*. + * This exercises the same generic WOLFSSL_X509 getter APIs that + * wolfCLU_X509FillCert relies on. */ +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; + } + + strncpy(cert.subject.country, "US", CTC_NAME_SIZE - 1); + strncpy(cert.subject.state, "Washington", CTC_NAME_SIZE - 1); + strncpy(cert.subject.locality, "Seattle", CTC_NAME_SIZE - 1); + strncpy(cert.subject.org, "wolfSSL", CTC_NAME_SIZE - 1); + strncpy(cert.subject.unit, "Testing", CTC_NAME_SIZE - 1); + strncpy(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]; + + memset(&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(strcmp(name.commonName, "wolfSSL") == 0, + "SetCertNameFieldByNid CN value"); + + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_countryName, "US", 2); + CHECK(ret == WOLFCLU_SUCCESS, "SetCertNameFieldByNid valid C"); + CHECK(strcmp(name.country, "US") == 0, "SetCertNameFieldByNid C value"); + + /* NULL dst */ + ret = wolfCLU_SetCertNameFieldByNid(NULL, NID_commonName, "wolfSSL", 7); + CHECK(ret == BAD_FUNC_ARG, "SetCertNameFieldByNid NULL dst"); + + /* NULL val */ + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_commonName, NULL, 7); + CHECK(ret == BAD_FUNC_ARG, "SetCertNameFieldByNid NULL val"); + + /* valLen <= 0 */ + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_commonName, "wolfSSL", 0); + CHECK(ret == BAD_FUNC_ARG, "SetCertNameFieldByNid valLen 0"); + + ret = wolfCLU_SetCertNameFieldByNid(&name, NID_commonName, "wolfSSL", -1); + CHECK(ret == BAD_FUNC_ARG, "SetCertNameFieldByNid valLen -1"); + + /* value too long */ + memset(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"); +} + +#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"); +#if defined(WOLFSSL_ALT_NAMES) && (defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL) || defined(WOLFSSL_QT)) + CHECK(wolfCLU_ExtHandledNid(NID_subject_alt_name) == 1, + "ExtHandledNid subject_alt_name"); +#endif + CHECK(wolfCLU_ExtHandledNid(NID_commonName) == 0, + "ExtHandledNid commonName not handled"); +} +#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; + } + + memset(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(memcmp(buf + lenPrefixSz, t->data, (size_t)t->length) == 0, + "Asn1TimeToCertDate value bytes"); + } + + /* bad tag */ + memset(&bad, 0, sizeof(bad)); + bad.type = 99; /* not UTCTime or GeneralizedTime */ + bad.length = 13; + memset(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; + + memset(&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(strcmp(dst.commonName, "wolfCLU Cert Setup Test") == 0, + "CopyX509NameToCert commonName matches"); + CHECK(strcmp(dst.country, "US") == 0, "CopyX509NameToCert country matches"); + CHECK(strcmp(dst.org, "wolfSSL") == 0, "CopyX509NameToCert org matches"); + + /* NULL args */ + ret = wolfCLU_CopyX509NameToCert(NULL, &dst); + CHECK(ret == BAD_FUNC_ARG, "CopyX509NameToCert NULL name"); + ret = wolfCLU_CopyX509NameToCert(name, NULL); + CHECK(ret == 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 == BAD_FUNC_ARG, "CopyX509SanToCert NULL x509"); + ret = wolfCLU_CopyX509SanToCert(x509, NULL); + CHECK(ret == 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"); +} + +/* Exercises the actual copy branch (a real subjectAltName present on the + * x509), which the no-SAN fixture above never reaches. wolfCLU_CopyX509SanToCert + * reads the SAN extension by re-parsing x509's DER (via + * wolfSSL_X509_get_ext_by_NID/get_ext), so the SAN has to be baked into the + * signed DER itself -- setting it post-hoc on the in-memory WOLFSSL_X509 + * (e.g. via wolfCLU_parseAddExt) does not reach that DER and is not visible + * to this copy path. Cert.altNames/altNamesSz hold the raw DER content of the + * SAN extension's OCTET STRING (a GeneralNames SEQUENCE), per + * SetAltNames-adjacent encoding in wolfcrypt/src/asn.c. */ +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; + /* DER: SEQUENCE { [2] IA5String "test.wolfssl.com" } -- a GeneralNames + * SEQUENCE containing 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*)malloc((size_t)derBufSz); + if (derBuf == NULL) { + CHECK(0, "CopyX509SanToCertWithSan: malloc derBuf"); + wc_FreeRng(&rng); + return; + } + + if (wc_InitRsaKey(&key, HEAP_HINT) != 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_InitRsaKey"); + free(derBuf); + wc_FreeRng(&rng); + return; + } + if (wc_MakeRsaKey(&key, 2048, 65537, &rng) != 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_MakeRsaKey"); + wc_FreeRsaKey(&key); + free(derBuf); + wc_FreeRng(&rng); + return; + } + if (wc_InitCert(&cert) != 0) { + CHECK(0, "CopyX509SanToCertWithSan: wc_InitCert (fixture)"); + wc_FreeRsaKey(&key); + free(derBuf); + wc_FreeRng(&rng); + return; + } + strncpy(cert.subject.country, "US", CTC_NAME_SIZE - 1); + strncpy(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); + free(derBuf); + 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); + free(derBuf); + 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); + free(derBuf); + 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); + free(derBuf); + 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(memcmp(outCert.altNames, sanData->data, + (size_t)sanData->length) == 0, + "CopyX509SanToCertWithSan: altNames bytes match " + "source extension"); + } + } + } + } + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + free(derBuf); + wc_FreeRng(&rng); +} +#endif /* WOLFSSL_ALT_NAMES */ + +#ifdef WOLFSSL_CERT_EXT +static void testCopyX509ExtsToCert(WOLFSSL_X509* x509) +{ + Cert cert; + int ret; + + if (wc_InitCert(&cert) != 0) { + CHECK(0, "CopyX509ExtsToCert wc_InitCert"); + return; + } + + ret = wolfCLU_CopyX509ExtsToCert(x509, &cert); + CHECK(ret == WOLFCLU_SUCCESS, "CopyX509ExtsToCert success/no-crash"); + + /* no-crash smoke check; nothing custom was added for this fixture */ + wolfCLU_FreeCertCustomExts(&cert); +} +#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); + 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(strcmp(outCert.subject.commonName, + "wolfCLU Cert Setup Test") == 0, + "X509FillCert subject commonName"); + CHECK(outCert.selfSigned == 1, "X509FillCert selfSigned (no caCert)"); +#ifdef WOLFSSL_CERT_EXT + wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + /* Test with caCert != NULL */ + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, key, RSA_TYPE, + key, RSA_TYPE, x509, 1); + CHECK(ret == WOLFCLU_SUCCESS, "X509FillCert with caCert success"); + if (ret == WOLFCLU_SUCCESS) { + CHECK(outCert.selfSigned == 0, "X509FillCert with caCert selfSigned == 0"); + CHECK(strcmp(outCert.issuer.commonName, "wolfCLU Cert Setup Test") == 0, + "X509FillCert with caCert issuer commonName"); +#ifdef WOLFSSL_CERT_EXT + wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + /* policySanitized == 0 must be refused */ + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, key, RSA_TYPE, + NULL, 0, NULL, 0); + 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); + CHECK(ret == BAD_FUNC_ARG, "X509FillCert NULL x509"); +} + +/* A non-CA CSR that only asks for digitalSignature/nonRepudiation must not + * lose the RSA-default keyEncipherment bit, and the CSR's extra + * nonRepudiation bit should still come through. */ +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*)malloc((size_t)derBufSz); + 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"); + free(derBuf); + wc_FreeRng(&rng); + return; + } + + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, &key, + RSA_TYPE, NULL, 0, NULL, 1); + 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 + wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + free(derBuf); + wc_FreeRng(&rng); +} + +/* A non-RSA leaf key (ECDSA here, standing in for the other signature-only + * types: Ed25519/Ed448, ML-DSA, Falcon) must get plain digitalSignature, + * never the RSA-only keyEncipherment default. */ +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*)malloc((size_t)derBufSz); + 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"); + free(derBuf); + 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); + free(derBuf); + wc_FreeRng(&rng); + return; + } + + if (wc_InitCert(&cert) != 0) { + CHECK(0, "leaf keyUsage non-RSA: wc_InitCert"); + wc_ecc_free(&key); + free(derBuf); + wc_FreeRng(&rng); + return; + } + strncpy(cert.subject.country, "US", CTC_NAME_SIZE - 1); + strncpy(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); + free(derBuf); + 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); + free(derBuf); + 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); + free(derBuf); + 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); + free(derBuf); + wc_FreeRng(&rng); + return; + } + + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wECDSA, &key, + ECC_TYPE, NULL, 0, NULL, 1); + 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 + wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + wolfSSL_X509_free(x509); + wc_ecc_free(&key); + free(derBuf); + wc_FreeRng(&rng); +} + +static void testReadFileToBuffer(void) +{ + byte* buf = NULL; + int bufSz = 0; + int ret; + const char* testFile = "test_read_file.tmp"; + FILE* f; + + /* NULL args */ + ret = wolfCLU_ReadFileToBuffer(NULL, 100, &buf, &bufSz); + CHECK(ret == BAD_FUNC_ARG, "ReadFileToBuffer NULL path"); + ret = wolfCLU_ReadFileToBuffer(testFile, 100, NULL, &bufSz); + CHECK(ret == BAD_FUNC_ARG, "ReadFileToBuffer NULL outBuf"); + ret = wolfCLU_ReadFileToBuffer(testFile, 100, &buf, NULL); + CHECK(ret == BAD_FUNC_ARG, "ReadFileToBuffer NULL outSz"); + ret = wolfCLU_ReadFileToBuffer(testFile, 0, &buf, &bufSz); + CHECK(ret == BAD_FUNC_ARG, "ReadFileToBuffer maxSz <= 0"); + + /* Missing file */ + remove(testFile); /* Ensure it doesn't exist */ + ret = wolfCLU_ReadFileToBuffer(testFile, 100, &buf, &bufSz); + CHECK(ret == BAD_FUNC_ARG, "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); + } + + /* 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); + } + remove(testFile); + } + + /* 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(memcmp(buf, "12345", 5) == 0, "ReadFileToBuffer content"); + CHECK(buf[5] == '\0', "ReadFileToBuffer null terminated"); + XFREE(buf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + } else { + fclose(f); + } + remove(testFile); + } +} + +int main(void) +{ + WC_RNG rng; + RsaKey key; + WOLFSSL_X509* x509 = NULL; + byte* derBuf = NULL; + int derBufSz = 8192; + int outDerSz = 0; + + if (wc_InitRng(&rng) != 0) { + printf("FAIL: wc_InitRng\n"); + return 1; + } + + derBuf = (byte*)malloc((size_t)derBufSz); + 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"); + free(derBuf); + wc_FreeRng(&rng); + return 1; + } + + testSetCertNameFieldByNid(); +#ifdef WOLFSSL_CERT_EXT + testExtHandledNid(); +#endif + testAsn1TimeToCertDate(x509); + testCopyX509NameToCert(x509); +#ifdef WOLFSSL_ALT_NAMES + testCopyX509SanToCert(x509); + testCopyX509SanToCertWithSan(); +#endif +#ifdef WOLFSSL_CERT_EXT + testCopyX509ExtsToCert(x509); +#endif + testX509FillCert(x509, &key); + testX509FillCertLeafKeyUsageMerge(); + testX509FillCertLeafKeyUsageNonRsa(); + testReadFileToBuffer(); + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + free(derBuf); + wc_FreeRng(&rng); + + 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..08ab378f --- /dev/null +++ b/tests/x509/unit_include.am @@ -0,0 +1,17 @@ +# 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/tools/clu_funcs.c diff --git a/wolfclu/clu_header_main.h b/wolfclu/clu_header_main.h index 57d245ce..bc5cfc55 100644 --- a/wolfclu/clu_header_main.h +++ b/wolfclu/clu_header_main.h @@ -613,6 +613,14 @@ int wolfCLU_PKCS12(int argc, char** argv); */ void wolfCLU_ForceZero(void* mem, unsigned int len); +/** + * @brief read an entire file into a newly XMALLOC'd buffer, capped at maxSz. + * On success *outBuf and *outSz are set; caller frees *outBuf with XFREE + * (DYNAMIC_TYPE_TMP_BUFFER). Returns WOLFCLU_SUCCESS or an error code. + */ +int wolfCLU_ReadFileToBuffer(const char* path, long maxSz, byte** outBuf, + int* outSz); + /** * @brief example client */ diff --git a/wolfclu/x509/clu_cert.h b/wolfclu/x509/clu_cert.h index 5256ae17..4e9d41dd 100644 --- a/wolfclu/x509/clu_cert.h +++ b/wolfclu/x509/clu_cert.h @@ -32,3 +32,26 @@ 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); +/* frees oid buffers allocated by wolfCLU_CopyX509ExtsToCert; caller must + * invoke this after the Cert is done being used (after signing/encoding) */ +void wolfCLU_FreeCertCustomExts(Cert* cert); +#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); +#endif /* WOLFSSL_CERT_GEN */