tls: getPeerCertificate(detailed)
Add `raw` property to certificate, add mode to output full certificate chain.
This commit is contained in:
parent
b3ef289ffb
commit
345c40b661
@ -644,27 +644,32 @@ specified CAs, otherwise `false`
|
|||||||
The reason why the peer's certificate has not been verified. This property
|
The reason why the peer's certificate has not been verified. This property
|
||||||
becomes available only when `tlsSocket.authorized === false`.
|
becomes available only when `tlsSocket.authorized === false`.
|
||||||
|
|
||||||
### tlsSocket.getPeerCertificate()
|
### tlsSocket.getPeerCertificate([ detailed ])
|
||||||
|
|
||||||
Returns an object representing the peer's certificate. The returned object has
|
Returns an object representing the peer's certificate. The returned object has
|
||||||
some properties corresponding to the field of the certificate.
|
some properties corresponding to the field of the certificate. If `detailed`
|
||||||
|
argument is `true` - the full chain with `issuer` property will be returned,
|
||||||
|
if `false` - only the top certificate without `issuer` property.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
{ subject:
|
{ subject:
|
||||||
{ C: 'UK',
|
{ C: 'UK',
|
||||||
ST: 'Acknack Ltd',
|
ST: 'Acknack Ltd',
|
||||||
L: 'Rhys Jones',
|
L: 'Rhys Jones',
|
||||||
O: 'node.js',
|
O: 'node.js',
|
||||||
OU: 'Test TLS Certificate',
|
OU: 'Test TLS Certificate',
|
||||||
CN: 'localhost' },
|
CN: 'localhost' },
|
||||||
issuer:
|
issuerInfo:
|
||||||
{ C: 'UK',
|
{ C: 'UK',
|
||||||
ST: 'Acknack Ltd',
|
ST: 'Acknack Ltd',
|
||||||
L: 'Rhys Jones',
|
L: 'Rhys Jones',
|
||||||
O: 'node.js',
|
O: 'node.js',
|
||||||
OU: 'Test TLS Certificate',
|
OU: 'Test TLS Certificate',
|
||||||
CN: 'localhost' },
|
CN: 'localhost' },
|
||||||
|
issuer:
|
||||||
|
{ ... another certificate ... },
|
||||||
|
raw: < RAW DER buffer >,
|
||||||
valid_from: 'Nov 11 09:52:22 2009 GMT',
|
valid_from: 'Nov 11 09:52:22 2009 GMT',
|
||||||
valid_to: 'Nov 6 09:52:22 2029 GMT',
|
valid_to: 'Nov 6 09:52:22 2029 GMT',
|
||||||
fingerprint: '2A:7A:C2:DD:E5:F9:CC:53:72:35:99:7A:02:5A:71:38:52:EC:8A:DF',
|
fingerprint: '2A:7A:C2:DD:E5:F9:CC:53:72:35:99:7A:02:5A:71:38:52:EC:8A:DF',
|
||||||
|
@ -134,6 +134,9 @@ exports.translatePeerCertificate = function translatePeerCertificate(c) {
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
|
if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
|
||||||
|
if (c.issuerCertificate && c.issuerCertificate !== c) {
|
||||||
|
c.issuerCertificate = translatePeerCertificate(c.issuerCertificate);
|
||||||
|
}
|
||||||
if (c.subject) c.subject = tls.parseCertString(c.subject);
|
if (c.subject) c.subject = tls.parseCertString(c.subject);
|
||||||
if (c.infoAccess) {
|
if (c.infoAccess) {
|
||||||
var info = c.infoAccess;
|
var info = c.infoAccess;
|
||||||
|
@ -378,9 +378,11 @@ CryptoStream.prototype.__defineGetter__('bytesWritten', function() {
|
|||||||
return this.socket ? this.socket.bytesWritten : 0;
|
return this.socket ? this.socket.bytesWritten : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
CryptoStream.prototype.getPeerCertificate = function() {
|
CryptoStream.prototype.getPeerCertificate = function(detailed) {
|
||||||
if (this.pair.ssl)
|
if (this.pair.ssl) {
|
||||||
return common.translatePeerCertificate(this.pair.ssl.getPeerCertificate());
|
return common.translatePeerCertificate(
|
||||||
|
this.pair.ssl.getPeerCertificate(detailed));
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -473,9 +473,11 @@ TLSSocket.prototype.setSession = function(session) {
|
|||||||
this.ssl.setSession(session);
|
this.ssl.setSession(session);
|
||||||
};
|
};
|
||||||
|
|
||||||
TLSSocket.prototype.getPeerCertificate = function() {
|
TLSSocket.prototype.getPeerCertificate = function(detailed) {
|
||||||
if (this.ssl)
|
if (this.ssl) {
|
||||||
return common.translatePeerCertificate(this.ssl.getPeerCertificate());
|
return common.translatePeerCertificate(
|
||||||
|
this.ssl.getPeerCertificate(detailed));
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -120,6 +120,7 @@ namespace node {
|
|||||||
V(ipv6_lc_string, "ipv6") \
|
V(ipv6_lc_string, "ipv6") \
|
||||||
V(ipv6_string, "IPv6") \
|
V(ipv6_string, "IPv6") \
|
||||||
V(issuer_string, "issuer") \
|
V(issuer_string, "issuer") \
|
||||||
|
V(issuercert_string, "issuerCertificate") \
|
||||||
V(kill_signal_string, "killSignal") \
|
V(kill_signal_string, "killSignal") \
|
||||||
V(mac_string, "mac") \
|
V(mac_string, "mac") \
|
||||||
V(mark_sweep_compact_string, "mark-sweep-compact") \
|
V(mark_sweep_compact_string, "mark-sweep-compact") \
|
||||||
@ -169,6 +170,7 @@ namespace node {
|
|||||||
V(priority_string, "priority") \
|
V(priority_string, "priority") \
|
||||||
V(processed_string, "processed") \
|
V(processed_string, "processed") \
|
||||||
V(prototype_string, "prototype") \
|
V(prototype_string, "prototype") \
|
||||||
|
V(raw_string, "raw") \
|
||||||
V(rdev_string, "rdev") \
|
V(rdev_string, "rdev") \
|
||||||
V(readable_string, "readable") \
|
V(readable_string, "readable") \
|
||||||
V(received_shutdown_string, "receivedShutdown") \
|
V(received_shutdown_string, "receivedShutdown") \
|
||||||
|
@ -76,6 +76,7 @@ namespace crypto {
|
|||||||
using v8::Array;
|
using v8::Array;
|
||||||
using v8::Boolean;
|
using v8::Boolean;
|
||||||
using v8::Context;
|
using v8::Context;
|
||||||
|
using v8::EscapableHandleScope;
|
||||||
using v8::Exception;
|
using v8::Exception;
|
||||||
using v8::False;
|
using v8::False;
|
||||||
using v8::FunctionCallbackInfo;
|
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,
|
// Read a file that contains our certificate in "PEM" format,
|
||||||
// possibly followed by a sequence of CA certificates that should be
|
// possibly followed by a sequence of CA certificates that should be
|
||||||
// sent to the peer in the Certificate message.
|
// sent to the peer in the Certificate message.
|
||||||
//
|
//
|
||||||
// Taken from OpenSSL - editted for style.
|
// Taken from OpenSSL - editted for style.
|
||||||
int SSL_CTX_use_certificate_chain(SSL_CTX *ctx,
|
int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
|
||||||
BIO *in,
|
BIO* in,
|
||||||
X509** cert,
|
X509** cert,
|
||||||
X509** issuer) {
|
X509** issuer) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
X509 *x = NULL;
|
X509* x = NULL;
|
||||||
|
|
||||||
x = PEM_read_bio_X509_AUX(in, NULL, CryptoPemCallback, 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
|
// Try getting issuer from a cert store
|
||||||
if (ret) {
|
if (ret) {
|
||||||
if (*issuer == NULL) {
|
if (*issuer == NULL) {
|
||||||
X509_STORE* store = SSL_CTX_get_cert_store(ctx);
|
ret = SSL_CTX_get_issuer(ctx, x, issuer);
|
||||||
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 = ret < 0 ? 0 : 1;
|
ret = ret < 0 ? 0 : 1;
|
||||||
// NOTE: get_cert_store doesn't increment reference count,
|
// NOTE: get_cert_store doesn't increment reference count,
|
||||||
// no need to free `store`
|
// no need to free `store`
|
||||||
@ -1081,6 +1091,158 @@ void SSLWrap<Base>::OnClientHello(void* arg,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Local<Object> X509ToObject(Environment* env, X509* cert) {
|
||||||
|
EscapableHandleScope scope(env->isolate());
|
||||||
|
|
||||||
|
Local<Object> info = Object::New(env->isolate());
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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_EXTENSION* ext;
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
ext = X509_get_ext(cert, index);
|
||||||
|
assert(ext != NULL);
|
||||||
|
|
||||||
|
rv = X509V3_EXT_print(bio, ext, 0, 0);
|
||||||
|
assert(rv == 1);
|
||||||
|
|
||||||
|
BIO_get_mem_ptr(bio, &mem);
|
||||||
|
info->Set(keys[i],
|
||||||
|
OneByteString(env->isolate(), mem->data, mem->length));
|
||||||
|
|
||||||
|
(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(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] = ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (md_size > 0) {
|
||||||
|
fingerprint[(3*(md_size-1))+2] = '\0';
|
||||||
|
} else {
|
||||||
|
fingerprint[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
return scope.Escape(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO(indutny): Split it into multiple smaller functions
|
// TODO(indutny): Split it into multiple smaller functions
|
||||||
template <class Base>
|
template <class Base>
|
||||||
void SSLWrap<Base>::GetPeerCertificate(
|
void SSLWrap<Base>::GetPeerCertificate(
|
||||||
@ -1093,148 +1255,96 @@ void SSLWrap<Base>::GetPeerCertificate(
|
|||||||
ClearErrorOnReturn clear_error_on_return;
|
ClearErrorOnReturn clear_error_on_return;
|
||||||
(void) &clear_error_on_return; // Silence unused variable warning.
|
(void) &clear_error_on_return; // Silence unused variable warning.
|
||||||
|
|
||||||
Local<Object> info = Object::New(env->isolate());
|
Local<Object> result;
|
||||||
X509* peer_cert = SSL_get_peer_certificate(w->ssl_);
|
Local<Object> info;
|
||||||
if (peer_cert != NULL) {
|
X509* cert;
|
||||||
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(peer_cert);
|
STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(w->ssl_);
|
||||||
if (X509_NAME_print_ex(bio, issuer_name, 0, X509_NAME_FLAGS) > 0) {
|
STACK_OF(X509)* peer_certs = NULL;
|
||||||
BIO_get_mem_ptr(bio, &mem);
|
if (ssl_certs == NULL)
|
||||||
info->Set(env->issuer_string(),
|
goto done;
|
||||||
OneByteString(args.GetIsolate(), mem->data, mem->length));
|
|
||||||
}
|
|
||||||
(void) BIO_reset(bio);
|
|
||||||
|
|
||||||
int nids[] = { NID_subject_alt_name, NID_info_access };
|
if (sk_X509_num(ssl_certs) == 0) {
|
||||||
Local<String> keys[] = { env->subjectaltname_string(),
|
goto done;
|
||||||
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;
|
|
||||||
|
|
||||||
X509_EXTENSION* ext;
|
|
||||||
int rv;
|
|
||||||
|
|
||||||
ext = X509_get_ext(peer_cert, index);
|
|
||||||
assert(ext != NULL);
|
|
||||||
|
|
||||||
rv = X509V3_EXT_print(bio, ext, 0, 0);
|
|
||||||
assert(rv == 1);
|
|
||||||
|
|
||||||
BIO_get_mem_ptr(bio, &mem);
|
|
||||||
info->Set(keys[i],
|
|
||||||
OneByteString(args.GetIsolate(), mem->data, mem->length));
|
|
||||||
|
|
||||||
(void) BIO_reset(bio);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (pkey != NULL) {
|
|
||||||
EVP_PKEY_free(pkey);
|
|
||||||
pkey = NULL;
|
|
||||||
}
|
|
||||||
if (rsa != NULL) {
|
|
||||||
RSA_free(rsa);
|
|
||||||
rsa = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
info->Set(env->fingerprint_string(),
|
|
||||||
OneByteString(args.GetIsolate(), fingerprint));
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
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(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
X509_free(peer_cert);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args.GetReturnValue().Set(info);
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issuer not found, break out of the loop
|
||||||
|
if (i == sk_X509_num(peer_certs))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,8 +33,9 @@ var join = require('path').join;
|
|||||||
var spawn = require('child_process').spawn;
|
var spawn = require('child_process').spawn;
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
key: fs.readFileSync(join(common.fixturesDir, 'agent.key')),
|
key: fs.readFileSync(join(common.fixturesDir, 'keys', 'agent1-key.pem')),
|
||||||
cert: fs.readFileSync(join(common.fixturesDir, 'alice.crt'))
|
cert: fs.readFileSync(join(common.fixturesDir, 'keys', 'agent1-cert.pem')),
|
||||||
|
ca: [ fs.readFileSync(join(common.fixturesDir, 'keys', 'ca1-cert.pem')) ]
|
||||||
};
|
};
|
||||||
var verified = false;
|
var verified = false;
|
||||||
|
|
||||||
@ -47,10 +48,21 @@ server.listen(common.PORT, function() {
|
|||||||
rejectUnauthorized: false
|
rejectUnauthorized: false
|
||||||
}, function() {
|
}, function() {
|
||||||
var peerCert = socket.getPeerCertificate();
|
var peerCert = socket.getPeerCertificate();
|
||||||
|
assert.ok(!peerCert.issuerCertificate);
|
||||||
|
|
||||||
|
// Verify that detailed return value has all certs
|
||||||
|
peerCert = socket.getPeerCertificate(true);
|
||||||
|
assert.ok(peerCert.issuerCertificate);
|
||||||
|
|
||||||
common.debug(util.inspect(peerCert));
|
common.debug(util.inspect(peerCert));
|
||||||
assert.equal(peerCert.subject.subjectAltName,
|
assert.equal(peerCert.subject.emailAddress, 'ry@tinyclouds.org');
|
||||||
'uniformResourceIdentifier:http://localhost:8000/alice.foaf#me');
|
assert.equal(peerCert.serialNumber, '9A84ABCFB8A72ABE');
|
||||||
assert.equal(peerCert.serialNumber, 'B9B0D332A1AA5635');
|
assert.deepEqual(peerCert.infoAccess['OCSP - URI'],
|
||||||
|
[ 'http://ocsp.nodejs.org/' ]);
|
||||||
|
|
||||||
|
var issuer = peerCert.issuerCertificate;
|
||||||
|
assert.ok(issuer.issuerCertificate === issuer);
|
||||||
|
assert.equal(issuer.serialNumber, 'B5090C899FC2FF93');
|
||||||
verified = true;
|
verified = true;
|
||||||
server.close();
|
server.close();
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user