@noz-ele/edgca 0.2.0 → 0.3.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/dist/bytes.d.ts CHANGED
@@ -6,4 +6,5 @@ export declare function bytesToBinary(bytes: Uint8Array): string;
6
6
  export declare function binaryToBytes(binary: string): Uint8Array;
7
7
  export declare function cloneBytes(bytes: Uint8Array): Uint8Array;
8
8
  export declare function arrayBufferFromBytes(bytes: Uint8Array): ArrayBuffer;
9
+ export declare function randomBytes(length: number): Uint8Array;
9
10
  //# sourceMappingURL=bytes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,CAWpE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAWhE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEnD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAYpD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAUvD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAQxD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAExD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAInE"}
1
+ {"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,CAWpE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAWhE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEnD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAYpD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAUvD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAQxD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAExD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAInE;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAItD"}
package/dist/bytes.js CHANGED
@@ -56,3 +56,8 @@ export function arrayBufferFromBytes(bytes) {
56
56
  copy.set(bytes);
57
57
  return copy.buffer;
58
58
  }
59
+ export function randomBytes(length) {
60
+ const bytes = new Uint8Array(length);
61
+ crypto.getRandomValues(bytes);
62
+ return bytes;
63
+ }
package/dist/der.d.ts CHANGED
@@ -31,6 +31,7 @@ export declare function boolean(value: boolean): Uint8Array;
31
31
  export declare function integer(value: bigint | number | Uint8Array): Uint8Array;
32
32
  export declare function bitString(bytes: Uint8Array, unusedBits?: number): Uint8Array;
33
33
  export declare function octetString(bytes: Uint8Array): Uint8Array;
34
+ export declare function nullValue(): Uint8Array;
34
35
  export declare function utf8String(value: string): Uint8Array;
35
36
  export declare function printableString(value: string): Uint8Array;
36
37
  export declare function ia5String(value: string): Uint8Array;
