Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for PKCS12_set_mac #2128

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 61 additions & 36 deletions crypto/pkcs8/pkcs12_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ static bssl::UniquePtr<X509> MakeTestCert(EVP_PKEY *key) {
return x509;
}

static bool PKCS12CreateVector(std::vector<uint8_t> *out, EVP_PKEY *pkey,
static bool PKCS12CreateVector(bssl::UniquePtr<PKCS12> *p12, EVP_PKEY *pkey,
const std::vector<X509 *> &certs) {
bssl::UniquePtr<STACK_OF(X509)> chain(sk_X509_new_null());
if (!chain) {
Expand All @@ -546,35 +546,21 @@ static bool PKCS12CreateVector(std::vector<uint8_t> *out, EVP_PKEY *pkey,
}
}

bssl::UniquePtr<PKCS12> p12(PKCS12_create(kPassword, nullptr /* name */, pkey,
nullptr /* cert */, chain.get(), 0,
0, 0, 0, 0));
if (!p12) {
p12->reset(PKCS12_create(kPassword, nullptr /* name */, pkey,
nullptr /* cert */, chain.get(), 0, 0, 0, 0, 0));
if (!p12->get()) {
return false;
}

int len = i2d_PKCS12(p12.get(), nullptr);
if (len < 0) {
return false;
}
out->resize(static_cast<size_t>(len));
uint8_t *ptr = out->data();
return i2d_PKCS12(p12.get(), &ptr) == len;
return true;
}

static void ExpectPKCS12Parse(bssl::Span<const uint8_t> in,
EVP_PKEY *expect_key, X509 *expect_cert,
static void ExpectPKCS12Parse(PKCS12 *p12, EVP_PKEY *expect_key,
X509 *expect_cert,
const std::vector<X509 *> &expect_ca_certs) {
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(in.data(), in.size()));
ASSERT_TRUE(bio);

bssl::UniquePtr<PKCS12> p12(d2i_PKCS12_bio(bio.get(), nullptr));
ASSERT_TRUE(p12);

EVP_PKEY *key = nullptr;
X509 *cert = nullptr;
STACK_OF(X509) *ca_certs = nullptr;
ASSERT_TRUE(PKCS12_parse(p12.get(), kPassword, &key, &cert, &ca_certs));
ASSERT_TRUE(PKCS12_parse(p12, kPassword, &key, &cert, &ca_certs));

bssl::UniquePtr<EVP_PKEY> delete_key(key);
bssl::UniquePtr<X509> delete_cert(cert);
Expand Down Expand Up @@ -618,34 +604,37 @@ TEST(PKCS12Test, Order) {
ASSERT_TRUE(cert3);

// PKCS12_parse uses the key to select the main certificate.
std::vector<uint8_t> p12;
bssl::UniquePtr<PKCS12> p12;
ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(),
{cert1.get(), cert2.get(), cert3.get()}));
ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert2.get(), cert3.get()});
ExpectPKCS12Parse(p12.get(), key1.get(), cert1.get(),
{cert2.get(), cert3.get()});

ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(),
{cert3.get(), cert1.get(), cert2.get()}));
ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert3.get(), cert2.get()});
ExpectPKCS12Parse(p12.get(), key1.get(), cert1.get(),
{cert3.get(), cert2.get()});

ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(),
{cert2.get(), cert3.get(), cert1.get()}));
ExpectPKCS12Parse(p12, key1.get(), cert1.get(), {cert2.get(), cert3.get()});
ExpectPKCS12Parse(p12.get(), key1.get(), cert1.get(),
{cert2.get(), cert3.get()});

// In case of duplicates, the last one is selected. (It is unlikely anything
// depends on which is selected, but we match OpenSSL.)
ASSERT_TRUE(
PKCS12CreateVector(&p12, key1.get(), {cert1.get(), cert1b.get()}));
ExpectPKCS12Parse(p12, key1.get(), cert1b.get(), {cert1.get()});
ExpectPKCS12Parse(p12.get(), key1.get(), cert1b.get(), {cert1.get()});

