Skip to content

Commit 37e4402

Browse files
authored
feat: expose client's peer certificate der data (uNetworking#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
1 parent 77e43e7 commit 37e4402

File tree

3 files changed

+183
-0
lines changed

3 files changed

+183
-0
lines changed

examples/PeerCertificate.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
const https = require('https');
2+
const forge = require('node-forge');
3+
const crypto = require('crypto');
4+
const fs = require('fs');
5+
const path = require('path');
6+
// Generate CA certificate
7+
const ca = generateCACertificate();
8+
const caCertPem = pemEncodeCert(ca.cert);
9+
10+
// Generate server certificate signed by CA
11+
const serverCert = generateCertificate(ca, 'localhost');
12+
const serverCertPem = pemEncodeCert(serverCert.cert);
13+
const serverKeyPem = pemEncodeKey(serverCert.privateKey);
14+
15+
// Generate client certificate signed by CA
16+
const clientCert = generateCertificate(ca, 'client');
17+
const clientCertPem = pemEncodeCert(clientCert.cert);
18+
const clientKeyPem = pemEncodeKey(clientCert.privateKey);
19+
20+
fs.writeFileSync(path.join(__dirname, "server.ca"), caCertPem);
21+
fs.writeFileSync(path.join(__dirname, "server.key"), serverKeyPem);
22+
fs.writeFileSync(path.join(__dirname, "server.cert"), serverCertPem);
23+
24+
const uWS = require('../dist/uws');
25+
const port = 8086;
26+
27+
const app = uWS.SSLApp({
28+
cert_file_name: path.join(__dirname, "server.cert"),
29+
key_file_name: path.join(__dirname, "server.key"),
30+
ca_file_name: path.join(__dirname, "server.ca")
31+
}).get('/*', (res, req) => {
32+
const clientCert = res.getX509Certificate();
33+
const x509 = new crypto.X509Certificate(clientCert);
34+
if (x509.verify(crypto.createPublicKey(caCertPem))) {
35+
res.end(`Hello World! your certificate is valid!`);
36+
}
37+
else
38+
res.end('Hello World!');
39+
40+
}).listen(port, (token) => {
41+
if (token) {
42+
console.log('Listening to port ' + port);
43+
sendClientRequest();
44+
} else {
45+
console.log('Failed to listen to port ' + port);
46+
}
47+
});
48+
49+
function generateCACertificate() {
50+
const keys = forge.pki.rsa.generateKeyPair(2048);
51+
const cert = forge.pki.createCertificate();
52+
cert.publicKey = keys.publicKey;
53+
cert.serialNumber = '01';
54+
cert.validity.notBefore = new Date();
55+
cert.validity.notAfter = new Date();
56+
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
57+
const attrs = [
58+
{ name: 'commonName', value: 'example.org' },
59+
{ name: 'countryName', value: 'US' },
60+
{ shortName: 'ST', value: 'California' },
61+
{ name: 'localityName', value: 'San Francisco' },
62+
{ name: 'organizationName', value: 'example.org' },
63+
{ shortName: 'OU', value: 'Test' }
64+
];
65+
cert.setSubject(attrs);
66+
cert.setIssuer(attrs);
67+
cert.setExtensions([{
68+
name: 'basicConstraints',
69+
cA: true
70+
}]);
71+
cert.sign(keys.privateKey, forge.md.sha256.create());
72+
return {
73+
privateKey: keys.privateKey,
74+
publicKey: keys.publicKey,
75+
cert: cert
76+
};
77+
}
78+
79+
function generateCertificate(ca, commonName) {
80+
const keys = forge.pki.rsa.generateKeyPair(2048);
81+
const cert = forge.pki.createCertificate();
82+
cert.publicKey = keys.publicKey;
83+
cert.serialNumber = '02';
84+
cert.validity.notBefore = new Date();
85+
cert.validity.notAfter = new Date();
86+
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
87+
const attrs = [
88+
{ name: 'commonName', value: commonName }
89+
];
90+
cert.setSubject(attrs);
91+
cert.setIssuer(ca.cert.subject.attributes);
92+
cert.sign(ca.privateKey, forge.md.sha256.create());
93+
return {
94+
privateKey: keys.privateKey,
95+
cert: cert
96+
};
97+
}
98+
99+
function pemEncodeCert(cert) {
100+
return forge.pki.certificateToPem(cert);
101+
}
102+
103+
function pemEncodeKey(key) {
104+
return forge.pki.privateKeyToPem(key);
105+
}
106+
107+
function sendClientRequest() {
108+
const clientOptions = {
109+
hostname: 'localhost',
110+
port: port,
111+
path: '/',
112+
method: 'GET',
113+
key: clientKeyPem,
114+
cert: clientCertPem,
115+
ca: [caCertPem],
116+
rejectUnauthorized: false
117+
};
118+
119+
const req = https.request(clientOptions, (res) => {
120+
let data = '';
121+
res.on('data', (chunk) => {
122+
data += chunk;
123+
});
124+
res.on('end', () => {
125+
console.log('Response from server:', data);
126+
});
127+
});
128+
129+
req.on('error', (e) => {
130+
console.error('errp', e);
131+
});
132+
133+
req.end();
134+
}

