@noz-ele/edgca 0.1.0 → 0.2.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.
- package/README.md +98 -17
- package/dist/ca.d.ts +2 -1
- package/dist/ca.d.ts.map +1 -1
- package/dist/ca.js +63 -34
- package/dist/crypto.d.ts +7 -7
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +98 -44
- package/dist/csr.d.ts +25 -0
- package/dist/csr.d.ts.map +1 -0
- package/dist/csr.js +266 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/oids.d.ts +7 -0
- package/dist/oids.d.ts.map +1 -1
- package/dist/oids.js +8 -1
- package/dist/pem.d.ts +0 -2
- package/dist/pem.d.ts.map +1 -1
- package/dist/pem.js +0 -6
- package/dist/types.d.ts +18 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/x509.d.ts +4 -2
- package/dist/x509.d.ts.map +1 -1
- package/dist/x509.js +6 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,15 +9,19 @@ The scope is intentionally narrow:
|
|
|
9
9
|
- Create a self-signed root CA.
|
|
10
10
|
- Issue an intermediate CA from a root CA.
|
|
11
11
|
- Issue an mTLS client certificate and private key from an intermediate CA.
|
|
12
|
+
- Issue an mTLS client certificate from a caller-provided public key (no private key returned). Pairs with the CSR helpers below.
|
|
13
|
+
- Parse a PKCS#10 CSR (subject, requested SAN, public key, raw extensions/attributes) and verify its proof-of-possession signature.
|
|
12
14
|
- Decide whether a received client certificate was issued by your own CA.
|
|
13
|
-
- Encode/decode certificates
|
|
15
|
+
- Encode/decode certificates as PEM/DER. Keys are exchanged as `CryptoKey` only — the library never returns or accepts string forms (PEM, JWK, etc.) of private keys.
|
|
14
16
|
- Delegate all cryptographic operations to `globalThis.crypto.subtle`.
|
|
15
17
|
|
|
18
|
+
ECDSA on **NIST P-256, P-384, and P-521** is supported throughout (signing, verification, CSR parsing). RSA, EdDSA, and other curves are intentionally out of scope.
|
|
19
|
+
|
|
16
20
|
> ⚠ **Not a PKI runtime.** EdgCA is an issuance toolkit, not a general-purpose PKI library or runtime. It does **not** provide chain validation, revocation (CRL/OCSP), key storage, or rotation. `verifyClientCertificateIssuedBy` is **not** mTLS verification and does **not** authenticate the presenter — see [Verify](#verify-cloudflare-worker) below. Operating a CA safely is the caller's responsibility. Full list: [docs/en/NON_GOALS.md](https://github.com/noz-ele/EdgCA/blob/main/docs/en/NON_GOALS.md).
|
|
17
21
|
|
|
18
22
|
## Status
|
|
19
23
|
|
|
20
|
-
EdgCA is in **v0.
|
|
24
|
+
EdgCA is in **v0.2.x — early stabilization**. The author is currently validating the library against real Cloudflare Workers deployments, and the API surface may still shift. To keep that validation focused, **external Issues and PRs are temporarily restricted** and will be re-opened once the API settles. Reading, cloning, forking, and `npm install` are unaffected.
|
|
21
25
|
|
|
22
26
|
## Install
|
|
23
27
|
|
|
@@ -57,10 +61,12 @@ const client = await issueClientCert({
|
|
|
57
61
|
});
|
|
58
62
|
|
|
59
63
|
// Persist these via your secrets manager / KV / vault.
|
|
60
|
-
//
|
|
64
|
+
// `client.privateKey` is a CryptoKey. To persist it, export with
|
|
65
|
+
// crypto.subtle.exportKey("pkcs8", client.privateKey) (or another form)
|
|
66
|
+
// and treat the resulting bytes as a secret — never log or transmit them.
|
|
61
67
|
// client.certPem — public certificate
|
|
62
68
|
// client.certChainPem — full chain to present during mTLS
|
|
63
|
-
// client.
|
|
69
|
+
// client.privateKey — secret CryptoKey, hand off only over a trusted channel
|
|
64
70
|
```
|
|
65
71
|
|
|
66
72
|
The basic shape is:
|
|
@@ -117,12 +123,22 @@ These parsers live in the caller, not in the library, because (a) we do not want
|
|
|
117
123
|
### Example
|
|
118
124
|
|
|
119
125
|
```ts
|
|
120
|
-
import { importCertificateAuthority, verifyClientCertificateIssuedBy } from "@noz-ele/edgca";
|
|
126
|
+
import { importCertificateAuthority, pemToDer, verifyClientCertificateIssuedBy } from "@noz-ele/edgca";
|
|
121
127
|
|
|
122
128
|
// At Worker startup: import the CA loaded from your vault once.
|
|
129
|
+
// The library accepts the private key as a CryptoKey only — convert from
|
|
130
|
+
// whatever persistence format you use (PKCS#8 PEM, JWK, raw bytes, ...).
|
|
131
|
+
const pkcs8Der = pemToDer(env.CA_PRIVATE_KEY_PEM);
|
|
132
|
+
const privateKey = await crypto.subtle.importKey(
|
|
133
|
+
"pkcs8",
|
|
134
|
+
pkcs8Der,
|
|
135
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
136
|
+
/* extractable */ false,
|
|
137
|
+
["sign"]
|
|
138
|
+
);
|
|
123
139
|
const ca = await importCertificateAuthority({
|
|
124
140
|
certPem: env.CA_CERT_PEM,
|
|
125
|
-
|
|
141
|
+
privateKey
|
|
126
142
|
});
|
|
127
143
|
|
|
128
144
|
export default {
|
|
@@ -169,6 +185,42 @@ export default {
|
|
|
169
185
|
- "Not issued by us" and "outside the validity window" return `false`; malformed PEM/DER throws. The two error categories are deliberately split.
|
|
170
186
|
- Pass the **direct issuer (one cert)** as `ca`. Verifying a leaf issued via an intermediate against the root will return `false` — chain walking is not performed.
|
|
171
187
|
|
|
188
|
+
## Issue from a CSR
|
|
189
|
+
|
|
190
|
+
When a client manages its own private key and submits a PKCS#10 CSR, EdgCA parses the CSR, verifies its proof-of-possession signature, and issues a certificate that embeds the CSR's public key. The library does **not** auto-adopt the CSR's claimed subject / SAN — the caller passes those explicitly, derived from whatever policy applies in the application layer.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import {
|
|
194
|
+
importCertificateAuthority,
|
|
195
|
+
issueClientCertForPublicKey,
|
|
196
|
+
parseCertificateSigningRequest,
|
|
197
|
+
verifyCertificateSigningRequestSignature
|
|
198
|
+
} from "@noz-ele/edgca";
|
|
199
|
+
|
|
200
|
+
const csr = await parseCertificateSigningRequest(csrPemFromClient);
|
|
201
|
+
if (!await verifyCertificateSigningRequestSignature(csr)) {
|
|
202
|
+
return new Response("CSR proof-of-possession failed", { status: 400 });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Application decides what subject and SAN to issue with. The CSR's claimed
|
|
206
|
+
// values are available on csr.subject / csr.requestedDnsNames /
|
|
207
|
+
// csr.requestedIpAddresses, but treating them as authoritative is a policy
|
|
208
|
+
// decision that lives outside EdgCA.
|
|
209
|
+
const issued = await issueClientCertForPublicKey({
|
|
210
|
+
ca,
|
|
211
|
+
publicKey: csr.publicKey,
|
|
212
|
+
subject: policyDerivedSubject,
|
|
213
|
+
days: 30,
|
|
214
|
+
dnsNames: policyDerivedDnsNames
|
|
215
|
+
});
|
|
216
|
+
// issued has certPem / certDer / certChainPem only — no privateKey, because
|
|
217
|
+
// the client owns it.
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
CSRs signed with anything other than `ecdsa-with-SHA256` / `ecdsa-with-SHA384` / `ecdsa-with-SHA512` are rejected at parse time with an explicit error. CSR-level attributes other than `extensionRequest` are surfaced as raw DER under `csr.otherAttributes` for callers that need them; X.509 extensions other than SAN are surfaced under `csr.requestedExtensions` as `{ oid, critical, valueDer }` for caller-side decoding.
|
|
221
|
+
|
|
222
|
+
POP verification proves only that whoever produced the CSR holds the matching private key. **It is not authorization.** Combine it with whatever transport-level (mTLS) and application-level checks make sense for your enrollment flow.
|
|
223
|
+
|
|
172
224
|
## Subject
|
|
173
225
|
|
|
174
226
|
Subject only accepts a structured input. DN strings such as `CN=dev-root,O=Example` are not accepted.
|
|
@@ -194,13 +246,14 @@ Dotted OID strings are also accepted. The ASN.1 string type for values is fixed
|
|
|
194
246
|
|
|
195
247
|
In scope:
|
|
196
248
|
|
|
197
|
-
- ECDSA P-256
|
|
249
|
+
- ECDSA on NIST P-256 / P-384 / P-521 (paired with SHA-256 / SHA-384 / SHA-512 respectively).
|
|
198
250
|
- Key generation, signing, digest, and key import/export via WebCrypto.
|
|
199
251
|
- Root CA creation.
|
|
200
252
|
- Intermediate CA issuance.
|
|
201
|
-
- mTLS client certificate issuance.
|
|
253
|
+
- mTLS client certificate issuance (with internal key generation, or from a caller-provided public key).
|
|
254
|
+
- CSR (PKCS#10) parsing and proof-of-possession signature verification.
|
|
202
255
|
- Identity check that a cert was issued by your own CA (`verifyClientCertificateIssuedBy`, with optional time-validity check).
|
|
203
|
-
- PEM/DER helpers.
|
|
256
|
+
- PEM/DER helpers (certificates only — keys are exchanged as `CryptoKey`).
|
|
204
257
|
- Basic Constraints, Key Usage, Extended Key Usage, Subject Alternative Name, SKI, AKI.
|
|
205
258
|
|
|
206
259
|
Intentionally out of scope:
|
|
@@ -210,36 +263,64 @@ Intentionally out of scope:
|
|
|
210
263
|
- Extracting time fields from a cert. `verifyClientCertificateIssuedBy`'s `validity` option performs the time check, but the `notBefore` / `notAfter` values are passed in by the caller from `cf.tlsClientAuth`.
|
|
211
264
|
- CRL, OCSP, revocation databases, revocation checks.
|
|
212
265
|
- Key storage, encryption-at-rest, rotation-state persistence, and integration with KV/D1/R2/Secrets.
|
|
213
|
-
- RSA, EdDSA, other elliptic curves.
|
|
266
|
+
- RSA, EdDSA, other elliptic curves (CSRs signed with these algorithms are rejected at parse time).
|
|
267
|
+
- A general certificate parsing API (Cloudflare hands you parsed values via `cf.tlsClientAuth.cert*`; the library does not duplicate that).
|
|
268
|
+
- Issuance policy decisions (whether to honor a CSR's claimed subject/SAN, deduplicate, etc.) — caller's responsibility.
|
|
214
269
|
- DN string parsing.
|
|
215
270
|
- Multi-valued RDNs.
|
|
216
271
|
|
|
217
272
|
## Key Handling
|
|
218
273
|
|
|
219
|
-
|
|
274
|
+
EdgCA exchanges keys as `CryptoKey` only. The library never returns or accepts string forms (PEM, JWK, base64, ...) of private keys, so secret material does not live on the JS string heap at the library boundary. Internally generated keys are extractable so the caller can persist them by calling `crypto.subtle.exportKey` directly, but the choice of persistence format is the caller's.
|
|
220
275
|
|
|
221
|
-
EdgCA only handles key generation and
|
|
276
|
+
EdgCA only handles key generation, signing, and SPKI export of public keys. Where keys are stored, how they are encrypted at rest, how rotation state is persisted, and how they integrate with Cloudflare storage products are all the application's responsibility.
|
|
222
277
|
|
|
223
278
|
### Bringing your own CA key (recommended)
|
|
224
279
|
|
|
225
|
-
Root and intermediate CAs are long-lived. To keep key management on the caller's side, `createRootCA` and `issueIntermediateCA` accept an existing `
|
|
280
|
+
Root and intermediate CAs are long-lived. To keep key management on the caller's side, `createRootCA` and `issueIntermediateCA` accept an existing `keyPair: CryptoKeyPair`. This lets the caller's key-management infrastructure handle the full key lifecycle (generation, storage, rotation) consistently — including the choice of persistence format — which is the recommended path.
|
|
226
281
|
|
|
227
282
|
```ts
|
|
283
|
+
// Restore a CryptoKeyPair from whatever persistence format you use.
|
|
284
|
+
// Below is one example that converts PKCS#8 PEM stored in a vault.
|
|
285
|
+
async function loadKeyPair(label: string): Promise<CryptoKeyPair> {
|
|
286
|
+
const pkcs8 = pemToDer(loadFromVault(`${label}-private-pem`));
|
|
287
|
+
const privateKey = await crypto.subtle.importKey(
|
|
288
|
+
"pkcs8",
|
|
289
|
+
pkcs8,
|
|
290
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
291
|
+
/* extractable */ false,
|
|
292
|
+
["sign"]
|
|
293
|
+
);
|
|
294
|
+
// Derive the matching public key. If you also persist the public key as
|
|
295
|
+
// SPKI, import that directly instead of round-tripping through JWK.
|
|
296
|
+
const jwk = await crypto.subtle.exportKey("jwk", privateKey);
|
|
297
|
+
delete jwk.d;
|
|
298
|
+
jwk.key_ops = ["verify"];
|
|
299
|
+
const publicKey = await crypto.subtle.importKey(
|
|
300
|
+
"jwk",
|
|
301
|
+
jwk,
|
|
302
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
303
|
+
true,
|
|
304
|
+
["verify"]
|
|
305
|
+
);
|
|
306
|
+
return { privateKey, publicKey };
|
|
307
|
+
}
|
|
308
|
+
|
|
228
309
|
const root = await createRootCA({
|
|
229
310
|
subject: [{ type: "CN", value: "dev-root" }],
|
|
230
311
|
days: 3650,
|
|
231
|
-
|
|
312
|
+
keyPair: await loadKeyPair("root")
|
|
232
313
|
});
|
|
233
314
|
|
|
234
315
|
const intermediate = await issueIntermediateCA({
|
|
235
316
|
ca: root,
|
|
236
317
|
subject: [{ type: "CN", value: "dev-intermediate" }],
|
|
237
318
|
days: 365,
|
|
238
|
-
|
|
319
|
+
keyPair: await loadKeyPair("intermediate")
|
|
239
320
|
});
|
|
240
321
|
```
|
|
241
322
|
|
|
242
|
-
Omitting `
|
|
323
|
+
Omitting `keyPair` causes the library to generate a key pair internally — convenient for tests and PoCs. Client-certificate keys are intended to be ephemeral, so `issueClientCert` always generates internally.
|
|
243
324
|
|
|
244
325
|
## Development
|
|
245
326
|
|
|
@@ -259,7 +340,7 @@ Round-trip invariants in the lower layers are expressed as `fast-check` property
|
|
|
259
340
|
- [test/der.property.test.ts](https://github.com/noz-ele/EdgCA/blob/main/test/der.property.test.ts) — TLV round-trip for INTEGER / OID / OCTET STRING / BIT STRING / SEQUENCE
|
|
260
341
|
- [test/bytes.property.test.ts](https://github.com/noz-ele/EdgCA/blob/main/test/bytes.property.test.ts) — `concatBytes`, `binaryToBytes`/`bytesToBinary`, `bytesEqual`, `cloneBytes`
|
|
261
342
|
- [test/ip.property.test.ts](https://github.com/noz-ele/EdgCA/blob/main/test/ip.property.test.ts) — IPv4 dotted-quad and IPv6 (full form / `::` compression) encoding
|
|
262
|
-
- [test/pem.property.test.ts](https://github.com/noz-ele/EdgCA/blob/main/test/pem.property.test.ts) — round-trip between `certificateToPem`
|
|
343
|
+
- [test/pem.property.test.ts](https://github.com/noz-ele/EdgCA/blob/main/test/pem.property.test.ts) — round-trip between `certificateToPem` and `pemToDer` / `pemToDerWithLabel` / `splitPemBlocks`
|
|
263
344
|
|
|
264
345
|
`vitest.config.ts` includes `test/**/*.test.ts`, so `npm run test` runs them all together. The certificate-assembly layer (`ca.ts` / `x509.ts`) is intentionally outside the PBT scope and stays example-based in [test/edgca.test.ts](https://github.com/noz-ele/EdgCA/blob/main/test/edgca.test.ts).
|
|
265
346
|
|
package/dist/ca.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { CertificateAuthority, CreateRootCAOptions, ImportCertificateAuthorityOptions, IssueClientCertOptions, IssueIntermediateCAOptions, IssuedClientCertificate } from "./types.js";
|
|
1
|
+
import type { CertificateAuthority, CreateRootCAOptions, ImportCertificateAuthorityOptions, IssueClientCertForPublicKeyOptions, IssueClientCertOptions, IssueIntermediateCAOptions, IssuedClientCertificate, IssuedClientCertificateForPublicKey } from "./types.js";
|
|
2
2
|
export declare function createRootCA(options: CreateRootCAOptions): Promise<CertificateAuthority>;
|
|
3
3
|
export declare function issueIntermediateCA(options: IssueIntermediateCAOptions): Promise<CertificateAuthority>;
|
|
4
4
|
export declare function issueClientCert(options: IssueClientCertOptions): Promise<IssuedClientCertificate>;
|
|
5
|
+
export declare function issueClientCertForPublicKey(options: IssueClientCertForPublicKeyOptions): Promise<IssuedClientCertificateForPublicKey>;
|
|
5
6
|
export declare function importCertificateAuthority(options: ImportCertificateAuthorityOptions): Promise<CertificateAuthority>;
|
|
6
7
|
//# sourceMappingURL=ca.d.ts.map
|
package/dist/ca.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ca.d.ts","sourceRoot":"","sources":["../src/ca.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ca.d.ts","sourceRoot":"","sources":["../src/ca.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACnB,iCAAiC,EACjC,kCAAkC,EAClC,sBAAsB,EACtB,0BAA0B,EAC1B,uBAAuB,EACvB,mCAAmC,EAEpC,MAAM,YAAY,CAAC;AAapB,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA0B9F;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgC5G;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAqBvG;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,mCAAmC,CAAC,CAkB9C;AAuDD,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,iCAAiC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAe1H"}
|
package/dist/ca.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { bytesEqual, cloneBytes } from "./bytes.js";
|
|
2
|
-
import { assertKeyPairMatches, exportSpki, generateKeyPair,
|
|
2
|
+
import { assertKeyPairMatches, curveOf, exportSpki, generateKeyPair, keyIdentifierFromSpki, signDer } from "./crypto.js";
|
|
3
3
|
import { encodeName } from "./name.js";
|
|
4
4
|
import { certificateToPem, pemToDer, pemToDerWithLabel, splitPemBlocks } from "./pem.js";
|
|
5
5
|
import { parseCertificateDer } from "./parser.js";
|
|
6
6
|
import { authorityKeyIdentifierExtension, basicConstraintsCaExtension, basicConstraintsLeafExtension, buildCertificate, buildTbsCertificate, extendedKeyUsageClientAuthExtension, keyUsageExtension, subjectAltNameExtension, subjectKeyIdentifierExtension } from "./x509.js";
|
|
7
7
|
export async function createRootCA(options) {
|
|
8
|
-
const keyPair = await resolveKeyPair(options.
|
|
8
|
+
const keyPair = await resolveKeyPair(options.keyPair);
|
|
9
|
+
const issuerCurve = curveOf(keyPair.privateKey);
|
|
9
10
|
const subjectNameDer = encodeName(options.subject);
|
|
10
11
|
const spki = await exportSpki(keyPair.publicKey);
|
|
11
12
|
const keyIdentifier = await keyIdentifierFromSpki(spki);
|
|
@@ -23,10 +24,11 @@ export async function createRootCA(options) {
|
|
|
23
24
|
issuerNameDer: subjectNameDer,
|
|
24
25
|
subjectNameDer,
|
|
25
26
|
subjectPublicKeyInfoDer: spki,
|
|
26
|
-
extensions
|
|
27
|
+
extensions,
|
|
28
|
+
issuerCurve
|
|
27
29
|
});
|
|
28
30
|
const signatureDer = await signDer(keyPair.privateKey, tbsCertificateDer);
|
|
29
|
-
const certDer = buildCertificate(tbsCertificateDer, signatureDer);
|
|
31
|
+
const certDer = buildCertificate(tbsCertificateDer, signatureDer, issuerCurve);
|
|
30
32
|
return assembleCertificateAuthority(certDer, keyPair, "");
|
|
31
33
|
}
|
|
32
34
|
export async function issueIntermediateCA(options) {
|
|
@@ -34,7 +36,8 @@ export async function issueIntermediateCA(options) {
|
|
|
34
36
|
const issuerChainPem = options.ca.issuerChainPem;
|
|
35
37
|
assertCanIssueCertificate(issuer);
|
|
36
38
|
assertCanIssueIntermediate(issuer, issuerChainPem, options.pathLenConstraint);
|
|
37
|
-
const
|
|
39
|
+
const issuerCurve = curveOf(options.ca.privateKey);
|
|
40
|
+
const keyPair = await resolveKeyPair(options.keyPair);
|
|
38
41
|
const subjectNameDer = encodeName(options.subject);
|
|
39
42
|
const spki = await exportSpki(keyPair.publicKey);
|
|
40
43
|
const subjectKeyIdentifier = await keyIdentifierFromSpki(spki);
|
|
@@ -52,23 +55,57 @@ export async function issueIntermediateCA(options) {
|
|
|
52
55
|
issuerNameDer: issuer.subjectNameDer,
|
|
53
56
|
subjectNameDer,
|
|
54
57
|
subjectPublicKeyInfoDer: spki,
|
|
55
|
-
extensions
|
|
58
|
+
extensions,
|
|
59
|
+
issuerCurve
|
|
56
60
|
});
|
|
57
61
|
const signatureDer = await signDer(options.ca.privateKey, tbsCertificateDer);
|
|
58
|
-
const certDer = buildCertificate(tbsCertificateDer, signatureDer);
|
|
62
|
+
const certDer = buildCertificate(tbsCertificateDer, signatureDer, issuerCurve);
|
|
59
63
|
const childChainPem = joinPemChain([options.ca.certPem, issuerChainPem]);
|
|
60
64
|
return assembleCertificateAuthority(certDer, keyPair, childChainPem);
|
|
61
65
|
}
|
|
62
66
|
export async function issueClientCert(options) {
|
|
63
|
-
const issuer = await parseIssuer(options.ca);
|
|
64
|
-
const issuerChainPem = options.ca.issuerChainPem;
|
|
65
|
-
assertCanIssueCertificate(issuer);
|
|
66
67
|
const keyPair = await generateKeyPair();
|
|
67
|
-
const
|
|
68
|
-
|
|
68
|
+
const built = await buildClientCertificate(options.ca, keyPair.publicKey, {
|
|
69
|
+
subject: options.subject,
|
|
70
|
+
days: options.days,
|
|
71
|
+
notBefore: options.notBefore,
|
|
72
|
+
serialNumber: options.serialNumber,
|
|
73
|
+
dnsNames: options.dnsNames,
|
|
74
|
+
ipAddresses: options.ipAddresses
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
certPem: built.certPem,
|
|
78
|
+
certDer: built.certDer,
|
|
79
|
+
privateKey: keyPair.privateKey,
|
|
80
|
+
publicKey: keyPair.publicKey,
|
|
81
|
+
certChainPem: built.certChainPem
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export async function issueClientCertForPublicKey(options) {
|
|
85
|
+
const built = await buildClientCertificate(options.ca, options.publicKey, {
|
|
86
|
+
subject: options.subject,
|
|
87
|
+
days: options.days,
|
|
88
|
+
notBefore: options.notBefore,
|
|
89
|
+
serialNumber: options.serialNumber,
|
|
90
|
+
dnsNames: options.dnsNames,
|
|
91
|
+
ipAddresses: options.ipAddresses
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
certPem: built.certPem,
|
|
95
|
+
certDer: built.certDer,
|
|
96
|
+
certChainPem: built.certChainPem
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function buildClientCertificate(ca, subjectPublicKey, content) {
|
|
100
|
+
const issuer = await parseIssuer(ca);
|
|
101
|
+
const issuerChainPem = ca.issuerChainPem;
|
|
102
|
+
assertCanIssueCertificate(issuer);
|
|
103
|
+
const issuerCurve = curveOf(ca.privateKey);
|
|
104
|
+
const subjectNameDer = encodeName(content.subject);
|
|
105
|
+
const spki = await exportSpki(subjectPublicKey);
|
|
69
106
|
const subjectKeyIdentifier = await keyIdentifierFromSpki(spki);
|
|
70
107
|
const authorityKeyIdentifier = issuer.subjectKeyIdentifier ?? await keyIdentifierFromSpki(issuer.subjectPublicKeyInfoDer);
|
|
71
|
-
const san = subjectAltNameExtension(
|
|
108
|
+
const san = subjectAltNameExtension(content.dnsNames, content.ipAddresses);
|
|
72
109
|
const extensions = [
|
|
73
110
|
basicConstraintsLeafExtension(),
|
|
74
111
|
keyUsageExtension(["digitalSignature"]),
|
|
@@ -78,25 +115,22 @@ export async function issueClientCert(options) {
|
|
|
78
115
|
...(san ? [san] : [])
|
|
79
116
|
];
|
|
80
117
|
const { tbsCertificateDer } = buildTbsCertificate({
|
|
81
|
-
serialNumber:
|
|
82
|
-
notBefore:
|
|
83
|
-
days:
|
|
118
|
+
serialNumber: content.serialNumber,
|
|
119
|
+
notBefore: content.notBefore,
|
|
120
|
+
days: content.days,
|
|
84
121
|
issuerNameDer: issuer.subjectNameDer,
|
|
85
122
|
subjectNameDer,
|
|
86
123
|
subjectPublicKeyInfoDer: spki,
|
|
87
|
-
extensions
|
|
124
|
+
extensions,
|
|
125
|
+
issuerCurve
|
|
88
126
|
});
|
|
89
|
-
const signatureDer = await signDer(
|
|
90
|
-
const certDer = buildCertificate(tbsCertificateDer, signatureDer);
|
|
127
|
+
const signatureDer = await signDer(ca.privateKey, tbsCertificateDer);
|
|
128
|
+
const certDer = buildCertificate(tbsCertificateDer, signatureDer, issuerCurve);
|
|
91
129
|
const certPem = certificateToPem(certDer);
|
|
92
130
|
return {
|
|
93
131
|
certPem,
|
|
94
|
-
privateKeyPem: await privateKeyToPem(keyPair.privateKey),
|
|
95
|
-
publicKeyPem: await publicKeyToPem(keyPair.publicKey),
|
|
96
132
|
certDer: cloneBytes(certDer),
|
|
97
|
-
|
|
98
|
-
publicKey: keyPair.publicKey,
|
|
99
|
-
certChainPem: joinPemChain([certPem, options.ca.certPem, issuerChainPem])
|
|
133
|
+
certChainPem: joinPemChain([certPem, ca.certPem, issuerChainPem])
|
|
100
134
|
};
|
|
101
135
|
}
|
|
102
136
|
export async function importCertificateAuthority(options) {
|
|
@@ -104,14 +138,11 @@ export async function importCertificateAuthority(options) {
|
|
|
104
138
|
assertIssuerChainPem(issuerChainPem);
|
|
105
139
|
const certDer = pemToDerWithLabel(options.certPem, "CERTIFICATE");
|
|
106
140
|
const parsed = await parseCertificateDer(certDer);
|
|
107
|
-
|
|
108
|
-
await assertKeyPairMatches(privateKey, parsed.publicKey);
|
|
141
|
+
await assertKeyPairMatches(options.privateKey, parsed.publicKey);
|
|
109
142
|
return {
|
|
110
143
|
certPem: options.certPem,
|
|
111
|
-
privateKeyPem: options.privateKeyPem,
|
|
112
|
-
publicKeyPem: await publicKeyToPem(parsed.publicKey),
|
|
113
144
|
certDer: cloneBytes(certDer),
|
|
114
|
-
privateKey,
|
|
145
|
+
privateKey: options.privateKey,
|
|
115
146
|
publicKey: parsed.publicKey,
|
|
116
147
|
issuerChainPem
|
|
117
148
|
};
|
|
@@ -128,17 +159,15 @@ function assertIssuerChainPem(chainPem) {
|
|
|
128
159
|
pemToDerWithLabel(block, "CERTIFICATE");
|
|
129
160
|
}
|
|
130
161
|
}
|
|
131
|
-
async function resolveKeyPair(
|
|
132
|
-
if (
|
|
133
|
-
return
|
|
162
|
+
async function resolveKeyPair(provided) {
|
|
163
|
+
if (provided !== undefined) {
|
|
164
|
+
return provided;
|
|
134
165
|
}
|
|
135
166
|
return generateKeyPair();
|
|
136
167
|
}
|
|
137
168
|
async function assembleCertificateAuthority(certDer, keyPair, issuerChainPem) {
|
|
138
169
|
return {
|
|
139
170
|
certPem: certificateToPem(certDer),
|
|
140
|
-
privateKeyPem: await privateKeyToPem(keyPair.privateKey),
|
|
141
|
-
publicKeyPem: await publicKeyToPem(keyPair.publicKey),
|
|
142
171
|
certDer: cloneBytes(certDer),
|
|
143
172
|
privateKey: keyPair.privateKey,
|
|
144
173
|
publicKey: keyPair.publicKey,
|
package/dist/crypto.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type SupportedCurve = "P-256" | "P-384" | "P-521";
|
|
2
|
+
export declare function curveOf(key: CryptoKey): SupportedCurve;
|
|
3
|
+
export declare function componentSizeForCurve(curve: SupportedCurve): number;
|
|
4
|
+
export declare function signatureAlgorithmOidForCurve(curve: SupportedCurve): string;
|
|
5
|
+
export declare function generateKeyPair(curve?: SupportedCurve): Promise<CryptoKeyPair>;
|
|
2
6
|
export declare function signDer(privateKey: CryptoKey, data: Uint8Array): Promise<Uint8Array>;
|
|
3
7
|
export declare function verifyDer(publicKey: CryptoKey, signatureDer: Uint8Array, data: Uint8Array): Promise<boolean>;
|
|
4
8
|
export declare function digestSha256(data: Uint8Array): Promise<Uint8Array>;
|
|
5
9
|
export declare function digestSha1(data: Uint8Array): Promise<Uint8Array>;
|
|
6
10
|
export declare function keyIdentifierFromSpki(spki: Uint8Array): Promise<Uint8Array>;
|
|
7
|
-
export declare function privateKeyToPem(key: CryptoKey): Promise<string>;
|
|
8
|
-
export declare function publicKeyToPem(key: CryptoKey): Promise<string>;
|
|
9
11
|
export declare function exportSpki(key: CryptoKey): Promise<Uint8Array>;
|
|
10
|
-
export declare function importPrivateKeyPem(pem: string): Promise<CryptoKey>;
|
|
11
|
-
export declare function keyPairFromPrivateKeyPem(pem: string): Promise<CryptoKeyPair>;
|
|
12
12
|
export declare function importPublicKeySpki(spki: Uint8Array): Promise<CryptoKey>;
|
|
13
13
|
export declare function assertKeyPairMatches(privateKey: CryptoKey, publicKey: CryptoKey): Promise<void>;
|
|
14
|
-
export declare function ecdsaRawToDer(raw: Uint8Array): Uint8Array;
|
|
15
|
-
export declare function ecdsaDerToRaw(signature: Uint8Array): Uint8Array;
|
|
14
|
+
export declare function ecdsaRawToDer(raw: Uint8Array, componentSize: number): Uint8Array;
|
|
15
|
+
export declare function ecdsaDerToRaw(signature: Uint8Array, componentSize: number): Uint8Array;
|
|
16
16
|
//# sourceMappingURL=crypto.d.ts.map
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAkCzD,wBAAgB,OAAO,CAAC,GAAG,EAAE,SAAS,GAAG,cAAc,CAUtD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAEnE;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAE3E;AAED,wBAAsB,eAAe,CAAC,KAAK,GAAE,cAAwB,GAAG,OAAO,CAAC,aAAa,CAAC,CAE7F;AAED,wBAAsB,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAO1F;AAED,wBAAsB,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CASlH;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAExE;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAEtE;AAID,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAWjF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAEpE;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAS9E;AAkCD,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAQrG;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,GAAG,UAAU,CAMhF;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,GAAG,UAAU,CAetF"}
|
package/dist/crypto.js
CHANGED
|
@@ -1,23 +1,61 @@
|
|
|
1
1
|
import { arrayBufferFromBytes, concatBytes } from "./bytes.js";
|
|
2
|
-
import { integer, readChildren, readElement, readSequenceChildren, sequence, TAG } from "./der.js";
|
|
3
|
-
import {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { decodeOid, integer, readChildren, readElement, readSequenceChildren, sequence, TAG } from "./der.js";
|
|
3
|
+
import { OID } from "./oids.js";
|
|
4
|
+
const CURVE_PROFILE = {
|
|
5
|
+
"P-256": {
|
|
6
|
+
componentSize: 32,
|
|
7
|
+
hash: "SHA-256",
|
|
8
|
+
signatureAlgorithmOid: OID.ecdsaWithSha256,
|
|
9
|
+
curveOid: OID.secp256r1
|
|
10
|
+
},
|
|
11
|
+
"P-384": {
|
|
12
|
+
componentSize: 48,
|
|
13
|
+
hash: "SHA-384",
|
|
14
|
+
signatureAlgorithmOid: OID.ecdsaWithSha384,
|
|
15
|
+
curveOid: OID.secp384r1
|
|
16
|
+
},
|
|
17
|
+
"P-521": {
|
|
18
|
+
componentSize: 66,
|
|
19
|
+
hash: "SHA-512",
|
|
20
|
+
signatureAlgorithmOid: OID.ecdsaWithSha512,
|
|
21
|
+
curveOid: OID.secp521r1
|
|
22
|
+
}
|
|
7
23
|
};
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
24
|
+
const CURVE_OID_TO_NAME = {
|
|
25
|
+
[OID.secp256r1]: "P-256",
|
|
26
|
+
[OID.secp384r1]: "P-384",
|
|
27
|
+
[OID.secp521r1]: "P-521"
|
|
11
28
|
};
|
|
12
|
-
export
|
|
13
|
-
|
|
29
|
+
export function curveOf(key) {
|
|
30
|
+
const algorithm = key.algorithm;
|
|
31
|
+
if (algorithm.name !== "ECDSA") {
|
|
32
|
+
throw new Error(`Expected ECDSA key, got ${algorithm.name}`);
|
|
33
|
+
}
|
|
34
|
+
const curve = algorithm.namedCurve;
|
|
35
|
+
if (curve !== "P-256" && curve !== "P-384" && curve !== "P-521") {
|
|
36
|
+
throw new Error(`Unsupported ECDSA curve: ${curve}`);
|
|
37
|
+
}
|
|
38
|
+
return curve;
|
|
39
|
+
}
|
|
40
|
+
export function componentSizeForCurve(curve) {
|
|
41
|
+
return CURVE_PROFILE[curve].componentSize;
|
|
42
|
+
}
|
|
43
|
+
export function signatureAlgorithmOidForCurve(curve) {
|
|
44
|
+
return CURVE_PROFILE[curve].signatureAlgorithmOid;
|
|
45
|
+
}
|
|
46
|
+
export async function generateKeyPair(curve = "P-256") {
|
|
47
|
+
return crypto.subtle.generateKey({ name: "ECDSA", namedCurve: curve }, true, ["sign", "verify"]);
|
|
14
48
|
}
|
|
15
49
|
export async function signDer(privateKey, data) {
|
|
16
|
-
const
|
|
17
|
-
|
|
50
|
+
const curve = curveOf(privateKey);
|
|
51
|
+
const profile = CURVE_PROFILE[curve];
|
|
52
|
+
const raw = new Uint8Array(await crypto.subtle.sign({ name: "ECDSA", hash: profile.hash }, privateKey, arrayBufferFromBytes(data)));
|
|
53
|
+
return ecdsaRawToDer(raw, profile.componentSize);
|
|
18
54
|
}
|
|
19
55
|
export async function verifyDer(publicKey, signatureDer, data) {
|
|
20
|
-
|
|
56
|
+
const curve = curveOf(publicKey);
|
|
57
|
+
const profile = CURVE_PROFILE[curve];
|
|
58
|
+
return crypto.subtle.verify({ name: "ECDSA", hash: profile.hash }, publicKey, arrayBufferFromBytes(ecdsaDerToRaw(signatureDer, profile.componentSize)), arrayBufferFromBytes(data));
|
|
21
59
|
}
|
|
22
60
|
export async function digestSha256(data) {
|
|
23
61
|
return new Uint8Array(await crypto.subtle.digest("SHA-256", arrayBufferFromBytes(data)));
|
|
@@ -39,30 +77,43 @@ export async function keyIdentifierFromSpki(spki) {
|
|
|
39
77
|
}
|
|
40
78
|
return digestSha1(subjectPublicKey.value.subarray(1));
|
|
41
79
|
}
|
|
42
|
-
export async function privateKeyToPem(key) {
|
|
43
|
-
const der = new Uint8Array(await crypto.subtle.exportKey("pkcs8", key));
|
|
44
|
-
return privateKeyDerToPem(der);
|
|
45
|
-
}
|
|
46
|
-
export async function publicKeyToPem(key) {
|
|
47
|
-
const der = new Uint8Array(await crypto.subtle.exportKey("spki", key));
|
|
48
|
-
return publicKeyDerToPem(der);
|
|
49
|
-
}
|
|
50
80
|
export async function exportSpki(key) {
|
|
51
81
|
return new Uint8Array(await crypto.subtle.exportKey("spki", key));
|
|
52
82
|
}
|
|
53
|
-
export async function importPrivateKeyPem(pem) {
|
|
54
|
-
return crypto.subtle.importKey("pkcs8", arrayBufferFromBytes(pemToDerWithLabel(pem, "PRIVATE KEY")), EC_ALGORITHM, true, ["sign"]);
|
|
55
|
-
}
|
|
56
|
-
export async function keyPairFromPrivateKeyPem(pem) {
|
|
57
|
-
const privateKey = await importPrivateKeyPem(pem);
|
|
58
|
-
const jwk = await crypto.subtle.exportKey("jwk", privateKey);
|
|
59
|
-
delete jwk.d;
|
|
60
|
-
jwk.key_ops = ["verify"];
|
|
61
|
-
const publicKey = await crypto.subtle.importKey("jwk", jwk, EC_ALGORITHM, true, ["verify"]);
|
|
62
|
-
return { privateKey, publicKey };
|
|
63
|
-
}
|
|
64
83
|
export async function importPublicKeySpki(spki) {
|
|
65
|
-
|
|
84
|
+
const curve = curveFromSpki(spki);
|
|
85
|
+
return crypto.subtle.importKey("spki", arrayBufferFromBytes(spki), { name: "ECDSA", namedCurve: curve }, true, ["verify"]);
|
|
86
|
+
}
|
|
87
|
+
// SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING }
|
|
88
|
+
// AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY }
|
|
89
|
+
// For EC keys, parameters is the named-curve OID.
|
|
90
|
+
function curveFromSpki(spki) {
|
|
91
|
+
const root = readElement(spki);
|
|
92
|
+
if (root.tag !== TAG.SEQUENCE) {
|
|
93
|
+
throw new Error("Invalid SubjectPublicKeyInfo");
|
|
94
|
+
}
|
|
95
|
+
const algorithm = readSequenceChildren(root)[0];
|
|
96
|
+
if (!algorithm || algorithm.tag !== TAG.SEQUENCE) {
|
|
97
|
+
throw new Error("Invalid SubjectPublicKeyInfo algorithm");
|
|
98
|
+
}
|
|
99
|
+
const algorithmChildren = readSequenceChildren(algorithm);
|
|
100
|
+
const algorithmOidElement = algorithmChildren[0];
|
|
101
|
+
const parametersElement = algorithmChildren[1];
|
|
102
|
+
if (!algorithmOidElement || algorithmOidElement.tag !== TAG.OBJECT_IDENTIFIER) {
|
|
103
|
+
throw new Error("Invalid SubjectPublicKeyInfo algorithm OID");
|
|
104
|
+
}
|
|
105
|
+
if (decodeOid(algorithmOidElement.value) !== OID.ecPublicKey) {
|
|
106
|
+
throw new Error("SubjectPublicKeyInfo is not an EC public key");
|
|
107
|
+
}
|
|
108
|
+
if (!parametersElement || parametersElement.tag !== TAG.OBJECT_IDENTIFIER) {
|
|
109
|
+
throw new Error("EC SubjectPublicKeyInfo parameters must be a named-curve OID");
|
|
110
|
+
}
|
|
111
|
+
const curveOid = decodeOid(parametersElement.value);
|
|
112
|
+
const curve = CURVE_OID_TO_NAME[curveOid];
|
|
113
|
+
if (!curve) {
|
|
114
|
+
throw new Error(`Unsupported EC named curve OID: ${curveOid}`);
|
|
115
|
+
}
|
|
116
|
+
return curve;
|
|
66
117
|
}
|
|
67
118
|
export async function assertKeyPairMatches(privateKey, publicKey) {
|
|
68
119
|
const data = new TextEncoder().encode("edgca-key-pair-check");
|
|
@@ -72,13 +123,13 @@ export async function assertKeyPairMatches(privateKey, publicKey) {
|
|
|
72
123
|
throw new Error("Private key does not match CA certificate public key");
|
|
73
124
|
}
|
|
74
125
|
}
|
|
75
|
-
export function ecdsaRawToDer(raw) {
|
|
76
|
-
if (raw.length !==
|
|
77
|
-
throw new Error(
|
|
126
|
+
export function ecdsaRawToDer(raw, componentSize) {
|
|
127
|
+
if (raw.length !== componentSize * 2) {
|
|
128
|
+
throw new Error(`ECDSA raw signature must be ${componentSize * 2} bytes`);
|
|
78
129
|
}
|
|
79
|
-
return sequence(integer(raw.subarray(0,
|
|
130
|
+
return sequence(integer(raw.subarray(0, componentSize)), integer(raw.subarray(componentSize)));
|
|
80
131
|
}
|
|
81
|
-
export function ecdsaDerToRaw(signature) {
|
|
132
|
+
export function ecdsaDerToRaw(signature, componentSize) {
|
|
82
133
|
const root = readElement(signature);
|
|
83
134
|
if (root.tag !== TAG.SEQUENCE || root.end !== signature.length) {
|
|
84
135
|
throw new Error("Invalid DER ECDSA signature");
|
|
@@ -87,18 +138,21 @@ export function ecdsaDerToRaw(signature) {
|
|
|
87
138
|
if (!r || !s || r.tag !== TAG.INTEGER || s.tag !== TAG.INTEGER) {
|
|
88
139
|
throw new Error("Invalid DER ECDSA signature integers");
|
|
89
140
|
}
|
|
90
|
-
return concatBytes([
|
|
141
|
+
return concatBytes([
|
|
142
|
+
integerToFixedWidth(r.value, componentSize),
|
|
143
|
+
integerToFixedWidth(s.value, componentSize)
|
|
144
|
+
]);
|
|
91
145
|
}
|
|
92
|
-
function integerToFixedWidth(value) {
|
|
146
|
+
function integerToFixedWidth(value, size) {
|
|
93
147
|
let start = 0;
|
|
94
148
|
while (start < value.length - 1 && value[start] === 0) {
|
|
95
149
|
start += 1;
|
|
96
150
|
}
|
|
97
151
|
const trimmed = value.subarray(start);
|
|
98
|
-
if (trimmed.length >
|
|
99
|
-
throw new Error(
|
|
152
|
+
if (trimmed.length > size) {
|
|
153
|
+
throw new Error(`ECDSA integer is wider than ${size} bytes`);
|
|
100
154
|
}
|
|
101
|
-
const out = new Uint8Array(
|
|
102
|
-
out.set(trimmed,
|
|
155
|
+
const out = new Uint8Array(size);
|
|
156
|
+
out.set(trimmed, size - trimmed.length);
|
|
103
157
|
return out;
|
|
104
158
|
}
|
package/dist/csr.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Subject } from "./types.js";
|
|
2
|
+
export interface CertificateSigningRequestExtension {
|
|
3
|
+
oid: string;
|
|
4
|
+
critical: boolean;
|
|
5
|
+
valueDer: Uint8Array;
|
|
6
|
+
}
|
|
7
|
+
export interface CertificateSigningRequestAttribute {
|
|
8
|
+
oid: string;
|
|
9
|
+
valuesDer: ReadonlyArray<Uint8Array>;
|
|
10
|
+
}
|
|
11
|
+
export interface ParsedCertificateSigningRequest {
|
|
12
|
+
subject: Subject;
|
|
13
|
+
publicKey: CryptoKey;
|
|
14
|
+
subjectPublicKeyInfoDer: Uint8Array;
|
|
15
|
+
requestedDnsNames: readonly string[];
|
|
16
|
+
requestedIpAddresses: readonly string[];
|
|
17
|
+
requestedExtensions: readonly CertificateSigningRequestExtension[];
|
|
18
|
+
otherAttributes: readonly CertificateSigningRequestAttribute[];
|
|
19
|
+
signatureAlgorithmOid: string;
|
|
20
|
+
signatureDer: Uint8Array;
|
|
21
|
+
certificationRequestInfoDer: Uint8Array;
|
|
22
|
+
}
|
|
23
|
+
export declare function parseCertificateSigningRequest(input: string | Uint8Array): Promise<ParsedCertificateSigningRequest>;
|
|
24
|
+
export declare function verifyCertificateSigningRequestSignature(csr: ParsedCertificateSigningRequest): Promise<boolean>;
|
|
25
|
+
//# sourceMappingURL=csr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csr.d.ts","sourceRoot":"","sources":["../src/csr.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAwB,MAAM,YAAY,CAAC;AAkBhE,MAAM,WAAW,kCAAkC;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,UAAU,CAAC;CACtB;AAED,MAAM,WAAW,kCAAkC;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,+BAA+B;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,SAAS,CAAC;IACrB,uBAAuB,EAAE,UAAU,CAAC;IACpC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,mBAAmB,EAAE,SAAS,kCAAkC,EAAE,CAAC;IACnE,eAAe,EAAE,SAAS,kCAAkC,EAAE,CAAC;IAC/D,qBAAqB,EAAE,MAAM,CAAC;IAC9B,YAAY,EAAE,UAAU,CAAC;IACzB,2BAA2B,EAAE,UAAU,CAAC;CACzC;AAED,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,MAAM,GAAG,UAAU,GACzB,OAAO,CAAC,+BAA+B,CAAC,CAwE1C;AAED,wBAAsB,wCAAwC,CAC5D,GAAG,EAAE,+BAA+B,GACnC,OAAO,CAAC,OAAO,CAAC,CAElB"}
|
package/dist/csr.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { cloneBytes } from "./bytes.js";
|
|
2
|
+
import { importPublicKeySpki, verifyDer } from "./crypto.js";
|
|
3
|
+
import { decodeInteger, decodeOid, readChildren, readElement, readSequenceChildren, TAG } from "./der.js";
|
|
4
|
+
import { OID, SUBJECT_ATTRIBUTE_OIDS } from "./oids.js";
|
|
5
|
+
import { pemToDerWithLabel, splitPemBlocks } from "./pem.js";
|
|
6
|
+
const SHORT_NAME_BY_OID = Object.fromEntries(Object.entries(SUBJECT_ATTRIBUTE_OIDS).map(([shortName, oid]) => [oid, shortName]));
|
|
7
|
+
const VALUE_DECODERS = new Map([
|
|
8
|
+
[TAG.UTF8_STRING, (b) => new TextDecoder("utf-8", { fatal: true }).decode(b)],
|
|
9
|
+
[TAG.PRINTABLE_STRING, (b) => decodeAscii(b, "PrintableString")],
|
|
10
|
+
[TAG.IA5_STRING, (b) => decodeAscii(b, "IA5String")]
|
|
11
|
+
]);
|
|
12
|
+
const SUPPORTED_SIGNATURE_OIDS = new Set([
|
|
13
|
+
OID.ecdsaWithSha256,
|
|
14
|
+
OID.ecdsaWithSha384,
|
|
15
|
+
OID.ecdsaWithSha512
|
|
16
|
+
]);
|
|
17
|
+
export async function parseCertificateSigningRequest(input) {
|
|
18
|
+
const der = typeof input === "string" ? csrPemToDer(input) : input;
|
|
19
|
+
const root = readElement(der);
|
|
20
|
+
if (root.tag !== TAG.SEQUENCE || root.end !== der.length) {
|
|
21
|
+
throw new Error("Invalid CSR DER");
|
|
22
|
+
}
|
|
23
|
+
const [requestInfo, signatureAlgorithm, signatureValue] = readSequenceChildren(root);
|
|
24
|
+
if (!requestInfo || !signatureAlgorithm || !signatureValue) {
|
|
25
|
+
throw new Error("Invalid CSR structure");
|
|
26
|
+
}
|
|
27
|
+
if (requestInfo.tag !== TAG.SEQUENCE) {
|
|
28
|
+
throw new Error("Invalid CSR certificationRequestInfo");
|
|
29
|
+
}
|
|
30
|
+
if (signatureAlgorithm.tag !== TAG.SEQUENCE) {
|
|
31
|
+
throw new Error("Invalid CSR signatureAlgorithm");
|
|
32
|
+
}
|
|
33
|
+
if (signatureValue.tag !== TAG.BIT_STRING || signatureValue.value[0] !== 0) {
|
|
34
|
+
throw new Error("Invalid CSR signature value");
|
|
35
|
+
}
|
|
36
|
+
const signatureAlgorithmOid = readAlgorithmOid(signatureAlgorithm);
|
|
37
|
+
if (!SUPPORTED_SIGNATURE_OIDS.has(signatureAlgorithmOid)) {
|
|
38
|
+
throw new Error(`Unsupported CSR signatureAlgorithm: ${signatureAlgorithmOid}`);
|
|
39
|
+
}
|
|
40
|
+
const infoChildren = readSequenceChildren(requestInfo);
|
|
41
|
+
const versionElement = infoChildren[0];
|
|
42
|
+
const subjectElement = infoChildren[1];
|
|
43
|
+
const spkiElement = infoChildren[2];
|
|
44
|
+
const attributesElement = infoChildren[3];
|
|
45
|
+
if (!versionElement || !subjectElement || !spkiElement) {
|
|
46
|
+
throw new Error("Invalid CSR certificationRequestInfo structure");
|
|
47
|
+
}
|
|
48
|
+
if (versionElement.tag !== TAG.INTEGER || decodeInteger(versionElement.value) !== 0n) {
|
|
49
|
+
throw new Error("Unsupported CSR version (only v1 / INTEGER 0 is supported)");
|
|
50
|
+
}
|
|
51
|
+
if (spkiElement.tag !== TAG.SEQUENCE) {
|
|
52
|
+
throw new Error("Invalid CSR subjectPublicKeyInfo");
|
|
53
|
+
}
|
|
54
|
+
if (attributesElement && attributesElement.tag !== 0xa0) {
|
|
55
|
+
throw new Error("Invalid CSR attributes tag (must be IMPLICIT [0])");
|
|
56
|
+
}
|
|
57
|
+
const subject = decodeName(subjectElement);
|
|
58
|
+
const publicKey = await importPublicKeySpki(spkiElement.raw);
|
|
59
|
+
const allAttributes = attributesElement ? decodeAttributes(attributesElement.value) : [];
|
|
60
|
+
const extensionRequest = allAttributes.find((attribute) => attribute.oid === OID.extensionRequest);
|
|
61
|
+
const otherAttributes = allAttributes.filter((attribute) => attribute.oid !== OID.extensionRequest);
|
|
62
|
+
const requestedExtensions = extensionRequest
|
|
63
|
+
? decodeRequestedExtensions(extensionRequest.valuesDer)
|
|
64
|
+
: [];
|
|
65
|
+
const sanExtension = requestedExtensions.find((extension) => extension.oid === OID.subjectAltName);
|
|
66
|
+
const { dnsNames, ipAddresses } = sanExtension
|
|
67
|
+
? decodeSubjectAltName(sanExtension.valueDer)
|
|
68
|
+
: { dnsNames: [], ipAddresses: [] };
|
|
69
|
+
return {
|
|
70
|
+
subject,
|
|
71
|
+
publicKey,
|
|
72
|
+
subjectPublicKeyInfoDer: cloneBytes(spkiElement.raw),
|
|
73
|
+
requestedDnsNames: dnsNames,
|
|
74
|
+
requestedIpAddresses: ipAddresses,
|
|
75
|
+
requestedExtensions,
|
|
76
|
+
otherAttributes,
|
|
77
|
+
signatureAlgorithmOid,
|
|
78
|
+
signatureDer: cloneBytes(signatureValue.value.subarray(1)),
|
|
79
|
+
certificationRequestInfoDer: cloneBytes(requestInfo.raw)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export async function verifyCertificateSigningRequestSignature(csr) {
|
|
83
|
+
return verifyDer(csr.publicKey, csr.signatureDer, csr.certificationRequestInfoDer);
|
|
84
|
+
}
|
|
85
|
+
function csrPemToDer(pem) {
|
|
86
|
+
// RFC 7468 §7 uses "CERTIFICATE REQUEST"; some legacy tools emit "NEW CERTIFICATE REQUEST".
|
|
87
|
+
const blocks = splitPemBlocks(pem);
|
|
88
|
+
for (const block of blocks) {
|
|
89
|
+
const labelMatch = /-----BEGIN (.+?)-----/.exec(block);
|
|
90
|
+
const label = labelMatch?.[1];
|
|
91
|
+
if (label === "CERTIFICATE REQUEST" || label === "NEW CERTIFICATE REQUEST") {
|
|
92
|
+
return pemToDerWithLabel(block, label);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw new Error("Invalid CSR PEM: expected CERTIFICATE REQUEST or NEW CERTIFICATE REQUEST block");
|
|
96
|
+
}
|
|
97
|
+
function readAlgorithmOid(algorithm) {
|
|
98
|
+
const children = readSequenceChildren(algorithm);
|
|
99
|
+
const oidElement = children[0];
|
|
100
|
+
if (!oidElement || oidElement.tag !== TAG.OBJECT_IDENTIFIER) {
|
|
101
|
+
throw new Error("Invalid AlgorithmIdentifier OID");
|
|
102
|
+
}
|
|
103
|
+
return decodeOid(oidElement.value);
|
|
104
|
+
}
|
|
105
|
+
function decodeName(nameElement) {
|
|
106
|
+
if (nameElement.tag !== TAG.SEQUENCE) {
|
|
107
|
+
throw new Error("Invalid Name structure");
|
|
108
|
+
}
|
|
109
|
+
const subject = [];
|
|
110
|
+
for (const rdn of readSequenceChildren(nameElement)) {
|
|
111
|
+
if (rdn.tag !== TAG.SET) {
|
|
112
|
+
throw new Error("Invalid RDN (expected SET)");
|
|
113
|
+
}
|
|
114
|
+
const attributes = readChildren(rdn.value);
|
|
115
|
+
if (attributes.length !== 1) {
|
|
116
|
+
throw new Error("Multi-valued RDNs are not supported");
|
|
117
|
+
}
|
|
118
|
+
const attribute = attributes[0];
|
|
119
|
+
if (attribute.tag !== TAG.SEQUENCE) {
|
|
120
|
+
throw new Error("Invalid AttributeTypeAndValue");
|
|
121
|
+
}
|
|
122
|
+
const [oidElement, valueElement] = readSequenceChildren(attribute);
|
|
123
|
+
if (!oidElement || !valueElement || oidElement.tag !== TAG.OBJECT_IDENTIFIER) {
|
|
124
|
+
throw new Error("Invalid AttributeTypeAndValue contents");
|
|
125
|
+
}
|
|
126
|
+
const attributeOid = decodeOid(oidElement.value);
|
|
127
|
+
const decoder = VALUE_DECODERS.get(valueElement.tag);
|
|
128
|
+
if (!decoder) {
|
|
129
|
+
throw new Error(`Unsupported AttributeValue string type: tag 0x${valueElement.tag.toString(16)}`);
|
|
130
|
+
}
|
|
131
|
+
const value = decoder(valueElement.value);
|
|
132
|
+
const shortName = SHORT_NAME_BY_OID[attributeOid];
|
|
133
|
+
subject.push({
|
|
134
|
+
type: shortName ?? attributeOid,
|
|
135
|
+
value
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return subject;
|
|
139
|
+
}
|
|
140
|
+
function decodeAttributes(value) {
|
|
141
|
+
const attributes = [];
|
|
142
|
+
for (const attribute of readChildren(value)) {
|
|
143
|
+
if (attribute.tag !== TAG.SEQUENCE) {
|
|
144
|
+
throw new Error("Invalid CSR attribute");
|
|
145
|
+
}
|
|
146
|
+
const children = readSequenceChildren(attribute);
|
|
147
|
+
const oidElement = children[0];
|
|
148
|
+
const valuesElement = children[1];
|
|
149
|
+
if (!oidElement || oidElement.tag !== TAG.OBJECT_IDENTIFIER) {
|
|
150
|
+
throw new Error("Invalid CSR attribute OID");
|
|
151
|
+
}
|
|
152
|
+
if (!valuesElement || valuesElement.tag !== TAG.SET) {
|
|
153
|
+
throw new Error("Invalid CSR attribute values (expected SET)");
|
|
154
|
+
}
|
|
155
|
+
const valuesDer = readChildren(valuesElement.value).map((value) => cloneBytes(value.raw));
|
|
156
|
+
attributes.push({
|
|
157
|
+
oid: decodeOid(oidElement.value),
|
|
158
|
+
valuesDer
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return attributes;
|
|
162
|
+
}
|
|
163
|
+
function decodeRequestedExtensions(valuesDer) {
|
|
164
|
+
if (valuesDer.length !== 1) {
|
|
165
|
+
throw new Error("extensionRequest attribute must contain exactly one SEQUENCE OF Extension");
|
|
166
|
+
}
|
|
167
|
+
const outer = readElement(valuesDer[0]);
|
|
168
|
+
if (outer.tag !== TAG.SEQUENCE) {
|
|
169
|
+
throw new Error("extensionRequest value must be a SEQUENCE OF Extension");
|
|
170
|
+
}
|
|
171
|
+
const extensions = [];
|
|
172
|
+
for (const extension of readSequenceChildren(outer)) {
|
|
173
|
+
if (extension.tag !== TAG.SEQUENCE) {
|
|
174
|
+
throw new Error("Invalid Extension");
|
|
175
|
+
}
|
|
176
|
+
const children = readSequenceChildren(extension);
|
|
177
|
+
const oidElement = children[0];
|
|
178
|
+
if (!oidElement || oidElement.tag !== TAG.OBJECT_IDENTIFIER) {
|
|
179
|
+
throw new Error("Invalid Extension OID");
|
|
180
|
+
}
|
|
181
|
+
let cursor = 1;
|
|
182
|
+
let critical = false;
|
|
183
|
+
if (children[cursor]?.tag === TAG.BOOLEAN) {
|
|
184
|
+
critical = children[cursor].value[0] !== 0;
|
|
185
|
+
cursor += 1;
|
|
186
|
+
}
|
|
187
|
+
const valueElement = children[cursor];
|
|
188
|
+
if (!valueElement || valueElement.tag !== TAG.OCTET_STRING) {
|
|
189
|
+
throw new Error("Invalid Extension value");
|
|
190
|
+
}
|
|
191
|
+
extensions.push({
|
|
192
|
+
oid: decodeOid(oidElement.value),
|
|
193
|
+
critical,
|
|
194
|
+
valueDer: cloneBytes(valueElement.value)
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return extensions;
|
|
198
|
+
}
|
|
199
|
+
function decodeSubjectAltName(value) {
|
|
200
|
+
const root = readElement(value);
|
|
201
|
+
if (root.tag !== TAG.SEQUENCE) {
|
|
202
|
+
throw new Error("Invalid SubjectAltName extension");
|
|
203
|
+
}
|
|
204
|
+
const dnsNames = [];
|
|
205
|
+
const ipAddresses = [];
|
|
206
|
+
for (const generalName of readChildren(root.value)) {
|
|
207
|
+
if (generalName.tag === 0x82) {
|
|
208
|
+
dnsNames.push(decodeAscii(generalName.value, "SAN dNSName"));
|
|
209
|
+
}
|
|
210
|
+
else if (generalName.tag === 0x87) {
|
|
211
|
+
ipAddresses.push(formatIpAddress(generalName.value));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { dnsNames, ipAddresses };
|
|
215
|
+
}
|
|
216
|
+
function decodeAscii(bytes, label) {
|
|
217
|
+
for (const byte of bytes) {
|
|
218
|
+
if (byte > 0x7f) {
|
|
219
|
+
throw new Error(`${label} contains a non-ASCII byte`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return new TextDecoder("ascii").decode(bytes);
|
|
223
|
+
}
|
|
224
|
+
function formatIpAddress(bytes) {
|
|
225
|
+
if (bytes.length === 4) {
|
|
226
|
+
return Array.from(bytes).join(".");
|
|
227
|
+
}
|
|
228
|
+
if (bytes.length === 16) {
|
|
229
|
+
const groups = [];
|
|
230
|
+
for (let i = 0; i < 16; i += 2) {
|
|
231
|
+
groups.push(((bytes[i] << 8) | bytes[i + 1]).toString(16));
|
|
232
|
+
}
|
|
233
|
+
return compressIpv6(groups);
|
|
234
|
+
}
|
|
235
|
+
throw new Error(`Invalid SAN iPAddress length: ${bytes.length}`);
|
|
236
|
+
}
|
|
237
|
+
// RFC 5952: collapse the longest run of consecutive "0" groups (length ≥ 2) into "::".
|
|
238
|
+
function compressIpv6(groups) {
|
|
239
|
+
let bestStart = -1;
|
|
240
|
+
let bestLength = 0;
|
|
241
|
+
let currentStart = -1;
|
|
242
|
+
let currentLength = 0;
|
|
243
|
+
for (let i = 0; i < groups.length; i += 1) {
|
|
244
|
+
if (groups[i] === "0") {
|
|
245
|
+
if (currentStart === -1) {
|
|
246
|
+
currentStart = i;
|
|
247
|
+
currentLength = 0;
|
|
248
|
+
}
|
|
249
|
+
currentLength += 1;
|
|
250
|
+
if (currentLength > bestLength) {
|
|
251
|
+
bestStart = currentStart;
|
|
252
|
+
bestLength = currentLength;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
currentStart = -1;
|
|
257
|
+
currentLength = 0;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (bestLength < 2) {
|
|
261
|
+
return groups.join(":");
|
|
262
|
+
}
|
|
263
|
+
const head = groups.slice(0, bestStart).join(":");
|
|
264
|
+
const tail = groups.slice(bestStart + bestLength).join(":");
|
|
265
|
+
return `${head}::${tail}`;
|
|
266
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { createRootCA, importCertificateAuthority, issueClientCert, issueIntermediateCA } from "./ca.js";
|
|
1
|
+
export { createRootCA, importCertificateAuthority, issueClientCert, issueClientCertForPublicKey, issueIntermediateCA } from "./ca.js";
|
|
2
|
+
export { parseCertificateSigningRequest, verifyCertificateSigningRequestSignature, type ParsedCertificateSigningRequest, type CertificateSigningRequestExtension, type CertificateSigningRequestAttribute } from "./csr.js";
|
|
2
3
|
export { pemToDer, certificateToPem } from "./pem.js";
|
|
3
|
-
export { privateKeyToPem, publicKeyToPem } from "./crypto.js";
|
|
4
4
|
export { verifyClientCertificateIssuedBy, type VerifyClientCertificateIssuedByOptions, type VerifyClientCertificateValidity } from "./verify.js";
|
|
5
|
-
export type { CertificateAuthority, CreateRootCAOptions, ImportCertificateAuthorityOptions, IssueClientCertOptions, IssueIntermediateCAOptions, IssuedClientCertificate, SerialNumber, ShortSubjectAttributeType, Subject, SubjectAttribute, SubjectAttributeType } from "./types.js";
|
|
5
|
+
export type { CertificateAuthority, CreateRootCAOptions, ImportCertificateAuthorityOptions, IssueClientCertForPublicKeyOptions, IssueClientCertOptions, IssueIntermediateCAOptions, IssuedClientCertificate, IssuedClientCertificateForPublicKey, SerialNumber, ShortSubjectAttributeType, Subject, SubjectAttribute, SubjectAttributeType } from "./types.js";
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,0BAA0B,EAC1B,eAAe,EACf,mBAAmB,EACpB,MAAM,SAAS,CAAC;AACjB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,0BAA0B,EAC1B,eAAe,EACf,2BAA2B,EAC3B,mBAAmB,EACpB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,8BAA8B,EAC9B,wCAAwC,EACxC,KAAK,+BAA+B,EACpC,KAAK,kCAAkC,EACvC,KAAK,kCAAkC,EACxC,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EACL,+BAA+B,EAC/B,KAAK,sCAAsC,EAC3C,KAAK,+BAA+B,EACrC,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,iCAAiC,EACjC,kCAAkC,EAClC,sBAAsB,EACtB,0BAA0B,EAC1B,uBAAuB,EACvB,mCAAmC,EACnC,YAAY,EACZ,yBAAyB,EACzB,OAAO,EACP,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createRootCA, importCertificateAuthority, issueClientCert, issueIntermediateCA } from "./ca.js";
|
|
1
|
+
export { createRootCA, importCertificateAuthority, issueClientCert, issueClientCertForPublicKey, issueIntermediateCA } from "./ca.js";
|
|
2
|
+
export { parseCertificateSigningRequest, verifyCertificateSigningRequestSignature } from "./csr.js";
|
|
2
3
|
export { pemToDer, certificateToPem } from "./pem.js";
|
|
3
|
-
export { privateKeyToPem, publicKeyToPem } from "./crypto.js";
|
|
4
4
|
export { verifyClientCertificateIssuedBy } from "./verify.js";
|
package/dist/oids.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ShortSubjectAttributeType } from "./types.js";
|
|
2
2
|
export declare const OID: {
|
|
3
|
+
readonly ecPublicKey: "1.2.840.10045.2.1";
|
|
4
|
+
readonly secp256r1: "1.2.840.10045.3.1.7";
|
|
5
|
+
readonly secp384r1: "1.3.132.0.34";
|
|
6
|
+
readonly secp521r1: "1.3.132.0.35";
|
|
3
7
|
readonly ecdsaWithSha256: "1.2.840.10045.4.3.2";
|
|
8
|
+
readonly ecdsaWithSha384: "1.2.840.10045.4.3.3";
|
|
9
|
+
readonly ecdsaWithSha512: "1.2.840.10045.4.3.4";
|
|
4
10
|
readonly basicConstraints: "2.5.29.19";
|
|
5
11
|
readonly keyUsage: "2.5.29.15";
|
|
6
12
|
readonly extendedKeyUsage: "2.5.29.37";
|
|
@@ -8,6 +14,7 @@ export declare const OID: {
|
|
|
8
14
|
readonly subjectKeyIdentifier: "2.5.29.14";
|
|
9
15
|
readonly authorityKeyIdentifier: "2.5.29.35";
|
|
10
16
|
readonly clientAuth: "1.3.6.1.5.5.7.3.2";
|
|
17
|
+
readonly extensionRequest: "1.2.840.113549.1.9.14";
|
|
11
18
|
};
|
|
12
19
|
export declare const SUBJECT_ATTRIBUTE_OIDS: Record<ShortSubjectAttributeType, string>;
|
|
13
20
|
export declare const SUBJECT_VALUE_LENGTH_LIMITS: Record<ShortSubjectAttributeType, number>;
|
package/dist/oids.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oids.d.ts","sourceRoot":"","sources":["../src/oids.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D,eAAO,MAAM,GAAG
|
|
1
|
+
{"version":3,"file":"oids.d.ts","sourceRoot":"","sources":["../src/oids.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;CAgBN,CAAC;AAEX,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,yBAAyB,EAAE,MAAM,CAgB5E,CAAC;AAGF,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,yBAAyB,EAAE,MAAM,CAgBjF,CAAC;AAIF,eAAO,MAAM,wBAAwB,MAAM,CAAC"}
|
package/dist/oids.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
export const OID = {
|
|
2
|
+
ecPublicKey: "1.2.840.10045.2.1",
|
|
3
|
+
secp256r1: "1.2.840.10045.3.1.7",
|
|
4
|
+
secp384r1: "1.3.132.0.34",
|
|
5
|
+
secp521r1: "1.3.132.0.35",
|
|
2
6
|
ecdsaWithSha256: "1.2.840.10045.4.3.2",
|
|
7
|
+
ecdsaWithSha384: "1.2.840.10045.4.3.3",
|
|
8
|
+
ecdsaWithSha512: "1.2.840.10045.4.3.4",
|
|
3
9
|
basicConstraints: "2.5.29.19",
|
|
4
10
|
keyUsage: "2.5.29.15",
|
|
5
11
|
extendedKeyUsage: "2.5.29.37",
|
|
6
12
|
subjectAltName: "2.5.29.17",
|
|
7
13
|
subjectKeyIdentifier: "2.5.29.14",
|
|
8
14
|
authorityKeyIdentifier: "2.5.29.35",
|
|
9
|
-
clientAuth: "1.3.6.1.5.5.7.3.2"
|
|
15
|
+
clientAuth: "1.3.6.1.5.5.7.3.2",
|
|
16
|
+
extensionRequest: "1.2.840.113549.1.9.14"
|
|
10
17
|
};
|
|
11
18
|
export const SUBJECT_ATTRIBUTE_OIDS = {
|
|
12
19
|
CN: "2.5.4.3",
|
package/dist/pem.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export declare function certificateToPem(der: Uint8Array): string;
|
|
2
|
-
export declare function privateKeyDerToPem(der: Uint8Array): string;
|
|
3
|
-
export declare function publicKeyDerToPem(der: Uint8Array): string;
|
|
4
2
|
export declare function pemToDer(pem: string): Uint8Array;
|
|
5
3
|
export declare function pemToDerWithLabel(pem: string, label: string): Uint8Array;
|
|
6
4
|
export declare function splitPemBlocks(pem: string): string[];
|
package/dist/pem.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pem.d.ts","sourceRoot":"","sources":["../src/pem.ts"],"names":[],"mappings":"AAIA,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAExD;AAED,wBAAgB,
|
|
1
|
+
{"version":3,"file":"pem.d.ts","sourceRoot":"","sources":["../src/pem.ts"],"names":[],"mappings":"AAIA,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAExD;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAWhD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAaxE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAUpD"}
|
package/dist/pem.js
CHANGED
|
@@ -3,12 +3,6 @@ const PEM_LINE_LENGTH = 64;
|
|
|
3
3
|
export function certificateToPem(der) {
|
|
4
4
|
return encodePem("CERTIFICATE", der);
|
|
5
5
|
}
|
|
6
|
-
export function privateKeyDerToPem(der) {
|
|
7
|
-
return encodePem("PRIVATE KEY", der);
|
|
8
|
-
}
|
|
9
|
-
export function publicKeyDerToPem(der) {
|
|
10
|
-
return encodePem("PUBLIC KEY", der);
|
|
11
|
-
}
|
|
12
6
|
export function pemToDer(pem) {
|
|
13
7
|
const match = /-----BEGIN (.+?)-----([\s\S]*?)-----END \1-----/.exec(pem);
|
|
14
8
|
if (!match || !match[2]) {
|
package/dist/types.d.ts
CHANGED
|
@@ -9,8 +9,6 @@ export type Subject = SubjectAttribute[];
|
|
|
9
9
|
export type SerialNumber = bigint | number | string | Uint8Array;
|
|
10
10
|
export interface CertificateAuthority {
|
|
11
11
|
certPem: string;
|
|
12
|
-
privateKeyPem: string;
|
|
13
|
-
publicKeyPem: string;
|
|
14
12
|
certDer: Uint8Array;
|
|
15
13
|
privateKey: CryptoKey;
|
|
16
14
|
publicKey: CryptoKey;
|
|
@@ -18,8 +16,6 @@ export interface CertificateAuthority {
|
|
|
18
16
|
}
|
|
19
17
|
export interface IssuedClientCertificate {
|
|
20
18
|
certPem: string;
|
|
21
|
-
privateKeyPem: string;
|
|
22
|
-
publicKeyPem: string;
|
|
23
19
|
certDer: Uint8Array;
|
|
24
20
|
privateKey: CryptoKey;
|
|
25
21
|
publicKey: CryptoKey;
|
|
@@ -31,7 +27,7 @@ export interface CreateRootCAOptions {
|
|
|
31
27
|
notBefore?: Date;
|
|
32
28
|
serialNumber?: SerialNumber;
|
|
33
29
|
pathLenConstraint?: number;
|
|
34
|
-
|
|
30
|
+
keyPair?: CryptoKeyPair;
|
|
35
31
|
}
|
|
36
32
|
export interface IssueIntermediateCAOptions {
|
|
37
33
|
ca: CertificateAuthority;
|
|
@@ -40,7 +36,7 @@ export interface IssueIntermediateCAOptions {
|
|
|
40
36
|
notBefore?: Date;
|
|
41
37
|
serialNumber?: SerialNumber;
|
|
42
38
|
pathLenConstraint?: number;
|
|
43
|
-
|
|
39
|
+
keyPair?: CryptoKeyPair;
|
|
44
40
|
}
|
|
45
41
|
export interface IssueClientCertOptions {
|
|
46
42
|
ca: CertificateAuthority;
|
|
@@ -51,9 +47,24 @@ export interface IssueClientCertOptions {
|
|
|
51
47
|
dnsNames?: string[];
|
|
52
48
|
ipAddresses?: string[];
|
|
53
49
|
}
|
|
50
|
+
export interface IssueClientCertForPublicKeyOptions {
|
|
51
|
+
ca: CertificateAuthority;
|
|
52
|
+
publicKey: CryptoKey;
|
|
53
|
+
subject: Subject;
|
|
54
|
+
days: number;
|
|
55
|
+
notBefore?: Date;
|
|
56
|
+
serialNumber?: SerialNumber;
|
|
57
|
+
dnsNames?: string[];
|
|
58
|
+
ipAddresses?: string[];
|
|
59
|
+
}
|
|
60
|
+
export interface IssuedClientCertificateForPublicKey {
|
|
61
|
+
certPem: string;
|
|
62
|
+
certDer: Uint8Array;
|
|
63
|
+
certChainPem: string;
|
|
64
|
+
}
|
|
54
65
|
export interface ImportCertificateAuthorityOptions {
|
|
55
66
|
certPem: string;
|
|
56
|
-
|
|
67
|
+
privateKey: CryptoKey;
|
|
57
68
|
issuerChainPem?: string;
|
|
58
69
|
}
|
|
59
70
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,yBAAyB,GACjC,IAAI,GACJ,GAAG,GACH,IAAI,GACJ,GAAG,GACH,IAAI,GACJ,GAAG,GACH,GAAG,GACH,IAAI,GACJ,cAAc,GACd,QAAQ,GACR,YAAY,GACZ,OAAO,GACP,WAAW,GACX,SAAS,GACT,KAAK,CAAC;AAEV,MAAM,MAAM,SAAS,GAAG,GAAG,MAAM,IAAI,MAAM,GAAG,MAAM,EAAE,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,yBAAyB,GAAG,SAAS,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;AAEzC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,yBAAyB,GACjC,IAAI,GACJ,GAAG,GACH,IAAI,GACJ,GAAG,GACH,IAAI,GACJ,GAAG,GACH,GAAG,GACH,IAAI,GACJ,cAAc,GACd,QAAQ,GACR,YAAY,GACZ,OAAO,GACP,WAAW,GACX,SAAS,GACT,KAAK,CAAC;AAEV,MAAM,MAAM,SAAS,GAAG,GAAG,MAAM,IAAI,MAAM,GAAG,MAAM,EAAE,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,yBAAyB,GAAG,SAAS,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;AAEzC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,UAAU,CAAC;IACpB,UAAU,EAAE,SAAS,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,UAAU,CAAC;IACpB,UAAU,EAAE,SAAS,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,oBAAoB,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,oBAAoB,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,kCAAkC;IACjD,EAAE,EAAE,oBAAoB,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,mCAAmC;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,UAAU,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iCAAiC;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,SAAS,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
package/dist/x509.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SupportedCurve } from "./crypto.js";
|
|
1
2
|
import type { SerialNumber } from "./types.js";
|
|
2
3
|
export interface CertificateBuildInput {
|
|
3
4
|
serialNumber?: SerialNumber | undefined;
|
|
@@ -7,6 +8,7 @@ export interface CertificateBuildInput {
|
|
|
7
8
|
subjectNameDer: Uint8Array;
|
|
8
9
|
subjectPublicKeyInfoDer: Uint8Array;
|
|
9
10
|
extensions: Uint8Array[];
|
|
11
|
+
issuerCurve: SupportedCurve;
|
|
10
12
|
}
|
|
11
13
|
export interface TbsCertificateResult {
|
|
12
14
|
tbsCertificateDer: Uint8Array;
|
|
@@ -15,8 +17,8 @@ export interface TbsCertificateResult {
|
|
|
15
17
|
notAfter: Date;
|
|
16
18
|
}
|
|
17
19
|
export declare function buildTbsCertificate(input: CertificateBuildInput): TbsCertificateResult;
|
|
18
|
-
export declare function buildCertificate(tbsCertificateDer: Uint8Array, signatureDer: Uint8Array): Uint8Array;
|
|
19
|
-
export declare function
|
|
20
|
+
export declare function buildCertificate(tbsCertificateDer: Uint8Array, signatureDer: Uint8Array, issuerCurve: SupportedCurve): Uint8Array;
|
|
21
|
+
export declare function ecdsaSignatureAlgorithmIdentifier(curve: SupportedCurve): Uint8Array;
|
|
20
22
|
export declare function basicConstraintsCaExtension(pathLenConstraint: number): Uint8Array;
|
|
21
23
|
export declare function basicConstraintsLeafExtension(): Uint8Array;
|
|
22
24
|
export declare function keyUsageExtension(usages: readonly KeyUsageBit[]): Uint8Array;
|
package/dist/x509.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x509.d.ts","sourceRoot":"","sources":["../src/x509.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"x509.d.ts","sourceRoot":"","sources":["../src/x509.ts"],"names":[],"mappings":"AACA,OAAO,EAAiC,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAkBjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACxC,SAAS,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,UAAU,CAAC;IAC1B,cAAc,EAAE,UAAU,CAAC;IAC3B,uBAAuB,EAAE,UAAU,CAAC;IACpC,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,WAAW,EAAE,cAAc,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,iBAAiB,EAAE,UAAU,CAAC;IAC9B,eAAe,EAAE,UAAU,CAAC;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,IAAI,CAAC;CAChB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,qBAAqB,GAAG,oBAAoB,CAoBtF;AAED,wBAAgB,gBAAgB,CAC9B,iBAAiB,EAAE,UAAU,EAC7B,YAAY,EAAE,UAAU,EACxB,WAAW,EAAE,cAAc,GAC1B,UAAU,CAEZ;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,cAAc,GAAG,UAAU,CAEnF;AAED,wBAAgB,2BAA2B,CAAC,iBAAiB,EAAE,MAAM,GAAG,UAAU,CAMjF;AAED,wBAAgB,6BAA6B,IAAI,UAAU,CAE1D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,UAAU,CAgB5E;AAED,wBAAgB,mCAAmC,IAAI,UAAU,CAEhE;AAED,wBAAgB,6BAA6B,CAAC,aAAa,EAAE,UAAU,GAAG,UAAU,CAEnF;AAED,wBAAgB,+BAA+B,CAAC,aAAa,EAAE,UAAU,GAAG,UAAU,CAErF;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,SAAS,CAmC7H;AAwID,QAAA,MAAM,cAAc;;;;CAIV,CAAC;AAEX,KAAK,WAAW,GAAG,MAAM,OAAO,cAAc,CAAC"}
|
package/dist/x509.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { asciiBytes, concatBytes } from "./bytes.js";
|
|
2
|
+
import { signatureAlgorithmOidForCurve } from "./crypto.js";
|
|
2
3
|
import { bitString, boolean, contextPrimitive, der, explicit, generalizedTime, integer, octetString, oid, readElement, sequence, TAG, utcTime } from "./der.js";
|
|
3
4
|
import { encodeIpAddress } from "./ip.js";
|
|
4
5
|
import { OID } from "./oids.js";
|
|
5
6
|
export function buildTbsCertificate(input) {
|
|
6
7
|
const { notBefore, notAfter } = resolveValidity(input.notBefore, input.days);
|
|
7
8
|
const serialNumberDer = encodeSerialNumber(input.serialNumber);
|
|
8
|
-
const tbsCertificateDer = sequence(explicit(0, integer(2)), serialNumberDer,
|
|
9
|
+
const tbsCertificateDer = sequence(explicit(0, integer(2)), serialNumberDer, ecdsaSignatureAlgorithmIdentifier(input.issuerCurve), input.issuerNameDer, sequence(encodeTime(notBefore), encodeTime(notAfter)), input.subjectNameDer, input.subjectPublicKeyInfoDer, explicit(3, sequence(...input.extensions)));
|
|
9
10
|
return {
|
|
10
11
|
tbsCertificateDer,
|
|
11
12
|
serialNumberDer,
|
|
@@ -13,11 +14,11 @@ export function buildTbsCertificate(input) {
|
|
|
13
14
|
notAfter
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
|
-
export function buildCertificate(tbsCertificateDer, signatureDer) {
|
|
17
|
-
return sequence(tbsCertificateDer,
|
|
17
|
+
export function buildCertificate(tbsCertificateDer, signatureDer, issuerCurve) {
|
|
18
|
+
return sequence(tbsCertificateDer, ecdsaSignatureAlgorithmIdentifier(issuerCurve), bitString(signatureDer));
|
|
18
19
|
}
|
|
19
|
-
export function
|
|
20
|
-
return sequence(oid(
|
|
20
|
+
export function ecdsaSignatureAlgorithmIdentifier(curve) {
|
|
21
|
+
return sequence(oid(signatureAlgorithmOidForCurve(curve)));
|
|
21
22
|
}
|
|
22
23
|
export function basicConstraintsCaExtension(pathLenConstraint) {
|
|
23
24
|
return extension(OID.basicConstraints, true, sequence(boolean(true), integer(pathLenConstraint)));
|