// If there is no key, all certificates are returned as "CA" certificates.
ASSERT_TRUE(PKCS12CreateVector(&p12, nullptr,
{cert1.get(), cert2.get(), cert3.get()}));
ExpectPKCS12Parse(p12, nullptr, nullptr,
ExpectPKCS12Parse(p12.get(), nullptr, nullptr,
{cert1.get(), cert2.get(), cert3.get()});

// The same happens if there is a key, but it does not match any certificate.
ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(), {cert2.get(), cert3.get()}));
ExpectPKCS12Parse(p12, key1.get(), nullptr, {cert2.get(), cert3.get()});
ExpectPKCS12Parse(p12.get(), key1.get(), nullptr, {cert2.get(), cert3.get()});
}

TEST(PKCS12Test, CreateWithAlias) {
Expand All @@ -663,13 +652,8 @@ TEST(PKCS12Test, CreateWithAlias) {
ASSERT_EQ(res, 1);

std::vector<X509 *> certs = {cert1.get(), cert2.get()};
std::vector<uint8_t> der;
ASSERT_TRUE(PKCS12CreateVector(&der, key.get(), certs));

bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(der.data(), der.size()));
ASSERT_TRUE(bio);
bssl::UniquePtr<PKCS12> p12(d2i_PKCS12_bio(bio.get(), nullptr));
ASSERT_TRUE(p12);
bssl::UniquePtr<PKCS12> p12;
ASSERT_TRUE(PKCS12CreateVector(&p12, key.get(), certs));

EVP_PKEY *parsed_key = nullptr;
X509 *parsed_cert = nullptr;
Expand All @@ -695,3 +679,44 @@ TEST(PKCS12Test, BasicAlloc) {
bssl::UniquePtr<PKCS12> p12(PKCS12_new());
ASSERT_TRUE(p12);
}

TEST(PKCS12Test, SetMac) {
bssl::UniquePtr<EVP_PKEY> key1 = MakeTestKey();
ASSERT_TRUE(key1);
bssl::UniquePtr<X509> cert1 = MakeTestCert(key1.get());
ASSERT_TRUE(cert1);
bssl::UniquePtr<X509> cert1b = MakeTestCert(key1.get());
ASSERT_TRUE(cert1b);
bssl::UniquePtr<EVP_PKEY> key2 = MakeTestKey();
ASSERT_TRUE(key2);
bssl::UniquePtr<X509> cert2 = MakeTestCert(key2.get());
ASSERT_TRUE(cert2);
bssl::UniquePtr<EVP_PKEY> key3 = MakeTestKey();
ASSERT_TRUE(key3);
bssl::UniquePtr<X509> cert3 = MakeTestCert(key3.get());
ASSERT_TRUE(cert3);

// PKCS12_parse uses the key to select the main certificate.
bssl::UniquePtr<PKCS12> p12;
ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(),
{cert1.get(), cert2.get(), cert3.get()}));
EXPECT_TRUE(PKCS12_set_mac(p12.get(), kPassword, strlen(kPassword), nullptr,
0, 0, nullptr));
ExpectPKCS12Parse(p12.get(), key1.get(), cert1.get(),
{cert2.get(), cert3.get()});

ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(),
{cert3.get(), cert1.get(), cert2.get()}));
EXPECT_TRUE(PKCS12_set_mac(p12.get(), kPassword, strlen(kPassword), nullptr,
0, 0, nullptr));
ExpectPKCS12Parse(p12.get(), key1.get(), cert1.get(),
{cert3.get(), cert2.get()});

ASSERT_TRUE(PKCS12CreateVector(&p12, key1.get(),
{cert2.get(), cert3.get(), cert1.get()}));
EXPECT_TRUE(PKCS12_set_mac(p12.get(), kPassword, strlen(kPassword), nullptr,
0, 0, nullptr));
ExpectPKCS12Parse(p12.get(), key1.get(), cert1.get(),
{cert2.get(), cert3.get()});
}

163 changes: 137 additions & 26 deletions crypto/pkcs8/pkcs8_x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,44 @@ static int add_encrypted_data(CBB *out, int pbe_nid, const char *password,
return ret;
}