src/HttpResponseWrapper.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,18 @@ struct HttpResponseWrapper {
183183
}
184184
}
185185

186+
template <int PROTOCOL>
187+
static void res_getX509Certificate(const FunctionCallbackInfo<Value> &args) {
188+
Isolate *isolate = args.GetIsolate();
189+
auto *res = getHttpResponse<PROTOCOL>(args);
190+
if (res) {
191+
void* sslHandle = res->getNativeHandle();
192+
SSL* ssl = static_cast<SSL*>(sslHandle);
193+
std::string x509cert = extractX509PemCertificate(ssl);
194+
args.GetReturnValue().Set(String::NewFromUtf8(isolate, x509cert.c_str(), NewStringType::kNormal).ToLocalChecked());
195+
}
196+
}
197+
186198
/* Returns the current write offset */
187199
template <int SSL>
188200
static void res_getWriteOffset(const FunctionCallbackInfo<Value> &args) {
@@ -466,6 +478,10 @@ struct HttpResponseWrapper {
466478
}
467479
}
468480

481+
if constexpr (SSL == 1) {
482+
resTemplateLocal->PrototypeTemplate()->Set(String::NewFromUtf8(isolate, "getX509Certificate", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, res_getX509Certificate<SSL>));
483+
}
484+
469485
/* Create our template */
470486
Local<Object> resObjectLocal = resTemplateLocal->GetFunction(isolate->GetCurrentContext()).ToLocalChecked()->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
471487

src/Utilities.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#ifndef ADDON_UTILITIES_H
1919
#define ADDON_UTILITIES_H
2020

21+
#include <openssl/ssl.h>
22+
#include <openssl/x509.h>
2123
#include <v8.h>
2224
using namespace v8;
2325

@@ -169,4 +171,35 @@ class NativeString {
169171
}
170172
};
171173

174+
// Utility function to extract raw certificate data
175+
std::string extractX509PemCertificate(SSL* ssl) {
176+
std::string pemCertificate;
177+
178+
if (!ssl) {
179+
return pemCertificate;
180+
}
181+
182+
// Get the peer certificate
183+
X509* peerCertificate = SSL_get_peer_certificate(ssl);
184+
if (!peerCertificate) {
185+
// No peer certificate available
186+
return pemCertificate;
187+
}
188+
189+
// Convert X509 certificate to PEM format
190+
BIO* bio = BIO_new(BIO_s_mem());
191+
if(bio) {
192+
if (PEM_write_bio_X509(bio, peerCertificate)) {
193+
char* buffer;
194+
long length = BIO_get_mem_data(bio, &buffer);
195+
pemCertificate.assign(buffer, length);
196+
}
197+
BIO_free(bio);
198+
}
199+
200+
// Free the peer certificate
201+
X509_free(peerCertificate);
202+
return pemCertificate;
203+
}
204+
172205
#endif

0 commit comments

Comments
 (0)