diff --git a/include/wolfprovider/alg_funcs.h b/include/wolfprovider/alg_funcs.h index 771a1a76..6a78b98c 100644 --- a/include/wolfprovider/alg_funcs.h +++ b/include/wolfprovider/alg_funcs.h @@ -221,6 +221,9 @@ typedef void (*DFUNC)(void); /* Decoder names. */ #define WP_NAMES_DER "DER" +/* Private name: only the file store fetches these, so no other decoder chain + * picks them up. */ +#define WP_NAMES_DER2OBJ "wolfprov-der2obj" /* Store names. */ #define WP_NAMES_FILE "file" @@ -546,6 +549,8 @@ extern const OSSL_DISPATCH wp_mldsa87_spki_decoder_functions[]; extern const OSSL_DISPATCH wp_mldsa87_pki_decoder_functions[]; extern const OSSL_DISPATCH wp_pem_to_der_decoder_functions[]; extern const OSSL_DISPATCH wp_epki_to_pki_decoder_functions[]; +extern const OSSL_DISPATCH wp_der_to_cert_decoder_functions[]; +extern const OSSL_DISPATCH wp_der_to_crl_decoder_functions[]; /* Encode implementations. */ extern const OSSL_DISPATCH wp_rsa_spki_der_encoder_functions[]; extern const OSSL_DISPATCH wp_rsa_spki_pem_encoder_functions[]; diff --git a/src/wp_file_store.c b/src/wp_file_store.c index 32b825ad..e772b2d0 100644 --- a/src/wp_file_store.c +++ b/src/wp_file_store.c @@ -307,6 +307,168 @@ static int wp_file_decoder_set_input_structure(OSSL_DECODER_CTX* decCtx, return ok; } +/** + * DER certificate/CRL to object decoder context. + */ +typedef struct wp_Der2Obj { + /** Provider context - used to read the core BIO. */ + WOLFPROV_CTX* provCtx; +} wp_Der2Obj; + +/** + * Create a new DER to object decoder context. + * + * @param [in] provCtx Provider context. + * @return Pointer to context. + */ +static wp_Der2Obj* wp_der2obj_newctx(WOLFPROV_CTX* provCtx) +{ + wp_Der2Obj* ctx = NULL; + + if (wolfssl_prov_is_running()) { + ctx = (wp_Der2Obj*)OPENSSL_zalloc(sizeof(*ctx)); + } + if (ctx != NULL) { + ctx->provCtx = provCtx; + } + + return ctx; +} + +/** + * Dispose of DER to object decoder context. + * + * @param [in] ctx DER to object decoder context. + */ +static void wp_der2obj_freectx(wp_Der2Obj* ctx) +{ + OPENSSL_free(ctx); +} + +/** + * Length of the DER SEQUENCE at the start of the data. + * + * @param [in] data DER data. + * @param [in] len Length of data in bytes. + * @return Length of the SEQUENCE including its header. + * @return 0 when the data does not start with a complete DER SEQUENCE. + */ +static word32 wp_der2obj_seq_len(const unsigned char* data, word32 len) +{ + word32 hdrLen = 2; + word32 contentLen = 0; + word32 n; + word32 i; + + if ((len < 2) || (data[0] != 0x30)) { + return 0; + } + if (data[1] < 0x80) { + contentLen = data[1]; + } + else { + n = data[1] & 0x7f; + if ((n == 0) || (n > 4) || (len < 2 + n)) { + return 0; + } + for (i = 0; i < n; i++) { + contentLen = (contentLen << 8) | data[2 + i]; + } + hdrLen += n; + } + if (contentLen > len - hdrLen) { + return 0; + } + return hdrLen + contentLen; +} + +/** + * Pass DER data up as a certificate or CRL object, unparsed. + * + * The file store needs this for DER input, as OpenSSL's file store does + * (file_store_any2obj.c): the PEM to DER decoder does the same for PEM input. + * No cryptographic operation is performed. + * + * @param [in] ctx DER to object decoder context. + * @param [in, out] coreBio BIO wrapped for the core. + * @param [in] obj Object type: OSSL_OBJECT_CERT or OSSL_OBJECT_CRL. + * @param [in] dataCb Callback to pass the object to. + * @param [in] dataCbArg Argument to pass to callback. + * @return 1 on success or when the data is not a DER SEQUENCE. + * @return 0 on failure. + */ +static int wp_der2obj_decode(wp_Der2Obj* ctx, OSSL_CORE_BIO* coreBio, int obj, + OSSL_CALLBACK* dataCb, void* dataCbArg) +{ + int ok = 1; + unsigned char* data = NULL; + word32 len = 0; + word32 objLen; + OSSL_PARAM params[3]; + + WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_der2obj_decode"); + + if (!wp_read_der_bio(ctx->provCtx, coreBio, &data, &len)) { + ok = 0; + } + /* Not a DER SEQUENCE: not for this decoder, let others try. */ + else if ((objLen = wp_der2obj_seq_len(data, len)) != 0) { + params[0] = OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_DATA, + data, objLen); + params[1] = OSSL_PARAM_construct_int(OSSL_OBJECT_PARAM_TYPE, &obj); + params[2] = OSSL_PARAM_construct_end(); + ok = dataCb(params, dataCbArg); + } + OPENSSL_free(data); + + WOLFPROV_LEAVE(WP_LOG_COMP_PROVIDER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); + return ok; +} + +/** + * Decode DER certificate: pass it up as a certificate object. + */ +static int wp_der2cert_decode(wp_Der2Obj* ctx, OSSL_CORE_BIO* coreBio, + int selection, OSSL_CALLBACK* dataCb, void* dataCbArg, + OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) +{ + (void)selection; + (void)pwCb; + (void)pwCbArg; + return wp_der2obj_decode(ctx, coreBio, OSSL_OBJECT_CERT, dataCb, + dataCbArg); +} + +/** + * Decode DER CRL: pass it up as a CRL object. + */ +static int wp_der2crl_decode(wp_Der2Obj* ctx, OSSL_CORE_BIO* coreBio, + int selection, OSSL_CALLBACK* dataCb, void* dataCbArg, + OSSL_PASSPHRASE_CALLBACK* pwCb, void* pwCbArg) +{ + (void)selection; + (void)pwCb; + (void)pwCbArg; + return wp_der2obj_decode(ctx, coreBio, OSSL_OBJECT_CRL, dataCb, + dataCbArg); +} + +/** Dispatch table for DER certificate to object decoder. */ +const OSSL_DISPATCH wp_der_to_cert_decoder_functions[] = { + { OSSL_FUNC_DECODER_NEWCTX, (DFUNC)wp_der2obj_newctx }, + { OSSL_FUNC_DECODER_FREECTX, (DFUNC)wp_der2obj_freectx }, + { OSSL_FUNC_DECODER_DECODE, (DFUNC)wp_der2cert_decode }, + { 0, NULL } +}; + +/** Dispatch table for DER CRL to object decoder. */ +const OSSL_DISPATCH wp_der_to_crl_decoder_functions[] = { + { OSSL_FUNC_DECODER_NEWCTX, (DFUNC)wp_der2obj_newctx }, + { OSSL_FUNC_DECODER_FREECTX, (DFUNC)wp_der2obj_freectx }, + { OSSL_FUNC_DECODER_DECODE, (DFUNC)wp_der2crl_decode }, + { 0, NULL } +}; + /** * Information about supported decoders from file data. */ @@ -404,6 +566,41 @@ static int wp_file_decoder_prop_query(const char* base, const char* caller, return ok; } +/** + * Fetch a decoder and add it to the decoder context. + * + * @param [in] ctx File system context object. + * @param [in, out] decCtx OpenSSL decoder context. + * @param [in] name Decoder name. + * @param [in] propQuery Structure property query. May be NULL. + * @return 1 on success. + * @return 0 on failure. + */ +static int wp_file_add_decoder(wp_FileCtx* ctx, OSSL_DECODER_CTX* decCtx, + const char* name, const char* propQuery) +{ + int ok = 1; + OSSL_DECODER* decoder = NULL; + char* query = NULL; + + if (!wp_file_decoder_prop_query(propQuery, ctx->propQuery, &query)) { + ok = 0; + } + if (ok) { + decoder = OSSL_DECODER_fetch(ctx->provCtx->libCtx, name, query); + if (decoder == NULL) { + ok = 0; + } + } + if (ok && !OSSL_DECODER_CTX_add_decoder(decCtx, decoder)) { + ok = 0; + } + OSSL_DECODER_free(decoder); + OPENSSL_free(query); + + return ok; +} + /** * Set the decoders into the decoder context. * @@ -416,29 +613,22 @@ static int wp_file_set_decoder(wp_FileCtx* ctx, OSSL_DECODER_CTX* decCtx) { int ok = 1; size_t i; - OSSL_DECODER* decoder; - char* query = NULL; WOLFPROV_ENTER(WP_LOG_COMP_PROVIDER, "wp_file_set_decoder"); for (i = 0; ok && (i < WP_DECODERS_SIZE); i++) { - if (!wp_file_decoder_prop_query(wp_decoders[i].propQuery, - ctx->propQuery, &query)) { - ok = 0; - } - if (ok) { - decoder = OSSL_DECODER_fetch(ctx->provCtx->libCtx, - wp_decoders[i].name, query); - if (decoder == NULL) { - ok = 0; - } - if (ok && !OSSL_DECODER_CTX_add_decoder(decCtx, decoder)) { - ok = 0; - } - OSSL_DECODER_free(decoder); - } - OPENSSL_free(query); - query = NULL; + ok = wp_file_add_decoder(ctx, decCtx, wp_decoders[i].name, + wp_decoders[i].propQuery); + } + /* DER certificates and CRLs are passed up unparsed, as OpenSSL's file + * store does. Only added when one is expected: key loading is unchanged. */ + if (ok && (ctx->type == OSSL_STORE_INFO_CERT)) { + ok = wp_file_add_decoder(ctx, decCtx, WP_NAMES_DER2OBJ, + "structure=Certificate"); + } + else if (ok && (ctx->type == OSSL_STORE_INFO_CRL)) { + ok = wp_file_add_decoder(ctx, decCtx, WP_NAMES_DER2OBJ, + "structure=CertificateList"); } WOLFPROV_LEAVE(WP_LOG_COMP_PROVIDER, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); diff --git a/src/wp_wolfprov.c b/src/wp_wolfprov.c index 69446838..b648a13e 100644 --- a/src/wp_wolfprov.c +++ b/src/wp_wolfprov.c @@ -1660,6 +1660,14 @@ static const OSSL_ALGORITHM wolfprov_decoder[] = { wp_epki_to_pki_decoder_functions, "" }, + /* DER certificate and CRL pass-through for the file store. */ + { WP_NAMES_DER2OBJ, WP_DECODER_PROPERTIES(Certificate), + wp_der_to_cert_decoder_functions, + "" }, + { WP_NAMES_DER2OBJ, WP_DECODER_PROPERTIES(CertificateList), + wp_der_to_crl_decoder_functions, + "" }, + { NULL, NULL, NULL, NULL } }; diff --git a/test/test_rsa.c b/test/test_rsa.c index 8886afe6..efec1871 100644 --- a/test/test_rsa.c +++ b/test/test_rsa.c @@ -2110,6 +2110,76 @@ int test_rsa_load_cert(void* data) return err; } +/** + * Load a DER certificate through the file store. The expected DER comes from + * OpenSSL's own PEM and ASN.1 code; the store must return the same bytes. + */ +int test_rsa_load_cert_der(void* data) +{ + int err; + BIO* pemBio = NULL; + BIO* derBio = NULL; + X509* oracle = NULL; + X509* cert = NULL; + unsigned char* oracleDer = NULL; + unsigned char* certDer = NULL; + int oracleLen = 0; + int certLen = 0; + OSSL_STORE_CTX* ctx = NULL; + OSSL_STORE_INFO* info = NULL; + + (void)data; + + PRINT_MSG("Convert certificate to DER with OpenSSL"); + pemBio = BIO_new_file(CERTS_DIR "/server-cert.pem", "r"); + err = pemBio == NULL; + if (err == 0) { + oracle = PEM_read_bio_X509(pemBio, NULL, NULL, NULL); + err = oracle == NULL; + } + if (err == 0) { + oracleLen = i2d_X509(oracle, &oracleDer); + err = oracleLen <= 0; + } + if (err == 0) { + derBio = BIO_new_mem_buf(oracleDer, oracleLen); + err = derBio == NULL; + } + if (err == 0) { + PRINT_MSG("Load DER certificate from the file store"); + ctx = OSSL_STORE_attach(derBio, "file", wpLibCtx, NULL, NULL, NULL, + NULL, NULL, NULL); + err = ctx == NULL; + } + if (err == 0) { + err = OSSL_STORE_expect(ctx, OSSL_STORE_INFO_CERT) != 1; + } + if (err == 0) { + info = OSSL_STORE_load(ctx); + err = info == NULL; + } + if (err == 0) { + cert = OSSL_STORE_INFO_get1_CERT(info); + err = cert == NULL; + } + if (err == 0) { + PRINT_MSG("Compare with OpenSSL's DER"); + certLen = i2d_X509(cert, &certDer); + err = (certLen != oracleLen) || + (memcmp(certDer, oracleDer, oracleLen) != 0); + } + + OPENSSL_free(certDer); + OPENSSL_free(oracleDer); + X509_free(cert); + X509_free(oracle); + OSSL_STORE_INFO_free(info); + OSSL_STORE_close(ctx); + BIO_free(derBio); + BIO_free(pemBio); + return err; +} + /** * Load the RSA private key from the file store with a caller property query. * diff --git a/test/unit.c b/test/unit.c index 2db3f5a0..ebeba5f6 100644 --- a/test/unit.c +++ b/test/unit.c @@ -417,6 +417,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_rsa_pss_no_params, NULL), TEST_DECL(test_rsa_load_key, NULL), TEST_DECL(test_rsa_load_cert, NULL), + TEST_DECL(test_rsa_load_cert_der, NULL), TEST_DECL(test_rsa_load_key_prop_query, NULL), TEST_DECL(test_rsa_fromdata, NULL), TEST_DECL(test_rsa_fromdata_oversize, NULL), diff --git a/test/unit.h b/test/unit.h index a315c3a0..29ac3539 100644 --- a/test/unit.h +++ b/test/unit.h @@ -404,6 +404,7 @@ int test_rsa_pss_no_params(void *data); int test_rsa_load_key(void* data); int test_rsa_load_cert(void* data); +int test_rsa_load_cert_der(void* data); int test_rsa_load_key_prop_query(void* data); int test_rsa_fromdata(void* data); int test_rsa_fromdata_oversize(void* data);