static int pkcs12_gen_and_write_mac(CBB *out_pfx, const uint8_t *auth_safe_data,
size_t auth_safe_data_len,
const char *password, size_t password_len,
uint8_t *mac_salt, size_t salt_len,
int mac_iterations, const EVP_MD *md) {
int ret = 0;
uint8_t mac_key[EVP_MAX_MD_SIZE];
uint8_t mac[EVP_MAX_MD_SIZE];
unsigned mac_len;
if (!pkcs12_key_gen(password, password_len, mac_salt, salt_len, PKCS12_MAC_ID,
mac_iterations, EVP_MD_size(md), mac_key, md) ||
!HMAC(md, mac_key, EVP_MD_size(md), auth_safe_data, auth_safe_data_len,
mac, &mac_len)) {
goto out;
}

CBB mac_data, digest_info, mac_cbb, mac_salt_cbb;
if (!CBB_add_asn1(out_pfx, &mac_data, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&mac_data, &digest_info, CBS_ASN1_SEQUENCE) ||
!EVP_marshal_digest_algorithm(&digest_info, md) ||
!CBB_add_asn1(&digest_info, &mac_cbb, CBS_ASN1_OCTETSTRING) ||
!CBB_add_bytes(&mac_cbb, mac, mac_len) ||
!CBB_add_asn1(&mac_data, &mac_salt_cbb, CBS_ASN1_OCTETSTRING) ||
!CBB_add_bytes(&mac_salt_cbb, mac_salt, salt_len) ||
// The iteration count has a DEFAULT of 1, but RFC 7292 says "The default
// is for historical reasons and its use is deprecated." Thus we
// explicitly encode the iteration count, though it is not valid DER.
!CBB_add_asn1_uint64(&mac_data, mac_iterations) ||
!CBB_flush(out_pfx)) {
goto out;
}
ret = 1;

out:
OPENSSL_cleanse(mac_key, sizeof(mac_key));
return ret;
}

