@freedomofpress/sigstore-browser 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/.github/workflows/conformance.yml +29 -0
  2. package/.github/workflows/publish.yml +45 -0
  3. package/README.md +10 -0
  4. package/dist/asn1/error.d.ts +5 -0
  5. package/dist/asn1/error.d.ts.map +1 -0
  6. package/dist/asn1/error.js +20 -0
  7. package/dist/asn1/error.js.map +1 -0
  8. package/dist/asn1/index.d.ts +2 -0
  9. package/dist/asn1/index.d.ts.map +1 -0
  10. package/dist/asn1/index.js +17 -0
  11. package/dist/asn1/index.js.map +1 -0
  12. package/dist/asn1/length.d.ts +4 -0
  13. package/dist/asn1/length.d.ts.map +1 -0
  14. package/dist/asn1/length.js +59 -0
  15. package/dist/asn1/length.js.map +1 -0
  16. package/dist/asn1/obj.d.ts +15 -0
  17. package/dist/asn1/obj.d.ts.map +1 -0
  18. package/dist/asn1/obj.js +153 -0
  19. package/dist/asn1/obj.js.map +1 -0
  20. package/dist/asn1/parse.d.ts +7 -0
  21. package/dist/asn1/parse.d.ts.map +1 -0
  22. package/dist/asn1/parse.js +118 -0
  23. package/dist/asn1/parse.js.map +1 -0
  24. package/dist/asn1/tag.d.ts +17 -0
  25. package/dist/asn1/tag.d.ts.map +1 -0
  26. package/dist/asn1/tag.js +86 -0
  27. package/dist/asn1/tag.js.map +1 -0
  28. package/dist/bundle.d.ts +87 -0
  29. package/dist/bundle.d.ts.map +1 -0
  30. package/dist/bundle.js +2 -0
  31. package/dist/bundle.js.map +1 -0
  32. package/dist/canonicalize.d.ts +2 -0
  33. package/dist/canonicalize.d.ts.map +1 -0
  34. package/dist/canonicalize.js +64 -0
  35. package/dist/canonicalize.js.map +1 -0
  36. package/dist/cli.d.ts +3 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +217 -0
  39. package/dist/cli.js.map +1 -0
  40. package/dist/crypto.d.ts +5 -0
  41. package/dist/crypto.d.ts.map +1 -0
  42. package/dist/crypto.js +260 -0
  43. package/dist/crypto.js.map +1 -0
  44. package/dist/dsse.d.ts +2 -0
  45. package/dist/dsse.d.ts.map +1 -0
  46. package/dist/dsse.js +42 -0
  47. package/dist/dsse.js.map +1 -0
  48. package/dist/encoding.d.ts +17 -0
  49. package/dist/encoding.d.ts.map +1 -0
  50. package/dist/encoding.js +112 -0
  51. package/dist/encoding.js.map +1 -0
  52. package/dist/errors.d.ts +39 -0
  53. package/dist/errors.d.ts.map +1 -0
  54. package/dist/errors.js +79 -0
  55. package/dist/errors.js.map +1 -0
  56. package/dist/index.d.ts +8 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +5 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/interfaces.d.ts +104 -0
  61. package/dist/interfaces.d.ts.map +1 -0
  62. package/dist/interfaces.js +26 -0
  63. package/dist/interfaces.js.map +1 -0
  64. package/dist/oid.d.ts +5 -0
  65. package/dist/oid.d.ts.map +1 -0
  66. package/dist/oid.js +25 -0
  67. package/dist/oid.js.map +1 -0
  68. package/dist/pem.d.ts +3 -0
  69. package/dist/pem.d.ts.map +1 -0
  70. package/dist/pem.js +41 -0
  71. package/dist/pem.js.map +1 -0
  72. package/dist/rfc3161/error.d.ts +3 -0
  73. package/dist/rfc3161/error.d.ts.map +1 -0
  74. package/dist/rfc3161/error.js +18 -0
  75. package/dist/rfc3161/error.js.map +1 -0
  76. package/dist/rfc3161/index.d.ts +2 -0
  77. package/dist/rfc3161/index.d.ts.map +1 -0
  78. package/dist/rfc3161/index.js +17 -0
  79. package/dist/rfc3161/index.js.map +1 -0
  80. package/dist/rfc3161/timestamp.d.ts +36 -0
  81. package/dist/rfc3161/timestamp.d.ts.map +1 -0
  82. package/dist/rfc3161/timestamp.js +203 -0
  83. package/dist/rfc3161/timestamp.js.map +1 -0
  84. package/dist/rfc3161/tstinfo.d.ts +13 -0
  85. package/dist/rfc3161/tstinfo.d.ts.map +1 -0
  86. package/dist/rfc3161/tstinfo.js +47 -0
  87. package/dist/rfc3161/tstinfo.js.map +1 -0
  88. package/dist/sigstore.d.ts +33 -0
  89. package/dist/sigstore.d.ts.map +1 -0
  90. package/dist/sigstore.js +531 -0
  91. package/dist/sigstore.js.map +1 -0
  92. package/dist/stream.d.ts +22 -0
  93. package/dist/stream.d.ts.map +1 -0
  94. package/dist/stream.js +114 -0
  95. package/dist/stream.js.map +1 -0
  96. package/dist/timestamp/tsa.d.ts +24 -0
  97. package/dist/timestamp/tsa.d.ts.map +1 -0
  98. package/dist/timestamp/tsa.js +150 -0
  99. package/dist/timestamp/tsa.js.map +1 -0
  100. package/dist/tlog/body.d.ts +8 -0
  101. package/dist/tlog/body.d.ts.map +1 -0
  102. package/dist/tlog/body.js +45 -0
  103. package/dist/tlog/body.js.map +1 -0
  104. package/dist/tlog/checkpoint.d.ts +23 -0
  105. package/dist/tlog/checkpoint.d.ts.map +1 -0
  106. package/dist/tlog/checkpoint.js +187 -0
  107. package/dist/tlog/checkpoint.js.map +1 -0
  108. package/dist/tlog/dsse.d.ts +4 -0
  109. package/dist/tlog/dsse.d.ts.map +1 -0
  110. package/dist/tlog/dsse.js +85 -0
  111. package/dist/tlog/dsse.js.map +1 -0
  112. package/dist/tlog/hashedrekord.d.ts +4 -0
  113. package/dist/tlog/hashedrekord.d.ts.map +1 -0
  114. package/dist/tlog/hashedrekord.js +63 -0
  115. package/dist/tlog/hashedrekord.js.map +1 -0
  116. package/dist/tlog/intoto.d.ts +4 -0
  117. package/dist/tlog/intoto.d.ts.map +1 -0
  118. package/dist/tlog/intoto.js +50 -0
  119. package/dist/tlog/intoto.js.map +1 -0
  120. package/dist/tlog/merkle.d.ts +3 -0
  121. package/dist/tlog/merkle.d.ts.map +1 -0
  122. package/dist/tlog/merkle.js +112 -0
  123. package/dist/tlog/merkle.js.map +1 -0
  124. package/dist/trust/tuf-root.json +1 -0
  125. package/dist/trust/tuf.d.ts +95 -0
  126. package/dist/trust/tuf.d.ts.map +1 -0
  127. package/dist/trust/tuf.js +150 -0
  128. package/dist/trust/tuf.js.map +1 -0
  129. package/dist/x509/cert.d.ts +52 -0
  130. package/dist/x509/cert.d.ts.map +1 -0
  131. package/dist/x509/cert.js +256 -0
  132. package/dist/x509/cert.js.map +1 -0
  133. package/dist/x509/chain.d.ts +20 -0
  134. package/dist/x509/chain.d.ts.map +1 -0
  135. package/dist/x509/chain.js +141 -0
  136. package/dist/x509/chain.js.map +1 -0
  137. package/dist/x509/ext.d.ts +48 -0
  138. package/dist/x509/ext.d.ts.map +1 -0
  139. package/dist/x509/ext.js +164 -0
  140. package/dist/x509/ext.js.map +1 -0
  141. package/dist/x509/index.d.ts +4 -0
  142. package/dist/x509/index.d.ts.map +1 -0
  143. package/dist/x509/index.js +19 -0
  144. package/dist/x509/index.js.map +1 -0
  145. package/dist/x509/sct.d.ts +25 -0
  146. package/dist/x509/sct.d.ts.map +1 -0
  147. package/dist/x509/sct.js +123 -0
  148. package/dist/x509/sct.js.map +1 -0
  149. package/eslint.config.mjs +31 -0
  150. package/package.json +39 -0
  151. package/src/asn1/error.ts +18 -0
  152. package/src/asn1/index.ts +16 -0
  153. package/src/asn1/length.ts +71 -0
  154. package/src/asn1/obj.ts +184 -0
  155. package/src/asn1/parse.ts +146 -0
  156. package/src/asn1/tag.ts +105 -0
  157. package/src/bundle.ts +99 -0
  158. package/src/canonicalize.ts +61 -0
  159. package/src/cli.ts +285 -0
  160. package/src/crypto.ts +310 -0
  161. package/src/dsse.ts +49 -0
  162. package/src/encoding.ts +141 -0
  163. package/src/errors.ts +82 -0
  164. package/src/index.browser.test.ts +219 -0
  165. package/src/index.ts +14 -0
  166. package/src/interfaces.ts +118 -0
  167. package/src/oid.ts +28 -0
  168. package/src/pem.ts +48 -0
  169. package/src/rfc3161/error.ts +16 -0
  170. package/src/rfc3161/index.ts +17 -0
  171. package/src/rfc3161/timestamp.ts +270 -0
  172. package/src/rfc3161/tstinfo.ts +80 -0
  173. package/src/sigstore.ts +728 -0
  174. package/src/stream.ts +141 -0
  175. package/src/timestamp/tsa.ts +205 -0
  176. package/src/tlog/body.ts +65 -0
  177. package/src/tlog/checkpoint.ts +248 -0
  178. package/src/tlog/dsse.ts +147 -0
  179. package/src/tlog/hashedrekord.ts +130 -0
  180. package/src/tlog/intoto.ts +102 -0
  181. package/src/tlog/merkle.ts +152 -0
  182. package/src/trust/README.md +38 -0
  183. package/src/trust/tuf-root.json +1 -0
  184. package/src/trust/tuf.ts +223 -0
  185. package/src/types/tuf-browser.d.ts +13 -0
  186. package/src/x509/cert.ts +319 -0
  187. package/src/x509/chain.ts +212 -0
  188. package/src/x509/ext.ts +212 -0
  189. package/src/x509/index.ts +19 -0
  190. package/src/x509/sct.ts +154 -0
  191. package/tsconfig.json +18 -0
  192. package/vite.config.playwright.ts +26 -0
  193. package/vite.config.ts +18 -0
