diff --git a/certgen/Makefile b/certgen/Makefile index 665b3a168..1f355cafd 100644 --- a/certgen/Makefile +++ b/certgen/Makefile @@ -29,7 +29,7 @@ CFLAGS+=$(OPTIMIZE) #LIBS+=$(STATIC_LIB) LIBS+=$(DYN_LIB) -all:certgen_example certgen_ca_example csr_example csr_w_ed25519_example csr_sign csr_cryptocb custom_ext custom_ext_callback +all:certgen_example certgen_ca_example csr_example csr_w_ed25519_example csr_sign csr_cryptocb csr_ecc_rawpub custom_ext custom_ext_callback certgen_example:certgen_example.o $(CC) -o $@ $^ $(CFLAGS) $(CPPFLAGS) $(LIBS) @@ -49,6 +49,9 @@ csr_sign:csr_sign.o csr_cryptocb:csr_cryptocb.o $(CC) -o $@ $^ $(CFLAGS) $(CPPFLAGS) $(LIBS) +csr_ecc_rawpub:csr_ecc_rawpub.o + $(CC) -o $@ $^ $(CFLAGS) $(CPPFLAGS) $(LIBS) + custom_ext:custom_ext.o $(CC) -o $@ $^ $(CFLAGS) $(CPPFLAGS) $(LIBS) @@ -58,16 +61,17 @@ custom_ext_callback:custom_ext_callback.o .PHONY: clean all check clean: - rm -f *.o certgen_example certgen_ca_example csr_example csr_w_ed25519_example csr_sign csr_cryptocb custom_ext custom_ext_callback + rm -f *.o certgen_example certgen_ca_example csr_example csr_w_ed25519_example csr_sign csr_cryptocb csr_ecc_rawpub custom_ext custom_ext_callback rm -f newCert.* -check: certgen_example certgen_ca_example csr_w_ed25519_example csr_example csr_sign csr_cryptocb custom_ext custom_ext_callback +check: certgen_example certgen_ca_example csr_w_ed25519_example csr_example csr_sign csr_cryptocb csr_ecc_rawpub custom_ext custom_ext_callback out=$$(./certgen_example) && printf '%s' "$$out" | grep -q 'Tests passed' out=$$(./certgen_ca_example) && printf '%s' "$$out" | grep -q 'Tests passed' out=$$(./csr_w_ed25519_example 2>&1) && printf '%s' "$$out" | grep -q -- '-----END CERTIFICATE REQUEST-----' out=$$(./csr_example ecc) && printf '%s' "$$out" | grep -q 'Saved PEM to "ecc-csr.pem"' out=$$(./csr_sign ecc-csr.pem ca-ecc-cert.der ca-ecc-key.der) && printf '%s' "$$out" | grep -q 'Tests passed' out=$$(./csr_cryptocb ecc) && printf '%s' "$$out" | grep -q 'Saved CSR PEM to "ecc-csr.pem"' + out=$$(./csr_ecc_rawpub) && printf '%s' "$$out" | grep -q 'Tests passed' out=$$(./custom_ext) && printf '%s' "$$out" | grep -q 'Tests passed' out=$$(./custom_ext_callback newCert.der) && printf '%s' "$$out" | grep -q 'Success' @echo "PASS: certgen checks" diff --git a/certgen/README.md b/certgen/README.md index eba1d403e..7529b26d0 100644 --- a/certgen/README.md +++ b/certgen/README.md @@ -268,6 +268,43 @@ GxI= ``` +## Certificate Signing Request (CSR) Example with a Raw ECC Public Key + +Example of building a CSR around an ECC public key supplied as raw bytes - no DER SubjectPublicKeyInfo and no PEM. A secure element or HSM usually hands back either the affine coordinates Qx and Qy or the X9.63 point `0x04||X||Y`; `csr_ecc_rawpub` imports both forms with `wc_ecc_import_unsigned()` and `wc_ecc_import_x963_ex()`. + +Passing `d = NULL` to `wc_ecc_import_unsigned()` leaves the key public only, which is all `wc_MakeCertReq_ex()` needs. Where the private key lives in hardware, give that public key a `devId` so `wc_SignCert_ex()` routes to a crypto callback - see the `csr_cryptocb` example below for the callback plumbing. + +Tested with these wolfSSL build options: + +```sh +./autogen.sh # If cloned from GitHub +./configure --enable-certreq --enable-certgen --enable-certext --enable-ecc +make +sudo make install +sudo ldconfig # required on some targets +``` + +### `csr_ecc_rawpub` Example output + +``` +% ./csr_ecc_rawpub +Request body from Qx/Qy and from the X9.63 point match: 212 bytes +Signed CSR: 302 bytes +Parsed back and signature verified +-----BEGIN CERTIFICATE REQUEST----- +MIIBKjCB0QIBADBvMQswCQYDVQQGEwJVUzELMAkGA1UECAwCT1IxETAPBgNVBAcM +CFBvcnRsYW5kMRAwDgYDVQQKDAd3b2xmU1NMMRQwEgYDVQQLDAtEZXZlbG9wbWVu +dDEYMBYGA1UEAwwPd3d3LndvbGZzc2wuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAE/rF//42PvQmzLaTkHm7U2bD2GPRw4szv7AKfFthiMdT0MY612CDl4BIM +6rQ44CAfH1CA2urhIBlGCdMlqMcFFKAAMAoGCCqGSM49BAMCA0gAMEUCIFBPIc/G +Hyv95pXPNQN9E0d+ojt4Gcpq17P8sPJ+BTP6AiEAwc8VcNY28gWDq/74FBa5Lv9d +XvvZV3GtIFgiBDzaOI4= +-----END CERTIFICATE REQUEST----- + +Tests passed +``` + + ## Certificate Signing Request (CSR) Example with Crypto Callbacks Example of generating a PEM-encoded certificate signing request (CSR) using the diff --git a/certgen/csr_ecc_rawpub.c b/certgen/csr_ecc_rawpub.c new file mode 100644 index 000000000..cf6c6482e --- /dev/null +++ b/certgen/csr_ecc_rawpub.c @@ -0,0 +1,280 @@ +/* csr_ecc_rawpub.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Certificate signing request built around an ECC public key supplied as raw + * bytes - no DER SubjectPublicKeyInfo and no PEM. A secure element or HSM + * typically hands back either the affine coordinates Qx and Qy or the X9.63 + * point 0x04||X||Y; both forms are shown here. + * + * The key material below is a throw-away P-256 key, standing in for whatever + * the device holds. Where the private key lives in hardware, drop the private + * import and give the public key a devId instead, so wc_SignCert_ex() calls a + * crypto callback - see csr_cryptocb.c. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(WOLFSSL_CERT_REQ) && defined(WOLFSSL_CERT_GEN) && \ + defined(HAVE_ECC) && !defined(NO_SHA256) && !defined(WC_NO_RNG) + +#define CSR_BUF_SZ 1024 + +/* P-256 public key, affine coordinates, big endian, 32 bytes each. */ +static const byte eccQx[] = { + 0xFE, 0xB1, 0x7F, 0xFF, 0x8D, 0x8F, 0xBD, 0x09, + 0xB3, 0x2D, 0xA4, 0xE4, 0x1E, 0x6E, 0xD4, 0xD9, + 0xB0, 0xF6, 0x18, 0xF4, 0x70, 0xE2, 0xCC, 0xEF, + 0xEC, 0x02, 0x9F, 0x16, 0xD8, 0x62, 0x31, 0xD4 +}; +static const byte eccQy[] = { + 0xF4, 0x31, 0x8E, 0xB5, 0xD8, 0x20, 0xE5, 0xE0, + 0x12, 0x0C, 0xEA, 0xB4, 0x38, 0xE0, 0x20, 0x1F, + 0x1F, 0x50, 0x80, 0xDA, 0xEA, 0xE1, 0x20, 0x19, + 0x46, 0x09, 0xD3, 0x25, 0xA8, 0xC7, 0x05, 0x14 +}; +/* Matching private scalar, used here only to sign the request. */ +static const byte eccD[] = { + 0xF2, 0x52, 0xDE, 0x2D, 0x5E, 0x75, 0xDD, 0x94, + 0xA7, 0xE1, 0x56, 0xDE, 0x8D, 0x12, 0x91, 0x0A, + 0x26, 0x54, 0x0F, 0xCD, 0x58, 0x30, 0xB6, 0xAF, + 0xF8, 0x41, 0xDA, 0x8C, 0x16, 0xCE, 0x46, 0xD1 +}; + +/* Same public key as an X9.63 uncompressed point: 0x04 || X || Y. */ +static byte eccPoint[1 + sizeof(eccQx) + sizeof(eccQy)]; + +static void make_point(void) +{ + eccPoint[0] = 0x04; + XMEMCPY(eccPoint + 1, eccQx, sizeof(eccQx)); + XMEMCPY(eccPoint + 1 + sizeof(eccQx), eccQy, sizeof(eccQy)); +} + +static void fill_subject(Cert* req) +{ + strncpy(req->subject.country, "US", CTC_NAME_SIZE); + strncpy(req->subject.state, "OR", CTC_NAME_SIZE); + strncpy(req->subject.locality, "Portland", CTC_NAME_SIZE); + strncpy(req->subject.org, "wolfSSL", CTC_NAME_SIZE); + strncpy(req->subject.unit, "Development", CTC_NAME_SIZE); + strncpy(req->subject.commonName, "www.wolfssl.com", CTC_NAME_SIZE); + req->version = 0; + req->sigType = CTC_SHA256wECDSA; +} + +/* Encode the unsigned request body around pubKey. Returns its size. */ +static int make_req_body(ecc_key* pubKey, Cert* req, byte* der, word32 derBufSz) +{ + int ret; + + ret = wc_InitCert(req); + if (ret != 0) { + printf("wc_InitCert failed: %d\n", ret); + return ret; + } + fill_subject(req); + + ret = wc_MakeCertReq_ex(req, der, derBufSz, ECC_TYPE, pubKey); + if (ret <= 0) { + printf("wc_MakeCertReq_ex failed: %d\n", ret); + } + return ret; +} + +#ifdef WOLFSSL_DER_TO_PEM +static int print_pem(byte* der, int derSz) +{ + byte pem[CSR_BUF_SZ * 2]; + int pemSz; + + pemSz = wc_DerToPem(der, (word32)derSz, pem, (word32)sizeof(pem), + CERTREQ_TYPE); + if (pemSz <= 0) { + printf("wc_DerToPem failed: %d\n", pemSz); + return pemSz; + } + printf("%.*s\n", pemSz, pem); + return 0; +} +#endif + +int main(void) +{ + ecc_key pubKey; + ecc_key pointKey; + ecc_key signKey; + WC_RNG rng; + Cert req; + static DecodedCert decoded; + byte der[CSR_BUF_SZ]; + byte derPoint[CSR_BUF_SZ]; + int ret; + int derSz; + int pointSz; + + make_point(); + + ret = wolfCrypt_Init(); + if (ret != 0) { + printf("wolfCrypt_Init failed: %d\n", ret); + return ret; + } + + ret = wc_InitRng(&rng); + if (ret != 0) { + printf("wc_InitRng failed: %d\n", ret); + wolfCrypt_Cleanup(); + return ret; + } + + /* Zero every key first so the cleanup below is safe even if one of the + * inits fails partway. */ + XMEMSET(&signKey, 0, sizeof(signKey)); + XMEMSET(&pubKey, 0, sizeof(pubKey)); + XMEMSET(&pointKey, 0, sizeof(pointKey)); + + ret = wc_ecc_init(&signKey); + if (ret == 0) + ret = wc_ecc_init(&pubKey); + if (ret == 0) + ret = wc_ecc_init(&pointKey); + if (ret != 0) { + printf("wc_ecc_init failed: %d\n", ret); + goto exit; + } + + /* The signer. A device would leave this out and route wc_SignCert_ex() + * to its secure element through a crypto callback. */ + ret = wc_ecc_import_unsigned(&signKey, eccQx, eccQy, eccD, + ECC_SECP256R1); + if (ret != 0) { + printf("private wc_ecc_import_unsigned failed: %d\n", ret); + goto exit; + } + + /* Public key from bare Qx and Qy. A NULL private scalar leaves the key + * public only, which is all the request body needs. */ + ret = wc_ecc_import_unsigned(&pubKey, eccQx, eccQy, NULL, + ECC_SECP256R1); + if (ret != 0) { + printf("wc_ecc_import_unsigned failed: %d\n", ret); + goto exit; + } + + /* The same key from the X9.63 point. */ + ret = wc_ecc_import_x963_ex(eccPoint, (word32)sizeof(eccPoint), &pointKey, + ECC_SECP256R1); + if (ret != 0) { + printf("wc_ecc_import_x963_ex failed: %d\n", ret); + goto exit; + } + + derSz = make_req_body(&pubKey, &req, der, (word32)sizeof(der)); + if (derSz <= 0) { + ret = derSz; + goto exit; + } + pointSz = make_req_body(&pointKey, &req, derPoint, + (word32)sizeof(derPoint)); + if (pointSz <= 0) { + ret = pointSz; + goto exit; + } + + /* The two import forms describe the same key, so the request bodies + * must be byte for byte identical. */ + if ((derSz != pointSz) || (XMEMCMP(der, derPoint, (size_t)derSz) != 0)) { + printf("Request bodies differ: %d vs %d bytes\n", derSz, pointSz); + ret = -1; + goto exit; + } + printf("Request body from Qx/Qy and from the X9.63 point match: " + "%d bytes\n", derSz); + + /* Rebuild the body for the key being signed, so req describes der. */ + derSz = make_req_body(&pubKey, &req, der, (word32)sizeof(der)); + if (derSz <= 0) { + ret = derSz; + goto exit; + } + + ret = wc_SignCert_ex(req.bodySz, req.sigType, der, (word32)sizeof(der), + ECC_TYPE, &signKey, &rng); + if (ret <= 0) { + printf("wc_SignCert_ex failed: %d\n", ret); + goto exit; + } + derSz = ret; + printf("Signed CSR: %d bytes\n", derSz); + + /* Parse the request back and check its signature, so a malformed CSR + * fails rather than reporting success. */ + wc_InitDecodedCert(&decoded, der, (word32)derSz, NULL); + ret = wc_ParseCert(&decoded, CERTREQ_TYPE, VERIFY, NULL); + if (ret == 0) { + printf("Parsed back and signature verified\n"); + } + else { + printf("wc_ParseCert failed: %d\n", ret); + } + wc_FreeDecodedCert(&decoded); + if (ret != 0) { + goto exit; + } + +#ifdef WOLFSSL_DER_TO_PEM + ret = print_pem(der, derSz); + if (ret != 0) { + goto exit; + } +#endif + + printf("Tests passed\n"); + ret = 0; + +exit: + wc_ecc_free(&pointKey); + wc_ecc_free(&pubKey); + wc_ecc_free(&signKey); + wc_FreeRng(&rng); + wolfCrypt_Cleanup(); + return ret; +} + +#else + +int main(void) +{ + printf("Please compile wolfSSL with --enable-certgen --enable-certreq " + "--enable-certext --enable-ecc, and with SHA-256 and the RNG " + "enabled\n"); + return 0; +} + +#endif