1// Flags: --security-revert=CVE-2021-44532 2'use strict'; 3 4const common = require('../common'); 5if (!common.hasCrypto) 6 common.skip('missing crypto'); 7 8const assert = require('assert'); 9const tls = require('tls'); 10const fixtures = require('../common/fixtures'); 11 12const { hasOpenSSL3 } = common; 13 14// Test escaping rules for subject alternative names. 15{ 16 const expectedSANs = [ 17 'DNS:good.example.com, DNS:evil.example.com', 18 // URIs should not require escaping. 19 'URI:http://example.com/', 20 'URI:http://example.com/?a=b&c=d', 21 // Unless they contain commas. 22 'URI:http://example.com/a,b', 23 // Percent encoding should not require escaping. 24 'URI:http://example.com/a%2Cb', 25 // Malicious attempts should be escaped. 26 'URI:http://example.com/a, DNS:good.example.com', 27 // Non-ASCII characters in DNS names should be treated as Latin-1. 28 'DNS:ex�mple.com', 29 // It should not be possible to cause unescaping without escaping. 30 'DNS:"evil.example.com"', 31 // IPv4 addresses should be represented as usual. 32 'IP Address:8.8.8.8', 33 'IP Address:8.8.4.4', 34 // For backward-compatibility, include invalid IP address lengths. 35 hasOpenSSL3 ? 'IP Address:<invalid length=5>' : 'IP Address:<invalid>', 36 hasOpenSSL3 ? 'IP Address:<invalid length=6>' : 'IP Address:<invalid>', 37 // IPv6 addresses are represented as OpenSSL does. 38 'IP Address:A0B:C0D:E0F:0:0:0:7A7B:7C7D', 39 // Regular email addresses don't require escaping. 40 'email:foo@example.com', 41 // ... but should be escaped if they contain commas. 42 'email:foo@example.com, DNS:good.example.com', 43 'DirName:/C=DE/L=Hannover', 44 // TODO(tniessen): support UTF8 in DirName 45 'DirName:/C=DE/L=M\\xC3\\xBCnchen', 46 'DirName:/C=DE/L=Berlin, DNS:good.example.com', 47 'DirName:/C=DE/L=Berlin, DNS:good.example.com\\x00' + 48 'evil.example.com', 49 'DirName:/C=DE/L=Berlin, DNS:good.example.com\\\\x00' + 50 'evil.example.com', 51 // These next two tests might be surprising. OpenSSL applies its own rules 52 // first, which introduce backslashes, which activate node's escaping. 53 // Unfortunately, there are also differences between OpenSSL 1.1.1 and 3.0. 54 'DirName:/C=DE/L=Berlin\\x0D\\x0A', 55 hasOpenSSL3 ? 56 'DirName:/C=DE/L=Berlin\\/CN=good.example.com' : 57 'DirName:/C=DE/L=Berlin/CN=good.example.com', 58 // TODO(tniessen): even OIDs that are well-known (such as the following, 59 // which is sha256WithRSAEncryption) should be represented numerically only. 60 'Registered ID:sha256WithRSAEncryption', 61 // This is an OID that will likely never be assigned to anything, thus 62 // OpenSSL hould not know it. 63 'Registered ID:1.3.9999.12.34', 64 hasOpenSSL3 ? 65 'othername: XmppAddr::abc123' : 66 'othername:<unsupported>', 67 hasOpenSSL3 ? 68 'othername: XmppAddr::abc123, DNS:good.example.com' : 69 'othername:<unsupported>', 70 hasOpenSSL3 ? 71 null : 72 'othername:<unsupported>', 73 // This is unsupported because the OID is not recognized. 74 hasOpenSSL3 ? 75 'othername: 1.3.9999.12.34::abc123' : 76 'othername:<unsupported>', 77 hasOpenSSL3 ? 'othername: SRVName::abc123' : 'othername:<unsupported>', 78 // This is unsupported because it is an SRVName with a UTF8String value, 79 // which is not allowed for SRVName. 80 hasOpenSSL3 ? 81 null : 'othername:<unsupported>', 82 hasOpenSSL3 ? 83 null : 84 'othername:<unsupported>', 85 ]; 86 87 const serverKey = fixtures.readSync('x509-escaping/server-key.pem', 'utf8'); 88 89 for (let i = 0; i < expectedSANs.length; i++) { 90 const pem = fixtures.readSync(`x509-escaping/alt-${i}-cert.pem`, 'utf8'); 91 92 // X509Certificate interface is not supported in v12.x & v14.x. Disable 93 // checks for subjectAltName with expectedSANs. The testcase is ported 94 // from v17.x 95 // 96 // Test the subjectAltName property of the X509Certificate API. 97 // const cert = new X509Certificate(pem); 98 // assert.strictEqual(cert.subjectAltName, expectedSANs[i]); 99 100 // Test that the certificate obtained by checkServerIdentity has the correct 101 // subjectaltname property. 102 const server = tls.createServer({ 103 key: serverKey, 104 cert: pem, 105 }, common.mustCall((conn) => { 106 conn.destroy(); 107 server.close(); 108 })).listen(common.mustCall(() => { 109 const { port } = server.address(); 110 tls.connect(port, { 111 ca: pem, 112 servername: 'example.com', 113 checkServerIdentity: (hostname, peerCert) => { 114 assert.strictEqual(hostname, 'example.com'); 115 assert.strictEqual(peerCert.subjectaltname, expectedSANs[i]); 116 }, 117 }, common.mustCall()); 118 })); 119 } 120} 121 122// Test escaping rules for authority info access. 123{ 124 const expectedInfoAccess = [ 125 { 126 text: 'OCSP - URI:http://good.example.com/\n' + 127 'OCSP - URI:http://evil.example.com/', 128 legacy: { 129 'OCSP - URI': [ 130 'http://good.example.com/', 131 'http://evil.example.com/', 132 ], 133 }, 134 }, 135 { 136 text: 'CA Issuers - URI:http://ca.example.com/\n' + 137 'OCSP - URI:http://evil.example.com\n' + 138 'OCSP - DNS:good.example.com\n' + 139 'OCSP - URI:http://ca.nodejs.org/ca.cert', 140 legacy: { 141 'CA Issuers - URI': [ 142 'http://ca.example.com/', 143 ], 144 'OCSP - DNS': [ 145 'good.example.com', 146 ], 147 'OCSP - URI': [ 148 'http://evil.example.com', 149 'http://ca.nodejs.org/ca.cert', 150 ], 151 }, 152 }, 153 { 154 text: '1.3.9999.12.34 - URI:http://ca.example.com/', 155 legacy: { 156 '1.3.9999.12.34 - URI': [ 157 'http://ca.example.com/', 158 ], 159 }, 160 }, 161 hasOpenSSL3 ? { 162 text: 'OCSP - othername: XmppAddr::good.example.com\n' + 163 'OCSP - othername: 1.3.9999.12.34::abc123\n' + 164 'OCSP - othername: SRVName::abc123', 165 legacy: { 166 'OCSP - othername': [ 167 ' XmppAddr::good.example.com', 168 ' 1.3.9999.12.34::abc123', 169 ' SRVName::abc123', 170 ], 171 }, 172 } : { 173 text: 'OCSP - othername:<unsupported>\n' + 174 'OCSP - othername:<unsupported>\n' + 175 'OCSP - othername:<unsupported>', 176 legacy: { 177 'OCSP - othername': [ 178 '<unsupported>', 179 '<unsupported>', 180 '<unsupported>', 181 ], 182 }, 183 }, 184 hasOpenSSL3 ? { 185 text: null, 186 legacy: null, 187 } : { 188 text: 'OCSP - othername:<unsupported>', 189 legacy: { 190 'OCSP - othername': [ 191 '<unsupported>', 192 ] 193 }, 194 }, 195 ]; 196 197 const serverKey = fixtures.readSync('x509-escaping/server-key.pem', 'utf8'); 198 199 for (let i = 0; i < expectedInfoAccess.length; i++) { 200 const pem = fixtures.readSync(`x509-escaping/info-${i}-cert.pem`, 'utf8'); 201 const expected = expectedInfoAccess[i]; 202 203 // X509Certificate interface is not supported in v12.x & v14.x. Disable 204 // checks for cert.infoAccess with expected text. The testcase is ported 205 // from v17.x 206 // 207 // Test the subjectAltName property of the X509Certificate API. 208 // const cert = new X509Certificate(pem); 209 // assert.strictEqual(cert.infoAccess, expected.text ? 210 // `${expected.text}${hasOpenSSL3 ? '' : '\n'}` : 211 // expected.text); 212 213 // Test that the certificate obtained by checkServerIdentity has the correct 214 // subjectaltname property. 215 const server = tls.createServer({ 216 key: serverKey, 217 cert: pem, 218 }, common.mustCall((conn) => { 219 conn.destroy(); 220 server.close(); 221 })).listen(common.mustCall(() => { 222 const { port } = server.address(); 223 tls.connect(port, { 224 ca: pem, 225 servername: 'example.com', 226 checkServerIdentity: (hostname, peerCert) => { 227 assert.strictEqual(hostname, 'example.com'); 228 assert.deepStrictEqual(peerCert.infoAccess, 229 expected.legacy ? 230 Object.assign(Object.create(null), 231 expected.legacy) : 232 expected.legacy); 233 }, 234 }, common.mustCall()); 235 })); 236 } 237} 238 239// The internal parsing logic must match the JSON specification exactly. 240{ 241 // This list is partially based on V8's own JSON tests. 242 const invalidJSON = [ 243 '"\\a invalid escape"', 244 '"\\v invalid escape"', 245 '"\\\' invalid escape"', 246 '"\\x42 invalid escape"', 247 '"\\u202 invalid escape"', 248 '"\\012 invalid escape"', 249 '"Unterminated string', 250 '"Unterminated string\\"', 251 '"Unterminated string\\\\\\"', 252 '"\u0000 control character"', 253 '"\u001e control character"', 254 '"\u001f control character"', 255 ]; 256 257 for (const invalidStringLiteral of invalidJSON) { 258 // Usually, checkServerIdentity returns an error upon verification failure. 259 // In this case, however, it should throw an error since this is not a 260 // verification error. Node.js itself will never produce invalid JSON string 261 // literals, so this can only happen when users construct invalid subject 262 // alternative name strings (that do not follow escaping rules). 263 assert.throws(() => { 264 tls.checkServerIdentity('example.com', { 265 subjectaltname: `DNS:${invalidStringLiteral}`, 266 }); 267 }, { 268 code: 'ERR_TLS_CERT_ALTNAME_FORMAT', 269 message: 'Invalid subject alternative name string' 270 }); 271 } 272} 273 274// While node does not produce commas within SAN entries, it should parse them 275// correctly (i.e., not simply split at commas). 276{ 277 // Regardless of the quotes, splitting this SAN string at commas would 278 // cause checkServerIdentity to see 'DNS:b.example.com' and thus to accept 279 // the certificate for b.example.com. 280 const san = 'DNS:"a.example.com, DNS:b.example.com, DNS:c.example.com"'; 281 282 // This is what node used to do, and which is not correct! 283 const hostname = 'b.example.com'; 284 assert.strictEqual(san.split(', ')[1], `DNS:${hostname}`); 285 286 // The new implementation should parse the string correctly. 287 const err = tls.checkServerIdentity(hostname, { subjectaltname: san }); 288 assert(err); 289 assert.strictEqual(err.code, 'ERR_TLS_CERT_ALTNAME_INVALID'); 290 assert.strictEqual(err.message, 'Hostname/IP does not match certificate\'s ' + 291 'altnames: Host: b.example.com. is not in ' + 292 'the cert\'s altnames: DNS:"a.example.com, ' + 293 'DNS:b.example.com, DNS:c.example.com"'); 294} 295 296// The subject MUST be ignored if a dNSName subject alternative name exists. 297{ 298 const key = fixtures.readKey('incorrect_san_correct_subject-key.pem'); 299 const cert = fixtures.readKey('incorrect_san_correct_subject-cert.pem'); 300 301 // The hostname is the CN, but not a SAN entry. 302 const servername = 'good.example.com'; 303 304 // X509Certificate interface is not supported in v12.x & v14.x. Disable 305 // checks for certX509.subject and certX509.subjectAltName with expected 306 // value. The testcase is ported from v17.x 307 // 308 // const certX509 = new X509Certificate(cert); 309 // assert.strictEqual(certX509.subject, `CN=${servername}`); 310 // assert.strictEqual(certX509.subjectAltName, 'DNS:evil.example.com'); 311 312 // Try connecting to a server that uses the self-signed certificate. 313 const server = tls.createServer({ key, cert }, common.mustNotCall()); 314 server.listen(common.mustCall(() => { 315 const { port } = server.address(); 316 const socket = tls.connect(port, { 317 ca: cert, 318 servername, 319 }, common.mustNotCall()); 320 socket.on('error', common.mustCall((err) => { 321 assert.strictEqual(err.code, 'ERR_TLS_CERT_ALTNAME_INVALID'); 322 assert.strictEqual(err.message, 'Hostname/IP does not match ' + 323 "certificate's altnames: Host: " + 324 "good.example.com. is not in the cert's" + 325 ' altnames: DNS:evil.example.com'); 326 })); 327 })).unref(); 328} 329 330// The subject MUST NOT be ignored if no dNSName subject alternative name 331// exists, even if other subject alternative names exist. 332{ 333 const key = fixtures.readKey('irrelevant_san_correct_subject-key.pem'); 334 const cert = fixtures.readKey('irrelevant_san_correct_subject-cert.pem'); 335 336 // The hostname is the CN, but there is no dNSName SAN entry. 337 const servername = 'good.example.com'; 338 339 // X509Certificate interface is not supported in v12.x & v14.x. Disable 340 // checks for certX509.subject and certX509.subjectAltName with expected 341 // value. The testcase is ported from v17.x 342 // 343 // const certX509 = new X509Certificate(cert); 344 // assert.strictEqual(certX509.subject, `CN=${servername}`); 345 // assert.strictEqual(certX509.subjectAltName, 'IP Address:1.2.3.4'); 346 347 // Connect to a server that uses the self-signed certificate. 348 const server = tls.createServer({ key, cert }, common.mustCall((socket) => { 349 socket.destroy(); 350 server.close(); 351 })).listen(common.mustCall(() => { 352 const { port } = server.address(); 353 tls.connect(port, { 354 ca: cert, 355 servername, 356 }, common.mustCall(() => { 357 // Do nothing, the server will close the connection. 358 })); 359 })); 360} 361