Skip to content

Commit 1f260cc

Browse files
committed
Add TLS-ALPN-01 challenge cert support (RFC 8737 acmeId extension)
1 parent 1c9555c commit 1f260cc

5 files changed

Lines changed: 237 additions & 3 deletions

File tree

tests/api.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11626,6 +11626,90 @@ static int test_wolfSSL_mcast(void)
1162611626
| Wolfcrypt
1162711627
*----------------------------------------------------------------------------*/
1162811628

11629+
/*
11630+
* Testing wc_SetAcmeIdentifierExt() round-trip — the RFC 8737
11631+
* id-pe-acmeIdentifier (1.3.6.1.5.5.7.1.31) extension used by
11632+
* TLS-ALPN-01 ACME challenge certs.
11633+
*/
11634+
static int test_wc_SetAcmeIdentifierExt(void)
11635+
{
11636+
EXPECT_DECLS;
11637+
#if defined(WOLFSSL_ACME_OID) && defined(WOLFSSL_CERT_GEN) && \
11638+
defined(HAVE_ECC) && !defined(NO_SHA256) && !defined(NO_ASN_TIME) && \
11639+
!defined(WC_NO_RNG) && !defined(NO_RSA)
11640+
Cert cert;
11641+
DecodedCert decoded;
11642+
WC_RNG rng;
11643+
ecc_key key;
11644+
byte der[TWOK_BUF];
11645+
int derSz = 0;
11646+
int rngInited = 0, keyInited = 0;
11647+
const char* keyAuth = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA"
11648+
".kZdq0qaDcXNVxKBkP_uiKvw2Yg5sRJ8KBfQa9Ru13nE";
11649+
word32 keyAuthSz = (word32)XSTRLEN(keyAuth);
11650+
byte expected[WC_SHA256_DIGEST_SIZE];
11651+
11652+
XMEMSET(&cert, 0, sizeof(cert));
11653+
XMEMSET(&decoded, 0, sizeof(decoded));
11654+
XMEMSET(&rng, 0, sizeof(rng));
11655+
XMEMSET(&key, 0, sizeof(key));
11656+
XMEMSET(der, 0, sizeof(der));
11657+
11658+
/* Compute the expected digest */
11659+
ExpectIntEQ(wc_Sha256Hash((const byte*)keyAuth, keyAuthSz, expected), 0);
11660+
11661+
/* Input validation. */
11662+
ExpectIntEQ(wc_SetAcmeIdentifierExt(NULL, (const byte*)keyAuth, keyAuthSz),
11663+
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
11664+
ExpectIntEQ(wc_SetAcmeIdentifierExt(&cert, NULL, keyAuthSz),
11665+
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
11666+
ExpectIntEQ(wc_SetAcmeIdentifierExt(&cert, (const byte*)keyAuth, 0),
11667+
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
11668+
11669+
/* Build a P-256 keypair to sign the cert. */
11670+
ExpectIntEQ(wc_InitRng(&rng), 0);
11671+
rngInited = 1;
11672+
ExpectIntEQ(wc_ecc_init(&key), 0);
11673+
keyInited = 1;
11674+
ExpectIntEQ(wc_ecc_make_key_ex(&rng, KEY32, &key, ECC_SECP256R1), 0);
11675+
11676+
/* Build a minimal self-signed cert template carrying the extension. */
11677+
ExpectIntEQ(wc_InitCert(&cert), 0);
11678+
cert.sigType = CTC_SHA256wECDSA;
11679+
cert.daysValid = 1;
11680+
cert.isCA = 0;
11681+
XSTRNCPY(cert.subject.commonName, "acme-test.example", CTC_NAME_SIZE);
11682+
11683+
ExpectIntEQ(wc_SetAcmeIdentifierExt(&cert, (const byte*)keyAuth,
11684+
keyAuthSz), 0);
11685+
ExpectIntEQ(cert.acmeIdentifierSz, WC_SHA256_DIGEST_SIZE);
11686+
ExpectIntEQ(XMEMCMP(cert.acmeIdentifier, expected,
11687+
WC_SHA256_DIGEST_SIZE), 0);
11688+
11689+
/* MakeCert + SignCert. ECC_TYPE selects the ECDSA signing path. */
11690+
ExpectIntGT(derSz = wc_MakeCert_ex(&cert, der, sizeof(der),
11691+
ECC_TYPE, &key, &rng), 0);
11692+
ExpectIntGT(derSz = wc_SignCert_ex(cert.bodySz, cert.sigType,
11693+
der, sizeof(der),
11694+
ECC_TYPE, &key, &rng), 0);
11695+
11696+
/* Parse the cert back and verify the extension survives the
11697+
* round-trip via DecodeAcmeId. */
11698+
wc_InitDecodedCert(&decoded, der, derSz, NULL);
11699+
ExpectIntEQ(wc_ParseCert(&decoded, CERT_TYPE, NO_VERIFY, NULL), 0);
11700+
ExpectIntEQ(decoded.extAcmeIdentifierSet, 1);
11701+
ExpectIntEQ(decoded.extAcmeIdentifierCrit, 1);
11702+
ExpectIntEQ(decoded.acmeIdentifierSz, WC_SHA256_DIGEST_SIZE);
11703+
ExpectIntEQ(XMEMCMP(decoded.acmeIdentifier, expected,
11704+
WC_SHA256_DIGEST_SIZE), 0);
11705+
11706+
wc_FreeDecodedCert(&decoded);
11707+
if (keyInited) wc_ecc_free(&key);
11708+
if (rngInited) wc_FreeRng(&rng);
11709+
#endif
11710+
return EXPECT_RESULT();
11711+
} /* END test_wc_SetAcmeIdentifierExt */
11712+
1162911713
/*
1163011714
* Testing wc_SetKeyUsage()
1163111715
*/
@@ -37017,6 +37101,7 @@ TEST_CASE testCases[] = {
3701737101
#ifdef WOLFSSL_CERT_SIGN_CB
3701837102
TEST_DECL(test_wc_SignCert_cb),
3701937103
#endif
37104+
TEST_DECL(test_wc_SetAcmeIdentifierExt),
3702037105
TEST_DECL(test_wc_SetKeyUsage),
3702137106
TEST_DECL(test_wc_SetAuthKeyIdFromPublicKey_ex),
3702237107
TEST_DECL(test_wc_SetSubjectBuffer),

wolfcrypt/src/asn.c

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19014,6 +19014,39 @@ static int DecodeKeyUsageInternal(const byte* input, word32 sz,
1901419014
return DecodeKeyUsage(input, sz, &cert->extKeyUsage);
1901519015
}
1901619016

19017+
#ifdef WOLFSSL_ACME_OID
19018+
/* Decodes the RFC 8737 id-pe-acmeIdentifier (1.3.6.1.5.5.7.1.31)
19019+
* extension value into cert->acmeIdentifier.
19020+
*
19021+
* The extnValue is an OCTET STRING wrapping a SHA-256 digest of the
19022+
* ACME keyAuth, per RFC 8737 3. Length is verified against
19023+
* WC_SHA256_DIGEST_SIZE.
19024+
*
19025+
* @param [in] input ASN.1 DER-encoded extension value.
19026+
* @param [in] sz Length of input in bytes.
19027+
* @param [in, out] cert DecodedCert to populate (acmeIdentifier and
19028+
* acmeIdentifierSz fields).
19029+
*
19030+
* @return 0 on success.
19031+
* @return ASN_PARSE_E when the inner OCTET STRING is missing or not
19032+
* exactly WC_SHA256_DIGEST_SIZE bytes.
19033+
*/
19034+
static int DecodeAcmeId(const byte* input, word32 sz, DecodedCert* cert)
19035+
{
19036+
word32 hashIdx = 0;
19037+
int hashLen = 0;
19038+
19039+
if (GetOctetString(input, &hashIdx, &hashLen, sz) < 0)
19040+
return ASN_PARSE_E;
19041+
if (hashLen != WC_SHA256_DIGEST_SIZE)
19042+
return ASN_PARSE_E;
19043+
19044+
XMEMCPY(cert->acmeIdentifier, &input[hashIdx], WC_SHA256_DIGEST_SIZE);
19045+
cert->acmeIdentifierSz = WC_SHA256_DIGEST_SIZE;
19046+
return 0;
19047+
}
19048+
#endif /* WOLFSSL_ACME_OID */
19049+
1901719050
#ifdef WOLFSSL_ASN_TEMPLATE
1901819051
/* ASN.1 template for KeyPurposeId.
1901919052
* X.509: RFC 5280, 4.2.1.12 - Extended Key Usage.
@@ -20236,6 +20269,14 @@ int DecodeExtensionType(const byte* input, word32 length, word32 oid,
2023620269
return ASN_PARSE_E;
2023720270
break;
2023820271
#endif /* WOLFSSL_DUAL_ALG_CERTS */
20272+
#ifdef WOLFSSL_ACME_OID
20273+
case ACME_IDENTIFIER_OID:
20274+
VERIFY_AND_SET_OID(cert->extAcmeIdentifierSet);
20275+
cert->extAcmeIdentifierCrit = critical ? 1 : 0;
20276+
if (DecodeAcmeId(&input[idx], length, cert) < 0)
20277+
return ASN_PARSE_E;
20278+
break;
20279+
#endif
2023920280
default:
2024020281
if (isUnknownExt != NULL)
2024120282
*isUnknownExt = 1;
@@ -26510,6 +26551,12 @@ static const ASNItem static_certExtsASN[] = {
2651026551
/* CRLINFO_SEQ */ { 0, ASN_SEQUENCE, 1, 1, 0 },
2651126552
/* CRLINFO_OID */ { 1, ASN_OBJECT_ID, 0, 0, 0 },
2651226553
/* CRLINFO_STR */ { 1, ASN_OCTET_STRING, 0, 0, 0 },
26554+
/* RFC 8737 id-pe-acmeIdentifier */
26555+
/* ACMEID_SEQ */ { 0, ASN_SEQUENCE, 1, 1, 0 },
26556+
/* ACMEID_OID */ { 1, ASN_OBJECT_ID, 0, 0, 0 },
26557+
/* ACMEID_CRIT */ { 1, ASN_BOOLEAN, 0, 0, 0 },
26558+
/* ACMEID_STR */ { 1, ASN_OCTET_STRING, 0, 1, 0 },
26559+
/* ACMEID_HASH */ { 2, ASN_OCTET_STRING, 0, 0, 0 },
2651326560
#ifdef WOLFSSL_DUAL_ALG_CERTS
2651426561
/* SAPKI_SEQ */ { 0, ASN_SEQUENCE, 1, 1, 0 },
2651526562
/* SAPKI_OID */ { 1, ASN_OBJECT_ID, 0, 0, 0 },
@@ -26568,6 +26615,11 @@ enum {
2656826615
CERTEXTSASN_IDX_CRLINFO_SEQ,
2656926616
CERTEXTSASN_IDX_CRLINFO_OID,
2657026617
CERTEXTSASN_IDX_CRLINFO_STR,
26618+
CERTEXTSASN_IDX_ACMEID_SEQ,
26619+
CERTEXTSASN_IDX_ACMEID_OID,
26620+
CERTEXTSASN_IDX_ACMEID_CRIT,
26621+
CERTEXTSASN_IDX_ACMEID_STR,
26622+
CERTEXTSASN_IDX_ACMEID_HASH,
2657126623
#ifdef WOLFSSL_DUAL_ALG_CERTS
2657226624
CERTEXTSASN_IDX_SAPKI_SEQ,
2657326625
CERTEXTSASN_IDX_SAPKI_OID,
@@ -26623,6 +26675,10 @@ static int EncodeExtensions(Cert* cert, byte* output, word32 maxSz,
2662326675
static const byte nsCertOID[] = { 0x60, 0x86, 0x48, 0x01,
2662426676
0x86, 0xF8, 0x42, 0x01, 0x01 };
2662526677
static const byte crlInfoOID[] = { 0x55, 0x1D, 0x1F };
26678+
#ifdef WOLFSSL_ACME_OID
26679+
static const byte acmeIdOID[] = { 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07,
26680+
0x01, 0x1F };
26681+
#endif
2662626682
#ifdef WOLFSSL_DUAL_ALG_CERTS
2662726683
static const byte sapkiOID[] = { 0x55, 0x1d, 0x48 };
2662826684
static const byte altSigAlgOID[] = { 0x55, 0x1d, 0x49 };
@@ -26878,6 +26934,24 @@ static int EncodeExtensions(Cert* cert, byte* output, word32 maxSz,
2687826934
CERTEXTSASN_IDX_CRLINFO_STR);
2687926935
}
2688026936

26937+
#ifdef WOLFSSL_ACME_OID
26938+
/* id-pe-acmeIdentifier (TLS-ALPN-01 challenge cert).
26939+
* Always critical=TRUE. */
26940+
if (cert->acmeIdentifierSz == WC_SHA256_DIGEST_SIZE) {
26941+
SetASN_Buffer(&dataASN[CERTEXTSASN_IDX_ACMEID_OID],
26942+
acmeIdOID, sizeof(acmeIdOID));
26943+
SetASN_Boolean(&dataASN[CERTEXTSASN_IDX_ACMEID_CRIT], 1);
26944+
SetASN_Buffer(&dataASN[CERTEXTSASN_IDX_ACMEID_HASH],
26945+
cert->acmeIdentifier, (word32)cert->acmeIdentifierSz);
26946+
}
26947+
else
26948+
#endif /* WOLFSSL_ACME_OID */
26949+
{
26950+
/* Don't write out the ACME identifier extension. */
26951+
SetASNItem_NoOut(dataASN, CERTEXTSASN_IDX_ACMEID_SEQ,
26952+
CERTEXTSASN_IDX_ACMEID_HASH);
26953+
}
26954+
2688126955
#ifdef WOLFSSL_DUAL_ALG_CERTS
2688226956
if (cert->sapkiDer != NULL) {
2688326957
/* Set subject alternative public key info OID, criticality and
@@ -29313,6 +29387,40 @@ int wc_SetExtKeyUsage(Cert *cert, const char *value)
2931329387
return ret;
2931429388
}
2931529389

29390+
#ifdef WOLFSSL_ACME_OID
29391+
/* Set the id-pe-acmeIdentifier extension value from the ACME
29392+
* keyAuth string. Computes SHA-256 over keyAuth and stores the digest
29393+
* as the extension value. RFC 8737 3 requires critical=TRUE; that's
29394+
* applied at encode time in EncodeExtensions.
29395+
*
29396+
* keyAuth is the raw bytes of the key authorization string per
29397+
* RFC 8555 8.1: token "." JWK_thumbprint.
29398+
*/
29399+
int wc_SetAcmeIdentifierExt(Cert *cert, const byte *keyAuth, word32 keyAuthSz)
29400+
{
29401+
int ret;
29402+
byte digest[WC_SHA256_DIGEST_SIZE];
29403+
wc_Sha256 sha;
29404+
29405+
if (cert == NULL || keyAuth == NULL || keyAuthSz == 0)
29406+
return BAD_FUNC_ARG;
29407+
29408+
ret = wc_InitSha256(&sha);
29409+
if (ret != 0)
29410+
return ret;
29411+
ret = wc_Sha256Update(&sha, keyAuth, keyAuthSz);
29412+
if (ret == 0)
29413+
ret = wc_Sha256Final(&sha, digest);
29414+
wc_Sha256Free(&sha);
29415+
if (ret != 0)
29416+
return ret;
29417+
29418+
XMEMCPY(cert->acmeIdentifier, digest, WC_SHA256_DIGEST_SIZE);
29419+
cert->acmeIdentifierSz = WC_SHA256_DIGEST_SIZE;
29420+
return 0;
29421+
}
29422+
#endif /* WOLFSSL_ACME_OID */
29423+
2931629424
#ifdef WOLFSSL_EKU_OID
2931729425
/*
2931829426
* cert structure to set EKU oid in

wolfssl/wolfcrypt/asn.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2107,7 +2107,13 @@ struct DecodedCert {
21072107
WC_BITFIELD extAltSigAlgCrit:1;
21082108
WC_BITFIELD extAltSigValCrit:1;
21092109
#endif /* WOLFSSL_DUAL_ALG_CERTS */
2110-
2110+
#ifdef WOLFSSL_ACME_OID
2111+
/* id-pe-acmeIdentifier (TLS-ALPN-01 challenge cert) */
2112+
byte acmeIdentifier[WC_SHA256_DIGEST_SIZE];
2113+
int acmeIdentifierSz;
2114+
WC_BITFIELD extAcmeIdentifierSet:1;
2115+
WC_BITFIELD extAcmeIdentifierCrit:1;
2116+
#endif /* WOLFSSL_ACME_OID */
21112117
WOLFSSL_AIA_ENTRY extAuthInfoList[WOLFSSL_MAX_AIA_ENTRIES];
21122118
WC_BITFIELD extAuthInfoListSz:7;
21132119
WC_BITFIELD extAuthInfoListOverflow:1;

wolfssl/wolfcrypt/asn_public.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ This library defines the interface APIs for X509 certificates.
3636
#include <wolfssl/wolfcrypt/dsa.h>
3737
#endif
3838
#include <wolfssl/wolfcrypt/random.h>
39+
#ifdef WOLFSSL_ACME_OID
40+
#include <wolfssl/wolfcrypt/sha256.h>
41+
#endif
3942

4043
#ifdef __cplusplus
4144
extern "C" {
@@ -469,6 +472,10 @@ typedef struct Cert {
469472
word16 certPoliciesNb; /* Number of Cert Policy */
470473
byte crlInfo[CTC_MAX_CRLINFO_SZ]; /* CRL Distribution points */
471474
int crlInfoSz;
475+
#ifdef WOLFSSL_ACME_OID
476+
byte acmeIdentifier[WC_SHA256_DIGEST_SIZE]; /* SHA256 of ACME keyAuth */
477+
int acmeIdentifierSz;
478+
#endif
472479
#endif
473480
#if defined(WOLFSSL_CERT_EXT) || defined(OPENSSL_EXTRA) || \
474481
defined(WOLFSSL_CERT_REQ)
@@ -622,6 +629,26 @@ WOLFSSL_API int wc_SetKeyUsage(Cert *cert, const char *value);
622629
*/
623630
WOLFSSL_API int wc_SetExtKeyUsage(Cert *cert, const char *value);
624631

632+
#ifdef WOLFSSL_ACME_OID
633+
/* Set the RFC 8737 id-pe-acmeIdentifier (1.3.6.1.5.5.7.1.31) extension on
634+
* the certificate. Used to construct TLS-ALPN-01 ACME challenge response
635+
* certificates.
636+
*
637+
* keyAuth is the ACME key authorization string (token "." JWK_thumbprint
638+
* per RFC 8555 8.1), as raw bytes — keyAuthSz is its byte length.
639+
* wc_SetAcmeIdentifierExt computes SHA-256 over keyAuth internally and
640+
* stores the digest as the extension value, emitted critical=TRUE per
641+
* RFC 8737 3.
642+
*
643+
* Returns 0 on success.
644+
* Returns BAD_FUNC_ARG when cert is NULL, keyAuth is NULL, or
645+
* keyAuthSz is 0.
646+
* May return a SHA-256 error code if hashing fails. */
647+
WOLFSSL_API int wc_SetAcmeIdentifierExt(Cert *cert,
648+
const byte *keyAuth,
649+
word32 keyAuthSz);
650+
#endif /* WOLFSSL_ACME_OID */
651+
625652

626653
#ifdef WOLFSSL_EKU_OID
627654
/* Set ExtendedKeyUsage with unique OID

wolfssl/wolfcrypt/oid_sum.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,11 @@ enum Extensions_Sum {
431431
/* 0x55,0x1d,0x49 */
432432
ALT_SIG_ALG_OID = 187, /* 2.5.29.73 */
433433
/* 0x55,0x1d,0x4a */
434-
ALT_SIG_VAL_OID = 188 /* 2.5.29.74 */
434+
ALT_SIG_VAL_OID = 188, /* 2.5.29.74 */
435+
#ifdef WOLFSSL_ACME_OID
436+
/* 0x2b,0x06,0x01,0x05,0x05,0x07,0x01,0x1f */
437+
ACME_IDENTIFIER_OID = 99 /* 1.3.6.1.5.5.7.1.31 */
438+
#endif
435439
#else
436440
/* 0x55,0x1d,0x13 */
437441
BASIC_CA_OID = 0x7fec1daa, /* 2.5.29.19 */
@@ -488,7 +492,11 @@ enum Extensions_Sum {
488492
/* 0x55,0x1d,0x49 */
489493
ALT_SIG_ALG_OID = 0x7fb61daa, /* 2.5.29.73 */
490494
/* 0x55,0x1d,0x4a */
491-
ALT_SIG_VAL_OID = 0x7fb51daa /* 2.5.29.74 */
495+
ALT_SIG_VAL_OID = 0x7fb51daa, /* 2.5.29.74 */
496+
#ifdef WOLFSSL_ACME_OID
497+
/* 0x2b,0x06,0x01,0x05,0x05,0x07,0x01,0x1f */
498+
ACME_IDENTIFIER_OID = 0x1a00012e /* 1.3.6.1.5.5.7.1.31 */
499+
#endif
492500
#endif
493501
};
494502

0 commit comments

Comments
 (0)