diff --git a/src/x509/clu_cert_setup.c b/src/x509/clu_cert_setup.c index a7cda803..0b3783c5 100644 --- a/src/x509/clu_cert_setup.c +++ b/src/x509/clu_cert_setup.c @@ -670,7 +670,10 @@ int wolfCLU_certSetup(int argc, char **argv) keyUsage = 0; } - wolfCLU_extKeyUsagePrint(out, keyUsage, 0, 1); + if ((ret = wolfCLU_extKeyUsagePrint(out, keyUsage, 0, 1)) + != WOLFCLU_SUCCESS) { + wolfCLU_LogError("Unable to print ext key usage"); + } #else wolfCLU_LogError("Extended key function not supported by this" " version of wolfSSL"); diff --git a/src/x509/clu_parse.c b/src/x509/clu_parse.c index 0784f961..f72299ca 100644 --- a/src/x509/clu_parse.c +++ b/src/x509/clu_parse.c @@ -143,67 +143,67 @@ int wolfCLU_printX509PubKey(WOLFSSL_X509* x509, WOLFSSL_BIO* out) } +/* Print the extended key usages held in 'keyUsage'. + * + * 'keyUsage' is the bit map returned by wolfSSL_X509_get_extended_key_usage(), + * so it must be masked with the XKU_* flags rather than with the + * ExtKeyUsage_Sum OID values, which are not single bit flags. + * + * When 'flag' is set every purpose is listed with a YES/NO verdict, otherwise + * only the purposes present in the certificate are printed. + */ int wolfCLU_extKeyUsagePrint(WOLFSSL_BIO* bio, unsigned int keyUsage, int indent, int flag) { - unsigned int ava; +#if LIBWOLFSSL_VERSION_HEX > 0x05001000 + unsigned int i; char scratch[MAX_TERM_WIDTH]; - if (flag) { - XSNPRINTF(scratch, MAX_TERM_WIDTH, "%*s%s\n", indent, "", - "Certificate Purpose:"); - wolfSSL_BIO_write(bio, scratch, (int)XSTRLEN(scratch)); - } - - ava = (EKU_ANY_OID & keyUsage); - if (ava | flag) { - XSNPRINTF(scratch, MAX_TERM_WIDTH, "%*s%s%s\n", indent, "", - "Any Extended Key Usage", - (flag == 1)? (ava > 0) ? " : YES" : " : NO" : ""); - wolfSSL_BIO_write(bio, scratch, (int)XSTRLEN(scratch)); - } + static const struct { + unsigned int bit; + const char* name; + } extKeyUsages[] = { + { XKU_ANYEKU, "Any Extended Key Usage" }, + { XKU_SSL_SERVER, "TLS Web Server Authentication" }, + { XKU_SSL_CLIENT, "TLS Web Client Authentication" }, + { XKU_CODE_SIGN, "Code Signing" }, + { XKU_OCSP_SIGN, "OCSP Signing" }, + { XKU_SMIME, "Email Protect" }, + { XKU_TIMESTAMP, "Time Stamp Signing" }, + }; - ava = (EKU_SERVER_AUTH_OID & keyUsage); - if (ava | flag) { - XSNPRINTF(scratch, MAX_TERM_WIDTH, "%*s%s%s\n", indent, "", - "TLS Web Server Authentication", - (flag == 1)? (ava > 0) ? " : YES" : " : NO" : ""); - wolfSSL_BIO_write(bio, scratch, (int)XSTRLEN(scratch)); - } - - ava = (EKU_CLIENT_AUTH_OID & keyUsage); - if (ava | flag) { - XSNPRINTF(scratch, MAX_TERM_WIDTH, "%*s%s%s\n", indent, "", - "TLS Web Client Authentication", - (flag == 1)? (ava > 0) ? " : YES" : " : NO" : ""); - wolfSSL_BIO_write(bio, scratch, (int)XSTRLEN(scratch)); + if (bio == NULL) { + return WOLFCLU_FATAL_ERROR; } - ava = (EKU_OCSP_SIGN_OID & keyUsage); - if (ava | flag) { - XSNPRINTF(scratch, MAX_TERM_WIDTH, "%*s%s%s\n", indent, "", - "OCSP Signing", - (flag == 1)? (ava > 0) ? " : YES" : " : NO" : ""); + if (flag) { + XSNPRINTF(scratch, MAX_TERM_WIDTH, "%*s%s\n", indent, "", + "Certificate Purpose:"); wolfSSL_BIO_write(bio, scratch, (int)XSTRLEN(scratch)); } - ava = (EKU_EMAILPROTECT_OID & keyUsage); - if (ava | flag) { - XSNPRINTF(scratch, MAX_TERM_WIDTH, "%*s%s%s\n", indent, "", - "Email Protect", - (flag == 1)? (ava > 0) ? " : YES" : " : NO" : ""); - wolfSSL_BIO_write(bio, scratch, (int)XSTRLEN(scratch)); - } + for (i = 0; i < (sizeof(extKeyUsages) / sizeof(extKeyUsages[0])); i++) { + unsigned int ava = (extKeyUsages[i].bit & keyUsage); - ava = (EKU_TIMESTAMP_OID & keyUsage); - if (ava | flag) { - XSNPRINTF(scratch, MAX_TERM_WIDTH, "%*s%s%s\n", indent, "", - "Time Stamp Signing", - (flag == 1)? (ava > 0) ? " : YES" : " : NO" : ""); - wolfSSL_BIO_write(bio, scratch, (int)XSTRLEN(scratch)); + if (ava || flag) { + XSNPRINTF(scratch, MAX_TERM_WIDTH, "%*s%s%s\n", indent, "", + extKeyUsages[i].name, + (flag == 1)? (ava > 0) ? " : YES" : " : NO" : ""); + wolfSSL_BIO_write(bio, scratch, (int)XSTRLEN(scratch)); + } } - return WOLFSSL_SUCCESS; + return WOLFCLU_SUCCESS; +#else + (void)bio; + (void)keyUsage; + (void)indent; + (void)flag; + + wolfCLU_LogError("Extended key function not supported by this" + " version of wolfSSL"); + return NOT_COMPILED_IN; +#endif } #endif /* WOLFCLU_NO_FILESYSTEM */ diff --git a/tests/x509/expect-purpose.txt b/tests/x509/expect-purpose.txt index 6d9f0f62..058f86e8 100644 --- a/tests/x509/expect-purpose.txt +++ b/tests/x509/expect-purpose.txt @@ -1,7 +1,8 @@ Certificate Purpose: -Any Extended Key Usage : YES +Any Extended Key Usage : NO TLS Web Server Authentication : YES TLS Web Client Authentication : YES -OCSP Signing : YES -Email Protect : YES -Time Stamp Signing : YES +Code Signing : NO +OCSP Signing : NO +Email Protect : NO +Time Stamp Signing : NO diff --git a/tests/x509/x509-process-test.py b/tests/x509/x509-process-test.py index 3e63ed86..a12abc7e 100644 --- a/tests/x509/x509-process-test.py +++ b/tests/x509/x509-process-test.py @@ -420,6 +420,81 @@ def test_3j_purpose(self): self.assertEqual(r.returncode, 0, r.stderr) self.assertEqual(r.stdout.strip(), expected) + def test_3j2_purpose_per_cert(self): + """-purpose must report exactly the EKUs each certificate carries. + + The purposes are read from a bit map, so a cert with a single EKU has + to answer NO to every other purpose. Covers the regression where the + bit map was masked with OID sums instead of the XKU_* flags, which + made every purpose read YES (or every one NO) regardless of the cert. + """ + # cert -> the purposes that must report YES; all others must be NO + cases = { + "server-cert.pem": ["TLS Web Server Authentication", + "TLS Web Client Authentication"], + "server-ecc.pem": ["TLS Web Server Authentication"], + "client-int-cert.pem": ["TLS Web Client Authentication", + "Email Protect"], + "ocsp-responder-cert.pem": ["OCSP Signing"], + } + for cert, expected_yes in cases.items(): + with self.subTest(cert=cert): + r = run_wolfssl("x509", "-in", os.path.join(CERTS_DIR, cert), + "-purpose", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + + lines = r.stdout.strip().splitlines() + self.assertEqual(lines[0].strip(), "Certificate Purpose:") + + got_yes = [] + for line in lines[1:]: + name, _, verdict = line.rpartition(" : ") + self.assertIn(verdict, ("YES", "NO"), + "bad purpose line: {}".format(line)) + if verdict == "YES": + got_yes.append(name.strip()) + + self.assertEqual(sorted(got_yes), sorted(expected_yes)) + + @unittest.skipUnless(HAS_OPENSSL, "openssl is not avaliable") + def test_3j3_purpose_generated_ekus(self): + """-purpose on EKUs no checked-in certificate carries. + + codeSigning, timeStamping and anyExtendedKeyUsage are absent from + certs/, so build a cert with them to cover the rest of the bit map. + """ + key = "test_3j3.key" + cert = "test_3j3.pem" + conf = "test_3j3.cnf" + self._clean(key, cert, conf) + + with open(conf, "w") as f: + f.write("[req]\n" + "distinguished_name = dn\n" + "x509_extensions = v3\n" + "prompt = no\n" + "[dn]\n" + "CN = wolfCLU EKU test\n" + "[v3]\n" + "extendedKeyUsage = codeSigning, timeStamping, " + "anyExtendedKeyUsage\n") + # TODO: change this to wolfssl when + # https://github.com/wolfSSL/wolfCLU/pull/285 is merged + subprocess.run( + ["openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes", + "-keyout", key, "-out", cert, "-days", "1", "-config", conf], + check=True, capture_output=True, timeout=120) + + r = run_wolfssl("x509", "-in", cert, "-purpose", "-noout") + self.assertEqual(r.returncode, 0, r.stderr) + + got_yes = [line.rpartition(" : ")[0].strip() + for line in r.stdout.strip().splitlines()[1:] + if line.rstrip().endswith("YES")] + self.assertEqual(sorted(got_yes), + sorted(["Any Extended Key Usage", "Code Signing", + "Time Stamp Signing"])) + def test_3k_hash(self): expected_file = os.path.join(TESTS_X509_DIR, "expect-hash.txt") with open(expected_file) as f: