Skip to content
File

Blob: src/workerd/api/node/tests/crypto_x509-test.js

javascript570 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4//
5// Copyright Joyent, Inc. and other Node contributors.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the
9// "Software"), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to permit
12// persons to whom the Software is furnished to do so, subject to the
13// following conditions:
14//
15// The above copyright notice and this permission notice shall be included
16// in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT ORs
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24// USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26import { deepStrictEqual, ok, strictEqual, throws } from 'node:assert';
27 
28import { Buffer } from 'node:buffer';
29import assert from 'node:assert';
30 
31import { X509Certificate, PublicKeyObject } from 'node:crypto';
32 
33const cert = Buffer.from(`-----BEGIN CERTIFICATE-----
34MIID6DCCAtCgAwIBAgIUFH02wcL3Qgben6tfIibXitsApCYwDQYJKoZIhvcNAQEL
35BQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G
36A1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe
37BgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMCAXDTIyMDkwMzIxNDAzN1oY
38DzIyOTYwNjE3MjE0MDM3WjB9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJ
39BgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05vZGUuanMxDzAN
40BgNVBAMMBmFnZW50MTEgMB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcw
41ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUVjIK+yDTgnCT3CxChO0E
4237q9VuHdrlKeKLeQzUJW2yczSfNzX/0zfHpjY+zKWie39z3HCJqWxtiG2wxiOI8c
433WqWOvzVmdWADlh6EfkIlg+E7VC6JaKDA+zabmhPvnuu3JzogBMnsWl68lCXzuPx
44deQAmEwNtqjrh74DtM+Ud0ulb//Ixjxo1q3rYKu+aaexSramuee6qJta2rjrB4l8
45B/bU+j1mDf9XQQfSjo9jRnp4hiTFdBl2k+lZzqE2L/rhu6EMjA2IhAq/7xA2MbLo
469cObVUin6lfoo5+JKRgT9Fp2xEgDOit+2EA/S6oUfPNeLSVUqmXOSWlXlwlb9Nxr
47AgMBAAGjYTBfMF0GCCsGAQUFBwEBBFEwTzAjBggrBgEFBQcwAYYXaHR0cDovL29j
48c3Aubm9kZWpzLm9yZy8wKAYIKwYBBQUHMAKGHGh0dHA6Ly9jYS5ub2RlanMub3Jn
49L2NhLmNlcnQwDQYJKoZIhvcNAQELBQADggEBAMM0mBBjLMt9pYXePtUeNO0VTw9y
50FWCM8nAcAO2kRNwkJwcsispNpkcsHZ5o8Xf5mpCotdvziEWG1hyxwU6nAWyNOLcN
51G0a0KUfbMO3B6ZYe1GwPDjXaQnv75SkAdxgX5zOzca3xnhITcjUUGjQ0fbDfwFV5
52ix8mnzvfXjDONdEznVa7PFcN6QliFUMwR/h8pCRHtE5+a10OSPeJSrGG+FtrGnRW
53G1IJUv6oiGF/MvWCr84REVgc1j78xomGANJIu2hN7bnD1nEMON6em8IfnDOUtynV
549wfWTqiQYD5Zifj6WcGa0aAHMuetyFG4lIfMAHmd3gaKpks7j9l26LwRPvI=
55-----END CERTIFICATE-----
56`);
57 
58const ca = Buffer.from(`-----BEGIN CERTIFICATE-----
59MIIDlDCCAnygAwIBAgIUSrFsjf1qfQ0t/KvfnEsOksatAikwDQYJKoZIhvcNAQEL
60BQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G
61A1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe
62BgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMCAXDTIyMDkwMzIxNDAzN1oY
63DzIyOTYwNjE3MjE0MDM3WjB6MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJ
64BgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05vZGUuanMxDDAK
65BgNVBAMMA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwggEi
66MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNvf4OGGep+ak+4DNjbuNgy0S/
67AZPxahEFp4gpbcvsi9YLOPZ31qpilQeQf7d27scIZ02Qx1YBAzljxELB8H/ZxuYS
68cQK0s+DNP22xhmgwMWznO7TezkHP5ujN2UkbfbUpfUxGFgncXeZf9wR7yFWppeHi
69RWNBOgsvY7sTrS12kXjWGjqntF7xcEDHc7h+KyF6ZjVJZJCnP6pJEQ+rUjd51eCZ
70Xt4WjowLnQiCS1VKzXiP83a++Ma1BKKkUitTR112/Uwd5eGoiByhmLzb/BhxnHJN
7107GXjhlMItZRm/jfbZsx1mwnNOO3tx4r08l+DaqkinIadvazs+1ugCaKQn8xAgMB
72AAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFqG0RXURDam
7356x5accdg9sY5zEGP5VQhkK3ZDc2NyNNa25rwvrjCpO+e0OSwKAmm4aX6iIf2woY
74wF2f9swWYzxn9CG4fDlUA8itwlnHxupeL4fGMTYb72vf31plUXyBySRsTwHwBloc
75F7KvAZpYYKN9EMH1S/267By6H2I33BT/Ethv//n8dSfmuCurR1kYRaiOC4PVeyFk
76B3sj8TtolrN0y/nToWUhmKiaVFnDx3odQ00yhmxR3t21iB7yDkko6D8Vf2dVC4j/
77YYBVprXGlTP/hiYRLDoP20xKOYznx5cvHPJ9p+lVcOZUJsJj/Iy750+2n5UiBmXt
78lz88C25ucKA=
79-----END CERTIFICATE-----
80`);
81 
82const der = Buffer.from(
83 '308203e8308202d0a0030201020214147d36c1c2f74206de9fab5f2226d78adb00a42630' +
84 '0d06092a864886f70d01010b0500307a310b3009060355040613025553310b3009060355' +
85 '04080c024341310b300906035504070c025346310f300d060355040a0c064a6f79656e74' +
86 '3110300e060355040b0c074e6f64652e6a73310c300a06035504030c036361313120301e' +
87 '06092a864886f70d010901161172794074696e79636c6f7564732e6f72673020170d3232' +
88 '303930333231343033375a180f32323936303631373231343033375a307d310b30090603' +
89 '55040613025553310b300906035504080c024341310b300906035504070c025346310f30' +
90 '0d060355040a0c064a6f79656e743110300e060355040b0c074e6f64652e6a73310f300d' +
91 '06035504030c066167656e74313120301e06092a864886f70d010901161172794074696e' +
92 '79636c6f7564732e6f726730820122300d06092a864886f70d01010105000382010f0030' +
93 '82010a0282010100d456320afb20d3827093dc2c4284ed04dfbabd56e1ddae529e28b790' +
94 'cd4256db273349f3735ffd337c7a6363ecca5a27b7f73dc7089a96c6d886db0c62388f1c' +
95 'dd6a963afcd599d5800e587a11f908960f84ed50ba25a28303ecda6e684fbe7baedc9ce8' +
96 '801327b1697af25097cee3f175e400984c0db6a8eb87be03b4cf94774ba56fffc8c63c68' +
97 'd6adeb60abbe69a7b14ab6a6b9e7baa89b5adab8eb07897c07f6d4fa3d660dff574107d2' +
98 '8e8f63467a788624c574197693e959cea1362ffae1bba10c8c0d88840abfef103631b2e8' +
99 'f5c39b5548a7ea57e8a39f89291813f45a76c448033a2b7ed8403f4baa147cf35e2d2554' +
100 'aa65ce49695797095bf4dc6b0203010001a361305f305d06082b06010505070101045130' +
101 '4f302306082b060105050730018617687474703a2f2f6f6373702e6e6f64656a732e6f72' +
102 '672f302806082b06010505073002861c687474703a2f2f63612e6e6f64656a732e6f7267' +
103 '2f63612e63657274300d06092a864886f70d01010b05000382010100c3349810632ccb7d' +
104 'a585de3ed51e34ed154f0f7215608cf2701c00eda444dc2427072c8aca4da6472c1d9e68' +
105 'f177f99a90a8b5dbf3884586d61cb1c14ea7016c8d38b70d1b46b42947db30edc1e9961e' +
106 'd46c0f0e35da427bfbe52900771817e733b371adf19e12137235141a34347db0dfc05579' +
107 '8b1f269f3bdf5e30ce35d1339d56bb3c570de9096215433047f87ca42447b44e7e6b5d0e' +
108 '48f7894ab186f85b6b1a74561b520952fea888617f32f582afce1111581cd63efcc68986' +
109 '00d248bb684dedb9c3d6710c38de9e9bc21f9c3394b729d5f707d64ea890603e5989f8fa' +
110 '59c19ad1a00732e7adc851b89487cc00799dde068aa64b3b8fd976e8bc113ef2',
111 'hex'
112);
113 
114const _key = Buffer.from(`-----BEGIN RSA PRIVATE KEY-----
115MIIEpAIBAAKCAQEA1FYyCvsg04Jwk9wsQoTtBN+6vVbh3a5Snii3kM1CVtsnM0nz
116c1/9M3x6Y2Psylont/c9xwialsbYhtsMYjiPHN1qljr81ZnVgA5YehH5CJYPhO1Q
117uiWigwPs2m5oT757rtyc6IATJ7FpevJQl87j8XXkAJhMDbao64e+A7TPlHdLpW//
118yMY8aNat62CrvmmnsUq2prnnuqibWtq46weJfAf21Po9Zg3/V0EH0o6PY0Z6eIYk
119xXQZdpPpWc6hNi/64buhDIwNiIQKv+8QNjGy6PXDm1VIp+pX6KOfiSkYE/RadsRI
120AzorfthAP0uqFHzzXi0lVKplzklpV5cJW/TcawIDAQABAoIBAAvbtHfAhpjJVBgt
12115rvaX04MWmZjIugzKRgib/gdq/7FTlcC+iJl85kSUF7tyGl30n62MxgwqFhAX6m
122hQ6HMhbelrFFIhGbwbyhEHfgwROlrcAysKt0pprCgVvBhrnNXYLqdyjU3jz9P3LK
123TY3s0/YMK2uNFdI+PTjKH+Z9Foqn9NZUnUonEDepGyuRO7fLeccWJPv2L4CR4a/5
124ku4VbDgVpvVSVRG3PSVzbmxobnpdpl52og+T7tPx1cLnIknPtVljXPWtZdfekh2E
125eAp2KxCCHOKzzG3ItBKsVu0woeqEpy8JcoO6LbgmEoVnZpgmtQClbBgef8+i+oGE
126BgW9nmECgYEA8gA63QQuZOUC56N1QXURexN2PogF4wChPaCTFbQSJXvSBkQmbqfL
127qRSD8P0t7GOioPrQK6pDwFf4BJB01AvkDf8Z6DxxOJ7cqIC7LOwDupXocWX7Q0Qk
128O6cwclBVsrDZK00v60uRRpl/a39GW2dx7IiQDkKQndLh3/0TbMIWHNcCgYEA4J6r
129yinZbLpKw2+ezhi4B4GT1bMLoKboJwpZVyNZZCzYR6ZHv+lS7HR/02rcYMZGoYbf
130n7OHwF4SrnUS7vPhG4g2ZsOhKQnMvFSQqpGmK1ZTuoKGAevyvtouhK/DgtLWzGvX
1319fSahiq/UvfXs/z4M11q9Rv9ztPCmG1cwSEHlo0CgYEAogQNZJK8DMhVnYcNpXke
1327uskqtCeQE/Xo06xqkIYNAgloBRYNpUYAGa/vsOBz1UVN/kzDUi8ezVp0oRz8tLT
133J5u2WIi+tE2HJTiqF3UbOfvK1sCT64DfUSCpip7GAQ/tFNRkVH8PD9kMOYfILsGe
134v+DdsO5Xq5HXrwHb02BNNZkCgYBsl8lt33WiPx5OBfS8pu6xkk+qjPkeHhM2bKZs
135nkZlS9j0KsudWGwirN/vkkYg8zrKdK5AQ0dqFRDrDuasZ3N5IA1M+V88u+QjWK7o
136B6pSYVXxYZDv9OZSpqC+vUrEQLJf+fNakXrzSk9dCT1bYv2Lt6ox/epix7XYg2bI
137Z/OHMQKBgQC2FUGhlndGeugTJaoJ8nhT/0VfRUX/h6sCgSerk5qFr/hNCBV4T022
138x0NDR2yLG6MXyqApJpG6rh3QIDElQoQCNlI3/KJ6JfEfmqrLLN2OigTvA5sE4fGU
139Dp/ha8OQAx95EwXuaG7LgARduvOIK3x8qi8KsZoUGJcg2ywurUbkWA==
140-----END RSA PRIVATE KEY-----
141`);
142 
143const subjectCheck = `C=US
144ST=CA
145L=SF
146O=Joyent
147OU=Node.js
148CN=agent1
149emailAddress=ry@tinyclouds.org`;
150 
151const issuerCheck = `C=US
152ST=CA
153L=SF
154O=Joyent
155OU=Node.js
156CN=ca1
157emailAddress=ry@tinyclouds.org`;
158 
159let infoAccessCheck = `OCSP - URI:http://ocsp.nodejs.org/
160CA Issuers - URI:http://ca.nodejs.org/ca.cert\n`;
161 
162const legacyObjectCheck = {
163 subject: {
164 C: 'US',
165 ST: 'CA',
166 L: 'SF',
167 O: 'Joyent',
168 OU: 'Node.js',
169 CN: 'agent1',
170 emailAddress: 'ry@tinyclouds.org',
171 },
172 issuer: {
173 C: 'US',
174 ST: 'CA',
175 L: 'SF',
176 O: 'Joyent',
177 OU: 'Node.js',
178 CN: 'ca1',
179 emailAddress: 'ry@tinyclouds.org',
180 },
181 infoAccess: {
182 'OCSP - URI': ['http://ocsp.nodejs.org/'],
183 'CA Issuers - URI': ['http://ca.nodejs.org/ca.cert'],
184 },
185 modulus:
186 'D456320AFB20D3827093DC2C4284ED04DFBABD56E1DDAE529E28B790CD42' +
187 '56DB273349F3735FFD337C7A6363ECCA5A27B7F73DC7089A96C6D886DB0C' +
188 '62388F1CDD6A963AFCD599D5800E587A11F908960F84ED50BA25A28303EC' +
189 'DA6E684FBE7BAEDC9CE8801327B1697AF25097CEE3F175E400984C0DB6A8' +
190 'EB87BE03B4CF94774BA56FFFC8C63C68D6ADEB60ABBE69A7B14AB6A6B9E7' +
191 'BAA89B5ADAB8EB07897C07F6D4FA3D660DFF574107D28E8F63467A788624' +
192 'C574197693E959CEA1362FFAE1BBA10C8C0D88840ABFEF103631B2E8F5C3' +
193 '9B5548A7EA57E8A39F89291813F45A76C448033A2B7ED8403F4BAA147CF3' +
194 '5E2D2554AA65CE49695797095BF4DC6B',
195 bits: 2048,
196 exponent: '0x10001',
197 valid_from: 'Sep 3 21:40:37 2022 GMT',
198 valid_to: 'Jun 17 21:40:37 2296 GMT',
199 fingerprint: '8B:89:16:C4:99:87:D2:13:1A:64:94:36:38:A5:32:01:F0:95:3B:53',
200 fingerprint256:
201 '2C:62:59:16:91:89:AB:90:6A:3E:98:88:A6:D3:C5:58:58:6C:AE:FF:9C:33:' +
202 '22:7C:B6:77:D3:34:E7:53:4B:05',
203 fingerprint512:
204 '51:62:18:39:E2:E2:77:F5:86:11:E8:C0:CA:54:43:7C:76:83:19:05:D0:03:' +
205 '24:21:B8:EB:14:61:FB:24:16:EB:BD:51:1A:17:91:04:30:03:EB:68:5F:DC:' +
206 '86:E1:D1:7C:FB:AF:78:ED:63:5F:29:9C:32:AF:A1:8E:22:96:D1:02',
207 serialNumber: '147D36C1C2F74206DE9FAB5F2226D78ADB00A426',
208};
209 
210export const test_ok = {
211 async test() {
212 const x509 = new X509Certificate(cert);
213 ok(!x509.ca);
214 strictEqual(x509.subject, subjectCheck);
215 strictEqual(x509.subjectAltName, undefined);
216 strictEqual(x509.issuer, issuerCheck);
217 strictEqual(x509.infoAccess, infoAccessCheck);
218 strictEqual(x509.validFrom, 'Sep 3 21:40:37 2022 GMT');
219 strictEqual(x509.validTo, 'Jun 17 21:40:37 2296 GMT');
220 strictEqual(
221 x509.fingerprint.length,
222 '8B:89:16:C4:99:87:D2:13:1A:64:94:36:38:A5:32:01:F0:95:3B:53'.length
223 );
224 strictEqual(
225 x509.fingerprint256,
226 '2C:62:59:16:91:89:AB:90:6A:3E:98:88:A6:D3:C5:58:58:6C:AE:FF:9C:33:' +
227 '22:7C:B6:77:D3:34:E7:53:4B:05'
228 );
229 strictEqual(
230 x509.fingerprint512,
231 '0B:6F:D0:4D:6B:22:53:99:66:62:51:2D:2C:96:F2:58:3F:95:1C:CC:4C:44:' +
232 '9D:B5:59:AA:AD:A8:F6:2A:24:8A:BB:06:A5:26:42:52:30:A3:37:61:30:A9:' +
233 '5A:42:63:E0:21:2F:D6:70:63:07:96:6F:27:A7:78:12:08:02:7A:8B'
234 );
235 strictEqual(x509.keyUsage, undefined);
236 strictEqual(x509.serialNumber, '147D36C1C2F74206DE9FAB5F2226D78ADB00A426');
237 
238 deepStrictEqual(x509.raw, der);
239 
240 ok(x509.publicKey instanceof PublicKeyObject);
241 ok(x509.publicKey.type === 'public');
242 
243 strictEqual(
244 x509.toString().replaceAll('\r\n', '\n'),
245 cert.toString().replaceAll('\r\n', '\n')
246 );
247 strictEqual(x509.toJSON(), x509.toString());
248 
249 // TODO(soon): Need to implement createPrivateKey this check
250 // const privateKey = createPrivateKey(key);
251 // ok(x509.checkPrivateKey(privateKey));
252 throws(() => x509.checkPrivateKey(x509.publicKey), {
253 code: 'ERR_INVALID_ARG_TYPE',
254 });
255 
256 strictEqual(x509.checkIP('127.0.0.1'), undefined);
257 strictEqual(x509.checkIP('::'), undefined);
258 strictEqual(x509.checkHost('agent1'), 'agent1');
259 strictEqual(x509.checkHost('agent2'), undefined);
260 strictEqual(x509.checkEmail('ry@tinyclouds.org'), 'ry@tinyclouds.org');
261 strictEqual(x509.checkEmail('sally@example.com'), undefined);
262 ok(!x509.checkIssued(x509));
263 
264 throws(() => x509.checkHost('agent\x001'));
265 throws(() => x509.checkIP('[::]'));
266 throws(() => x509.checkEmail('not\x00hing'));
267 
268 [1, false].forEach((i) => {
269 throws(() => x509.checkHost('agent1', i));
270 throws(() => x509.checkHost('agent1', { subject: i }));
271 });
272 
273 [
274 'wildcards',
275 'partialWildcards',
276 'multiLabelWildcards',
277 'singleLabelSubdomains',
278 ].forEach((key) => {
279 [1, '', {}].forEach((i) => {
280 throws(() => x509.checkHost('agent1', { [key]: i }), {
281 code: 'ERR_INVALID_ARG_TYPE',
282 });
283 });
284 });
285 
286 const ca_cert = new X509Certificate(ca);
287 ok(ca_cert.ca);
288 ok(x509.checkIssued(ca_cert));
289 
290 ok(x509.verify(ca_cert.publicKey));
291 
292 throws(() => x509.checkIssued({}));
293 throws(() => x509.checkIssued(''));
294 throws(() => x509.verify({}));
295 throws(() => x509.verify(''));
296 // eslint-disable-next-line no-undef
297 throws(() => x509.verify(privateKey));
298 
299 const legacyObject = x509.toLegacyObject();
300 deepStrictEqual(legacyObject.raw, x509.raw);
301 
302 deepStrictEqual(legacyObject.subject, legacyObjectCheck.subject);
303 deepStrictEqual(legacyObject.issuer, legacyObjectCheck.issuer);
304 deepStrictEqual(legacyObject.infoAccess, legacyObjectCheck.infoAccess);
305 strictEqual(legacyObject.modulus, legacyObjectCheck.modulus);
306 strictEqual(legacyObject.bits, legacyObjectCheck.bits);
307 strictEqual(legacyObject.exponent, legacyObjectCheck.exponent);
308 strictEqual(legacyObject.valid_from, legacyObjectCheck.valid_from);
309 strictEqual(legacyObject.valid_to, legacyObjectCheck.valid_to);
310 strictEqual(legacyObject.fingerprint, legacyObjectCheck.fingerprint);
311 strictEqual(legacyObject.fingerprint256, legacyObjectCheck.fingerprint256);
312 strictEqual(legacyObject.serialNumber, legacyObjectCheck.serialNumber);
313 },
314};
315 
316// This X.509 Certificate can be parsed by OpenSSL because it contains a
317// structurally sound TBSCertificate structure. However, the SPKI field of the
318// TBSCertificate contains the subjectPublicKey as a BIT STRING, and this bit
319// sequence is not a valid public key. Ensure that X509Certificate.publicKey
320// does not abort in this case.
321// TODO(soon): F
322// const badCert = `-----BEGIN CERTIFICATE-----
323// MIIDpDCCAw0CFEc1OZ8g17q+PZnna3iQ/gfoZ7f3MA0GCSqGSIb3DQEBBQUAMIHX
324// MRMwEQYLKwYBBAGCNzwCAQMTAkdJMR0wGwYDVQQPExRQcml2YXRlIE9yZ2FuaXph
325// dGlvbjEOMAwGA1UEBRMFOTkxOTExCzAJBgNVBAYTAkdJMRIwEAYDVQQIFAlHaWJy
326// YWx0YXIxEjAQBgNVBAcUCUdpYnJhbHRhcjEgMB4GA1UEChQXV0hHIChJbnRlcm5h
327// dGlvbmFsKSBMdGQxHDAaBgNVBAsUE0ludGVyYWN0aXZlIEJldHRpbmcxHDAaBgNV
328// BAMUE3d3dy53aWxsaWFtaGlsbC5jb20wIhgPMjAxNDAyMDcwMDAwMDBaGA8yMDE1
329// MDIyMTIzNTk1OVowgbAxCzAJBgNVBAYTAklUMQ0wCwYDVQQIEwRSb21lMRAwDgYD
330// VQQHEwdQb21lemlhMRYwFAYDVQQKEw1UZWxlY29taXRhbGlhMRIwEAYDVQQrEwlB
331// RE0uQVAuUE0xHTAbBgNVBAMTFHd3dy50ZWxlY29taXRhbGlhLml0MTUwMwYJKoZI
332// hvcNAQkBFiZ2YXNlc2VyY2l6aW9wb3J0YWxpY29AdGVsZWNvbWl0YWxpYS5pdDCB
333// nzANBgkqhkiG9w0BAQEFAAOBjQA4gYkCgYEA5m/Vf7PevH+inMfUJOc8GeR7WVhM
334// CQwcMM5k46MSZo7kCk7VZuaq5G2JHGAGnLPaPUkeXlrf5qLpTxXXxHNtz+WrDlFt
335// boAdnTcqpX3+72uBGOaT6Wi/9YRKuCs5D5/cAxAc3XjHfpRXMoXObj9Vy7mLndfV
336// /wsnTfU9QVeBkgsCAwEAAaOBkjCBjzAdBgNVHQ4EFgQUfLjAjEiC83A+NupGrx5+
337// Qe6nhRMwbgYIKwYBBQUHAQwEYjBgoV6gXDBaMFgwVhYJaW1hZ2UvZ2lmMCEwHzAH
338// BgUrDgMCGgQUS2u5KJYGDLvQUjibKaxLB4shBRgwJhYkaHR0cDovL2xvZ28udmVy
339// aXNpZ24uY29tL3ZzbG9nbzEuZ2lmMA0GCSqGSIb3DQEBBQUAA4GBALLiAMX0cIMp
340// +V/JgMRhMEUKbrt5lYKfv9dil/f22ezZaFafb070jGMMPVy9O3/PavDOkHtTv3vd
341// tAt3hIKFD1bJt6c6WtMH2Su3syosWxmdmGk5ihslB00lvLpfj/wed8i3bkcB1doq
342// UcXd/5qu2GhokrKU2cPttU+XAN2Om6a0
343// -----END CERTIFICATE-----`;
344 
345// export const test_error = {
346// test() {
347// throws(() => new X509Certificate(badCert));
348// }
349// };
350 
351// Verify that invalid certificate data throws a user-facing error (not an internal error).
352export const test_invalid_cert = {
353 test() {
354 // Completely invalid data
355 throws(() => new X509Certificate('not a certificate'), {
356 code: 'ERR_INVALID_ARG_VALUE',
357 });
358 
359 // Invalid base64 in PEM wrapper
360 throws(
361 () =>
362 new X509Certificate(
363 '-----BEGIN CERTIFICATE-----\n!!!invalid!!!\n-----END CERTIFICATE-----'
364 ),
365 {
366 code: 'ERR_INVALID_ARG_VALUE',
367 }
368 );
369 
370 // Empty buffer
371 throws(() => new X509Certificate(Buffer.alloc(0)), {
372 code: 'ERR_INVALID_ARG_VALUE',
373 });
374 
375 // Random bytes (not a valid DER or PEM certificate)
376 throws(() => new X509Certificate(Buffer.from([0x01, 0x02, 0x03])), {
377 code: 'ERR_INVALID_ARG_VALUE',
378 });
379 },
380};
381 
382// Test parsing a SHA384 ECDSA (P-384) certificate in DER (.cer) format
383//
384// Generated with:
385// # CA key + self-signed CA cert
386// openssl ecparam -name secp384r1 -genkey -noout -out ec384_ca_key.pem
387// openssl req -new -x509 -key ec384_ca_key.pem -sha384 -days 100000 \
388// -out ec384_ca_cert.pem -subj "/C=US/ST=CA/L=SF/O=Test/OU=TestUnit/CN=TestCA"
389//
390// # End-entity key + CSR + cert signed by the CA
391// openssl ecparam -name secp384r1 -genkey -noout -out ec384_key.pem
392// openssl req -new -key ec384_key.pem -sha384 -out ec384_csr.pem \
393// -subj "/C=US/ST=CA/L=SF/O=Test/OU=TestUnit/CN=agent1/emailAddress=test@example.com"
394// openssl x509 -req -in ec384_csr.pem -CA ec384_ca_cert.pem -CAkey ec384_ca_key.pem \
395// -CAcreateserial -out ec384_cert.pem -days 100000 -sha384
396const ecCert = Buffer.from(`-----BEGIN CERTIFICATE-----
397MIICVzCCAd6gAwIBAgIUczuwDn2NET/tEfVmMXL4mPYyz4AwCgYIKoZIzj0EAwMw
398WjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsGA1UE
399CgwEVGVzdDERMA8GA1UECwwIVGVzdFVuaXQxDzANBgNVBAMMBlRlc3RDQTAgFw0y
400NjA0MDgwNTE1MzZaGA8yMzAwMDEyMjA1MTUzNlowezELMAkGA1UEBhMCVVMxCzAJ
401BgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsGA1UECgwEVGVzdDERMA8GA1UECwwI
402VGVzdFVuaXQxDzANBgNVBAMMBmFnZW50MTEfMB0GCSqGSIb3DQEJARYQdGVzdEBl
403eGFtcGxlLmNvbTB2MBAGByqGSM49AgEGBSuBBAAiA2IABP66lBV0gd87DBgeGtRG
404c+nKyL5u9snb0ByLhcw+8CL+YGGv6SWOtVa5LFTiAEP95yBl861RBYExDzSbLGfb
405Oifr+9EmUxKsahSaMNHIRXxoALnseTOOnDYcn2B66fck+qNCMEAwHQYDVR0OBBYE
406FEefA0NT2iIPiO8NQ3eBUeUIrAQIMB8GA1UdIwQYMBaAFGMNBZGlbYnBCXgHLTrU
407KRDLfkFOMAoGCCqGSM49BAMDA2cAMGQCME8lAYbgOeO10lqvwUGx2xR29G13pouH
408k4dEBxJF5hiUaOyRHcv+57tm8aBL/nWmEQIwf+G5sVuUJGcou41ick7RXM+fDg4t
409mnbgde9KSx8Ogi9u2RimDFan8BMiJWbJIwBj
410-----END CERTIFICATE-----
411`);
412 
413const ecCa = Buffer.from(`-----BEGIN CERTIFICATE-----
414MIICSTCCAc6gAwIBAgIUJbLH/BA5dfjCxho12s8bw8BjxlcwCgYIKoZIzj0EAwMw
415WjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsGA1UE
416CgwEVGVzdDERMA8GA1UECwwIVGVzdFVuaXQxDzANBgNVBAMMBlRlc3RDQTAgFw0y
417NjA0MDgwNTE1MTVaGA8yMzAwMDEyMjA1MTUxNVowWjELMAkGA1UEBhMCVVMxCzAJ
418BgNVBAgMAkNBMQswCQYDVQQHDAJTRjENMAsGA1UECgwEVGVzdDERMA8GA1UECwwI
419VGVzdFVuaXQxDzANBgNVBAMMBlRlc3RDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IA
420BPA7rTck1nzyMF+VHeP17ih9EYcwDkSJ5MtWr4ftLLcgPD6gPxMosNv+ykPLpw0P
421V8IycMa4gFceYGp8NFPE5tavX2Y1loRrmQbduo5ctox11so+/4TisC/Kl3Ovile2
422/aNTMFEwHQYDVR0OBBYEFGMNBZGlbYnBCXgHLTrUKRDLfkFOMB8GA1UdIwQYMBaA
423FGMNBZGlbYnBCXgHLTrUKRDLfkFOMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0E
424AwMDaQAwZgIxAN5cy8aaW3zyozwFRVqR6k7vorwpuemQnqYSocfmuQI5CxbXgYEy
4252cVrA4j32pZD5wIxAJBE4gfYmjJt4OG/YdOaslrnn84Jk1O21cTzxtivtTxxNOcQ
426rd0vE+2ygF1pCs5aEw==
427-----END CERTIFICATE-----
428`);
429 
430// Convert the PEM to DER for testing the raw property and to ensure that the same data is being parsed in both cases.
431// openssl x509 -in ec384_cert.pem -outform DER | python3 -c "import sys; print(sys.stdin.buffer.read().hex())"
432const ecDer = Buffer.from(
433 '30820257308201dea0030201020214733bb00e7d8d113fed11f5663172f898f632cf80' +
434 '300a06082a8648ce3d040303305a310b3009060355040613025553310b300906035504' +
435 '080c024341310b300906035504070c025346310d300b060355040a0c04546573743111' +
436 '300f060355040b0c0854657374556e6974310f300d06035504030c06546573744341' +
437 '3020170d3236303430383035313533365a180f32333030303132323035313533365a' +
438 '307b310b3009060355040613025553310b300906035504080c024341310b3009060355' +
439 '04070c025346310d300b060355040a0c04546573743111300f060355040b0c08546573' +
440 '74556e6974310f300d06035504030c066167656e7431311f301d06092a864886f70d01' +
441 '0901161074657374406578616d706c652e636f6d3076301006072a8648ce3d02010605' +
442 '2b8104002203620004feba94157481df3b0c181e1ad44673e9cac8be6ef6c9dbd01c8b' +
443 '85cc3ef022fe6061afe9258eb556b92c54e20043fde72065f3ad510581310f349b2c67' +
444 'db3a27ebfbd1265312ac6a149a30d1c8457c6800b9ec79338e9c361c9f607ae9f724fa' +
445 'a3423040301d0603551d0e04160414479f034353da220f88ef0d43778151e508ac0408' +
446 '301f0603551d23041830168014630d0591a56d89c10978072d3ad42910cb7e414e300a' +
447 '06082a8648ce3d040303036700306402304f250186e039e3b5d25aafc141b1db1476f4' +
448 '6d77a68b87938744071245e6189468ec911dcbfee7bb66f1a04bfe75a61102307fe1b9' +
449 'b15b94246728bb8d62724ed15ccf9f0e0e2d9a76e075ef4a4b1f0e822f6ed918a60c56' +
450 'a7f013222566c9230063',
451 'hex'
452);
453 
454const ecSubjectCheck = `C=US
455ST=CA
456L=SF
457O=Test
458OU=TestUnit
459CN=agent1
460emailAddress=test@example.com`;
461 
462const ecIssuerCheck = `C=US
463ST=CA
464L=SF
465O=Test
466OU=TestUnit
467CN=TestCA`;
468 
469export const test_sha384_ecdsa = {
470 async test() {
471 // Test parsing a SHA384 ECDSA certificate (P-384 curve)
472 const x509 = new X509Certificate(ecCert);
473 ok(!x509.ca);
474 strictEqual(x509.subject, ecSubjectCheck);
475 strictEqual(x509.subjectAltName, undefined);
476 strictEqual(x509.issuer, ecIssuerCheck);
477 strictEqual(x509.validFrom, 'Apr 8 05:15:36 2026 GMT');
478 strictEqual(x509.validTo, 'Jan 22 05:15:36 2300 GMT');
479 strictEqual(
480 x509.fingerprint,
481 'F5:CD:67:3C:C2:41:ED:A0:E9:7B:1E:F9:8D:CD:72:36:FB:BC:27:E6'
482 );
483 strictEqual(
484 x509.fingerprint256,
485 '97:87:A6:43:9C:54:D4:F7:5D:42:58:2C:E0:D7:F1:2E:08:DF:08:BF:' +
486 '18:F4:62:4D:2E:52:E1:C4:EC:41:6B:77'
487 );
488 strictEqual(
489 x509.fingerprint512,
490 '28:02:30:8A:87:86:07:5E:C6:FC:33:3C:D3:D9:C1:35:A5:72:F3:F2:' +
491 'D4:D8:33:D2:9C:F4:73:7B:A5:33:6B:9B:3E:23:D6:84:70:B5:37:F8:' +
492 'C2:D7:A3:03:9C:61:F1:40:92:AC:43:44:E6:6C:43:2D:98:AE:CF:0B:' +
493 '9A:99:90:6C'
494 );
495 strictEqual(x509.serialNumber, '733BB00E7D8D113FED11F5663172F898F632CF80');
496 
497 // Verify DER round-trip
498 deepStrictEqual(x509.raw, ecDer);
499 
500 // Public key should be an EC key
501 ok(x509.publicKey instanceof PublicKeyObject);
502 ok(x509.publicKey.type === 'public');
503 
504 // PEM round-trip
505 strictEqual(
506 x509.toString().replaceAll('\r\n', '\n'),
507 ecCert.toString().replaceAll('\r\n', '\n')
508 );
509 strictEqual(x509.toJSON(), x509.toString());
510 
511 // Test parsing from DER (.cer) format directly
512 const x509FromDer = new X509Certificate(ecDer);
513 strictEqual(
514 x509FromDer.serialNumber,
515 '733BB00E7D8D113FED11F5663172F898F632CF80'
516 );
517 strictEqual(x509FromDer.subject, ecSubjectCheck);
518 strictEqual(x509FromDer.issuer, ecIssuerCheck);
519 deepStrictEqual(x509FromDer.raw, ecDer);
520 
521 // Host/email checks
522 strictEqual(x509.checkHost('agent1'), 'agent1');
523 strictEqual(x509.checkHost('agent2'), undefined);
524 strictEqual(x509.checkEmail('test@example.com'), 'test@example.com');
525 strictEqual(x509.checkEmail('other@example.com'), undefined);
526 
527 // CA certificate verification
528 const caCert = new X509Certificate(ecCa);
529 ok(caCert.ca);
530 ok(x509.checkIssued(caCert));
531 ok(x509.verify(caCert.publicKey));
532 ok(!x509.checkIssued(x509));
533 
534 // Legacy object format
535 const legacyObject = x509.toLegacyObject();
536 deepStrictEqual(legacyObject.raw, x509.raw);
537 deepStrictEqual(legacyObject.subject, {
538 C: 'US',
539 ST: 'CA',
540 L: 'SF',
541 O: 'Test',
542 OU: 'TestUnit',
543 CN: 'agent1',
544 emailAddress: 'test@example.com',
545 });
546 deepStrictEqual(legacyObject.issuer, {
547 C: 'US',
548 ST: 'CA',
549 L: 'SF',
550 O: 'Test',
551 OU: 'TestUnit',
552 CN: 'TestCA',
553 });
554 strictEqual(legacyObject.valid_from, 'Apr 8 05:15:36 2026 GMT');
555 strictEqual(legacyObject.valid_to, 'Jan 22 05:15:36 2300 GMT');
556 strictEqual(
557 legacyObject.serialNumber,
558 '733BB00E7D8D113FED11F5663172F898F632CF80'
559 );
560 },
561};
562 
563// Ref: https://github.com/unjs/unenv/pull/310
564export const shouldImportCertificate = {
565 test() {
566 // This is allowed by Node.js as well.
567 assert(process.getBuiltinModule('crypto').Certificate);
568 },
569};