From e2ba16cf936de6929ec34281158458c02427c5c5 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 11:09:24 +0000 Subject: [PATCH 01/13] openldap 2.6.13: enable CRL with --enable-openldap, update workflow OpenLDAP's TLSCRLCheck maps to X509_STORE_set_flags(X509_V_FLAG_CRL_CHECK), which needs HAVE_CRL. Force CRL on for --enable-openldap like the other application recipes do. --- .github/workflows/openldap.yml | 2 ++ configure.ac | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/openldap.yml b/.github/workflows/openldap.yml index e681022b990..f5999888bc4 100644 --- a/.github/workflows/openldap.yml +++ b/.github/workflows/openldap.yml @@ -55,6 +55,8 @@ jobs: git_ref: OPENLDAP_REL_ENG_2_6_7 - osp_ref: 2.6.9 git_ref: OPENLDAP_REL_ENG_2_6_9 + - osp_ref: 2.6.13 + git_ref: OPENLDAP_REL_ENG_2_6_13 name: ${{ matrix.osp_ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/configure.ac b/configure.ac index 0028f10ba0b..6892e398568 100644 --- a/configure.ac +++ b/configure.ac @@ -9055,7 +9055,7 @@ if test "x$ENABLED_NGINX" = "xyes" || test "x$ENABLED_HAPROXY" = "xyes" || \ test "x$ENABLED_OPENVPN" = "xyes" || test "x$ENABLED_WPAS" != "xno" || \ test "x$ENABLED_LIGHTY" = "xyes" || test "x$ENABLED_NETSNMP" = "xyes" || \ test "x$ENABLED_KRB" = "xyes" || test "x$ENABLED_STRONGSWAN" = "xyes" || \ - test "x$ENABLED_MOSQUITTO" = "xyes" + test "x$ENABLED_MOSQUITTO" = "xyes" || test "x$ENABLED_OPENLDAP" = "xyes" then ENABLED_CRL=yes fi From a41ada807a87220d7232c82c965bee87544f1afd Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 11:09:11 +0000 Subject: [PATCH 02/13] msmtp 1.8.32: add to workflow matrix --- .github/workflows/msmtp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/msmtp.yml b/.github/workflows/msmtp.yml index 1596a8da22e..fff780f542d 100644 --- a/.github/workflows/msmtp.yml +++ b/.github/workflows/msmtp.yml @@ -46,7 +46,7 @@ jobs: strategy: fail-fast: false matrix: - ref: [ 1.8.28 ] + ref: [ 1.8.28, 1.8.32 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 From 853f5c4609f56a0cbf5fef8d48ab8b059a1e2e01 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 12:46:18 +0000 Subject: [PATCH 03/13] libvncserver 0.9.15: allow DH_compute_key without generator, update workflow OpenSSL's DH_compute_key only needs p and priv_key. libvncserver's ARD authentication sets just those and failed in SetDhInternal. Make g optional there but leave the key marked unsynced so a generator set later is picked up, and require g in the paths that need it. --- .github/workflows/libvncserver.yml | 2 +- src/pk.c | 29 ++++++++++++++++++----------- tests/api/test_ossl_dh.c | 26 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/.github/workflows/libvncserver.yml b/.github/workflows/libvncserver.yml index 18a0e27ffcf..7d73af91904 100644 --- a/.github/workflows/libvncserver.yml +++ b/.github/workflows/libvncserver.yml @@ -48,7 +48,7 @@ jobs: strategy: fail-fast: false matrix: - ref: [ 0.9.13, 0.9.14 ] + ref: [ 0.9.13, 0.9.14, 0.9.15 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/src/pk.c b/src/pk.c index c86f4b4c107..1e64caa073c 100644 --- a/src/pk.c +++ b/src/pk.c @@ -2901,8 +2901,8 @@ WOLFSSL_DH* wolfSSL_DH_dup(WOLFSSL_DH* dh) WOLFSSL_ENTER("wolfSSL_DH_dup"); - /* Validate parameters. */ - if (dh == NULL) { + /* Validate parameters. Duplicating needs the full parameter set. */ + if ((dh == NULL) || (dh->p == NULL) || (dh->g == NULL)) { WOLFSSL_ERROR_MSG("Bad parameter"); err = 1; } @@ -3672,7 +3672,7 @@ int wolfSSL_i2d_DHparams(const WOLFSSL_DH *dh, unsigned char **out) int err = 0; /* Validate parameters. */ - if (dh == NULL) { + if ((dh == NULL) || (dh->p == NULL) || (dh->g == NULL)) { WOLFSSL_ERROR_MSG("Bad parameters"); err = 1; } @@ -3716,7 +3716,7 @@ int wolfSSL_i2d_DHparams(const WOLFSSL_DH *dh, unsigned char **out) WOLFSSL_ENTER("wolfSSL_i2d_DHparams"); /* Validate parameters. */ - if (dh == NULL) { + if ((dh == NULL) || (dh->p == NULL) || (dh->g == NULL)) { WOLFSSL_ERROR_MSG("Bad parameters"); len = 0; } @@ -3993,8 +3993,13 @@ static int wolfssl_dhparams_to_der(WOLFSSL_DH* dh, unsigned char** out, (void)heap; + /* Validate parameters. */ + if ((dh->p == NULL) || (dh->g == NULL)) { + WOLFSSL_ERROR_MSG("Bad parameters"); + err = 1; + } /* Set internal parameters based on external parameters. */ - if ((dh->inSet == 0) && (SetDhInternal(dh) != 1)) { + if ((!err) && (dh->inSet == 0) && (SetDhInternal(dh) != 1)) { WOLFSSL_ERROR_MSG("Unable to set internal DH structure"); err = 1; } @@ -4197,8 +4202,8 @@ int SetDhInternal(WOLFSSL_DH* dh) WOLFSSL_ENTER("SetDhInternal"); - /* Validate parameters. */ - if ((dh == NULL) || (dh->p == NULL) || (dh->g == NULL)) { + /* Validate parameters. g is optional: key agreement only needs p. */ + if ((dh == NULL) || (dh->p == NULL)) { WOLFSSL_ERROR_MSG("Bad function arguments"); ret = WOLFSSL_FATAL_ERROR; } @@ -4218,8 +4223,8 @@ int SetDhInternal(WOLFSSL_DH* dh) ret = WOLFSSL_FATAL_ERROR; } } - if (ret == 1) { - /* Transfer generator. */ + /* Transfer generator if available. */ + if ((ret == 1) && (dh->g != NULL)) { if (wolfssl_bn_get_value(dh->g, &key->g) != 1) { ret = WOLFSSL_FATAL_ERROR; } @@ -4250,8 +4255,10 @@ int SetDhInternal(WOLFSSL_DH* dh) #endif /* WOLFSSL_DH_EXTRA */ if (ret == 1) { - /* On success record that the internal values have been set. */ - dh->inSet = 1; + /* Record that the internal values have been set. Without a generator + * the internal key is incomplete, so keep it unset to force a + * re-sync when the generator is added later. */ + dh->inSet = (dh->g != NULL); } return ret; diff --git a/tests/api/test_ossl_dh.c b/tests/api/test_ossl_dh.c index 76b0da42f07..7f77208636d 100644 --- a/tests/api/test_ossl_dh.c +++ b/tests/api/test_ossl_dh.c @@ -89,6 +89,32 @@ int test_wolfSSL_DH(void) ExpectIntEQ(DH_compute_key_padded(buf2, dh->pub_key, dh2), sz1); ExpectIntEQ(XMEMCMP(buf, buf2, (size_t)sz1), 0); + /* Key agreement with only p and priv_key set (no g). */ + if (EXPECT_SUCCESS()) { + DH *dh3 = NULL; + + ExpectNotNull(dh3 = DH_new()); + if (dh3 != NULL) { + ExpectNotNull(dh3->p = BN_dup(dh->p)); + ExpectNotNull(dh3->priv_key = BN_dup(dh->priv_key)); + } + ExpectIntEQ(DH_compute_key_padded(buf2, dh2->pub_key, dh3), sz1); + ExpectIntEQ(XMEMCMP(buf, buf2, (size_t)sz1), 0); + /* Encoding and duplicating parameters still need g. */ + ExpectIntEQ(i2d_DHparams(dh3, NULL), 0); + ExpectNull(DHparams_dup(dh3)); + /* Adding g afterwards must give a usable key pair. */ + if (dh3 != NULL) { + ExpectNotNull(dh3->g = BN_dup(dh->g)); + } + ExpectIntEQ(DH_generate_key(dh3), 1); + if (dh3 != NULL) { + ExpectIntNE(BN_is_zero(dh3->pub_key), 1); + ExpectIntGT(DH_compute_key(buf2, dh3->pub_key, dh2), 0); + } + DH_free(dh3); + } + if (dh2 != NULL) DH_free(dh2); } From ca35377d17a85977443d3d4c12a70b9e31b77702 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 11:05:34 +0000 Subject: [PATCH 04/13] jwt-cpp 0.7.2: add to workflow matrix --- .github/workflows/jwt-cpp.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/jwt-cpp.yml b/.github/workflows/jwt-cpp.yml index e3263f3f996..d25901feab4 100644 --- a/.github/workflows/jwt-cpp.yml +++ b/.github/workflows/jwt-cpp.yml @@ -50,6 +50,8 @@ jobs: fail-fast: false matrix: config: + - ref: 0.7.2 + runner: ubuntu-24.04 - ref: 0.7.0 runner: ubuntu-24.04 - ref: 0.6.0 From 00b710a8b5f7dc96e6d4efc7d22a84c50658d8e6 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 10:54:30 +0000 Subject: [PATCH 05/13] net-snmp 5.9.5.2: add to workflow matrix net-snmp 5.9.5 and newer has wolfSSL support upstream, so the new entry builds without an OSP patch. The patch file is now a matrix variable. --- .github/workflows/net-snmp.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/net-snmp.yml b/.github/workflows/net-snmp.yml index 78a0a6238a1..e5aa69a6e14 100644 --- a/.github/workflows/net-snmp.yml +++ b/.github/workflows/net-snmp.yml @@ -49,6 +49,11 @@ jobs: # List of releases to test include: - ref: 5.9.3 + patch_file: $GITHUB_WORKSPACE/osp/net-snmp/5.9.3.patch + test_opts: -e 'agentxperl' + # 5.9.5+ has wolfSSL support upstream, no patch needed + - ref: 5.9.5.2 + patch_file: '' test_opts: -e 'agentxperl' name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} @@ -78,7 +83,7 @@ jobs: repository: net-snmp/net-snmp ref: v${{ matrix.ref }} path: net-snmp - patch-file: $GITHUB_WORKSPACE/osp/net-snmp/${{ matrix.ref }}.patch + patch-file: ${{ matrix.patch_file }} configure: --disable-shared --with-wolfssl=$GITHUB_WORKSPACE/build-dir check: false From 2af6f3ad6e904a592fce0dac399bf13c20f0ccbe Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 12:22:14 +0000 Subject: [PATCH 06/13] bind9 9.20.23: accept EVP_PKEY_HMAC in EVP_PKEY_new_raw_private_key OpenSSL accepts EVP_PKEY_HMAC there. wolfSSL only accepted Ed25519, Ed448, X25519 and X448. Forward HMAC keys to wolfSSL_EVP_PKEY_new_mac_key(). Add bind9 9.20.23 to the bind workflow matrix. --- .github/workflows/bind.yml | 2 +- tests/api/test_evp_pkey.c | 18 ++++++++++++++++++ wolfcrypt/src/evp_pk.c | 10 ++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bind.yml b/.github/workflows/bind.yml index 9164461ad4c..2d8c52517cf 100644 --- a/.github/workflows/bind.yml +++ b/.github/workflows/bind.yml @@ -48,7 +48,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 9.18.0, 9.18.28, 9.18.33, 9.20.11 ] + ref: [ 9.18.0, 9.18.28, 9.18.33, 9.20.11, 9.20.23 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 9414b943fda..5e7ed5e8c5e 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -508,6 +508,24 @@ int test_wolfSSL_EVP_PKEY_new_mac_key(void) ExpectIntEQ((int)checkPwSz, 0); wolfSSL_EVP_PKEY_free(key); key = NULL; + + /* EVP_PKEY_new_raw_private_key accepts HMAC keys too. */ + ExpectNotNull(key = wolfSSL_EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, + NULL, pw, (size_t)pwSz)); + ExpectIntEQ(EVP_PKEY_id(key), EVP_PKEY_HMAC); + checkPw = NULL; + checkPwSz = 0; + ExpectNotNull(checkPw = wolfSSL_EVP_PKEY_get0_hmac(key, &checkPwSz)); + ExpectIntEQ((int)checkPwSz, pwSz); + ExpectIntEQ(XMEMCMP(checkPw, pw, pwSz), 0); + wolfSSL_EVP_PKEY_free(key); + key = NULL; + + ExpectNotNull(key = wolfSSL_EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, + NULL, NULL, 0)); + ExpectIntEQ(key->pkey_sz, 0); + wolfSSL_EVP_PKEY_free(key); + key = NULL; #endif /* OPENSSL_EXTRA */ return EXPECT_RESULT(); } diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index c0347f95b14..5dd8e08b50c 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -591,6 +591,16 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_raw_private_key(int type, (void)e; WOLFSSL_ENTER("wolfSSL_EVP_PKEY_new_raw_private_key"); + #ifdef OPENSSL_EXTRA + /* HMAC keys are raw octets, same as EVP_PKEY_new_mac_key. */ + if (type == WC_EVP_PKEY_HMAC) { + if (len > (size_t)INT_MAX) { + return NULL; + } + return wolfSSL_EVP_PKEY_new_mac_key(type, e, priv, (int)len); + } + #endif /* OPENSSL_EXTRA */ + if (priv == NULL || len == 0) { return NULL; } From 97d7f6b7f59ceb0b6ec5866da4fa10ae1862981e Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 12:11:12 +0000 Subject: [PATCH 07/13] libspdm 3.8.2: add EVP_PKEY_dup, parse basicConstraints strings, update workflow libspdm 3.8.2 calls EVP_PKEY_dup() unconditionally when generating a CSR and builds the CSR's basicConstraints extension from an OpenSSL style string with X509V3_EXT_nconf_nid(). - Record whether an EVP_PKEY holds private key material in a new isPriv flag, set wherever a key is built. The struct is zeroed on creation, so a key that holds nothing reads as public. - Add wolfSSL_EVP_PKEY_dup(): a deep copy that re-decodes the cached DER the same way it was made, so no failed decode is left on the error queue. - Parse "CA:TRUE|FALSE[,pathlen:N]" in createExtFromStr(). The format is OpenSSL's, so the parser lives in src/x509.c next to its only caller. Spaces and tabs around a token are ignored, CA is required, each token may appear once and the path length is capped at WOLFSSL_MAX_PATH_LEN. - Accept the "critical," prefix for every NID in X509V3_EXT_nconf_nid() and X509V3_EXT_nconf(), and only as a prefix, as OpenSSL does. - Add unit tests and libspdm 3.8.2 to the workflow matrix. --- .github/workflows/libspdm.yml | 2 +- src/x509.c | 126 ++++++++++++++++ tests/api/test_evp_pkey.c | 260 +++++++++++++++++++++++++++++++++ tests/api/test_evp_pkey.h | 2 + tests/api/test_ossl_x509_ext.c | 55 +++++++ wolfcrypt/src/evp.c | 16 ++ wolfcrypt/src/evp_pk.c | 47 +++++- wolfssl/openssl/evp.h | 2 + wolfssl/ssl.h | 1 + 9 files changed, 508 insertions(+), 3 deletions(-) diff --git a/.github/workflows/libspdm.yml b/.github/workflows/libspdm.yml index 66318ba68d7..936656b61f7 100644 --- a/.github/workflows/libspdm.yml +++ b/.github/workflows/libspdm.yml @@ -48,7 +48,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 3.7.0 ] + ref: [ 3.7.0, 3.8.2 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/src/x509.c b/src/x509.c index 514c474bd0f..1a59431db2d 100644 --- a/src/x509.c +++ b/src/x509.c @@ -3239,6 +3239,124 @@ void wolfSSL_X509V3_set_ctx_nodb(WOLFSSL_X509V3_CTX* ctx) #endif /* !NO_WOLFSSL_STUB */ #ifdef OPENSSL_EXTRA +static const char* wolfssl_x509v3_skip_ws(const char* s) +{ + while (*s == ' ' || *s == '\t') + s++; + return s; +} + +/* Check for a leading "critical," in an extension value. Whitespace is + * allowed around the keyword and the comma. Advances value past the prefix + * and any leading whitespace. + * + * @return 1 when the value is critical. + * @return 0 otherwise. + */ +static int wolfssl_x509v3_check_critical(const char** value) +{ + const char* s = wolfssl_x509v3_skip_ws(*value); + int crit = 0; + + if (XSTRNCMP(s, "critical", 8) == 0) { + const char* p = wolfssl_x509v3_skip_ws(s + 8); + if (*p == ',') { + s = wolfssl_x509v3_skip_ws(p + 1); + crit = 1; + } + } + + *value = s; + return crit; +} + +/* Set the basicConstraints extension value from an OpenSSL style string. + * Format: "CA:TRUE|FALSE[,pathlen:N]". Spaces and tabs around a token are + * ignored. CA is required. Each token may appear only once. + * + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE on bad value or allocation error. + */ +static int wolfssl_ext_bc_from_str(WOLFSSL_X509_EXTENSION* ext, + const char* value) +{ + const char* s = value; + int isCa = 0; + int caSet = 0; + int pathLen = 0; + int pathLenSet = 0; + + ext->obj = wolfSSL_OBJ_nid2obj(WC_NID_basic_constraints); + if (ext->obj == NULL) { + WOLFSSL_MSG("wolfSSL_OBJ_nid2obj failed"); + return WOLFSSL_FAILURE; + } + + for (;;) { + const char* tok; + const char* end; + const char* next; + size_t len; + + tok = wolfssl_x509v3_skip_ws(s); + end = tok; + while ((*end != '\0') && (*end != ',')) + end++; + next = end; + while ((end > tok) && ((end[-1] == ' ') || (end[-1] == '\t'))) + end--; + len = (size_t)(end - tok); + + if ((len == 7) && (XSTRNCASECMP(tok, "CA:TRUE", 7) == 0)) { + if (caSet) + return WOLFSSL_FAILURE; + isCa = 1; + caSet = 1; + } + else if ((len == 8) && (XSTRNCASECMP(tok, "CA:FALSE", 8) == 0)) { + if (caSet) + return WOLFSSL_FAILURE; + caSet = 1; + } + else if ((len > 8) && (XSTRNCASECMP(tok, "pathlen:", 8) == 0)) { + const char* num; + + if (pathLenSet) + return WOLFSSL_FAILURE; + for (num = tok + 8; num < end; num++) { + if ((*num < '0') || (*num > '9')) + return WOLFSSL_FAILURE; + pathLen = (pathLen * 10) + (*num - '0'); + if (pathLen > WOLFSSL_MAX_PATH_LEN) + return WOLFSSL_FAILURE; + } + pathLenSet = 1; + } + else { + return WOLFSSL_FAILURE; + } + + if (*next != ',') + break; + s = next + 1; + } + + if (!caSet) { + WOLFSSL_MSG("basicConstraints value missing CA"); + return WOLFSSL_FAILURE; + } + + ext->obj->ca = isCa; + if (pathLenSet) { + ext->obj->pathlen = wolfSSL_ASN1_INTEGER_new(); + if (ext->obj->pathlen == NULL) + return WOLFSSL_FAILURE; + ext->obj->pathlen->length = pathLen; + } + + return WOLFSSL_SUCCESS; +} + static WOLFSSL_X509_EXTENSION* createExtFromStr(int nid, const char *value) { WOLFSSL_X509_EXTENSION* ext; @@ -3249,6 +3367,7 @@ static WOLFSSL_X509_EXTENSION* createExtFromStr(int nid, const char *value) return NULL; } ext->value.nid = nid; + ext->crit = wolfssl_x509v3_check_critical(&value); switch (nid) { case WC_NID_subject_key_identifier: @@ -3315,6 +3434,12 @@ static WOLFSSL_X509_EXTENSION* createExtFromStr(int nid, const char *value) } ext->value.type = EXT_KEY_USAGE_OID; break; + case WC_NID_basic_constraints: + if (wolfssl_ext_bc_from_str(ext, value) != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("wolfssl_ext_bc_from_str error"); + goto err_cleanup; + } + break; default: WOLFSSL_MSG("invalid or unsupported NID"); goto err_cleanup; @@ -6450,6 +6575,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) } XMEMCPY(key->pkey.ptr, x509->pubKey.buffer, x509->pubKey.length); key->pkey_sz = (int)x509->pubKey.length; + key->isPriv = 0; #ifdef HAVE_ECC key->pkey_curve = (int)x509->pkCurveOID; diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 5e7ed5e8c5e..25a2753f057 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -832,6 +832,266 @@ int test_EVP_PKEY_cmp(void) return EXPECT_RESULT(); } +#if defined(OPENSSL_EXTRA) && \ + ((!defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048)) || \ + (defined(HAVE_ECC) && defined(USE_CERT_BUFFERS_256))) +/* Check dup is a distinct key with identical encoding, usable after src is + * freed. */ +static int test_EVP_PKEY_dup_check(EVP_PKEY** src, int id, int priv) +{ + EXPECT_DECLS; + EVP_PKEY* dup = NULL; + unsigned char* srcDer = NULL; + unsigned char* dupDer = NULL; + int srcSz = 0; + int dupSz = 0; + + ERR_clear_error(); + ExpectNotNull(dup = EVP_PKEY_dup(*src)); + /* A successful dup must not leave anything on the error queue. */ + ExpectIntEQ(ERR_peek_error(), 0); + ExpectPtrNE(dup, *src); + ExpectIntEQ(EVP_PKEY_id(dup), id); + if (priv) { + ExpectIntGT(srcSz = i2d_PrivateKey(*src, &srcDer), 0); + } + else { + ExpectIntGT(srcSz = i2d_PUBKEY(*src, &srcDer), 0); + } + EVP_PKEY_free(*src); + *src = NULL; + if (priv) { + ExpectIntGT(dupSz = i2d_PrivateKey(dup, &dupDer), 0); + } + else { + ExpectIntGT(dupSz = i2d_PUBKEY(dup, &dupDer), 0); + } + ExpectIntEQ(srcSz, dupSz); + ExpectIntEQ(XMEMCMP(srcDer, dupDer, (size_t)srcSz), 0); +#ifndef NO_RSA + if (id == EVP_PKEY_RSA) { + ExpectNotNull(EVP_PKEY_get0_RSA(dup)); + } +#endif +#ifdef HAVE_ECC + if (id == EVP_PKEY_EC) { + ExpectNotNull(EVP_PKEY_get0_EC_KEY(dup)); + } +#endif + XFREE(srcDer, NULL, DYNAMIC_TYPE_OPENSSL); + XFREE(dupDer, NULL, DYNAMIC_TYPE_OPENSSL); + EVP_PKEY_free(dup); + + return EXPECT_RESULT(); +} +#endif /* OPENSSL_EXTRA && (RSA 2048 or ECC 256 cert buffers) */ + +/* d2i_evp_pkey() only knows DSA and DH in these builds. */ +#if defined(OPENSSL_EXTRA) && !defined(NO_DSA) && \ + defined(USE_CERT_BUFFERS_2048) && (defined(WOLFSSL_QT) || \ + defined(OPENSSL_ALL) || defined(WOLFSSL_OPENSSH)) + #define TEST_EVP_PKEY_DUP_DSA +#endif +#if defined(OPENSSL_EXTRA) && !defined(NO_DH) && \ + defined(USE_CERT_BUFFERS_2048) && (defined(WOLFSSL_QT) || \ + defined(OPENSSL_ALL) || defined(WOLFSSL_OPENSSH)) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GT(2,0)) + #define TEST_EVP_PKEY_DUP_DH +#endif + +#if defined(TEST_EVP_PKEY_DUP_DSA) || defined(TEST_EVP_PKEY_DUP_DH) +/* Check dup carries the same cached DER and the same private/public state, + * and that it left the error queue clean. */ +static int test_EVP_PKEY_dup_der_check(EVP_PKEY* src, int id, int priv) +{ + EXPECT_DECLS; + EVP_PKEY* dup = NULL; + + ERR_clear_error(); + ExpectNotNull(dup = EVP_PKEY_dup(src)); + /* A successful dup must not leave anything on the error queue. */ + ExpectIntEQ(ERR_peek_error(), 0); + ExpectPtrNE(dup, src); + ExpectIntEQ(EVP_PKEY_id(dup), id); + ExpectIntEQ(dup->isPriv, priv); + ExpectIntEQ(dup->pkey_sz, src->pkey_sz); + ExpectIntEQ(XMEMCMP(dup->pkey.ptr, src->pkey.ptr, (size_t)src->pkey_sz), + 0); + + EVP_PKEY_free(dup); + + return EXPECT_RESULT(); +} +#endif /* TEST_EVP_PKEY_DUP_DSA || TEST_EVP_PKEY_DUP_DH */ + +int test_wolfSSL_EVP_PKEY_dup(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) + EVP_PKEY* key = NULL; + EVP_PKEY* dup = NULL; + const unsigned char* in; +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) && \ + defined(WOLFSSL_KEY_TO_DER) + EVP_PKEY* set1 = NULL; + RSA* rsa = NULL; +#endif + +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) + in = client_key_der_2048; + ExpectNotNull(key = wolfSSL_d2i_PrivateKey(EVP_PKEY_RSA, NULL, &in, + (long)sizeof_client_key_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_check(&key, EVP_PKEY_RSA, 1), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; + + in = client_keypub_der_2048; + ExpectNotNull(key = d2i_PUBKEY(NULL, &in, + (long)sizeof_client_keypub_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_check(&key, EVP_PKEY_RSA, 0), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; +#endif + +#if defined(HAVE_ECC) && defined(USE_CERT_BUFFERS_256) + in = ecc_clikey_der_256; + ExpectNotNull(key = wolfSSL_d2i_PrivateKey(EVP_PKEY_EC, NULL, &in, + (long)sizeof_ecc_clikey_der_256)); + ExpectIntEQ(test_EVP_PKEY_dup_check(&key, EVP_PKEY_EC, 1), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; + + in = ecc_clikeypub_der_256; + ExpectNotNull(key = d2i_PUBKEY(NULL, &in, + (long)sizeof_ecc_clikeypub_der_256)); + ExpectIntEQ(test_EVP_PKEY_dup_check(&key, EVP_PKEY_EC, 0), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; +#endif + +#ifdef TEST_EVP_PKEY_DUP_DSA + in = dsa_key_der_2048; + ExpectNotNull(key = d2i_PrivateKey(EVP_PKEY_DSA, NULL, &in, + (long)sizeof_dsa_key_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_der_check(key, EVP_PKEY_DSA, 1), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; + + in = dsa_pub_key_der_2048; + ExpectNotNull(key = d2i_PublicKey(EVP_PKEY_DSA, NULL, &in, + (long)sizeof_dsa_pub_key_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_der_check(key, EVP_PKEY_DSA, 0), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; +#endif /* TEST_EVP_PKEY_DUP_DSA */ + +#ifdef TEST_EVP_PKEY_DUP_DH + in = dh_key_der_2048; + ExpectNotNull(key = d2i_PrivateKey(EVP_PKEY_DH, NULL, &in, + (long)sizeof_dh_key_der_2048)); + ExpectIntEQ(test_EVP_PKEY_dup_der_check(key, EVP_PKEY_DH, 1), + TEST_SUCCESS); + EVP_PKEY_free(key); + key = NULL; +#endif /* TEST_EVP_PKEY_DUP_DH */ + + /* HMAC key */ + ExpectNotNull(key = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, + (const unsigned char*)"password", 8)); + ExpectNotNull(dup = EVP_PKEY_dup(key)); + ExpectPtrNE(dup, key); + ExpectIntEQ(EVP_PKEY_id(dup), EVP_PKEY_HMAC); + ExpectIntEQ(dup->pkey_sz, 8); + ExpectIntEQ(XMEMCMP(dup->pkey.ptr, "password", 8), 0); + EVP_PKEY_free(dup); + dup = NULL; + EVP_PKEY_free(key); + key = NULL; + +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) && \ + defined(WOLFSSL_KEY_TO_DER) + /* EVP_PKEY_set1_RSA() re-encodes the RSA key, so the flag has to follow + * the encoder it picks. */ + in = client_keypub_der_2048; + ExpectNotNull(key = d2i_PUBKEY(NULL, &in, + (long)sizeof_client_keypub_der_2048)); + ExpectNotNull(rsa = EVP_PKEY_get1_RSA(key)); + ExpectNotNull(set1 = EVP_PKEY_new()); + ExpectIntEQ(EVP_PKEY_set1_RSA(set1, rsa), WOLFSSL_SUCCESS); + ExpectIntEQ(set1->isPriv, 0); + ExpectNotNull(dup = EVP_PKEY_dup(set1)); + ExpectIntEQ(dup->isPriv, 0); + EVP_PKEY_free(dup); + dup = NULL; + EVP_PKEY_free(set1); + set1 = NULL; + RSA_free(rsa); + rsa = NULL; + EVP_PKEY_free(key); + key = NULL; + + in = client_key_der_2048; + ExpectNotNull(key = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &in, + (long)sizeof_client_key_der_2048)); + ExpectNotNull(rsa = EVP_PKEY_get1_RSA(key)); + ExpectNotNull(set1 = EVP_PKEY_new()); + ExpectIntEQ(EVP_PKEY_set1_RSA(set1, rsa), WOLFSSL_SUCCESS); + ExpectIntEQ(set1->isPriv, 1); + ExpectNotNull(dup = EVP_PKEY_dup(set1)); + ExpectIntEQ(dup->isPriv, 1); + EVP_PKEY_free(dup); + dup = NULL; + EVP_PKEY_free(set1); + set1 = NULL; + RSA_free(rsa); + rsa = NULL; + EVP_PKEY_free(key); + key = NULL; +#endif /* !NO_RSA && USE_CERT_BUFFERS_2048 && WOLFSSL_KEY_TO_DER */ + + /* The constructors record whether the key holds private material. */ +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) + in = client_key_der_2048; + ExpectNotNull(key = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &in, + (long)sizeof_client_key_der_2048)); + ExpectIntEQ(key->isPriv, 1); + EVP_PKEY_free(key); + key = NULL; + + in = client_keypub_der_2048; + ExpectNotNull(key = d2i_PublicKey(EVP_PKEY_RSA, NULL, &in, + (long)sizeof_client_keypub_der_2048)); + ExpectIntEQ(key->isPriv, 0); + EVP_PKEY_free(key); + key = NULL; +#endif + ExpectNotNull(key = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, + (const unsigned char*)"password", 8)); + ExpectIntEQ(key->isPriv, 1); + EVP_PKEY_free(key); + key = NULL; + + /* A new key holds nothing, so it is public by default. */ + ExpectNotNull(key = EVP_PKEY_new()); + ExpectIntEQ(key->isPriv, 0); + EVP_PKEY_free(key); + key = NULL; + + /* Bad cases */ + ExpectNull(EVP_PKEY_dup(NULL)); + ExpectNotNull(key = EVP_PKEY_new()); + ExpectNull(EVP_PKEY_dup(key)); + EVP_PKEY_free(key); +#endif /* OPENSSL_EXTRA */ + return EXPECT_RESULT(); +} + int test_wolfSSL_EVP_PKEY_set1_get1_DSA(void) { EXPECT_DECLS; diff --git a/tests/api/test_evp_pkey.h b/tests/api/test_evp_pkey.h index 8af6d24712e..2cbb3d3100e 100644 --- a/tests/api/test_evp_pkey.h +++ b/tests/api/test_evp_pkey.h @@ -37,6 +37,7 @@ int test_wolfSSL_EVP_PKEY_new_mac_key(void); int test_wolfSSL_EVP_PKEY_hkdf(void); int test_wolfSSL_EVP_PBE_scrypt(void); int test_EVP_PKEY_cmp(void); +int test_wolfSSL_EVP_PKEY_dup(void); int test_wolfSSL_EVP_PKEY_set1_get1_DSA(void); int test_wolfSSL_EVP_PKEY_set1_get1_EC_KEY (void); int test_wolfSSL_EVP_PKEY_get0_EC_KEY(void); @@ -94,6 +95,7 @@ int test_wolfSSL_CTX_use_PrivateKey_pkcs8_repopulate(void); TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_hkdf), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PBE_scrypt), \ TEST_DECL_GROUP("evp_pkey", test_EVP_PKEY_cmp), \ + TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_dup), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_set1_get1_DSA), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_set1_get1_EC_KEY), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_get0_EC_KEY), \ diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index af2bedc46e2..c7024c40806 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -1144,6 +1144,7 @@ int test_wolfSSL_X509V3_EXT_nconf(void) size_t i; X509_EXTENSION* ext = NULL; X509* x509 = NULL; + X509* x509Crit = NULL; unsigned int keyUsageFlags; unsigned int extKeyUsageFlags; WOLFSSL_CONF conf; @@ -1151,6 +1152,7 @@ int test_wolfSSL_X509V3_EXT_nconf(void) #ifndef NO_WOLFSSL_STUB WOLFSSL_LHASH lhash; #endif + char bcTooLong[32]; ExpectNotNull(x509 = X509_new()); ExpectNull(X509V3_EXT_nconf(NULL, NULL, ext_names[0], NULL)); @@ -1195,6 +1197,59 @@ int test_wolfSSL_X509V3_EXT_nconf(void) ext = NULL; } + /* basicConstraints from string */ + ExpectNotNull(ext = X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "CA:FALSE")); + if (ext != NULL) { + ExpectNotNull(ext->obj); + ExpectIntEQ(ext->obj->type, NID_basic_constraints); + ExpectIntEQ(ext->obj->ca, 0); + ExpectNull(ext->obj->pathlen); + ExpectIntEQ(ext->crit, 0); + } + X509_EXTENSION_free(ext); + ext = NULL; + ExpectNotNull(ext = X509V3_EXT_nconf_nid(NULL, NULL, + NID_basic_constraints, "critical, CA:TRUE, pathlen:2")); + if (ext != NULL) { + ExpectNotNull(ext->obj); + ExpectIntEQ(ext->obj->ca, 1); + ExpectNotNull(ext->obj->pathlen); + ExpectIntEQ(ext->obj->pathlen->length, 2); + ExpectIntEQ(ext->crit, 1); + } + ExpectIntEQ(X509_add_ext(x509, ext, -1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_get_pathLength(x509), 2); + X509_EXTENSION_free(ext); + ext = NULL; + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "CA:MAYBE")); + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "pathlen:1")); + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "CA:TRUE,pathlen:x")); + XSNPRINTF(bcTooLong, sizeof(bcTooLong), "CA:TRUE,pathlen:%d", + WOLFSSL_MAX_PATH_LEN + 1); + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + bcTooLong)); + /* critical is only accepted as a prefix, and only once. */ + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "CA:TRUE,critical")); + ExpectNull(X509V3_EXT_nconf_nid(NULL, NULL, NID_basic_constraints, + "critical,critical,CA:TRUE")); + + /* The critical prefix is handled for every extension type. */ + ExpectNotNull(ext = X509V3_EXT_nconf_nid(NULL, NULL, NID_key_usage, + "critical,digitalSignature")); + ExpectIntEQ(ext->crit, 1); + ExpectNotNull(x509Crit = X509_new()); + ExpectIntEQ(X509_add_ext(x509Crit, ext, -1), WOLFSSL_SUCCESS); + ExpectIntEQ(X509_get_key_usage(x509Crit), KU_DIGITAL_SIGNATURE); + X509_free(x509Crit); + x509Crit = NULL; + X509_EXTENSION_free(ext); + ext = NULL; + /* Test adding extension to X509 */ for (i = 0; i < ext_nids_count; i++) { ExpectNotNull(ext = X509V3_EXT_nconf(NULL, NULL, ext_names[i], diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index ca0ccdd7432..8870d4e16b6 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -4015,6 +4015,7 @@ int wolfSSL_EVP_PKEY_keygen(WOLFSSL_EVP_PKEY_CTX *ctx, pkey->pkey.ptr = (char*)edDer; pkey->pkey_sz = edDerSz; pkey->pkcs8HeaderSz = (word16)hdrIdx; + pkey->isPriv = 1; ret = WOLFSSL_SUCCESS; } else { @@ -4794,6 +4795,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_mac_key(int type, WOLFSSL_ENGINE* e, } pkey->pkey_sz = keylen; pkey->type = pkey->save_type = type; + pkey->isPriv = 1; } } @@ -4845,6 +4847,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_CMAC_key(WOLFSSL_ENGINE* e, pkey->pkey_sz = (int)len; pkey->type = pkey->save_type = WC_EVP_PKEY_CMAC; pkey->cmacCtx = ctx; + pkey->isPriv = 1; } } else { @@ -9335,6 +9338,7 @@ static void clearEVPPkeyKeys(WOLFSSL_EVP_PKEY *pkey) if(pkey == NULL) return; WOLFSSL_ENTER("clearEVPPkeyKeys"); + pkey->isPriv = 0; #ifndef NO_RSA if (pkey->rsa != NULL && pkey->ownRsa == 1) { wolfSSL_RSA_free(pkey->rsa); @@ -9560,6 +9564,7 @@ static int PopulateRSAEvpPkeyDer(WOLFSSL_EVP_PKEY *pkey) } else { pkey->pkey_sz = derSz; + pkey->isPriv = (rsa->type == RSA_PRIVATE); return WOLFSSL_SUCCESS; } } @@ -9699,6 +9704,7 @@ int wolfSSL_EVP_PKEY_set1_DSA(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DSA *key) return WOLFSSL_FAILURE; } pkey->pkey_sz = derSz; + pkey->isPriv = (dsa->type == DSA_PRIVATE); XMEMCPY(pkey->pkey.ptr, derBuf, (size_t)derSz); XFREE(derBuf, pkey->heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -9898,6 +9904,7 @@ int wolfSSL_EVP_PKEY_set1_DH(WOLFSSL_EVP_PKEY *pkey, WOLFSSL_DH *key) /* Store DH key into pkey (DER format) */ pkey->pkey.ptr = (char*)derBuf; pkey->pkey_sz = (int)derSz; + pkey->isPriv = (havePrivate != 0); return WOLFSSL_SUCCESS; } @@ -10034,6 +10041,7 @@ static int ECC_populate_EVP_PKEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY *key) pkey->pkey_sz = (int)derSz; pkey->pkey.ptr = (char*)derBuf; pkey->pkcs8HeaderSz = key->pkcs8HeaderSz; + pkey->isPriv = 1; return WOLFSSL_SUCCESS; } else { @@ -10083,6 +10091,7 @@ static int ECC_populate_EVP_PKEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY *key) * it, or the export paths start inside the new * encoding. */ pkey->pkcs8HeaderSz = 0; + pkey->isPriv = 1; return WOLFSSL_SUCCESS; } else { @@ -10150,6 +10159,7 @@ static int ECC_populate_EVP_PKEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY *key) } if (derBuf != NULL) { pkey->pkey_sz = (int)derSz; + pkey->isPriv = 0; return WOLFSSL_SUCCESS; } else { @@ -10200,6 +10210,7 @@ void* wolfSSL_EVP_X_STATE(const WOLFSSL_EVP_CIPHER_CTX* ctx) int wolfSSL_EVP_PKEY_assign_EC_KEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY* key) { int ret; + int isPriv; if (pkey == NULL || key == NULL) return WOLFSSL_FAILURE; @@ -10207,7 +10218,11 @@ int wolfSSL_EVP_PKEY_assign_EC_KEY(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_EC_KEY* key) /* try and populate public pkey_sz and pkey.ptr */ ret = ECC_populate_EVP_PKEY(pkey, key); if (ret == WOLFSSL_SUCCESS) { /* take ownership of key if can be used */ + /* The encoding just cached describes the new key, so carry its + * private/public state over the clear below. */ + isPriv = pkey->isPriv; clearEVPPkeyKeys(pkey); /* clear out any previous keys */ + pkey->isPriv = isPriv; pkey->type = WC_EVP_PKEY_EC; pkey->ecc = key; @@ -11006,6 +11021,7 @@ int wolfSSL_EVP_PKEY_assign_RSA(WOLFSSL_EVP_PKEY* pkey, WOLFSSL_RSA* key) if (ret >= 0) { pkey->pkey_sz = ret; pkey->pkey.ptr = (char*)derBuf; + pkey->isPriv = 1; } else { /* failure - okay to ignore */ XFREE(derBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index 5dd8e08b50c..e25c0da46c9 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -55,8 +55,6 @@ static int d2i_make_pkey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, int prevSz = 0; int ret = 1; - (void)priv; - /* Get or create the EVP PKEY object. */ if (*out != NULL) { pkey = *out; @@ -120,6 +118,7 @@ static int d2i_make_pkey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, if (ret == 1) { /* Set key type passed in and return object. */ pkey->type = type; + pkey->isPriv = (priv != 0); *out = pkey; } if ((ret == 0) && (*out == NULL)) { @@ -575,6 +574,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_raw_public_key(int type, } XMEMCPY(pkey->pkey.ptr, pub, len); pkey->pkey_sz = (int)len; + pkey->isPriv = 0; return pkey; } @@ -745,6 +745,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_raw_private_key(int type, } XMEMCPY(pkey->pkey.ptr, priv, len); pkey->pkey_sz = (int)len; + pkey->isPriv = 1; return pkey; } @@ -1549,6 +1550,7 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, local->type = type; local->pkey_sz = (int)inSz; local->pkcs8HeaderSz = pkcs8HeaderSz; + local->isPriv = (priv != 0); local->pkey.ptr = (char*)XMALLOC((size_t)inSz, NULL, DYNAMIC_TYPE_PUBLIC_KEY); if (local->pkey.ptr == NULL) { @@ -1713,6 +1715,45 @@ WOLFSSL_EVP_PKEY* wolfSSL_d2i_PrivateKey(int type, WOLFSSL_EVP_PKEY** out, return d2i_evp_pkey(type, out, in, inSz, 1); } + +/* Deep copy of a key by re-decoding its cached DER. + * + * @param [in] pkey Key to copy. + * @return New WOLFSSL_EVP_PKEY on success. + * @return NULL when pkey is NULL, holds no DER or decoding fails. + */ +WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_dup(const WOLFSSL_EVP_PKEY* pkey) +{ + WOLFSSL_EVP_PKEY* dup = NULL; + const unsigned char* der; + + WOLFSSL_ENTER("wolfSSL_EVP_PKEY_dup"); + + if (pkey == NULL || pkey->pkey.ptr == NULL || pkey->pkey_sz <= 0) { + WOLFSSL_MSG("No key data to duplicate"); + return NULL; + } + + der = (const unsigned char*)pkey->pkey.ptr; + if (pkey->type == WC_EVP_PKEY_HMAC) { + dup = wolfSSL_EVP_PKEY_new_mac_key(WC_EVP_PKEY_HMAC, NULL, der, + pkey->pkey_sz); + } + else { + /* Every path that caches DER records whether it is private, so the + * encoding is decoded the same way it was made. */ + dup = d2i_evp_pkey(pkey->type, NULL, &der, pkey->pkey_sz, + pkey->isPriv); + if (dup != NULL) { + #ifdef HAVE_ECC + dup->pkey_curve = pkey->pkey_curve; + #endif + dup->save_type = pkey->save_type; + } + } + + return dup; +} #endif /* OPENSSL_EXTRA */ #ifdef OPENSSL_ALL @@ -2089,6 +2130,7 @@ WOLFSSL_PKCS8_PRIV_KEY_INFO* wolfSSL_d2i_PKCS8_PKEY( /* Copy in DER data and size. */ XMEMCPY(pkcs8->pkey.ptr, rawDer.buffer, rawDer.length); pkcs8->pkey_sz = (int)rawDer.length; + pkcs8->isPriv = 1; } /* Dispose of PKCS#8 DER data - raw DER reference data in pkcs8Der. */ @@ -2186,6 +2228,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_d2i_PrivateKey_id(int type, WOLFSSL_EVP_PKEY** out, local->type = type; local->pkey_sz = 0; local->pkcs8HeaderSz = 0; + local->isPriv = 1; switch (type) { #ifndef NO_RSA diff --git a/wolfssl/openssl/evp.h b/wolfssl/openssl/evp.h index 74e8a8ba126..8caa45b39e1 100644 --- a/wolfssl/openssl/evp.h +++ b/wolfssl/openssl/evp.h @@ -1022,6 +1022,7 @@ WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_raw_private_key(int type, WOLFSSL_API void wolfSSL_EVP_PKEY_free(WOLFSSL_EVP_PKEY* key); WOLFSSL_API int wolfSSL_EVP_PKEY_size(WOLFSSL_EVP_PKEY *pkey); WOLFSSL_API int wolfSSL_EVP_PKEY_copy_parameters(WOLFSSL_EVP_PKEY *to, const WOLFSSL_EVP_PKEY *from); +WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_dup(const WOLFSSL_EVP_PKEY* pkey); WOLFSSL_API int wolfSSL_EVP_PKEY_missing_parameters(WOLFSSL_EVP_PKEY *pkey); WOLFSSL_API int wolfSSL_EVP_PKEY_cmp(const WOLFSSL_EVP_PKEY *a, const WOLFSSL_EVP_PKEY *b); WOLFSSL_API int wolfSSL_EVP_PKEY_type(int type); @@ -1440,6 +1441,7 @@ WOLFSSL_API int wolfSSL_EVP_SignInit_ex(WOLFSSL_EVP_MD_CTX* ctx, #define EVP_PKEY_new_raw_private_key wolfSSL_EVP_PKEY_new_raw_private_key #define EVP_PKEY_free wolfSSL_EVP_PKEY_free #define EVP_PKEY_up_ref wolfSSL_EVP_PKEY_up_ref +#define EVP_PKEY_dup wolfSSL_EVP_PKEY_dup #define EVP_PKEY_size wolfSSL_EVP_PKEY_size #define EVP_PKEY_copy_parameters wolfSSL_EVP_PKEY_copy_parameters #define EVP_PKEY_missing_parameters wolfSSL_EVP_PKEY_missing_parameters diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 69c7d802491..7a8451e5001 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -645,6 +645,7 @@ struct WOLFSSL_EVP_PKEY { word16 pkcs8HeaderSz; /* option bits */ + WC_BITFIELD isPriv:1; /* key holds private material, 0 means public only */ WC_BITFIELD ownDh:1; /* if struct owns DH and should free it */ WC_BITFIELD ownEcc:1; /* if struct owns ECC and should free it */ WC_BITFIELD ownDsa:1; /* if struct owns DSA and should free it */ From 23752ea6bc7d5a67d9e981b910a265ba824123fd Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 13:17:45 +0000 Subject: [PATCH 08/13] socat 1.8.1.1: fix "aNULL" cipher list keyword, update workflow The OpenSSL-style "aNULL" keyword only set the anon signature flag, so InitSuites never generated the DH_anon suites (they also need haveDH), and nothing set useAnon from a cipher list, so a server without a certificate that listed anonymous suites failed in wolfSSL_accept with NO_PRIVATE_KEY. Set haveDH for "aNULL" and, in the OpenSSL compat parser, allow anon on the CTX/SSL when the resulting list contains an anonymous suite. socat OPENSSL-LISTEN with ciphers=aNULL then completes the handshake instead of erroring out (its test 318 records ok either way, so the suite does not show the difference). Add the socat 1.8.1.1 matrix entry. Its test numbering matches 1.8.0.3 (the new tests are appended), so the expect_fail list is the 1.8.0.3 one minus 402, 459 and 460: 402 passes now, and the osp patch keeps the derived config.h.in defines through autoheader, so the ispeed and tcpwrap deny-table options exist again. --- .github/workflows/socat.yml | 2 + src/internal.c | 24 +++++++- src/ssl.c | 21 ++++++- tests/api.c | 117 ++++++++++++++++++++++++++++++++++++ wolfssl/internal.h | 3 + 5 files changed, 165 insertions(+), 2 deletions(-) diff --git a/.github/workflows/socat.yml b/.github/workflows/socat.yml index 56afc0210d8..64d1fafbcd0 100644 --- a/.github/workflows/socat.yml +++ b/.github/workflows/socat.yml @@ -56,6 +56,8 @@ jobs: expect_fail: "36,64,146,155,156,205,216,227,307,309,310,321,386,399,402,403,459,460,467,468,475,478,491,492,528,529" - socat_version: "1.8.0.3" expect_fail: "23,146,155,156,307,321,386,399,402,459,460,467,468,475,478,491,492,495,528,529" + - socat_version: "1.8.1.1" + expect_fail: "23,146,155,156,307,321,386,399,467,468,475,478,491,492,495,528,529" steps: - name: Checkout wolfSSL CI actions uses: actions/checkout@v5 diff --git a/src/internal.c b/src/internal.c index 2f00929b753..2421ee15890 100644 --- a/src/internal.c +++ b/src/internal.c @@ -31837,6 +31837,25 @@ static void RemoveExcludedSuites(byte* suites, int* idx, int anon, int enull) } *idx = out; } + +#ifdef HAVE_ANON +/* Return 1 when suites contains an anonymous (no peer authentication) + * cipher suite. */ +int SuitesHaveAnon(const Suites* suites) +{ + int i; + + if (suites == NULL) + return 0; + for (i = 0; (i + 1) < suites->suiteSz; i += 2) { + if (CipherSuiteExcluded(suites->suites[i], suites->suites[i + 1], + 1, 0)) { + return 1; + } + } + return 0; +} +#endif /* HAVE_ANON */ #endif /* OPENSSL_EXTRA || OPENSSL_ALL */ /** @@ -32027,8 +32046,11 @@ static int ParseCipherList(Suites* suites, } if (XSTRCMP(name, "aNULL") == 0) { - if (allowing) + if (allowing) { haveSig |= SIG_ANON; + /* Anonymous suites are DH_anon; InitSuites needs DH too. */ + haveDH = 1; + } else haveSig &= ~SIG_ANON; /* Track exclusion (sticky) so an explicit ADH suite is dropped at diff --git a/src/ssl.c b/src/ssl.c index 894fcf026e5..c3636f56b67 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -3099,7 +3099,7 @@ static int CheckcipherList(const char* list) * * returns WOLFSSL_SUCCESS on success and sets the cipher suite list */ -static int wolfSSL_parse_cipher_list(WOLFSSL_CTX* ctx, WOLFSSL* ssl, +static int wolfSSL_parse_cipher_list_ex(WOLFSSL_CTX* ctx, WOLFSSL* ssl, Suites* suites, const char* list) { int ret = 0; @@ -3227,6 +3227,25 @@ static int wolfSSL_parse_cipher_list(WOLFSSL_CTX* ctx, WOLFSSL* ssl, return ret; } +static int wolfSSL_parse_cipher_list(WOLFSSL_CTX* ctx, WOLFSSL* ssl, + Suites* suites, const char* list) +{ + int ret = wolfSSL_parse_cipher_list_ex(ctx, ssl, suites, list); + +#ifdef HAVE_ANON + /* Like OpenSSL, a list with anonymous suites lets a server run without + * a certificate. */ + if (ret == WOLFSSL_SUCCESS && SuitesHaveAnon(suites)) { + if (ctx != NULL) + ctx->useAnon = 1; + else if (ssl != NULL) + ssl->options.useAnon = 1; + } +#endif + + return ret; +} + #endif diff --git a/tests/api.c b/tests/api.c index 777336230c8..bbcdc1bf0aa 100644 --- a/tests/api.c +++ b/tests/api.c @@ -2851,6 +2851,122 @@ static int test_wolfSSL_set_cipher_list_exclusions(void) #endif /* TEST_CIPHER_EXCLUDE_ANON || TEST_CIPHER_EXCLUDE_NULL */ return EXPECT_RESULT(); } + +/* OpenSSL "aNULL" selects the anonymous suites; a server that only lists + * them has no certificate. Check the keyword generates the ADH suites and + * that both ends handshake without any certificate over TLS 1.2. */ +#if defined(OPENSSL_EXTRA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) && \ + defined(BUILD_TLS_DH_anon_WITH_AES_128_CBC_SHA) && \ + !defined(WOLFSSL_SESSION_EXPORT) + #define TEST_CIPHER_LIST_ANON +#endif + +#ifdef TEST_CIPHER_LIST_ANON +static int test_cipher_list_anon_ctx_ready(WOLFSSL_CTX* ctx) +{ + EXPECT_DECLS; + wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx, "aNULL"), WOLFSSL_SUCCESS); + return EXPECT_RESULT(); +} + +static int test_cipher_list_anon_on_result(WOLFSSL* ssl) +{ + EXPECT_DECLS; + ExpectStrEQ(wolfSSL_get_cipher_name(ssl), "ADH-AES128-SHA"); + return EXPECT_RESULT(); +} +#endif + +static int test_wolfSSL_set_cipher_list_anon(void) +{ + EXPECT_DECLS; +#ifdef TEST_CIPHER_LIST_ANON + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + test_ssl_cbf client_cbf; + test_ssl_cbf server_cbf; + + /* OPENSSL_COMPATIBLE_DEFAULTS already allows anon on every CTX, so clear + * the flag first: the checks below must see the cipher list set it. */ + + /* "aNULL" on the CTX allows anon there and on SSLs made from it. A + * certificate-less server CTX can only make an SSL once anon is on. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())); + if (ctx != NULL) + ctx->useAnon = 0; + ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx, "aNULL"), WOLFSSL_SUCCESS); + ExpectIntEQ(ctx->useAnon, 1); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ssl->options.useAnon, 1); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* "aNULL" alone must generate the anonymous suites. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + if (ctx != NULL) + ctx->useAnon = 0; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + ssl->options.useAnon = 0; + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "aNULL"), WOLFSSL_SUCCESS); + ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE, + TLS_DH_anon_WITH_AES_128_CBC_SHA), 1); + ExpectIntEQ(ssl->options.useAnon, 1); + wolfSSL_free(ssl); + ssl = NULL; + + /* An explicit anonymous suite also allows anon on the SSL. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + ssl->options.useAnon = 0; + ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ADH-AES128-SHA"), + WOLFSSL_SUCCESS); + ExpectIntEQ(ssl->options.useAnon, 1); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* A list without anonymous suites must not turn anon on. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + if (ctx != NULL) + ctx->useAnon = 0; + ExpectIntEQ(wolfSSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL"), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx->useAnon, 0); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ssl->options.useAnon, 0); + ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE, + TLS_DH_anon_WITH_AES_128_CBC_SHA), 0); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* Handshake with no certificates on either side. */ + XMEMSET(&client_cbf, 0, sizeof(client_cbf)); + XMEMSET(&server_cbf, 0, sizeof(server_cbf)); + client_cbf.method = wolfTLSv1_2_client_method; + server_cbf.method = wolfTLSv1_2_server_method; + client_cbf.caPemFile = ""; + client_cbf.certPemFile = ""; + client_cbf.keyPemFile = ""; + server_cbf.caPemFile = ""; + server_cbf.certPemFile = ""; + server_cbf.keyPemFile = ""; + client_cbf.ctx_ready = test_cipher_list_anon_ctx_ready; + server_cbf.ctx_ready = test_cipher_list_anon_ctx_ready; + client_cbf.on_result = test_cipher_list_anon_on_result; + server_cbf.on_result = test_cipher_list_anon_on_result; + ExpectIntEQ(test_wolfSSL_client_server_nofail_memio(&client_cbf, + &server_cbf, NULL), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} #ifdef TEST_CIPHER_EXCLUDE_ANON #undef TEST_CIPHER_EXCLUDE_ANON #endif @@ -42013,6 +42129,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_set_cipher_list_tls12_with_version), TEST_DECL(test_wolfSSL_set_cipher_list_tls13_with_version), TEST_DECL(test_wolfSSL_set_cipher_list_exclusions), + TEST_DECL(test_wolfSSL_set_cipher_list_anon), TEST_DECL(test_wolfSSL_set_alpn_protos_default_fails), TEST_DECL(test_wolfSSL_CTX_use_certificate), TEST_DECL(test_wolfSSL_CTX_use_certificate_file), diff --git a/wolfssl/internal.h b/wolfssl/internal.h index f8fe59be7a0..f7a751d86b7 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2574,6 +2574,9 @@ WOLFSSL_LOCAL int SetCipherList(const WOLFSSL_CTX* ctx, Suites* suites, const char* list); WOLFSSL_LOCAL int SetCipherListFromBytes(WOLFSSL_CTX* ctx, Suites* suites, const byte* list, const int listSz); +#if defined(HAVE_ANON) && (defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) +WOLFSSL_LOCAL int SuitesHaveAnon(const Suites* suites); +#endif WOLFSSL_LOCAL int SetSuitesHashSigAlgo(Suites* suites, const char* list); #ifndef PSK_TYPES_DEFINED From b80bb8a888c0a4efd37ebafecf906fbd7678a16c Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 10:08:45 +0000 Subject: [PATCH 09/13] krb5 1.22.2: update workflow --- .github/workflows/krb5.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/krb5.yml b/.github/workflows/krb5.yml index 80810e9a30b..ffaee69880f 100644 --- a/.github/workflows/krb5.yml +++ b/.github/workflows/krb5.yml @@ -51,7 +51,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 1.21.1 ] + ref: [ 1.21.1, 1.22.2 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-22.04 From 7f8f81023986bc0bdca4941ee8ff820e69a5af38 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 11:53:10 +0000 Subject: [PATCH 10/13] libimobiledevice 1.4.0: add ASN1_TIME_set, fix X509_pubkey_digest, add workflow ASN1_TIME_set() was a stub, so certificates generated through the compatibility layer only had the default validity period. X509V3_set_ctx() now stores the issuer and subject in the context instead of allocating an unused X509. libimobiledevice builds the subjectKeyIdentifier "hash" from X509_pubkey_digest(). OpenSSL digests the subjectPublicKey, but X509_set_pubkey() stores a SubjectPublicKeyInfo where a decoded certificate stores the key alone, so certificates built in memory got a different digest. Skip the SubjectPublicKeyInfo wrapper when present. Add the libimobiledevice workflow. libimobiledevice has no test suite and a runner has no iOS device, so .github/scripts/idevice-emulator.py plays one: it speaks the usbmuxd protocol on a Unix socket (ListDevices, Listen, Connect, ReadBUID and the pair record calls) and the lockdownd protocol on the connection tunnelled behind it (QueryType, GetValue, Pair, ValidatePair, Unpair, StartSession with the TLS upgrade, StopSession, StartService, Goodbye). The workflow builds the dependency chain (libplist, libimobiledevice-glue, libusbmuxd, libtatsu) from source, checks that the library and tools link against wolfSSL, and runs idevice_id, idevicepair pair/validate/unpair and ideviceinfo against the emulator. It then asserts on the emulator log: the pair record certificates and private keys check out, the session negotiated TLS with the host presenting the RootCertificate as its client certificate, requests were served inside that session, and the unpair reached the pair record store. A second run reports iOS 9, for which libimobiledevice pins the session to TLS 1.0, so wolfSSL is built with --enable-tlsv10. --- .../ci-deps/packages-ubuntu-24.04-full.txt | 2 + .github/scripts/idevice-emulator.py | 559 ++++++++++++++++++ .github/workflows/libimobiledevice.yml | 224 +++++++ src/ssl_asn1.c | 17 +- src/x509.c | 68 +-- tests/api.c | 19 + tests/api/test_ossl_asn1.c | 54 ++ tests/api/test_ossl_asn1.h | 2 + tests/api/test_ossl_x509_ext.c | 31 +- wolfssl/ssl.h | 3 +- 10 files changed, 916 insertions(+), 63 deletions(-) create mode 100755 .github/scripts/idevice-emulator.py create mode 100644 .github/workflows/libimobiledevice.yml diff --git a/.github/ci-deps/packages-ubuntu-24.04-full.txt b/.github/ci-deps/packages-ubuntu-24.04-full.txt index 9e4cbc106cf..ecb82acac80 100644 --- a/.github/ci-deps/packages-ubuntu-24.04-full.txt +++ b/.github/ci-deps/packages-ubuntu-24.04-full.txt @@ -77,6 +77,7 @@ libsqlite3-dev libssl-dev libtool liburcu-dev +libusb-1.0-0-dev libuv1-dev linux-libc-dev make @@ -88,6 +89,7 @@ ninja-build pkg-config pkgconf psmisc +python3-cryptography python3-docutils python3-impacket python3-ldb diff --git a/.github/scripts/idevice-emulator.py b/.github/scripts/idevice-emulator.py new file mode 100755 index 00000000000..3a8c2a35112 --- /dev/null +++ b/.github/scripts/idevice-emulator.py @@ -0,0 +1,559 @@ +#!/usr/bin/env python3 +# Emulated iOS device for the libimobiledevice CI job. +# +# libimobiledevice never speaks USB: it talks to usbmuxd over a Unix socket, +# and libusbmuxd 2.x uses the one named by USBMUXD_SOCKET_ADDRESS=UNIX:. +# This serves that socket plus the lockdownd service usbmuxd tunnels to, so +# the unmodified tools run a real pair / session exchange with no hardware. +# +# Two protocols, both plists on a socket: +# +# usbmuxd 16-byte little-endian header (length including the header, +# version, message type, tag) followed by an XML plist. +# See usbmuxd/src/usbmuxd-proto.h and src/client.c. +# lockdownd 4-byte big-endian length followed by an XML plist, on the +# connection usbmuxd tunnels to port 62078. +# See libimobiledevice/src/lockdown.c. +# +# The device RSA key is generated at startup. The host reads its public half +# through GetValue DevicePublicKey, issues the pair record certificates from +# it, and sends them in the Pair request; this checks those certificates and +# then serves the session TLS with the DeviceCertificate it was handed, +# requiring the host to present the RootCertificate as its client +# certificate - which is what libimobiledevice sends (src/idevice.c). + +import argparse +import asyncio +import contextlib +import datetime +import errno +import os +import plistlib +import shutil +import signal +import socket +import ssl +import struct +import sys +import tempfile +import uuid +import warnings + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding, rsa + +# usbmuxd framing and constants (usbmuxd/src/usbmuxd-proto.h). +USBMUX_HEADER = struct.Struct('I') +LOCKDOWN_PORT = 62078 +LOCKDOWN_TYPE = 'com.apple.mobile.lockdown' + +# pair_record_generate_keys_and_certs() dates the certificates ten years out. +MIN_DEVICE_CERT_LIFETIME = datetime.timedelta(days=9 * 365) + + +class Log: + """One line per event, for the CI job to assert on.""" + + def __init__(self, path): + self._file = open(path, 'w', buffering=1, encoding='utf-8') + + def __call__(self, message): + stamp = datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3] + self._file.write('%s %s\n' % (stamp, message)) + + def close(self): + self._file.close() + + +def not_valid_after(cert): + """cryptography 42 deprecated the naive not_valid_after.""" + value = getattr(cert, 'not_valid_after_utc', None) + if value is None: + value = cert.not_valid_after.replace(tzinfo=datetime.timezone.utc) + return value + + +def subject_text(cert): + """The tools' certificates carry an empty subject DN.""" + return cert.subject.rfc4514_string() or '' + + +class Device: + """Everything the emulated device knows about itself.""" + + def __init__(self, args): + self.udid = args.udid + self.device_id = 1 + self.buid = str(uuid.uuid4()).upper() + self.key = rsa.generate_private_key(public_exponent=65537, + key_size=2048) + # lockdownd hands out the device public key in PKCS#1 form; that is + # what pair_record_generate_keys_and_certs() reads back with + # PEM_read_bio_RSAPublicKey(). + self.public_key_pem = self.key.public_key().public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1) + self.values = { + None: { + 'BuildVersion': '22G86', + 'CPUArchitecture': 'arm64e', + 'DeviceClass': 'iPhone', + 'DeviceColor': '1', + 'DeviceName': args.device_name, + 'DevicePublicKey': self.public_key_pem, + 'HardwareModel': 'D73AP', + 'HardwarePlatform': 't8110', + 'ProductName': 'iPhone OS', + 'ProductType': args.product_type, + 'ProductVersion': args.product_version, + 'ProtocolVersion': '2', + 'SerialNumber': 'F2LX90ABCDEF', + 'TimeIntervalSince1970': 0, + 'TimeZone': 'Etc/UTC', + 'UniqueDeviceID': self.udid, + 'WiFiAddress': args.wifi_address, + }, + } + # The pair record the device itself keeps, as set by Pair and dropped + # by Unpair. Separate from the host-side records usbmuxd stores. + self.pair_record = None + + +class Emulator: + def __init__(self, args, log): + self.log = log + self.device = Device(args) + # The host-side pair record store usbmuxd keeps, keyed by UDID. + self.records = {} + self._tmpdir = tempfile.mkdtemp(prefix='idevice-emulator-') + + def close(self): + shutil.rmtree(self._tmpdir, ignore_errors=True) + + # ---- usbmuxd ----------------------------------------------------------- + + def _attached(self): + return { + 'MessageType': 'Attached', + 'DeviceID': self.device.device_id, + 'Properties': { + 'ConnectionSpeed': 480000000, + 'ConnectionType': 'USB', + 'DeviceID': self.device.device_id, + 'LocationID': 0, + 'ProductID': 0x12a8, + 'SerialNumber': self.device.udid, + }, + } + + @staticmethod + def _send_usbmux(writer, tag, payload): + body = plistlib.dumps(payload) + writer.write(USBMUX_HEADER.pack(USBMUX_HEADER.size + len(body), + USBMUX_VERSION_PLIST, + USBMUX_MESSAGE_PLIST, tag) + body) + + def _send_result(self, writer, tag, number): + self._send_usbmux(writer, tag, {'MessageType': 'Result', + 'Number': number}) + + async def handle_usbmux(self, reader, writer): + try: + while True: + header = await reader.readexactly(USBMUX_HEADER.size) + length, _, message, tag = USBMUX_HEADER.unpack(header) + payload = await reader.readexactly( + max(0, length - USBMUX_HEADER.size)) + if message != USBMUX_MESSAGE_PLIST or not payload: + self.log('usbmux: message type %d -> BadCommand' % message) + self._send_result(writer, tag, RESULT_BADCOMMAND) + continue + if await self._usbmux_request(reader, writer, tag, + plistlib.loads(payload)): + return + await writer.drain() + except (asyncio.IncompleteReadError, ConnectionResetError, + BrokenPipeError): + pass + finally: + with contextlib.suppress(OSError): + writer.close() + + async def _usbmux_request(self, reader, writer, tag, request): + """Answer one usbmuxd request; True means the connection was taken + over by the tunnelled lockdownd session.""" + message = request.get('MessageType') + record_id = request.get('PairRecordID') + + if message == 'ListDevices': + self._send_usbmux(writer, tag, {'DeviceList': [self._attached()]}) + self.log('usbmux: ListDevices -> 1 device %s' % self.device.udid) + elif message == 'Listen': + self._send_result(writer, tag, RESULT_OK) + self._send_usbmux(writer, 0, self._attached()) + self.log('usbmux: Listen -> Attached DeviceID=%d' + % self.device.device_id) + elif message == 'ReadBUID': + self._send_usbmux(writer, tag, {'BUID': self.device.buid}) + self.log('usbmux: ReadBUID -> %s' % self.device.buid) + elif message == 'ReadPairRecord': + data = self.records.get(record_id) + if data is None: + self._send_result(writer, tag, RESULT_NOENT) + self.log('usbmux: ReadPairRecord %s -> not found (ENOENT)' + % record_id) + else: + self._send_usbmux(writer, tag, {'PairRecordData': data}) + self.log('usbmux: ReadPairRecord %s -> %d bytes' + % (record_id, len(data))) + elif message == 'SavePairRecord': + data = request['PairRecordData'] + try: + detail = self._check_saved_record(data) + except (ValueError, KeyError, TypeError) as exc: + self._send_result(writer, tag, RESULT_BADCOMMAND) + self.log('pair: stored pair record REJECTED: %s' % exc) + return False + self.records[record_id] = data + self._send_result(writer, tag, RESULT_OK) + self.log('usbmux: SavePairRecord %s -> stored %d bytes, %s' + % (record_id, len(data), detail)) + elif message == 'DeletePairRecord': + existed = self.records.pop(record_id, None) is not None + self._send_result(writer, tag, RESULT_OK) + self.log('usbmux: DeletePairRecord %s -> %s' % ( + record_id, 'deleted' if existed else 'no such record')) + elif message == 'Connect': + return await self._usbmux_connect(reader, writer, tag, request) + else: + self._send_result(writer, tag, RESULT_BADCOMMAND) + self.log('usbmux: %s -> BadCommand' % message) + return False + + async def _usbmux_connect(self, reader, writer, tag, request): + # PortNumber travels in network byte order. + port = socket.ntohs(request.get('PortNumber', 0) & 0xffff) + device_id = request.get('DeviceID') + if device_id != self.device.device_id: + self._send_result(writer, tag, RESULT_BADDEV) + self.log('usbmux: Connect DeviceID=%s -> BadDevice' % device_id) + return False + if port != LOCKDOWN_PORT: + self._send_result(writer, tag, RESULT_CONNREFUSED) + self.log('usbmux: Connect port=%d -> ConnectionRefused' % port) + return False + self._send_result(writer, tag, RESULT_OK) + await writer.drain() + self.log('usbmux: Connect DeviceID=%d port=%d -> lockdownd' + % (device_id, port)) + # From here the socket carries the device stream verbatim, so the + # lockdownd handler takes it over for the rest of the connection. + await self.serve_lockdown(reader, writer) + return True + + # ---- lockdownd --------------------------------------------------------- + + @staticmethod + async def _send_lockdown(writer, reply): + body = plistlib.dumps(reply) + writer.write(LOCKDOWN_HEADER.pack(len(body)) + body) + await writer.drain() + + async def serve_lockdown(self, reader, writer): + session_id = None + try: + while True: + header = await reader.readexactly(LOCKDOWN_HEADER.size) + (length,) = LOCKDOWN_HEADER.unpack(header) + request = plistlib.loads(await reader.readexactly(length)) + name = request.get('Request') + tail = ' [session %s]' % session_id if session_id else '' + + if name == 'QueryType': + reply = {'Request': name, 'Type': LOCKDOWN_TYPE} + self.log('lockdown: QueryType -> %s%s' + % (LOCKDOWN_TYPE, tail)) + elif name == 'GetValue': + reply = self._get_value(request, tail) + elif name in ('Pair', 'ValidatePair', 'Unpair'): + reply = self._pairing(name, request) + elif name == 'StartSession': + reply = self._start_session(request) + elif name == 'StopSession': + reply = {'Request': name, 'Result': 'Success'} + self.log('lockdown: StopSession%s -> Success' % tail) + session_id = None + elif name == 'StartService': + # No services are emulated. + reply = {'Request': name, 'Error': 'InvalidService'} + self.log('lockdown: StartService %s -> InvalidService%s' + % (request.get('Service'), tail)) + elif name == 'Goodbye': + reply = {'Request': name, 'Result': 'Success'} + self.log('lockdown: Goodbye -> Success%s' % tail) + await self._send_lockdown(writer, reply) + return + else: + reply = {'Request': name, 'Error': 'InvalidRequest'} + self.log('lockdown: %s -> InvalidRequest%s' % (name, tail)) + + await self._send_lockdown(writer, reply) + + if reply.get('EnableSessionSSL'): + session_id = reply['SessionID'] + reader, writer = await self._enable_ssl(reader, writer) + except (asyncio.IncompleteReadError, ConnectionResetError, + BrokenPipeError, ssl.SSLError): + pass + + def _get_value(self, request, tail): + domain = request.get('Domain') + key = request.get('Key') + values = self.device.values.get(domain, {}) + reply = {'Request': 'GetValue'} + if domain is not None: + reply['Domain'] = domain + if key is not None: + reply['Key'] = key + if key is None: + reply['Value'] = values + shown = '%d values' % len(values) + elif key in values: + reply['Value'] = values[key] + shown = repr(values[key])[:60] + else: + reply['Error'] = 'MissingValue' + shown = 'MissingValue' + self.log('lockdown: GetValue domain=%s key=%s -> %s%s' + % (domain or '-', key or '-', shown, tail)) + return reply + + def _pairing(self, name, request): + record = request.get('PairRecord') or {} + host_id = record.get('HostID') + if name == 'Unpair': + self.device.pair_record = None + self.log('lockdown: Unpair HostID=%s -> Success, device pair ' + 'record dropped' % host_id) + return {'Request': name, 'Result': 'Success'} + if name == 'ValidatePair': + known = self.device.pair_record + if not known or known.get('HostID') != host_id: + self.log('lockdown: ValidatePair HostID=%s -> InvalidHostID' + % host_id) + return {'Request': name, 'Error': 'InvalidHostID'} + self.log('lockdown: ValidatePair HostID=%s -> Success' % host_id) + return {'Request': name, 'Result': 'Success'} + + self.log('lockdown: Pair HostID=%s SystemBUID=%s' + % (host_id, record.get('SystemBUID'))) + try: + self.log('pair: pair record verified: %s' % self._check_record( + record)) + except (ValueError, KeyError, TypeError, + x509.ExtensionNotFound) as exc: + self.log('pair: pair record REJECTED: %s' % exc) + return {'Request': name, 'Error': 'InvalidPairRecord'} + self.device.pair_record = record + # A real device answers a successful pairing with an escrow bag. + return {'Request': name, 'Result': 'Success', + 'EscrowBag': os.urandom(64)} + + def _check_record(self, record): + """Check the certificates the host generated for this device.""" + root = x509.load_pem_x509_certificate(record['RootCertificate']) + host = x509.load_pem_x509_certificate(record['HostCertificate']) + device = x509.load_pem_x509_certificate(record['DeviceCertificate']) + if not record.get('HostID') or not record.get('SystemBUID'): + raise ValueError('HostID or SystemBUID missing') + + for name, cert in (('root', root), ('host', host), + ('device', device)): + # Every certificate is signed by the root private key, and the + # root signs itself. + try: + root.public_key().verify(cert.signature, + cert.tbs_certificate_bytes, + padding.PKCS1v15(), + cert.signature_hash_algorithm) + except Exception: + raise ValueError('%s certificate does not chain to the root' + % name) + + if not root.extensions.get_extension_for_class( + x509.BasicConstraints).value.ca: + raise ValueError('root certificate is not a CA') + for name, cert in (('host', host), ('device', device)): + if cert.extensions.get_extension_for_class( + x509.BasicConstraints).value.ca: + raise ValueError('%s certificate is a CA' % name) + usage = cert.extensions.get_extension_for_class( + x509.KeyUsage).value + if not (usage.digital_signature and usage.key_encipherment + and not usage.content_commitment + and not usage.data_encipherment + and not usage.key_agreement + and not usage.key_cert_sign and not usage.crl_sign): + raise ValueError('%s certificate key usage is %s' + % (name, usage)) + + public_key = self.device.key.public_key() + if device.public_key().public_numbers() != public_key.public_numbers(): + raise ValueError('device certificate carries a foreign key') + # RFC 5280 method 1: the SHA-1 of the public key bit string. + expected = x509.SubjectKeyIdentifier.from_public_key(public_key) + found = device.extensions.get_extension_for_class( + x509.SubjectKeyIdentifier).value + if found.digest != expected.digest: + raise ValueError('device certificate subject key identifier %s ' + 'does not match the device key' + % found.digest.hex()) + + lifetime = not_valid_after(device) - datetime.datetime.now( + datetime.timezone.utc) + if lifetime < MIN_DEVICE_CERT_LIFETIME: + raise ValueError('device certificate expires in %s' % lifetime) + + return ('root is a CA and self-signed (%s), host and device ' + 'certificates chain to it, neither is a CA, both carry ' + 'digitalSignature+keyEncipherment, device SKI %s matches the ' + 'device key, device certificate valid for %d days' + % (root.signature_hash_algorithm.name, + found.digest.hex(), lifetime.days)) + + def _check_saved_record(self, data): + """Check the record the host stores for itself. + + The Pair request strips the private keys out, so they are only + visible here; libimobiledevice reads the root key back out of this + record to authenticate the session TLS (src/idevice.c). + """ + record = plistlib.loads(data) + for name in ('Root', 'Host'): + cert = x509.load_pem_x509_certificate(record[name + + 'Certificate']) + key = serialization.load_pem_private_key( + record[name + 'PrivateKey'], None) + if (key.public_key().public_numbers() + != cert.public_key().public_numbers()): + raise ValueError('%sPrivateKey does not match %sCertificate' + % (name, name)) + return 'root and host private keys match their certificates' + + def _start_session(self, request): + host_id = request.get('HostID') + known = self.device.pair_record + if not known or known.get('HostID') != host_id: + self.log('lockdown: StartSession HostID=%s -> InvalidHostID' + % host_id) + return {'Request': 'StartSession', 'Error': 'InvalidHostID'} + session_id = str(uuid.uuid4()).upper() + self.log('lockdown: StartSession HostID=%s -> SessionID=%s ' + 'EnableSessionSSL=true' % (host_id, session_id)) + return {'Request': 'StartSession', 'Result': 'Success', + 'SessionID': session_id, 'EnableSessionSSL': True} + + # ---- session TLS ------------------------------------------------------- + + def _ssl_context(self): + record = self.device.pair_record + cert = os.path.join(self._tmpdir, 'device.pem') + with open(cert, 'wb') as handle: + handle.write(record['DeviceCertificate']) + handle.write(self.device.key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption())) + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + with warnings.catch_warnings(): + # The enum member is deprecated, TLS 1.0 itself still works and + # libimobiledevice pins it for devices below iOS 10 + # (src/idevice.c). + warnings.simplefilter('ignore', DeprecationWarning) + context.minimum_version = ssl.TLSVersion.TLSv1 + context.set_ciphers('DEFAULT:@SECLEVEL=0') + # The host presents the RootCertificate as its client certificate. + context.verify_mode = ssl.CERT_REQUIRED + context.load_verify_locations( + cadata=record['RootCertificate'].decode('ascii')) + context.load_cert_chain(cert) + return context + + async def _enable_ssl(self, reader, writer): + # Stop reading before the StartSession reply reaches the host, so its + # ClientHello cannot land in the stream buffer that start_tls() is + # about to replace. + writer.transport.pause_reading() + await writer.start_tls(self._ssl_context()) + session = writer.get_extra_info('ssl_object') + peer = x509.load_der_x509_certificate(session.getpeercert(True)) + root = x509.load_pem_x509_certificate( + self.device.pair_record['RootCertificate']) + self.log('tls: session established version=%s cipher=%s client ' + 'certificate subject=%s (%s)' + % (session.version(), session.cipher()[0], subject_text(peer), + 'matches RootCertificate from the pair record' + if peer == root else 'NOT the RootCertificate')) + return reader, writer + + +async def main_async(args): + log = Log(args.log) + emulator = Emulator(args, log) + if os.path.exists(args.socket): + os.unlink(args.socket) + server = await asyncio.start_unix_server(emulator.handle_usbmux, + path=args.socket) + log('ready: udid=%s ProductVersion=%s socket=%s' + % (emulator.device.udid, args.product_version, args.socket)) + + stop = asyncio.Event() + loop = asyncio.get_running_loop() + for signame in (signal.SIGTERM, signal.SIGINT): + loop.add_signal_handler(signame, stop.set) + await stop.wait() + + log('stopping on signal') + server.close() + await server.wait_closed() + emulator.close() + with contextlib.suppress(OSError): + os.unlink(args.socket) + log.close() + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--socket', default='/tmp/idevice-emulator.sock', + help='Unix socket to serve the usbmuxd protocol on') + parser.add_argument('--log', default='/tmp/idevice-emulator.log', + help='file to write one line per event to') + parser.add_argument('--udid', + default='1e2f3a4b5c6d7e8f90a1b2c3d4e5f60718293a4b', + help='UDID the emulated device reports') + parser.add_argument('--product-version', default='18.6', + help='iOS version the emulated device reports') + parser.add_argument('--product-type', default='iPhone15,2') + parser.add_argument('--device-name', default='CI Emulated Device') + parser.add_argument('--wifi-address', default='00:1a:2b:3c:4d:5e') + args = parser.parse_args() + asyncio.run(main_async(args)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/.github/workflows/libimobiledevice.yml b/.github/workflows/libimobiledevice.yml new file mode 100644 index 00000000000..6846e96dff0 --- /dev/null +++ b/.github/workflows/libimobiledevice.yml @@ -0,0 +1,224 @@ +name: libimobiledevice Tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +jobs: + build_wolfssl: + name: Build wolfSSL + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + # Just to keep it the same as the testing target + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 4 + steps: + - name: Build wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + path: wolfssl + configure: --enable-all --enable-tlsv10 + install: true + check: false + + - name: tar build-dir + run: tar -zcf build-dir.tgz build-dir + + - name: Upload built lib + uses: actions/upload-artifact@v6 + with: + name: wolf-install-libimobiledevice + path: build-dir.tgz + retention-days: 5 + + libimobiledevice_check: + strategy: + fail-fast: false + matrix: + # List of releases to test + include: + - ref: 1.4.0 + libplist: 2.7.0 + glue: 1.3.2 + libusbmuxd: 2.1.1 + libtatsu: 1.0.5 + name: ${{ matrix.ref }} + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 15 + needs: build_wolfssl + steps: + - name: Checkout wolfSSL CI actions + uses: actions/checkout@v5 + with: + sparse-checkout: | + .github/actions + .github/scripts + fetch-depth: 1 + + - name: Download lib + uses: actions/download-artifact@v7 + with: + name: wolf-install-libimobiledevice + + - name: untar build-dir + run: tar -xf build-dir.tgz + + - name: Install dependencies + uses: ./.github/actions/install-apt-deps + with: + packages: libusb-1.0-0-dev libcurl4-openssl-dev libreadline-dev python3-cryptography + ghcr-debs-tag: ubuntu-24.04-full + + - name: Checkout OSP + uses: actions/checkout@v5 + with: + repository: wolfssl/osp + path: osp + fetch-depth: 1 + + # The Ubuntu packages are too old (libtatsu needs libplist >= 2.6.0) + # and libimobiledevice-glue/libtatsu are not packaged, so build the + # dependency chain from source into the wolfSSL prefix. None of these + # use OpenSSL or wolfSSL. + - name: Checkout libplist + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libplist + path: libplist + ref: ${{ matrix.libplist }} + fetch-depth: 1 + + - name: Checkout libimobiledevice-glue + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libimobiledevice-glue + path: libimobiledevice-glue + ref: ${{ matrix.glue }} + fetch-depth: 1 + + - name: Checkout libusbmuxd + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libusbmuxd + path: libusbmuxd + ref: ${{ matrix.libusbmuxd }} + fetch-depth: 1 + + - name: Checkout libtatsu + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libtatsu + path: libtatsu + ref: ${{ matrix.libtatsu }} + fetch-depth: 1 + + - name: Checkout libimobiledevice + uses: actions/checkout@v5 + with: + repository: libimobiledevice/libimobiledevice + path: libimobiledevice + ref: ${{ matrix.ref }} + fetch-depth: 1 + + - name: Build dependencies + run: | + export PKG_CONFIG_PATH=$GITHUB_WORKSPACE/build-dir/lib/pkgconfig + for dep in libplist libimobiledevice-glue libusbmuxd libtatsu; do + pushd $dep + ./autogen.sh --prefix=$GITHUB_WORKSPACE/build-dir --without-cython + make -j + make install + popd + done + + - name: Build libimobiledevice + working-directory: libimobiledevice + run: | + export PKG_CONFIG_PATH=$GITHUB_WORKSPACE/build-dir/lib/pkgconfig + patch -p1 < $GITHUB_WORKSPACE/osp/libimobiledevice/${{ matrix.ref }}.patch + ./autogen.sh --prefix=$GITHUB_WORKSPACE/build-dir --enable-wolfssl --without-cython + make -j + make install + + # libimobiledevice has no test suite and a runner has no iOS device. + # .github/scripts/idevice-emulator.py plays one: it serves the usbmuxd + # socket named by USBMUXD_SOCKET_ADDRESS and the lockdownd service + # behind it, checks the pair record the tools generate through wolfSSL + # and requires the session TLS. The second run reports iOS 9, for which + # libimobiledevice pins the session to TLS 1.0 (needs --enable-tlsv10). + - name: Test libimobiledevice + env: + UDID: 1e2f3a4b5c6d7e8f90a1b2c3d4e5f60718293a4b + USBMUXD_SOCKET_ADDRESS: UNIX:/tmp/idevice-emulator.sock + run: | + export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/build-dir/lib + bin=$GITHUB_WORKSPACE/build-dir/bin + ldd $GITHUB_WORKSPACE/build-dir/lib/libimobiledevice-1.0.so | grep wolfssl + ldd $bin/idevicepair | grep wolfssl + + run_against_emulator() { + local version=$1 want_tls=$2 emulator i devices + local log=/tmp/idevice-emulator-$version.log + rm -f /tmp/idevice-emulator.sock + python3 .github/scripts/idevice-emulator.py \ + --socket /tmp/idevice-emulator.sock --log $log \ + --udid $UDID --product-version $version & + emulator=$! + for i in $(seq 1 100); do + [ -S /tmp/idevice-emulator.sock ] && break + sleep 0.1 + done + + # Assign on its own line: a command substitution inside test, + # or inside a `local` declaration, would discard idevice_id's + # exit status. + devices=$($bin/idevice_id -l) + test "$devices" = "$UDID" + $bin/idevicepair pair + $bin/idevicepair validate + $bin/ideviceinfo | grep "^ProductVersion: $version\$" + $bin/idevicepair unpair + + # Also checks that the emulator exits cleanly on SIGTERM. + kill $emulator + wait $emulator + + # What the emulator saw: pair record OK, session over the expected + # TLS version with the RootCertificate as client certificate, + # requests served inside it, unpair reached the record store. + grep -q 'pair: pair record verified: ' $log + grep -q 'private keys match their certificates' $log + grep -qE "tls: session established version=$want_tls" $log + grep -q 'matches RootCertificate from the pair record' $log + grep -q 'lockdown: GetValue .* \[session ' $log + grep -q 'usbmux: DeletePairRecord .* -> deleted' $log + if grep -qE 'REJECTED|NOT the RootCertificate' $log; then + exit 1 + fi + } + + run_against_emulator 18.6 'TLSv1\.[23] ' + run_against_emulator 9.3 'TLSv1 ' + + - name: Emulator logs + if: always() + run: | + for log in /tmp/idevice-emulator-*.log; do + [ -f "$log" ] || continue + echo "=== $log ===" + cat "$log" + done diff --git a/src/ssl_asn1.c b/src/ssl_asn1.c index 29f35fb6be5..070524dc20b 100644 --- a/src/ssl_asn1.c +++ b/src/ssl_asn1.c @@ -3895,23 +3895,28 @@ void wolfSSL_ASN1_TIME_free(WOLFSSL_ASN1_TIME* t) XFREE(t, NULL, DYNAMIC_TYPE_OPENSSL); } -#ifndef NO_WOLFSSL_STUB +#if !defined(NO_ASN_TIME) && !defined(USER_TIME) && !defined(TIME_OVERRIDES) /* Set the Unix time GMT into ASN.1 TIME object. * - * Not implemented. - * - * @param [in, out] a ASN.1 TIME object. + * @param [in, out] a ASN.1 TIME object. Allocated when NULL. * @param [in] t Unix time GMT. - * @return An ASN.1 TIME object. + * @return An ASN.1 TIME object on success. + * @return NULL on failure. */ WOLFSSL_ASN1_TIME *wolfSSL_ASN1_TIME_set(WOLFSSL_ASN1_TIME *a, time_t t) +{ + WOLFSSL_ENTER("wolfSSL_ASN1_TIME_set"); + return wolfSSL_ASN1_TIME_adj(a, t, 0, 0); +} +#elif !defined(NO_WOLFSSL_STUB) +WOLFSSL_ASN1_TIME *wolfSSL_ASN1_TIME_set(WOLFSSL_ASN1_TIME *a, time_t t) { WOLFSSL_STUB("wolfSSL_ASN1_TIME_set"); (void)a; (void)t; return a; } -#endif /* !NO_WOLFSSL_STUB */ +#endif /* !NO_ASN_TIME && !USER_TIME && !TIME_OVERRIDES */ #ifndef NO_ASN_TIME /* Convert time to Unix time (GMT). diff --git a/src/x509.c b/src/x509.c index 1a59431db2d..04d166734b7 100644 --- a/src/x509.c +++ b/src/x509.c @@ -3939,6 +3939,11 @@ int wolfSSL_X509_pubkey_digest(const WOLFSSL_X509 *x509, const WOLFSSL_EVP_MD *digest, unsigned char* buf, unsigned int* len) { int ret; + const byte* key; + word32 sz; + word32 idx = 0; + int keySz; + int len2 = 0; WOLFSSL_ENTER("wolfSSL_X509_pubkey_digest"); @@ -3952,8 +3957,23 @@ int wolfSSL_X509_pubkey_digest(const WOLFSSL_X509 *x509, return WOLFSSL_FAILURE; } - ret = wolfSSL_EVP_Digest(x509->pubKey.buffer, x509->pubKey.length, buf, - len, digest, NULL); + key = x509->pubKey.buffer; + sz = x509->pubKey.length; + keySz = (int)sz; + + /* OpenSSL digests the subjectPublicKey. Decoded certificates keep the + * key alone but wolfSSL_X509_set_pubkey stores a SubjectPublicKeyInfo, + * so step over that wrapper when it is present. */ + if ((GetSequence(key, &idx, &len2, sz) >= 0) && + (GetSequence(key, &idx, &len2, sz) >= 0)) { + idx += (word32)len2; + if (CheckBitString(key, &idx, &len2, sz, 1, NULL) >= 0) { + key += idx; + keySz = len2; + } + } + + ret = wolfSSL_EVP_Digest(key, keySz, buf, len, digest, NULL); WOLFSSL_LEAVE("wolfSSL_X509_pubkey_digest", ret); return ret; } @@ -17218,54 +17238,24 @@ void wolfSSL_X509V3_set_ctx(WOLFSSL_X509V3_CTX* ctx, WOLFSSL_X509* issuer, WOLFSSL_X509* subject, WOLFSSL_X509* req, WOLFSSL_X509_CRL* crl, int flag) { - int ret = WOLFSSL_SUCCESS; WOLFSSL_ENTER("wolfSSL_X509V3_set_ctx"); - if (!ctx) { - ret = WOLFSSL_FAILURE; + if (ctx == NULL) { WOLFSSL_MSG("wolfSSL_X509V3_set_ctx() called with null ctx."); + return; } - if (ret == WOLFSSL_SUCCESS && (ctx->x509 != NULL)) { - ret = WOLFSSL_FAILURE; - WOLFSSL_MSG("wolfSSL_X509V3_set_ctx() called " - "with ctx->x509 already allocated."); - } - - if (ret == WOLFSSL_SUCCESS) { - ctx->x509 = wolfSSL_X509_new_ex( - (issuer && issuer->heap) ? issuer->heap : - (subject && subject->heap) ? subject->heap : - (req && req->heap) ? req->heap : - NULL); - if (!ctx->x509) { - ret = WOLFSSL_FAILURE; - WOLFSSL_MSG("wolfSSL_X509_new_ex() failed " - "in wolfSSL_X509V3_set_ctx()."); - } - } - - /* Set parameters in ctx as long as ret == WOLFSSL_SUCCESS */ - if (ret == WOLFSSL_SUCCESS && issuer) - ret = wolfSSL_X509_set_issuer_name(ctx->x509, &issuer->issuer); - - if (ret == WOLFSSL_SUCCESS && subject) - ret = wolfSSL_X509_set_subject_name(ctx->x509, &subject->subject); + ctx->issuer = issuer; + ctx->subject = subject; - if (ret == WOLFSSL_SUCCESS && req) { + if (req != NULL) { WOLFSSL_MSG("req not implemented."); } - - if (ret == WOLFSSL_SUCCESS && crl) { + if (crl != NULL) { WOLFSSL_MSG("crl not implemented."); } - - if (ret == WOLFSSL_SUCCESS && flag) { + if (flag != 0) { WOLFSSL_MSG("flag not implemented."); } - - if (ret != WOLFSSL_SUCCESS) { - WOLFSSL_MSG("Error setting WOLFSSL_X509V3_CTX parameters."); - } } #ifndef NO_BIO diff --git a/tests/api.c b/tests/api.c index bbcdc1bf0aa..0647a4fd0ef 100644 --- a/tests/api.c +++ b/tests/api.c @@ -14405,7 +14405,12 @@ static int test_wolfSSL_certs(void) /************* Get Digest of Certificate ******************/ { byte digest[64]; /* max digest size */ + byte digest2[64]; word32 digestSz; + byte keyId[WC_SHA_DIGEST_SIZE]; + int keyIdSz = (int)sizeof(keyId); + EVP_PKEY* pubKey = NULL; + X509* x509Pub = NULL; X509* x509Empty = NULL; XMEMSET(digest, 0, sizeof(digest)); @@ -14419,6 +14424,20 @@ static int test_wolfSSL_certs(void) NULL), WOLFSSL_SUCCESS); ExpectIntEQ(X509_pubkey_digest(x509ext, wolfSSL_EVP_sha1(), digest, &digestSz), WOLFSSL_SUCCESS); + /* SHA-1 of the subjectPublicKey is the subject key identifier. */ + ExpectIntEQ((int)digestSz, WC_SHA_DIGEST_SIZE); + ExpectNotNull(wolfSSL_X509_get_subjectKeyID(x509ext, keyId, &keyIdSz)); + ExpectIntEQ(keyIdSz, WC_SHA_DIGEST_SIZE); + ExpectIntEQ(XMEMCMP(digest, keyId, WC_SHA_DIGEST_SIZE), 0); + /* A key stored with X509_set_pubkey digests to the same value. */ + ExpectNotNull(pubKey = X509_get_pubkey(x509ext)); + ExpectNotNull(x509Pub = wolfSSL_X509_new()); + ExpectIntEQ(X509_set_pubkey(x509Pub, pubKey), WOLFSSL_SUCCESS); + ExpectIntEQ(X509_pubkey_digest(x509Pub, wolfSSL_EVP_sha1(), digest2, + &digestSz), WOLFSSL_SUCCESS); + ExpectIntEQ(XMEMCMP(digest2, keyId, WC_SHA_DIGEST_SIZE), 0); + wolfSSL_X509_free(x509Pub); + EVP_PKEY_free(pubKey); ExpectIntEQ(X509_pubkey_digest(x509ext, wolfSSL_EVP_sha256(), digest, &digestSz), WOLFSSL_SUCCESS); diff --git a/tests/api/test_ossl_asn1.c b/tests/api/test_ossl_asn1.c index c646141ed7d..597bdd216dd 100644 --- a/tests/api/test_ossl_asn1.c +++ b/tests/api/test_ossl_asn1.c @@ -1997,6 +1997,60 @@ int test_wolfSSL_ASN1_TIME_adj(void) return EXPECT_RESULT(); } +int test_wolfSSL_ASN1_TIME_set(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_ASN_TIME) && \ + !defined(USER_TIME) && !defined(TIME_OVERRIDES) + const int year = 365*24*60*60; + const int day = 24*60*60; + const int hour = 60*60; + const int mini = 60; + WOLFSSL_ASN1_TIME* s = NULL; + WOLFSSL_ASN1_TIME* asn_time = NULL; + char date_str[CTC_DATE_SIZE + 1]; + time_t t; + + /* 2000/2/15 20:30:00 */ + t = (time_t)30 * year + 45 * day + 20 * hour + 30 * mini + 7 * day; + + ExpectNotNull(s = wolfSSL_ASN1_TIME_new()); + ExpectPtrEq(wolfSSL_ASN1_TIME_set(s, t), s); + if (s != NULL) { + ExpectIntEQ(s->type, ASN_UTC_TIME); + ExpectIntEQ(s->length, ASN_UTC_TIME_SIZE - 1); + XMEMCPY(date_str, s->data, CTC_DATE_SIZE); + date_str[CTC_DATE_SIZE] = '\0'; + ExpectIntEQ(XMEMCMP(date_str, "000215203000Z", 13), 0); + } + wolfSSL_ASN1_TIME_free(s); + s = NULL; + + /* Allocated when NULL passed in. */ + ExpectNotNull(asn_time = wolfSSL_ASN1_TIME_set(NULL, t)); + if (asn_time != NULL) { + ExpectIntEQ(asn_time->type, ASN_UTC_TIME); + ExpectIntEQ(XMEMCMP(asn_time->data, "000215203000Z", 13), 0); + } + wolfSSL_ASN1_TIME_free(asn_time); + asn_time = NULL; + +#if !defined(TIME_T_NOT_64BIT) && !defined(NO_64BIT) + /* 2055/03/01 09:00:00 uses GeneralizedTime. */ + t = (time_t)85 * year + 59 * day + 9 * hour + 21 * day; + ExpectNotNull(asn_time = wolfSSL_ASN1_TIME_set(NULL, t)); + if (asn_time != NULL) { + ExpectIntEQ(asn_time->type, ASN_GENERALIZED_TIME); + ExpectIntEQ(asn_time->length, ASN_GENERALIZED_TIME_SIZE - 1); + ExpectIntEQ(XMEMCMP(asn_time->data, "20550301090000Z", 15), 0); + } + wolfSSL_ASN1_TIME_free(asn_time); + asn_time = NULL; +#endif +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_ASN1_TIME_to_tm(void) { EXPECT_DECLS; diff --git a/tests/api/test_ossl_asn1.h b/tests/api/test_ossl_asn1.h index 7ae66eb0672..2afd8e2b393 100644 --- a/tests/api/test_ossl_asn1.h +++ b/tests/api/test_ossl_asn1.h @@ -50,6 +50,7 @@ int test_wolfSSL_ASN1_TIME(void); int test_wolfSSL_ASN1_TIME_to_string(void); int test_wolfSSL_ASN1_TIME_diff_compare(void); int test_wolfSSL_ASN1_TIME_adj(void); +int test_wolfSSL_ASN1_TIME_set(void); int test_wolfSSL_ASN1_TIME_to_tm(void); int test_wolfSSL_ASN1_TIME_to_generalizedtime(void); int test_wolfSSL_ASN1_TIME_print(void); @@ -97,6 +98,7 @@ int test_ASN1_strings(void); TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_to_string), \ TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_diff_compare), \ TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_adj), \ + TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_set), \ TEST_DECL_GROUP("ossl_asn1_tm", test_wolfSSL_ASN1_TIME_to_tm), \ TEST_DECL_GROUP("ossl_asn1_tm", \ test_wolfSSL_ASN1_TIME_to_generalizedtime), \ diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index c7024c40806..f013fd0002d 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -1016,25 +1016,22 @@ int test_wolfSSL_X509V3_set_ctx(void) wolfSSL_X509V3_set_ctx(NULL, NULL, NULL, NULL, NULL, 0); wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, NULL, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; + ExpectNull(ctx.issuer); + ExpectNull(ctx.subject); wolfSSL_X509V3_set_ctx(&ctx, issuer, NULL, NULL, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; + ExpectPtrEq(ctx.issuer, issuer); + ExpectNull(ctx.subject); wolfSSL_X509V3_set_ctx(&ctx, NULL, subject, NULL, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; - wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, &req, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; - wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, NULL, &crl, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; - wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, NULL, NULL, 1); - /* X509 allocated in context results in 'failure' (but not return). */ - wolfSSL_X509V3_set_ctx(&ctx, NULL, NULL, NULL, NULL, 0); - wolfSSL_X509_free(ctx.x509); - ctx.x509 = NULL; + ExpectNull(ctx.issuer); + ExpectPtrEq(ctx.subject, subject); + wolfSSL_X509V3_set_ctx(&ctx, issuer, subject, &req, &crl, 1); + ExpectPtrEq(ctx.issuer, issuer); + ExpectPtrEq(ctx.subject, subject); + /* No zero init required. */ + XMEMSET(&ctx, 0xff, sizeof(ctx)); + wolfSSL_X509V3_set_ctx(&ctx, issuer, subject, NULL, NULL, 0); + ExpectPtrEq(ctx.issuer, issuer); + ExpectPtrEq(ctx.subject, subject); wolfSSL_X509_free(subject); wolfSSL_X509_free(issuer); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 7a8451e5001..46bb6dbcf47 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -524,7 +524,8 @@ struct WOLFSSL_ACCESS_DESCRIPTION { }; struct WOLFSSL_X509V3_CTX { - WOLFSSL_X509* x509; + WOLFSSL_X509* issuer; + WOLFSSL_X509* subject; }; struct WOLFSSL_ASN1_OBJECT { From a8d69424b799f34b3deb4068da56a9b439154931 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 09:43:14 +0000 Subject: [PATCH 11/13] tcpdump 4.99.6: enable MD5 with --enable-tcpdump, add workflow tcpdump uses MD5 for the TCP MD5 signature option. The "options that require MD5" check runs before the tcpdump block sets OPENSSL_EXTRA and DES3, so MD5 stayed disabled. Turn it on in the tcpdump block. The workflow builds libpcap 1.10.5 next to tcpdump because the libpcap 1.10.4 shipped by Ubuntu 24.04 reads pcap timestamps as signed 32-bit values and fails the time_2038_overflow, time_2039, time_2106 and time_2106_max tests. --- .../ci-deps/packages-ubuntu-24.04-full.txt | 2 + .github/workflows/tcpdump.yml | 122 ++++++++++++++++++ configure.ac | 5 + 3 files changed, 129 insertions(+) create mode 100644 .github/workflows/tcpdump.yml diff --git a/.github/ci-deps/packages-ubuntu-24.04-full.txt b/.github/ci-deps/packages-ubuntu-24.04-full.txt index ecb82acac80..dc14cb6b339 100644 --- a/.github/ci-deps/packages-ubuntu-24.04-full.txt +++ b/.github/ci-deps/packages-ubuntu-24.04-full.txt @@ -9,6 +9,7 @@ autoconf-archive automake autopoint bc +bison bubblewrap build-essential ccache @@ -16,6 +17,7 @@ clang clang-14 clang-19 cmake +flex g++-10 g++-11 g++-12 diff --git a/.github/workflows/tcpdump.yml b/.github/workflows/tcpdump.yml new file mode 100644 index 00000000000..accbbfcb09c --- /dev/null +++ b/.github/workflows/tcpdump.yml @@ -0,0 +1,122 @@ +name: tcpdump Tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +jobs: + build_wolfssl: + name: Build wolfSSL + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + # Just to keep it the same as the testing target + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 4 + steps: + - name: Build wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + path: wolfssl + configure: --enable-tcpdump + install: true + check: false + + - name: tar build-dir + run: tar -zcf build-dir.tgz build-dir + + - name: Upload built lib + uses: actions/upload-artifact@v6 + with: + name: wolf-install-tcpdump + path: build-dir.tgz + retention-days: 5 + + tcpdump_check: + strategy: + fail-fast: false + matrix: + # List of releases to test + ref: [ 4.99.6 ] + name: ${{ matrix.ref }} + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + # This should be a safe limit for the tests to run. + timeout-minutes: 10 + needs: build_wolfssl + steps: + - name: Checkout wolfSSL CI actions + uses: actions/checkout@v5 + with: + sparse-checkout: .github/actions + fetch-depth: 1 + + - name: Download lib + uses: actions/download-artifact@v7 + with: + name: wolf-install-tcpdump + + - name: untar build-dir + run: tar -xf build-dir.tgz + + - name: Install dependencies + uses: ./.github/actions/install-apt-deps + with: + packages: bison flex + ghcr-debs-tag: ubuntu-24.04-full + + - name: Checkout OSP + uses: actions/checkout@v5 + with: + repository: wolfssl/osp + path: osp + fetch-depth: 1 + + # Ubuntu 24.04 ships libpcap 1.10.4, which reads pcap timestamps as + # signed 32-bit values. That breaks the time_2038_overflow, time_2039, + # time_2106 and time_2106_max tests. Build libpcap 1.10.5 next to + # tcpdump instead; tcpdump's configure picks up ../libpcap on its own. + - name: Checkout libpcap + uses: actions/checkout@v5 + with: + repository: the-tcpdump-group/libpcap + path: libpcap + ref: libpcap-1.10.5 + fetch-depth: 1 + + - name: Build libpcap + working-directory: libpcap + run: | + ./autogen.sh + ./configure + make -j + + - name: Checkout tcpdump + uses: actions/checkout@v5 + with: + repository: the-tcpdump-group/tcpdump + path: tcpdump + ref: tcpdump-${{ matrix.ref }} + fetch-depth: 1 + + - name: Build and test tcpdump + working-directory: tcpdump + run: | + export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/build-dir/lib:$LD_LIBRARY_PATH + patch -p1 < $GITHUB_WORKSPACE/osp/tcpdump/${{ matrix.ref }}/tcpdump-${{ matrix.ref }}.patch + autoreconf -ivf + ./configure --with-wolfssl=$GITHUB_WORKSPACE/build-dir + make -j + ldd ./tcpdump | grep wolfssl + make check diff --git a/configure.ac b/configure.ac index 6892e398568..e79317f56b8 100644 --- a/configure.ac +++ b/configure.ac @@ -10557,6 +10557,11 @@ then then ENABLED_DES3="yes" fi + + if test "x$ENABLED_MD5" = "xno" + then + ENABLED_MD5="yes" + fi fi # sblim-sfcb support From e36d066af7f0aa3b1acb8fcdf35161935075ea17 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 09:31:17 +0000 Subject: [PATCH 12/13] python 3.14.5: add X509_VERIFY_PARAM_get_hostflags, free AIA entries CPython 3.14 calls X509_VERIFY_PARAM_get_hostflags() unconditionally. Add the getter next to the existing setter and cover it in test_wolfSSL_X509_VERIFY_PARAM. OpenSSL's AUTHORITY_INFO_ACCESS_free is an ASN.1 item free and releases the ACCESS_DESCRIPTION entries. wolfSSL's freed only the stack nodes, so CPython's _get_aia_uri leaked the entries. No in-tree caller relied on the shallow free. Add the 3.14.5 entry to the Python workflow matrix. --- .github/workflows/python.yml | 16 ++++++++++++++++ src/x509.c | 16 ++++++++++++---- tests/api/test_ossl_x509_ext.c | 5 +++++ tests/api/test_ossl_x509_vp.c | 4 ++++ wolfssl/openssl/ssl.h | 1 + wolfssl/ssl.h | 2 ++ 6 files changed, 40 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 95d33be9965..7255df7bf9d 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -96,6 +96,22 @@ jobs: test_urllib2_localnet test_xmlrpc test_docxmlrpc + - python_ver: 3.14.5 + tests: >- + test_ssl + test.test_asyncio.test_ssl + test.test_asyncio.test_sslproto + test_hashlib + test_hmac + test_secrets + test_ftplib + test_imaplib + test_poplib + test_smtplib + test_httplib + test_urllib2_localnet + test_xmlrpc + test_docxmlrpc name: Python ${{ matrix.python_ver }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/src/x509.c b/src/x509.c index 04d166734b7..c989d1bbe4c 100644 --- a/src/x509.c +++ b/src/x509.c @@ -5066,14 +5066,13 @@ void wolfSSL_sk_ACCESS_DESCRIPTION_free(WOLFSSL_STACK* sk) } -/* AUTHORITY_INFO_ACCESS object is a stack of ACCESS_DESCRIPTION objects, - * to free the stack the WOLFSSL_ACCESS_DESCRIPTION stack free function is - * used */ +/* AUTHORITY_INFO_ACCESS object is a stack of ACCESS_DESCRIPTION objects. + * Free the entries and the stack, as OpenSSL does. */ void wolfSSL_AUTHORITY_INFO_ACCESS_free( WOLF_STACK_OF(WOLFSSL_ACCESS_DESCRIPTION)* sk) { WOLFSSL_ENTER("wolfSSL_AUTHORITY_INFO_ACCESS_free"); - wolfSSL_sk_ACCESS_DESCRIPTION_free(sk); + wolfSSL_sk_ACCESS_DESCRIPTION_pop_free(sk, wolfSSL_ACCESS_DESCRIPTION_free); } void wolfSSL_AUTHORITY_INFO_ACCESS_pop_free( @@ -10897,6 +10896,15 @@ void wolfSSL_X509_VERIFY_PARAM_set_hostflags(WOLFSSL_X509_VERIFY_PARAM* param, } } +unsigned int wolfSSL_X509_VERIFY_PARAM_get_hostflags( + const WOLFSSL_X509_VERIFY_PARAM* param) +{ + if (param == NULL) { + return 0; + } + return param->hostFlags; +} + /* Sets the expected IP address to ipasc. * * param is a pointer to the X509_VERIFY_PARAM structure diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index f013fd0002d..16cbc2e5c74 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -1503,6 +1503,11 @@ int test_wolfSSL_X509V3_EXT_aia(void) wolfSSL_AUTHORITY_INFO_ACCESS_pop_free(aia, wolfSSL_ACCESS_DESCRIPTION_free); + aia = NULL; + /* Plain free releases the entries too. */ + ExpectNotNull(aia = (WOLFSSL_AUTHORITY_INFO_ACCESS *) + wolfSSL_X509V3_EXT_d2i(ext)); + wolfSSL_AUTHORITY_INFO_ACCESS_free(aia); wolfSSL_ASN1_OBJECT_free(entry); wolfSSL_sk_free(node); wolfSSL_ASN1_OBJECT_free(obj); diff --git a/tests/api/test_ossl_x509_vp.c b/tests/api/test_ossl_x509_vp.c index f3b463b36e2..0abcd96e740 100644 --- a/tests/api/test_ossl_x509_vp.c +++ b/tests/api/test_ossl_x509_vp.c @@ -74,9 +74,11 @@ int test_wolfSSL_X509_VERIFY_PARAM(void) (int)XSTRLEN(testhostName1)), 1); X509_VERIFY_PARAM_set_hostflags(NULL, 0x00); + ExpectIntEQ(X509_VERIFY_PARAM_get_hostflags(NULL), 0); X509_VERIFY_PARAM_set_hostflags(paramFrom, 0x01); ExpectIntEQ(0x01, paramFrom->hostFlags); + ExpectIntEQ(X509_VERIFY_PARAM_get_hostflags(paramFrom), 0x01); ExpectIntEQ(X509_VERIFY_PARAM_set1_ip_asc(NULL, testIPv4), 0); @@ -96,10 +98,12 @@ int test_wolfSSL_X509_VERIFY_PARAM(void) ExpectIntEQ(X509_VERIFY_PARAM_set1(NULL, NULL), 0); /* inherit flags test : VPARAM_DEFAULT */ + ExpectIntEQ(X509_VERIFY_PARAM_get_hostflags(paramTo), 0); ExpectIntEQ(X509_VERIFY_PARAM_set1(paramTo, paramFrom), 1); ExpectIntEQ(0, XSTRNCMP(paramTo->hostName, testhostName1, (int)XSTRLEN(testhostName1))); ExpectIntEQ(0x01, paramTo->hostFlags); + ExpectIntEQ(X509_VERIFY_PARAM_get_hostflags(paramTo), 0x01); ExpectIntEQ(0, XSTRNCMP(paramTo->ipasc, testIPv6, WOLFSSL_MAX_IPSTR)); /* inherit flags test : VPARAM OVERWRITE */ diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 762eb5833fa..ce28eb33bfa 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -821,6 +821,7 @@ wolfSSL_X509_STORE_set_verify_cb((WOLFSSL_X509_STORE *)(s), (WOLFSSL_X509_STORE_ #define X509_VERIFY_PARAM_get_flags wolfSSL_X509_VERIFY_PARAM_get_flags #define X509_VERIFY_PARAM_clear_flags wolfSSL_X509_VERIFY_PARAM_clear_flags #define X509_VERIFY_PARAM_set_hostflags wolfSSL_X509_VERIFY_PARAM_set_hostflags +#define X509_VERIFY_PARAM_get_hostflags wolfSSL_X509_VERIFY_PARAM_get_hostflags #define SSL_set1_host wolfSSL_set1_host #define X509_VERIFY_PARAM_set1_host wolfSSL_X509_VERIFY_PARAM_set1_host #define X509_VERIFY_PARAM_set1_ip_asc wolfSSL_X509_VERIFY_PARAM_set1_ip_asc diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 46bb6dbcf47..3e6bf1fc79b 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2570,6 +2570,8 @@ WOLFSSL_API int wolfSSL_X509_VERIFY_PARAM_clear_flags(WOLFSSL_X509_VERIFY_PARAM unsigned long flags); WOLFSSL_API void wolfSSL_X509_VERIFY_PARAM_set_hostflags( WOLFSSL_X509_VERIFY_PARAM* param, unsigned int flags); +WOLFSSL_API unsigned int wolfSSL_X509_VERIFY_PARAM_get_hostflags( + const WOLFSSL_X509_VERIFY_PARAM* param); WOLFSSL_API int wolfSSL_set1_host(WOLFSSL* ssl, const char * name); WOLFSSL_API int wolfSSL_X509_VERIFY_PARAM_set1_host(WOLFSSL_X509_VERIFY_PARAM* pParam, const char* name, From 1884475f163930374c53fc916fb7441ac1dd6510 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 4 Sep 2026 09:48:52 +0000 Subject: [PATCH 13/13] softhsm 2.7.0: add ERR_set_mark/ERR_pop_to_mark, BN_mask_bits, update workflow - Add ERR_set_mark and ERR_pop_to_mark for both error queue variants. - Add EVP_PKEY_RSA_PSS. - Implement BN_mask_bits (was a stub) and guard its declaration like the definition: wolfSSL_mask_bits is not built with WOLFSSL_SP_MATH, and an unconditional declaration turned a missing API into a link error. - EVP_DecryptUpdate with padding: flush the held back block as soon as partial data is buffered, like OpenSSL. Otherwise a later update could write inl + 2 * block_size - 1 bytes into a buffer sized inl + block_size. - Add softhsm 2.7.0 to the workflow matrix. --- .github/workflows/softhsm.yml | 2 +- src/ssl_bn.c | 30 +++++++++---- src/ssl_err.c | 31 ++++++++++++++ tests/api.c | 55 ++++++++++++++++++++++++ tests/api/test_evp_cipher.c | 69 ++++++++++++++++++++++++++++++ tests/api/test_evp_cipher.h | 2 + tests/api/test_evp_pkey.c | 3 ++ tests/api/test_ossl_bn.c | 22 ++++++++-- wolfcrypt/src/evp.c | 9 ++++ wolfcrypt/src/logging.c | 79 +++++++++++++++++++++++++++++++++++ wolfcrypt/test/test.c | 15 ++++--- wolfssl/openssl/bn.h | 4 ++ wolfssl/openssl/evp.h | 5 +++ wolfssl/openssl/ssl.h | 2 + wolfssl/ssl.h | 2 + wolfssl/wolfcrypt/logging.h | 2 + 16 files changed, 314 insertions(+), 18 deletions(-) diff --git a/.github/workflows/softhsm.yml b/.github/workflows/softhsm.yml index aeb60ce2271..6b0610638bf 100644 --- a/.github/workflows/softhsm.yml +++ b/.github/workflows/softhsm.yml @@ -48,7 +48,7 @@ jobs: fail-fast: false matrix: # List of releases to test - ref: [ 2.6.1 ] + ref: [ 2.6.1, 2.7.0 ] name: ${{ matrix.ref }} if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} runs-on: ubuntu-24.04 diff --git a/src/ssl_bn.c b/src/ssl_bn.c index ea1355cb4b3..f74c42a4704 100644 --- a/src/ssl_bn.c +++ b/src/ssl_bn.c @@ -880,24 +880,38 @@ int wolfSSL_BN_is_odd(const WOLFSSL_BIGNUM* bn) return ret; } -#ifndef NO_WOLFSSL_STUB -/* Mask the lowest n bits. - * - * TODO: mp_mod_2d() +#ifndef WOLFSSL_SP_MATH +/* Keep only the lowest n bits. bn = bn mod 2^n * * Return compliant with OpenSSL. * * @param [in, out] bn Big number to operation on. * @param [in] n Number of bits. + * @return 1 on success. + * @return 0 when bn or internal representation of bn is NULL. + * @return 0 when n is negative. * @return 0 on failure. */ int wolfSSL_mask_bits(WOLFSSL_BIGNUM* bn, int n) { - (void)bn; - (void)n; + int ret = 1; + WOLFSSL_ENTER("wolfSSL_BN_mask_bits"); - WOLFSSL_STUB("BN_mask_bits"); - return 0; + + /* Validate parameters. */ + if (BN_IS_NULL(bn) || (n < 0)) { + WOLFSSL_MSG("bn NULL error"); + ret = 0; + } + + /* Use wolfCrypt perform operation. */ + if ((ret == 1) && (mp_mod_2d((mp_int*)bn->internal, n, + (mp_int*)bn->internal) != MP_OKAY)) { + WOLFSSL_MSG("mp_mod_2d error"); + ret = 0; + } + + return ret; } #endif diff --git a/src/ssl_err.c b/src/ssl_err.c index 27100bf7c7b..eab59398aab 100644 --- a/src/ssl_err.c +++ b/src/ssl_err.c @@ -693,6 +693,37 @@ void wolfSSL_ERR_remove_state(unsigned long id) WOLFSSL_MSG("Error with removing the state"); } } + +/* Mark the newest entry in the error queue. + * + * @return 1 on success. + * @return 0 when the queue is empty or there is no error queue. + */ +int wolfSSL_ERR_set_mark(void) +{ + WOLFSSL_ENTER("wolfSSL_ERR_set_mark"); +#ifdef WOLFSSL_HAVE_ERROR_QUEUE + return wc_SetErrorMark(); +#else + return 0; +#endif +} + +/* Remove entries newer than the last mark and clear that mark. + * + * @return 1 when a mark was found. + * @return 0 when no mark was found and the queue is now empty, or there is + * no error queue. + */ +int wolfSSL_ERR_pop_to_mark(void) +{ + WOLFSSL_ENTER("wolfSSL_ERR_pop_to_mark"); +#ifdef WOLFSSL_HAVE_ERROR_QUEUE + return wc_PopErrorMark(); +#else + return 0; +#endif +} #endif #endif /* !WOLFCRYPT_ONLY */ diff --git a/tests/api.c b/tests/api.c index 0647a4fd0ef..4d5942fbb04 100644 --- a/tests/api.c +++ b/tests/api.c @@ -20823,6 +20823,60 @@ static int test_wolfSSL_ERR_get_error_order(void) return EXPECT_RESULT(); } +static int test_wolfSSL_ERR_set_mark(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_ERROR_QUEUE) && defined(OPENSSL_EXTRA) + wolfSSL_ERR_clear_error(); + + /* Empty queue: nothing to mark, nothing to pop. */ + ExpectIntEQ(wolfSSL_ERR_set_mark(), 0); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 0); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); + + /* Pop removes entries newer than the mark and keeps the marked one. */ + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_NO_SIGNER_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_set_mark(), 1); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E), "test", 0); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_PARSE_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_peek_last_error(), + -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_get_error(), -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); + + /* Mark is cleared by the pop, so a second pop empties the queue. */ + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_NO_SIGNER_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_set_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_peek_error(), -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 0); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); + + /* No mark: pop empties the queue. */ + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_NO_SIGNER_E), "test", 0); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 0); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); + + /* Nested marks. */ + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_NO_SIGNER_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_set_mark(), 1); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_set_mark(), 1); + wolfSSL_ERR_put_error(0, 0, WC_NO_ERR_TRACE(ASN_PARSE_E), "test", 0); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_peek_last_error(), + -WC_NO_ERR_TRACE(ASN_SELF_SIGNED_E)); + ExpectIntEQ(wolfSSL_ERR_pop_to_mark(), 1); + ExpectIntEQ(wolfSSL_ERR_peek_last_error(), + -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_get_error(), -WC_NO_ERR_TRACE(ASN_NO_SIGNER_E)); + ExpectIntEQ(wolfSSL_ERR_get_error(), 0); +#endif /* WOLFSSL_HAVE_ERROR_QUEUE && OPENSSL_EXTRA */ + return EXPECT_RESULT(); +} + static int test_wolfSSL_ERR_GET_REASON_version_mismatch(void) { EXPECT_DECLS; @@ -41954,6 +42008,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_error_queue_per_thread), TEST_DECL(test_wolfSSL_ERR_put_error), TEST_DECL(test_wolfSSL_ERR_get_error_order), + TEST_DECL(test_wolfSSL_ERR_set_mark), TEST_DECL(test_wolfSSL_ERR_GET_REASON_version_mismatch), #ifndef NO_BIO TEST_DECL(test_wolfSSL_ERR_print_errors), diff --git a/tests/api/test_evp_cipher.c b/tests/api/test_evp_cipher.c index c3de8b18b23..eb08b32c23c 100644 --- a/tests/api/test_evp_cipher.c +++ b/tests/api/test_evp_cipher.c @@ -639,6 +639,75 @@ int test_wolfSSL_EVP_CipherUpdate_Null(void) return EXPECT_RESULT(); } +/* Decrypting with padding in parts must never write more than + * inl + block_size bytes into out. */ +int test_wolfSSL_EVP_DecryptUpdate_partial(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_128) + WOLFSSL_EVP_CIPHER_CTX* ctx = NULL; + const byte key[16] = { 0 }; + const byte iv[16] = { 0 }; + byte plain[70]; + byte cipher[80]; + byte dec[96]; + byte out[96]; + int cipherLen = 0; + int decLen = 0; + int outl = 0; + int i; + /* Part sizes that leave a held back block next to buffered bytes. */ + const int parts[] = { 3, 16, 3, 29, 16, 8, 5 }; + int off = 0; + + for (i = 0; i < (int)sizeof(plain); i++) + plain[i] = (byte)i; + + ExpectNotNull(ctx = wolfSSL_EVP_CIPHER_CTX_new()); + ExpectIntEQ(wolfSSL_EVP_CipherInit_ex(ctx, wolfSSL_EVP_aes_128_cbc(), + NULL, key, iv, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_EVP_CipherUpdate(ctx, cipher, &outl, plain, + (int)sizeof(plain)), WOLFSSL_SUCCESS); + cipherLen = outl; + ExpectIntEQ(wolfSSL_EVP_CipherFinal(ctx, cipher + cipherLen, &outl), + WOLFSSL_SUCCESS); + cipherLen += outl; + ExpectIntEQ(cipherLen, 80); + + ExpectIntEQ(wolfSSL_EVP_CipherInit_ex(ctx, wolfSSL_EVP_aes_128_cbc(), + NULL, key, iv, 0), WOLFSSL_SUCCESS); + for (i = 0; i < (int)(sizeof(parts) / sizeof(parts[0])); i++) { + int inl = parts[i]; + int j; + + XMEMSET(out, 0xAA, sizeof(out)); + ExpectIntEQ(wolfSSL_EVP_CipherUpdate(ctx, out, &outl, cipher + off, + inl), WOLFSSL_SUCCESS); + ExpectIntLE(outl, inl + 16); + /* Nothing written past inl + block_size. */ + for (j = inl + 16; j < (int)sizeof(out); j++) + ExpectIntEQ(out[j], 0xAA); + ExpectIntLE(decLen + outl, (int)sizeof(dec)); + if (EXPECT_SUCCESS()) + XMEMCPY(dec + decLen, out, (size_t)outl); + decLen += outl; + off += inl; + } + ExpectIntEQ(off, cipherLen); + ExpectIntEQ(wolfSSL_EVP_CipherFinal(ctx, out, &outl), WOLFSSL_SUCCESS); + ExpectIntLE(decLen + outl, (int)sizeof(dec)); + if (EXPECT_SUCCESS()) + XMEMCPY(dec + decLen, out, (size_t)outl); + decLen += outl; + ExpectIntEQ(decLen, (int)sizeof(plain)); + ExpectBufEQ(dec, plain, sizeof(plain)); + + wolfSSL_EVP_CIPHER_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* Test for wolfSSL_EVP_CIPHER_type_string() */ int test_wolfSSL_EVP_CIPHER_type_string(void) { diff --git a/tests/api/test_evp_cipher.h b/tests/api/test_evp_cipher.h index 7c8c1a0e1bd..e02fcbe4bc7 100644 --- a/tests/api/test_evp_cipher.h +++ b/tests/api/test_evp_cipher.h @@ -32,6 +32,7 @@ int test_wolfSSL_EVP_get_cipherbynid(void); int test_wolfSSL_EVP_CIPHER_block_size(void); int test_wolfSSL_EVP_CIPHER_iv_length(void); int test_wolfSSL_EVP_CipherUpdate_Null(void); +int test_wolfSSL_EVP_DecryptUpdate_partial(void); int test_wolfSSL_EVP_CIPHER_type_string(void); int test_wolfSSL_EVP_BytesToKey(void); int test_wolfSSL_EVP_Cipher_extra(void); @@ -77,6 +78,7 @@ int test_evp_cipher_aead_aad_overflow(void); TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_CIPHER_block_size), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_CIPHER_iv_length), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_CipherUpdate_Null), \ + TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_DecryptUpdate_partial), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_CIPHER_type_string), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_BytesToKey), \ TEST_DECL_GROUP("evp_cipher", test_wolfSSL_EVP_Cipher_extra), \ diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 25a2753f057..f1666a6a2b0 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -144,6 +144,9 @@ int test_wolfSSL_EVP_PKEY_id(void) ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), EVP_PKEY_RSA); + ExpectIntEQ(EVP_PKEY_RSA_PSS, NID_rsassaPss); + ExpectIntNE(EVP_PKEY_RSA_PSS, EVP_PKEY_RSA); + EVP_PKEY_free(pkey); #endif return EXPECT_RESULT(); diff --git a/tests/api/test_ossl_bn.c b/tests/api/test_ossl_bn.c index 46d3f67cd40..5fe7ef19725 100644 --- a/tests/api/test_ossl_bn.c +++ b/tests/api/test_ossl_bn.c @@ -532,9 +532,25 @@ int test_wolfSSL_BN_bits(void) ExpectIntEQ(BN_set_bit(a, 129), 1); ExpectIntEQ(BN_get_word(a), WOLFSSL_BN_MAX_VAL); -#ifndef NO_WOLFSSL_STUB - ExpectIntEQ(BN_mask_bits(a, 1), 0); -#endif + /* Invalid parameters. */ + ExpectIntEQ(BN_mask_bits(NULL, 1), 0); + ExpectIntEQ(BN_mask_bits(&emptyBN, 1), 0); + ExpectIntEQ(BN_mask_bits(a, -1), 0); + + /* a = 2^129 + 2 */ + ExpectIntEQ(BN_mask_bits(a, 128), 1); + ExpectIntEQ(BN_num_bits(a), 2); + ExpectIntEQ(BN_get_word(a), 2); + /* No change when a already fits. */ + ExpectIntEQ(BN_mask_bits(a, 200), 1); + ExpectIntEQ(BN_get_word(a), 2); + ExpectIntEQ(BN_mask_bits(a, 1), 1); + ExpectIntEQ(BN_is_zero(a), 1); + ExpectIntEQ(BN_set_word(a, 0xff), 1); + ExpectIntEQ(BN_mask_bits(a, 4), 1); + ExpectIntEQ(BN_get_word(a), 0xf); + ExpectIntEQ(BN_mask_bits(a, 0), 1); + ExpectIntEQ(BN_is_zero(a), 1); BN_free(a); #endif diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index 8870d4e16b6..3eb806d1f47 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -1267,6 +1267,15 @@ int wolfSSL_EVP_CipherUpdate(WOLFSSL_EVP_CIPHER_CTX *ctx, /* put fraction into buff */ fillBuff(ctx, in, inl); /* no increase of outl */ + + /* Decrypting with padding: a block followed by buffered bytes can not + * be the last one. Output it now so that the next update writes at + * most inl + block_size bytes, as OpenSSL does. */ + if ((ctx->enc == 0) && (ctx->lastUsed == 1)) { + XMEMCPY(out, ctx->lastBlock, (size_t)ctx->block_size); + *outl += ctx->block_size; + ctx->lastUsed = 0; + } } (void)out; /* silence warning in case not read */ diff --git a/wolfcrypt/src/logging.c b/wolfcrypt/src/logging.c index 736adbebdb6..b6fb4c60fa4 100644 --- a/wolfcrypt/src/logging.c +++ b/wolfcrypt/src/logging.c @@ -728,6 +728,7 @@ struct wc_error_entry { char file[WOLFSSL_MAX_ERROR_SZ]; int line; int err; + int mark; /* set by wc_SetErrorMark */ }; struct wc_error_queue { @@ -956,6 +957,34 @@ int wc_ERR_remove_state(void) return 0; } +/* Mark the newest entry. Returns 0 if the queue is empty. */ +int wc_SetErrorMark(void) +{ + struct wc_error_entry *entry = get_entry(-1); + + if (entry == NULL) { + return 0; + } + entry->mark = 1; + return 1; +} + +/* Remove entries newer than the last mark and clear that mark. + * Returns 0 if no mark was found and the queue is now empty. */ +int wc_PopErrorMark(void) +{ + struct wc_error_entry *entry; + + while ((entry = get_entry(-1)) != NULL) { + if (entry->mark) { + entry->mark = 0; + return 1; + } + wc_RemoveErrorNode(-1); + } + return 0; +} + /** * Get the first entry's values in the ERR queue that is not filtered * by the provided `ignore_err` callback. All ignored entries are removed, @@ -1078,6 +1107,7 @@ struct wc_error_queue { char file[WOLFSSL_MAX_ERROR_SZ]; int value; int line; + int mark; /* set by wc_SetErrorMark */ }; /* The global list of errors encountered */ @@ -1477,6 +1507,45 @@ int wc_ERR_remove_state(void) return 0; } +/* Mark the newest node. Returns 0 if the queue is empty. */ +int wc_SetErrorMark(void) +{ + int ret = 0; + + if (ERRQ_LOCK() != 0) { + WOLFSSL_MSG("Lock debug mutex failed"); + return 0; + } + if (wc_last_node != NULL) { + wc_last_node->mark = 1; + ret = 1; + } + ERRQ_UNLOCK(); + return ret; +} + +/* Remove nodes newer than the last mark and clear that mark. + * Returns 0 if no mark was found and the queue is now empty. */ +int wc_PopErrorMark(void) +{ + int ret = 0; + + if (ERRQ_LOCK() != 0) { + WOLFSSL_MSG("Lock debug mutex failed"); + return 0; + } + while (wc_last_node != NULL) { + if (wc_last_node->mark) { + wc_last_node->mark = 0; + ret = 1; + break; + } + removeErrorNode(-1); + } + ERRQ_UNLOCK(); + return ret; +} + unsigned long wc_PeekErrorNodeLineData(const char **file, int *line, const char **data, int *flags, int (*ignore_err)(int err)) @@ -1680,6 +1749,16 @@ int wc_ERR_remove_state(void) return 0; } +int wc_SetErrorMark(void) +{ + return 0; +} + +int wc_PopErrorMark(void) +{ + return 0; +} + /* Returns 0 both when the error queue is empty and when * WOLFSSL_HAVE_ERROR_QUEUE is not compiled in. */ unsigned long wc_PeekErrorNodeLineData(const char **file, int *line, diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 4310aefe208..3328f32d105 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -35771,13 +35771,14 @@ static wc_test_ret_t openssl_aes_cbc_test(void) if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6], 12) == 0) return WC_TEST_RET_ENC_NC; - if (outlen != 0) + if (outlen != 16) + return WC_TEST_RET_ENC_NC; total += outlen; if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6+12], 14) == 0) return WC_TEST_RET_ENC_NC; - if (outlen != 16) + if (outlen != 0) return WC_TEST_RET_ENC_NC; total += outlen; @@ -37627,13 +37628,14 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t openssl_test(void) if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6], 12) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - if(outlen != 0) + if(outlen != 16) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); total += outlen; if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6+12], 14) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - if(outlen != 16) + if(outlen != 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); total += outlen; @@ -37692,12 +37694,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t openssl_test(void) if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6], 12) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - if(outlen != 0) + if(outlen != 16) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); total += outlen; if (wolfSSL_EVP_CipherUpdate(de, (byte*)&plain[total], &outlen, (byte*)&cipher[6+12], 14) == 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); - if(outlen != 16) + if(outlen != 0) ERROR_OUT(WC_TEST_RET_ENC_NC, out); total += outlen; diff --git a/wolfssl/openssl/bn.h b/wolfssl/openssl/bn.h index 7371f8a03ac..badb69d6341 100644 --- a/wolfssl/openssl/bn.h +++ b/wolfssl/openssl/bn.h @@ -140,7 +140,9 @@ WOLFSSL_API int wolfSSL_BN_bn2binpad(const WOLFSSL_BIGNUM* bn, unsigned char* r, WOLFSSL_API WOLFSSL_BIGNUM* wolfSSL_BN_bin2bn(const unsigned char* str, int len, WOLFSSL_BIGNUM* ret); +#ifndef WOLFSSL_SP_MATH WOLFSSL_API int wolfSSL_mask_bits(WOLFSSL_BIGNUM* bn, int n); +#endif WOLFSSL_API int wolfSSL_BN_pseudo_rand(WOLFSSL_BIGNUM* bn, int bits, int top, int bottom); @@ -263,7 +265,9 @@ typedef WOLFSSL_BN_GENCB BN_GENCB; #define BN_gcd wolfSSL_BN_gcd #define BN_value_one wolfSSL_BN_value_one +#ifndef WOLFSSL_SP_MATH #define BN_mask_bits wolfSSL_mask_bits +#endif #define BN_pseudo_rand wolfSSL_BN_pseudo_rand #define BN_rand wolfSSL_BN_rand diff --git a/wolfssl/openssl/evp.h b/wolfssl/openssl/evp.h index 8caa45b39e1..bffa7f400f5 100644 --- a/wolfssl/openssl/evp.h +++ b/wolfssl/openssl/evp.h @@ -493,6 +493,10 @@ enum { #define WOLFSSL_EVP_PKEY_PRINT_INDENT_MAX 128 +/* wolfSSL decodes RSA-PSS keys as WC_EVP_PKEY_RSA. This id is only for + * applications that switch on it alongside WC_EVP_PKEY_RSA. */ +#define WC_EVP_PKEY_RSA_PSS WC_NID_rsassaPss + #define WC_EVP_PKEY_OP_SIGN (1 << 3) #define WC_EVP_PKEY_OP_VERIFY (1 << 5) #define WC_EVP_PKEY_OP_ENCRYPT (1 << 6) @@ -518,6 +522,7 @@ enum { #define ARC4_TYPE WC_ARC4_TYPE #define NULL_CIPHER_TYPE WC_NULL_CIPHER_TYPE #define EVP_PKEY_RSA WC_EVP_PKEY_RSA +#define EVP_PKEY_RSA_PSS WC_EVP_PKEY_RSA_PSS #define EVP_PKEY_DSA WC_EVP_PKEY_DSA #define EVP_PKEY_EC WC_EVP_PKEY_EC #define AES_128_GCM_TYPE WC_AES_128_GCM_TYPE diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index ce28eb33bfa..a4172e6c200 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -1240,6 +1240,8 @@ typedef wolfSSL_custom_ext_parse_cb custom_ext_parse_cb; #define ERR_print_errors_cb wolfSSL_ERR_print_errors_cb #define ERR_print_errors wolfSSL_ERR_print_errors #define ERR_clear_error wolfSSL_ERR_clear_error +#define ERR_set_mark wolfSSL_ERR_set_mark +#define ERR_pop_to_mark wolfSSL_ERR_pop_to_mark #define ERR_free_strings wolfSSL_ERR_free_strings #define ERR_remove_state wolfSSL_ERR_remove_state #define ERR_remove_thread_state wolfSSL_ERR_remove_thread_state diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 3e6bf1fc79b..487046d7cfd 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3440,6 +3440,8 @@ WOLFSSL_API unsigned long wolfSSL_ERR_get_error_line_data(const char** file, int WOLFSSL_API unsigned long wolfSSL_ERR_get_error(void); WOLFSSL_API void wolfSSL_ERR_clear_error(void); +WOLFSSL_API int wolfSSL_ERR_set_mark(void); +WOLFSSL_API int wolfSSL_ERR_pop_to_mark(void); WOLFSSL_API int wolfSSL_RAND_status(void); diff --git a/wolfssl/wolfcrypt/logging.h b/wolfssl/wolfcrypt/logging.h index 631c1cb9acc..13542dfb1be 100644 --- a/wolfssl/wolfcrypt/logging.h +++ b/wolfssl/wolfcrypt/logging.h @@ -236,6 +236,8 @@ WOLFSSL_API void wolfSSL_SetLoggingPrefix(const char* prefix); int *line); WOLFSSL_API int wc_SetLoggingHeap(void* h); WOLFSSL_API int wc_ERR_remove_state(void); + WOLFSSL_LOCAL int wc_SetErrorMark(void); + WOLFSSL_LOCAL int wc_PopErrorMark(void); WOLFSSL_LOCAL unsigned long wc_PeekErrorNodeLineData( const char **file, int *line, const char **data, int *flags, int (*ignore_err)(int err));