Skip to content

Commit d18f15d

Browse files
committed
include SAN in TL certification verification #1421
1 parent 57390c6 commit d18f15d

File tree

5 files changed

+76
-42
lines changed

5 files changed

+76
-42
lines changed

modules/crypt/etc/ber.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ export default class BER {
4545
else if (buffer instanceof Uint8Array)
4646
this.#a = buffer;
4747
else
48-
this.#a = new Uint8Array(new ArrayBuffer(0, {maxByteLength: 0x10000000}));
48+
this.#a = new Uint8Array(new ArrayBuffer(0, {maxByteLength: 16896}));
49+
}
50+
get readable() {
51+
return this.#a.byteLength - this.#i;
4952
}
5053
getTag() {
5154
return this.#a[this.#i++];
@@ -73,7 +76,7 @@ export default class BER {
7376
}
7477
next() {
7578
const i = this.#i;
76-
this.getTag();
79+
this.skip(1);
7780
this.skip(this.getLength());
7881
return this.#a.subarray(i, this.#i)
7982
}
@@ -432,8 +435,8 @@ export default class BER {
432435
case 0x16: // IA5 string
433436
res = String.fromArrayBuffer(b.getChunk(len).slice().buffer);
434437
break;
435-
case 0x17: // ITC time
436438
/*
439+
case 0x17: // ITC time
437440
case 0x18: {// generalized time
438441
let s = String.fromArrayBuffer(b.getChunk(len));
439442
let prefix = ""

modules/crypt/etc/x509.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ const X509 = {
8181
return tbs;
8282
},
8383
getSPK(spki) { // Subject Public Key Info
84-
spki = this._decodeSPKI(spki);
84+
spki = decodeSPKI(spki);
8585
if (!spki)
8686
throw new Error("x509: no SPKI");
8787
spki = new BER(spki);
@@ -175,17 +175,34 @@ const X509 = {
175175
let len = ber.getLength();
176176
let endp = ber.i + len;
177177
while (ber.i < endp) {
178-
if ((ber.getTag() & 0x1f) == 0) {
179-
len = ber.getLength();
180-
return ber.getChunk(len);
181-
}
178+
if ((ber.getTag() & 0x1f) == 0)
179+
return ber.getChunk(ber.getLength());
182180
ber.skip(ber.getLength());
183181
}
184182
}
185183
},
186-
_decodeSPKI(buf) @ "xs_x509_decodeSPKI",
184+
decodeSAN(buf) {
185+
let b = this.decodeExtension(buf, [2, 5, 29, 17]); // Subject Alternative Name
186+
if (!b) return;
187+
188+
b = new BER((new BER(b)).getSequence());
189+
190+
const names = [];
191+
while (b.readable) {
192+
const tag = b.getTag() & 0x7F;
193+
let value = b.getChunk(b.getLength()).slice().buffer;
194+
if ((2 === tag) || (1 === tag) || (6 === tag)) // IA5String: dNSName, rfc822Name, uniformResourceIdentifier
195+
value = String.fromArrayBuffer(value);
196+
names.push({tag, value});
197+
}
198+
199+
return names;
200+
},
187201
decodeExtension(buf, extid) @ "xs_x509_decodeExtension",
188202
};
203+
204+
function decodeSPKI(buf) @ "xs_x509_decodeSPKI";
205+
189206
Object.freeze(X509);
190207

191208
function parseDate(date) {

modules/crypt/ssl/ssl_cert.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class CertificateManager {
111111

112112
return X509.decodeSPKI(data);
113113
}
114-
verify(certs) {
114+
verify(certs, options) {
115115
if (!this.#verify)
116116
return true;
117117

@@ -121,6 +121,41 @@ class CertificateManager {
121121
// this approach calls decodeSPKI once more than necessary in favor of minimizing memory use
122122
for (let i = 0; i < length; i++) {
123123
x509 = X509.decode(certs[i]);
124+
const names = X509.decodeSAN(certs[i]);
125+
if (!names) return false;
126+
127+
let tls_server_name = options.tls_server_name, match;
128+
if (tls_server_name) {
129+
tls_server_name = tls_server_name.toLowerCase();
130+
131+
for (let j = 0; j < names.length; j++) {
132+
let name = names[j];
133+
if (2 === name.tag) { // dNSName
134+
name = name.value.toLowerCase();
135+
if (42 === name.charCodeAt(0)) { // wildcard ("*")
136+
if (name.indexOf("*", 1) > 0) // only one wild card
137+
continue;
138+
let position = tls_server_name.indexOf(".");
139+
if (position < 0) continue;
140+
match = name.slice(1).toLowerCase() === tls_server_name.slice(position);
141+
}
142+
else
143+
match = name.toLowerCase() === tls_server_name;
144+
}
145+
else if (7 === name.tag) { // iPAddress
146+
name = name.value;
147+
if (4 !== name.byteLength) continue; // only handling IPv4 for now
148+
name = (new Uint8Array(name)).join(".");
149+
match = name === tls_server_name;
150+
}
151+
if (match) break;
152+
}
153+
}
154+
if (!match) {
155+
trace(`subjectAltName match failed for ${tls_server_name}\n`);
156+
return false;
157+
}
158+
124159
validity = X509.decodeTBS(x509.tbs).validity;
125160
if (!((validity.from < now) && (now < validity.to))) {
126161
trace("date validation failed on received certificate\n");

modules/crypt/ssl/ssl_handshake.js

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -478,42 +478,19 @@ const handshakeProtocol = {
478478
certificate: {
479479
name: "certificate",
480480
msgType: certificate,
481-
// matchName(re, name) {
482-
// re = re.replace(/\./g, "\\.").replace(/\*/g, "[^.]*");
483-
// var a = name.match(new RegExp("^" + re + "$", "i"));
484-
// return a && a.length == 1;
485-
// },
486-
// verifyHost(session, cert) {
487-
// //@@ this fails because session.socket.host doesn't exist
488-
// var altNames = X509.decodeExtension(cert, 'subjectAlternativeName');
489-
// var hostname = session.socket.host;
490-
// for (var i = 0; i < altNames.length; i++) {
491-
// var name = altNames[i];
492-
// if (typeof name == "string" && this.matchName(name, hostname))
493-
// return true;
494-
// }
495-
// var arr = X509.decodeTBS(cert).subject.match(/CN=([^,]*)/);
496-
// return arr && arr.length > 1 && this.matchName(arr[1], hostname);
497-
// },
498481

499482
unpacketize(session, s) {
500483
session.traceProtocol(this);
501-
let certs = [];
484+
const certs = [];
502485
let ttlSize = s.readChars(3);
503486
while (ttlSize > 0 && s.bytesAvailable > 0) {
504-
let certSize = s.readChars(3);
505-
certs.push(s.readChunk(certSize, true));
506-
ttlSize -= certSize + 3;
487+
const size = s.readChars(3);
488+
certs.push(s.readChunk(size, true));
489+
ttlSize -= size + 3;
507490
}
508-
if (!session.certificateManager.verify(certs))
491+
if (!session.certificateManager.verify(certs, session.options))
509492
throw new TLSError("certificate: auth err");
510493

511-
/*
512-
if (session.options.verifyHost) {
513-
if (!this.verifyHost(session, certs[0]))
514-
throw new TLSError("certificate: bad host");
515-
}
516-
*/
517494
session.peerCert = certs[0].slice(0).buffer; // could we store only the key?
518495
return session.certificateManager.register(session.peerCert); // tail call optimization
519496
},

modules/crypt/ssl/ssl_stream.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class SSLStream {
5252
this.#write = buffer.length;
5353
}
5454
else {
55-
this.#bytes = new Uint8Array(new ArrayBuffer(initial ?? 32, {maxByteLength: 0x10000000}));
55+
this.#bytes = new Uint8Array(new ArrayBuffer(initial ?? 32, {maxByteLength: 16896})); // TLS chunks can't be bigger than 16 KB, so this should be enough (and XS doesn't really use this value)
5656
this.#bytes.i = true;
5757
}
5858
}
@@ -83,11 +83,13 @@ class SSLStream {
8383
this.writeChunk(ArrayBuffer.fromString(s));
8484
}
8585
readChar() {
86-
return this.#read < this.#write ? this.#bytes[this.#read++] : undefined;
86+
if (this.#read >= this.#write)
87+
throw new Error;
88+
return this.#bytes[this.#read++];
8789
}
8890
readChars(n) {
8991
if (this.#read + n > this.#write)
90-
return;
92+
throw new Error;
9193
let v = 0;
9294
while (--n >= 0)
9395
v = (v << 8) | this.#bytes[this.#read++];
@@ -96,7 +98,7 @@ class SSLStream {
9698
readChunk(n, reference) {
9799
const read = this.#read;
98100
if (read + n > this.#write)
99-
return;
101+
throw new Error;
100102

101103
this.#read += n;
102104
if (reference)

0 commit comments

Comments
 (0)