diff options
author | Fedor Indutny <fedor@indutny.com> | 2014-04-17 15:57:36 +0400 |
---|---|---|
committer | Fedor Indutny <fedor@indutny.com> | 2014-04-18 02:21:16 +0400 |
commit | 345c40b6615c499b586b6f7f5528bcbea28ca12e (patch) | |
tree | 4eda064476220fd51c6c714d739c5f5624f6ef02 /src/node_crypto.cc | |
parent | b3ef289ffb7db476d284866658213f04415ea92d (diff) | |
download | node-new-345c40b6615c499b586b6f7f5528bcbea28ca12e.tar.gz |
tls: `getPeerCertificate(detailed)`
Add `raw` property to certificate, add mode to output full certificate
chain.
Diffstat (limited to 'src/node_crypto.cc')
-rw-r--r-- | src/node_crypto.cc | 382 |
1 files changed, 246 insertions, 136 deletions
diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 0d685c9f51..e29948520c 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -76,6 +76,7 @@ namespace crypto { using v8::Array; using v8::Boolean; using v8::Context; +using v8::EscapableHandleScope; using v8::Exception; using v8::False; using v8::FunctionCallbackInfo; @@ -471,17 +472,35 @@ void SecureContext::SetKey(const FunctionCallbackInfo<Value>& args) { } +int SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert, X509** issuer) { + int ret; + + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + X509_STORE_CTX store_ctx; + + ret = X509_STORE_CTX_init(&store_ctx, store, NULL, NULL); + if (!ret) + goto end; + + ret = X509_STORE_CTX_get1_issuer(issuer, &store_ctx, cert); + X509_STORE_CTX_cleanup(&store_ctx); + +end: + return ret; +} + + // Read a file that contains our certificate in "PEM" format, // possibly followed by a sequence of CA certificates that should be // sent to the peer in the Certificate message. // // Taken from OpenSSL - editted for style. -int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, - BIO *in, +int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, + BIO* in, X509** cert, X509** issuer) { int ret = 0; - X509 *x = NULL; + X509* x = NULL; x = PEM_read_bio_X509_AUX(in, NULL, CryptoPemCallback, NULL); @@ -542,16 +561,7 @@ int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, // Try getting issuer from a cert store if (ret) { if (*issuer == NULL) { - X509_STORE* store = SSL_CTX_get_cert_store(ctx); - X509_STORE_CTX store_ctx; - - ret = X509_STORE_CTX_init(&store_ctx, store, NULL, NULL); - if (!ret) - goto end; - - ret = X509_STORE_CTX_get1_issuer(issuer, &store_ctx, x); - X509_STORE_CTX_cleanup(&store_ctx); - + ret = SSL_CTX_get_issuer(ctx, x, issuer); ret = ret < 0 ? 0 : 1; // NOTE: get_cert_store doesn't increment reference count, // no need to free `store` @@ -1081,160 +1091,260 @@ void SSLWrap<Base>::OnClientHello(void* arg, } -// TODO(indutny): Split it into multiple smaller functions -template <class Base> -void SSLWrap<Base>::GetPeerCertificate( - const FunctionCallbackInfo<Value>& args) { - HandleScope scope(args.GetIsolate()); +static Local<Object> X509ToObject(Environment* env, X509* cert) { + EscapableHandleScope scope(env->isolate()); - Base* w = Unwrap<Base>(args.Holder()); - Environment* env = w->ssl_env(); + Local<Object> info = Object::New(env->isolate()); - ClearErrorOnReturn clear_error_on_return; - (void) &clear_error_on_return; // Silence unused variable warning. + BIO* bio = BIO_new(BIO_s_mem()); + BUF_MEM* mem; + if (X509_NAME_print_ex(bio, + X509_get_subject_name(cert), + 0, + X509_NAME_FLAGS) > 0) { + BIO_get_mem_ptr(bio, &mem); + info->Set(env->subject_string(), + OneByteString(env->isolate(), mem->data, mem->length)); + } + (void) BIO_reset(bio); - Local<Object> info = Object::New(env->isolate()); - X509* peer_cert = SSL_get_peer_certificate(w->ssl_); - if (peer_cert != NULL) { - BIO* bio = BIO_new(BIO_s_mem()); - BUF_MEM* mem; - if (X509_NAME_print_ex(bio, - X509_get_subject_name(peer_cert), - 0, - X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio, &mem); - info->Set(env->subject_string(), - OneByteString(args.GetIsolate(), mem->data, mem->length)); - } - (void) BIO_reset(bio); + X509_NAME* issuer_name = X509_get_issuer_name(cert); + if (X509_NAME_print_ex(bio, issuer_name, 0, X509_NAME_FLAGS) > 0) { + BIO_get_mem_ptr(bio, &mem); + info->Set(env->issuer_string(), + OneByteString(env->isolate(), mem->data, mem->length)); + } + (void) BIO_reset(bio); + + int nids[] = { NID_subject_alt_name, NID_info_access }; + Local<String> keys[] = { env->subjectaltname_string(), + env->infoaccess_string() }; + CHECK_EQ(ARRAY_SIZE(nids), ARRAY_SIZE(keys)); + for (unsigned int i = 0; i < ARRAY_SIZE(nids); i++) { + int index = X509_get_ext_by_NID(cert, nids[i], -1); + if (index < 0) + continue; - X509_NAME* issuer_name = X509_get_issuer_name(peer_cert); - if (X509_NAME_print_ex(bio, issuer_name, 0, X509_NAME_FLAGS) > 0) { - BIO_get_mem_ptr(bio, &mem); - info->Set(env->issuer_string(), - OneByteString(args.GetIsolate(), mem->data, mem->length)); - } - (void) BIO_reset(bio); + X509_EXTENSION* ext; + int rv; - int nids[] = { NID_subject_alt_name, NID_info_access }; - Local<String> keys[] = { env->subjectaltname_string(), - env->infoaccess_string() }; - CHECK_EQ(ARRAY_SIZE(nids), ARRAY_SIZE(keys)); - for (unsigned int i = 0; i < ARRAY_SIZE(nids); i++) { - int index = X509_get_ext_by_NID(peer_cert, nids[i], -1); - if (index < 0) - continue; + ext = X509_get_ext(cert, index); + assert(ext != NULL); - X509_EXTENSION* ext; - int rv; + rv = X509V3_EXT_print(bio, ext, 0, 0); + assert(rv == 1); - ext = X509_get_ext(peer_cert, index); - assert(ext != NULL); + BIO_get_mem_ptr(bio, &mem); + info->Set(keys[i], + OneByteString(env->isolate(), mem->data, mem->length)); - rv = X509V3_EXT_print(bio, ext, 0, 0); - assert(rv == 1); + (void) BIO_reset(bio); + } + EVP_PKEY* pkey = X509_get_pubkey(cert); + RSA* rsa = NULL; + if (pkey != NULL) + rsa = EVP_PKEY_get1_RSA(pkey); + + if (rsa != NULL) { + BN_print(bio, rsa->n); BIO_get_mem_ptr(bio, &mem); - info->Set(keys[i], - OneByteString(args.GetIsolate(), mem->data, mem->length)); + info->Set(env->modulus_string(), + OneByteString(env->isolate(), mem->data, mem->length)); + (void) BIO_reset(bio); + BN_print(bio, rsa->e); + BIO_get_mem_ptr(bio, &mem); + info->Set(env->exponent_string(), + OneByteString(env->isolate(), mem->data, mem->length)); (void) BIO_reset(bio); + } + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + if (rsa != NULL) { + RSA_free(rsa); + rsa = NULL; + } + + ASN1_TIME_print(bio, X509_get_notBefore(cert)); + BIO_get_mem_ptr(bio, &mem); + info->Set(env->valid_from_string(), + OneByteString(env->isolate(), mem->data, mem->length)); + (void) BIO_reset(bio); + + ASN1_TIME_print(bio, X509_get_notAfter(cert)); + BIO_get_mem_ptr(bio, &mem); + info->Set(env->valid_to_string(), + OneByteString(env->isolate(), mem->data, mem->length)); + BIO_free_all(bio); + + unsigned int md_size, i; + unsigned char md[EVP_MAX_MD_SIZE]; + if (X509_digest(cert, EVP_sha1(), md, &md_size)) { + const char hex[] = "0123456789ABCDEF"; + char fingerprint[EVP_MAX_MD_SIZE * 3]; + + // TODO(indutny): Unify it with buffer's code + for (i = 0; i < md_size; i++) { + fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4]; + fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)]; + fingerprint[(3*i)+2] = ':'; } - EVP_PKEY* pkey = X509_get_pubkey(peer_cert); - RSA* rsa = NULL; - if (pkey != NULL) - rsa = EVP_PKEY_get1_RSA(pkey); - - if (rsa != NULL) { - BN_print(bio, rsa->n); - BIO_get_mem_ptr(bio, &mem); - info->Set(env->modulus_string(), - OneByteString(args.GetIsolate(), mem->data, mem->length)); - (void) BIO_reset(bio); - - BN_print(bio, rsa->e); - BIO_get_mem_ptr(bio, &mem); - info->Set(env->exponent_string(), - OneByteString(args.GetIsolate(), mem->data, mem->length)); - (void) BIO_reset(bio); + if (md_size > 0) { + fingerprint[(3*(md_size-1))+2] = '\0'; + } else { + fingerprint[0] = '\0'; } - if (pkey != NULL) { - EVP_PKEY_free(pkey); - pkey = NULL; + info->Set(env->fingerprint_string(), + OneByteString(env->isolate(), fingerprint)); + } + + STACK_OF(ASN1_OBJECT)* eku = static_cast<STACK_OF(ASN1_OBJECT)*>( + X509_get_ext_d2i(cert, NID_ext_key_usage, NULL, NULL)); + if (eku != NULL) { + Local<Array> ext_key_usage = Array::New(env->isolate()); + char buf[256]; + + int j = 0; + for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { + if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku, i), 1) >= 0) + ext_key_usage->Set(j++, OneByteString(env->isolate(), buf)); } - if (rsa != NULL) { - RSA_free(rsa); - rsa = NULL; + + sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); + info->Set(env->ext_key_usage_string(), ext_key_usage); + } + + if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert)) { + if (BIGNUM* bn = ASN1_INTEGER_to_BN(serial_number, NULL)) { + if (char* buf = BN_bn2hex(bn)) { + info->Set(env->serial_number_string(), + OneByteString(env->isolate(), buf)); + OPENSSL_free(buf); + } + BN_free(bn); } + } - ASN1_TIME_print(bio, X509_get_notBefore(peer_cert)); - BIO_get_mem_ptr(bio, &mem); - info->Set(env->valid_from_string(), - OneByteString(args.GetIsolate(), mem->data, mem->length)); - (void) BIO_reset(bio); + // Raw DER certificate + int size = i2d_X509(cert, NULL); + Local<Object> buff = Buffer::New(env, size); + unsigned char* serialized = reinterpret_cast<unsigned char*>( + Buffer::Data(buff)); + i2d_X509(cert, &serialized); + info->Set(env->raw_string(), buff); - ASN1_TIME_print(bio, X509_get_notAfter(peer_cert)); - BIO_get_mem_ptr(bio, &mem); - info->Set(env->valid_to_string(), - OneByteString(args.GetIsolate(), mem->data, mem->length)); - BIO_free_all(bio); + return scope.Escape(info); +} - unsigned int md_size, i; - unsigned char md[EVP_MAX_MD_SIZE]; - if (X509_digest(peer_cert, EVP_sha1(), md, &md_size)) { - const char hex[] = "0123456789ABCDEF"; - char fingerprint[EVP_MAX_MD_SIZE * 3]; - - // TODO(indutny): Unify it with buffer's code - for (i = 0; i < md_size; i++) { - fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4]; - fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)]; - fingerprint[(3*i)+2] = ':'; - } - if (md_size > 0) { - fingerprint[(3*(md_size-1))+2] = '\0'; - } else { - fingerprint[0] = '\0'; - } +// TODO(indutny): Split it into multiple smaller functions +template <class Base> +void SSLWrap<Base>::GetPeerCertificate( + const FunctionCallbackInfo<Value>& args) { + HandleScope scope(args.GetIsolate()); - info->Set(env->fingerprint_string(), - OneByteString(args.GetIsolate(), fingerprint)); - } + Base* w = Unwrap<Base>(args.Holder()); + Environment* env = w->ssl_env(); + + ClearErrorOnReturn clear_error_on_return; + (void) &clear_error_on_return; // Silence unused variable warning. - STACK_OF(ASN1_OBJECT)* eku = static_cast<STACK_OF(ASN1_OBJECT)*>( - X509_get_ext_d2i(peer_cert, NID_ext_key_usage, NULL, NULL)); - if (eku != NULL) { - Local<Array> ext_key_usage = Array::New(env->isolate()); - char buf[256]; + Local<Object> result; + Local<Object> info; + X509* cert; - int j = 0; - for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { - if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku, i), 1) >= 0) - ext_key_usage->Set(j++, OneByteString(args.GetIsolate(), buf)); - } + STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(w->ssl_); + STACK_OF(X509)* peer_certs = NULL; + if (ssl_certs == NULL) + goto done; - sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); - info->Set(env->ext_key_usage_string(), ext_key_usage); - } + if (sk_X509_num(ssl_certs) == 0) { + goto done; + } - if (ASN1_INTEGER* serial_number = X509_get_serialNumber(peer_cert)) { - if (BIGNUM* bn = ASN1_INTEGER_to_BN(serial_number, NULL)) { - if (char* buf = BN_bn2hex(bn)) { - info->Set(env->serial_number_string(), - OneByteString(args.GetIsolate(), buf)); - OPENSSL_free(buf); - } - BN_free(bn); - } + // Short result requested + if (args.Length() < 1 || !args[0]->IsTrue()) { + result = X509ToObject(env, sk_X509_value(ssl_certs, 0)); + goto done; + } + + // Clone `ssl_certs`, because we are going to destruct it + peer_certs = sk_X509_new(NULL); + for (int i = 0; i < sk_X509_num(ssl_certs); i++) { + cert = X509_dup(sk_X509_value(ssl_certs, i)); + if (cert == NULL) + goto done; + if (!sk_X509_push(peer_certs, cert)) + goto done; + } + + // First and main certificate + cert = sk_X509_value(peer_certs, 0); + result = X509ToObject(env, cert); + info = result; + + // Put issuer inside the object + cert = sk_X509_delete(peer_certs, 0); + while (sk_X509_num(peer_certs) > 0) { + int i; + for (i = 0; i < sk_X509_num(peer_certs); i++) { + X509* ca = sk_X509_value(peer_certs, i); + if (X509_check_issued(ca, cert) != X509_V_OK) + continue; + + Local<Object> ca_info = X509ToObject(env, ca); + info->Set(env->issuercert_string(), ca_info); + info = ca_info; + + // NOTE: Intentionally freeing cert that is not used anymore + X509_free(cert); + + // Delete cert and continue aggregating issuers + cert = sk_X509_delete(peer_certs, i); + break; } - X509_free(peer_cert); + // Issuer not found, break out of the loop + if (i == sk_X509_num(peer_certs)) + break; } - args.GetReturnValue().Set(info); + // Last certificate should be self-signed + while (X509_check_issued(cert, cert) != X509_V_OK) { + X509* ca; + if (SSL_CTX_get_issuer(w->ssl_->ctx, cert, &ca) <= 0) + break; + + Local<Object> ca_info = X509ToObject(env, ca); + info->Set(env->issuercert_string(), ca_info); + info = ca_info; + + // NOTE: Intentionally freeing cert that is not used anymore + X509_free(cert); + + // Delete cert and continue aggregating issuers + cert = ca; + } + + // Self-issued certificate + if (X509_check_issued(cert, cert) == X509_V_OK) + info->Set(env->issuercert_string(), info); + + CHECK_NE(cert, NULL); + X509_free(cert); + +done: + if (peer_certs != NULL) + sk_X509_pop_free(peer_certs, X509_free); + if (result.IsEmpty()) + result = Object::New(env->isolate()); + args.GetReturnValue().Set(result); } |