Browse Source

tls: `getPeerCertificate(detailed)`

Add `raw` property to certificate, add mode to output full certificate
chain.
v0.11.13-release
Fedor Indutny 11 years ago
parent
commit
345c40b661
  1. 13
      doc/api/tls.markdown
  2. 3
      lib/_tls_common.js
  3. 8
      lib/_tls_legacy.js
  4. 8
      lib/_tls_wrap.js
  5. 2
      src/env.h
  6. 382
      src/node_crypto.cc
  7. 22
      test/simple/test-tls-peer-certificate.js

13
doc/api/tls.markdown

@ -644,27 +644,32 @@ specified CAs, otherwise `false`
The reason why the peer's certificate has not been verified. This property
becomes available only when `tlsSocket.authorized === false`.
### tlsSocket.getPeerCertificate()
### tlsSocket.getPeerCertificate([ detailed ])
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:
{ subject:
{ subject:
{ C: 'UK',
ST: 'Acknack Ltd',
L: 'Rhys Jones',
O: 'node.js',
OU: 'Test TLS Certificate',
CN: 'localhost' },
issuer:
issuerInfo:
{ C: 'UK',
ST: 'Acknack Ltd',
L: 'Rhys Jones',
O: 'node.js',
OU: 'Test TLS Certificate',
CN: 'localhost' },
issuer:
{ ... another certificate ... },
raw: < RAW DER buffer >,
valid_from: 'Nov 11 09:52:22 2009 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',

3
lib/_tls_common.js

@ -134,6 +134,9 @@ exports.translatePeerCertificate = function translatePeerCertificate(c) {
return null;
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.infoAccess) {
var info = c.infoAccess;

8
lib/_tls_legacy.js

@ -378,9 +378,11 @@ CryptoStream.prototype.__defineGetter__('bytesWritten', function() {
return this.socket ? this.socket.bytesWritten : 0;
});
CryptoStream.prototype.getPeerCertificate = function() {
if (this.pair.ssl)
return common.translatePeerCertificate(this.pair.ssl.getPeerCertificate());
CryptoStream.prototype.getPeerCertificate = function(detailed) {
if (this.pair.ssl) {
return common.translatePeerCertificate(
this.pair.ssl.getPeerCertificate(detailed));
}
return null;
};

8
lib/_tls_wrap.js

@ -473,9 +473,11 @@ TLSSocket.prototype.setSession = function(session) {
this.ssl.setSession(session);
};
TLSSocket.prototype.getPeerCertificate = function() {
if (this.ssl)
return common.translatePeerCertificate(this.ssl.getPeerCertificate());
TLSSocket.prototype.getPeerCertificate = function(detailed) {
if (this.ssl) {
return common.translatePeerCertificate(
this.ssl.getPeerCertificate(detailed));
}
return null;
};

2
src/env.h

@ -120,6 +120,7 @@ namespace node {
V(ipv6_lc_string, "ipv6") \
V(ipv6_string, "IPv6") \
V(issuer_string, "issuer") \
V(issuercert_string, "issuerCertificate") \
V(kill_signal_string, "killSignal") \
V(mac_string, "mac") \
V(mark_sweep_compact_string, "mark-sweep-compact") \
@ -169,6 +170,7 @@ namespace node {
V(priority_string, "priority") \
V(processed_string, "processed") \
V(prototype_string, "prototype") \
V(raw_string, "raw") \
V(rdev_string, "rdev") \
V(readable_string, "readable") \
V(received_shutdown_string, "receivedShutdown") \

382
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);
}

22
test/simple/test-tls-peer-certificate.js

@ -33,8 +33,9 @@ var join = require('path').join;
var spawn = require('child_process').spawn;
var options = {
key: fs.readFileSync(join(common.fixturesDir, 'agent.key')),
cert: fs.readFileSync(join(common.fixturesDir, 'alice.crt'))
key: fs.readFileSync(join(common.fixturesDir, 'keys', 'agent1-key.pem')),
cert: fs.readFileSync(join(common.fixturesDir, 'keys', 'agent1-cert.pem')),
ca: [ fs.readFileSync(join(common.fixturesDir, 'keys', 'ca1-cert.pem')) ]
};
var verified = false;
@ -47,10 +48,21 @@ server.listen(common.PORT, function() {
rejectUnauthorized: false
}, function() {
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));
assert.equal(peerCert.subject.subjectAltName,
'uniformResourceIdentifier:http://localhost:8000/alice.foaf#me');
assert.equal(peerCert.serialNumber, 'B9B0D332A1AA5635');
assert.equal(peerCert.subject.emailAddress, 'ry@tinyclouds.org');
assert.equal(peerCert.serialNumber, '9A84ABCFB8A72ABE');
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;
server.close();
});

Loading…
Cancel
Save