Skip to content

Commit

Permalink
feat: expose client's peer certificate der data (#1062)
Browse files Browse the repository at this point in the history
* expose getPeerCertificate function

* add util method to extract certificate info

* add peer certificate verification example

* lint

* return the x509cert in pem format

* check for nullptr
  • Loading branch information
cruz3rblade authored Aug 7, 2024
1 parent 77e43e7 commit 37e4402
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 0 deletions.
134 changes: 134 additions & 0 deletions examples/PeerCertificate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const https = require('https');
const forge = require('node-forge');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
// Generate CA certificate
const ca = generateCACertificate();
const caCertPem = pemEncodeCert(ca.cert);

// Generate server certificate signed by CA
const serverCert = generateCertificate(ca, 'localhost');
const serverCertPem = pemEncodeCert(serverCert.cert);
const serverKeyPem = pemEncodeKey(serverCert.privateKey);

// Generate client certificate signed by CA
const clientCert = generateCertificate(ca, 'client');
const clientCertPem = pemEncodeCert(clientCert.cert);
const clientKeyPem = pemEncodeKey(clientCert.privateKey);

fs.writeFileSync(path.join(__dirname, "server.ca"), caCertPem);
fs.writeFileSync(path.join(__dirname, "server.key"), serverKeyPem);
fs.writeFileSync(path.join(__dirname, "server.cert"), serverCertPem);

const uWS = require('../dist/uws');
const port = 8086;

const app = uWS.SSLApp({
cert_file_name: path.join(__dirname, "server.cert"),
key_file_name: path.join(__dirname, "server.key"),
ca_file_name: path.join(__dirname, "server.ca")
}).get('/*', (res, req) => {
const clientCert = res.getX509Certificate();
const x509 = new crypto.X509Certificate(clientCert);
if (x509.verify(crypto.createPublicKey(caCertPem))) {
res.end(`Hello World! your certificate is valid!`);
}
else
res.end('Hello World!');

}).listen(port, (token) => {
if (token) {
console.log('Listening to port ' + port);
sendClientRequest();
} else {
console.log('Failed to listen to port ' + port);
}
});

function generateCACertificate() {
const keys = forge.pki.rsa.generateKeyPair(2048);
const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
const attrs = [
{ name: 'commonName', value: 'example.org' },
{ name: 'countryName', value: 'US' },
{ shortName: 'ST', value: 'California' },
{ name: 'localityName', value: 'San Francisco' },
{ name: 'organizationName', value: 'example.org' },
{ shortName: 'OU', value: 'Test' }
];
cert.setSubject(attrs);
cert.setIssuer(attrs);
cert.setExtensions([{
name: 'basicConstraints',
cA: true
}]);
cert.sign(keys.privateKey, forge.md.sha256.create());
return {
privateKey: keys.privateKey,
publicKey: keys.publicKey,
cert: cert
};
}

function generateCertificate(ca, commonName) {
const keys = forge.pki.rsa.generateKeyPair(2048);
const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = '02';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
const attrs = [
{ name: 'commonName', value: commonName }
];
cert.setSubject(attrs);
cert.setIssuer(ca.cert.subject.attributes);
cert.sign(ca.privateKey, forge.md.sha256.create());
return {
privateKey: keys.privateKey,
cert: cert
};
}

function pemEncodeCert(cert) {
return forge.pki.certificateToPem(cert);
}

function pemEncodeKey(key) {
return forge.pki.privateKeyToPem(key);
}

function sendClientRequest() {
const clientOptions = {
hostname: 'localhost',
port: port,
path: '/',
method: 'GET',
key: clientKeyPem,
cert: clientCertPem,
ca: [caCertPem],
rejectUnauthorized: false
};

const req = https.request(clientOptions, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('Response from server:', data);
});
});

req.on('error', (e) => {
console.error('errp', e);
});

req.end();
}
16 changes: 16 additions & 0 deletions src/HttpResponseWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,18 @@ struct HttpResponseWrapper {
}
}

template <int PROTOCOL>
static void res_getX509Certificate(const FunctionCallbackInfo<Value> &args) {
Isolate *isolate = args.GetIsolate();
auto *res = getHttpResponse<PROTOCOL>(args);
if (res) {
void* sslHandle = res->getNativeHandle();
SSL* ssl = static_cast<SSL*>(sslHandle);
std::string x509cert = extractX509PemCertificate(ssl);
args.GetReturnValue().Set(String::NewFromUtf8(isolate, x509cert.c_str(), NewStringType::kNormal).ToLocalChecked());
}
}

/* Returns the current write offset */
template <int SSL>
static void res_getWriteOffset(const FunctionCallbackInfo<Value> &args) {
Expand Down Expand Up @@ -466,6 +478,10 @@ struct HttpResponseWrapper {
}
}

if constexpr (SSL == 1) {
resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getX509Certificate", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getX509Certificate<SSL>));
}

/* Create our template */
Local<Object> resObjectLocal = resTemplateLocal->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();

Expand Down
33 changes: 33 additions & 0 deletions src/Utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#ifndef ADDON_UTILITIES_H
#define ADDON_UTILITIES_H

#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <v8.h>
using namespace v8;

Expand Down Expand Up @@ -169,4 +171,35 @@ class NativeString {
}
};

// Utility function to extract raw certificate data
std::string extractX509PemCertificate(SSL* ssl) {
std::string pemCertificate;

if (!ssl) {
return pemCertificate;
}

// Get the peer certificate
X509* peerCertificate = SSL_get_peer_certificate(ssl);
if (!peerCertificate) {
// No peer certificate available
return pemCertificate;
}

// Convert X509 certificate to PEM format
BIO* bio = BIO_new(BIO_s_mem());
if(bio) {
if (PEM_write_bio_X509(bio, peerCertificate)) {
char* buffer;
long length = BIO_get_mem_data(bio, &buffer);
pemCertificate.assign(buffer, length);
}
BIO_free(bio);
}

// Free the peer certificate
X509_free(peerCertificate);
return pemCertificate;
}

#endif

0 comments on commit 37e4402

Please # to comment.