diff --git a/include/ekmfweb/ekmfweb.h b/include/ekmfweb/ekmfweb.h index cadecf20..5f1cd8ce 100644 --- a/include/ekmfweb/ekmfweb.h +++ b/include/ekmfweb/ekmfweb.h @@ -208,4 +208,140 @@ int ekmf_reencipher_identity_key(const struct ekmf_config *config, const struct ekmf_ext_lib *ext_lib, bool verbose); +struct ekmf_rsa_pss_params { + int salt_len; /* salt length in bytes, or OpenSSL constants + RSA_PSS_SALTLEN_DIGEST (-1), + RSA_PSS_SALTLEN_AUTO (-2), or + RSA_PSS_SALTLEN_MAX(-3) */ + int mgf_digest_nid; /* OpenSSl digest nid, or zero to use the same + digest algorithm as the signature algorithm */ +}; + +/** + * Generate a certificate signing request using the secure identity key (field + * identity_secure_key in config structure) with the specified subject name, + * certificate extensions (if any), and writes the CSR to the specified file + * in PEM format. + * + * To renew an existing certificate, specify renew_cert = true. In this case + * the existing certificate (field sign_certificate in config struct) is read, + * and the subject name is extracted from it. Any specified subject name RDNs + * are added to the CSR. Also, the extensions are taken from the existing + * certificate, and any specified extensions are added to the CSR. + * + * The CSR is signed using the secure identity key (field identity_secure_key in + * config structure) with an signing algorithm matching the identity key (ECDSA, + * RSA-PKCS, or RSA-PSS if rsa_pss is true), and the specified digest. If the + * digest nid is zero, then a default digest is used. + * + * @param config the configuration structure. Only field + * identity_secure_key must be specified, all others + * are optional. + * @param subject_rdns an array of strings, each string representing an + * RDN in the form '[+]type=value'. If the type is + * prepended with a '+', then this RDN is added to the + * previous one. + * @param num_subject_rdns number of RDN elements in the array. + * @param subject_utf8 if true, RDNs of type MBSTRING_UTF8 are created, + * otherwise type is MBSTRING_ASC is used. + * @param renew_cert_filename if not NULL, specifies the file name of a PEM file + * containing an existing certificate that is renewed + * @param extensions an array of strings, each string representing an + * certificate extension in the form 'type=value'. + * @param num_extensions number of extension elements in the array. + * @param digest_nid the OpenSSL digest nid to use with the signature + * algorithm, or 0 to use the default + * @param rsa_pss_params if not NULL and the identity key is an RSA key, then + * the CSR is signed with RSA-PSS using the specified + * PSS parameters. Ignored if the identity key is an EC + * key + * @param csr_pem_filename the name of the PEM file to which the CSR is written + * @param new_hdr if true, output "NEW" in the PEM header lines + * @param ext_lib External secure key crypto library to use + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success: + * -EINVAL: invalid parameter + * -ENOMEM: Failed to allocate memory + * -EBADMSG: an RDN or extension is not formatted correctly + * -EIO: OpenSSL failed to create the CSR + * -EEXIST: if one of the RDN name entries or extensions to add is a + * duplicate + * -ENOTSUP: the specified digest is not supported + * any other errno from file I/O routines + */ +int ekmf_generate_csr(const struct ekmf_config *config, + const char *subject_rdns[], size_t num_subject_rdns, + bool subject_utf8, const char *renew_cert_filename, + const char *extensions[], size_t num_extensions, + int digest_nid, + struct ekmf_rsa_pss_params *rsa_pss_params, + const char *csr_pem_filename, bool new_hdr, + const struct ekmf_ext_lib *ext_lib, bool verbose); + +/** + * Generate a self signed certificate using the secure identity key (field + * identity_secure_key in config structure) with the specified subject name, + * certificate extensions (if any), and writes the certificate the specified + * file in PEM format. + * + * To renew an existing certificate, specify renew_cert = true. In this case + * the existing certificate (field sign_certificate in config struct) is read, + * and the subject name is extracted from it. Any specified subject name RDNs + * are added to the certificate. Also, the extensions are taken from the + * existing certificate, and any specified extensions are added to the new + * certificate. + * + * The certificate is signed using the secure identity key (field + * identity_secure_key in config structure) with an signing algorithm matching + * the identity key (ECDSA, RSA-PKCS, or RSA-PSS if rsa_pss is true), and the + * specified digest. If the digest nid is zero, then a default digest is used. + * + * @param config the configuration structure. Only field + * identity_secure_key must be specified, all others + * are optional. + * @param subject_rdns an array of strings, each string representing an + * RDN in the form '[+]type=value'. If the type is + * prepended with a '+', then this RDN is added to the + * previous one. + * @param num_subject_rdns number of RDN elements in the array. + * @param subject_utf8 if true, RDNs of type MBSTRING_UTF8 are created, + * otherwise type is MBSTRING_ASC is used. + * @param renew_cert_filename if not NULL, specifies the file name of a PEM file + * containing an existing certificate that is renewed + * @param extensions an array of strings, each string representing an + * certificate extension in the form 'type=value'. + * @param num_extensions number of extension elements in the array. + * @param validity_days number if day from the current date how long the + * certificate is valid. + * @param digest_nid the OpenSSL digest nid to use with the signature + * algorithm, or 0 to use the default + * @param rsa_pss_params if not NULL and the identity key is an RSA key, then + * the certificate is signed with RSA-PSS using the + * specified PSS parameters. Ignored if the identity + * key is an EC key + * @param cert_pem_filename the name of the PEM file to which the Certificate + * is written + * @param ext_lib External secure key crypto library to use + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + * -EINVAL: invalid parameter + * -ENOMEM: Failed to allocate memory + * -EBADMSG: an RDN or extension is not formatted correctly + * -EIO: OpenSSL failed to create the certificate + * -EEXIST: if one of the RDN name entries or extensions to add is a + * duplicate + * -ENOTSUP: the specified digest is not supported + * any other errno from file I/O routines + */ +int ekmf_generate_ss_cert(const struct ekmf_config *config, + const char *subject_rdns[], size_t num_subject_rdns, + bool subject_utf8, const char *renew_cert_filename, + const char *extensions[], size_t num_extensions, + int validity_days, int digest_nid, + struct ekmf_rsa_pss_params *rsa_pss_params, + const char *cert_pem_filename, + const struct ekmf_ext_lib *ext_lib, bool verbose); + #endif diff --git a/libekmfweb/Makefile b/libekmfweb/Makefile index ab54fea6..0212398c 100644 --- a/libekmfweb/Makefile +++ b/libekmfweb/Makefile @@ -61,7 +61,7 @@ skip-libekmfweb-curl: all: $(BUILD_TARGETS) ekmfweb.o: check-dep-libekmfweb ekmfweb.c utilities.h cca.h $(rootdir)include/ekmfweb/ekmfweb.h -utilities.o: check-dep-libekmfweb utilities.c utilities.h +utilities.o: check-dep-libekmfweb utilities.c utilities.h $(rootdir)include/ekmfweb/ekmfweb.h cca.o: check-dep-libekmfweb cca.c cca.h utilities.h $(rootdir)include/ekmfweb/ekmfweb.h libekmfweb.so: ALL_CFLAGS += -fPIC diff --git a/libekmfweb/cca.c b/libekmfweb/cca.c index 437dbaf1..cb32107e 100644 --- a/libekmfweb/cca.c +++ b/libekmfweb/cca.c @@ -14,6 +14,10 @@ #include "lib/zt_common.h" +#include +#include +#include + #include "cca.h" #include "utilities.h" @@ -90,6 +94,59 @@ struct cca_section_header { #define CCA_SECTION_ID_RSA_ME_1024_EOPK_PRIV 0x30 #define CCA_SECTION_ID_RSA_CRT_4096_EOPK_PRIV 0x31 +struct cca_ecc_pub_key_section { + struct cca_section_header section_header; + uint8_t reserved1[4]; + uint8_t curve_type; + uint8_t reserved2; + uint16_t prime_bits_length; + uint16_t pub_key_length; /* Incl. compression indication byte */ + /* Public key of length pub_key_length */ +} __packed; + +struct cca_rsa_pub_key_section { + struct cca_section_header section_header; + uint16_t reserved1; + uint16_t pub_exp_length; + uint16_t modulus_bits_length; + uint16_t modulus_length; /* if 0 -> see priv key section */ + /* Public exponent of length pub_exp_length */ + /* Modulus of length modulus_length */ +} __packed; + +struct cca_rsa_crt_priv_key_section { + struct cca_section_header section_header; + uint16_t assoc_data_length; + uint16_t payload_length; + uint16_t reserved1; + uint8_t assoc_data_version; + uint8_t key_format; + uint8_t key_source; + uint8_t reserved2; + uint8_t hash_type; + uint8_t hash[32]; + uint8_t reserved3[3]; + uint8_t key_usage; + uint8_t format_restriction; + uint16_t p_length; + uint16_t q_length; + uint16_t dp_length; + uint16_t dq_length; + uint16_t u_length; + uint16_t modulus_length; + uint32_t reserved4; + uint8_t opk[48]; + uint8_t kvp[16]; + uint16_t reserved6; + /* Public modulus in length modulus_length */ + /* Encrypted payload (AESKW-wrapped key material) */ +} __packed; + +#define POINT_CONVERSION_COMPRESSED 0x02 +#define POINT_CONVERSION_UNCOMPRESSED 0x04 +#define POINT_CONVERSION_HYBRID 0x06 +#define POINT_CONVERSION_ODD_EVEN 0x01 + /** * Gets the CCA library function entry points from the library handle */ @@ -102,9 +159,10 @@ static int _cca_get_library_functions(const struct ekmf_cca_lib *cca_lib, cca->dll_CSNDPKB = (CSNDPKB_t)dlsym(cca_lib->cca_lib, "CSNDPKB"); cca->dll_CSNDPKG = (CSNDPKG_t)dlsym(cca_lib->cca_lib, "CSNDPKG"); cca->dll_CSNDKTC = (CSNDKTC_t)dlsym(cca_lib->cca_lib, "CSNDKTC"); + cca->dll_CSNDDSG = (CSNDDSG_t)dlsym(cca_lib->cca_lib, "CSNDDSG"); if (cca->dll_CSNDPKB == NULL || cca->dll_CSNDPKG == NULL || - cca->dll_CSNDKTC == NULL) + cca->dll_CSNDKTC == NULL || cca->dll_CSNDDSG == NULL) return -EIO; return 0; @@ -432,6 +490,201 @@ int cca_get_key_type(const unsigned char *key_token, size_t key_token_length, return 0; } +/** + * Extracts the ECC public key from an CCA internal ECC key token, and returns a + * it a OpenSSL PKEY. + * + * @param key_token the key token containing an CCA ECC key + * @param key_token_length the size of the key token + * @param pkey On return: a PKEY containing the public key + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + */ +int cca_get_ecc_pub_key_as_pkey(const unsigned char *key_token, + size_t key_token_length, + EVP_PKEY **pkey, bool verbose) +{ + struct cca_ecc_pub_key_section *ecc_pub_section; + const unsigned char *ecc_pub_key, *x, *y; + unsigned char *buf = NULL; + size_t prime_len; + int nid, y_bit = 0; + int rc = 0; + + if (key_token == NULL || pkey == NULL) + return -EINVAL; + + ecc_pub_section = (struct cca_ecc_pub_key_section *) + _cca_get_pka_section(key_token, key_token_length, + CCA_SECTION_ID_ECC_PUBL, verbose); + if (ecc_pub_section == NULL) + return -EINVAL; + if (ecc_pub_section->section_header.section_version != 0x00) { + pr_verbose(verbose, "invalid ECC public key section version"); + return -EINVAL; + } + if (ecc_pub_section->section_header.section_length < + sizeof(struct cca_ecc_pub_key_section)) { + pr_verbose(verbose, "invalid ECC public key section length"); + return -EINVAL; + } + + ecc_pub_key = ((unsigned char *)ecc_pub_section) + + sizeof(struct cca_ecc_pub_key_section); + + if (ecc_pub_section->curve_type == CCA_PRIME_CURVE) + nid = ecc_get_prime_curve_by_prime_bits( + ecc_pub_section->prime_bits_length); + else if (ecc_pub_section->curve_type == CCA_BRAINPOOL_CURVE) + nid = ecc_get_brainpool_curve_by_prime_bits( + ecc_pub_section->prime_bits_length); + else + nid = 0; + if (nid == 0) { + pr_verbose(verbose, "unsupported curve"); + rc = -EIO; + goto out; + } + prime_len = ecc_get_curve_prime_length(nid); + + x = ecc_pub_key + 1; + + /* First byte of public key contains indication of key compression */ + switch (ecc_pub_key[0]) { + case POINT_CONVERSION_COMPRESSED: + case POINT_CONVERSION_COMPRESSED + POINT_CONVERSION_ODD_EVEN: + /* Compressed form, only x is available */ + y_bit = (ecc_pub_key[0] & POINT_CONVERSION_ODD_EVEN) ? 1 : 0; + + buf = malloc(prime_len); + if (buf == NULL) { + pr_verbose(verbose, "malloc failed"); + rc = -ENOMEM; + goto out; + } + + rc = ecc_calculate_y_coordinate(nid, prime_len, x, y_bit, buf); + if (rc != 0) { + pr_verbose(verbose, "ecc_calculate_y_coordinate " + "failed"); + goto out; + } + + y = buf; + break; + + case POINT_CONVERSION_UNCOMPRESSED: + case POINT_CONVERSION_HYBRID: + case POINT_CONVERSION_HYBRID + POINT_CONVERSION_ODD_EVEN: + /* Uncompressed or hybrid, x and y are available */ + y = x + prime_len; + break; + + default: + pr_verbose(verbose, "invalid compression indication"); + rc = -EIO; + goto out; + } + + rc = ecc_pub_key_as_pkey(nid, prime_len, x, y, pkey); + if (rc != 0) { + pr_verbose(verbose, "ecc_pub_key_as_pkey failed"); + goto out; + } + +out: + if (buf != NULL) + free(buf); + + return rc; +} + +/** + * Extracts the RSA public key from an CCA internal RSA key token, and returns a + * it a OpenSSL PKEY. + * + * @param key_token the key token containing an CCA RSA key + * @param key_token_length the size of the key token + * @param pkey_type the PKEY type (EVP_PKEY_RSA or EVP_PKEY_RSA_PSS)* + * @param pkey On return: a PKEY containing the public key + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + */ +int cca_get_rsa_pub_key_as_pkey(const unsigned char *key_token, + size_t key_token_length, + int pkey_type, EVP_PKEY **pkey, bool verbose) +{ + const struct cca_rsa_crt_priv_key_section *rsa_priv_section; + const struct cca_rsa_pub_key_section *rsa_pub_section; + const unsigned char *pub_exp, *modulus; + size_t modulus_length; + int rc = 0; + + if (key_token == NULL || pkey == NULL) + return -EINVAL; + + rsa_pub_section = (struct cca_rsa_pub_key_section *) + _cca_get_pka_section(key_token, key_token_length, + CCA_SECTION_ID_RSA_PUBL, verbose); + if (rsa_pub_section == NULL) + return -EINVAL; + if (rsa_pub_section->section_header.section_version != 0x00) { + pr_verbose(verbose, "invalid RSA public key section version"); + return -EINVAL; + } + if (rsa_pub_section->section_header.section_length < + sizeof(struct cca_ecc_pub_key_section)) { + pr_verbose(verbose, "invalid RSA public key section length"); + return -EINVAL; + } + + pub_exp = ((unsigned char *)rsa_pub_section) + + sizeof(struct cca_rsa_pub_key_section); + modulus = pub_exp + rsa_pub_section->pub_exp_length; + modulus_length = rsa_pub_section->modulus_length; + + /* + * The public key section may have a modulus_length of zero, need to + * get the modulus from the private key section instead. + */ + if (rsa_pub_section->modulus_length == 0) { + rsa_priv_section = (struct cca_rsa_crt_priv_key_section *) + _cca_get_pka_section(key_token, key_token_length, + CCA_SECTION_ID_RSA_CRT_4096_EOPK_PRIV, verbose); + + if (rsa_priv_section == NULL) + return -EINVAL; + if (rsa_priv_section->section_header.section_version != 0x00) { + pr_verbose(verbose, "invalid RSA private key section " + "version"); + return -EINVAL; + } + if (rsa_priv_section->section_header.section_length < + sizeof(struct cca_rsa_crt_priv_key_section)) { + pr_verbose(verbose, "invalid RSA private key section " + "length"); + return -EINVAL; + } + + modulus = ((unsigned char *)rsa_priv_section) + + sizeof(struct cca_rsa_crt_priv_key_section); + modulus_length = rsa_priv_section->modulus_length; + } + + rc = rsa_pub_key_as_pkey(modulus, modulus_length, pub_exp, + rsa_pub_section->pub_exp_length, + pkey_type, pkey); + if (rc != 0) { + pr_verbose(verbose, "rsa_pub_key_as_pkey failed"); + goto out; + } + +out: + return rc; +} + /** * Re-enciphers a key token with a new CCA master key. * @@ -519,3 +772,431 @@ int cca_reencipher_key(const struct ekmf_cca_lib *cca_lib, return 0; } + +static const unsigned char der_DigestInfo_SHA1[] = { + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, + 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14, }; +static const unsigned char der_DigestInfo_SHA224[] = { + 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, + 0x00, 0x04, 0x1C, }; +static const unsigned char der_DigestInfo_SHA256[] = { + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20, }; +static const unsigned char der_DigestInfo_SHA384[] = { + 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, + 0x00, 0x04, 0x30, }; +static const unsigned char der_DigestInfo_SHA512[] = { + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40, }; +static const unsigned char der_DigestInfo_SHA3_224[] = { + 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x07, 0x05, + 0x00, 0x04, 0x1C, }; +static const unsigned char der_DigestInfo_SHA3_256[] = { + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05, + 0x00, 0x04, 0x20, }; +static const unsigned char der_DigestInfo_SHA3_384[] = { + 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09, 0x05, + 0x00, 0x04, 0x30, }; +static const unsigned char der_DigestInfo_SHA3_512[] = { + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a, 0x05, + 0x00, 0x04, 0x40, }; + +struct digest_info { + int digest_nid; + size_t digest_size; + const char *cca_keyword; + const unsigned char *der; + size_t der_size; +}; + +static const struct digest_info digest_list[] = { + { .digest_nid = NID_sha1, .digest_size = SHA_DIGEST_LENGTH, + .cca_keyword = "SHA-1 ", .der = der_DigestInfo_SHA1, + .der_size = sizeof(der_DigestInfo_SHA1), }, + { .digest_nid = NID_sha224, .digest_size = SHA224_DIGEST_LENGTH, + .cca_keyword = "SHA-224 ", .der = der_DigestInfo_SHA224, + .der_size = sizeof(der_DigestInfo_SHA224), }, + { .digest_nid = NID_sha256, .digest_size = SHA256_DIGEST_LENGTH, + .cca_keyword = "SHA-256 ", .der = der_DigestInfo_SHA256, + .der_size = sizeof(der_DigestInfo_SHA256), }, + { .digest_nid = NID_sha384, .digest_size = SHA384_DIGEST_LENGTH, + .cca_keyword = "SHA-384 ", .der = der_DigestInfo_SHA384, + .der_size = sizeof(der_DigestInfo_SHA384), }, + { .digest_nid = NID_sha512, .digest_size = SHA512_DIGEST_LENGTH, + .cca_keyword = "SHA-512 ", .der = der_DigestInfo_SHA512, + .der_size = sizeof(der_DigestInfo_SHA512), }, + { .digest_nid = NID_sha3_224, .digest_size = SHA224_DIGEST_LENGTH, + .cca_keyword = NULL, .der = der_DigestInfo_SHA3_224, + .der_size = sizeof(der_DigestInfo_SHA3_224), }, + { .digest_nid = NID_sha3_256, .digest_size = SHA256_DIGEST_LENGTH, + .cca_keyword = NULL, .der = der_DigestInfo_SHA3_256, + .der_size = sizeof(der_DigestInfo_SHA3_256), }, + { .digest_nid = NID_sha3_384, .digest_size = SHA384_DIGEST_LENGTH, + .cca_keyword = NULL, .der = der_DigestInfo_SHA3_384, + .der_size = sizeof(der_DigestInfo_SHA3_384), }, + { .digest_nid = NID_sha3_512, .digest_size = SHA512_DIGEST_LENGTH, + .cca_keyword = NULL, .der = der_DigestInfo_SHA3_512, + .der_size = sizeof(der_DigestInfo_SHA3_512), }, +}; + +static const int digest_list_num = sizeof(digest_list) / + sizeof(struct digest_info); + +static const struct digest_info *get_digest_info(int digest_nid) +{ + int i; + + for (i = 0; i < digest_list_num; i++) { + if (digest_list[i].digest_nid == digest_nid) + return &digest_list[i]; + } + + return NULL; +} + +struct cca_private_data { + CSNDDSG_t dll_CSNDDSG; + bool verbose; +}; + +/** + * Sign data using RSA. + * + * @param cca_lib the CCA library structure + * @param key_token the RSA key token + * @param key_token_length the length of the key token + * @param sig a buffer to store the signature on return. + * @param siglen on input: the size if the signature buffer + * on return: the size of the signature + * @param tbs the data to be signed. + * @param tbslen the size of the data to be signed + * @param padding_type the OpenSSL padding type (RSA_X931_PADDING or + * RSA_PKCS1_PADDING) + * @param digest_nid the OpenSSL nid of the message digest used to + * produce the data to be signed + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + */ +int cca_rsa_sign(const struct ekmf_cca_lib *cca_lib, + const unsigned char *key_token, size_t key_token_length, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, + int padding_type, int md_nid, bool verbose) +{ + long return_code, reason_code, rule_array_count, exit_data_len = 0; + long token_length, hash_length, sign_bit_length, sign_length; + unsigned char rule_array[3 * CCA_KEYWORD_SIZE] = { 0, }; + unsigned char *hash = NULL, *buf = NULL; + const struct digest_info *digest; + unsigned char *exit_data = NULL; + struct cca_lib cca; + int rc; + + if (cca_lib == NULL || key_token == NULL || sig == NULL || + siglen == NULL || tbs == NULL) + return -EINVAL; + + rc = _cca_get_library_functions(cca_lib, &cca); + if (rc != 0) { + pr_verbose(verbose, "Failed to get CCA functions from library"); + return rc; + } + + rule_array_count = 3; + memcpy(rule_array, "RSA ", CCA_KEYWORD_SIZE); + memcpy(rule_array + CCA_KEYWORD_SIZE, "HASH ", CCA_KEYWORD_SIZE); + + switch (padding_type) { + case RSA_X931_PADDING: + hash = (unsigned char *)tbs; + hash_length = tbslen; + + memcpy(rule_array + 2 * CCA_KEYWORD_SIZE, "X9.31 ", + CCA_KEYWORD_SIZE); + break; + + case RSA_PKCS1_PADDING: + digest = get_digest_info(md_nid); + if (digest == NULL) { + pr_verbose(verbose, "Invalid digest nid: %d", md_nid); + return -EINVAL; + } + + if (tbslen != digest->digest_size) { + pr_verbose(verbose, "Invalid data length: %lu", tbslen); + return -EINVAL; + } + + hash_length = digest->der_size + tbslen; + buf = (unsigned char *)malloc(hash_length); + if (buf == NULL) { + pr_verbose(verbose, "malloc failed"); + return -ENOMEM; + } + + memcpy(buf, digest->der, digest->der_size); + memcpy(buf + digest->der_size, tbs, tbslen); + hash = buf; + + memcpy(rule_array + 2 * CCA_KEYWORD_SIZE, "PKCS-1.1", + CCA_KEYWORD_SIZE); + break; + + default: + pr_verbose(verbose, "Invalid padding type: %d", padding_type); + return -EINVAL; + } + + token_length = key_token_length; + sign_length = *siglen; + + cca.dll_CSNDDSG(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &token_length, (unsigned char *)key_token, + &hash_length, hash, + &sign_length, &sign_bit_length, sig); + + if (return_code != 0) { + pr_verbose(verbose, "CCA CSNDDSG (DIG. SIGNATURE CREATE, RSA) " + "failed: return_code: %ld reason_code: %ld", + return_code, reason_code); + rc = -EIO; + goto out; + } + + *siglen = sign_length; + rc = 0; + +out: + if (buf != NULL) + free(buf); + + return rc; +} + +/** + * Sign data using RSA-PSS. + * + * @param cca_lib the CCA library structure + * @param key_token the RSA key token + * @param key_token_length the length of the key token + * @param sig a buffer to store the signature on return. + * @param siglen on input: the size if the signature buffer + * on return: the size of the signature + * @param tbs the data to be signed. + * @param tbslen the size of the data to be signed + * @param digest_nid the OpenSSL nid of the message digest used to + * produce the data to be signed + * @param mgf_digest_nid the OpenSSL nid of the mask generation function for + * PSS padding + * @param saltlen the length of the salt for PSS + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + */ +int cca_rsa_pss_sign(const struct ekmf_cca_lib *cca_lib, + const unsigned char *key_token, size_t key_token_length, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, + int digest_nid, int mgf_digest_nid, int saltlen, + bool verbose) +{ + long return_code, reason_code, rule_array_count, exit_data_len = 0; + unsigned char rule_array[4 * CCA_KEYWORD_SIZE] = { 0, }; + long token_length, hash_length, sign_bit_length, sign_length; + const struct digest_info *digest; + unsigned char *exit_data = NULL; + unsigned char *buf = NULL; + struct cca_lib cca; + uint32_t salt_len; + int rc; + + if (cca_lib == NULL || key_token == NULL || sig == NULL || + siglen == NULL || tbs == NULL) + return -EINVAL; + + rc = _cca_get_library_functions(cca_lib, &cca); + if (rc != 0) { + pr_verbose(verbose, "Failed to get CCA functions from library"); + return rc; + } + + if (mgf_digest_nid != digest_nid) { + pr_verbose(verbose, "Mgf nid must be the same as the message " + "digest nid"); + return -EINVAL; + } + + digest = get_digest_info(digest_nid); + if (digest == NULL || digest->cca_keyword == NULL) { + pr_verbose(verbose, "Invalid mgf nid: %d", digest_nid); + return -EINVAL; + } + + rule_array_count = 4; + memcpy(rule_array, "RSA ", CCA_KEYWORD_SIZE); + memcpy(rule_array + CCA_KEYWORD_SIZE, "PKCS-PSS", CCA_KEYWORD_SIZE); + memcpy(rule_array + 2 * CCA_KEYWORD_SIZE, "HASH ", CCA_KEYWORD_SIZE); + memcpy(rule_array + 3 * CCA_KEYWORD_SIZE, digest->cca_keyword, + CCA_KEYWORD_SIZE); + + hash_length = sizeof(uint32_t) + tbslen; + buf = (unsigned char *)malloc(hash_length); + if (buf == NULL) { + pr_verbose(verbose, "malloc failed"); + return -ENOMEM; + } + + salt_len = saltlen; + memcpy(buf, &salt_len, sizeof(uint32_t)); + memcpy(buf + sizeof(uint32_t), tbs, tbslen); + + token_length = key_token_length; + sign_length = *siglen; + + cca.dll_CSNDDSG(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &token_length, (unsigned char *)key_token, + &hash_length, buf, + &sign_length, &sign_bit_length, sig); + + if (return_code != 0) { + pr_verbose(verbose, "CCA CSNDDSG (DIG. SIGNATURE CREATE, " + "RSA-PSS) failed: return_code: %ld reason_code: %ld", + return_code, reason_code); + rc = -EIO; + goto out; + } + + *siglen = sign_length; + rc = 0; + +out: + free(buf); + + return rc; +} + +/** + * Sign data using ECDSA. + * + * @param cca_lib the CCA library structure + * @param key_token the RSA key token + * @param key_token_length the length of the key token + * @param sig a buffer to store the signature on return. + * @param siglen on input: the size if the signature buffer + * on return: the size of the signature + * @param tbs the data to be signed. + * @param tbslen the size of the data to be signed + * @param digest_nid the OpenSSL nid of the message digest used to + * produce the data to be signed + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + */ +int cca_ecdsa_sign(const struct ekmf_cca_lib *cca_lib, + const unsigned char *key_token, size_t key_token_length, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, + int UNUSED(digest_nid), bool verbose) +{ + long return_code, reason_code, rule_array_count, exit_data_len = 0; + long token_length, hash_length, sign_bit_length, sign_length; + unsigned char rule_array[2 * CCA_KEYWORD_SIZE] = { 0, }; + unsigned char *exit_data = NULL; + unsigned char *der = NULL; + ECDSA_SIG *ec_sig = NULL; + BIGNUM *bn_r = NULL; + BIGNUM *bn_s = NULL; + struct cca_lib cca; + int rc, der_len; + + if (cca_lib == NULL || key_token == NULL || sig == NULL || + siglen == NULL || tbs == NULL) + return -EINVAL; + + rc = _cca_get_library_functions(cca_lib, &cca); + if (rc != 0) { + pr_verbose(verbose, "Failed to get CCA functions from library"); + return rc; + } + + rule_array_count = 2; + memcpy(rule_array, "ECDSA ", CCA_KEYWORD_SIZE); + memcpy(rule_array + CCA_KEYWORD_SIZE, "HASH ", CCA_KEYWORD_SIZE); + + hash_length = tbslen; + token_length = key_token_length; + sign_length = *siglen; + + cca.dll_CSNDDSG(&return_code, &reason_code, + &exit_data_len, exit_data, + &rule_array_count, rule_array, + &token_length, (unsigned char *)key_token, + &hash_length, (unsigned char *)tbs, + &sign_length, &sign_bit_length, sig); + + if (return_code != 0) { + pr_verbose(verbose, "CCA CSNDDSG (DIG. SIGNATURE CREATE, ECDSA)" + " failed: return_code: %ld reason_code: %ld", + return_code, reason_code); + return -EIO; + } + + ec_sig = ECDSA_SIG_new(); + if (ec_sig == NULL) { + rc = -ENOMEM; + goto out; + } + + bn_r = BN_bin2bn(sig, sign_length / 2, NULL); + bn_s = BN_bin2bn(sig + sign_length / 2, sign_length / 2, NULL); + if (bn_r == NULL || bn_s == NULL) { + rc = -EIO; + goto out; + } + + if (ECDSA_SIG_set0(ec_sig, bn_r, bn_s) != 1) { + rc = -EIO; + goto out; + } + bn_r = NULL; + bn_s = NULL; + + der_len = i2d_ECDSA_SIG(ec_sig, NULL); + if (der_len > (int)*siglen) { + rc = -ERANGE; + goto out; + } + + memset(sig, 0, *siglen); + der = sig; + *siglen = i2d_ECDSA_SIG(ec_sig, &der); + + if (*siglen == 0) { + rc = -EIO; + goto out; + } + + rc = 0; +out: + if (ec_sig != NULL) + ECDSA_SIG_free(ec_sig); + if (bn_r != NULL) + BN_free(bn_r); + if (bn_s != NULL) + BN_free(bn_s); + + return rc; +} diff --git a/libekmfweb/cca.h b/libekmfweb/cca.h index 1ec56915..f7c5f342 100644 --- a/libekmfweb/cca.h +++ b/libekmfweb/cca.h @@ -65,10 +65,26 @@ typedef void (*CSNDKTC_t)(long *return_code, long *key_identifier_length, unsigned char *key_identifier); +/* CCA Digital Signature Generate function */ +typedef void (*CSNDDSG_t)(long *return_code, + long *reason_code, + long *exit_data_length, + unsigned char *exit_data, + long *rule_array_count, + unsigned char *rule_array, + long *PKA_private_key_identifier_length, + unsigned char *PKA_private_key_identifier, + long *hash_length, + unsigned char *hash, + long *signature_field_length, + long *signature_bit_length, + unsigned char *signature_field); + struct cca_lib { CSNDPKB_t dll_CSNDPKB; CSNDPKG_t dll_CSNDPKG; CSNDKTC_t dll_CSNDKTC; + CSNDDSG_t dll_CSNDDSG; }; #define CCA_MAX_PKA_KEY_TOKEN_SIZE 3500 @@ -89,4 +105,31 @@ int cca_reencipher_key(const struct ekmf_cca_lib *cca_lib, const unsigned char *key_token, size_t key_token_length, bool to_new, bool verbose); +int cca_get_ecc_pub_key_as_pkey(const unsigned char *key_token, + size_t key_token_length, + EVP_PKEY **pkey, bool verbose); + +int cca_get_rsa_pub_key_as_pkey(const unsigned char *key_token, + size_t key_token_length, + int pkey_type, EVP_PKEY **pkey, bool verbose); + +int cca_rsa_sign(const struct ekmf_cca_lib *cca_lib, + const unsigned char *key_token, size_t key_token_length, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, + int padding_type, int digest_nid, bool verbose); + +int cca_rsa_pss_sign(const struct ekmf_cca_lib *cca_lib, + const unsigned char *key_token, size_t key_token_length, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, + int digest_nid, int mgf_digest_nid, int saltlen, + bool verbose); + +int cca_ecdsa_sign(const struct ekmf_cca_lib *cca_lib, + const unsigned char *key_token, size_t key_token_length, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, int digest_nid, + bool verbose); + #endif diff --git a/libekmfweb/ekmfweb.c b/libekmfweb/ekmfweb.c index 14e7ed1f..2528d42a 100644 --- a/libekmfweb/ekmfweb.c +++ b/libekmfweb/ekmfweb.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #ifndef JSON_C_TO_STRING_NOSLASHESCAPE @@ -33,6 +34,8 @@ #include "utilities.h" #include "cca.h" +#define SERIAL_NUMBER_BIT_SIZE 159 + #define MAX_KEY_BLOB_SIZE CCA_MAX_PKA_KEY_TOKEN_SIZE #define pr_verbose(verbose, fmt...) do { \ @@ -1295,6 +1298,682 @@ int ekmf_reencipher_identity_key(const struct ekmf_config *config, return 0; } +struct private_data { + const struct ekmf_ext_lib *ext_lib; + bool verbose; +}; + +/** + * Wrapper for the RSA sign callback to route the call to the selected + * secure key library. + */ +static int _ekmf_rsa_sign(const unsigned char *key_blob, size_t key_blob_length, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, + int padding_type, int md_nid, void *private) +{ + struct private_data *prv = (struct private_data *)private; + + if (prv == NULL || prv->ext_lib == NULL) + return 1; + + switch (prv->ext_lib->type) { + case EKMF_EXT_LIB_CCA: + return cca_rsa_sign(prv->ext_lib->cca, key_blob, + key_blob_length, sig, siglen, tbs, tbslen, + padding_type, md_nid, prv->verbose); + default: + return 1; + } +} + +/** + * Wrapper for the RSA-PSS sign callback to route the call to the selected + * secure key library. + */ +static int _ekmf_rsa_pss_sign(const unsigned char *key_blob, + size_t key_blob_length, unsigned char *sig, + size_t *siglen, const unsigned char *tbs, + size_t tbslen, int md_nid, int mgfmd_nid, + int saltlen, void *private) +{ + struct private_data *prv = (struct private_data *)private; + + if (prv == NULL || prv->ext_lib == NULL) + return 1; + + switch (prv->ext_lib->type) { + case EKMF_EXT_LIB_CCA: + return cca_rsa_pss_sign(prv->ext_lib->cca, key_blob, + key_blob_length, sig, siglen, tbs, + tbslen, md_nid, mgfmd_nid, saltlen, + prv->verbose); + default: + return 1; + } +} + +/** + * Wrapper for the ECDSA sign callback to route the call to the selected + * secure key library. + */ +static int _ekmf_ecdsa_sign(const unsigned char *key_blob, + size_t key_blob_length, unsigned char *sig, + size_t *siglen, const unsigned char *tbs, + size_t tbslen, int md_nid, void *private) +{ + struct private_data *prv = (struct private_data *)private; + + if (prv == NULL || prv->ext_lib == NULL) + return 1; + + switch (prv->ext_lib->type) { + case EKMF_EXT_LIB_CCA: + return cca_ecdsa_sign(prv->ext_lib->cca, key_blob, + key_blob_length, sig, siglen, tbs, tbslen, + md_nid, prv->verbose); + default: + return 1; + } +} + +/** + * Gets the public key from the key blob as a PKEY object. + */ +static int _ekmf_get_pub_key_as_pkey(const unsigned char *key_blob, + size_t key_blob_size, EVP_PKEY **pkey, + bool rsa_pss, + const struct ekmf_ext_lib *ext_lib, + bool verbose) +{ + int rc, pkey_type; + + switch (ext_lib->type) { + case EKMF_EXT_LIB_CCA: + rc = cca_get_key_type(key_blob, key_blob_size, &pkey_type); + if (rc != 0) { + pr_verbose(verbose, "Failed to get the identity key " + "type: %s", strerror(-rc)); + return rc; + } + + switch (pkey_type) { + case EVP_PKEY_EC: + rc = cca_get_ecc_pub_key_as_pkey(key_blob, + key_blob_size, pkey, verbose); + break; + case EVP_PKEY_RSA: + case EVP_PKEY_RSA_PSS: + rc = cca_get_rsa_pub_key_as_pkey(key_blob, + key_blob_size, rsa_pss ? + EVP_PKEY_RSA_PSS : pkey_type, + pkey, verbose); + break; + default: + pr_verbose(verbose, "Invalid identity key type: %d", + pkey_type); + return -EIO; + } + + if (rc != 0) + return rc; + break; + default: + pr_verbose(verbose, "Invalid ext lib type: %d", ext_lib->type); + return -EINVAL; + } + + return 0; +} + +/** + * Setup a signing context for the specified key, digest_nid, and RSA-PSS + * parameters. + */ +static int _ekmf_setup_sign_context(const unsigned char *key_blob, + size_t key_blob_size, EVP_PKEY *pkey, + int digest_nid, + struct ekmf_rsa_pss_params *rsa_pss_params, + EVP_MD_CTX **md_ctx, + EVP_PKEY_CTX **pkey_ctx, + struct private_data *private, + bool verbose) +{ + struct sk_pkey_sign_func sign_func; + EVP_PKEY_CTX *pctx = NULL; + const EVP_MD *md = NULL; + int rc, default_nid; + EVP_MD_CTX *ctx; + + rc = setup_secure_key_pkey_method(EVP_PKEY_id(pkey)); + if (rc != 0) { + pr_verbose(verbose, "Failed to setup secure key PKEY method"); + return rc; + } + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + pr_verbose(verbose, "Failed to allocate the digest context"); + rc = -ENOMEM; + goto out; + } + + if (digest_nid != 0) { + md = EVP_get_digestbynid(digest_nid); + if (md == NULL) { + pr_verbose(verbose, "Requested digest not supported"); + rc = -ENOTSUP; + goto out; + } + + if (EVP_PKEY_get_default_digest_nid(pkey, &default_nid) == 2 && + default_nid == 0) { + pr_verbose(verbose, "The signing algorithm requires " + "there to be no digest"); + md = NULL; + } + } + + rc = EVP_DigestSignInit(ctx, &pctx, md, NULL, pkey); + if (rc != 1) { + pr_verbose(verbose, "Failed to initialize the signing " + "operation"); + rc = -EIO; + goto out; + } + + sign_func.rsa_sign = _ekmf_rsa_sign; + sign_func.rsa_pss_sign = _ekmf_rsa_pss_sign; + sign_func.ecdsa_sign = _ekmf_ecdsa_sign; + + rc = setup_secure_key_pkey_context(pctx, key_blob, key_blob_size, + &sign_func, private); + if (rc != 0) { + pr_verbose(verbose, "Failed to setup the secure key PKEY " + "context: %s", strerror(-rc)); + goto out; + } + + if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA_PSS && rsa_pss_params != NULL) { + rc = setup_rsa_pss_pkey_context(pctx, rsa_pss_params); + if (rc != 0) { + pr_verbose(verbose, "Failed to setup RSA-PSS context"); + goto out; + } + } + + *md_ctx = ctx; + *pkey_ctx = pctx; + +out: + if (rc != 0) { + cleanup_secure_key_pkey_method(EVP_PKEY_id(pkey)); + if (ctx != NULL) + EVP_MD_CTX_free(ctx); + } + return rc; +} + +/** + * Generate a certificate signing request using the secure identity key (field + * identity_secure_key in config structure) with the specified subject name, + * certificate extensions (if any), and writes the CSR to the specified file + * in PEM format. + * + * To renew an existing certificate, specify renew_cert = true. In this case + * the existing certificate (field sign_certificate in config struct) is read, + * and the subject name is extracted from it. Any specified subject name RDNs + * are added to the CSR. Also, the extensions are taken from the existing + * certificate, and any specified extensions are added to the CSR. + * + * The CSR is signed using the secure identity key (field identity_secure_key in + * config structure) with an signing algorithm matching the identity key (ECDSA, + * RSA-PKCS, or RSA-PSS if rsa_pss is true), and the specified digest. If the + * digest nid is zero, then a default digest is used. + * + * @param config the configuration structure. Only field + * identity_secure_key must be specified, all others + * are optional. + * @param subject_rdns an array of strings, each string representing an + * RDN in the form '[+]type=value'. If the type is + * prepended with a '+', then this RDN is added to the + * previous one. + * @param num_subject_rdns number of RDN elements in the array. + * @param subject_utf8 if true, RDNs of type MBSTRING_UTF8 are created, + * otherwise type is MBSTRING_ASC is used. + * @param renew_cert_filename if not NULL, specifies the file name of a PEM file + * containing an existing certificate that is renewed + * @param extensions an array of strings, each string representing an + * certificate extension in the form 'type=value'. + * @param num_extensions number of extension elements in the array. + * @param digest_nid the OpenSSL digest nid to use with the signature + * algorithm, or 0 to use the default + * @param rsa_pss_params if not NULL and the identity key is an RSA key, then + * the CSR is signed with RSA-PSS using the specified + * PSS parameters. Ignored if the identity key is an EC + * key + * @param csr_pem_filename the name of the PEM file to which the CSR is written + * @param new_hdr if true, output "NEW" in the PEM header lines + * @param ext_lib External secure key crypto library to use + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success: + * -EINVAL: invalid parameter + * -ENOMEM: Failed to allocate memory + * -EBADMSG: an RDN or extension is not formatted correctly + * -EIO: OpenSSL failed to create the CSR + * -EEXIST: if one of the RDN name entries or extensions to add is a + * duplicate + * -ENOTSUP: the specified digest is not supported + * any other errno from file I/O routines + */ +int ekmf_generate_csr(const struct ekmf_config *config, + const char *subject_rdns[], size_t num_subject_rdns, + bool subject_utf8, const char *renew_cert_filename, + const char *extensions[], size_t num_extensions, + int digest_nid, + struct ekmf_rsa_pss_params *rsa_pss_params, + const char *csr_pem_filename, bool new_hdr, + const struct ekmf_ext_lib *ext_lib, bool verbose) +{ + const STACK_OF(X509_EXTENSION) *cert_exts = NULL; + unsigned char key_blob[MAX_KEY_BLOB_SIZE]; + size_t key_blob_size = sizeof(key_blob); + X509_NAME *subject_name = NULL; + EVP_PKEY_CTX *pkey_ctx = NULL; + struct private_data private; + EVP_MD_CTX *md_ctx = NULL; + bool pkey_meth = false; + EVP_PKEY *pkey = NULL; + X509_REQ *req = NULL; + X509 *cert = NULL; + int rc; + + if (config == NULL || ext_lib == NULL || csr_pem_filename == NULL) + return -EINVAL; + if (config->identity_secure_key == NULL) + return -EINVAL; + if (renew_cert_filename == NULL && + (subject_rdns == NULL || num_subject_rdns == 0)) + return -EINVAL; + if (num_extensions != 0 && extensions == NULL) + return -EINVAL; + + rc = read_key_blob(config->identity_secure_key, key_blob, + &key_blob_size); + if (rc != 0) { + pr_verbose(verbose, "Failed to read identity key from file " + "'%s': %s", config->identity_secure_key, + strerror(-rc)); + goto out; + } + + rc = _ekmf_get_pub_key_as_pkey(key_blob, key_blob_size, &pkey, + rsa_pss_params != NULL, ext_lib, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to get identity key as PKEY from " + "file '%s': %s", config->identity_secure_key, + strerror(-rc)); + goto out; + } + + req = X509_REQ_new(); + if (req == NULL) { + pr_verbose(verbose, "X509_REQ_new failed"); + rc = -ENOMEM; + goto out; + } + + rc = X509_REQ_set_version(req, 0L); + if (rc != 1) { + pr_verbose(verbose, "X509_REQ_set_version failed: rc: %d", rc); + rc = -EIO; + goto out; + } + + if (renew_cert_filename != NULL) { + rc = read_x509_certificate(renew_cert_filename, &cert); + if (rc != 0) { + pr_verbose(verbose, "Failed to open renew cert file " + "'%s': %s", renew_cert_filename, + strerror(-rc)); + goto out; + } + + subject_name = X509_NAME_dup(X509_get_subject_name(cert)); + cert_exts = X509_get0_extensions(cert); + } + + if (subject_rdns != NULL && num_subject_rdns > 0) { + rc = build_subject_name(&subject_name, subject_rdns, + num_subject_rdns, subject_utf8); + if (rc != 0) { + pr_verbose(verbose, "Failed to parse the subject name " + "RDSn: %s", strerror(-rc)); + goto out; + } + } + + if (subject_name == NULL) { + rc = -EINVAL; + pr_verbose(verbose, "Subject name can not be empty"); + goto out; + } + + rc = X509_REQ_set_subject_name(req, subject_name); + if (rc != 1) { + rc = -EIO; + pr_verbose(verbose, "Failed to set subject name into request"); + goto out; + } + + rc = build_certificate_extensions(NULL, req, extensions, + num_extensions, cert_exts); + if (rc != 0) { + pr_verbose(verbose, "Failed to parse the extensions: " + "%s", strerror(-rc)); + goto out; + } + + rc = X509_REQ_set_pubkey(req, pkey); + if (rc != 1) { + pr_verbose(verbose, "Failed to set the public key"); + rc = -EIO; + goto out; + } + + private.ext_lib = ext_lib; + private.verbose = verbose; + + rc = _ekmf_setup_sign_context(key_blob, key_blob_size, pkey, digest_nid, + rsa_pss_params, &md_ctx, &pkey_ctx, + &private, verbose); + if (rc != 0) + goto out; + pkey_meth = true; + + rc = X509_REQ_sign_ctx(req, md_ctx); + if (rc <= 0) { + pr_verbose(verbose, "Failed to perform the signing operation"); + rc = -EIO; + goto out; + } + + rc = write_x509_request(csr_pem_filename, req, new_hdr); + if (rc != 0) { + pr_verbose(verbose, "Failed to write CSR to file " + "'%s': %s", csr_pem_filename, strerror(-rc)); + goto out; + } + + if (verbose) { + pr_verbose(verbose, "Certificate Signing Request created:"); + X509_REQ_print_fp(stderr, req); + } + +out: + if (md_ctx != NULL) + EVP_MD_CTX_free(md_ctx); + if (pkey_meth) + cleanup_secure_key_pkey_method(EVP_PKEY_id(pkey)); + if (subject_name != NULL) + X509_NAME_free(subject_name); + if (cert != NULL) + X509_free(cert); + if (req != NULL) + X509_REQ_free(req); + if (pkey != NULL) + EVP_PKEY_free(pkey); + + return rc; +} + +/** + * Generate a self signed certificate using the secure identity key (field + * identity_secure_key in config structure) with the specified subject name, + * certificate extensions (if any), and writes the certificate the specified + * file in PEM format. + * + * To renew an existing certificate, specify renew_cert = true. In this case + * the existing certificate (field sign_certificate in config struct) is read, + * and the subject name is extracted from it. Any specified subject name RDNs + * are added to the certificate. Also, the extensions are taken from the + * existing certificate, and any specified extensions are added to the new + * certificate. + * + * The certificate is signed using the secure identity key (field + * identity_secure_key in config structure) with an signing algorithm matching + * the identity key (ECDSA, RSA-PKCS, or RSA-PSS if rsa_pss is true), and the + * specified digest. If the digest nid is zero, then a default digest is used. + * + * @param config the configuration structure. Only field + * identity_secure_key must be specified, all others + * are optional. + * @param subject_rdns an array of strings, each string representing an + * RDN in the form '[+]type=value'. If the type is + * prepended with a '+', then this RDN is added to the + * previous one. + * @param num_subject_rdns number of RDN elements in the array. + * @param subject_utf8 if true, RDNs of type MBSTRING_UTF8 are created, + * otherwise type is MBSTRING_ASC is used. + * @param renew_cert_filename if not NULL, specifies the file name of a PEM file + * containing an existing certificate that is renewed + * @param extensions an array of strings, each string representing an + * certificate extension in the form 'type=value'. + * @param num_extensions number of extension elements in the array. + * @param validity_days number if day from the current date how long the + * certificate is valid. + * @param digest_nid the OpenSSL digest nid to use with the signature + * algorithm, or 0 to use the default + * @param rsa_pss_params if not NULL and the identity key is an RSA key, then + * the certificate is signed with RSA-PSS using the + * specified PSS parameters. Ignored if the identity + * key is an EC key + * @param cert_pem_filename the name of the PEM file to which the Certificate + * is written + * @param ext_lib External secure key crypto library to use + * @param verbose if true, verbose messages are printed + * + * @returns a negative errno in case of an error, 0 if success. + * -EINVAL: invalid parameter + * -ENOMEM: Failed to allocate memory + * -EBADMSG: an RDN or extension is not formatted correctly + * -EIO: OpenSSL failed to create the certificate + * -EEXIST: if one of the RDN name entries or extensions to add is a + * duplicate + * -ENOTSUP: the specified digest is not supported + * any other errno from file I/O routines + */ +int ekmf_generate_ss_cert(const struct ekmf_config *config, + const char *subject_rdns[], size_t num_subject_rdns, + bool subject_utf8, const char *renew_cert_filename, + const char *extensions[], size_t num_extensions, + int validity_days, int digest_nid, + struct ekmf_rsa_pss_params *rsa_pss_params, + const char *cert_pem_filename, + const struct ekmf_ext_lib *ext_lib, bool verbose) +{ + const STACK_OF(X509_EXTENSION) *cert_exts = NULL; + unsigned char key_blob[MAX_KEY_BLOB_SIZE]; + size_t key_blob_size = sizeof(key_blob); + X509_NAME *subject_name = NULL; + EVP_PKEY_CTX *pkey_ctx = NULL; + struct private_data private; + EVP_MD_CTX *md_ctx = NULL; + bool pkey_meth = false; + EVP_PKEY *pkey = NULL; + X509 *rcert = NULL; + X509 *cert = NULL; + int rc; + + if (config == NULL || ext_lib == NULL || cert_pem_filename == NULL) + return -EINVAL; + if (config->identity_secure_key == NULL) + return -EINVAL; + if (renew_cert_filename == NULL && + (subject_rdns == NULL || num_subject_rdns == 0)) + return -EINVAL; + if (num_extensions != 0 && extensions == NULL) + return -EINVAL; + + rc = read_key_blob(config->identity_secure_key, key_blob, + &key_blob_size); + if (rc != 0) { + pr_verbose(verbose, "Failed to read identity key from file " + "'%s': %s", config->identity_secure_key, + strerror(-rc)); + goto out; + } + + rc = _ekmf_get_pub_key_as_pkey(key_blob, key_blob_size, &pkey, + rsa_pss_params != NULL, ext_lib, + verbose); + if (rc != 0) { + pr_verbose(verbose, "Failed to get identity key as PKEY from " + "file '%s': %s", config->identity_secure_key, + strerror(-rc)); + goto out; + } + + cert = X509_new(); + if (cert == NULL) { + pr_verbose(verbose, "X509_new failed"); + rc = -ENOMEM; + goto out; + } + + rc = X509_set_version(cert, 2L); + if (rc != 1) { + pr_verbose(verbose, "X509_set_version failed: rc: %d", rc); + rc = -EIO; + goto out; + } + + rc = generate_x509_serial_number(cert, SERIAL_NUMBER_BIT_SIZE); + if (rc != 0) { + pr_verbose(verbose, "Failed to set the serial number: %s", + strerror(-rc)); + goto out; + } + + if (renew_cert_filename != NULL) { + rc = read_x509_certificate(renew_cert_filename, &rcert); + if (rc != 0) { + pr_verbose(verbose, "Failed to open renew cert file " + "'%s': %s", renew_cert_filename, + strerror(-rc)); + goto out; + } + + subject_name = X509_NAME_dup(X509_get_subject_name(rcert)); + cert_exts = X509_get0_extensions(rcert); + } + + if (subject_rdns != NULL && num_subject_rdns > 0) { + rc = build_subject_name(&subject_name, subject_rdns, + num_subject_rdns, subject_utf8); + if (rc != 0) { + pr_verbose(verbose, "Failed to parse the subject name " + "RDSn: %s", strerror(-rc)); + goto out; + } + } + + if (subject_name == NULL) { + rc = -EINVAL; + pr_verbose(verbose, "Subject name can not be empty"); + goto out; + } + + rc = X509_set_subject_name(cert, subject_name); + if (rc != 1) { + rc = -EIO; + pr_verbose(verbose, "Failed to set subject name into cert"); + goto out; + } + + rc = X509_set_issuer_name(cert, subject_name); + if (rc != 1) { + rc = -EIO; + pr_verbose(verbose, "Failed to set issuer name into cert"); + goto out; + } + + rc = build_certificate_extensions(cert, NULL, extensions, + num_extensions, cert_exts); + if (rc != 0) { + pr_verbose(verbose, "Failed to parse the extensions: " + "%s", strerror(-rc)); + goto out; + } + + if (X509_gmtime_adj(X509_getm_notBefore(cert), 0) == NULL) { + rc = -EIO; + pr_verbose(verbose, "Failed to set notBefore time inti cert"); + goto out; + } + + if (X509_time_adj_ex(X509_getm_notAfter(cert), + validity_days, 0, NULL) == NULL) { + rc = -EIO; + pr_verbose(verbose, "Failed to set notAfter time into cert"); + goto out; + } + + rc = X509_set_pubkey(cert, pkey); + if (rc != 1) { + pr_verbose(verbose, "Failed to set the public key"); + rc = -EIO; + goto out; + } + + private.ext_lib = ext_lib; + private.verbose = verbose; + + rc = _ekmf_setup_sign_context(key_blob, key_blob_size, pkey, digest_nid, + rsa_pss_params, &md_ctx, &pkey_ctx, + &private, verbose); + if (rc != 0) + goto out; + pkey_meth = true; + + rc = X509_sign_ctx(cert, md_ctx); + if (rc <= 0) { + pr_verbose(verbose, "Failed to perform the signing operation"); + rc = -EIO; + goto out; + } + + rc = write_x509_certificate(cert_pem_filename, cert); + if (rc != 0) { + pr_verbose(verbose, "Failed to write Certificate to file " + "'%s': %s", cert_pem_filename, strerror(-rc)); + goto out; + } + + if (verbose) { + pr_verbose(verbose, "Self-signed Certificate created:"); + X509_print_fp(stderr, cert); + } + +out: + if (md_ctx != NULL) + EVP_MD_CTX_free(md_ctx); + if (pkey_meth) + cleanup_secure_key_pkey_method(EVP_PKEY_id(pkey)); + if (subject_name != NULL) + X509_NAME_free(subject_name); + if (cert != NULL) + X509_free(cert); + if (rcert != NULL) + X509_free(rcert); + if (pkey != NULL) + EVP_PKEY_free(pkey); + + return rc; +} + /** * Library constructor */ diff --git a/libekmfweb/libekmfweb.map b/libekmfweb/libekmfweb.map index 2b8d6b9c..7ba168eb 100644 --- a/libekmfweb/libekmfweb.map +++ b/libekmfweb/libekmfweb.map @@ -5,5 +5,7 @@ LIBEKMFWEB_1.0 { ekmf_check_login_token; ekmf_generate_identity_key; ekmf_reencipher_identity_key; + ekmf_generate_csr; + ekmf_generate_ss_cert; local: *; }; diff --git a/libekmfweb/utilities.c b/libekmfweb/utilities.c index 528f3a4a..dfa241e8 100644 --- a/libekmfweb/utilities.c +++ b/libekmfweb/utilities.c @@ -14,6 +14,11 @@ #include #include +#include +#include +#include +#include +#include #include "utilities.h" @@ -522,6 +527,268 @@ int ecc_get_brainpool_curve_by_prime_bits(size_t prime_bits) return 0; } +/** + * Calculates the y coordinate of a point on an EC curve using the x coordinate + * and the y bit. x and y must be supplied by the caller with prime_len bytes. + * On return y contains the calculated y coordinate. + * + * @param nid the OpenSSL nid of the ECC curve used + * @param prime_len the length of the prime in bytes. This is also the + * length of the x and y coordinates. + * @param x the x coordinate as big endian binary number in + * prime_len size + * @param y_bit the y-bit to identify which of the two possible + * values for y should be used + * @param y buffer to store the y coordinate as big endian + * binary number in prime_len size. + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: a function parameter is invalid + * -ENOMEM: failed to allocate memory + * -EIO: OpenSSL failed to calculate the y coordinate + * -ENOENT: OpenSSL does not know/support the curve (nid) + */ +int ecc_calculate_y_coordinate(int nid, size_t prime_len, + const unsigned char *x, int y_bit, + unsigned char *y) +{ + EC_GROUP *group = NULL; + EC_POINT *point = NULL; + BIGNUM *bn_x = NULL; + BIGNUM *bn_y = NULL; + BN_CTX *ctx = NULL; + int rc = 0; + + if (x == NULL || y == NULL) + return -EINVAL; + + bn_x = BN_bin2bn(x, prime_len, NULL); + if (bn_x == NULL) { + rc = -EIO; + goto out; + } + + group = EC_GROUP_new_by_curve_name(nid); + if (group == NULL) { + rc = -ENOENT; + goto out; + } + + point = EC_POINT_new(group); + if (point == NULL) { + rc = -EIO; + goto out; + } + + bn_y = BN_new(); + if (bn_y == NULL) { + rc = -ENOMEM; + goto out; + } + + ctx = BN_CTX_new(); + if (ctx == NULL) { + rc = -ENOMEM; + goto out; + } + + if (!EC_POINT_set_compressed_coordinates(group, point, bn_x, + y_bit, ctx)) { + rc = -EIO; + goto out; + } + + if (!EC_POINT_is_on_curve(group, point, ctx)) { + rc = -EIO; + goto out; + } + + if (!EC_POINT_get_affine_coordinates(group, point, bn_x, bn_y, + ctx)) { + rc = -EIO; + goto out; + } + + BN_bn2binpad(bn_y, y, prime_len); + +out: + if (ctx != NULL) + BN_CTX_free(ctx); + if (point != NULL) + EC_POINT_free(point); + if (group != NULL) + EC_GROUP_free(group); + if (bn_x != NULL) + BN_free(bn_x); + if (bn_y != NULL) + BN_free(bn_y); + + return rc; +} + +/** + * Converts an ECC public key given by the nid and the x and y coordinates into + * an OpenSSL PKEY. + * + * @param nid the OpenSSL nid of the ECC curve used + * @param prime_len the length of the prime in bytes. This is also the + * length of the x and y coordinates. + * @param x the x coordinate as big endian binary number in + * prime_len size + * @param y the y coordinate as big endian binary number in + * prime_len size + * @param pkey On return: A PKEY containing the ECC public key. + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: a function parameter is invalid + * -ENOMEM: failed to allocate memory + * -EIO: OpenSSL failed to generate the PKEY + * -ENOENT: OpenSSL does not know/support the curve (nid) + */ +int ecc_pub_key_as_pkey(int nid, size_t prime_len, const unsigned char *x, + const unsigned char *y, EVP_PKEY **pkey) +{ + BIGNUM *bn_x = NULL, *bn_y = NULL; + EC_GROUP *group = NULL; + EC_KEY *ec = NULL; + int rc; + + if (pkey == NULL || x == NULL || y == NULL) + return -EINVAL; + + *pkey = NULL; + + bn_x = BN_bin2bn(x, prime_len, NULL); + bn_y = BN_bin2bn(y, prime_len, NULL); + if (bn_x == NULL || bn_y == NULL) { + rc = -ENOMEM; + goto out; + } + + group = EC_GROUP_new_by_curve_name(nid); + if (group == NULL) { + rc = -ENOENT; + goto out; + } + + ec = EC_KEY_new(); + if (ec == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = EC_KEY_set_group(ec, group); + if (rc != 1) { + rc = -EIO; + goto out; + } + + rc = EC_KEY_set_public_key_affine_coordinates(ec, bn_x, bn_y); + if (rc != 1) { + rc = -EIO; + goto out; + } + + *pkey = EVP_PKEY_new(); + if (*pkey == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = EVP_PKEY_assign_EC_KEY(*pkey, ec); + if (rc != 1) { + rc = -EIO; + goto out; + } + + rc = 0; +out: + if (bn_x != NULL) + BN_free(bn_x); + if (bn_y != NULL) + BN_free(bn_y); + if (group != NULL) + EC_GROUP_free(group); + if (rc != 0 && *pkey != NULL) { + EVP_PKEY_free(*pkey); + *pkey = NULL; + } + return rc; +} + +/** + * Converts an RSA public key given by the modulus and public exponent into + * an OpenSSL PKEY. + * + * @param modulus the modulus as big endian number + * @param modulus_length the length of the modulus in bytes + * @param pub_exp the public exponent as big endian number + * @param pub_exp_length the length of the public exponent in bytes + * @param pkey_type the PKEY type (EVP_PKEY_RSA or EVP_PKEY_RSA_PSS) + * @param pkey On return: A PKEY containing the RSA public key. + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: a function parameter is invalid + * -ENOMEM: failed to allocate memory + * -EIO: OpenSSL failed to generate the PKEY + */ +int rsa_pub_key_as_pkey(const unsigned char *modulus, size_t modulus_length, + const unsigned char *pub_exp, size_t pub_exp_length, + int pkey_type, EVP_PKEY **pkey) +{ + BIGNUM *bn_modulus = NULL, *bn_pub_exp = NULL; + RSA *rsa; + int rc; + + if (pkey == NULL || modulus == NULL || pub_exp == NULL) + return -EINVAL; + + if (pkey_type != EVP_PKEY_RSA && pkey_type != EVP_PKEY_RSA_PSS) + return -EINVAL; + + *pkey = NULL; + + bn_modulus = BN_bin2bn(modulus, modulus_length, NULL); + bn_pub_exp = BN_bin2bn(pub_exp, pub_exp_length, NULL); + if (bn_modulus == NULL || bn_pub_exp == NULL) { + rc = -ENOMEM; + goto out; + } + + rsa = RSA_new(); + if (rsa == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = RSA_set0_key(rsa, bn_modulus, bn_pub_exp, NULL); + if (rc != 1) { + rc = -EIO; + goto out; + } + + *pkey = EVP_PKEY_new(); + if (*pkey == NULL) { + rc = -ENOMEM; + goto out; + } + + rc = EVP_PKEY_assign(*pkey, pkey_type, rsa); + if (rc != 1) { + rc = -EIO; + goto out; + } + + rc = 0; + +out: + if (rc != 0 && bn_modulus != NULL) + BN_free(bn_modulus); + if (rc != 0 && bn_pub_exp != NULL) + BN_free(bn_pub_exp); + + return rc; +} + /** * Write a secure key blob to the specified file. * @@ -644,3 +911,927 @@ int read_x509_certificate(const char *pem_filename, X509 **cert) return 0; } + +/** + * Writes a X.509 certificate to the specified PEM file. + * + * @param pem_filename the name of the PEM file to write to + * @param cert the X.509 certificate object to write + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid parameter + * -EIO: error during writing out the certificate + * any other errno as returned by fopen + */ +int write_x509_certificate(const char *pem_filename, X509 *cert) +{ + FILE *fp; + int rc; + + if (pem_filename == NULL || cert == NULL) + return -EINVAL; + + fp = fopen(pem_filename, "w"); + if (fp == NULL) + return -errno; + + rc = PEM_write_X509(fp, cert); + + fclose(fp); + + if (rc != 1) + return -EIO; + + return 0; +} + +/** + * Writes a X.509 certificate signing request to the specified PEM file. + * + * @param pem_filename the name of the PEM file to write to + * @param req the X.509 request object to write + * @param new_hdr if true, output "NEW" in the PEM header lines + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid parameter + * -EIO: error during writing out the certificate + * any other errno as returned by fopen + */ +int write_x509_request(const char *pem_filename, X509_REQ *req, bool new_hdr) +{ + FILE *fp; + int rc; + + if (pem_filename == NULL || req == NULL) + return -EINVAL; + + fp = fopen(pem_filename, "w"); + if (fp == NULL) + return -errno; + + if (new_hdr) + rc = PEM_write_X509_REQ_NEW(fp, req); + else + rc = PEM_write_X509_REQ(fp, req); + + fclose(fp); + + if (rc != 1) + return -EIO; + + return 0; +} + +/* Secure key PKEY context control */ +#define EVP_PKEY_CTRL_SK_KEY_BLOB 0x10000001 +#define EVP_PKEY_CTRL_SK_SIGN_FUNCTIONS 0x10000002 +#define EVP_PKEY_CTRL_SK_PRIVATE_DATA 0x10000003 + +/* Secure key PKEY context data */ +struct sk_pkey_ctx { + rsa_sign_t rsa_sign; + rsa_pss_sign_t rsa_pss_sign; + ecdsa_sign_t ecdsa_sign; + + unsigned char *key_blob; + size_t key_blob_length; + + const EVP_MD *md; + int rsa_padding; + int rsa_pss_saltlen; + const EVP_MD *rsa_pss_mgfmd; + + void *private; +}; + +/** + * Initialize an secure key PKEY context + * + * @param ctx the PKEY context to initialize + * + * @returns 1 for success, 0 in case of an error + */ +static int sk_pkey_meth_init(EVP_PKEY_CTX *ctx) +{ + struct sk_pkey_ctx *sk_ctx; + EVP_PKEY *pkey; + + pkey = EVP_PKEY_CTX_get0_pkey(ctx); + if (pkey == NULL) + return 0; + + sk_ctx = OPENSSL_zalloc(sizeof(struct sk_pkey_ctx)); + if (sk_ctx == NULL) + return 0; + + if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA_PSS) { + sk_ctx->rsa_pss_saltlen = RSA_PSS_SALTLEN_AUTO; + sk_ctx->rsa_padding = RSA_PKCS1_PSS_PADDING; + } + if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA) + sk_ctx->rsa_padding = RSA_PKCS1_PADDING; + + EVP_PKEY_CTX_set_data(ctx, sk_ctx); + + return 1; +} + +/** + * Cleanup an secure key PKEY context + * + * @param ctx the PKEY context to to clean + */ +static void sk_pkey_meth_cleanup(EVP_PKEY_CTX *ctx) +{ + struct sk_pkey_ctx *sk_ctx; + + sk_ctx = EVP_PKEY_CTX_get_data(ctx); + if (sk_ctx != NULL) { + OPENSSL_free(sk_ctx); + EVP_PKEY_CTX_set_data(ctx, NULL); + } +} + +/** + * Copy an secure key PKEY context + * + * @param dst the source PKEY context to copy from + * @param src the destinationPKEY context to copy to + * + * @returns 1 for success, 0 in case of an error + */ +static int sk_pkey_meth_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) +{ + struct sk_pkey_ctx *sk_ctx_src, *sk_ctx_dst; + + if (sk_pkey_meth_init(dst) != 1) + return 0; + + sk_ctx_src = EVP_PKEY_CTX_get_data(src); + if (sk_ctx_src == NULL) + return 0; + + sk_ctx_dst = EVP_PKEY_CTX_get_data(dst); + if (sk_ctx_dst == NULL) + return 0; + + sk_ctx_dst->rsa_sign = sk_ctx_src->rsa_sign; + sk_ctx_dst->rsa_pss_sign = sk_ctx_src->rsa_pss_sign; + sk_ctx_dst->ecdsa_sign = sk_ctx_src->ecdsa_sign; + sk_ctx_dst->key_blob = sk_ctx_src->key_blob; + sk_ctx_dst->key_blob_length = sk_ctx_src->key_blob_length; + sk_ctx_dst->md = sk_ctx_src->md; + sk_ctx_dst->rsa_pss_saltlen = sk_ctx_src->rsa_pss_saltlen; + sk_ctx_dst->rsa_padding = sk_ctx_src->rsa_padding; + sk_ctx_dst->rsa_pss_mgfmd = sk_ctx_src->rsa_pss_mgfmd; + sk_ctx_dst->private = sk_ctx_src->private; + + return 1; +} + +/** + * Perform a sign operation with the secure key PKEY context. + * The data to be signed has already been hashed. + * + * The key to perform the sign operation with is available in the PKEY context. + * For this secure key case, the pkey as well as the secure key blob is + * available. + * + * @param ctx the PKEY context to sign with + * @param sig the buffer to store the signature + * @param siglen On input: the size of the signature buffer + * On return: the size of the signature + * @param tbs the data to be signed + * @param tbslen the size of the data to be signed + * + * @returns 1 for success, 0 in case of an error + */ +static int sk_pkey_meth_sign(EVP_PKEY_CTX *ctx, unsigned char *sig, + size_t *siglen, const unsigned char *tbs, + size_t tbslen) +{ + int rc, md_type, mgf_md_type, saltlen, max_saltlen, hlen; + struct sk_pkey_ctx *sk_ctx; + EVP_PKEY *pkey; + + sk_ctx = EVP_PKEY_CTX_get_data(ctx); + if (sk_ctx == NULL) + return 0; + + pkey = EVP_PKEY_CTX_get0_pkey(ctx); + if (pkey == NULL) + return 0; + + md_type = sk_ctx->md != NULL ? EVP_MD_type(sk_ctx->md) : NID_sha1; + + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + if (sk_ctx->rsa_sign == NULL) + return 0; + rc = sk_ctx->rsa_sign(sk_ctx->key_blob, sk_ctx->key_blob_length, + sig, siglen, tbs, tbslen, + sk_ctx->rsa_padding, md_type, + sk_ctx->private); + break; + + case EVP_PKEY_RSA_PSS: + if (sk_ctx->rsa_pss_sign == NULL) + return 0; + + if (sk_ctx->md != NULL) + hlen = EVP_MD_size(sk_ctx->md); + else + hlen = SHA_DIGEST_LENGTH; + + if (sk_ctx->rsa_pss_mgfmd != NULL) { + mgf_md_type = EVP_MD_type(sk_ctx->rsa_pss_mgfmd); + hlen = EVP_MD_size(sk_ctx->rsa_pss_mgfmd); + } else { + mgf_md_type = md_type; + } + + /* + * We should be using RSA_bits(EVP_PKEY_get0_RSA(pkey)) here, + * but EVP_PKEY_get0_RSA(pkey) does not work with PKEY/type + * EVP_PKEY_RSA_PSS on older OpenSSL versions, so we fall back + * on EVP_PKEY_bits in this case. + */ + max_saltlen = (EVP_PKEY_get0_RSA(pkey) != NULL ? + RSA_bits(EVP_PKEY_get0_RSA(pkey)) : + EVP_PKEY_bits(pkey)) / 8 - hlen - 2; + + switch (sk_ctx->rsa_pss_saltlen) { + case RSA_PSS_SALTLEN_DIGEST: + saltlen = hlen; + break; + case RSA_PSS_SALTLEN_AUTO: + case RSA_PSS_SALTLEN_MAX: + saltlen = max_saltlen; + break; + default: + saltlen = sk_ctx->rsa_pss_saltlen; + break; + } + + if (saltlen > max_saltlen || saltlen < 0) + return 0; + + rc = sk_ctx->rsa_pss_sign(sk_ctx->key_blob, + sk_ctx->key_blob_length, + sig, siglen, tbs, tbslen, + md_type, mgf_md_type, saltlen, + sk_ctx->private); + break; + + case EVP_PKEY_EC: + if (sk_ctx->ecdsa_sign == NULL) + return 0; + + rc = sk_ctx->ecdsa_sign(sk_ctx->key_blob, + sk_ctx->key_blob_length, sig, siglen, + tbs, tbslen, md_type, sk_ctx->private); + break; + + default: + rc = -1; + } + + return rc == 0 ? 1 : 0; +} + +/** + * Control options of the secure key PKEY context + * + * Besides some standard controls, the following secure key PKEY context + * specific controls are available: + * EVP_PKEY_CTRL_SK_KEY_BLOB set the secure key blob into the context + * EVP_PKEY_CTRL_SK_SIGN_FUNCTIONS set the sign functions for the context + * EVP_PKEY_CTRL_SK_PRIVATE_DATA set the private data to the context + * + * @param ctx the PKEY context to control + * @param type the control type + * @param p1 an integer option + * @param p2 a pointer option + * + * @returns 1 for success, 0 in case of an error, -2 if the control is not + * supported by the context + */ +static int sk_pkey_meth_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2) +{ + struct sk_pkey_sign_func *func; + struct sk_pkey_ctx *sk_ctx; + EVP_PKEY *pkey; + int md_type; + + sk_ctx = EVP_PKEY_CTX_get_data(ctx); + if (sk_ctx == NULL) + return 0; + + pkey = EVP_PKEY_CTX_get0_pkey(ctx); + if (pkey == NULL) + return 0; + + switch (type) { + case EVP_PKEY_CTRL_SK_KEY_BLOB: + sk_ctx->key_blob = p2; + sk_ctx->key_blob_length = p1; + break; + + case EVP_PKEY_CTRL_SK_SIGN_FUNCTIONS: + func = (struct sk_pkey_sign_func *)p2; + sk_ctx->rsa_sign = func->rsa_sign; + sk_ctx->rsa_pss_sign = func->rsa_pss_sign; + sk_ctx->ecdsa_sign = func->ecdsa_sign; + break; + + case EVP_PKEY_CTRL_SK_PRIVATE_DATA: + sk_ctx->private = p2; + break; + + case EVP_PKEY_CTRL_MD: + md_type = EVP_MD_type((const EVP_MD *)p2); + if (md_type != NID_sha1 && + md_type != NID_ecdsa_with_SHA1 && + md_type != NID_sha224 && + md_type != NID_sha256 && + md_type != NID_sha384 && + md_type != NID_sha512 && + md_type != NID_sha3_224 && + md_type != NID_sha3_256 && + md_type != NID_sha3_384 && + md_type != NID_sha3_512) + return 0; + sk_ctx->md = p2; + break; + + case EVP_PKEY_CTRL_GET_MD: + *(const EVP_MD **)p2 = sk_ctx->md; + break; + + case EVP_PKEY_CTRL_RSA_PADDING: + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_EC: + return -2; + case EVP_PKEY_RSA: + break; + case EVP_PKEY_RSA_PSS: + if (p1 != RSA_PKCS1_PSS_PADDING) + return -2; + break; + } + sk_ctx->rsa_padding = p1; + break; + + case EVP_PKEY_CTRL_GET_RSA_PADDING: + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_EC: + return -1; + case EVP_PKEY_RSA: + case EVP_PKEY_RSA_PSS: + *(int *)p2 = sk_ctx->rsa_padding; + break; + } + break; + + case EVP_PKEY_CTRL_RSA_PSS_SALTLEN: + if (sk_ctx->rsa_padding != RSA_PKCS1_PSS_PADDING) + return -2; + if (p1 < RSA_PSS_SALTLEN_MAX) + return -2; + if (p1 == RSA_PSS_SALTLEN_MAX) + sk_ctx->rsa_pss_saltlen = RSA_PSS_SALTLEN_MAX_SIGN; + else + sk_ctx->rsa_pss_saltlen = p1; + break; + + case EVP_PKEY_CTRL_GET_RSA_PSS_SALTLEN: + if (sk_ctx->rsa_padding != RSA_PKCS1_PSS_PADDING) + return -2; + *(int *)p2 = sk_ctx->rsa_pss_saltlen; + break; + + case EVP_PKEY_CTRL_RSA_MGF1_MD: + if (sk_ctx->rsa_padding != RSA_PKCS1_PSS_PADDING) + return -2; + sk_ctx->rsa_pss_mgfmd = p2; + break; + + case EVP_PKEY_CTRL_GET_RSA_MGF1_MD: + if (sk_ctx->rsa_padding != RSA_PKCS1_PSS_PADDING) + return -2; + *(const EVP_MD **)p2 = sk_ctx->rsa_pss_mgfmd != NULL ? + sk_ctx->rsa_pss_mgfmd : sk_ctx->md; + break; + + case EVP_PKEY_CTRL_DIGESTINIT: + case EVP_PKEY_CTRL_PEER_KEY: + case EVP_PKEY_CTRL_PKCS7_SIGN: + case EVP_PKEY_CTRL_CMS_SIGN: + break; + + default: + return -2; + } + + return 1; +} + +/** + * Sets up a secure key PKEY method to handle PKEY sign operations of the + * specified PKEY id (type) using secure key functions, instead of the default + * ones. + * + * Note: This should be done only right before the sign operation is used, and + * should be cleaned up using cleanup_secure_key_pkey_method() right after the + * sign operation is finished, to not interfere with other PKEY usage. + * + * @param pkey_id the PKEY id (type) to setup special handling + * for. Use EVP_PKEY_id() to get the id of an existing + * pkey. + * + * @returns zero for success, a negative errno in case of an error: + * -ENOMEM: failed to allocate memory + * -EIO: OpenSSL failed to setup the method + */ +int setup_secure_key_pkey_method(int pkey_id) +{ + EVP_PKEY_METHOD *pkey_meth; + + pkey_meth = EVP_PKEY_meth_new(pkey_id, 0); + if (pkey_meth == NULL) + return -ENOMEM; + + EVP_PKEY_meth_set_init(pkey_meth, sk_pkey_meth_init); + EVP_PKEY_meth_set_cleanup(pkey_meth, sk_pkey_meth_cleanup); + EVP_PKEY_meth_set_copy(pkey_meth, sk_pkey_meth_copy); + EVP_PKEY_meth_set_ctrl(pkey_meth, sk_pkey_meth_ctrl, NULL); + EVP_PKEY_meth_set_sign(pkey_meth, NULL, sk_pkey_meth_sign); + + if (EVP_PKEY_meth_add0(pkey_meth) != 1) + return -EIO; + + return 0; +} + +/** + * Cleans up the secure key PKEY method to handle PKEY sign operations of the + * specified PKEY id (type) using secure key functions. + * + * Note: Only call this function of you have previously setup the PKEY method + * using setup_secure_key_pkey_method(). + * + * @param pkey_id the PKEY id (type) to setup special handling + * for. Use EVP_PKEY_id() to get the id of an existing + * pkey. + * + * @returns zero for success, a negative errno in case of an error: + * -ENOENT: method not found + * -EIO: OpenSSL failed to cleanup the method + */ +int cleanup_secure_key_pkey_method(int pkey_id) +{ + const EVP_PKEY_METHOD *pkey_meth; + + pkey_meth = EVP_PKEY_meth_find(pkey_id); + if (pkey_meth == NULL) + return -ENOENT; + + if (EVP_PKEY_meth_remove(pkey_meth) != 1) + return -EIO; + + EVP_PKEY_meth_free((EVP_PKEY_METHOD *)pkey_meth); + + return 0; +} + +/** + * Sets up a secure key PKEY context to handle PKEY sign operations using the + * specified secure key blob, and secure key sign functions. The secure key blob + * must match the PKEY that was used to create the context with. The pkey + * contains the public key parts only, the secure key blob contains the + * private (and possibly public) key parts of the same key. + * + * Note: Only call this function of you have previously setup the PKEY method + * using setup_secure_key_pkey_method(). + * + * @param pkey_ctx the PKEY context to setup special handling for + * @param key_blob the secure key blob to sign with + * @param key_blob_len the size of the key bob in bytes + * @param sign_funcs secure key specific sign functions + * @param private a pointer passed as is to the sign functions + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid argument + * -EIO: OpenSSL failed to setup the context + */ +int setup_secure_key_pkey_context(EVP_PKEY_CTX *pkey_ctx, + const unsigned char *key_blob, + size_t key_blob_len, + struct sk_pkey_sign_func *sign_funcs, + void *private) +{ + int rc; + + if (pkey_ctx == NULL || sign_funcs == NULL) + return -EINVAL; + + rc = EVP_PKEY_CTX_ctrl(pkey_ctx, -1, -1, EVP_PKEY_CTRL_SK_KEY_BLOB, + key_blob_len, (void *)key_blob); + if (rc != 1) + return -EIO; + + rc = EVP_PKEY_CTX_ctrl(pkey_ctx, -1, -1, + EVP_PKEY_CTRL_SK_SIGN_FUNCTIONS, + 0, sign_funcs); + if (rc != 1) + return -EIO; + + rc = EVP_PKEY_CTX_ctrl(pkey_ctx, -1, -1, EVP_PKEY_CTRL_SK_PRIVATE_DATA, + 0, private); + if (rc != 1) + return -EIO; + + return 0; +} + +/** + * Sets up a PKEY context with RSA-PSS parameters. + * + * @param pkey_ctx the PKEY context to setup special handling for + * @param rsa_pss_params the RSA-PSS paramaters + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid argument + * -EIO: OpenSSL failed to setup the context + */ +int setup_rsa_pss_pkey_context(EVP_PKEY_CTX *pkey_ctx, + struct ekmf_rsa_pss_params *rsa_pss_params) +{ + const EVP_MD *mgf_md; + int rc; + + if (pkey_ctx == NULL || rsa_pss_params == NULL) + return -EINVAL; + + rc = EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, + RSA_PKCS1_PSS_PADDING); + if (rc != 1) + return -EIO; + + rc = EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, + rsa_pss_params->salt_len); + if (rc != 1) + return -EIO; + + if (rsa_pss_params->mgf_digest_nid != 0) { + mgf_md = EVP_get_digestbynid( + rsa_pss_params->mgf_digest_nid); + if (mgf_md == NULL) + return -ENOENT; + + rc = EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf_md); + if (rc != 1) + return -EIO; + } + + return 0; +} + +/** + * Checks if an exact duplicate of the name entry is part of the name already. + */ +static bool is_duplicate_name_entry(X509_NAME *name, X509_NAME_ENTRY *entry) +{ + X509_NAME_ENTRY *ne; + int count, i; + + count = X509_NAME_entry_count(name); + for (i = 0; i < count; i++) { + ne = X509_NAME_get_entry(name, i); + if (ne == NULL) + break; + + if (OBJ_cmp(X509_NAME_ENTRY_get_object(entry), + X509_NAME_ENTRY_get_object(ne)) == 0 && + ASN1_STRING_cmp(X509_NAME_ENTRY_get_data(entry), + X509_NAME_ENTRY_get_data(ne)) == 0) + return true; + } + + return false; +} + +/** + * Parse an array of relative distinguished names and builds an X.509 subject + * name. The RDNs are created with type MBSTRING_ASC, unless utf8 is requested, + * then they are created with MBSTRING_UTF8. + * To create a multiple-RDS name, prepend the RDS to add to the previous RDS + * with a '+' character. + * + * @param name the X.509 name created. If *name is not NULL, then + * the RDNs are added to the existing X.509 name. + * @param rdns an array of strings, each string representing an + * RDN in the form '[+]type=value'. If the type is + * prepended with a '+', then this RDN is added to the + * previous one. + * @param num_rdns number of elements in the array. + * @param utf8 if true, RDNs of type MBSTRING_UTF8 are created, + * otherwise type is MBSTRING_ASC is used. + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid parameter + * -EBADMSG: an RDN is not formatted correctly + * -EIO: OpenSSL failed to create an X.509 name entry + * -EEXIST: if one of the name entries to add is a duplicate + */ +int build_subject_name(X509_NAME **name, const char *rdns[], size_t num_rdns, + bool utf8) +{ + char *rdn, *type, *value; + X509_NAME_ENTRY *ne; + X509_NAME *n; + int rc = 0; + bool multi; + size_t i; + + if (name == NULL || rdns == NULL) + return -EINVAL; + + if (*name != NULL) + n = *name; + else + n = X509_NAME_new(); + if (n == NULL) + return -ENOMEM; + + for (i = 0; i < num_rdns; i++) { + if (rdns[i] == NULL) { + rc = -EINVAL; + break; + } + + rdn = strdup(rdns[i]); + if (rdn == NULL) { + rc = -ENOMEM; + break; + } + + multi = (rdn[0] == '+'); + type = &rdn[multi ? 1 : 0]; + + for (value = type; *value != '=' && *value != '\0'; value++) + ; + if (*value != '=') { + rc = -EBADMSG; + free(rdn); + break; + } + *value = '\0'; + value++; + + ne = X509_NAME_ENTRY_create_by_txt(NULL, type, + utf8 ? MBSTRING_UTF8 : + MBSTRING_ASC, + (unsigned char *)value, -1); + if (ne == NULL) { + rc = -EBADMSG; + free(rdn); + break; + } + + if (is_duplicate_name_entry(n, ne)) { + rc = -EEXIST; + X509_NAME_ENTRY_free(ne); + free(rdn); + break; + } + + rc = X509_NAME_add_entry(n, ne, -1, multi ? -1 : 0); + + free(rdn); + X509_NAME_ENTRY_free(ne); + + if (rc != 1) { + rc = -EIO; + break; + } + rc = 0; + } + + if (rc == 0) + *name = n; + else if (*name == NULL) + X509_NAME_free(n); + + return rc; +} + +/** + * Compares X509 Extensions by their nid + */ +static int X509_EXTENSION_compfunc(const X509_EXTENSION * const* a, + const X509_EXTENSION * const* b) +{ + + return (OBJ_obj2nid(X509_EXTENSION_get_object((X509_EXTENSION *)a)) - + OBJ_obj2nid(X509_EXTENSION_get_object((X509_EXTENSION *)b))); +} + +/** + * Parse an array of textual X.509 certificate extensions and adds them to + * either an X.509 certificate signing request, or an X.509 certificate. + * + * When adding extensions, a check is performed if an extension with the same + * nid is already added. If so, a duplicate extension is not added, even if + * its value is different from the existing one. + * + * @param cert the X.509 certificate to add the extensions to. + * Either req or cert can be specified. + * @param req the X.509 certificate signing request to add the + * extensions to. Either req or cert can be specified. + * @param exts an array of strings, each string representing an + * certificate extension in the form 'type=value'. + * can be NULL if num_exts is zero. + * @param num_exts number of elements in the array. + * @param addl_exts a stack of extensions to add (can be NULL) + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid parameter + * -EBADMSG: an extension is not formatted correctly + * -EIO: OpenSSL failed to create an X.509 extension + * -EEXIST: if one of the extensions to add is a duplicate + */ +int build_certificate_extensions(X509 *cert, X509_REQ *req, + const char *exts[], size_t num_exts, + const STACK_OF(X509_EXTENSION) *addl_exts) +{ + STACK_OF(X509_EXTENSION) *sk_ext; + char *ext, *type, *value; + X509V3_CTX x509v3_ctx; + int count, k, rc = 0; + X509_EXTENSION *ex; + size_t i; + + if (num_exts > 0 && exts == NULL) + return -EINVAL; + if (cert == NULL && req == NULL) + return -EINVAL; + if (cert != NULL && req != NULL) + return -EINVAL; + + sk_ext = sk_X509_EXTENSION_new_null(); + if (sk_ext == NULL) + return -ENOMEM; + + sk_X509_EXTENSION_set_cmp_func(sk_ext, X509_EXTENSION_compfunc); + + for (i = 0; exts != NULL && i < num_exts; i++) { + if (exts[i] == NULL) { + rc = -EINVAL; + break; + } + + ext = strdup(exts[i]); + if (ext == NULL) { + rc = -ENOMEM; + break; + } + + type = &ext[0]; + + for (value = type; *value != '=' && *value != '\0'; value++) + ; + if (*value != '=') { + rc = -EBADMSG; + free(ext); + break; + } + *value = '\0'; + value++; + + rc = -EBADMSG; + ex = X509V3_EXT_conf(NULL, NULL, type, value); + if (ex != NULL) { + if (sk_X509_EXTENSION_find(sk_ext, ex) >= 0) { + rc = -EEXIST; + free(ext); + break; + } + + rc = sk_X509_EXTENSION_push(sk_ext, ex); + if (rc < 1) { + rc = -EIO; + free(ext); + break; + } + rc = 0; + } + + free(ext); + } + + if (rc != 0) + goto out; + + if (addl_exts != NULL) { + count = sk_X509_EXTENSION_num(addl_exts); + for (k = 0; k < count; k++) { + ex = sk_X509_EXTENSION_value(addl_exts, k); + if (ex != NULL) { + if (sk_X509_EXTENSION_find(sk_ext, ex) >= 0) { + rc = -EEXIST; + break; + } + + rc = sk_X509_EXTENSION_push(sk_ext, + X509_EXTENSION_dup(ex)); + if (rc < 1) { + rc = -EIO; + break; + } + rc = 0; + } + } + } + + if (rc != 0) + goto out; + + if (req != NULL && sk_X509_EXTENSION_num(sk_ext) > 0) { + if (X509_REQ_add_extensions(req, sk_ext) != 1) + rc = -EIO; + sk_X509_EXTENSION_pop_free(sk_ext, X509_EXTENSION_free); + sk_ext = NULL; + goto out; + } + + if (cert != NULL && sk_X509_EXTENSION_num(sk_ext) > 0) { + X509V3_set_ctx_nodb(&x509v3_ctx); + X509V3_set_ctx(&x509v3_ctx, cert, cert, NULL, NULL, 0); + + rc = 0; + while ((ex = sk_X509_EXTENSION_pop(sk_ext)) != NULL) { + if (rc == 0) { + if (X509_add_ext(cert, ex, -1) != 1) + rc = -EIO; + } + X509_EXTENSION_free(ex); + } + } + +out: + if (sk_ext != NULL) + sk_X509_EXTENSION_pop_free(sk_ext, X509_EXTENSION_free); + return rc; +} + +/** + * Generates a serial number of a specified bit size by random and sets it + * as serial number into the certificate. + * + * @param cert the certificate to set the serial number for + * @param sn_bit_size the size of the serial number in bits + * + * @returns zero for success, a negative errno in case of an error: + * -EINVAL: invalid parameter + * -EIO: error during serial number generation + */ +int generate_x509_serial_number(X509 *cert, size_t sn_bit_size) +{ + ASN1_INTEGER *ai = NULL; + BIGNUM *bn = NULL; + int rc; + + if (cert == NULL) + return -EINVAL; + + bn = BN_new(); + if (bn == NULL) + return -ENOMEM; + + rc = BN_rand(bn, sn_bit_size, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY); + if (rc != 1) { + rc = -EIO; + goto out; + } + + ai = X509_get_serialNumber(cert); + if (ai == NULL) { + rc = -EIO; + goto out; + } + + if (BN_to_ASN1_INTEGER(bn, ai) == NULL) { + rc = -EIO; + goto out; + } + + rc = 0; + +out: + if (bn != NULL) + BN_free(bn); + + return rc; +} + diff --git a/libekmfweb/utilities.h b/libekmfweb/utilities.h index 1bff2de3..3c01bc97 100644 --- a/libekmfweb/utilities.h +++ b/libekmfweb/utilities.h @@ -15,9 +15,12 @@ #include #include +#include #include +#include "ekmfweb/ekmfweb.h" + int decode_base64url(unsigned char *output, size_t *outlen, const char *input, size_t inlen); @@ -37,6 +40,17 @@ int ecc_get_curve_by_id(const char *curve_id); int ecc_get_prime_curve_by_prime_bits(size_t prime_bits); int ecc_get_brainpool_curve_by_prime_bits(size_t prime_bits); +int ecc_calculate_y_coordinate(int nid, size_t prime_len, + const unsigned char *x, int y_bit, + unsigned char *y); + +int ecc_pub_key_as_pkey(int nid, size_t prime_len, const unsigned char *x, + const unsigned char *y, EVP_PKEY **pkey); + +int rsa_pub_key_as_pkey(const unsigned char *modulus, size_t modulus_length, + const unsigned char *pub_exp, size_t pub_exp_length, + int pkey_type, EVP_PKEY **pkey); + int write_key_blob(const char *filename, unsigned char *key_blob, size_t key_blob_len); @@ -45,4 +59,49 @@ int read_key_blob(const char *filename, unsigned char *key_blob, int read_x509_certificate(const char *pem_filename, X509 **cert); +int write_x509_certificate(const char *pem_filename, X509 *cert); + +int write_x509_request(const char *pem_filename, X509_REQ *req, bool new_hdr); + +typedef int (*rsa_sign_t)(const unsigned char *key_blob, size_t key_blob_length, + unsigned char *sig, size_t *siglen, + const unsigned char *tbs, size_t tbslen, + int padding_type, int md_nid, + void *private); +typedef int (*rsa_pss_sign_t)(const unsigned char *key_blob, + size_t key_blob_length, unsigned char *sig, + size_t *siglen, const unsigned char *tbs, + size_t tbslen, int md_nid, int mfgmd_nid, + int saltlen, void *private); +typedef int (*ecdsa_sign_t)(const unsigned char *key_blob, + size_t key_blob_length, unsigned char *sig, + size_t *siglen, const unsigned char *tbs, + size_t tbslen, int md_nid, void *private); + +struct sk_pkey_sign_func { + rsa_sign_t rsa_sign; + rsa_pss_sign_t rsa_pss_sign; + ecdsa_sign_t ecdsa_sign; +}; + +int setup_secure_key_pkey_method(int pkey_id); +int cleanup_secure_key_pkey_method(int pkey_id); +int setup_secure_key_pkey_context(EVP_PKEY_CTX *pkey_ctx, + const unsigned char *key_blob, + size_t key_blob_len, + struct sk_pkey_sign_func *sign_funcs, + void *private); + +int setup_rsa_pss_pkey_context(EVP_PKEY_CTX *pkey_ctx, + struct ekmf_rsa_pss_params *rsa_pss_params); + +int build_subject_name(X509_NAME **name, const char *rdns[], size_t num_rdns, + bool utf8); + +int build_certificate_extensions(X509 *cert, X509_REQ *req, + const char *exts[], size_t num_exts, + const STACK_OF(X509_EXTENSION) *addl_exts); + +int generate_x509_serial_number(X509 *cert, size_t sn_bit_size); + #endif