Skip to content

Commit

Permalink
crypto: make PEM parsing RFC7468-compliant
Browse files Browse the repository at this point in the history
PR-URL: nodejs#23164
Fixes: nodejs#13612
Fixes: nodejs#22815
Reviewed-By: Ben Noordhuis <[email protected]>
Reviewed-By: James M Snell <[email protected]>
  • Loading branch information
tniessen committed Oct 2, 2018
1 parent 0a1c650 commit ad07e44
Showing 1 changed file with 51 additions and 32 deletions.
83 changes: 51 additions & 32 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,6 @@
#include <memory>
#include <vector>

static const char PUBLIC_KEY_PFX[] = "-----BEGIN PUBLIC KEY-----";
static const int PUBLIC_KEY_PFX_LEN = sizeof(PUBLIC_KEY_PFX) - 1;
static const char PUBRSA_KEY_PFX[] = "-----BEGIN RSA PUBLIC KEY-----";
static const int PUBRSA_KEY_PFX_LEN = sizeof(PUBRSA_KEY_PFX) - 1;
static const char CERTIFICATE_PFX[] = "-----BEGIN CERTIFICATE-----";
static const int CERTIFICATE_PFX_LEN = sizeof(CERTIFICATE_PFX) - 1;

static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL
| ASN1_STRFLGS_UTF8_CONVERT
| XN_FLAG_SEP_MULTILINE
Expand Down Expand Up @@ -3650,38 +3643,64 @@ enum ParsePublicKeyResult {
kParsePublicFailed
};

static ParsePublicKeyResult TryParsePublicKey(
EVPKeyPointer* pkey,
const BIOPointer& bp,
const char* name,
// NOLINTNEXTLINE(runtime/int)
std::function<EVP_PKEY*(const unsigned char** p, long l)> parse) {
unsigned char* der_data;
long der_len; // NOLINT(runtime/int)

// This skips surrounding data and decodes PEM to DER.
{
MarkPopErrorOnReturn mark_pop_error_on_return;
if (PEM_bytes_read_bio(&der_data, &der_len, nullptr, name,
bp.get(), nullptr, nullptr) != 1)
return kParsePublicNotRecognized;
}

// OpenSSL might modify the pointer, so we need to make a copy before parsing.
const unsigned char* p = der_data;
pkey->reset(parse(&p, der_len));
OPENSSL_clear_free(der_data, der_len);

return *pkey ? kParsePublicOk : kParsePublicFailed;
}

static ParsePublicKeyResult ParsePublicKey(EVPKeyPointer* pkey,
const char* key_pem,
int key_pem_len) {
BIOPointer bp(BIO_new_mem_buf(const_cast<char*>(key_pem), key_pem_len));
if (!bp)
return kParsePublicFailed;

// Check if this is a PKCS#8 or RSA public key before trying as X.509.
if (strncmp(key_pem, PUBLIC_KEY_PFX, PUBLIC_KEY_PFX_LEN) == 0) {
pkey->reset(
PEM_read_bio_PUBKEY(bp.get(), nullptr, NoPasswordCallback, nullptr));
} else if (strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) {
RSAPointer rsa(PEM_read_bio_RSAPublicKey(
bp.get(), nullptr, PasswordCallback, nullptr));
if (rsa) {
pkey->reset(EVP_PKEY_new());
if (*pkey)
EVP_PKEY_set1_RSA(pkey->get(), rsa.get());
}
} else if (strncmp(key_pem, CERTIFICATE_PFX, CERTIFICATE_PFX_LEN) == 0) {
// X.509 fallback
X509Pointer x509(PEM_read_bio_X509(
bp.get(), nullptr, NoPasswordCallback, nullptr));
if (!x509)
return kParsePublicFailed;

pkey->reset(X509_get_pubkey(x509.get()));
} else {
return kParsePublicNotRecognized;
}

return *pkey ? kParsePublicOk : kParsePublicFailed;
ParsePublicKeyResult ret;

// Try PKCS#8 first.
ret = TryParsePublicKey(pkey, bp, "PUBLIC KEY",
[](const unsigned char** p, long l) { // NOLINT(runtime/int)
return d2i_PUBKEY(nullptr, p, l);
});
if (ret != kParsePublicNotRecognized)
return ret;

// Maybe it is PKCS#1.
CHECK(BIO_reset(bp.get()));
ret = TryParsePublicKey(pkey, bp, "RSA PUBLIC KEY",
[](const unsigned char** p, long l) { // NOLINT(runtime/int)
return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l);
});
if (ret != kParsePublicNotRecognized)
return ret;

// X.509 fallback.
CHECK(BIO_reset(bp.get()));
return TryParsePublicKey(pkey, bp, "CERTIFICATE",
[](const unsigned char** p, long l) { // NOLINT(runtime/int)
X509Pointer x509(d2i_X509(nullptr, p, l));
return x509 ? X509_get_pubkey(x509.get()) : nullptr;
});
}

void Verify::Initialize(Environment* env, v8::Local<Object> target) {
Expand Down

0 comments on commit ad07e44

Please sign in to comment.