PKCS12 *PKCS12_create(const char *password, const char *name,
const EVP_PKEY *pkey, X509 *cert,
const STACK_OF(X509)* chain, int key_nid, int cert_nid,
Expand Down Expand Up @@ -1194,9 +1232,7 @@ PKCS12 *PKCS12_create(const char *password, const char *name,
PKCS12 *ret = NULL;
CBB cbb, pfx, auth_safe, auth_safe_oid, auth_safe_wrapper, auth_safe_data,
content_infos;
uint8_t mac_key[EVP_MAX_MD_SIZE];
if (!CBB_init(&cbb, 0) ||
!CBB_add_asn1(&cbb, &pfx, CBS_ASN1_SEQUENCE) ||
if (!CBB_init(&cbb, 0) || !CBB_add_asn1(&cbb, &pfx, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1_uint64(&pfx, 3) ||
// auth_safe is a data ContentInfo.
!CBB_add_asn1(&pfx, &auth_safe, CBS_ASN1_SEQUENCE) ||
Expand Down Expand Up @@ -1301,30 +1337,11 @@ PKCS12 *PKCS12_create(const char *password, const char *name,
// covers |auth_safe_data|.
const EVP_MD *mac_md = EVP_sha1();
uint8_t mac_salt[PKCS5_SALT_LEN];
uint8_t mac[EVP_MAX_MD_SIZE];
unsigned mac_len;
if (!CBB_flush(&auth_safe_data) ||
!RAND_bytes(mac_salt, sizeof(mac_salt)) ||
!pkcs12_key_gen(password, password_len, mac_salt, sizeof(mac_salt),
PKCS12_MAC_ID, mac_iterations, EVP_MD_size(mac_md),
mac_key, mac_md) ||
!HMAC(mac_md, mac_key, EVP_MD_size(mac_md), CBB_data(&auth_safe_data),
CBB_len(&auth_safe_data), mac, &mac_len)) {
goto err;
}

CBB mac_data, digest_info, mac_cbb, mac_salt_cbb;
if (!CBB_add_asn1(&pfx, &mac_data, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1(&mac_data, &digest_info, CBS_ASN1_SEQUENCE) ||
!EVP_marshal_digest_algorithm(&digest_info, mac_md) ||
!CBB_add_asn1(&digest_info, &mac_cbb, CBS_ASN1_OCTETSTRING) ||
!CBB_add_bytes(&mac_cbb, mac, mac_len) ||
!CBB_add_asn1(&mac_data, &mac_salt_cbb, CBS_ASN1_OCTETSTRING) ||
!CBB_add_bytes(&mac_salt_cbb, mac_salt, sizeof(mac_salt)) ||
// The iteration count has a DEFAULT of 1, but RFC 7292 says "The default
// is for historical reasons and its use is deprecated." Thus we
// explicitly encode the iteration count, though it is not valid DER.
!CBB_add_asn1_uint64(&mac_data, mac_iterations)) {
!pkcs12_gen_and_write_mac(
&pfx, CBB_data(&auth_safe_data), CBB_len(&auth_safe_data), password,
password_len, mac_salt, sizeof(mac_salt), mac_iterations, mac_md)) {
goto err;
}

Expand All @@ -1337,7 +1354,6 @@ PKCS12 *PKCS12_create(const char *password, const char *name,
}

err:
OPENSSL_cleanse(mac_key, sizeof(mac_key));
CBB_cleanup(&cbb);
return ret;
}
Expand All @@ -1353,3 +1369,98 @@ void PKCS12_free(PKCS12 *p12) {
OPENSSL_free(p12->ber_bytes);
OPENSSL_free(p12);
}

int PKCS12_set_mac(PKCS12 *p12, const char *password, int password_len,
unsigned char *salt, int salt_len, int mac_iterations,
const EVP_MD *md) {
GUARD_PTR(p12);
int ret = 0;

if (mac_iterations == 0) {
mac_iterations = 1;
}
if (salt_len == 0) {
salt_len = PKCS5_SALT_LEN;
}
// Generate |mac_salt| if |salt| is NULL and copy if NULL.
uint8_t *mac_salt = OPENSSL_malloc(salt_len);
if (salt == NULL) {
if (!RAND_bytes(mac_salt, salt_len)) {
goto out;
}
} else {
OPENSSL_memcpy(mac_salt, salt, salt_len);
}
// Match OpenSSL in using SHA-1 as the default hash function.
if (md == NULL) {
md = EVP_sha1();
}

uint8_t *storage = NULL;
CBS ber_bytes, in, pfx, authsafe, content_type, wrapped_authsafes, authsafes;
uint64_t version;
// The input may be in BER format.
CBS_init(&ber_bytes, p12->ber_bytes, p12->ber_len);
if (!CBS_asn1_ber_to_der(&ber_bytes, &in, &storage)) {
OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
goto out;
}
// There's no use case for |storage| anymore, so we free early.
OPENSSL_free(storage);

if (!CBS_get_asn1(&in, &pfx, CBS_ASN1_SEQUENCE) || CBS_len(&in) != 0 ||
!CBS_get_asn1_uint64(&pfx, &version)) {
OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
goto out;
}
if (version < 3) {
OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_VERSION);
goto out;
}

if (!CBS_get_asn1(&pfx, &authsafe, CBS_ASN1_SEQUENCE)) {
OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
goto out;
}
// Save contents of |authsafe| to write back before the CBS is advanced.
const uint8_t *orig_authsafe = CBS_data(&authsafe);
size_t orig_authsafe_len = CBS_len(&authsafe);

// Parse for |authsafes| which is the data that we should be running HMAC on.
if (!CBS_get_asn1(&authsafe, &content_type, CBS_ASN1_OBJECT) ||
!CBS_get_asn1(&authsafe, &wrapped_authsafes,
CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
!CBS_get_asn1(&wrapped_authsafes, &authsafes, CBS_ASN1_OCTETSTRING)) {
OPENSSL_PUT_ERROR(PKCS8, PKCS8_R_BAD_PKCS12_DATA);
goto out;
}

// Rewrite contents of |p12| with the original contents and updated MAC.
CBB cbb, out_pfx, out_auth_safe;
if (!CBB_init(&cbb, 0) || !CBB_add_asn1(&cbb, &out_pfx, CBS_ASN1_SEQUENCE) ||
!CBB_add_asn1_uint64(&out_pfx, version) ||
!CBB_add_asn1(&out_pfx, &out_auth_safe, CBS_ASN1_SEQUENCE) ||
!CBB_add_bytes(&out_auth_safe, orig_authsafe, orig_authsafe_len) ||
!pkcs12_gen_and_write_mac(&out_pfx, CBS_data(&authsafes),
CBS_len(&authsafes), password, password_len,
mac_salt, salt_len, mac_iterations, md)) {
CBB_cleanup(&cbb);
goto out;
}

// Verify that the new password is consistent with the original. This is
// behavior specific to AWS-LC.
OPENSSL_free(p12->ber_bytes);
if(!CBB_finish(&cbb, &p12->ber_bytes, &p12->ber_len) ||
!PKCS12_verify_mac(p12, password, password_len)) {
CBB_cleanup(&cbb);
goto out;
}

ret = 1;

out:
OPENSSL_free(mac_salt);
return ret;
}

22 changes: 18 additions & 4 deletions include/openssl/pkcs8.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ OPENSSL_EXPORT EVP_PKEY *PKCS8_parse_encrypted_private_key(CBS *cbs,
// Any friendlyName attributes (RFC 2985) in the PKCS#12 structure will be
// returned on the |X509| objects as aliases. See also |X509_alias_get0|.
OPENSSL_EXPORT int PKCS12_get_key_and_certs(EVP_PKEY **out_key,
STACK_OF(X509) *out_certs,
CBS *in, const char *password);
STACK_OF(X509) *out_certs, CBS *in,
const char *password);


// Deprecated functions.
Expand All @@ -149,10 +149,10 @@ OPENSSL_EXPORT PKCS12 *d2i_PKCS12(PKCS12 **out_p12, const uint8_t **ber_bytes,
size_t ber_len);

// d2i_PKCS12_bio acts like |d2i_PKCS12| but reads from a |BIO|.
OPENSSL_EXPORT PKCS12* d2i_PKCS12_bio(BIO *bio, PKCS12 **out_p12);
OPENSSL_EXPORT PKCS12 *d2i_PKCS12_bio(BIO *bio, PKCS12 **out_p12);

// d2i_PKCS12_fp acts like |d2i_PKCS12| but reads from a |FILE|.
OPENSSL_EXPORT PKCS12* d2i_PKCS12_fp(FILE *fp, PKCS12 **out_p12);
OPENSSL_EXPORT PKCS12 *d2i_PKCS12_fp(FILE *fp, PKCS12 **out_p12);

// i2d_PKCS12 is a dummy function which copies the contents of |p12|. If |out|
// is not NULL then the result is written to |*out| and |*out| is advanced just
Expand Down Expand Up @@ -188,6 +188,20 @@ OPENSSL_EXPORT int PKCS12_parse(const PKCS12 *p12, const char *password,
EVP_PKEY **out_pkey, X509 **out_cert,
STACK_OF(X509) **out_ca_certs);

// PKCS12_set_mac generates the MAC for |p12| with the designated |password|,
// |salt|, |mac_iterations|, and |md| specified. |password| MUST be the same
// password originally used to encrypt |p12|. Although OpenSSL will allow an
// invalid state with a different |password|, AWS-LC will throw an error and
// return 0.
//
// If |salt| is NULL, a random salt of |salt_len| bytes is generated. If
// |salt_len| is zero, a default salt length is used instead.
// If |md| is NULL, the default is use SHA1 to align with OpenSSL.
OPENSSL_EXPORT int PKCS12_set_mac(PKCS12 *p12, const char *password,
int password_len, unsigned char *salt,
int salt_len, int mac_iterations,
const EVP_MD *md);

// PKCS12_verify_mac returns one if |password| is a valid password for |p12|
// and zero otherwise. Since |PKCS12_parse| doesn't take a length parameter,
// it's not actually possible to use a non-NUL-terminated password to actually
Expand Down
Loading