diff --git a/CLAUDE.md b/CLAUDE.md index af2a438..5433a82 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,6 +32,9 @@ configure time if the installed wolfSSL lacks any of `HAVE_PKCS7`, `WOLFSSL_KEY_GEN`, `WOLF_CRYPTO_CB`, `WOLFSSL_BASE64_ENCODE`, `WOLFSSL_ALT_NAMES`, or `WOLFSSL_CERT_NAME_ALL`, or if it was built with `NO_AES` / `NO_SHA256`, or if it provides neither TLS 1.2 nor TLS 1.3. +With SCEP enabled it also needs AES-128-CBC encrypt and decrypt +(`NO_AES_128`, `NO_AES_CBC` or `NO_AES_DECRYPT` hard-fail), since RFC 8894 +makes it mandatory-to-implement. It also link-probes the `WOLFSSL_ASN_API` helpers it calls, which a shared libwolfssl exports only under one of `WOLFSSL_PUBLIC_ASN` (the lean choice), `OPENSSL_EXTRA`, `OPENSSL_EXTRA_X509_SMALL` or `WOLFSSL_TEST_CERT`; a static diff --git a/CMakeLists.txt b/CMakeLists.txt index 30c8e8e..7141436 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,6 +227,23 @@ else() "the rsa:* key type will return WOLFCERT_ERR_UNSUPPORTED.") endif() +# AES-128-CBC: mandatory-to-implement for SCEP (RFC 8894 section 2.9). +if(WOLFCERT_ENABLE_SCEP) + unset(_have_aes128cbc CACHE) + check_c_source_compiles("${_wc_wolfssl_prologue} + #if !defined(WOLFSSL_AES_128) || !defined(HAVE_AES_CBC) || !defined(HAVE_AES_DECRYPT) + #error noaes128cbc + #endif + int main(void) { return 0; } + " _have_aes128cbc) + if(NOT _have_aes128cbc) + message(FATAL_ERROR + "wolfCert SCEP requires AES-128-CBC (RFC 8894). Rebuild wolfSSL " + "without NO_AES_128, NO_AES_CBC or NO_AES_DECRYPT, or set " + "-DWOLFCERT_ENABLE_SCEP=OFF for an EST-only build.") + endif() +endif() + # TLS: the HTTPS transport needs at least TLS 1.2 or TLS 1.3; the endpoints pin # their floor to whichever lower version is available. unset(_have_tls12 CACHE) diff --git a/Makefile.am b/Makefile.am index 7db25c1..17ba5c1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -196,6 +196,7 @@ test_scep_roundtrip_SOURCES = tests/integration/test_scep_roundtrip.c test_scep_roundtrip_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src test_scep_roundtrip_LDADD = libwolfcert.la $(WOLFSSL_LIBS) -lpthread test_scep_poll_roundtrip_SOURCES = tests/integration/test_scep_poll_roundtrip.c +test_scep_poll_roundtrip_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src test_scep_poll_roundtrip_LDADD = libwolfcert.la $(WOLFSSL_LIBS) -lpthread test_scep_async_roundtrip_SOURCES = tests/integration/test_scep_async_roundtrip.c test_scep_async_roundtrip_LDADD = libwolfcert.la $(WOLFSSL_LIBS) -lpthread diff --git a/cli/wolfcert_client.c b/cli/wolfcert_client.c index 741b252..760c961 100644 --- a/cli/wolfcert_client.c +++ b/cli/wolfcert_client.c @@ -179,8 +179,8 @@ typedef struct { const char* url; const char* trust_file; const char* user; - const char* pass; - const char* challenge; + char* pass; + char* challenge; const char* client_cert_file; const char* client_key_file; const char* key_type; @@ -227,8 +227,24 @@ static int opt_append(const char*** arr, size_t* len, size_t* cap, return 0; } +/* Copy a secret option value, then wipe it from argv so ps cannot show it. */ +static int opt_secret(char** dst, char* arg) +{ + if (*dst != NULL) + free_secret(*dst, strlen(*dst)); + + *dst = strdup(arg); + wc_ForceZero(arg, (word32)strlen(arg)); + + return *dst == NULL ? -1 : 0; +} + static void opts_free(Opts* opts) { + if (opts->pass != NULL) + free_secret(opts->pass, strlen(opts->pass)); + if (opts->challenge != NULL) + free_secret(opts->challenge, strlen(opts->challenge)); free(opts->san_dns); free(opts->san_ip); free(opts->san_uri); @@ -292,10 +308,12 @@ static int parse_common(int argc, char** argv, Opts* opts) opts->user = optarg; break; case 'P': - opts->pass = optarg; + if (opt_secret(&opts->pass, optarg) != 0) + return -1; break; case 'X': - opts->challenge = optarg; + if (opt_secret(&opts->challenge, optarg) != 0) + return -1; break; case 'M': opts->client_cert_file = optarg; diff --git a/cli/wolfcert_server.c b/cli/wolfcert_server.c index 96ba0b5..4f92e50 100644 --- a/cli/wolfcert_server.c +++ b/cli/wolfcert_server.c @@ -58,7 +58,7 @@ static void print_usage(FILE* out) { fprintf(out, "wolfcert-server %s\n" - "Usage: wolfcert-server --proto est|scep [--listen HOST:PORT]\n" + "Usage: wolfcert-server --proto est|scep [--listen ADDR:PORT]\n" " [--basic USER:PASS] [--challenge PASS]\n" " [--tls-cert PEM --tls-key PEM [--tls-client-ca PEM]]\n" " [--scep-require-approval] [--scep-enable-next-ca]\n" @@ -66,7 +66,7 @@ static void print_usage(FILE* out) "\n" "Options:\n" " --proto est|scep Protocol to serve (required)\n" - " --listen HOST:PORT Bind address (default 0.0.0.0:8080)\n" + " --listen ADDR:PORT Numeric IPv4 bind address (default 0.0.0.0:8080)\n" " --basic USER:PASS Require HTTP Basic auth (EST enroll)\n" " --challenge PASS Require this SCEP challengePassword in the CSR\n" " --tls-cert PEMFILE Terminate TLS with this server certificate (PEM);\n" @@ -88,7 +88,8 @@ static void print_usage(FILE* out) " TLS 1.3 post-handshake auth (RFC 8446 section 4.6.2): initial\n" " handshake is anonymous, client cert is requested when\n" " EST /simpleenroll is hit on the kept-alive connection.\n" - " Requires --tls-cert/-key and --tls-client-ca.\n" + " Requires --proto est, --tls-cert/-key and\n" + " --tls-client-ca.\n" " --csrattrs-file PATH Serve this DER-encoded CsrAttrs blob (RFC 7030 section 4.5.2)\n" " from GET /.well-known/est/csrattrs; without this the\n" " server answers 204 No Content.\n" @@ -120,14 +121,27 @@ static int parse_listen(const char* arg, char** host, uint16_t* port) return 0; } -static int parse_basic(const char* arg, char** user, char** pass) +static void free_secret(char* s) { - const char* colon = strchr(arg, ':'); + if (s != NULL) { + wc_ForceZero(s, (word32)strlen(s)); + free(s); + } +} + +static int parse_basic(char* arg, char** user, char** pass) +{ + char* colon = strchr(arg, ':'); if (colon == NULL) return -1; + free(*user); + free_secret(*pass); *user = strndup(arg, (size_t)(colon - arg)); *pass = strdup(colon + 1); + wc_ForceZero(colon + 1, (word32)strlen(colon + 1)); + if (*user == NULL || *pass == NULL) + return -2; return 0; } @@ -203,6 +217,7 @@ int main(int argc, char** argv) uint8_t* csr_attrs_blob = NULL; size_t csr_attrs_blob_len = 0; int est_require_csr_attrs = 0; + int basic_rc; int c; while ((c = getopt_long(argc, argv, "", opts, NULL)) != -1) { @@ -217,13 +232,24 @@ int main(int argc, char** argv) } break; case 'b': - if (parse_basic(optarg, &user, &pass) != 0) { + basic_rc = parse_basic(optarg, &user, &pass); + if (basic_rc == -2) { + fprintf(stderr, "out of memory copying --basic\n"); + return 1; + } + if (basic_rc != 0) { fprintf(stderr, "invalid --basic (expected USER:PASS)\n"); return 1; } break; case 'X': - challenge = optarg; + free_secret(challenge); + challenge = strdup(optarg); + wc_ForceZero(optarg, (word32)strlen(optarg)); + if (challenge == NULL) { + fprintf(stderr, "out of memory copying --challenge\n"); + return 1; + } break; case 'C': tls_cert = slurp(optarg, &tls_cert_len); @@ -339,7 +365,8 @@ int main(int argc, char** argv) free(csr_attrs_blob); free(host); free(user); - free(pass); + free_secret(pass); + free_secret(challenge); free(tls_cert); free(tls_key); free(tls_ca); @@ -376,7 +403,10 @@ int main(int argc, char** argv) int rc = wolfcert_server_start(&cfg, &g_server); if (rc != WOLFCERT_OK) { + const char* m = wolfcert_last_error_message(); fprintf(stderr, "wolfcert-server: start failed (%s)\n", wolfcert_strerror(rc)); + if (m != NULL && *m != '\0') + fprintf(stderr, "wolfcert-server: %s\n", m); goto out; } @@ -396,7 +426,8 @@ int main(int argc, char** argv) g_server = NULL; free(host); free(user); - free(pass); + free_secret(pass); + free_secret(challenge); free(tls_cert); free(tls_key); free(tls_ca); diff --git a/configure.ac b/configure.ac index 3a3aea3..31b57d1 100644 --- a/configure.ac +++ b/configure.ac @@ -269,6 +269,20 @@ AS_IF([test "x$have_rsa" = "xno"], RSA and the rsa:* key type will return WOLFCERT_ERR_UNSUPPORTED.])])]) AM_CONDITIONAL([WOLFCERT_HAVE_RSA], [test "x$have_rsa" = "xyes"]) +# AES-128-CBC: mandatory-to-implement for SCEP (RFC 8894 section 2.9). +AS_IF([test "x$enable_scep" = "xyes"], + [AC_MSG_CHECKING([whether wolfSSL provides AES-128-CBC]) + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM(WOLFCERT_WOLFSSL_PROLOGUE, + [[#if !defined(WOLFSSL_AES_128) || !defined(HAVE_AES_CBC) || !defined(HAVE_AES_DECRYPT) + #error noaes128cbc + #endif]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([wolfCert SCEP requires AES-128-CBC (RFC 8894). Rebuild + wolfSSL without NO_AES_128, NO_AES_CBC or NO_AES_DECRYPT, or configure + with --disable-scep.])])]) + # TLS: the HTTPS transport needs at least TLS 1.2 or TLS 1.3; the endpoints pin # their floor to whichever lower version is available. AC_MSG_CHECKING([whether wolfSSL provides TLS 1.2]) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 5e69c02..48c0d54 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -164,8 +164,10 @@ what enables the post-handshake-auth bootstrap below. **Test server PHA mode.** `WolfCertServerCfgSrv.tls_post_handshake_auth` turns the in-tree server into the other end of shape 3. It needs -`tls_client_ca_pem`; without it `wolfcert_server_start()` returns -`WOLFCERT_ERR_BAD_ARG`. The CTX gets `WOLFSSL_VERIFY_POST_HANDSHAKE` and not +`tls_client_ca_pem` and the EST protocol; without either +`wolfcert_server_start()` returns `WOLFCERT_ERR_BAD_ARG`, since no other +protocol requests the deferred certificate. The CTX gets +`WOLFSSL_VERIFY_POST_HANDSHAKE` and not `FAIL_IF_NO_PEER_CERT`, so a TLS 1.3 handshake stays anonymous and a TLS 1.2 client can still fetch `/cacerts`. A TLS 1.2 client is asked for its cert during the handshake, since TLS 1.2 has no PHA. On the first `/simpleenroll` diff --git a/docs/EMBEDDED.md b/docs/EMBEDDED.md index 00dd961..293e184 100644 --- a/docs/EMBEDDED.md +++ b/docs/EMBEDDED.md @@ -33,8 +33,9 @@ never `#define ... 0`, because the code tests presence with `#ifdef`.** result at compile time, so a contradictory or incomplete config fails with a clear `#error` rather than a confusing downstream error. It enforces the same rules the configure step does: at least one key algorithm, SCEP requires RSA -(RFC 8894), and the wolfSSL feature set wolfCert depends on (PKCS#7, cert -gen/req/ext, key gen, CryptoCb, base64 encode, alt names, +and AES-128-CBC (both RFC 8894, so `NO_AES_128`, `NO_AES_CBC` and +`NO_AES_DECRYPT` rule SCEP out), and the wolfSSL feature set wolfCert depends +on (PKCS#7, cert gen/req/ext, key gen, CryptoCb, base64 encode, alt names, `WOLFSSL_CERT_NAME_ALL`, AES, SHA-256, and TLS 1.2 or 1.3). The header you copy documents the matching wolfSSL configure flags. diff --git a/examples/user_settings.h.example b/examples/user_settings.h.example index 19fdbd6..e75eb73 100644 --- a/examples/user_settings.h.example +++ b/examples/user_settings.h.example @@ -44,7 +44,8 @@ * value of 0 still counts as enabled. * - At least one key algorithm must be enabled. * - SCEP is RSA-only (RFC 8894): enabling WOLFCERT_HAVE_SCEP requires - * WOLFCERT_HAVE_RSA. + * WOLFCERT_HAVE_RSA, and a wolfSSL with AES-128-CBC encrypt and decrypt + * (no NO_AES_128, NO_AES_CBC or NO_AES_DECRYPT). * * The enabled set must also be backed by the wolfSSL you link against. wolfCert * requires a wolfSSL (>= 5.9.4) built with at least: diff --git a/scripts/ci/assert-configure-fails.sh b/scripts/ci/assert-configure-fails.sh index 55cc970..c99438e 100755 --- a/scripts/ci/assert-configure-fails.sh +++ b/scripts/ci/assert-configure-fails.sh @@ -20,7 +20,8 @@ # of this gate. # # Scope: only misconfigurations that a *buildable* wolfSSL can express are -# covered here (RSA-off-with-SCEP, PKCS7 missing, ASN helpers not exported). +# covered here (RSA-off-with-SCEP, PKCS7 missing, ASN helpers not exported, +# AES-128 off with SCEP). # wolfSSL's own configure refuses to drop AES / SHA-256 / all TLS / all key # algorithms, so wolfCert's compile-time #error guards for those # (check_config.h) can't be fed by a real wolfSSL build and are not exercised @@ -56,6 +57,7 @@ CASE_TABLE=( "no-rsa-scep:neg-no-rsa:::SCEP is RSA-only" "no-pkcs7:neg-no-pkcs7:::HAVE_PKCS7|missing a required feature" "no-public-asn:neg-no-public-asn:::does not export its ASN helpers" + "no-aes128-scep:neg-no-aes128:::requires AES-128-CBC" ) lookup_case() { diff --git a/scripts/ci/build-wolfssl.sh b/scripts/ci/build-wolfssl.sh index 9b5d9eb..1749317 100755 --- a/scripts/ci/build-wolfssl.sh +++ b/scripts/ci/build-wolfssl.sh @@ -62,7 +62,7 @@ KNOWN_CONFIGS=( # wolfSSL's own configure refuses to drop AES/SHA-256/all-TLS/all-key-algs # (those are cascade-required), so wolfCert's compile-time #error guards for # them in check_config.h cannot be fed by a real wolfSSL build. - neg-no-rsa neg-no-pkcs7 neg-no-public-asn + neg-no-rsa neg-no-pkcs7 neg-no-public-asn neg-no-aes128 ) # Emit the configure argument list (one per line) for a config name. @@ -167,6 +167,10 @@ resolve_flags() { # No ASN-export macro -> "does not export its ASN helpers". _base_flags printf '%s\n' 'CPPFLAGS=-DWOLFSSL_ALT_NAMES -DWOLFSSL_CERT_NAME_ALL -DKEEP_PEER_CERT -DWOLFSSL_HAVE_TLS_UNIQUE' ;; + neg-no-aes128) + # NO_AES_128 with SCEP still requested -> "requires AES-128-CBC". + _base_flags + printf '%s\n' 'CPPFLAGS=-DWOLFSSL_ALT_NAMES -DWOLFSSL_CERT_NAME_ALL -DKEEP_PEER_CERT -DWOLFSSL_HAVE_TLS_UNIQUE -DWOLFSSL_PUBLIC_ASN -DNO_AES_128' ;; *) echo "ERROR: unknown wolfSSL config '$cfg'." >&2 echo " Known: ${KNOWN_CONFIGS[*]}" >&2 diff --git a/src/internal.c b/src/internal.c index 25c9b1f..9ae61f3 100644 --- a/src/internal.c +++ b/src/internal.c @@ -94,6 +94,20 @@ char* wolfcert_strdup(const char* s, void* heap) return r; } +int wolfcert_is_printable_string(const uint8_t* s, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + uint8_t c = s[i]; + if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && + (c < '0' || c > '9') && + (c == 0 || strchr(" '()+,-./:=?", c) == NULL)) + return 0; + } + return 1; +} + const char* wolfcert_last_error_message(void) { return g_err.message; diff --git a/src/internal.h b/src/internal.h index e11fcbb..9063415 100644 --- a/src/internal.h +++ b/src/internal.h @@ -177,7 +177,6 @@ typedef struct { struct WolfCertServer { WolfCertServerCfgSrv cfg; - char* cfg_bind_host; char* cfg_challenge; char* cfg_basic_user; char* cfg_basic_pass; @@ -396,6 +395,8 @@ WOLFCERT_TEST_VIS size_t wolfcert_oid_to_dotted(const uint8_t* oid, size_t oid_l char* out, size_t out_cap); /* SCEP pkiMessage helpers. */ +#define SCEP_NONCE_SZ 16 + typedef struct { const uint8_t* transaction_id; size_t transaction_id_len; @@ -507,6 +508,9 @@ WOLFCERT_TEST_VIS int wolfcert_scep_build_next_ca_response( int wolfcert_extract_spki(const uint8_t* der, size_t len, int is_csr, uint8_t** out_spki, size_t* out_len, void* heap); +/* 1 if every byte is in the X.680 PrintableString repertoire, else 0. */ +int wolfcert_is_printable_string(const uint8_t* s, size_t len); + /* RFC 8894: a CertRep must be signed by the CA or its RA. Confirm the response * signer certificate shares a public key with some certificate in the trusted * GetCACert bundle (one or more concatenated DER certs). Returns WOLFCERT_OK on diff --git a/src/scep/scep_client.c b/src/scep/scep_client.c index db625b4..a9c33f9 100644 --- a/src/scep/scep_client.c +++ b/src/scep/scep_client.c @@ -588,8 +588,6 @@ static const char* scep_renewal_msg_type(WolfCertScepRenewalMsgType m) return (m == WOLFCERT_SCEP_RENEWAL_MSG_PKCS_REQ) ? "19" : "17"; } -/* Shared SCEP round-trip sizes. */ -#define SCEP_NONCE_SZ 16 /* A random transactionID is 16 RNG bytes expanded to 32 hex characters. */ #define SCEP_TXID_RAND_SZ 16 @@ -727,18 +725,13 @@ static int scep_prepare(void* heap, const WolfCertScepCaps* caps, * can talk to a peer that requires a particular algorithm (e.g. a wolfSCEP * deployment expecting AES-256). AUTO keeps the RFC 8894 default: the * GetCACaps "AES" keyword advertises AES-128-CBC; otherwise fall back to - * the mandatory-to-implement triple DES-CBC. A wolfSSL built without 3DES - * cannot serve 3DES, so reject that request/fallback with a clear error - * instead of a cryptic encoder failure. */ + * triple DES-CBC. A wolfSSL built without 3DES cannot serve 3DES, so + * reject that request/fallback with a clear error instead of a cryptic + * encoder failure. */ switch (cipher) { case WOLFCERT_SCEP_CIPHER_AES128: -#if !defined(WOLFSSL_AES_128) || !defined(HAVE_AES_CBC) - return WOLFCERT_ERR(WOLFCERT_ERR_UNSUPPORTED, "scep", - "AES-128-CBC content cipher requested but wolfSSL lacks it"); -#else enc_oid = AES128CBCb; break; -#endif case WOLFCERT_SCEP_CIPHER_AES256: #if !defined(WOLFSSL_AES_256) || !defined(HAVE_AES_CBC) return WOLFCERT_ERR(WOLFCERT_ERR_UNSUPPORTED, "scep", @@ -757,21 +750,14 @@ static int scep_prepare(void* heap, const WolfCertScepCaps* caps, #endif case WOLFCERT_SCEP_CIPHER_AUTO: default: - /* The "AES" capability names AES-128-CBC and nothing else, so a - * wolfSSL that cannot do AES-128 has to take the 3DES path even - * against an AES-advertising peer rather than silently substitute - * a cipher the CA never offered. */ -#if defined(WOLFSSL_AES_128) && defined(HAVE_AES_CBC) if (caps != NULL && caps->aes) { enc_oid = AES128CBCb; } - else -#endif - { + else { #ifdef NO_DES3 return WOLFCERT_ERR(WOLFCERT_ERR_UNSUPPORTED, "scep", - "no usable content cipher: AES-128-CBC unavailable or " - "unadvertised, and wolfSSL lacks the 3DES fallback"); + "no usable content cipher: CA does not advertise AES and " + "wolfSSL lacks the 3DES fallback"); #else enc_oid = DES3b; #endif diff --git a/src/scep/scep_msg.c b/src/scep/scep_msg.c index 6752993..3dc4246 100644 --- a/src/scep/scep_msg.c +++ b/src/scep/scep_msg.c @@ -114,16 +114,20 @@ static int der_put_len(byte* out, size_t cap, size_t n) * here would land SET { SET { ... } } on the wire, which every other * RFC 8894 implementation rejects. * - * Returns total bytes written, or -1 if `cap` is too small. */ + * Returns total bytes written, WOLFCERT_ERR_BAD_ARG if `v` is not a + * PrintableString, or WOLFCERT_ERR_MEMORY if `cap` is too small. */ static int enc_printable_n(const byte* v, size_t vl, byte* out, size_t cap) { + if (!wolfcert_is_printable_string(v, vl)) + return WOLFCERT_ERR(WOLFCERT_ERR_BAD_ARG, "scep", + "attribute value is not a PrintableString"); if (cap < 1) - return -1; + return WOLFCERT_ERR_MEMORY; out[0] = 0x13; int ll = der_put_len(out + 1, cap - 1, vl); if (ll < 0 || 1 + (size_t)ll + vl > cap) - return -1; + return WOLFCERT_ERR_MEMORY; memcpy(out + 1 + ll, v, vl); return (int)(1 + (size_t)ll + vl); @@ -263,7 +267,7 @@ static int build_signed_attribs(const WolfCertScepAttrs* a, if (a->message_type != NULL) { int vl = enc_printable(a->message_type, scratch + off, scratch_cap - off); if (vl < 0) - return WOLFCERT_ERR_MEMORY; + return vl; attrs[n].oid = OID_MSG_TYPE; attrs[n].oidSz = sizeof(OID_MSG_TYPE); @@ -282,7 +286,7 @@ static int build_signed_attribs(const WolfCertScepAttrs* a, int vl = enc_printable_n(a->transaction_id, a->transaction_id_len, scratch + off, scratch_cap - off); if (vl < 0) - return WOLFCERT_ERR_MEMORY; + return vl; attrs[n].oid = OID_TRANS_ID; attrs[n].oidSz = sizeof(OID_TRANS_ID); @@ -320,7 +324,7 @@ static int build_signed_attribs(const WolfCertScepAttrs* a, if (a->pki_status != NULL) { int vl = enc_printable(a->pki_status, scratch + off, scratch_cap - off); if (vl < 0) - return WOLFCERT_ERR_MEMORY; + return vl; attrs[n].oid = OID_PKI_STATUS; attrs[n].oidSz = sizeof(OID_PKI_STATUS); @@ -332,7 +336,7 @@ static int build_signed_attribs(const WolfCertScepAttrs* a, if (a->fail_info != NULL) { int vl = enc_printable(a->fail_info, scratch + off, scratch_cap - off); if (vl < 0) - return WOLFCERT_ERR_MEMORY; + return vl; attrs[n].oid = OID_FAIL_INFO; attrs[n].oidSz = sizeof(OID_FAIL_INFO); diff --git a/src/scep/scep_server.c b/src/scep/scep_server.c index 76147fb..e691d10 100644 --- a/src/scep/scep_server.c +++ b/src/scep/scep_server.c @@ -46,17 +46,9 @@ /* Content-encryption cipher for the CertRep and its GetCACaps tokens. RFC 8894 * section 3.5.2: "AES" names AES128-CBC, and "SCEPStandard" implies "AES". */ -#if defined(WOLFSSL_AES_128) && defined(HAVE_AES_CBC) - #define SCEP_SRV_ENC_OID AES128CBCb - #define SCEP_SRV_CIPHER_CAP "AES\r\n" - #define SCEP_SRV_STD_CAP "SCEPStandard\r\n" -#elif !defined(NO_DES3) - #define SCEP_SRV_ENC_OID DES3b - #define SCEP_SRV_CIPHER_CAP "DES3\r\n" - #define SCEP_SRV_STD_CAP "" -#else - #error "wolfCert's SCEP test server needs AES-128-CBC or 3DES-CBC; rebuild wolfSSL with one of them, or configure without the test server" -#endif +#define SCEP_SRV_ENC_OID AES128CBCb +#define SCEP_SRV_CIPHER_CAP "AES\r\n" +#define SCEP_SRV_STD_CAP "SCEPStandard\r\n" typedef struct { /* rawbuf owns the request-line + header bytes read off the wire. It is @@ -513,6 +505,23 @@ static int signer_matches_csr(const uint8_t* signer_der, size_t signer_len, return rc; } +static int csr_verifies(const uint8_t* csr_der, size_t csr_len, void* heap) +{ + DecodedCert dc; + int rc; + + wc_InitDecodedCert(&dc, (byte*)csr_der, (word32)csr_len, heap); + rc = wc_ParseCert(&dc, CERTREQ_TYPE, VERIFY, NULL); + wc_FreeDecodedCert(&dc); + if (rc == MEMORY_E) + return WOLFCERT_ERR_WC(rc, "scep", "ParseCert(CSR)"); + if (rc != 0) + return WOLFCERT_ERR(WOLFCERT_ERR_PARSE, "scep", + "CSR does not parse or verify (%d)", rc); + + return WOLFCERT_OK; +} + /* Build + send a CertRep pkiMessage with the supplied pkiStatus. * When status==0 (success) the issued cert is enveloped for `env_target`; * when status==3 (pending) or status==2 (failure) the payload is empty. */ @@ -717,6 +726,18 @@ static const ScepIssued* issued_find(ScepPriv* p, void* heap, return NULL; } +/* Answer a rejected pkiMessage with a signed CertRep carrying pkiStatus + * FAILURE and failInfo, per RFC 8894 section 3.2.1. */ +static int send_pki_failure(WolfCertServer* s, int fd, + const uint8_t* tid, size_t tid_len, + const uint8_t* snonce, size_t snonce_len, + const char* fail_info) +{ + /* A FAILURE CertRep carries no messageData, hence no envelope target. */ + return send_cert_rep(s, fd, NULL, 0, NULL, 0, + tid, tid_len, snonce, snonce_len, "2", fail_info); +} + /* Issue the cert and answer with a success CertRep. */ static int issue_and_reply(WolfCertServer* s, int fd, const uint8_t* csr, size_t csr_len, @@ -728,7 +749,15 @@ static int issue_and_reply(WolfCertServer* s, int fd, size_t issued_len = 0; int rc = wolfcert_ca_issue(&s->ca, csr, csr_len, &issued, &issued_len); if (rc != WOLFCERT_OK) { - send_text(s, fd, 400, "Bad CSR", "text/plain", ""); + int send_rc; + + /* Closed by the non-OK return; the flag is for the header. */ + s->keep_alive = 0; + send_rc = send_pki_failure(s, fd, tid, tid_len, snonce, snonce_len, + "2" /* badRequest */); + if (send_rc != WOLFCERT_OK) + WOLFCERT_LOG_DBG("scep", "CertRep send failed: %d", send_rc); + return rc; } @@ -748,18 +777,6 @@ static int issue_and_reply(WolfCertServer* s, int fd, return rc; } -/* Answer a rejected pkiMessage with a signed CertRep carrying pkiStatus - * FAILURE and failInfo, per RFC 8894 section 3.2.1. */ -static int send_pki_failure(WolfCertServer* s, int fd, - const uint8_t* tid, size_t tid_len, - const uint8_t* snonce, size_t snonce_len, - const char* fail_info) -{ - /* A FAILURE CertRep carries no messageData, hence no envelope target. */ - return send_cert_rep(s, fd, NULL, 0, NULL, 0, - tid, tid_len, snonce, snonce_len, "2", fail_info); -} - /* Handle messageType=19 (PKCSReq) or 17 (RenewalReq) freshly arrived. */ static int handle_enroll(WolfCertServer* s, int fd, const char* mt, const WolfCertBuffer* csr, @@ -793,6 +810,14 @@ static int handle_enroll(WolfCertServer* s, int fd, const char* mt, /* Defer issuance; return pkiStatus=3 (PENDING). The client polls * with GetCertInitial (messageType 20) referencing this txid. */ ScepPriv* p = (ScepPriv*)s->priv; + + /* Refuse now a CSR that could never issue, rather than park it. */ + if (csr_verifies(csr->data, csr->len, s->heap) != WOLFCERT_OK) { + s->keep_alive = 0; + return send_pki_failure(s, fd, tid, tid_len, snonce, snonce_len, + "2" /* badRequest */); + } + if (pending_find(p, tid, tid_len) == NULL) { int add = pending_add(p, s->heap, tid, tid_len, csr->data, csr->len, @@ -821,6 +846,8 @@ static int handle_enroll(WolfCertServer* s, int fd, const char* mt, * transactionIDs return pkiStatus=2 (FAILURE) rather than pretending * to be pending forever. */ static int handle_get_cert_initial(WolfCertServer* s, int fd, + const uint8_t* signer_cert, + size_t signer_cert_len, const uint8_t* tid, size_t tid_len, const uint8_t* snonce, size_t snonce_len) { @@ -831,6 +858,14 @@ static int handle_get_cert_initial(WolfCertServer* s, int fd, "4" /* badCertId: no such transaction */); } + /* Only the key that parked the request may release it. */ + if (signer_cert == NULL || + signer_matches_csr(signer_cert, signer_cert_len, + e->csr_der, e->csr_len, s->heap) != WOLFCERT_OK) { + return send_pki_failure(s, fd, tid, tid_len, snonce, snonce_len, + "4" /* badCertId */); + } + /* Approve on first poll. A production implementation would hold * requests until an admin acts on a queue; for the test server a * single round trip through pending is enough to exercise the @@ -940,9 +975,11 @@ static int handle_pki_op(WolfCertServer* s, int fd, const ScepRequest* req) goto out; } - /* RFC 8894 section 3.2.1 requires all three in every message, so one that - * omits any of them is not a pkiMessage a CertRep could answer. */ - if (tid == NULL || tid_len == 0 || snonce == NULL || snonce_len == 0 || + /* RFC 8894 section 3.2.1 requires all three in every message, with a + * 16-byte senderNonce, or no CertRep could answer it. */ + if (tid == NULL || tid_len == 0 || + !wolfcert_is_printable_string(tid, tid_len) || + snonce == NULL || snonce_len != SCEP_NONCE_SZ || mt == NULL || mt[0] == '\0') { s->keep_alive = 0; send_text(s, fd, 400, "Bad Message", "text/plain", ""); @@ -953,9 +990,7 @@ static int handle_pki_op(WolfCertServer* s, int fd, const ScepRequest* req) rc = wolfcert_scep_deenvelop(s->ca.cert_der, s->ca.cert_der_len, s->ca.key_der, s->ca.key_der_len, env.data, env.len, &csr, s->heap); - if (rc != WOLFCERT_OK && strcmp(mt, "20") != 0) { - /* Decryption matters for 19/17 (CSR inside); for 20 the payload - * is IssuerAndSubject which the server matches by txid anyway. */ + if (rc != WOLFCERT_OK) { const char* fail_info = rc == WOLFCERT_ERR_UNSUPPORTED ? "0" /* badAlg */ : "2" /* badRequest */; @@ -975,7 +1010,8 @@ static int handle_pki_op(WolfCertServer* s, int fd, const ScepRequest* req) tid, tid_len, snonce, snonce_len); } else if (strcmp(mt, "20") == 0) { - rc = handle_get_cert_initial(s, fd, tid, tid_len, snonce, snonce_len); + rc = handle_get_cert_initial(s, fd, signer_cert, signer_cert_len, + tid, tid_len, snonce, snonce_len); } else if (strcmp(mt, "21") == 0 && s->cfg.scep_enable_get_cert) { const uint8_t* gc_signer = signer_cert; diff --git a/src/server.c b/src/server.c index 4be66b1..1c707e1 100644 --- a/src/server.c +++ b/src/server.c @@ -287,6 +287,17 @@ int wolfcert_server_start(const WolfCertServerCfgSrv* cfg, WolfCertServer** out) return WOLFCERT_ERR(WOLFCERT_ERR_TLS, "server", "EST requires TLS: set tls_cert_pem and tls_key_pem (RFC 7030)"); + /* Only EST requests the certificate that PHA defers past the handshake. */ + if (cfg->protocol != WOLFCERT_PROTO_EST && cfg->tls_post_handshake_auth) + return WOLFCERT_ERR(WOLFCERT_ERR_BAD_ARG, "server", + "post-handshake auth is supported only for EST"); + + struct in_addr bind_addr = { .s_addr = htonl(INADDR_ANY) }; + if (cfg->bind_host != NULL && + inet_pton(AF_INET, cfg->bind_host, &bind_addr) != 1) + return WOLFCERT_ERR(WOLFCERT_ERR_BAD_ARG, "server", + "bind_host \"%s\" is not a numeric IPv4 address", cfg->bind_host); + void* heap = cfg->heap ? cfg->heap : wolfcert_default_heap(); WolfCertServer* s = (WolfCertServer*)WOLFCERT_XMALLOC(sizeof(*s), heap); if (s == NULL) @@ -303,8 +314,6 @@ int wolfcert_server_start(const WolfCertServerCfgSrv* cfg, WolfCertServer** out) s->ops = ops; s->heap = heap; - s->cfg_bind_host = wolfcert_strdup(cfg->bind_host ? cfg->bind_host : "0.0.0.0", heap); - if (cfg->challenge_password) s->cfg_challenge = wolfcert_strdup(cfg->challenge_password, heap); @@ -373,9 +382,8 @@ int wolfcert_server_start(const WolfCertServerCfgSrv* cfg, WolfCertServer** out) int yes = 1; setsockopt(s->listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - struct sockaddr_in sa = { .sin_family = AF_INET, .sin_port = htons(cfg->bind_port) }; - if (inet_pton(AF_INET, s->cfg_bind_host, &sa.sin_addr) != 1) - sa.sin_addr.s_addr = htonl(INADDR_ANY); + struct sockaddr_in sa = { .sin_family = AF_INET, .sin_port = htons(cfg->bind_port), + .sin_addr = bind_addr }; if (bind(s->listen_fd, (struct sockaddr*)&sa, sizeof(sa)) < 0 || listen(s->listen_fd, 8) < 0) { @@ -574,7 +582,10 @@ void wolfcert_server_free(WolfCertServer* srv) close(srv->listen_fd); wolfcert_ca_free(&srv->ca); - WOLFCERT_XFREE(srv->cfg_bind_host, srv->heap); + if (srv->cfg_challenge != NULL) + wc_ForceZero(srv->cfg_challenge, (word32)strlen(srv->cfg_challenge)); + if (srv->cfg_basic_pass != NULL) + wc_ForceZero(srv->cfg_basic_pass, (word32)strlen(srv->cfg_basic_pass)); WOLFCERT_XFREE(srv->cfg_challenge, srv->heap); WOLFCERT_XFREE(srv->cfg_basic_user, srv->heap); WOLFCERT_XFREE(srv->cfg_basic_pass, srv->heap); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9a55a0a..32b1d99 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -138,6 +138,7 @@ if(WOLFCERT_ENABLE_SCEP AND WOLFCERT_ENABLE_SERVER) add_test(NAME scep_roundtrip COMMAND test_scep_roundtrip) add_executable(test_scep_poll_roundtrip integration/test_scep_poll_roundtrip.c) + target_include_directories(test_scep_poll_roundtrip PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(test_scep_poll_roundtrip PRIVATE wolfcert Threads::Threads) add_test(NAME scep_poll_roundtrip COMMAND test_scep_poll_roundtrip) diff --git a/tests/integration/cli_proto_scoping.sh b/tests/integration/cli_proto_scoping.sh index f3ac0c6..298d704 100755 --- a/tests/integration/cli_proto_scoping.sh +++ b/tests/integration/cli_proto_scoping.sh @@ -6,8 +6,8 @@ # either and quietly doing nothing. Both were only ever checked by hand. # # Most cases here fail before any network access. The --ca-fingerprint pinning -# group is the exception: it starts wolfcert-server, and skips itself when that -# binary was not built. +# and server argv groups are the exception: they start wolfcert-server, and skip +# themselves when that binary was not built. set -u @@ -213,6 +213,40 @@ else esac fi +# Credentials on the client command line must not stay visible to ps. The client +# parks opening a --trust FIFO that nobody writes, well after option parsing. +ctmp="$(mktemp -d -t wolfcert-argv.XXXXXX)" +mkfifo "$ctmp/trust" +"$CLI" enroll --proto est --url "$EST_URL" --trust "$ctmp/trust" \ + --subject CN=argv --user argvuser --pass argv-client-pass \ + --challenge argv-client-challenge >/dev/null 2>&1 & +cargv_pid=$! +trap 'kill "$cargv_pid" 2>/dev/null; rm -rf "$ctmp"' EXIT +i=0 +args="" +while [ "$i" -lt 10 ]; do + args="$(ps -ww -o args= -p "$cargv_pid")" + case "$args" in + *argv-client-pass*|*argv-client-challenge*) ;; + *--trust*) break ;; + esac + sleep 1 + i=$((i + 1)) +done +case "$args" in + *argv-client-pass*|*argv-client-challenge*) + echo "FAIL: client secrets still visible in ps: $args" + fails=$((fails + 1)) + ;; + *--trust*) echo "ok client scrubs --pass and --challenge from argv" ;; + *) + echo "FAIL: client did not stay parked on the --trust FIFO" + fails=$((fails + 1)) + ;; +esac +kill "$cargv_pid" 2>/dev/null +rm -rf "$ctmp" + # The pinning itself, end to end against the in-tree test server. wolfcert-server # is built alongside wolfcert-client whenever the server is enabled; without it # there is nothing to enroll against, so skip just this group. @@ -222,7 +256,9 @@ if [ ! -x "$SERVER" ]; then else tmp="$(mktemp -d -t wolfcert-cli.XXXXXX)" srv_pid="" - trap '[ -n "$srv_pid" ] && kill "$srv_pid" 2>/dev/null; rm -rf "$tmp"' EXIT + argv_pid="" + trap '[ -n "$srv_pid" ] && kill "$srv_pid" 2>/dev/null; + [ -n "$argv_pid" ] && kill "$argv_pid" 2>/dev/null; rm -rf "$tmp"' EXIT # Not every sleep(1) takes a fractional delay. Poll in whole seconds where # it does not, keeping the same ten-second budget. @@ -318,6 +354,64 @@ else fails=$((fails + 1)) fi fi + + # A hostname --listen is refused, and the reason reaches stderr. + "$SERVER" --proto scep --listen localhost:18090 >"$tmp/listen.log" 2>&1 & + listen_pid=$! + i=0 + while [ "$i" -lt "$poll_tries" ] && kill -0 "$listen_pid" 2>/dev/null; do + sleep "$poll_delay" + i=$((i + 1)) + done + if kill -0 "$listen_pid" 2>/dev/null; then + kill "$listen_pid" 2>/dev/null + echo "FAIL: --listen localhost was accepted" + fails=$((fails + 1)) + elif grep -q "not a numeric IPv4 address" "$tmp/listen.log"; then + echo "ok --listen localhost names the reason it was refused" + else + echo "FAIL: --listen localhost refused without the reason" + cat "$tmp/listen.log" + fails=$((fails + 1)) + fi + + # Secrets passed on the server command line must not stay visible to ps. + listening=0 + for port in 18089 18189 18289 18389; do + "$SERVER" --proto scep --listen "127.0.0.1:$port" \ + --basic argvuser:argv-basic-secret \ + --challenge argv-challenge-secret >"$tmp/argv.log" 2>&1 & + argv_pid=$! + i=0 + while [ "$i" -lt "$poll_tries" ] && kill -0 "$argv_pid" 2>/dev/null; do + if grep -q "listening" "$tmp/argv.log"; then + listening=1 + break + fi + sleep "$poll_delay" + i=$((i + 1)) + done + if [ "$listening" -eq 1 ]; then + break + fi + kill "$argv_pid" 2>/dev/null + done + + if [ "$listening" -ne 1 ]; then + echo "skip server argv scrubbing (no test server would start)" + cat "$tmp/argv.log" + else + args="$(ps -ww -o args= -p "$argv_pid")" + case "$args" in + *argv-basic-secret*|*argv-challenge-secret*) + echo "FAIL: server secrets still visible in ps: $args" + fails=$((fails + 1)) + ;; + *) echo "ok server scrubs --basic and --challenge from argv" ;; + esac + fi + kill "$argv_pid" 2>/dev/null + argv_pid="" fi if [ "$fails" -ne 0 ]; then diff --git a/tests/integration/test_est_pha_roundtrip.c b/tests/integration/test_est_pha_roundtrip.c index 37fc07c..c17695b 100644 --- a/tests/integration/test_est_pha_roundtrip.c +++ b/tests/integration/test_est_pha_roundtrip.c @@ -145,6 +145,13 @@ int main(void) bad.tls_client_ca_pem_len = 0; REQUIRE(wolfcert_server_start(&bad, &srv) == WOLFCERT_ERR_BAD_ARG); REQUIRE(srv == NULL); +#ifdef WOLFCERT_HAVE_SCEP + /* SCEP never requests the certificate, so PHA would drop mTLS there. */ + bad = cfg; + bad.protocol = WOLFCERT_PROTO_SCEP; + REQUIRE(wolfcert_server_start(&bad, &srv) == WOLFCERT_ERR_BAD_ARG); + REQUIRE(srv == NULL); +#endif int start_rc = wolfcert_server_start(&cfg, &srv); if (start_rc == WOLFCERT_ERR_UNSUPPORTED) { diff --git a/tests/integration/test_scep_async_roundtrip.c b/tests/integration/test_scep_async_roundtrip.c index 21b0b51..c2d72e0 100644 --- a/tests/integration/test_scep_async_roundtrip.c +++ b/tests/integration/test_scep_async_roundtrip.c @@ -578,6 +578,7 @@ static int blocking_poll_path(WolfCertServer* s) WolfCertScepResult r1 = { 0 }; WolfCertScepResult r2 = { 0 }; WolfCertScepResult r3 = { 0 }; + WolfCertScepResult r4 = { 0 }; uint8_t long_tid[200]; int ret = 1; @@ -605,7 +606,7 @@ static int blocking_poll_path(WolfCertServer* s) /* A 200-byte transactionID reaches the server intact: the round trip * completes and the server answers FAILURE/badCertId for the unknown * transaction, rather than the client refusing the argument. */ - memset(long_tid, 0x11, sizeof(long_tid)); + memset(long_tid, 'A', sizeof(long_tid)); REQUIRE_CLEAN(wolfcert_scep_session_get_cert_initial_ex(sess, &caps, ca_der->buffer, ca_der->length, ca_der->buffer, ca_der->length, NULL, 0, dk, csr.data, csr.len, @@ -613,6 +614,12 @@ static int blocking_poll_path(WolfCertServer* s) REQUIRE_CLEAN(r3.status == WOLFCERT_SCEP_STATUS_FAILURE); REQUIRE_CLEAN(r3.fail_info == 4); + long_tid[1] = '_'; + REQUIRE_CLEAN(wolfcert_scep_session_get_cert_initial_ex(sess, &caps, + ca_der->buffer, ca_der->length, ca_der->buffer, ca_der->length, + NULL, 0, dk, csr.data, csr.len, + long_tid, sizeof(long_tid), &r4) == WOLFCERT_ERR_BAD_ARG); + ret = 0; cleanup: if (sess != NULL) @@ -620,6 +627,7 @@ static int blocking_poll_path(WolfCertServer* s) wolfcert_scep_result_free(&r1); wolfcert_scep_result_free(&r2); wolfcert_scep_result_free(&r3); + wolfcert_scep_result_free(&r4); if (ca_der != NULL) wc_FreeDer(&ca_der); wolfcert_buffer_free(&ca_pem); diff --git a/tests/integration/test_scep_poll_roundtrip.c b/tests/integration/test_scep_poll_roundtrip.c index 1aace50..9adf086 100644 --- a/tests/integration/test_scep_poll_roundtrip.c +++ b/tests/integration/test_scep_poll_roundtrip.c @@ -36,9 +36,11 @@ #include #include #include +#include "internal.h" #include #include +#include #include #include @@ -59,6 +61,61 @@ static void* server_thread(void* arg) return NULL; } +/* POST a GetCertInitial for `tid` whose signed content is `content` rather + * than a pkcsPKIEnvelope, and return the CertRep's pkiStatus and failInfo. */ +static int post_unenveloped_poll(const char* url, WolfCertKey* dk, + const WolfCertBuffer* csr, + const uint8_t* tid, size_t tid_len, + const uint8_t* content, size_t content_len, + char** out_status, char** out_fail_info) +{ + uint8_t* signer = NULL; + size_t signer_len = 0; + uint8_t key_der[4096]; + int key_len; + uint8_t snonce[16]; + char pki_url[160]; + WolfCertScepAttrs attrs; + WolfCertBuffer pki = { 0 }; + WolfCertBuffer env = { 0 }; + WolfCertHttpResponse resp = { 0 }; + + REQUIRE(wolfcert_scep_self_signed_rsa((RsaKey*)dk->impl, csr->data, + csr->len, &signer, &signer_len, + NULL) == WOLFCERT_OK); + key_len = wc_RsaKeyToDer((RsaKey*)dk->impl, key_der, sizeof(key_der)); + REQUIRE(key_len > 0); + + memset(snonce, 0xC3, sizeof(snonce)); + memset(&attrs, 0, sizeof(attrs)); + attrs.transaction_id = tid; + attrs.transaction_id_len = tid_len; + attrs.sender_nonce = snonce; + attrs.sender_nonce_len = sizeof(snonce); + attrs.message_type = "20"; + REQUIRE(wolfcert_scep_build_pki_message(content, content_len, + signer, signer_len, + key_der, (size_t)key_len, 0, + &attrs, &pki, NULL) == WOLFCERT_OK); + + snprintf(pki_url, sizeof(pki_url), "%s?operation=PKIOperation", url); + WolfCertHttpRequest req = { .method = "POST", .url = pki_url, + .content_type = "application/x-pki-message", + .body = pki.data, .body_len = pki.len }; + REQUIRE(wolfcert_http_request(&req, &resp) == WOLFCERT_OK); + REQUIRE(resp.status_code == 200); + REQUIRE(wolfcert_scep_parse_pki_message(resp.body, resp.body_len, &env, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, out_status, + NULL, NULL, out_fail_info, NULL) == WOLFCERT_OK); + + wolfcert_buffer_free(&env); + wolfcert_http_response_free(&resp); + wolfcert_buffer_free(&pki); + wc_ForceZero(key_der, sizeof(key_der)); + WOLFCERT_XFREE(signer, NULL); + return 0; +} + static int poll_path(WolfCertServer* s) { char url[128]; @@ -102,6 +159,68 @@ static int poll_path(WolfCertServer* s) REQUIRE(legacy_rc == WOLFCERT_ERR_PENDING); wolfcert_buffer_free(&legacy_out); + /* A poll whose signed content is absent or not an envelope must be + * refused, and must leave the pending entry for the real poll below. */ + static const uint8_t not_env[] = { 0x04, 0x03, 'a', 'b', 'c' }; + const uint8_t* bad_content[2] = { NULL, not_env }; + size_t bad_len[2] = { 0, sizeof(not_env) }; + for (int i = 0; i < 2; i++) { + char* status = NULL; + char* fail_info = NULL; + REQUIRE(post_unenveloped_poll(url, dk, &csr, r1.transaction_id, + r1.transaction_id_len, bad_content[i], + bad_len[i], &status, &fail_info) == 0); + int refused = status != NULL && strcmp(status, "2") == 0 && + fail_info != NULL && strcmp(fail_info, "2") == 0; + WOLFCERT_XFREE(status, NULL); + WOLFCERT_XFREE(fail_info, NULL); + REQUIRE(refused); + } + + /* A valid poll signed by another key must not release the entry either. */ + WolfCertKey* other = NULL; + WolfCertBuffer other_csr = { 0 }; + WolfCertScepResult rx = { 0 }; + REQUIRE(wolfcert_key_generate(&kcfg, &other) == WOLFCERT_OK); + REQUIRE(wolfcert_csr_build(other, &meta, &other_csr) == WOLFCERT_OK); + rc = wolfcert_scep_get_cert_initial(&cli, &caps, + ca_der->buffer, ca_der->length, + ca_der->buffer, ca_der->length, + NULL, 0, + other, other_csr.data, other_csr.len, + r1.transaction_id, r1.transaction_id_len, + &rx); + REQUIRE(rc == WOLFCERT_OK); + REQUIRE(rx.status == WOLFCERT_SCEP_STATUS_FAILURE); + REQUIRE(rx.fail_info == 4); + wolfcert_scep_result_free(&rx); + + /* A CSR with a broken signature is refused on the PKCSReq, not parked. */ + other_csr.data[other_csr.len - 1] ^= 0x01; + rc = wolfcert_scep_pkcs_req_ex(&cli, &caps, + ca_der->buffer, ca_der->length, + ca_der->buffer, ca_der->length, + other, other_csr.data, other_csr.len, &rx); + REQUIRE(rc == WOLFCERT_OK); + REQUIRE(rx.status == WOLFCERT_SCEP_STATUS_FAILURE); + REQUIRE(rx.fail_info == 2); + other_csr.data[other_csr.len - 1] ^= 0x01; + WolfCertScepResult ry = { 0 }; + rc = wolfcert_scep_get_cert_initial(&cli, &caps, + ca_der->buffer, ca_der->length, + ca_der->buffer, ca_der->length, + NULL, 0, + other, other_csr.data, other_csr.len, + rx.transaction_id, rx.transaction_id_len, + &ry); + REQUIRE(rc == WOLFCERT_OK); + REQUIRE(ry.status == WOLFCERT_SCEP_STATUS_FAILURE); + REQUIRE(ry.fail_info == 4); + wolfcert_scep_result_free(&rx); + wolfcert_scep_result_free(&ry); + wolfcert_buffer_free(&other_csr); + wolfcert_key_free(other); + /* Step 2: GetCertInitial with the same transactionID -> SUCCESS. * signer_cert=NULL so the client regenerates the transient * "SCEP Enrollee" self-signed cert that PKCSReq used. */ @@ -154,7 +273,7 @@ static int poll_path(WolfCertServer* s) * it, so a value far longer than the generated 32-hex one is sent on the * wire (FAILURE/badCertId again) instead of being rejected up front. */ uint8_t long_tid[200]; - memset(long_tid, 0x11, sizeof(long_tid)); + memset(long_tid, 'A', sizeof(long_tid)); WolfCertScepResult r4 = { 0 }; rc = wolfcert_scep_get_cert_initial(&cli, &caps, ca_der->buffer, ca_der->length, @@ -167,6 +286,38 @@ static int poll_path(WolfCertServer* s) REQUIRE(r4.status == WOLFCERT_SCEP_STATUS_FAILURE); REQUIRE(r4.fail_info == 4); + /* Step 5: '_', '@', control bytes and bytes above 0x7F are outside the + * PrintableString set; its punctuation goes on the wire. */ + static const uint8_t ok_tid[] = { 'A', '-', ':', '.', ' ', '\'', '?' }; + WolfCertScepResult r6 = { 0 }; + rc = wolfcert_scep_get_cert_initial(&cli, &caps, + ca_der->buffer, ca_der->length, + ca_der->buffer, ca_der->length, + NULL, 0, + dk, csr.data, csr.len, + ok_tid, sizeof(ok_tid), &r6); + REQUIRE(rc == WOLFCERT_OK); + REQUIRE(r6.status == WOLFCERT_SCEP_STATUS_FAILURE); + REQUIRE(r6.fail_info == 4); + wolfcert_scep_result_free(&r6); + + static const uint8_t bad_tid[][4] = { + { 'A', 'B', '_', 'C' }, { 'A', 'B', 0x11, 'C' }, { 'A', 'B', 0x00, 'C' }, + { 'A', 'B', '@', 'C' }, { 'A', 'B', 0x80, 'C' } + }; + for (size_t i = 0; i < sizeof(bad_tid) / sizeof(bad_tid[0]); i++) { + WolfCertScepResult r5 = { 0 }; + rc = wolfcert_scep_get_cert_initial(&cli, &caps, + ca_der->buffer, ca_der->length, + ca_der->buffer, ca_der->length, + NULL, 0, + dk, csr.data, csr.len, + bad_tid[i], sizeof(bad_tid[i]), + &r5); + wolfcert_scep_result_free(&r5); + REQUIRE(rc == WOLFCERT_ERR_BAD_ARG); + } + wolfcert_scep_result_free(&r1); wolfcert_scep_result_free(&r2); wolfcert_scep_result_free(&r3); diff --git a/tests/integration/test_scep_roundtrip.c b/tests/integration/test_scep_roundtrip.c index ff6b247..fd4ca04 100644 --- a/tests/integration/test_scep_roundtrip.c +++ b/tests/integration/test_scep_roundtrip.c @@ -385,14 +385,40 @@ static int check_pubkey_txid(const WolfCertServerCfg* cli, return rc; } -/* The content-cipher checks force an AES-CBC cipher, so they only exist when - * wolfSSL can supply one. */ -#if defined(HAVE_AES_CBC) && \ - (defined(WOLFSSL_AES_128) || defined(WOLFSSL_AES_256)) -#define WOLFCERT_TEST_HAVE_CIPHER_OVERRIDE -#endif +/* A CSR whose self-signature is broken gets past the SPKI and challenge checks + * and is only refused at issuance, which must still answer with a CertRep. */ +static int check_bad_csr_sig(const WolfCertServerCfg* cli, + const WolfCertScepCaps* caps, + const WolfCertKey* key, + const uint8_t* csr, size_t csr_len, + const uint8_t* ca_der_buf, size_t ca_der_len) +{ + WolfCertScepResult r = { 0 }; + uint8_t* bad = NULL; + int rc = WOLFCERT_OK; + + bad = (uint8_t*)WOLFCERT_XMALLOC(csr_len, NULL); + if (bad == NULL) + rc = WOLFCERT_ERR_MEMORY; + if (rc == WOLFCERT_OK) { + memcpy(bad, csr, csr_len); + bad[csr_len - 1] ^= 0x01; + rc = wolfcert_scep_pkcs_req_ex(cli, caps, ca_der_buf, ca_der_len, + ca_der_buf, ca_der_len, key, + bad, csr_len, &r); + if (rc != WOLFCERT_OK) + fprintf(stderr, "bad CSR signature: rc=%d (%s)\n", rc, + wolfcert_strerror(rc)); + } + if (rc == WOLFCERT_OK && (r.status != WOLFCERT_SCEP_STATUS_FAILURE || + r.fail_info != 2)) + rc = -1; + + wolfcert_scep_result_free(&r); + WOLFCERT_XFREE(bad, NULL); + return rc; +} -#ifdef WOLFCERT_TEST_HAVE_CIPHER_OVERRIDE /* proto_opts.scep.content_cipher override: enrolling with an explicit * cipher must still issue a cert - the server de-envelops whatever OID the * request carries - proving AES-256 (and explicit AES-128) interoperate. */ @@ -425,7 +451,6 @@ static int check_content_cipher(const WolfCertServerCfg* cli, wolfcert_key_free(key); return rc; } -#endif /* WOLFCERT_TEST_HAVE_CIPHER_OVERRIDE */ /* The listener canned_srv_thread() accepts on and the response it sends. */ struct canned_ctx { @@ -815,7 +840,6 @@ static void* msgtype_srv_thread(void* arg) return NULL; } -#ifdef WOLFCERT_TEST_HAVE_CIPHER_OVERRIDE /* The end-to-end cipher check above only proves the server de-enveloped * whatever arrived, which it does for any OID, so it would pass even if the * override were ignored. Read the algorithm off the wire instead. */ @@ -857,7 +881,6 @@ static int check_content_cipher_wire(const WolfCertScepCaps* caps, REQUIRE(strcmp(mc.cipher, expect) == 0); return 0; } -#endif /* WOLFCERT_TEST_HAVE_CIPHER_OVERRIDE */ /* proto_opts.scep.renewal_msg_type picks the messageType a renewal carries, * while the signer stays the certificate being replaced either way. Default is @@ -1030,7 +1053,6 @@ static int check_getnextca_ca_id(const uint8_t* ca_der_buf, size_t ca_der_len) return 0; } -#if defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) /* RFC 8894 section 3.2.1 requires transactionID and a fresh senderNonce in every * pkiMessage; the client always sends both, so POST hand-built ones instead. */ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg, @@ -1043,7 +1065,7 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg, WolfCertBuffer env = { 0 }; uint8_t* signer = NULL; size_t signer_len = 0; - uint8_t tid[16], snonce[16]; + uint8_t tid[16], snonce[16], snonce_long[17]; size_t i; int rc; @@ -1059,12 +1081,13 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg, rc = wolfcert_scep_envelop(ca_der_buf, ca_der_len, csr.data, csr.len, AES128CBCb, &env, NULL); - memset(tid, 0x11, sizeof(tid)); + memset(tid, 'A', sizeof(tid)); memset(snonce, 0x22, sizeof(snonce)); + memset(snonce_long, 0x33, sizeof(snonce_long)); - /* Each round omits one required attribute; the last is the control that - * proves this raw-POST harness reaches the issuance path at all. */ - for (i = 0; rc == WOLFCERT_OK && i < 7; ++i) { + /* Each round omits or mis-sizes one required attribute; the last is the + * control that proves this raw-POST harness reaches the issuance path. */ + for (i = 0; rc == WOLFCERT_OK && i < 9; ++i) { WolfCertScepAttrs a = { .message_type = i == 4 ? NULL : i == 5 ? "" : "19" }; WolfCertBuffer msg = { 0 }; @@ -1095,6 +1118,15 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg, a.transaction_id = tid; a.transaction_id_len = sizeof(tid); a.sender_nonce = snonce; a.sender_nonce_len = sizeof(snonce); } + else if (i == 6) { /* short senderNonce */ + a.transaction_id = tid; a.transaction_id_len = sizeof(tid); + a.sender_nonce = snonce; a.sender_nonce_len = 8; + } + else if (i == 7) { /* long senderNonce */ + a.transaction_id = tid; a.transaction_id_len = sizeof(tid); + a.sender_nonce = snonce_long; + a.sender_nonce_len = sizeof(snonce_long); + } else { /* control: all three present */ a.transaction_id = tid; a.transaction_id_len = sizeof(tid); a.sender_nonce = snonce; a.sender_nonce_len = sizeof(snonce); @@ -1112,8 +1144,8 @@ static int check_required_attrs(WolfCertServer* s, const WolfCertKeyCfg* kcfg, msg.data, msg.len, 0, &rsp, &rsp_len); wolfcert_buffer_free(&msg); - if (i < 6) { - /* An attribute that is absent or empty is not a pkiMessage. */ + if (i < 8) { + /* An absent, empty or missized attribute is not a pkiMessage. */ ok = (st == 400); } else { @@ -1343,8 +1375,6 @@ static int check_malformed_dispatch(uint16_t port, const WolfCertKeyCfg* kcfg, return rc; } -#endif /* HAVE_AES_CBC && WOLFSSL_AES_128 */ - int main(void) { REQUIRE(wolfcert_init(NULL) == WOLFCERT_OK); @@ -1372,13 +1402,8 @@ int main(void) REQUIRE(caps.post_pki_operation); REQUIRE(caps.sha256); -#if defined(WOLFSSL_AES_128) && defined(HAVE_AES_CBC) REQUIRE(caps.aes == 1); REQUIRE(caps.scep_standard == 1); -#else - REQUIRE(caps.aes == 0); - REQUIRE(caps.scep_standard == 0); -#endif REQUIRE(caps.renewal); WolfCertBuffer ca_pem = { 0 }; @@ -1428,6 +1453,9 @@ int main(void) REQUIRE(check_pubkey_txid(&cli, &caps, &kcfg, ca_der->buffer, ca_der->length) == WOLFCERT_OK); + REQUIRE(check_bad_csr_sig(&cli, &caps, dk, csr.data, csr.len, + ca_der->buffer, ca_der->length) == WOLFCERT_OK); + /* ---- RSA-4096 enrollment ---------------------------------------------- */ rc = check_rsa4096(&cli, &caps, ca_der->buffer, ca_der->length); if (rc != WOLFCERT_OK) @@ -1453,18 +1481,16 @@ int main(void) wolfcert_buffer_free(&issued_hash); /* ---- Content-cipher override: explicit AES-256 and AES-128 both enroll. - * Each half needs the cipher wolfSSL was actually built with; scep_prepare - * returns WOLFCERT_ERR_UNSUPPORTED for one the library cannot do. */ -#if defined(WOLFSSL_AES_256) && defined(HAVE_AES_CBC) + * AES-256 needs wolfSSL built with it; scep_prepare returns + * WOLFCERT_ERR_UNSUPPORTED otherwise. */ +#if defined(WOLFSSL_AES_256) REQUIRE(check_content_cipher(&cli, &caps, &kcfg, ca_der->buffer, ca_der->length, WOLFCERT_SCEP_CIPHER_AES256) == WOLFCERT_OK); #endif -#if defined(WOLFSSL_AES_128) && defined(HAVE_AES_CBC) REQUIRE(check_content_cipher(&cli, &caps, &kcfg, ca_der->buffer, ca_der->length, WOLFCERT_SCEP_CIPHER_AES128) == WOLFCERT_OK); -#endif /* ---- Renewal messageType. The signer is the certificate being replaced * in both cases; only the attribute changes, and the in-tree server routes @@ -1530,18 +1556,16 @@ int main(void) /* ...and the same options read off the wire, since the server de-envelops * any OID and so cannot tell an honoured override from an ignored one. */ -#if defined(WOLFSSL_AES_256) && defined(HAVE_AES_CBC) +#if defined(WOLFSSL_AES_256) REQUIRE(check_content_cipher_wire(&caps, &kcfg, ca_der->buffer, ca_der->length, WOLFCERT_SCEP_CIPHER_AES256, "aes256") == 0); #endif -#if defined(WOLFSSL_AES_128) && defined(HAVE_AES_CBC) REQUIRE(check_content_cipher_wire(&caps, &kcfg, ca_der->buffer, ca_der->length, WOLFCERT_SCEP_CIPHER_AES128, "aes128") == 0); -#endif /* The session captures the SCEP options at open, so that path needs its own * check rather than inheriting the one-shot coverage above. */ @@ -1588,17 +1612,12 @@ int main(void) REQUIRE(raw_http_status(wolfcert_server_port(s), "/scep?operation=PKIOperation&message=QUJD", "XYZ") == 400); /* body freed */ -#if defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) REQUIRE(check_required_attrs(s, &kcfg, ca_der->buffer, ca_der->length) == WOLFCERT_OK); REQUIRE(check_malformed_dispatch(wolfcert_server_port(s), &kcfg, ca_der->buffer, ca_der->length) == WOLFCERT_OK); -#else - printf("SKIP required-attrs and malformed-dispatch " - "(wolfSSL built without AES-128-CBC)\n"); -#endif #ifdef WOLFCERT_HAVE_ED25519 /* Ed25519 signer must be rejected cleanly (RFC 8894 requires RSA). */ @@ -1719,7 +1738,8 @@ int main(void) uint8_t snonce[16], rnonce[16]; memset(snonce, 0x5A, sizeof(snonce)); memset(rnonce, 0xA5, sizeof(rnonce)); - const uint8_t wtid[16] = { 0 }; + const uint8_t wtid[16] = + { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; WolfCertScepAttrs wattrs = { .transaction_id = wtid, .transaction_id_len = sizeof(wtid), .sender_nonce = snonce, .sender_nonce_len = sizeof(snonce), diff --git a/tests/unit/test_scep_msg.c b/tests/unit/test_scep_msg.c index b7a16da..6d1694b 100644 --- a/tests/unit/test_scep_msg.c +++ b/tests/unit/test_scep_msg.c @@ -368,7 +368,7 @@ static int check_no_envelope(const uint8_t* ca_der, size_t ca_len, static const uint8_t ENVELOPED_OID[] = { 0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x07,0x03 }; static const uint8_t tid[16] = - { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }; + { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; uint8_t sn[16]; uint8_t rn[16]; @@ -1922,9 +1922,6 @@ static int test_scep_rejects_est_cfg(void) return 0; } -/* Only meaningful where wolfSSL can actually run an AES-CBC content cipher. */ -#if defined(HAVE_AES_CBC) && \ - (defined(WOLFSSL_AES_128) || defined(WOLFSSL_AES_256)) /* The content-cipher choice reaches the wire: enveloping with AES256CBCb / * AES128CBCb yields a message carrying the matching AES-CBC OID. */ static int test_envelop_cipher_oid(void) @@ -1942,7 +1939,7 @@ static int test_envelop_cipher_oid(void) size_t ca_len = 0, key_len = 0; REQUIRE(make_ca(&ca_der, &ca_len, &key_der, &key_len) == 0); -#if defined(WOLFSSL_AES_256) && defined(HAVE_AES_CBC) +#if defined(WOLFSSL_AES_256) WolfCertBuffer env256 = { 0 }; REQUIRE(wolfcert_scep_envelop(ca_der, ca_len, payload, sizeof(payload), AES256CBCb, &env256, NULL) == WOLFCERT_OK); @@ -1951,19 +1948,16 @@ static int test_envelop_cipher_oid(void) wolfcert_buffer_free(&env256); #endif -#if defined(WOLFSSL_AES_128) && defined(HAVE_AES_CBC) WolfCertBuffer env128 = { 0 }; REQUIRE(wolfcert_scep_envelop(ca_der, ca_len, payload, sizeof(payload), AES128CBCb, &env128, NULL) == WOLFCERT_OK); REQUIRE(memmem(env128.data, env128.len, OID_AES128, sizeof(OID_AES128)) != NULL); wolfcert_buffer_free(&env128); -#endif free(ca_der); free(key_der); return 0; } -#endif /* HAVE_AES_CBC && (WOLFSSL_AES_128 || WOLFSSL_AES_256) */ static const byte scep_oid_msg_type[] = { 0x06,0x0A,0x60,0x86,0x48,0x01,0x86,0xF8,0x45,0x01,0x09,0x02 }; @@ -2329,11 +2323,8 @@ int main(void) return 1; if (test_scep_rejects_est_cfg()) return 1; -#if defined(HAVE_AES_CBC) && \ - (defined(WOLFSSL_AES_128) || defined(WOLFSSL_AES_256)) if (test_envelop_cipher_oid()) return 1; -#endif if (test_ca_fingerprint()) return 1; if (test_pki_get_url()) diff --git a/tests/unit/test_server_ca_store.c b/tests/unit/test_server_ca_store.c index 7fecb9d..c279cfe 100644 --- a/tests/unit/test_server_ca_store.c +++ b/tests/unit/test_server_ca_store.c @@ -758,6 +758,41 @@ static int test_leaf_ca_rejected(void) } #endif /* WOLFCERT_HAVE_ECC */ +static int test_bad_bind_host_rejected(void) +{ + static const char* const hosts[] = { + "localhost", "::1", "", "127.0.0.300", "127.0.0.1 " + }; + WolfCertServerCfgSrv cfg; + WolfCertServer* srv = NULL; + WolfCertBuffer left = { 0 }; + size_t i; + + for (i = 0; i < sizeof(hosts) / sizeof(hosts[0]); i++) { + WolfCertStoreOps* mem = wolfcert_store_memory_open(NULL); + REQUIRE(mem != NULL); + + ca_store_cfg(&cfg, mem); + cfg.bind_host = hosts[i]; + REQUIRE(wolfcert_server_start(&cfg, &srv) == WOLFCERT_ERR_BAD_ARG); + REQUIRE(srv == NULL); + REQUIRE(mem->read(mem->ctx, "ca.cert.der", &left) == + WOLFCERT_ERR_NOT_FOUND); + REQUIRE(mem->read(mem->ctx, "ca.key.der", &left) == + WOLFCERT_ERR_NOT_FOUND); + wolfcert_store_memory_close(mem); + } + + /* NULL is the only way to bind every interface. */ + ca_store_cfg(&cfg, NULL); + cfg.bind_host = NULL; + REQUIRE(wolfcert_server_start(&cfg, &srv) == WOLFCERT_OK); + REQUIRE(wolfcert_server_port(srv) != 0); + wolfcert_server_free(srv); + + return 0; +} + int main(void) { REQUIRE(test_static_mem_init() == 0); @@ -796,6 +831,8 @@ int main(void) #endif if (test_ca_key_usage()) return 1; + if (test_bad_bind_host_rejected()) + return 1; #if CA_STORE_NEEDS_TLS free(srv_cert_pem); diff --git a/wolfcert/check_config.h b/wolfcert/check_config.h index e5cc370..8ae17ae 100644 --- a/wolfcert/check_config.h +++ b/wolfcert/check_config.h @@ -131,6 +131,11 @@ #ifdef NO_SHA256 #error "wolfSSL was built with NO_SHA256; wolfCert requires SHA-256. Rebuild wolfSSL with --enable-sha256." #endif +/* RFC 8894 section 2.9 makes AES128-CBC mandatory-to-implement for SCEP. */ +#if defined(WOLFCERT_HAVE_SCEP) && (!defined(WOLFSSL_AES_128) || \ + !defined(HAVE_AES_CBC) || !defined(HAVE_AES_DECRYPT)) +#error "wolfCert SCEP requires AES-128-CBC (RFC 8894). Rebuild wolfSSL without NO_AES_128, NO_AES_CBC or NO_AES_DECRYPT, or drop WOLFCERT_HAVE_SCEP." +#endif /* wc_ConstantCompare backs every constant-time comparison of secret material * (SCEP CA fingerprint / challenge password, EST Basic-auth credential). A * wolfSSL built WOLFSSL_NO_CONST_CMP drops the symbol, so catch it here with a diff --git a/wolfcert/scep.h b/wolfcert/scep.h index 231fbf5..022ac56 100644 --- a/wolfcert/scep.h +++ b/wolfcert/scep.h @@ -202,7 +202,8 @@ WOLFCERT_API int wolfcert_scep_renewal_req(const WolfCertServerCfg* srv, * * `transaction_id` must be the value returned by the prior request. It is * carried verbatim and the client imposes no length of its own, so whatever - * the server chose is echoed back to it unchanged. + * the server chose is echoed back to it unchanged. A value outside the + * PrintableString character set returns WOLFCERT_ERR_BAD_ARG. * `ra_cert` is the envelope target; `ca_bundle` is the trusted GetCACert bundle * the response signer is checked against (see wolfcert_scep_pkcs_req_ex). */ WOLFCERT_API int wolfcert_scep_get_cert_initial(const WolfCertServerCfg* srv, diff --git a/wolfcert/server.h b/wolfcert/server.h index 7fee4d4..76d6dfb 100644 --- a/wolfcert/server.h +++ b/wolfcert/server.h @@ -35,7 +35,8 @@ typedef struct WolfCertServer WolfCertServer; typedef struct { WolfCertProtocol protocol; - const char* bind_host; /* e.g. "0.0.0.0"; ignored when + const char* bind_host; /* numeric IPv4, NULL = all + * interfaces; ignored when * serve_fd() is used directly */ uint16_t bind_port; WolfCertStoreOps* ca_store; /* optional: persist the local CA @@ -113,9 +114,10 @@ typedef struct { int est_retry_after_sec; /* TLS 1.3 post-handshake auth (RFC 8446 section 4.6.2) for EST enrollment, - * checked against `tls_client_ca_pem`, which it requires. - * wolfcert_server_start() returns WOLFCERT_ERR_UNSUPPORTED without - * KEEP_PEER_CERT and WOLFSSL_HAVE_TLS_UNIQUE; see docs/ARCHITECTURE.md. */ + * checked against `tls_client_ca_pem`, which it requires. Other protocols + * reject it with WOLFCERT_ERR_BAD_ARG. wolfcert_server_start() returns + * WOLFCERT_ERR_UNSUPPORTED without KEEP_PEER_CERT and + * WOLFSSL_HAVE_TLS_UNIQUE; see docs/ARCHITECTURE.md. */ int tls_post_handshake_auth; /* EST /csrattrs body. When set, the EST server returns this diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 9f0c284..c53394b 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -32,7 +32,9 @@ config WOLFCERT_SCEP bool "SCEP (RFC 8894) support" depends on WOLFCERT_RSA help - Enable the SCEP client. + Enable the SCEP client. RFC 8894 makes AES-128-CBC mandatory, so the + wolfSSL settings file must not define NO_AES_128, NO_AES_CBC or + NO_AES_DECRYPT. config WOLFCERT_BUILTIN_TRANSPORT bool "Built-in BSD-socket transport"