package/dist/der.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"der.d.ts","sourceRoot":"","sources":["../src/der.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG;;;;;;;;;;;;;;CAcN,CAAC;AAEX,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,GAAG,EAAE,UAAU,CAAC;CACjB;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAE9D;AAED,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,CAE9D;AAED,wBAAgB,GAAG,CAAC,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,CAEzD;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAEzE;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAmB7C;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAElD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CA4BvE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,SAAI,GAAG,UAAU,CAMvE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAEzD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEpD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAMzD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEnD;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAEjF;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAO9C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAMtD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,SAAI,GAAG,UAAU,CAuBrE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU,EAAE,CAMtE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,CAW5D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CA4CnD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAevD"}
1
+ {"version":3,"file":"der.d.ts","sourceRoot":"","sources":["../src/der.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,GAAG;;;;;;;;;;;;;;CAcN,CAAC;AAEX,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,GAAG,EAAE,UAAU,CAAC;CACjB;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAE9D;AAED,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,CAE9D;AAED,wBAAgB,GAAG,CAAC,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,CAEzD;AAED,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAEzE;AAED,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAmB7C;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAElD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CA4BvE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,SAAI,GAAG,UAAU,CAMvE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAEzD;AAED,wBAAgB,SAAS,IAAI,UAAU,CAEtC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEpD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAMzD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEnD;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAEjF;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAO9C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAMtD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,SAAI,GAAG,UAAU,CAuBrE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,UAAU,GAAG,UAAU,EAAE,CAMtE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,CAW5D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CA4CnD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAevD"}
package/dist/der.js CHANGED
@@ -79,6 +79,9 @@ export function bitString(bytes, unusedBits = 0) {
79
79
  export function octetString(bytes) {
80
80
  return der(TAG.OCTET_STRING, bytes);
81
81
  }
82
+ export function nullValue() {
83
+ return new Uint8Array([TAG.NULL, 0x00]);
84
+ }
82
85
  export function utf8String(value) {
83
86
  return der(TAG.UTF8_STRING, utf8Bytes(value));
84
87
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { createRootCA, importCertificateAuthority, issueClientCert, issueClientCertForPublicKey, issueIntermediateCA } from "./ca.js";
2
2
  export { parseCertificateSigningRequest, verifyCertificateSigningRequestSignature, type ParsedCertificateSigningRequest, type CertificateSigningRequestExtension, type CertificateSigningRequestAttribute } from "./csr.js";
3
3
  export { pemToDer, certificateToPem } from "./pem.js";
4
+ export { exportPkcs12, type ExportPkcs12Input } from "./pkcs12.js";
4
5
  export { verifyClientCertificateIssuedBy, type VerifyClientCertificateIssuedByOptions, type VerifyClientCertificateValidity } from "./verify.js";
5
6
  export type { CertificateAuthority, CreateRootCAOptions, ImportCertificateAuthorityOptions, IssueClientCertForPublicKeyOptions, IssueClientCertOptions, IssueIntermediateCAOptions, IssuedClientCertificate, IssuedClientCertificateForPublicKey, SerialNumber, ShortSubjectAttributeType, Subject, SubjectAttribute, SubjectAttributeType } from "./types.js";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -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,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"}
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,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnE,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,5 @@
1
1
  export { createRootCA, importCertificateAuthority, issueClientCert, issueClientCertForPublicKey, issueIntermediateCA } from "./ca.js";
2
2
  export { parseCertificateSigningRequest, verifyCertificateSigningRequestSignature } from "./csr.js";
3
3
  export { pemToDer, certificateToPem } from "./pem.js";
4
+ export { exportPkcs12 } from "./pkcs12.js";
4
5
  export { verifyClientCertificateIssuedBy } from "./verify.js";
package/dist/oids.d.ts CHANGED
@@ -15,6 +15,18 @@ export declare const OID: {
15
15
  readonly authorityKeyIdentifier: "2.5.29.35";
16
16
  readonly clientAuth: "1.3.6.1.5.5.7.3.2";
17
17
  readonly extensionRequest: "1.2.840.113549.1.9.14";
18
+ readonly data: "1.2.840.113549.1.7.1";
19
+ readonly encryptedData: "1.2.840.113549.1.7.6";
20
+ readonly friendlyName: "1.2.840.113549.1.9.20";
21
+ readonly localKeyId: "1.2.840.113549.1.9.21";
22
+ readonly x509Certificate: "1.2.840.113549.1.9.22.1";
23
+ readonly certBag: "1.2.840.113549.1.12.10.1.3";
24
+ readonly pkcs8ShroudedKeyBag: "1.2.840.113549.1.12.10.1.2";
25
+ readonly pbes2: "1.2.840.113549.1.5.13";
26
+ readonly pbkdf2: "1.2.840.113549.1.5.12";
27
+ readonly hmacWithSha256: "1.2.840.113549.2.9";
28
+ readonly aes256Cbc: "2.16.840.1.101.3.4.1.42";
29
+ readonly sha256: "2.16.840.1.101.3.4.2.1";
18
30
  };
19
31
  export declare const SUBJECT_ATTRIBUTE_OIDS: Record<ShortSubjectAttributeType, string>;
20
32
  export declare const SUBJECT_VALUE_LENGTH_LIMITS: Record<ShortSubjectAttributeType, number>;
@@ -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;;;;;;;;;;;;;;;;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"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BN,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
@@ -13,7 +13,19 @@ export const OID = {
13
13
  subjectKeyIdentifier: "2.5.29.14",
14
14
  authorityKeyIdentifier: "2.5.29.35",
15
15
  clientAuth: "1.3.6.1.5.5.7.3.2",
16
- extensionRequest: "1.2.840.113549.1.9.14"
16
+ extensionRequest: "1.2.840.113549.1.9.14",
17
+ data: "1.2.840.113549.1.7.1",
18
+ encryptedData: "1.2.840.113549.1.7.6",
19
+ friendlyName: "1.2.840.113549.1.9.20",
20
+ localKeyId: "1.2.840.113549.1.9.21",
21
+ x509Certificate: "1.2.840.113549.1.9.22.1",
22
+ certBag: "1.2.840.113549.1.12.10.1.3",
23
+ pkcs8ShroudedKeyBag: "1.2.840.113549.1.12.10.1.2",
24
+ pbes2: "1.2.840.113549.1.5.13",
25
+ pbkdf2: "1.2.840.113549.1.5.12",
26
+ hmacWithSha256: "1.2.840.113549.2.9",
27
+ aes256Cbc: "2.16.840.1.101.3.4.1.42",
28
+ sha256: "2.16.840.1.101.3.4.2.1"
17
29
  };
18
30
  export const SUBJECT_ATTRIBUTE_OIDS = {
19
31
  CN: "2.5.4.3",
@@ -0,0 +1,11 @@
1
+ export interface ExportPkcs12Input {
2
+ certDer: Uint8Array;
3
+ chainDer?: Uint8Array[];
4
+ privateKey: CryptoKey;
5
+ password: Uint8Array;
6
+ friendlyName?: Uint8Array;
7
+ iterations?: number;
8
+ macIterations?: number;
9
+ }
10
+ export declare function exportPkcs12(input: ExportPkcs12Input): Promise<Uint8Array>;
11
+ //# sourceMappingURL=pkcs12.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkcs12.d.ts","sourceRoot":"","sources":["../src/pkcs12.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;IACxB,UAAU,EAAE,SAAS,CAAC;IACtB,QAAQ,EAAE,UAAU,CAAC;IACrB,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAUD,wBAAsB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CA8FhF"}
package/dist/pkcs12.js ADDED
@@ -0,0 +1,248 @@
1
+ import { arrayBufferFromBytes, concatBytes, randomBytes } from "./bytes.js";
2
+ import { digestSha256, keyIdentifierFromSpki } from "./crypto.js";
3
+ import { contextPrimitive, der, explicit, integer, nullValue, octetString, oid, sequence, set } from "./der.js";
4
+ import { OID } from "./oids.js";
5
+ import { parseCertificateDer } from "./parser.js";
6
+ const DEFAULT_PBKDF2_ITERATIONS = 600_000;
7
+ const DEFAULT_MAC_ITERATIONS = 100_000;
8
+ const SALT_LENGTH = 16;
9
+ const IV_LENGTH = 16;
10
+ const MAC_KEY_LENGTH = 32;
11
+ const SHA256_BLOCK_SIZE = 64;
12
+ const PKCS12_KDF_ID_MAC = 3;
13
+ export async function exportPkcs12(input) {
14
+ const iterations = input.iterations ?? DEFAULT_PBKDF2_ITERATIONS;
15
+ const macIterations = input.macIterations ?? DEFAULT_MAC_ITERATIONS;
16
+ validateInput(input, iterations, macIterations);
17
+ const parsedLeaf = await parseCertificateDer(input.certDer);
18
+ const localKeyId = await keyIdentifierFromSpki(parsedLeaf.subjectPublicKeyInfoDer);
19
+ const friendlyNameAttr = input.friendlyName !== undefined && input.friendlyName.length > 0
20
+ ? buildFriendlyNameAttr(input.friendlyName)
21
+ : undefined;
22
+ const localKeyIdAttr = buildLocalKeyIdAttr(localKeyId);
23
+ const leafAttrs = friendlyNameAttr ? [localKeyIdAttr, friendlyNameAttr] : [localKeyIdAttr];
24
+ const certBags = [buildCertSafeBag(input.certDer, leafAttrs)];
25
+ if (input.chainDer) {
26
+ for (const c of input.chainDer) {
27
+ certBags.push(buildCertSafeBag(c, []));
28
+ }
29
+ }
30
+ const certSafeContents = sequence(...certBags);
31
+ // Import the password into a non-extractable PBKDF2 baseKey once and reuse
32
+ // it across both cert-bag and key-bag encryption. This halves the number of
33
+ // password-byte ArrayBuffer copies handed to WebCrypto (which we cannot
34
+ // wipe) from two to one.
35
+ const pbkdf2BaseKey = await crypto.subtle.importKey("raw", arrayBufferFromBytes(input.password), "PBKDF2", false, ["deriveKey"]);
36
+ const certEncrypted = await pbes2EncryptAesCbc(certSafeContents, pbkdf2BaseKey, iterations);
37
+ const certContentInfo = sequence(oid(OID.encryptedData), explicit(0, sequence(integer(0n), sequence(oid(OID.data), certEncrypted.algorithm, contextPrimitive(0, certEncrypted.ciphertext)))));
38
+ const pkcs8 = new Uint8Array(await crypto.subtle.exportKey("pkcs8", input.privateKey));
39
+ const keyEncrypted = await pbes2EncryptAesCbc(pkcs8, pbkdf2BaseKey, iterations);
40
+ wipe(pkcs8);
41
+ const epki = sequence(keyEncrypted.algorithm, octetString(keyEncrypted.ciphertext));
42
+ const keyBag = buildKeySafeBag(epki, leafAttrs);
43
+ const keySafeContents = sequence(keyBag);
44
+ const keyContentInfo = sequence(oid(OID.data), explicit(0, octetString(keySafeContents)));
45
+ const authenticatedSafe = sequence(certContentInfo, keyContentInfo);
46
+ const macSalt = randomBytes(SALT_LENGTH);
47
+ const passwordUtf16 = utf8ToUtf16BeWithTerminator(input.password);
48
+ const macKey = await pkcs12V1KdfMacKey(passwordUtf16, macSalt, macIterations);
49
+ wipe(passwordUtf16);
50
+ const macKeyHandle = await crypto.subtle.importKey("raw", arrayBufferFromBytes(macKey), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
51
+ wipe(macKey);
52
+ const mac = new Uint8Array(await crypto.subtle.sign("HMAC", macKeyHandle, arrayBufferFromBytes(authenticatedSafe)));
53
+ const macData = sequence(sequence(sequence(oid(OID.sha256), nullValue()), octetString(mac)), octetString(macSalt), integer(BigInt(macIterations)));
54
+ const pfxAuthSafeContentInfo = sequence(oid(OID.data), explicit(0, octetString(authenticatedSafe)));
55
+ return sequence(integer(3n), pfxAuthSafeContentInfo, macData);
56
+ }
57
+ function validateInput(input, iterations, macIterations) {
58
+ if (!(input.password instanceof Uint8Array) || input.password.length === 0) {
59
+ throw new Error("password must be a non-empty Uint8Array of UTF-8 bytes");
60
+ }
61
+ if (!input.privateKey.extractable) {
62
+ throw new Error("privateKey must be extractable");
63
+ }
64
+ if (input.privateKey.algorithm.name !== "ECDSA") {
65
+ throw new Error("privateKey must be an ECDSA key");
66
+ }
67
+ if (!Number.isInteger(iterations) || iterations < 1) {
68
+ throw new Error("iterations must be a positive integer");
69
+ }
70
+ if (!Number.isInteger(macIterations) || macIterations < 1) {
71
+ throw new Error("macIterations must be a positive integer");
72
+ }
73
+ if (input.friendlyName !== undefined && !(input.friendlyName instanceof Uint8Array)) {
74
+ throw new Error("friendlyName must be a Uint8Array of UTF-8 bytes");
75
+ }
76
+ }
77
+ function buildCertSafeBag(certDer, attrs) {
78
+ const certBagInner = sequence(oid(OID.x509Certificate), explicit(0, octetString(certDer)));
79
+ return safeBag(OID.certBag, certBagInner, attrs);
80
+ }
81
+ function buildKeySafeBag(encryptedPrivateKeyInfo, attrs) {
82
+ return safeBag(OID.pkcs8ShroudedKeyBag, encryptedPrivateKeyInfo, attrs);
83
+ }
84
+ function safeBag(bagOid, bagValue, attrs) {
85
+ const parts = [oid(bagOid), explicit(0, bagValue)];
86
+ if (attrs.length > 0) {
87
+ parts.push(canonicalSetOf(attrs));
88
+ }
89
+ return sequence(...parts);
90
+ }
91
+ // X.690 §11.6: SET OF components are encoded in ascending order of their
92
+ // encoded octet strings, with the shorter component padded with trailing
93
+ // 0-octets for the comparison. Used for bagAttributes.
94
+ function canonicalSetOf(children) {
95
+ const sorted = [...children].sort(derSetCompare);
96
+ return set(...sorted);
97
+ }
98
+ function derSetCompare(a, b) {
99
+ const len = Math.max(a.length, b.length);
100
+ for (let i = 0; i < len; i += 1) {
101
+ const av = i < a.length ? a[i] : 0;
102
+ const bv = i < b.length ? b[i] : 0;
103
+ if (av !== bv)
104
+ return av - bv;
105
+ }
106
+ return 0;
107
+ }
108
+ function buildLocalKeyIdAttr(localKeyId) {
109
+ return sequence(oid(OID.localKeyId), set(octetString(localKeyId)));
110
+ }
111
+ function buildFriendlyNameAttr(friendlyNameUtf8) {
112
+ return sequence(oid(OID.friendlyName), set(bmpString(friendlyNameUtf8)));
113
+ }
114
+ function bmpString(utf8) {
115
+ const utf16 = utf8ToUtf16Be(utf8);
116
+ return der(0x1e, utf16);
117
+ }
118
+ async function pbes2EncryptAesCbc(plaintext, pbkdf2BaseKey, iterations) {
119
+ const salt = randomBytes(SALT_LENGTH);
120
+ const iv = randomBytes(IV_LENGTH);
121
+ const aesKey = await crypto.subtle.deriveKey({ name: "PBKDF2", salt: arrayBufferFromBytes(salt), iterations, hash: "SHA-256" }, pbkdf2BaseKey, { name: "AES-CBC", length: 256 }, false, ["encrypt"]);
122
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-CBC", iv: arrayBufferFromBytes(iv) }, aesKey, arrayBufferFromBytes(plaintext)));
123
+ return {
124
+ algorithm: algorithmIdentifierPbes2(salt, iterations, iv),
125
+ ciphertext
126
+ };
127
+ }
128
+ function algorithmIdentifierPbes2(salt, iterations, iv) {
129
+ const pbkdf2Params = sequence(octetString(salt), integer(BigInt(iterations)), sequence(oid(OID.hmacWithSha256), nullValue()));
130
+ const keyDerivationFunc = sequence(oid(OID.pbkdf2), pbkdf2Params);
131
+ const encryptionScheme = sequence(oid(OID.aes256Cbc), octetString(iv));
132
+ return sequence(oid(OID.pbes2), sequence(keyDerivationFunc, encryptionScheme));
133
+ }
134
+ // PKCS#12 v1 KDF (RFC 7292 App.B) producing an HMAC-SHA-256 key (32 bytes).
135
+ // Specialized for ID=3, n=u=32, v=64. Single-block output: no I update needed.
136
+ async function pkcs12V1KdfMacKey(passwordUtf16, salt, iterations) {
137
+ const v = SHA256_BLOCK_SIZE;
138
+ const D = new Uint8Array(v).fill(PKCS12_KDF_ID_MAC);
139
+ const sLen = Math.ceil(salt.length / v) * v;
140
+ const S2 = new Uint8Array(sLen);
141
+ for (let i = 0; i < sLen; i += 1) {
142
+ S2[i] = salt[i % salt.length];
143
+ }
144
+ const pLen = Math.ceil(passwordUtf16.length / v) * v;
145
+ const P2 = new Uint8Array(pLen);
146
+ for (let i = 0; i < pLen; i += 1) {
147
+ P2[i] = passwordUtf16[i % passwordUtf16.length];
148
+ }
149
+ const I = concatBytes([S2, P2]);
150
+ // T_0 holds D || S2 || P2 (raw password bytes embedded in P2).
151
+ // Each digestSha256 returns a fresh 32-byte buffer; the previous T's buffer
152
+ // becomes unreachable but, without an explicit wipe, would sit on the JS
153
+ // heap until GC. Zero it before reassigning. Successive Ts are H^j(D||I)
154
+ // values, which let an attacker reach H^c(D||I) = MAC key by continuing the
155
+ // chain — they're as sensitive as the MAC key itself.
156
+ let T = concatBytes([D, I]);
157
+ for (let j = 0; j < iterations; j += 1) {
158
+ const next = await digestSha256(T);
159
+ T.fill(0);
160
+ T = next;
161
+ }
162
+ wipe(D, S2, P2, I);
163
+ return T.subarray(0, MAC_KEY_LENGTH);
164
+ }
165
+ // Convert UTF-8 bytes to UTF-16BE bytes plus a 0x00 0x00 terminator,
166
+ // without going through any JavaScript string. Walks code points by hand.
167
+ function utf8ToUtf16BeWithTerminator(utf8) {
168
+ const utf16 = utf8ToUtf16Be(utf8);
169
+ const out = new Uint8Array(utf16.length + 2);
170
+ out.set(utf16, 0);
171
+ wipe(utf16);
172
+ return out;
173
+ }
174
+ // Two-pass to avoid an intermediate `number[]` that would retain
175
+ // password-derived bytes on the JS heap until GC. Pass 1 sizes the output;
176
+ // pass 2 writes directly into a tight Uint8Array. Both passes share the same
177
+ // boundary checks so an invalid leader byte fails fast in pass 1.
178
+ function utf8ToUtf16Be(utf8) {
179
+ let outLen = 0;
180
+ let i = 0;
181
+ while (i < utf8.length) {
182
+ const b1 = utf8[i];
183
+ if (b1 < 0x80) {
184
+ outLen += 2;
185
+ i += 1;
186
+ }
187
+ else if ((b1 & 0xe0) === 0xc0 && i + 1 < utf8.length) {
188
+ outLen += 2;
189
+ i += 2;
190
+ }
191
+ else if ((b1 & 0xf0) === 0xe0 && i + 2 < utf8.length) {
192
+ outLen += 2;
193
+ i += 3;
194
+ }
195
+ else if ((b1 & 0xf8) === 0xf0 && i + 3 < utf8.length) {
196
+ outLen += 4;
197
+ i += 4;
198
+ }
199
+ else {
200
+ throw new Error("Invalid UTF-8 sequence");
201
+ }
202
+ }
203
+ const out = new Uint8Array(outLen);
204
+ let oi = 0;
205
+ i = 0;
206
+ while (i < utf8.length) {
207
+ const b1 = utf8[i];
208
+ let cp;
209
+ if (b1 < 0x80) {
210
+ cp = b1;
211
+ i += 1;
212
+ }
213
+ else if ((b1 & 0xe0) === 0xc0) {
214
+ cp = ((b1 & 0x1f) << 6) | (utf8[i + 1] & 0x3f);
215
+ i += 2;
216
+ }
217
+ else if ((b1 & 0xf0) === 0xe0) {
218
+ cp = ((b1 & 0x0f) << 12) | ((utf8[i + 1] & 0x3f) << 6) | (utf8[i + 2] & 0x3f);
219
+ i += 3;
220
+ }
221
+ else {
222
+ cp = ((b1 & 0x07) << 18)
223
+ | ((utf8[i + 1] & 0x3f) << 12)
224
+ | ((utf8[i + 2] & 0x3f) << 6)
225
+ | (utf8[i + 3] & 0x3f);
226
+ i += 4;
227
+ }
228
+ if (cp < 0x10000) {
229
+ out[oi++] = (cp >> 8) & 0xff;
230
+ out[oi++] = cp & 0xff;
231
+ }
232
+ else {
233
+ const adj = cp - 0x10000;
234
+ const high = 0xd800 + ((adj >> 10) & 0x3ff);
235
+ const low = 0xdc00 + (adj & 0x3ff);
236
+ out[oi++] = (high >> 8) & 0xff;
237
+ out[oi++] = high & 0xff;
238
+ out[oi++] = (low >> 8) & 0xff;
239
+ out[oi++] = low & 0xff;
240
+ }
241
+ }
242
+ return out;
243
+ }
244
+ function wipe(...bufs) {
245
+ for (const b of bufs) {
246
+ b.fill(0);
247
+ }
248
+ }
@@ -1 +1 @@
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"}
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;AAuID,QAAA,MAAM,cAAc;;;;CAIV,CAAC;AAEX,KAAK,WAAW,GAAG,MAAM,OAAO,cAAc,CAAC"}
package/dist/x509.js CHANGED
@@ -1,4 +1,4 @@
1
- import { asciiBytes, concatBytes } from "./bytes.js";
1
+ import { asciiBytes, concatBytes, randomBytes } from "./bytes.js";
2
2
  import { signatureAlgorithmOidForCurve } from "./crypto.js";
3
3
  import { bitString, boolean, contextPrimitive, der, explicit, generalizedTime, integer, octetString, oid, readElement, sequence, TAG, utcTime } from "./der.js";
4
4
  import { encodeIpAddress } from "./ip.js";
@@ -106,8 +106,7 @@ function encodeSerialNumber(serialNumber) {
106
106
  }
107
107
  function encodeSerialNumberDer(serialNumber) {
108
108
  if (serialNumber === undefined) {
109
- const bytes = new Uint8Array(16);
110
- crypto.getRandomValues(bytes);
109
+ const bytes = randomBytes(16);
111
110
  bytes[0] = bytes[0] & 0x7f;
112
111
  if (bytes.every((byte) => byte === 0)) {
113
112
  bytes[15] = 1;
package/package.json CHANGED
@@ -1,66 +1,70 @@
1
- {
2
- "name": "@noz-ele/edgca",
3
- "version": "0.2.0",
4
- "description": "A small Cloudflare Workers-friendly toolkit for issuing self-managed CA and mTLS client certificates.",
5
- "type": "module",
6
- "license": "MIT",
7
- "author": "NOZ-ELE",
8
- "homepage": "https://github.com/noz-ele/EdgCA#readme",
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/noz-ele/EdgCA.git"
12
- },
13
- "bugs": {
14
- "url": "https://github.com/noz-ele/EdgCA/issues"
15
- },
16
- "keywords": [
17
- "ca",
18
- "x509",
19
- "mtls",
20
- "client-certificate",
21
- "pki",
22
- "ecdsa",
23
- "p-256",
24
- "webcrypto",
25
- "cloudflare-workers",
26
- "edge"
27
- ],
28
- "engines": {
29
- "node": ">=20"
30
- },
31
- "publishConfig": {
32
- "access": "public"
33
- },
34
- "sideEffects": false,
35
- "files": [
36
- "dist",
37
- "LICENSE",
38
- "README.md",
39
- "SECURITY.md"
40
- ],
41
- "exports": {
42
- ".": {
43
- "types": "./dist/index.d.ts",
44
- "import": "./dist/index.js"
45
- }
46
- },
47
- "types": "./dist/index.d.ts",
48
- "scripts": {
49
- "build": "tsc -p tsconfig.json",
50
- "typecheck": "tsc -p tsconfig.json --noEmit",
51
- "pretest": "npm run build",
52
- "test": "vitest run",
53
- "pretest:workers": "npm run build",
54
- "prepack": "npm run build",
55
- "prepublishOnly": "npm run typecheck && npm test",
56
- "test:workers": "vitest run --config vitest.config.ts"
57
- },
58
- "devDependencies": {
59
- "@cloudflare/vitest-pool-workers": "^0.15.2",
60
- "@vitest/coverage-istanbul": "^4.1.5",
61
- "fast-check": "^4.7.0",
62
- "typescript": "^5.9.3",
63
- "vitest": "^4.1.5",
64
- "wrangler": "^4.45.0"
65
- }
66
- }
1
+ {
2
+ "name": "@noz-ele/edgca",
3
+ "version": "0.3.0",
4
+ "description": "A small Cloudflare Workers-friendly toolkit for issuing self-managed CA and mTLS client certificates.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "NOZ-ELE",
8
+ "homepage": "https://github.com/noz-ele/EdgCA#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/noz-ele/EdgCA.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/noz-ele/EdgCA/issues"
15
+ },
16
+ "keywords": [
17
+ "ca",
18
+ "x509",
19
+ "mtls",
20
+ "client-certificate",
21
+ "pki",
22
+ "ecdsa",
23
+ "p-256",
24
+ "webcrypto",
25
+ "cloudflare-workers",
26
+ "edge"
27
+ ],
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "sideEffects": false,
35
+ "files": [
36
+ "dist",
37
+ "LICENSE",
38
+ "README.md",
39
+ "SECURITY.md"
40
+ ],
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/index.d.ts",
44
+ "import": "./dist/index.js"
45
+ },
46
+ "./pkcs12": {
47
+ "types": "./dist/pkcs12.d.ts",
48
+ "import": "./dist/pkcs12.js"
49
+ }
50
+ },
51
+ "types": "./dist/index.d.ts",
52
+ "scripts": {
53
+ "build": "tsc -p tsconfig.json",
54
+ "typecheck": "tsc -p tsconfig.json --noEmit",
55
+ "pretest": "npm run build",
56
+ "test": "vitest run && vitest run --config vitest.node.config.ts",
57
+ "pretest:workers": "npm run build",
58
+ "prepack": "npm run build",
59
+ "prepublishOnly": "npm run typecheck && npm test",
60
+ "test:workers": "vitest run --config vitest.config.ts"
61
+ },
62
+ "devDependencies": {
63
+ "@cloudflare/vitest-pool-workers": "^0.15.2",
64
+ "@vitest/coverage-istanbul": "^4.1.5",
65
+ "fast-check": "^4.7.0",
66
+ "typescript": "^5.9.3",
67
+ "vitest": "^4.1.5",
68
+ "wrangler": "^4.45.0"
69
+ }
70
+ }