@@ -0,0 +1,223 @@
1
+ /*
2
+ * TUF (The Update Framework) integration for trusted root management
3
+ *
4
+ * Provides secure updates for Sigstore trusted root metadata using TUF.
5
+ * Uses tuf-browser for browser-compatible TUF client functionality.
6
+ *
7
+ * Reference: https://github.com/freedomofpress/tuf-browser
8
+ */
9
+
10
+ import type { TrustedRoot } from "../interfaces.js";
11
+ import { Uint8ArrayToString } from "../encoding.js";
12
+ import type { TUFClient } from "tuf-browser/dist/tuf.js";
13
+
14
+ /**
15
+ * Options for TrustedRootProvider configuration
16
+ */
17
+ export interface TrustedRootProviderOptions {
18
+ /**
19
+ * TUF repository URL for metadata
20
+ * Default: Sigstore production TUF repository
21
+ */
22
+ metadataUrl?: string;
23
+
24
+ /**
25
+ * Target base URL for fetching target files
26
+ * If not specified, uses the same as metadataUrl
27
+ */
28
+ targetBaseUrl?: string;
29
+
30
+ /**
31
+ * Initial root metadata (1.root.json content)
32
+ * If not provided, will use embedded default
33
+ */
34
+ initialRoot?: string;
35
+
36
+ /**
37
+ * Namespace for TUF cache storage
38
+ * Default: 'sigstore-browser'
39
+ */
40
+ namespace?: string;
41
+
42
+ /**
43
+ * Name of the trusted root target file
44
+ * Default: 'trusted_root.json'
45
+ */
46
+ trustedRootTarget?: string;
47
+
48
+ /**
49
+ * Cache TTL in milliseconds
50
+ * Default: 1 hour (3600000 ms)
51
+ */
52
+ cacheTTL?: number;
53
+ }
54
+
55
+ /**
56
+ * Default configuration for Sigstore production TUF repository
57
+ */
58
+ const DEFAULT_CONFIG = {
59
+ metadataUrl: 'https://tuf-repo-cdn.sigstore.dev/',
60
+ namespace: 'sigstore-browser',
61
+ trustedRootTarget: 'trusted_root.json',
62
+ cacheTTL: 3600000, // 1 hour
63
+ };
64
+
65
+ /**
66
+ * Provides Sigstore trusted root via TUF for secure updates
67
+ *
68
+ * This class manages fetching and caching of Sigstore trusted root metadata
69
+ * using The Update Framework (TUF) for secure, verified updates.
70
+ *
71
+ * Example usage:
72
+ * ```typescript
73
+ * const provider = new TrustedRootProvider();
74
+ * const trustedRoot = await provider.getTrustedRoot();
75
+ * ```
76
+ */
77
+ export class TrustedRootProvider {
78
+ private metadataUrl: string;
79
+ private targetBaseUrl?: string;
80
+ private initialRoot?: string;
81
+ private namespace: string;
82
+ private trustedRootTarget: string;
83
+ private cacheTTL: number;
84
+
85
+ private tufClient?: TUFClient;
86
+ private cachedRoot?: TrustedRoot;
87
+ private cacheTimestamp?: number;
88
+
89
+ constructor(options: TrustedRootProviderOptions = {}) {
90
+ const metadataUrl = options.metadataUrl || DEFAULT_CONFIG.metadataUrl;
91
+ this.metadataUrl = metadataUrl.endsWith('/') ? metadataUrl : `${metadataUrl}/`;
92
+ this.targetBaseUrl = options.targetBaseUrl || this.metadataUrl;
93
+ this.initialRoot = options.initialRoot;
94
+ this.namespace = options.namespace || DEFAULT_CONFIG.namespace;
95
+ this.trustedRootTarget = options.trustedRootTarget || DEFAULT_CONFIG.trustedRootTarget;
96
+ this.cacheTTL = options.cacheTTL ?? DEFAULT_CONFIG.cacheTTL;
97
+ }
98
+
99
+ /**
100
+ * Initialize the TUF client
101
+ * Lazy initialization to avoid loading TUF client until needed
102
+ */
103
+ private async initTUFClient(): Promise<void> {
104
+ if (this.tufClient) {
105
+ return;
106
+ }
107
+
108
+ try {
109
+ const { TUFClient } = await import('tuf-browser/dist/tuf.js');
110
+
111
+ // Get initial root metadata
112
+ const rootMetadata = this.initialRoot || await this.getDefaultRoot();
113
+
114
+ this.tufClient = new TUFClient(
115
+ this.metadataUrl,
116
+ rootMetadata,
117
+ this.namespace,
118
+ this.targetBaseUrl
119
+ );
120
+ } catch (error) {
121
+ throw new Error(
122
+ `Failed to initialize TUF client: ${error instanceof Error ? error.message : String(error)}`
123
+ );
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Get the default embedded root metadata
129
+ * Returns the TUF root.json that bootstraps the TUF client
130
+ */
131
+ private async getDefaultRoot(): Promise<string> {
132
+ // Import the embedded TUF root metadata (base64-encoded 1.root.json as JSON string)
133
+ // This is the root of trust for TUF, not to be confused with the Sigstore trusted root
134
+ const tufRootModule = await import('./tuf-root.json', { with: { type: 'json' } });
135
+ const tufRootBase64 = tufRootModule.default as string;
136
+
137
+ // Decode from base64 to get the actual root.json content
138
+ const decoder = new TextDecoder();
139
+ const rootBytes = Uint8Array.from(atob(tufRootBase64), c => c.charCodeAt(0));
140
+ return decoder.decode(rootBytes);
141
+ }
142
+
143
+ /**
144
+ * Check if cached trusted root is still valid
145
+ */
146
+ private isCacheValid(): boolean {
147
+ if (!this.cachedRoot || !this.cacheTimestamp) {
148
+ return false;
149
+ }
150
+
151
+ const now = Date.now();
152
+ return (now - this.cacheTimestamp) < this.cacheTTL;
153
+ }
154
+
155
+ /**
156
+ * Get the Sigstore trusted root metadata
157
+ * Uses TUF to securely fetch and verify the trusted root
158
+ *
159
+ * @returns Promise<TrustedRoot> The verified trusted root metadata
160
+ * @throws Error if TUF verification fails or root cannot be fetched
161
+ */
162
+ async getTrustedRoot(): Promise<TrustedRoot> {
163
+ // Return cached root if still valid
164
+ if (this.isCacheValid() && this.cachedRoot) {
165
+ return this.cachedRoot;
166
+ }
167
+
168
+ // Initialize TUF client if needed
169
+ await this.initTUFClient();
170
+
171
+ if (!this.tufClient) {
172
+ throw new Error('TUF client not initialized');
173
+ }
174
+
175
+ try {
176
+ // Update TUF metadata (root, timestamp, snapshot, targets)
177
+ // This must be called before getTarget() to populate the metadata cache
178
+ await this.tufClient.updateTUF();
179
+
180
+ // Fetch the trusted root target via TUF
181
+ // TUF will handle all verification (signatures, rollback protection, etc.)
182
+ const trustedRootBytes = await this.tufClient.getTarget(this.trustedRootTarget);
183
+
184
+ // Parse the trusted root JSON
185
+ const trustedRootJson = Uint8ArrayToString(trustedRootBytes);
186
+ const trustedRoot = JSON.parse(trustedRootJson) as TrustedRoot;
187
+
188
+ // Cache the result
189
+ this.cachedRoot = trustedRoot;
190
+ this.cacheTimestamp = Date.now();
191
+
192
+ return trustedRoot;
193
+ } catch (error) {
194
+ throw new Error(
195
+ `Failed to fetch trusted root via TUF: ${error instanceof Error ? error.message : String(error)}`
196
+ );
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Manually refresh the trusted root from TUF
202
+ * Bypasses cache and forces a fresh fetch
203
+ *
204
+ * @returns Promise<TrustedRoot> The updated trusted root metadata
205
+ */
206
+ async refreshTrustedRoot(): Promise<TrustedRoot> {
207
+ // Clear cache
208
+ this.cachedRoot = undefined;
209
+ this.cacheTimestamp = undefined;
210
+
211
+ // Fetch fresh root
212
+ return await this.getTrustedRoot();
213
+ }
214
+
215
+ /**
216
+ * Clear the cached trusted root
217
+ * Next call to getTrustedRoot() will fetch fresh data
218
+ */
219
+ clearCache(): void {
220
+ this.cachedRoot = undefined;
221
+ this.cacheTimestamp = undefined;
222
+ }
223
+ }
@@ -0,0 +1,13 @@
1
+ declare module 'tuf-browser/dist/tuf.js' {
2
+ export class TUFClient {
3
+ constructor(
4
+ repositoryUrl: string,
5
+ startingRoot: string,
6
+ namespace: string,
7
+ targetBaseUrl?: string
8
+ );
9
+
10
+ updateTUF(): Promise<void>;
11
+ getTarget(targetPath: string): Promise<Uint8Array>;
12
+ }
13
+ }
@@ -0,0 +1,319 @@
1
+ /*
2
+ Copyright 2023 The Sigstore Authors.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ import { ASN1Obj } from "../asn1/index.js";
17
+ import { bufferEqual, importKey, verifySignature } from "../crypto.js";
18
+ import { Uint8ArrayToBase64 } from "../encoding.js";
19
+ import { KeyTypes } from "../interfaces.js";
20
+ import { ECDSA_CURVE_NAMES, ECDSA_SIGNATURE_ALGOS, RSA_SIGNATURE_ALGOS } from "../oid.js";
21
+ import * as pem from "../pem.js";
22
+ import {
23
+ X509AuthorityKeyIDExtension,
24
+ X509BasicConstraintsExtension,
25
+ X509Extension,
26
+ X509FulcioIssuerV1,
27
+ X509FulcioIssuerV2,
28
+ X509KeyUsageExtension,
29
+ X509SCTExtension,
30
+ X509SubjectAlternativeNameExtension,
31
+ X509SubjectKeyIDExtension,
32
+ } from "./ext.js";
33
+
34
+ // https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md
35
+ const EXTENSION_OID_SUBJECT_KEY_ID = "2.5.29.14";
36
+ const EXTENSION_OID_KEY_USAGE = "2.5.29.15";
37
+ const EXTENSION_OID_SUBJECT_ALT_NAME = "2.5.29.17";
38
+ const EXTENSION_OID_BASIC_CONSTRAINTS = "2.5.29.19";
39
+ const EXTENSION_OID_AUTHORITY_KEY_ID = "2.5.29.35";
40
+ const EXTENSION_OID_FULCIO_ISSUER_V1 = "1.3.6.1.4.1.57264.1.1";
41
+ const EXTENSION_OID_FULCIO_ISSUER_V2 = "1.3.6.1.4.1.57264.1.8";
42
+
43
+ export const EXTENSION_OID_SCT = "1.3.6.1.4.1.11129.2.4.2";
44
+
45
+ export class X509Certificate {
46
+ public root: ASN1Obj;
47
+
48
+ constructor(asn1: ASN1Obj) {
49
+ this.root = asn1;
50
+ }
51
+
52
+ public static parse(cert: Uint8Array | string): X509Certificate {
53
+ const der = typeof cert === "string" ? pem.toDER(cert) : cert;
54
+ const asn1 = ASN1Obj.parseBuffer(der);
55
+ return new X509Certificate(asn1);
56
+ }
57
+
58
+ get tbsCertificate(): ASN1Obj {
59
+ return this.tbsCertificateObj;
60
+ }
61
+
62
+ get version(): string {
63
+ // version number is the first element of the version context specific tag
64
+ const ver = this.versionObj.subs[0].toInteger();
65
+ return `v${(ver + BigInt(1)).toString()}`;
66
+ }
67
+
68
+ get serialNumber(): Uint8Array {
69
+ return this.serialNumberObj.value;
70
+ }
71
+
72
+ get notBefore(): Date {
73
+ // notBefore is the first element of the validity sequence
74
+ return this.validityObj.subs[0].toDate();
75
+ }
76
+
77
+ get notAfter(): Date {
78
+ // notAfter is the second element of the validity sequence
79
+ return this.validityObj.subs[1].toDate();
80
+ }
81
+
82
+ get issuer(): Uint8Array {
83
+ return this.issuerObj.value;
84
+ }
85
+
86
+ get subject(): Uint8Array {
87
+ return this.subjectObj.value;
88
+ }
89
+
90
+ get publicKey(): Uint8Array {
91
+ return this.subjectPublicKeyInfoObj.toDER();
92
+ }
93
+
94
+ /**
95
+ * Import the public key with a specific hash algorithm for RSA keys
96
+ * For ECDSA keys, the hash parameter is ignored
97
+ */
98
+ public async getPublicKeyObj(hashAlg?: string): Promise<CryptoKey> {
99
+ const publicKey = this.subjectPublicKeyInfoObj.toDER();
100
+ const spki = ASN1Obj.parseBuffer(publicKey);
101
+ const algorithmOID = spki.subs[0].subs[0].toOID();
102
+
103
+ // Check if it's an RSA key (OID 1.2.840.113549.1.1.1)
104
+ if (algorithmOID === "1.2.840.113549.1.1.1") {
105
+ // RSA key - use provided hash or default to SHA-256
106
+ const hash = hashAlg || "sha256";
107
+ let scheme: string;
108
+ if (hash.includes("384")) {
109
+ scheme = "SHA384";
110
+ } else if (hash.includes("512")) {
111
+ scheme = "SHA512";
112
+ } else {
113
+ scheme = "SHA256";
114
+ }
115
+ return importKey("RSA", `RSA_PKCS1V5_${scheme}`, Uint8ArrayToBase64(publicKey));
116
+ } else {
117
+ // ECDSA key - the curve OID is in the second element
118
+ const curveOID = spki.subs[0].subs[1]?.toOID();
119
+ const curve = ECDSA_CURVE_NAMES[curveOID];
120
+ if (!curve) {
121
+ throw new Error(`Unknown ECDSA curve OID: ${curveOID}`);
122
+ }
123
+ return importKey(KeyTypes.Ecdsa, curve, Uint8ArrayToBase64(publicKey));
124
+ }
125
+ }
126
+
127
+ get publicKeyObj(): Promise<CryptoKey> {
128
+ return this.getPublicKeyObj();
129
+ }
130
+
131
+ get signatureAlgorithm(): string {
132
+ const oid: string = this.signatureAlgorithmObj.subs[0].toOID();
133
+ return ECDSA_SIGNATURE_ALGOS[oid];
134
+ }
135
+
136
+ get signatureValue(): Uint8Array {
137
+ // Signature value is a bit string, so we need to skip the first byte
138
+ return this.signatureValueObj.value.subarray(1);
139
+ }
140
+
141
+ get subjectAltName(): string | undefined {
142
+ const ext = this.extSubjectAltName;
143
+ return ext?.uri || ext?.rfc822Name;
144
+ }
145
+
146
+ get extensions(): ASN1Obj[] {
147
+ // The extension list is the first (and only) element of the extensions
148
+ // context specific tag
149
+ const extSeq = this.extensionsObj?.subs[0];
150
+ return extSeq?.subs || /* istanbul ignore next */ [];
151
+ }
152
+
153
+ get extKeyUsage(): X509KeyUsageExtension | undefined {
154
+ const ext = this.findExtension(EXTENSION_OID_KEY_USAGE);
155
+ return ext ? new X509KeyUsageExtension(ext) : undefined;
156
+ }
157
+
158
+ get extBasicConstraints(): X509BasicConstraintsExtension | undefined {
159
+ const ext = this.findExtension(EXTENSION_OID_BASIC_CONSTRAINTS);
160
+ return ext ? new X509BasicConstraintsExtension(ext) : undefined;
161
+ }
162
+
163
+ get extSubjectAltName(): X509SubjectAlternativeNameExtension | undefined {
164
+ const ext = this.findExtension(EXTENSION_OID_SUBJECT_ALT_NAME);
165
+ return ext ? new X509SubjectAlternativeNameExtension(ext) : undefined;
166
+ }
167
+
168
+ get extAuthorityKeyID(): X509AuthorityKeyIDExtension | undefined {
169
+ const ext = this.findExtension(EXTENSION_OID_AUTHORITY_KEY_ID);
170
+ return ext ? new X509AuthorityKeyIDExtension(ext) : undefined;
171
+ }
172
+
173
+ get extSubjectKeyID(): X509SubjectKeyIDExtension | undefined {
174
+ const ext = this.findExtension(EXTENSION_OID_SUBJECT_KEY_ID);
175
+ return ext
176
+ ? new X509SubjectKeyIDExtension(ext)
177
+ : /* istanbul ignore next */ undefined;
178
+ }
179
+
180
+ get extSCT(): X509SCTExtension | undefined {
181
+ const ext = this.findExtension(EXTENSION_OID_SCT);
182
+ return ext ? new X509SCTExtension(ext) : undefined;
183
+ }
184
+
185
+ get extFulcioIssuerV1(): X509FulcioIssuerV1 | undefined {
186
+ const ext = this.findExtension(EXTENSION_OID_FULCIO_ISSUER_V1);
187
+ return ext ? new X509FulcioIssuerV1(ext) : undefined;
188
+ }
189
+
190
+ get extFulcioIssuerV2(): X509FulcioIssuerV2 | undefined {
191
+ const ext = this.findExtension(EXTENSION_OID_FULCIO_ISSUER_V2);
192
+ return ext ? new X509FulcioIssuerV2(ext) : undefined;
193
+ }
194
+
195
+ get isCA(): boolean {
196
+ const ca = this.extBasicConstraints?.isCA || false;
197
+
198
+ // If the KeyUsage extension is present, keyCertSign must be set
199
+ if (this.extKeyUsage) {
200
+ return ca && this.extKeyUsage.keyCertSign;
201
+ }
202
+
203
+ return ca;
204
+ }
205
+
206
+ public extension(oid: string): X509Extension | undefined {
207
+ const ext = this.findExtension(oid);
208
+ return ext ? new X509Extension(ext) : undefined;
209
+ }
210
+
211
+ public async verify(issuerCertificate?: X509Certificate): Promise<boolean> {
212
+ // Extract the hash algorithm from this certificate's signature algorithm
213
+ // (not the issuer's own signature algorithm)
214
+ const sigAlgOID = this.signatureAlgorithmObj.subs[0].toOID();
215
+ const hashAlg = RSA_SIGNATURE_ALGOS[sigAlgOID] || ECDSA_SIGNATURE_ALGOS[sigAlgOID];
216
+
217
+ // Use the issuer's public key if provided, otherwise use the subject's (for self-signed certs)
218
+ // Import with the correct hash algorithm for this certificate's signature
219
+ const publicKeyObj = issuerCertificate
220
+ ? await issuerCertificate.getPublicKeyObj(hashAlg)
221
+ : await this.getPublicKeyObj(hashAlg);
222
+
223
+ return await verifySignature(
224
+ publicKeyObj,
225
+ this.tbsCertificate.toDER(),
226
+ this.signatureValue,
227
+ this.signatureAlgorithm,
228
+ );
229
+ }
230
+
231
+ public validForDate(date: Date): boolean {
232
+ return this.notBefore <= date && date <= this.notAfter;
233
+ }
234
+
235
+ public equals(other: X509Certificate): boolean {
236
+ return bufferEqual(this.root.toDER(), other.root.toDER());
237
+ }
238
+
239
+ // Creates a copy of the certificate with a new buffer
240
+ public clone(): X509Certificate {
241
+ const der = this.root.toDER();
242
+ const clone = new Uint8Array(der);
243
+ return X509Certificate.parse(clone);
244
+ }
245
+
246
+ private findExtension(oid: string): ASN1Obj | undefined {
247
+ // Find the extension with the given OID. The OID will always be the first
248
+ // element of the extension sequence
249
+ return this.extensions.find((ext) => ext.subs[0].toOID() === oid);
250
+ }
251
+
252
+ /////////////////////////////////////////////////////////////////////////////
253
+ // The following properties use the documented x509 structure to locate the
254
+ // desired ASN.1 object
255
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1
256
+
257
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.1
258
+ private get tbsCertificateObj(): ASN1Obj {
259
+ // tbsCertificate is the first element of the certificate sequence
260
+ return this.root.subs[0];
261
+ }
262
+
263
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.2
264
+ private get signatureAlgorithmObj(): ASN1Obj {
265
+ // signatureAlgorithm is the second element of the certificate sequence
266
+ return this.root.subs[1];
267
+ }
268
+
269
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.3
270
+ private get signatureValueObj(): ASN1Obj {
271
+ // signatureValue is the third element of the certificate sequence
272
+ return this.root.subs[2];
273
+ }
274
+
275
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.1
276
+ private get versionObj(): ASN1Obj {
277
+ // version is the first element of the tbsCertificate sequence
278
+ return this.tbsCertificateObj.subs[0];
279
+ }
280
+
281
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.2
282
+ private get serialNumberObj(): ASN1Obj {
283
+ // serialNumber is the second element of the tbsCertificate sequence
284
+ return this.tbsCertificateObj.subs[1];
285
+ }
286
+
287
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.4
288
+ private get issuerObj(): ASN1Obj {
289
+ // issuer is the fourth element of the tbsCertificate sequence
290
+ return this.tbsCertificateObj.subs[3];
291
+ }
292
+
293
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.5
294
+ private get validityObj(): ASN1Obj {
295
+ // version is the fifth element of the tbsCertificate sequence
296
+ return this.tbsCertificateObj.subs[4];
297
+ }
298
+
299
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.6
300
+ private get subjectObj(): ASN1Obj {
301
+ // subject is the sixth element of the tbsCertificate sequence
302
+ return this.tbsCertificateObj.subs[5];
303
+ }
304
+
305
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.7
306
+ private get subjectPublicKeyInfoObj(): ASN1Obj {
307
+ // subjectPublicKeyInfo is the seventh element of the tbsCertificate sequence
308
+ return this.tbsCertificateObj.subs[6];
309
+ }
310
+
311
+ // Extensions can't be located by index because their position varies. Instead,
312
+ // we need to find the extensions context specific tag
313
+ // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.9
314
+ private get extensionsObj(): ASN1Obj | undefined {
315
+ return this.tbsCertificateObj.subs.find((sub) =>
316
+ sub.tag.isContextSpecific(0x03),
317
+ );
318
+ }
319
+ }