1// Flags: --expose-internals 2'use strict'; 3const common = require('../common'); 4 5if (!common.hasCrypto) 6 common.skip('missing crypto'); 7 8const { 9 X509Certificate, 10 createPrivateKey, 11 generateKeyPairSync, 12 createSign, 13} = require('crypto'); 14 15const { 16 isX509Certificate 17} = require('internal/crypto/x509'); 18 19const assert = require('assert'); 20const fixtures = require('../common/fixtures'); 21const { readFileSync } = require('fs'); 22 23const cert = readFileSync(fixtures.path('keys', 'agent1-cert.pem')); 24const key = readFileSync(fixtures.path('keys', 'agent1-key.pem')); 25const ca = readFileSync(fixtures.path('keys', 'ca1-cert.pem')); 26 27const privateKey = createPrivateKey(key); 28 29[1, {}, false, null].forEach((i) => { 30 assert.throws(() => new X509Certificate(i), { 31 code: 'ERR_INVALID_ARG_TYPE' 32 }); 33}); 34 35const subjectCheck = `C=US 36ST=CA 37L=SF 38O=Joyent 39OU=Node.js 40CN=agent1 41emailAddress=ry@tinyclouds.org`; 42 43const issuerCheck = `C=US 44ST=CA 45L=SF 46O=Joyent 47OU=Node.js 48CN=ca1 49emailAddress=ry@tinyclouds.org`; 50 51let infoAccessCheck = `OCSP - URI:http://ocsp.nodejs.org/ 52CA Issuers - URI:http://ca.nodejs.org/ca.cert`; 53if (!common.hasOpenSSL3) 54 infoAccessCheck += '\n'; 55 56const der = Buffer.from( 57 '308203e8308202d0a0030201020214147d36c1c2f74206de9fab5f2226d78adb00a42630' + 58 '0d06092a864886f70d01010b0500307a310b3009060355040613025553310b3009060355' + 59 '04080c024341310b300906035504070c025346310f300d060355040a0c064a6f79656e74' + 60 '3110300e060355040b0c074e6f64652e6a73310c300a06035504030c036361313120301e' + 61 '06092a864886f70d010901161172794074696e79636c6f7564732e6f72673020170d3232' + 62 '303930333231343033375a180f32323936303631373231343033375a307d310b30090603' + 63 '55040613025553310b300906035504080c024341310b300906035504070c025346310f30' + 64 '0d060355040a0c064a6f79656e743110300e060355040b0c074e6f64652e6a73310f300d' + 65 '06035504030c066167656e74313120301e06092a864886f70d010901161172794074696e' + 66 '79636c6f7564732e6f726730820122300d06092a864886f70d01010105000382010f0030' + 67 '82010a0282010100d456320afb20d3827093dc2c4284ed04dfbabd56e1ddae529e28b790' + 68 'cd4256db273349f3735ffd337c7a6363ecca5a27b7f73dc7089a96c6d886db0c62388f1c' + 69 'dd6a963afcd599d5800e587a11f908960f84ed50ba25a28303ecda6e684fbe7baedc9ce8' + 70 '801327b1697af25097cee3f175e400984c0db6a8eb87be03b4cf94774ba56fffc8c63c68' + 71 'd6adeb60abbe69a7b14ab6a6b9e7baa89b5adab8eb07897c07f6d4fa3d660dff574107d2' + 72 '8e8f63467a788624c574197693e959cea1362ffae1bba10c8c0d88840abfef103631b2e8' + 73 'f5c39b5548a7ea57e8a39f89291813f45a76c448033a2b7ed8403f4baa147cf35e2d2554' + 74 'aa65ce49695797095bf4dc6b0203010001a361305f305d06082b06010505070101045130' + 75 '4f302306082b060105050730018617687474703a2f2f6f6373702e6e6f64656a732e6f72' + 76 '672f302806082b06010505073002861c687474703a2f2f63612e6e6f64656a732e6f7267' + 77 '2f63612e63657274300d06092a864886f70d01010b05000382010100c3349810632ccb7d' + 78 'a585de3ed51e34ed154f0f7215608cf2701c00eda444dc2427072c8aca4da6472c1d9e68' + 79 'f177f99a90a8b5dbf3884586d61cb1c14ea7016c8d38b70d1b46b42947db30edc1e9961e' + 80 'd46c0f0e35da427bfbe52900771817e733b371adf19e12137235141a34347db0dfc05579' + 81 '8b1f269f3bdf5e30ce35d1339d56bb3c570de9096215433047f87ca42447b44e7e6b5d0e' + 82 '48f7894ab186f85b6b1a74561b520952fea888617f32f582afce1111581cd63efcc68986' + 83 '00d248bb684dedb9c3d6710c38de9e9bc21f9c3394b729d5f707d64ea890603e5989f8fa' + 84 '59c19ad1a00732e7adc851b89487cc00799dde068aa64b3b8fd976e8bc113ef2', 85 'hex'); 86 87{ 88 const x509 = new X509Certificate(cert); 89 90 assert(isX509Certificate(x509)); 91 92 assert(!x509.ca); 93 assert.strictEqual(x509.subject, subjectCheck); 94 assert.strictEqual(x509.subjectAltName, undefined); 95 assert.strictEqual(x509.issuer, issuerCheck); 96 assert.strictEqual(x509.infoAccess, infoAccessCheck); 97 assert.strictEqual(x509.validFrom, 'Sep 3 21:40:37 2022 GMT'); 98 assert.strictEqual(x509.validTo, 'Jun 17 21:40:37 2296 GMT'); 99 assert.strictEqual( 100 x509.fingerprint, 101 '8B:89:16:C4:99:87:D2:13:1A:64:94:36:38:A5:32:01:F0:95:3B:53'); 102 assert.strictEqual( 103 x509.fingerprint256, 104 '2C:62:59:16:91:89:AB:90:6A:3E:98:88:A6:D3:C5:58:58:6C:AE:FF:9C:33:' + 105 '22:7C:B6:77:D3:34:E7:53:4B:05' 106 ); 107 assert.strictEqual( 108 x509.fingerprint512, 109 '0B:6F:D0:4D:6B:22:53:99:66:62:51:2D:2C:96:F2:58:3F:95:1C:CC:4C:44:' + 110 '9D:B5:59:AA:AD:A8:F6:2A:24:8A:BB:06:A5:26:42:52:30:A3:37:61:30:A9:' + 111 '5A:42:63:E0:21:2F:D6:70:63:07:96:6F:27:A7:78:12:08:02:7A:8B' 112 ); 113 assert.strictEqual(x509.keyUsage, undefined); 114 assert.strictEqual(x509.serialNumber, '147D36C1C2F74206DE9FAB5F2226D78ADB00A426'); 115 116 assert.deepStrictEqual(x509.raw, der); 117 118 assert(x509.publicKey); 119 assert.strictEqual(x509.publicKey.type, 'public'); 120 121 assert.strictEqual(x509.toString().replaceAll('\r\n', '\n'), 122 cert.toString().replaceAll('\r\n', '\n')); 123 assert.strictEqual(x509.toJSON(), x509.toString()); 124 125 assert(x509.checkPrivateKey(privateKey)); 126 assert.throws(() => x509.checkPrivateKey(x509.publicKey), { 127 code: 'ERR_INVALID_ARG_VALUE' 128 }); 129 130 assert.strictEqual(x509.checkIP('127.0.0.1'), undefined); 131 assert.strictEqual(x509.checkIP('::'), undefined); 132 assert.strictEqual(x509.checkHost('agent1'), 'agent1'); 133 assert.strictEqual(x509.checkHost('agent2'), undefined); 134 assert.strictEqual(x509.checkEmail('ry@tinyclouds.org'), 'ry@tinyclouds.org'); 135 assert.strictEqual(x509.checkEmail('sally@example.com'), undefined); 136 assert.throws(() => x509.checkHost('agent\x001'), { 137 code: 'ERR_INVALID_ARG_VALUE' 138 }); 139 assert.throws(() => x509.checkIP('[::]'), { 140 code: 'ERR_INVALID_ARG_VALUE' 141 }); 142 assert.throws(() => x509.checkEmail('not\x00hing'), { 143 code: 'ERR_INVALID_ARG_VALUE' 144 }); 145 146 [1, false, null].forEach((i) => { 147 assert.throws(() => x509.checkHost('agent1', i), { 148 code: 'ERR_INVALID_ARG_TYPE' 149 }); 150 assert.throws(() => x509.checkHost('agent1', { subject: i }), { 151 code: 'ERR_INVALID_ARG_TYPE' 152 }); 153 }); 154 155 [ 156 'wildcards', 157 'partialWildcards', 158 'multiLabelWildcards', 159 'singleLabelSubdomains', 160 ].forEach((key) => { 161 [1, '', null, {}].forEach((i) => { 162 assert.throws(() => x509.checkHost('agent1', { [key]: i }), { 163 code: 'ERR_INVALID_ARG_TYPE' 164 }); 165 }); 166 }); 167 168 const ca_cert = new X509Certificate(ca); 169 170 assert(x509.checkIssued(ca_cert)); 171 assert(!x509.checkIssued(x509)); 172 assert(x509.verify(ca_cert.publicKey)); 173 assert(!x509.verify(x509.publicKey)); 174 175 assert.throws(() => x509.checkIssued({}), { 176 code: 'ERR_INVALID_ARG_TYPE' 177 }); 178 assert.throws(() => x509.checkIssued(''), { 179 code: 'ERR_INVALID_ARG_TYPE' 180 }); 181 assert.throws(() => x509.verify({}), { 182 code: 'ERR_INVALID_ARG_TYPE' 183 }); 184 assert.throws(() => x509.verify(''), { 185 code: 'ERR_INVALID_ARG_TYPE' 186 }); 187 assert.throws(() => x509.verify(privateKey), { 188 code: 'ERR_INVALID_ARG_VALUE' 189 }); 190 191 { 192 // https://github.com/nodejs/node/issues/45377 193 // https://github.com/nodejs/node/issues/45485 194 // Confirm failures of 195 // X509Certificate:verify() 196 // X509Certificate:CheckPrivateKey() 197 // X509Certificate:CheckCA() 198 // X509Certificate:CheckIssued() 199 // X509Certificate:ToLegacy() 200 // do not affect other functions that use OpenSSL. 201 // Subsequent calls to e.g. createPrivateKey should not throw. 202 const keyPair = generateKeyPairSync('ed25519'); 203 assert(!x509.verify(keyPair.publicKey)); 204 createPrivateKey(key); 205 assert(!x509.checkPrivateKey(keyPair.privateKey)); 206 createPrivateKey(key); 207 const certPem = ` 208-----BEGIN CERTIFICATE----- 209MIID6zCCAtOgAwIBAgIUTUREAaNcNL0zPkxAlMX0GJtJ/FcwDQYJKoZIhvcNAQEN 210BQAwgYkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMREwDwYDVQQH 211DAhDYXJsc2JhZDEPMA0GA1UECgwGVmlhc2F0MR0wGwYDVQQLDBRWaWFzYXQgU2Vj 212dXJlIE1vYmlsZTEiMCAGA1UEAwwZSGFja2VyT25lIHJlcG9ydCAjMTgwODU5NjAi 213GA8yMDIyMTIxNjAwMDAwMFoYDzIwMjMxMjE1MjM1OTU5WjCBiTELMAkGA1UEBhMC 214VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExETAPBgNVBAcMCENhcmxzYmFkMQ8wDQYD 215VQQKDAZWaWFzYXQxHTAbBgNVBAsMFFZpYXNhdCBTZWN1cmUgTW9iaWxlMSIwIAYD 216VQQDDBlIYWNrZXJPbmUgcmVwb3J0ICMxODA4NTk2MIIBIjANBgkqhkiG9w0BAQEF 217AAOCAQ8AMIIBCgKCAQEA6I7RBPm4E/9rIrCHV5lfsHI/yYzXtACJmoyP8OMkjbeB 218h21oSJJF9FEnbivk6bYaHZIPasa+lSAydRM2rbbmfhF+jQoWYCIbV2ztrbFR70S1 219wAuJrlYYm+8u+1HUru5UBZWUr/p1gFtv3QjpA8+43iwE4pXytTBKPXFo1f5iZwGI 220D5Bz6DohT7Tyb8cpQ1uMCMCT0EJJ4n8wUrvfBgwBO94O4qlhs9vYgnDKepJDjptc 221uSuEpvHALO8+EYkQ7nkM4Xzl/WK1yFtxxE93Jvd1OvViDGVrRVfsq+xYTKknGLX0 222QIeoDDnIr0OjlYPd/cqyEgMcFyFxwDSzSc1esxdCpQIDAQABo0UwQzAdBgNVHQ4E 223FgQUurygsEKdtQk0T+sjM0gEURdveRUwEgYDVR0TAQH/BAgwBgEB/wIB/zAOBgNV 224HQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQENBQADggEBAH7mIIXiQsQ4/QGNNFOQzTgP 225/bUbMSZJsY5TPAvS9rF9yQVzs4dJZnQk5kEb/qrDQSe27oP0L0hfFm1wTGy+aKfa 226BVGHdRmmvHtDUPLA9URCFShqKuS+GXp+6zt7dyZPRrPmiZaciiCMPHOnx59xSdPm 227AZG8cD3fmK2ThC4FAMyvRb0qeobka3s22xTQ2kjwJO5gykTkZ+BR6SzRHQTjYMuT 228iry9Bu8Kvbzu3r5n+/bmNz+xRNmEeehgT2qsHjA5b2YBVTr9MdN9Ro3H3saA3upr 229oans248kpal88CGqsN2so/wZKxVnpiXlPHMdiNL7hRSUqlHkUi07FrP2Htg8kjI= 230-----END CERTIFICATE-----`.trim(); 231 const c = new X509Certificate(certPem); 232 assert(!c.ca); 233 const signer = createSign('SHA256'); 234 assert(signer.sign(key, 'hex')); 235 236 const c1 = new X509Certificate(certPem); 237 assert(!c1.checkIssued(c1)); 238 const signer1 = createSign('SHA256'); 239 assert(signer1.sign(key, 'hex')); 240 241 const c2 = new X509Certificate(certPem); 242 assert(c2.toLegacyObject()); 243 const signer2 = createSign('SHA256'); 244 assert(signer2.sign(key, 'hex')); 245 } 246 247 // X509Certificate can be cloned via MessageChannel/MessagePort 248 const mc = new MessageChannel(); 249 mc.port1.onmessage = common.mustCall(({ data }) => { 250 assert(isX509Certificate(data)); 251 assert.deepStrictEqual(data.raw, x509.raw); 252 mc.port1.close(); 253 }); 254 mc.port2.postMessage(x509); 255 256 // Verify that legacy encoding works 257 const legacyObjectCheck = { 258 subject: Object.assign(Object.create(null), { 259 C: 'US', 260 ST: 'CA', 261 L: 'SF', 262 O: 'Joyent', 263 OU: 'Node.js', 264 CN: 'agent1', 265 emailAddress: 'ry@tinyclouds.org', 266 }), 267 issuer: Object.assign(Object.create(null), { 268 C: 'US', 269 ST: 'CA', 270 L: 'SF', 271 O: 'Joyent', 272 OU: 'Node.js', 273 CN: 'ca1', 274 emailAddress: 'ry@tinyclouds.org', 275 }), 276 infoAccess: Object.assign(Object.create(null), { 277 'OCSP - URI': ['http://ocsp.nodejs.org/'], 278 'CA Issuers - URI': ['http://ca.nodejs.org/ca.cert'] 279 }), 280 modulus: 'D456320AFB20D3827093DC2C4284ED04DFBABD56E1DDAE529E28B790CD42' + 281 '56DB273349F3735FFD337C7A6363ECCA5A27B7F73DC7089A96C6D886DB0C' + 282 '62388F1CDD6A963AFCD599D5800E587A11F908960F84ED50BA25A28303EC' + 283 'DA6E684FBE7BAEDC9CE8801327B1697AF25097CEE3F175E400984C0DB6A8' + 284 'EB87BE03B4CF94774BA56FFFC8C63C68D6ADEB60ABBE69A7B14AB6A6B9E7' + 285 'BAA89B5ADAB8EB07897C07F6D4FA3D660DFF574107D28E8F63467A788624' + 286 'C574197693E959CEA1362FFAE1BBA10C8C0D88840ABFEF103631B2E8F5C3' + 287 '9B5548A7EA57E8A39F89291813F45A76C448033A2B7ED8403F4BAA147CF3' + 288 '5E2D2554AA65CE49695797095BF4DC6B', 289 bits: 2048, 290 exponent: '0x10001', 291 valid_from: 'Sep 3 21:40:37 2022 GMT', 292 valid_to: 'Jun 17 21:40:37 2296 GMT', 293 fingerprint: '8B:89:16:C4:99:87:D2:13:1A:64:94:36:38:A5:32:01:F0:95:3B:53', 294 fingerprint256: 295 '2C:62:59:16:91:89:AB:90:6A:3E:98:88:A6:D3:C5:58:58:6C:AE:FF:9C:33:' + 296 '22:7C:B6:77:D3:34:E7:53:4B:05', 297 fingerprint512: 298 '51:62:18:39:E2:E2:77:F5:86:11:E8:C0:CA:54:43:7C:76:83:19:05:D0:03:' + 299 '24:21:B8:EB:14:61:FB:24:16:EB:BD:51:1A:17:91:04:30:03:EB:68:5F:DC:' + 300 '86:E1:D1:7C:FB:AF:78:ED:63:5F:29:9C:32:AF:A1:8E:22:96:D1:02', 301 serialNumber: '147D36C1C2F74206DE9FAB5F2226D78ADB00A426' 302 }; 303 304 const legacyObject = x509.toLegacyObject(); 305 306 assert.deepStrictEqual(legacyObject.raw, x509.raw); 307 assert.deepStrictEqual(legacyObject.subject, legacyObjectCheck.subject); 308 assert.deepStrictEqual(legacyObject.issuer, legacyObjectCheck.issuer); 309 assert.deepStrictEqual(legacyObject.infoAccess, legacyObjectCheck.infoAccess); 310 assert.strictEqual(legacyObject.modulus, legacyObjectCheck.modulus); 311 assert.strictEqual(legacyObject.bits, legacyObjectCheck.bits); 312 assert.strictEqual(legacyObject.exponent, legacyObjectCheck.exponent); 313 assert.strictEqual(legacyObject.valid_from, legacyObjectCheck.valid_from); 314 assert.strictEqual(legacyObject.valid_to, legacyObjectCheck.valid_to); 315 assert.strictEqual(legacyObject.fingerprint, legacyObjectCheck.fingerprint); 316 assert.strictEqual( 317 legacyObject.fingerprint256, 318 legacyObjectCheck.fingerprint256); 319 assert.strictEqual( 320 legacyObject.serialNumber, 321 legacyObjectCheck.serialNumber); 322} 323 324{ 325 // This X.509 Certificate can be parsed by OpenSSL because it contains a 326 // structurally sound TBSCertificate structure. However, the SPKI field of the 327 // TBSCertificate contains the subjectPublicKey as a BIT STRING, and this bit 328 // sequence is not a valid public key. Ensure that X509Certificate.publicKey 329 // does not abort in this case. 330 331 const certPem = `-----BEGIN CERTIFICATE----- 332MIIDpDCCAw0CFEc1OZ8g17q+PZnna3iQ/gfoZ7f3MA0GCSqGSIb3DQEBBQUAMIHX 333MRMwEQYLKwYBBAGCNzwCAQMTAkdJMR0wGwYDVQQPExRQcml2YXRlIE9yZ2FuaXph 334dGlvbjEOMAwGA1UEBRMFOTkxOTExCzAJBgNVBAYTAkdJMRIwEAYDVQQIFAlHaWJy 335YWx0YXIxEjAQBgNVBAcUCUdpYnJhbHRhcjEgMB4GA1UEChQXV0hHIChJbnRlcm5h 336dGlvbmFsKSBMdGQxHDAaBgNVBAsUE0ludGVyYWN0aXZlIEJldHRpbmcxHDAaBgNV 337BAMUE3d3dy53aWxsaWFtaGlsbC5jb20wIhgPMjAxNDAyMDcwMDAwMDBaGA8yMDE1 338MDIyMTIzNTk1OVowgbAxCzAJBgNVBAYTAklUMQ0wCwYDVQQIEwRSb21lMRAwDgYD 339VQQHEwdQb21lemlhMRYwFAYDVQQKEw1UZWxlY29taXRhbGlhMRIwEAYDVQQrEwlB 340RE0uQVAuUE0xHTAbBgNVBAMTFHd3dy50ZWxlY29taXRhbGlhLml0MTUwMwYJKoZI 341hvcNAQkBFiZ2YXNlc2VyY2l6aW9wb3J0YWxpY29AdGVsZWNvbWl0YWxpYS5pdDCB 342nzANBgkqhkiG9w0BAQEFAAOBjQA4gYkCgYEA5m/Vf7PevH+inMfUJOc8GeR7WVhM 343CQwcMM5k46MSZo7kCk7VZuaq5G2JHGAGnLPaPUkeXlrf5qLpTxXXxHNtz+WrDlFt 344boAdnTcqpX3+72uBGOaT6Wi/9YRKuCs5D5/cAxAc3XjHfpRXMoXObj9Vy7mLndfV 345/wsnTfU9QVeBkgsCAwEAAaOBkjCBjzAdBgNVHQ4EFgQUfLjAjEiC83A+NupGrx5+ 346Qe6nhRMwbgYIKwYBBQUHAQwEYjBgoV6gXDBaMFgwVhYJaW1hZ2UvZ2lmMCEwHzAH 347BgUrDgMCGgQUS2u5KJYGDLvQUjibKaxLB4shBRgwJhYkaHR0cDovL2xvZ28udmVy 348aXNpZ24uY29tL3ZzbG9nbzEuZ2lmMA0GCSqGSIb3DQEBBQUAA4GBALLiAMX0cIMp 349+V/JgMRhMEUKbrt5lYKfv9dil/f22ezZaFafb070jGMMPVy9O3/PavDOkHtTv3vd 350tAt3hIKFD1bJt6c6WtMH2Su3syosWxmdmGk5ihslB00lvLpfj/wed8i3bkcB1doq 351UcXd/5qu2GhokrKU2cPttU+XAN2Om6a0 352-----END CERTIFICATE-----`; 353 354 const cert = new X509Certificate(certPem); 355 assert.throws(() => cert.publicKey, { 356 message: common.hasOpenSSL3 ? /decode error/ : /wrong tag/, 357 name: 'Error' 358 }); 359 360 assert.strictEqual(cert.checkIssued(cert), false); 361} 362