From 7079e99fee57e8f92ba15e406437b47e8a6e2914 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Mon, 20 Jul 2026 16:03:16 -0600 Subject: [PATCH 1/4] Add secure file utilities and stream hashing helpers --- src/tools/clu_funcs.c | 293 +++++++++++++++++++++++++++++++++----- wolfclu/clu_header_main.h | 46 ++++++ 2 files changed, 303 insertions(+), 36 deletions(-) diff --git a/src/tools/clu_funcs.c b/src/tools/clu_funcs.c index 364bef3f..cb4bfa39 100644 --- a/src/tools/clu_funcs.c +++ b/src/tools/clu_funcs.c @@ -1112,6 +1112,204 @@ void wolfCLU_ForceZero(void* mem, unsigned int len) #endif } +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((size_t)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_FATAL_ERROR; + } + buf[sz] = '\0'; + XFCLOSE(f); + + *outBuf = buf; + *outSz = sz; + return WOLFCLU_SUCCESS; +} + +/* Creates path owner-only, failing if it already exists. access/mode select + * read/write and the fdopen() mode string, letting callers share this + * between write-only key files and files reopened for read+write. */ +#ifdef _WIN32 +#include +#include +#include +#include +#pragma comment(lib, "advapi32.lib") + +FILE* wolfCLU_CreateSecureFile(const char* path, DWORD access, + const char* mode) +{ + SECURITY_ATTRIBUTES sa; + PSECURITY_DESCRIPTOR pSD = NULL; + HANDLE hFile; + int fd; + FILE* f = NULL; + const char* sddl = "D:P(A;;FA;;;OW)"; + int crtFlags = _O_CREAT | _O_TRUNC | + ((access & GENERIC_READ) ? + ((access & GENERIC_WRITE) ? _O_RDWR : _O_RDONLY) : + _O_WRONLY); + + (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, access, 0, &sa, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + DWORD attrs = GetFileAttributesA(path); + if (attrs != INVALID_FILE_ATTRIBUTES && + (attrs & FILE_ATTRIBUTE_REPARSE_POINT)) { + /* path was replaced with a symlink/junction between the + * _unlink() above and this CreateFileA(); refuse to write + * through it. */ + CloseHandle(hFile); + hFile = INVALID_HANDLE_VALUE; + (void)_unlink(path); + } + } + if (hFile != INVALID_HANDLE_VALUE) { + fd = _open_osfhandle((intptr_t)hFile, crtFlags); + if (fd != -1) { + f = _fdopen(fd, mode); + } + if (f == NULL) { + if (fd != -1) _close(fd); + else CloseHandle(hFile); + (void)_unlink(path); + } + } + LocalFree(pSD); + } + return f; +} + +FILE* wolfCLU_OpenKeyFile(const char* path) +{ + return wolfCLU_CreateSecureFile(path, GENERIC_WRITE, "wb"); +} +#else +#include +#include +#ifndef O_NOFOLLOW + #define O_NOFOLLOW 0 +#endif +FILE* wolfCLU_CreateSecureFile(const char* path, int access, + const char* mode) +{ + int fd; + FILE* f; + struct stat st; + + /* Non-regular targets (character devices like /dev/null or + * /dev/stdout, FIFOs, etc.) can't be usefully hardened with + * unlink()+O_EXCL: unlink() on them commonly fails (they live in + * directories the caller can't modify) or, when it succeeds, would + * destroy and replace the special file with a plain regular file + * instead of writing through it. Fall back to a plain fopen() for + * these so redirecting output to them keeps working as it did before + * the O_EXCL hardening was added. */ + if (lstat(path, &st) == 0 && !S_ISREG(st.st_mode)) { + return fopen(path, mode); + } + + /* 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 | access | O_NOFOLLOW, 0600); + if (fd < 0) + return NULL; + f = fdopen(fd, mode); + if (f == NULL) { + close(fd); + (void)unlink(path); /* remove the stray empty file we just created */ + } + return f; +} + +FILE* wolfCLU_OpenKeyFile(const char* path) +{ + return wolfCLU_CreateSecureFile(path, O_WRONLY, "wb"); +} +#endif /* _WIN32 */ + +void wolfCLU_RemoveFile(const char* path) +{ +#ifdef _WIN32 + (void)_unlink(path); +#else + (void)unlink(path); +#endif +} + +WOLFSSL_BIO* wolfCLU_OpenKeyFileBio(const char* path) +{ + FILE* f = wolfCLU_OpenKeyFile(path); + WOLFSSL_BIO* bioOut = (f != NULL) ? + wolfSSL_BIO_new_fp(f, BIO_CLOSE) : NULL; + + if (bioOut == NULL) { + if (f != NULL) { + XFCLOSE(f); + wolfCLU_RemoveFile(path); + } + wolfCLU_LogError("Unable to open output file %s", path); + } + return bioOut; +} + #ifndef WOLFCLU_NO_TERM_SUPPORT int wolfCLU_GetPassword(char* password, int* passwordSz, char* arg) @@ -1313,18 +1511,65 @@ int wolfCLU_GetOpt(int argc, char** argv, const char *options, } +/* Reads bioIn in chunks, calling update() on each one until EOF or error. + * Shared by wolfCLU_streamHashBio and wolfCLU_hmacHash. */ +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; @@ -1342,21 +1587,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) { @@ -1392,7 +1625,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(); @@ -1422,24 +1660,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; - } - } - wolfCLU_ForceZero(chunk, sizeof(chunk)); + ret = wolfCLU_bioReadUpdate(in, wolfCLU_hmacUpdateCb, ctx); } if (ret == WOLFCLU_SUCCESS) { diff --git a/wolfclu/clu_header_main.h b/wolfclu/clu_header_main.h index 47766c72..72ac9974 100644 --- a/wolfclu/clu_header_main.h +++ b/wolfclu/clu_header_main.h @@ -613,6 +613,52 @@ 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 on error one of + * BAD_FUNC_ARG, MEMORY_E, or WOLFCLU_FATAL_ERROR. + */ +int wolfCLU_ReadFileToBuffer(const char* path, long maxSz, byte** outBuf, + int* outSz); + +/** + * @brief create path owner-only, failing if it already exists (deletes any + * existing file/symlink at path first). access/mode select read/write and + * the fdopen() mode string, letting callers share this between write-only + * key files and files reopened for read+write. + */ +#ifdef _WIN32 +FILE* wolfCLU_CreateSecureFile(const char* path, DWORD access, + const char* mode); +#else +FILE* wolfCLU_CreateSecureFile(const char* path, int access, + const char* mode); +#endif + +/** + * @brief open path for writing with owner-only permissions (POSIX 0600 / + * Windows single-owner ACE), replacing any pre-existing file at that path. + * Use for any file that will hold private key material. + */ +FILE* wolfCLU_OpenKeyFile(const char* path); + +/** + * @brief remove the file at path, ignoring the result. Use to clean up a + * stray empty file left by wolfCLU_OpenKeyFile() when a subsequent step + * (e.g. wrapping the FILE* in a BIO) fails. + */ +void wolfCLU_RemoveFile(const char* path); + +/** + * @brief open path for writing with owner-only permissions and wrap it in + * a WOLFSSL_BIO, cleaning up both the FILE* and the file on disk if the + * BIO wrap fails. Returns NULL and logs "Unable to open output file %s" + * on any failure. Use for any -out path that will hold private key + * material. + */ +WOLFSSL_BIO* wolfCLU_OpenKeyFileBio(const char* path); + /** * @brief example client */ From 136fa81edd3c34dcee6dec4e55903ceff7211554 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Mon, 20 Jul 2026 16:03:16 -0600 Subject: [PATCH 2/4] Add generic X.509 certificate helpers and unit tests --- .gitignore | 1 + Makefile.am | 1 + src/x509/clu_cert_setup.c | 970 ++++++++++++++++++++++++++++++ tests/x509/cert_setup_unit_test.c | 888 +++++++++++++++++++++++++++ tests/x509/unit_include.am | 17 + wolfclu/x509/clu_cert.h | 27 +- 6 files changed, 1901 insertions(+), 3 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 3902801f..11b9749c 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,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 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/x509/clu_cert_setup.c b/src/x509/clu_cert_setup.c index a7cda803..f218d95d 100644 --- a/src/x509/clu_cert_setup.c +++ b/src/x509/clu_cert_setup.c @@ -844,3 +844,973 @@ int wolfCLU_certSetup(int argc, char **argv) return WOLFCLU_FATAL_ERROR; #endif } + + +#ifdef WOLFSSL_CERT_GEN + +/* Native (non-OPENSSL_EXTRA) extension access. + * + * Reading extensions is done directly off the DER here to avoid + * requiring the full OPENSSL_EXTRA/OPENSSL_ALL compatibility layer. + * Only used by the WOLFSSL_CERT_EXT code below. SAN copying uses + * wc_SetAltNamesBuffer() directly (see wolfCLU_CopyX509SanToCert). + */ +#ifdef WOLFSSL_CERT_EXT + +/* DER content bytes (tag/length already stripped) of the extension OIDs + * this file recognizes explicitly elsewhere. */ +static const byte kOidBasicConstraints[] = {0x55, 0x1D, 0x13}; /* 2.5.29.19 */ +static const byte kOidKeyUsage[] = {0x55, 0x1D, 0x0F}; /* 2.5.29.15 */ +static const byte kOidExtKeyUsage[] = {0x55, 0x1D, 0x25}; /* 2.5.29.37 */ +static const byte kOidSubjectKeyIdentifier[] = {0x55, 0x1D, 0x0E}; /* 2.5.29.14 */ +static const byte kOidAuthorityKeyIdentifier[] = {0x55, 0x1D, 0x23}; /* 2.5.29.35 */ +static const byte kOidSubjectAltName[] = {0x55, 0x1D, 0x11}; /* 2.5.29.17 */ + +/* One decoded `Extension ::= SEQUENCE { extnID OID, critical BOOLEAN + * DEFAULT FALSE, extnValue OCTET STRING }` 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-OF entry at buf[*idx], advancing *idx + * past it on success. buf/bufSz cover the whole Extensions content (i.e. + * DecodedCert::extensions/extensionsSz). */ +static int wolfCLU_DerGetExtension(const byte* buf, word32 bufSz, + word32* idx, WOLFCLU_X509_EXT* ext) +{ + byte tag; + word32 len; + word32 seqIdx; + word32 seqEnd; + int ret; + + ret = wolfCLU_DerGetHeader(buf, bufSz, idx, &tag, &len); + if (ret != WOLFCLU_SUCCESS || tag != (ASN_SEQUENCE | ASN_CONSTRUCTED)) { + return ASN_PARSE_E; + } + seqIdx = *idx; + seqEnd = seqIdx + len; + *idx = seqEnd; + + 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; + + return WOLFCLU_SUCCESS; +} + +/* Parse x509's raw DER into dCert and point it at the CSR's Extensions. + * On WOLFCLU_SUCCESS the caller must wc_FreeDecodedCert(dCert) once done; + * dCert->extensions/extensionsSz point into x509's own DER buffer (owned + * by x509, not by dCert), so dCert must not outlive x509. */ +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; + } + + 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) { + break; + } + 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}; +static const byte kOidEkuAny[] = {0x55,0x1D,0x25,0x00}; /* 2.5.29.37.0 */ + +/* 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_dnQualifier: + field = dst->dnQualifier; + break; +#endif +#ifdef WOLFSSL_CERT_EXT + case NID_businessCategory: + field = dst->busCat; + break; +#endif + default: + /* No CertName field for this NID: the DN component is + * dropped from the issued certificate's subject/issuer. */ + wolfCLU_Log(WOLFCLU_L0, + "Warning: DN field (nid %d) has no destination in " + "the issued certificate and was dropped", nid); + 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. + * + * wc_SetAltNamesBuffer() takes the CSR's whole raw DER (not just the SAN + * extension bytes) and re-parses it with wolfCrypt's own SAN decoder + * internally -- reusing that instead of hand-rolling SAN parsing here + * avoids re-implementing GeneralName-type handling (dNSName, iPAddress, + * dirName, ...) and needs nothing from OPENSSL_EXTRA. */ +#if defined(WOLFSSL_ALT_NAMES) +int wolfCLU_CopyX509SanToCert(WOLFSSL_X509* x509, Cert* cert) +{ + 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 /* WOLFSSL_ALT_NAMES */ + +#ifdef WOLFSSL_CERT_EXT +/* Single source of truth for which extensions wolfCLU_X509FillCert already + * copies explicitly; the generic copier (wolfCLU_ExtHandledOid) must skip + * these to avoid duplicating an extension, and wolfCLU_ExtHandledNid + * exposes the same set by NID for callers/tests that don't have a raw OID + * on hand. Both functions look up this one table so they can't drift + * apart from each other. */ +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; +} + +/* Same skip-list as wolfCLU_ExtHandledNid, looked up by raw OID DER content + * bytes instead of NID -- what the native walker below actually has on + * hand. */ +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 (tag/length already stripped) into + * a NUL-terminated dotted-decimal string, e.g. {0x55,0x1D,0x11} -> + * "2.5.29.17" -- the form wc_SetCustomExtension() expects. */ +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 component (encoding the first two arcs as + * 40*X+Y) is itself base-128 encoded just like every later arc, so it + * must be accumulated across continuation bytes before being split + * back into X/Y -- a single-byte read here mis-decodes any OID whose + * first two arcs combine to >= 128 (e.g. 2.100.3 is {0x81,0x34,0x03}). */ + arc = 0; + firstArc = -1; + i = 0; + while (i < oidLen) { + 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++) { + 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 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 = WOLFCLU_SUCCESS; + int uncopied = 0; + DecodedCert dCert; + word32 idx = 0; + + if (extsDropped != NULL) { + *extsDropped = 0; + } + if (x509 == NULL || cert == NULL) { + return BAD_FUNC_ARG; + } + + ret = wolfCLU_GetX509RawExtensions(x509, &dCert); + if (ret != WOLFCLU_SUCCESS) { + return ret; + } + + while (ret == WOLFCLU_SUCCESS && idx < (word32)dCert.extensionsSz) { + WOLFCLU_X509_EXT ext; + + if (wolfCLU_DerGetExtension(dCert.extensions, + (word32)dCert.extensionsSz, &idx, &ext) != WOLFCLU_SUCCESS) { + break; + } + 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) { + ret = MEMORY_E; + } + else { + /* +1 so a legitimately empty (zero-length) extnValue + * doesn't turn into a 0-byte XMALLOC() call, which some + * allocators return NULL for -- that would otherwise be + * mistaken for a real out-of-memory failure below. */ + 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); + ret = MEMORY_E; + } + } + + 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) { + wolfCLU_LogError("Failed to copy extension (OID %s) " + "to the certificate", oid); + XFREE(oidHeap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(valHeap, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + ret = WOLFCLU_FATAL_ERROR; + } + } + } + } +#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 */ + } + + wc_FreeDecodedCert(&dCert); + + 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; +} + +/* Frees the oid/val buffers allocated by wolfCLU_CopyX509ExtsToCert; call + * 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 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* extsDropped) +{ + int ret = WOLFCLU_SUCCESS; + int ku; + int isCA; + 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 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) { + /* 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 { + /* Only RSA keys are also used for key encipherment; other types + * are signature-only. subjWcKeyType is the CertType space + * (RSA_TYPE/ECC_TYPE/...) also used below by + * wc_SetSubjectKeyIdFromPublicKey_ex(). */ + cert->keyUsage = (subjWcKeyType == RSA_TYPE) ? + (KU_DIGITAL_SIGNATURE | KU_KEY_ENCIPHERMENT) : + KU_DIGITAL_SIGNATURE; + /* 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. */ + if (ku >= 0) { + int leafKu = ku & ~(KU_KEY_CERT_SIGN | KU_CRL_SIGN); + if (leafKu > 0) + cert->keyUsage |= (word16)leafKu; + } + } + + /* 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) { + /* wolfSSL_X509_get_extended_key_usage() needs full OPENSSL_EXTRA; + * read the extKeyUsage extension natively instead. */ + WOLFCLU_X509_EXT ext; + int found = 0; + + ret = wolfCLU_FindX509Ext(&dCert, kOidExtKeyUsage, + (word32)sizeof(kOidExtKeyUsage), &ext, &found); + cert->extKeyUsage = (ret == WOLFCLU_SUCCESS && found) ? + wolfCLU_DerGetExtKeyUsageBits(ext.val, ext.valLen) : 0; + } +#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) { + WOLFCLU_X509_EXT ext; + int found = 0; + + ret = wolfCLU_FindX509Ext(&dCert, kOidSubjectKeyIdentifier, + (word32)sizeof(kOidSubjectKeyIdentifier), &ext, &found); + if (ret == WOLFCLU_SUCCESS && found) { + /* subjWcKey != NULL is enforced by the parameter validation + * above. */ + if (wc_SetSubjectKeyIdFromPublicKey_ex(cert, subjWcKeyType, + subjWcKey) < 0) { + wolfCLU_LogError("Error setting subject key identifier"); + ret = WOLFCLU_FATAL_ERROR; + } + } + } + + if (ret == WOLFCLU_SUCCESS && caWcKey != NULL) { + WOLFCLU_X509_EXT ext; + int found = 0; + + ret = wolfCLU_FindX509Ext(&dCert, kOidAuthorityKeyIdentifier, + (word32)sizeof(kOidAuthorityKeyIdentifier), &ext, &found); + if (ret == WOLFCLU_SUCCESS && found) { + if (wc_SetAuthKeyIdFromPublicKey_ex(cert, caWcKeyType, + caWcKey) < 0) { + wolfCLU_LogError("Error setting authority key identifier"); + ret = WOLFCLU_FATAL_ERROR; + } + } + } + + if (dCertValid) { + wc_FreeDecodedCert(&dCert); + dCertValid = 0; + } +#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, extsDropped); + } + + /* 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) { + wolfCLU_FreeCertCustomExts(cert); + } +#endif /* WOLFSSL_CERT_EXT */ + + return ret; +} +#endif /* WOLFSSL_CERT_GEN */ diff --git a/tests/x509/cert_setup_unit_test.c b/tests/x509/cert_setup_unit_test.c new file mode 100644 index 00000000..f3dba300 --- /dev/null +++ b/tests/x509/cert_setup_unit_test.c @@ -0,0 +1,888 @@ +/* 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 +#ifdef _WIN32 + #include + #define GETPID _getpid +#else + #include + #define GETPID getpid +#endif + +#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"); +#ifdef WOLFSSL_ALT_NAMES + /* wolfCLU_CopyX509SanToCert uses wc_SetAltNamesBuffer() natively now, so + * SAN handling no longer depends on OPENSSL_EXTRA/OPENSSL_ALL/WOLFSSL_QT + * the way the rest of wolfCLU_ExtHandledNid's cases still do below. */ + 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*)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; + } + 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); + 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(memcmp(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 */ + 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, 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(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, NULL); + 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, 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 == BAD_FUNC_ARG, "X509FillCert NULL x509"); + + /* NULL subjWcKey must be rejected up front, even for a fixture whose + * subjectKeyIdentifier extension would otherwise be requested. */ + ret = wolfCLU_X509FillCert(x509, &outCert, CTC_SHA256wRSA, NULL, RSA_TYPE, + NULL, 0, NULL, 1, NULL); + CHECK(ret == BAD_FUNC_ARG, "X509FillCert NULL subjWcKey"); +} + +/* 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*)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 + wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); +} + +/* A CA CSR that requests extra non-CA keyUsage bits (e.g. digitalSignature) + * must not have those bits carried onto the issued CA certificate: only + * keyCertSign/cRLSign are permitted on the 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 + wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + 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*)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; + } + 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); + 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 + wolfCLU_FreeCertCustomExts(&outCert); +#endif + } + + wolfSSL_X509_free(x509); + wc_ecc_free(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + wc_FreeRng(&rng); +} + +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 == 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*)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(); +#endif + testAsn1TimeToCertDate(x509); + testCopyX509NameToCert(x509); +#ifdef WOLFSSL_ALT_NAMES + testCopyX509SanToCert(x509); + testCopyX509SanToCertWithSan(); +#endif +#ifdef WOLFSSL_CERT_EXT + testCopyX509ExtsToCert(x509); +#endif + testX509FillCert(x509, &key); + testX509FillCertLeafKeyUsageMerge(); + testX509FillCertCaKeyUsageMask(); + testX509FillCertLeafKeyUsageNonRsa(); + testReadFileToBuffer(); + + wolfSSL_X509_free(x509); + wc_FreeRsaKey(&key); + XFREE(derBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + 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/x509/clu_cert.h b/wolfclu/x509/clu_cert.h index 5256ae17..91977e1c 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,28 @@ #define DER_FORM 2 #define RAW_FORM 3 -/* 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); +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, int* extsDropped); +#endif /* WOLFSSL_CERT_GEN */ From d778d16006adc319c3760c802de7c3480e7f33f4 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Mon, 20 Jul 2026 16:03:16 -0600 Subject: [PATCH 3/4] Refactor existing modules to use new generic helpers --- src/crypto/clu_crypto_setup.c | 75 ++-- src/dh/clu_dh.c | 19 +- src/dsa/clu_dsa.c | 19 +- src/genkey/clu_genkey.c | 162 ++++++--- src/ocsp/clu_ocsp.c | 9 +- src/pkcs/clu_pkcs12.c | 32 +- src/pkcs/clu_pkcs8.c | 4 +- src/pkey/clu_pkey.c | 28 +- src/pkey/clu_rsa.c | 26 +- src/sign-verify/clu_dgst_setup.c | 22 +- src/sign-verify/clu_sign.c | 163 +-------- src/sign-verify/clu_verify.c | 580 ++++--------------------------- src/x509/clu_request_setup.c | 6 +- src/x509/clu_x509_sign.c | 54 +-- 14 files changed, 375 insertions(+), 824 deletions(-) diff --git a/src/crypto/clu_crypto_setup.c b/src/crypto/clu_crypto_setup.c index 5ed6f67e..f6e123c4 100644 --- a/src/crypto/clu_crypto_setup.c +++ b/src/crypto/clu_crypto_setup.c @@ -610,6 +610,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, (byte*)mode, NULL); + return WOLFCLU_FATAL_ERROR; + } + + if (inCheck == 0 && decCheck == 1) { + wolfCLU_LogError("File/string to decrypt needed"); + wolfCLU_freeBins(pwdKey, iv, key, (byte*)mode, 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, (byte*)mode, 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, (byte*)mode, NULL); + return WOLFCLU_FATAL_ERROR; + } + if (pwdKeyChk == 0 && keyCheck == 0) { if (decCheck == 1) { WOLFCLU_LOG(WOLFCLU_L0, "\nDECRYPT ERROR:"); @@ -631,6 +667,10 @@ 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, (byte*)mode, NULL); + return WOLFCLU_FATAL_ERROR; + } pwdKeyChk = 1; } } @@ -647,41 +687,6 @@ 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, (byte*)mode, NULL); - return WOLFCLU_FATAL_ERROR; - } - - if (inCheck == 0 && decCheck == 1) { - wolfCLU_LogError("File/string to decrypt needed"); - wolfCLU_freeBins(pwdKey, iv, key, (byte*)mode, 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, (byte*)mode, 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, (byte*)mode, NULL); - return WOLFCLU_FATAL_ERROR; - } if (pwdKeyChk == 1 && keyCheck == 1) { XMEMSET(pwdKey, 0, keySize + block); diff --git a/src/dh/clu_dh.c b/src/dh/clu_dh.c index 373ccb1d..10269d57 100644 --- a/src/dh/clu_dh.c +++ b/src/dh/clu_dh.c @@ -534,11 +534,20 @@ int wolfCLU_DhParamSetup(int argc, char** argv) WOLFCLU_LOG(WOLFCLU_E0, "No filesystem support. Unable to open output file"); ret = WOLFCLU_FATAL_ERROR; #else - bioOut = wolfSSL_BIO_new_file(out, "wb"); - if (bioOut == NULL) { - wolfCLU_LogError("Unable to open output file %s", - optarg); - ret = WOLFCLU_FATAL_ERROR; + { + /* DH params aren't secret; use default perms, not the + * owner-only lockdown for private keys. */ + FILE* f = XFOPEN(out, "wb"); + + bioOut = (f != NULL) ? wolfSSL_BIO_new_fp(f, BIO_CLOSE) : NULL; + if (bioOut == NULL) { + if (f != NULL) { + XFCLOSE(f); + } + wolfCLU_LogError("Unable to open output file %s", + optarg); + ret = WOLFCLU_FATAL_ERROR; + } } #endif } diff --git a/src/dsa/clu_dsa.c b/src/dsa/clu_dsa.c index be6d9eb2..c7c8dc26 100644 --- a/src/dsa/clu_dsa.c +++ b/src/dsa/clu_dsa.c @@ -197,11 +197,20 @@ int wolfCLU_DsaParamSetup(int argc, char** argv) WOLFCLU_LOG(WOLFCLU_E0, "No filesystem support. Unable to open input file"); ret = WOLFCLU_FATAL_ERROR; #else - bioOut = wolfSSL_BIO_new_file(out, "wb"); - if (bioOut == NULL) { - wolfCLU_LogError("Unable to open output file %s", - optarg); - ret = WOLFCLU_FATAL_ERROR; + { + /* DSA params aren't secret; use default perms, not the + * owner-only lockdown for private keys. */ + FILE* f = XFOPEN(out, "wb"); + + bioOut = (f != NULL) ? wolfSSL_BIO_new_fp(f, BIO_CLOSE) : NULL; + if (bioOut == NULL) { + if (f != NULL) { + XFCLOSE(f); + } + wolfCLU_LogError("Unable to open output file %s", + optarg); + ret = WOLFCLU_FATAL_ERROR; + } } #endif } diff --git a/src/genkey/clu_genkey.c b/src/genkey/clu_genkey.c index e129096d..1415bccb 100644 --- a/src/genkey/clu_genkey.c +++ b/src/genkey/clu_genkey.c @@ -26,12 +26,29 @@ #if defined(WOLFSSL_KEY_GEN) && !defined(NO_ASN) +#include +#include /* strerror */ + #include #include #include #include #include /* PER_FORM/DER_FORM */ +/* wolfCLU_OpenKeyFile/wolfCLU_CreateSecureFile now live in src/tools/clu_funcs.c + * (declared in wolfclu/clu_header_main.h) so every caller writing sensitive + * key material -- not just genkey's own -- can get owner-only permissions. */ +#ifndef _WIN32 +#include +#include +#ifndef O_NOFOLLOW + #define O_NOFOLLOW 0 +#endif +#else +#include +#endif /* !_WIN32 */ + + #ifdef HAVE_ED25519 /* return WOLFCLU_SUCCESS on success */ int wolfCLU_genKey_ED25519(WC_RNG* rng, char* fOutNm, int directive, int format) @@ -121,7 +138,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 +680,23 @@ 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 write to outfile %s", + fOutNameBuf); + ret = OUTPUT_FILE_ERROR; + } + else { + bioPri = wolfSSL_BIO_new_fp(f, BIO_CLOSE); + if (bioPri == NULL) { + XFCLOSE(f); + wolfCLU_RemoveFile(fOutNameBuf); + wolfCLU_LogError("unable to write to outfile %s", + fOutNameBuf); + ret = OUTPUT_FILE_ERROR; + } + } } } @@ -849,7 +878,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 +1189,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 +1200,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 (XFWRITE(outBuf, 1, outBufSz, file) != (size_t)outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1187,7 +1216,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 +1267,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 (XFWRITE(outBuf, 1, outBufSz, file) != (size_t)outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1260,7 +1289,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 +1437,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 +1448,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 (XFWRITE(outBuf, 1, outBufSz, file) != (size_t)outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1435,7 +1464,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 +1520,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 (XFWRITE(outBuf, 1, outBufSz, file) != (size_t)outBufSz) { ret = OUTPUT_FILE_ERROR; } } @@ -1514,7 +1543,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) { @@ -1554,78 +1583,99 @@ 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; } filename = context; /* Open file for read and write. */ - file = fopen(filename, "rb+"); - if (!file) { - /* Create the file if it didn't exist. */ - file = fopen(filename, "wb+"); - if (!file) { - fprintf(stderr, "error: fopen(%s, \"w+\") failed.\n", filename); + errno = 0; + file = XFOPEN(filename, "rb+"); + if (file == XBADFILE) { + /* Only fall through to create-and-replace when the file genuinely + * doesn't exist yet. Any other open failure (e.g. permission + * denied, a transient I/O error) must NOT be treated the same way: + * wolfCLU_CreateSecureFile unconditionally unlinks its target + * before recreating it, and XMSS repeatedly reopens/updates this + * file in place to track one-time-signature state across the + * key's lifetime, so blindly recreating it here would silently + * destroy that persisted state instead of failing safely. */ + if (errno != ENOENT) { + XFPRINTF(stderr, "error: fopen(%s, \"rb+\") failed: %s\n", + filename, strerror(errno)); + return WC_XMSS_RC_WRITE_FAIL; + } + + /* XMSS reopens/updates this file in place, so it can't use + * wolfCLU_OpenKeyFile's create-and-replace pattern; lock down + * perms on creation here instead. */ +#ifndef _WIN32 + file = wolfCLU_CreateSecureFile(filename, O_RDWR, "wb+"); +#else + file = wolfCLU_CreateSecureFile(filename, GENERIC_READ | GENERIC_WRITE, "wb+"); +#endif + 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 = fopen(filename, "rb+"); - if (!file) { - fprintf(stderr, "error: fopen(%s, \"r+\") failed.\n", filename); + file = XFOPEN(filename, "rb+"); + if (file == XBADFILE) { + XFPRINTF(stderr, "error: fopen(%s, \"r+\") 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)); - free(buff); - fclose(file); + XFREE(buff, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFCLOSE(file); return WC_XMSS_RC_WRITE_FAIL; } n_cmp = XMEMCMP(buff, priv, n_write); - free(buff); + 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; } @@ -1640,28 +1690,28 @@ 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; } filename = context; - file = fopen(filename, "rb"); - if (!file) { - fprintf(stderr, "error: fopen(%s, \"rb\") failed\n", filename); + file = XFOPEN(filename, "rb"); + if (file == XBADFILE) { + XFPRINTF(stderr, "error: fopen(%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..c30d0cd4 100644 --- a/src/ocsp/clu_ocsp.c +++ b/src/ocsp/clu_ocsp.c @@ -865,12 +865,9 @@ 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++; - } + /* Counts toward -nrequest for any request that got a response, even + * an OCSP-level error; only transport failures above are excluded. */ + 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 29b08219..94a7d1c9 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*/ @@ -99,10 +102,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: @@ -115,10 +127,8 @@ int wolfCLU_PKCS12(int argc, char** argv) break; case WOLFCLU_OUTFILE: - bioOut = wolfSSL_BIO_new_file(optarg, "wb"); + bioOut = wolfCLU_OpenKeyFileBio(optarg); if (bioOut == NULL) { - wolfCLU_LogError("Unable to open output file %s", - optarg); ret = WOLFCLU_FATAL_ERROR; } break; @@ -226,10 +236,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); @@ -241,6 +256,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/pkcs/clu_pkcs8.c b/src/pkcs/clu_pkcs8.c index 9c4c7049..d91a0cfa 100644 --- a/src/pkcs/clu_pkcs8.c +++ b/src/pkcs/clu_pkcs8.c @@ -109,10 +109,8 @@ int wolfCLU_PKCS8(int argc, char** argv) break; case WOLFCLU_OUTFILE: - bioOut = wolfSSL_BIO_new_file(optarg, "wb"); + bioOut = wolfCLU_OpenKeyFileBio(optarg); if (bioOut == NULL) { - wolfCLU_LogError("Unable to open output file %s", - optarg); ret = WOLFCLU_FATAL_ERROR; } break; diff --git a/src/pkey/clu_pkey.c b/src/pkey/clu_pkey.c index 4d98a78b..7a6b9854 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); } } @@ -423,6 +425,7 @@ int wolfCLU_pKeySetup(int argc, char** argv) int pubOut = 0; int option; int longIndex = 1; + char* outPath = NULL; WOLFSSL_EVP_PKEY *pkey = NULL; WOLFSSL_BIO *bioIn = NULL; WOLFSSL_BIO *bioOut = NULL; @@ -455,12 +458,7 @@ int wolfCLU_pKeySetup(int argc, char** argv) break; case WOLFCLU_OUTFILE: - bioOut = wolfSSL_BIO_new_file(optarg, "wb"); - if (bioOut == NULL) { - wolfCLU_LogError("Unable to open output file %s", - optarg); - ret = WOLFCLU_FATAL_ERROR; - } + outPath = optarg; break; case WOLFCLU_INFORM: @@ -511,6 +509,22 @@ int wolfCLU_pKeySetup(int argc, char** argv) } } + if (ret == WOLFCLU_SUCCESS && outPath != NULL) { + if (pubOut) { + bioOut = wolfSSL_BIO_new_file(outPath, "wb"); + if (bioOut == NULL) { + wolfCLU_LogError("Unable to open output file %s", outPath); + ret = WOLFCLU_FATAL_ERROR; + } + } + else { + bioOut = wolfCLU_OpenKeyFileBio(outPath); + 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 fa748e41..568591cd 100644 --- a/src/pkey/clu_rsa.c +++ b/src/pkey/clu_rsa.c @@ -101,10 +101,8 @@ int wolfCLU_RSA(int argc, char** argv) break; case WOLFCLU_OUTFILE: - bioOut = wolfSSL_BIO_new_file(optarg, "wb"); + bioOut = wolfCLU_OpenKeyFileBio(optarg); if (bioOut == NULL) { - wolfCLU_LogError("unable to open out file %s", - optarg); ret = WOLFCLU_FATAL_ERROR; } break; @@ -243,6 +241,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 { @@ -264,18 +265,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 (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 dc34e6b2..29eadd72 100644 --- a/src/sign-verify/clu_dgst_setup.c +++ b/src/sign-verify/clu_dgst_setup.c @@ -590,12 +590,15 @@ int wolfCLU_dgst_setup(int argc, char** argv) int j; for (j = 0; dgst_options[j].name != NULL; j++) { + /* An option name always wins, even if a same-named file exists + * on disk: wolfCLU_GetOpt below parses it as that flag either + * way, so treating it as data here would read it twice. */ 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; } @@ -689,6 +692,11 @@ 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); + return WOLFCLU_SUCCESS; case ':': case '?': @@ -705,6 +713,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 831dc8da..63682ba5 100644 --- a/src/sign-verify/clu_sign.c +++ b/src/sign-verify/clu_sign.c @@ -202,7 +202,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,38 +239,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 */ @@ -341,10 +311,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); @@ -373,7 +339,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; @@ -400,38 +365,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 */ @@ -525,10 +461,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); @@ -556,7 +488,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; @@ -583,38 +514,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 */ @@ -697,10 +599,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); @@ -727,7 +625,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; @@ -771,41 +668,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 */ @@ -872,9 +738,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); diff --git a/src/sign-verify/clu_verify.c b/src/sign-verify/clu_verify.c index da8db85b..ad3c9769 100644 --- a/src/sign-verify/clu_verify.c +++ b/src/sign-verify/clu_verify.c @@ -27,310 +27,95 @@ * 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. */ +/* Generous upper bound (PEM-encoded, with headroom) for a Dilithium/ML-DSA + * or XMSS/XMSS^MT public key file read via wolfCLU_ReadFileToBuffer. */ +#define WOLFCLU_VERIFY_MAX_PQ_KEY_SIZE 8192 + +/* Upper bound (256MB) on a signature, hash, or message file the verify + * path will read into memory. */ #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 hSz = 0; + int fSz; int ret = WOLFCLU_FATAL_ERROR; byte* hash = NULL; byte* data = NULL; - XFILE h; - XFILE f; if (sig == NULL) { return BAD_FUNC_ARG; } - f = XFOPEN(sig, "rb"); - if (f == NULL) { - wolfCLU_LogError("unable to open file %s", sig); - return BAD_FUNC_ARG; - } - - XFSEEK(f, 0, SEEK_END); - fSz = XFTELL(f); - - if (fSz < 0) { - wolfCLU_LogError("Invalid Sig File %s.", sig); - XFCLOSE(f); - return BAD_FUNC_ARG; - } - - if (fSz > WOLFCLU_MAX_FILE_SIZE) { - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", sig, (unsigned)WOLFCLU_MAX_FILE_SIZE); - XFCLOSE(f); + if (wolfCLU_ReadFileToBuffer(sig, WOLFCLU_MAX_FILE_SIZE, &data, &fSz) != + WOLFCLU_SUCCESS) { return WOLFCLU_FATAL_ERROR; } - data = (byte*)XMALLOC(fSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (data == NULL) { - XFCLOSE(f); - return MEMORY_E; - } - if (XFSEEK(f, 0, SEEK_SET) != 0 || (long)XFREAD(data, 1, fSz, f) != fSz) { - XFCLOSE(f); - XFREE(data, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - return WOLFCLU_FATAL_ERROR; - } - XFCLOSE(f); - 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: - h = XFOPEN(hashFile,"rb"); - if (h == NULL) { - wolfCLU_LogError("unable to open file %s", hashFile); - ret = BAD_FUNC_ARG; - break; - } - - XFSEEK(h, 0, SEEK_END); - hSz = XFTELL(h); - - if (hSz < 0) { - wolfCLU_LogError("Unable to Get Size of Hash File %s.", - hashFile); - ret = BAD_FUNC_ARG; - XFCLOSE(h); - break; - } - - if (hSz > WOLFCLU_MAX_FILE_SIZE) { - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", hashFile, (unsigned)WOLFCLU_MAX_FILE_SIZE); + if (wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, &hash, &hSz) != + WOLFCLU_SUCCESS) { ret = WOLFCLU_FATAL_ERROR; - XFCLOSE(h); - break; - } - - hash = (byte*)XMALLOC(hSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (hash == NULL) { - ret = MEMORY_E; - XFCLOSE(h); break; } - - if (XFSEEK(h, 0, SEEK_SET) != 0 || (int)XFREAD(hash, 1, hSz, h) != hSz) { - XFCLOSE(h); - XFREE(hash, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(data, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - return WOLFCLU_FATAL_ERROR; - } - XFCLOSE(h); - 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 - h = XFOPEN(hashFile, "rb"); - if (h == NULL) { - wolfCLU_LogError("unable to open file %s", hashFile); - ret = BAD_FUNC_ARG; - break; - } - - XFSEEK(h, 0, SEEK_END); - hSz = XFTELL(h); - - - if (hSz < 0) { - wolfCLU_LogError("Unable to Get Size of Hash File %s.", - hashFile); - ret = BAD_FUNC_ARG; - XFCLOSE(h); - break; - } - - if (hSz > WOLFCLU_MAX_FILE_SIZE) { - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", hashFile, (unsigned)WOLFCLU_MAX_FILE_SIZE); + if (wolfCLU_ReadFileToBuffer(hashFile, WOLFCLU_MAX_FILE_SIZE, &hash, &hSz) != + WOLFCLU_SUCCESS) { ret = WOLFCLU_FATAL_ERROR; - XFCLOSE(h); - break; - } - - hash = (byte*)XMALLOC(hSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (hash == NULL) { - ret = MEMORY_E; - XFCLOSE(h); break; } - - if (XFSEEK(h, 0, SEEK_SET) != 0 || (int)XFREAD(hash, 1, hSz, h) != hSz) { - XFCLOSE(h); - XFREE(hash, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(data, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - return WOLFCLU_FATAL_ERROR; - } - XFCLOSE(h); - 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: - /* hashFIle means msgFile */ - h = XFOPEN(hashFile, "rb"); - if (h == NULL) { - wolfCLU_LogError("unable to open file %s", hashFile); - ret = BAD_FUNC_ARG; - break; - } - - /* hSz means msgLen */ - XFSEEK(h, 0, SEEK_END); - hSz = XFTELL(h); - - if (hSz < 0) { - wolfCLU_LogError("Unable to Get Size of Hash File %s.", - hashFile); - ret = BAD_FUNC_ARG; - XFCLOSE(h); - break; - } - - if (hSz > WOLFCLU_MAX_FILE_SIZE) { - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", hashFile, (unsigned)WOLFCLU_MAX_FILE_SIZE); + /* 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; - XFCLOSE(h); - break; - } - - /* hash means msg */ - hash = (byte*)XMALLOC(hSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (hash == NULL) { - ret = MEMORY_E; - XFCLOSE(h); break; } - if (XFSEEK(h, 0, SEEK_SET) != 0 || (int)XFREAD(hash, 1, hSz, h) != hSz) { - XFCLOSE(h); - XFREE(hash, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(data, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - return WOLFCLU_FATAL_ERROR; - } - XFCLOSE(h); - - 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: - /* hashFIle means msgFile */ - h = XFOPEN(hashFile, "rb"); - if (h == NULL) { - wolfCLU_LogError("unable to open file %s", hashFile); - ret = BAD_FUNC_ARG; - break; - } - - /* hSz means msgLen */ - XFSEEK(h, 0, SEEK_END); - hSz = XFTELL(h); - - - if (hSz < 0) { - wolfCLU_LogError("Unable to Get Size of Hash File %s.", - hashFile); - ret = BAD_FUNC_ARG; - XFCLOSE(h); - break; - } - - if (hSz > WOLFCLU_MAX_FILE_SIZE) { - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", hashFile, (unsigned)WOLFCLU_MAX_FILE_SIZE); + /* 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; - XFCLOSE(h); break; } - /* hash means msg */ - hash = (byte*)XMALLOC(hSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (hash == NULL) { - ret = MEMORY_E; - XFCLOSE(h); - break; - } - - if (XFSEEK(h, 0, SEEK_SET) != 0 || (int)XFREAD(hash, 1, hSz, h) != hSz) { - XFCLOSE(h); - XFREE(hash, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(data, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - return WOLFCLU_FATAL_ERROR; - } - XFCLOSE(h); - - 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: - /* hashFIle means msgFile */ - h = XFOPEN(hashFile, "rb"); - if (h == NULL) { - wolfCLU_LogError("unable to open file %s", hashFile); - ret = BAD_FUNC_ARG; - break; - } - - /* hSz means msgLen */ - XFSEEK(h, 0, SEEK_END); - hSz = XFTELL(h); - - if (hSz < 0) { - wolfCLU_LogError("Unable to Get Size of Hash File %s.", - hashFile); - ret = BAD_FUNC_ARG; - XFCLOSE(h); - break; - } - - if (hSz > WOLFCLU_MAX_FILE_SIZE) { - wolfCLU_LogError("File: %s exceeds max size of 0x%X " - "bytes.", hashFile, (unsigned)WOLFCLU_MAX_FILE_SIZE); + /* 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; - XFCLOSE(h); break; } - /* hash means msg */ - hash = (byte*)XMALLOC(hSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - if (hash == NULL) { - ret = MEMORY_E; - XFCLOSE(h); - break; - } - - if (XFSEEK(h, 0, SEEK_SET) != 0 || (int)XFREAD(hash, 1, hSz, h) != hSz) { - XFCLOSE(h); - XFREE(hash, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(data, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); - return WOLFCLU_FATAL_ERROR; - } - XFCLOSE(h); - - 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: @@ -353,9 +138,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; @@ -371,42 +155,14 @@ 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, RSA_MAX_SIZE / 8 * 16, + &keyBuf, &keyFileSz) == WOLFCLU_SUCCESS) ? 0 : + WOLFCLU_FATAL_ERROR; } /* convert PEM to DER if necessary */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&keyBuf, (int)keyFileSz, pubIn); + ret = wolfCLU_KeyPemToDer(&keyBuf, keyFileSz, pubIn); if (ret < 0) { if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { WOLFCLU_LOG(WOLFCLU_L0, @@ -426,8 +182,7 @@ int wolfCLU_verify_signature_rsa(byte* sig, char* out, int sigSz, char* keyPath, if (pubIn == 1) { /* decode public key from DER-encoded input */ if (ret == 0) { - ret = wc_RsaPublicKeyDecode(keyBuf, &index, &key, - (word32)keyFileSz); + ret = wc_RsaPublicKeyDecode(keyBuf, &index, &key, keyFileSz); if (ret != 0) { wolfCLU_LogError("Failed to decode public key from DER.\nRET: %d", ret); } @@ -436,8 +191,7 @@ int wolfCLU_verify_signature_rsa(byte* sig, char* out, int sigSz, char* keyPath, else { /* retrieve private key and store in the RsaKey */ if (ret == 0) { - ret = wc_RsaPrivateKeyDecode(keyBuf, &index, &key, - (word32)keyFileSz); + ret = wc_RsaPrivateKeyDecode(keyBuf, &index, &key, keyFileSz); if (ret != 0) { wolfCLU_LogError("Failed to decode private key.\nRET: %d", ret); } @@ -485,10 +239,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); } @@ -511,11 +261,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; @@ -531,42 +280,14 @@ 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, MAX_ECC_BITS_NEEDED / 8 * 16, + &keyBuf, &keyFileSz) == WOLFCLU_SUCCESS) ? 0 : + WOLFCLU_FATAL_ERROR; } /* convert PEM to DER if necessary */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&keyBuf, (int)keyFileSz, pubIn); + ret = wolfCLU_KeyPemToDer(&keyBuf, keyFileSz, pubIn); if (ret < 0) { if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { WOLFCLU_LOG(WOLFCLU_L0, @@ -586,7 +307,7 @@ int wolfCLU_verify_signature_ecc(byte* sig, int sigSz, byte* hash, int hashSz, if (pubIn == 1) { /* retrieve public key and store in the Ecc key */ if (ret == 0) { - ret = wc_EccPublicKeyDecode(keyBuf, &index, &key, (word32)keyFileSz); + ret = wc_EccPublicKeyDecode(keyBuf, &index, &key, keyFileSz); if (ret < 0 ) { wolfCLU_LogError("Failed to decode public key.\nRET: %d", ret); } @@ -595,7 +316,7 @@ int wolfCLU_verify_signature_ecc(byte* sig, int sigSz, byte* hash, int hashSz, else { /* retrieve private key and store in the Ecc Key */ if (ret == 0) { - ret = wc_EccPrivateKeyDecode(keyBuf, &index, &key, (word32)keyFileSz); + ret = wc_EccPrivateKeyDecode(keyBuf, &index, &key, keyFileSz); if (ret != 0 ) { wolfCLU_LogError("Failed to decode Ecc private key.\nRET: %d", ret); } @@ -663,10 +384,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); } @@ -690,9 +407,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; @@ -706,42 +422,14 @@ 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, ED25519_PRV_KEY_SIZE * 16, + &keyBuf, &keyFileSz) == WOLFCLU_SUCCESS) ? 0 : + WOLFCLU_FATAL_ERROR; } /* convert PEM to DER if necessary */ if (inForm == PEM_FORM && ret == 0) { - ret = wolfCLU_KeyPemToDer(&keyBuf, (int)keyFileSz, pubIn); + ret = wolfCLU_KeyPemToDer(&keyBuf, keyFileSz, pubIn); if (ret < 0) { if (ret == WC_NO_ERR_TRACE(ASN_NO_PEM_HEADER)) { WOLFCLU_LOG(WOLFCLU_L0, @@ -768,8 +456,7 @@ int wolfCLU_verify_signature_ed25519(byte* sig, int sigSz, } /* decode public key from DER-encoded input */ else { - ret = wc_Ed25519PublicKeyDecode(keyBuf, &index, &key, - (word32)keyFileSz); + ret = wc_Ed25519PublicKeyDecode(keyBuf, &index, &key, keyFileSz); if (ret != 0) { wolfCLU_LogError("Failed to decode public key from DER.\nRET: %d", ret); } @@ -787,8 +474,7 @@ int wolfCLU_verify_signature_ed25519(byte* sig, int sigSz, } } else { - ret = wc_Ed25519PrivateKeyDecode(keyBuf, &index, &key, - (word32)keyFileSz); + ret = wc_Ed25519PrivateKeyDecode(keyBuf, &index, &key, keyFileSz); if (ret != 0) { wolfCLU_LogError("Failed to import private key.\nRET: %d", ret); } @@ -819,10 +505,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); } @@ -842,9 +524,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; @@ -874,54 +555,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, WOLFCLU_VERIFY_MAX_PQ_KEY_SIZE, + &keyBuf, &keyFileSz) != WOLFCLU_SUCCESS) { wc_dilithium_free(key); #ifdef WOLFSSL_SMALL_STACK XFREE(key, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); @@ -929,16 +564,14 @@ 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); + ret = wolfCLU_KeyPemToDer(&keyBuf, 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); @@ -1011,9 +644,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 */ @@ -1038,48 +670,9 @@ 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, WOLFCLU_VERIFY_MAX_PQ_KEY_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) { - 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; } } @@ -1128,7 +721,7 @@ int wolfCLU_verify_signature_xmss(byte* sig, int sigSz, /* import the public key */ if (ret == 0) { - ret = wc_XmssKey_ImportPubRaw(key, keyBuf, (word32)keyFileSz); + ret = wc_XmssKey_ImportPubRaw(key, keyBuf, keyFileSz); if (ret != 0) { wolfCLU_LogError("Failed to decode public key." "\nRET: %d", ret); @@ -1147,9 +740,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); } @@ -1179,9 +769,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 */ @@ -1206,47 +795,9 @@ 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) { - ret = WOLFCLU_FATAL_ERROR; - wolfCLU_LogError("Failed to get size of public key FILE."); - } - else if (keyFileSz > WOLFCLU_MAX_FILE_SIZE) { - 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) { + if (wolfCLU_ReadFileToBuffer(pubKey, WOLFCLU_VERIFY_MAX_PQ_KEY_SIZE, + &keyBuf, &keyFileSz) != WOLFCLU_SUCCESS) { ret = WOLFCLU_FATAL_ERROR; - wolfCLU_LogError("Failed to read public key.\nRET: %d", ret); - } - else { - XFCLOSE(keyFile); - keyFile = NULL; } } @@ -1310,7 +861,7 @@ int wolfCLU_verify_signature_xmssmt(byte* sig, int sigSz, /* import the public key */ if (ret == 0) { - ret = wc_XmssKey_ImportPubRaw(key, keyBuf, (word32)keyFileSz); + ret = wc_XmssKey_ImportPubRaw(key, keyBuf, keyFileSz); if (ret != 0) { wolfCLU_LogError("Failed to decode public key." "\nRET: %d", ret); @@ -1329,9 +880,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/x509/clu_request_setup.c b/src/x509/clu_request_setup.c index 6cea40c0..b075c637 100644 --- a/src/x509/clu_request_setup.c +++ b/src/x509/clu_request_setup.c @@ -1110,7 +1110,7 @@ int wolfCLU_requestSetup(int argc, char** argv) WOLFSSL_BIO* keyOutBio; if (keyOut != NULL) { - keyOutBio = wolfSSL_BIO_new_file(keyOut, "wb"); + keyOutBio = wolfCLU_OpenKeyFileBio(keyOut); } else { keyOutBio = wolfSSL_BIO_new(wolfSSL_BIO_s_file()); @@ -1123,7 +1123,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; } diff --git a/src/x509/clu_x509_sign.c b/src/x509/clu_x509_sign.c index 75d5b382..84220a5b 100644 --- a/src/x509/clu_x509_sign.c +++ b/src/x509/clu_x509_sign.c @@ -664,26 +664,40 @@ 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); + { + /* Maps to the same NIDs as + * wolfCLU_SetCertNameFieldByNid(). */ + int subjNid = 0; + + if (XSTRCMP(key, "C") == 0) { + subjNid = NID_countryName; + } + else if (XSTRCMP(key, "ST") == 0) { + subjNid = NID_stateOrProvinceName; + } + else if (XSTRCMP(key, "L") == 0) { + subjNid = NID_localityName; + } + else if (XSTRCMP(key, "O") == 0) { + subjNid = NID_organizationName; + } + else if (XSTRCMP(key, "OU") == 0) { + subjNid = NID_organizationalUnitName; + } + else if (XSTRCMP(key, "CN") == 0) { + subjNid = NID_commonName; + } + else if (XSTRCMP(key, "emailAddress") == 0) { + subjNid = NID_emailAddress; + } + + if (subjNid != 0) { + ret = wolfCLU_SetCertNameFieldByNid(&newCert.subject, + subjNid, value, (int)XSTRLEN(value)); + if (ret != WOLFCLU_SUCCESS) { + break; + } + } } token = XSTRTOK(NULL, "/", &slash); From 4c115ad577f034ad63e401b81b911cacb89a7e15 Mon Sep 17 00:00:00 2001 From: Emma Stensland Date: Mon, 20 Jul 2026 16:03:16 -0600 Subject: [PATCH 4/4] Update integration tests for refactored commands --- tests/dgst/dgst-test.py | 91 +++++++++++++- tests/encrypt/enc-test.py | 6 +- tests/genkey_sign_ver/genkey-sign-ver-test.py | 119 ++++++++++++++++++ tests/ocsp/ocsp-test.py | 30 +++++ tests/pkcs/pkcs12-test.py | 30 +++++ tests/x509/x509-ca-test.py | 36 ++++++ 6 files changed, 306 insertions(+), 6 deletions(-) diff --git a/tests/dgst/dgst-test.py b/tests/dgst/dgst-test.py index 456224b5..a5b2de37 100644 --- a/tests/dgst/dgst-test.py +++ b/tests/dgst/dgst-test.py @@ -110,6 +110,70 @@ 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): + """The trailing "-signature " value must not be mistaken for + a positional data file just because the file exists on disk; + with no positional file, data must still come from stdin. + + Regression test: the last-arg heuristic used to skip its whole + option-matching loop whenever the last token was an existing file, + so an existing -signature/-verify value was silently read as the + data to hash instead of falling back to 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") must always be parsed as that option, never as the + positional data file, even if a same-named file exists on disk. + + wolfCLU_GetOpt's argv scan parses the token as a flag regardless of + the last-arg heuristic's choice, so treating it as data too would + read it twice under conflicting interpretations. Data must fall + back to stdin instead: sign real content under a normal name, then + verify with a same-named-as-a-flag decoy file as the trailing arg + while feeding the *real* content over stdin. Verification must + succeed, proving stdin (not the on-disk decoy) was hashed. + """ + 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" @@ -178,8 +242,10 @@ 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" is intentionally excluded: it's a + # checked-in fixture read by 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) @@ -192,7 +258,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) @@ -540,6 +608,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..7322b07d 100644 --- a/tests/genkey_sign_ver/genkey-sign-ver-test.py +++ b/tests/genkey_sign_ver/genkey-sign-ver-test.py @@ -2,6 +2,7 @@ """Key generation, signing, and verification tests for wolfCLU.""" import os +import subprocess import sys import unittest @@ -327,6 +328,124 @@ def test_rsa_sign_invalid_key_fails(self): "RSA signing with empty key should have failed") +def _icacls_entries(path): + """Return the list of ACE description strings icacls reports for path, + one string per trustee (e.g. "DOMAIN\\user:(F)").""" + r = subprocess.run(["icacls", path], capture_output=True, text=True, + timeout=10) + if r.returncode != 0: + raise RuntimeError("icacls {} failed: {}".format(path, r.stderr)) + + entries = [] + for line in r.stdout.splitlines(): + line = line.rstrip() + if not line: + break + if line.lower().startswith("successfully processed"): + break + if line.startswith(path): + line = line[len(path):].strip() + else: + line = line.strip() + if line: + entries.append(line) + return entries + + +class KeyFilePermissionsTest(unittest.TestCase): + """wolfCLU_OpenKeyFile must write private keys with owner-only + permissions (POSIX 0600 / Windows single-owner ACE), and must replace + (not append to) a pre-existing file. + + On Windows this exercises the SDDL/CreateFileA branch of + wolfCLU_OpenKeyFile via icacls instead of POSIX mode bits, since NTFS + ACLs are the actual enforcement mechanism there, not the mode bits + os.stat() reports. + """ + + @classmethod + def tearDownClass(cls): + _cleanup_files(_TEMP_FILES) + _TEMP_FILES.clear() + + def _assert_owner_only(self, priv, label): + if os.name == "nt": + entries = _icacls_entries(priv) + self.assertEqual(len(entries), 1, + "{}: expected exactly one owner-only ACL " + "entry, got: {}".format(label, entries)) + entry = entries[0] + self.assertIn("(F)", entry, + "{}: owner ACE missing full control: {}" + .format(label, entry)) + for forbidden in ("Everyone", "Authenticated Users", + "BUILTIN\\Users", "NT AUTHORITY"): + self.assertNotIn(forbidden, entry, + "{}: unexpected broad-access principal " + "{!r} in ACL: {}" + .format(label, forbidden, entry)) + else: + mode = os.stat(priv).st_mode & 0o777 + self.assertEqual(mode, 0o600, + "{}: private key file mode is {:o}, expected " + "600".format(label, mode)) + + 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 + + def test_rsa_priv_key_mode_is_owner_only(self): + priv = self._priv_mode("rsakey-perm-test") + self._assert_owner_only(priv, "RSA") + + 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) + self._assert_owner_only(priv, "ECC") + + 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) + self._assert_owner_only(priv, "Ed25519") + + 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") + if os.name != "nt": + 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") + + self._assert_owner_only(priv, "replaced RSA") + + @unittest.skipUnless(_has_algorithm("dilithium"), "dilithium not available") class DilithiumTest(_GenkeySignVerifyBase): diff --git a/tests/ocsp/ocsp-test.py b/tests/ocsp/ocsp-test.py index 077be417..1c974727 100644 --- a/tests/ocsp/ocsp-test.py +++ b/tests/ocsp/ocsp-test.py @@ -306,6 +306,36 @@ 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 that still gets an HTTP/OCSP-level + error response must count toward -nrequest, same as a successful + request -- otherwise 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) + + time.sleep(0.5) # let responder shut down + log = resp.read_log() + 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/x509-ca-test.py b/tests/x509/x509-ca-test.py index 514d00d7..5b13283d 100644 --- a/tests/x509/x509-ca-test.py +++ b/tests/x509/x509-ca-test.py @@ -727,6 +727,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: + # If req fails early because the build's wolfSSL enforces + # CTC_NAME_SIZE in the compat layer (e.g. on Windows CI), + # that's fine too - it didn't silently 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") + class TestCAOutdirPath(unittest.TestCase):