mirror of
https://github.com/uNetworking/uWebSockets.js.git
synced 2025-12-19 18:10:26 -05:00
feat: expose client's peer certificate der data (#1062)
* 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
This commit is contained in:
134
examples/PeerCertificate.js
vendored
Normal file
134
examples/PeerCertificate.js
vendored
Normal file
@@ -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();
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -169,4 +171,35 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user