@alchemy.run/acme 0.0.0 → 2.0.0-beta.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +48 -4
  3. package/lib/Account.d.ts +121 -0
  4. package/lib/Account.d.ts.map +1 -0
  5. package/lib/Account.js +163 -0
  6. package/lib/Account.js.map +1 -0
  7. package/lib/Certificate.d.ts +138 -0
  8. package/lib/Certificate.d.ts.map +1 -0
  9. package/lib/Certificate.js +157 -0
  10. package/lib/Certificate.js.map +1 -0
  11. package/lib/CertificateAuthority.d.ts +25 -0
  12. package/lib/CertificateAuthority.d.ts.map +1 -0
  13. package/lib/CertificateAuthority.js +22 -0
  14. package/lib/CertificateAuthority.js.map +1 -0
  15. package/lib/Client.d.ts +89 -0
  16. package/lib/Client.d.ts.map +1 -0
  17. package/lib/Client.js +237 -0
  18. package/lib/Client.js.map +1 -0
  19. package/lib/Der.d.ts +43 -0
  20. package/lib/Der.d.ts.map +1 -0
  21. package/lib/Der.js +138 -0
  22. package/lib/Der.js.map +1 -0
  23. package/lib/Dns.d.ts +59 -0
  24. package/lib/Dns.d.ts.map +1 -0
  25. package/lib/Dns.js +104 -0
  26. package/lib/Dns.js.map +1 -0
  27. package/lib/DnsSolver.d.ts +60 -0
  28. package/lib/DnsSolver.d.ts.map +1 -0
  29. package/lib/DnsSolver.js +48 -0
  30. package/lib/DnsSolver.js.map +1 -0
  31. package/lib/Errors.d.ts +93 -0
  32. package/lib/Errors.d.ts.map +1 -0
  33. package/lib/Errors.js +51 -0
  34. package/lib/Errors.js.map +1 -0
  35. package/lib/IssueCertificate.d.ts +76 -0
  36. package/lib/IssueCertificate.d.ts.map +1 -0
  37. package/lib/IssueCertificate.js +3 -0
  38. package/lib/IssueCertificate.js.map +1 -0
  39. package/lib/IssueCertificateHttp.d.ts +24 -0
  40. package/lib/IssueCertificateHttp.d.ts.map +1 -0
  41. package/lib/IssueCertificateHttp.js +56 -0
  42. package/lib/IssueCertificateHttp.js.map +1 -0
  43. package/lib/Pki.d.ts +56 -0
  44. package/lib/Pki.d.ts.map +1 -0
  45. package/lib/Pki.js +182 -0
  46. package/lib/Pki.js.map +1 -0
  47. package/lib/Providers.d.ts +30 -0
  48. package/lib/Providers.d.ts.map +1 -0
  49. package/lib/Providers.js +31 -0
  50. package/lib/Providers.js.map +1 -0
  51. package/lib/index.d.ts +12 -0
  52. package/lib/index.d.ts.map +1 -0
  53. package/lib/index.js +12 -0
  54. package/lib/index.js.map +1 -0
  55. package/package.json +47 -3
  56. package/src/Account.ts +254 -0
  57. package/src/Certificate.ts +272 -0
  58. package/src/CertificateAuthority.ts +41 -0
  59. package/src/Client.ts +405 -0
  60. package/src/Der.ts +157 -0
  61. package/src/Dns.ts +227 -0
  62. package/src/DnsSolver.ts +109 -0
  63. package/src/Errors.ts +90 -0
  64. package/src/IssueCertificate.ts +89 -0
  65. package/src/IssueCertificateHttp.ts +67 -0
  66. package/src/Pki.ts +275 -0
  67. package/src/Providers.ts +39 -0
  68. package/src/index.ts +11 -0
package/lib/Pki.js ADDED
@@ -0,0 +1,182 @@
1
+ import { Jose } from "@distilled.cloud/acme";
2
+ /**
3
+ * Keys, PKCS#10 certificate requests and X.509 parsing for the ACME flow.
4
+ * WebCrypto plus the tiny DER codec in `Der.ts` — no native code, so the
5
+ * same code runs at deploy time and inside a Worker or Fly Service.
6
+ */
7
+ import * as Effect from "effect/Effect";
8
+ import * as Redacted from "effect/Redacted";
9
+ import * as Der from "./Der.js";
10
+ import { PkiError } from "./Errors.js";
11
+ const OID = {
12
+ commonName: "2.5.4.3",
13
+ organization: "2.5.4.10",
14
+ extensionRequest: "1.2.840.113549.1.9.14",
15
+ subjectAltName: "2.5.29.17",
16
+ ecdsaWithSha256: "1.2.840.10045.4.3.2",
17
+ sha256WithRsa: "1.2.840.113549.1.1.11",
18
+ };
19
+ const keyGenParams = (alg) => alg === "RS256"
20
+ ? {
21
+ name: "RSASSA-PKCS1-v1_5",
22
+ modulusLength: 2048,
23
+ publicExponent: new Uint8Array([1, 0, 1]),
24
+ hash: "SHA-256",
25
+ }
26
+ : { name: "ECDSA", namedCurve: "P-256" };
27
+ const importParams = (alg) => alg === "RS256"
28
+ ? { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }
29
+ : { name: "ECDSA", namedCurve: "P-256" };
30
+ const signParams = (alg) => alg === "RS256" ? { name: "RSASSA-PKCS1-v1_5" } : { name: "ECDSA", hash: "SHA-256" };
31
+ const toPem = (label, der) => {
32
+ const b64 = btoa(String.fromCharCode(...der));
33
+ const lines = b64.match(/.{1,64}/g) ?? [];
34
+ return `-----BEGIN ${label}-----\n${lines.join("\n")}\n-----END ${label}-----\n`;
35
+ };
36
+ /** The DER bytes of the first (or `index`-th) PEM block in `pem`. */
37
+ export const fromPem = (pem, index = 0) => {
38
+ const blocks = pem.match(/-----BEGIN [^-]+-----[\s\S]*?-----END [^-]+-----/g);
39
+ const block = blocks?.[index];
40
+ if (block === undefined) {
41
+ throw new Error(`PEM block ${index} not found`);
42
+ }
43
+ const body = block
44
+ .replace(/-----BEGIN [^-]+-----/, "")
45
+ .replace(/-----END [^-]+-----/, "")
46
+ .replace(/\s+/g, "");
47
+ const binary = atob(body);
48
+ const out = new Uint8Array(binary.length);
49
+ for (let i = 0; i < binary.length; i++)
50
+ out[i] = binary.charCodeAt(i);
51
+ return out;
52
+ };
53
+ /** Every PEM block in a chain, leaf first. */
54
+ export const splitPemChain = (pem) => pem.match(/-----BEGIN [^-]+-----[\s\S]*?-----END [^-]+-----/g) ?? [];
55
+ const tryPromise = (message, run) => Effect.tryPromise({
56
+ try: run,
57
+ catch: (cause) => new PkiError({ message, cause }),
58
+ });
59
+ /** Generate a certificate key pair (ES256 P-256 by default). */
60
+ export const generateKey = (algorithm = "ES256") => tryPromise("Generating the certificate key failed.", async () => {
61
+ const pair = await crypto.subtle.generateKey(keyGenParams(algorithm), true, ["sign", "verify"]);
62
+ const pkcs8 = new Uint8Array(await crypto.subtle.exportKey("pkcs8", pair.privateKey));
63
+ const spki = new Uint8Array(await crypto.subtle.exportKey("spki", pair.publicKey));
64
+ return {
65
+ algorithm,
66
+ privateKeyPem: Redacted.make(toPem("PRIVATE KEY", pkcs8)),
67
+ privateKey: pair.privateKey,
68
+ spki,
69
+ };
70
+ });
71
+ const algorithmOfPkcs8 = (der) => {
72
+ // PrivateKeyInfo ::= SEQUENCE { version, AlgorithmIdentifier, ... }
73
+ const root = Der.read(der, 0);
74
+ const [, algorithm] = Der.children(root);
75
+ const [algOid] = Der.children(algorithm);
76
+ return Der.decodeOid(algOid) === "1.2.840.113549.1.1.1" ? "RS256" : "ES256";
77
+ };
78
+ /** Import a PKCS#8 PEM private key (ES256 or RS256, detected from the DER). */
79
+ export const importPrivateKey = (pem) => tryPromise("Importing the certificate key failed.", async () => {
80
+ const text = Redacted.isRedacted(pem) ? Redacted.value(pem) : pem;
81
+ const der = fromPem(text);
82
+ const algorithm = algorithmOfPkcs8(der);
83
+ const key = await crypto.subtle.importKey("pkcs8", der, importParams(algorithm), true, ["sign"]);
84
+ return { key, algorithm };
85
+ });
86
+ /** The private JWK of a PKCS#8 PEM key (for revocation signed by the certificate key). */
87
+ export const privateKeyToJwk = (pem) => importPrivateKey(pem).pipe(Effect.flatMap(({ key }) => tryPromise("Exporting the certificate key as a JWK failed.", async () => Redacted.make(JSON.stringify(await crypto.subtle.exportKey("jwk", key))))));
88
+ /** Raw `r || s` ECDSA signature → DER `SEQUENCE { r INTEGER, s INTEGER }`. */
89
+ const ecdsaToDer = (raw) => {
90
+ const half = raw.length / 2;
91
+ return Der.sequence(Der.integer(raw.subarray(0, half)), Der.integer(raw.subarray(half)));
92
+ };
93
+ /**
94
+ * A PKCS#10 certificate request for `identifiers` signed by `key`: the
95
+ * first identifier is the subject CN, all of them are `subjectAltName`
96
+ * dNSName entries. Returned as DER.
97
+ */
98
+ export const createCsr = (options) => tryPromise("Encoding the certificate request failed.", async () => {
99
+ const { key, identifiers } = options;
100
+ if (identifiers.length === 0) {
101
+ throw new Error("a certificate needs at least one identifier");
102
+ }
103
+ const subject = Der.sequence(Der.set(Der.sequence(Der.oid(OID.commonName), Der.utf8String(identifiers[0]))));
104
+ const sanExtension = Der.sequence(Der.oid(OID.subjectAltName), Der.octetString(Der.sequence(...identifiers.map((name) => Der.contextPrimitive(2, new TextEncoder().encode(name))))));
105
+ const attributes = Der.contextTag(0, Der.sequence(Der.oid(OID.extensionRequest), Der.set(Der.sequence(sanExtension))));
106
+ const info = Der.sequence(Der.integer(0), subject, key.spki, attributes);
107
+ const rawSignature = new Uint8Array(await crypto.subtle.sign(signParams(key.algorithm), key.privateKey, info));
108
+ const signature = key.algorithm === "ES256" ? ecdsaToDer(rawSignature) : rawSignature;
109
+ const algorithm = key.algorithm === "ES256"
110
+ ? Der.sequence(Der.oid(OID.ecdsaWithSha256))
111
+ : Der.sequence(Der.oid(OID.sha256WithRsa), Der.nullValue());
112
+ return Der.sequence(info, algorithm, Der.bitString(signature));
113
+ });
114
+ /** base64url of a DER CSR, as ACME's `finalize` expects. */
115
+ export const csrToBase64Url = (der) => Jose.base64url(der);
116
+ const decodeName = (name) => {
117
+ const parts = [];
118
+ for (const rdn of Der.children(name)) {
119
+ for (const attribute of Der.children(rdn)) {
120
+ const [typeNode, valueNode] = Der.children(attribute);
121
+ const type = Der.decodeOid(typeNode);
122
+ const value = new TextDecoder().decode(Der.content(valueNode));
123
+ const label = type === OID.commonName
124
+ ? "CN"
125
+ : type === OID.organization
126
+ ? "O"
127
+ : type === "2.5.4.6"
128
+ ? "C"
129
+ : type;
130
+ parts.push(`${label}=${value}`);
131
+ }
132
+ }
133
+ // CN first for readability; the rest keep their order.
134
+ parts.sort((a, b) => (a.startsWith("CN=") ? -1 : b.startsWith("CN=") ? 1 : 0));
135
+ return parts.join(", ");
136
+ };
137
+ /** Parse the first certificate of a PEM (or a DER buffer). */
138
+ export const parseCertificate = (input) => Effect.try({
139
+ try: () => {
140
+ const der = typeof input === "string" ? fromPem(input) : input;
141
+ const certificate = Der.read(der, 0);
142
+ const [tbs] = Der.children(certificate);
143
+ const fields = Der.children(tbs);
144
+ let index = 0;
145
+ if (fields[0].tag === 0xa0)
146
+ index++; // [0] EXPLICIT version
147
+ const serial = Der.toHex(Der.content(fields[index++]));
148
+ index++; // signature AlgorithmIdentifier
149
+ const issuer = decodeName(fields[index++]);
150
+ const [notBeforeNode, notAfterNode] = Der.children(fields[index++]);
151
+ const subject = decodeName(fields[index++]);
152
+ index++; // subjectPublicKeyInfo
153
+ const dnsNames = [];
154
+ for (const field of fields.slice(index)) {
155
+ if (field.tag !== 0xa3)
156
+ continue; // [3] EXPLICIT extensions
157
+ const [extensions] = Der.children(field);
158
+ for (const extension of Der.children(extensions)) {
159
+ const members = Der.children(extension);
160
+ if (Der.decodeOid(members[0]) !== OID.subjectAltName)
161
+ continue;
162
+ const valueNode = members[members.length - 1]; // OCTET STRING (critical may precede)
163
+ const generalNames = Der.read(Der.content(valueNode), 0);
164
+ for (const generalName of Der.children(generalNames)) {
165
+ if (generalName.tag === 0x82) {
166
+ dnsNames.push(new TextDecoder().decode(Der.content(generalName)));
167
+ }
168
+ }
169
+ }
170
+ }
171
+ return {
172
+ serial: serial.replace(/^00/, "") || "0",
173
+ notBefore: Der.decodeTime(notBeforeNode),
174
+ notAfter: Der.decodeTime(notAfterNode),
175
+ issuer,
176
+ subject,
177
+ dnsNames,
178
+ };
179
+ },
180
+ catch: (cause) => new PkiError({ message: "Parsing the certificate failed.", cause }),
181
+ });
182
+ //# sourceMappingURL=Pki.js.map
package/lib/Pki.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pki.js","sourceRoot":"","sources":["../src/Pki.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import * as Provider from "alchemy/Provider";
2
+ import * as Layer from "effect/Layer";
3
+ declare const Providers_base: Provider.ProviderCollection<Providers, "ACME">;
4
+ export declare class Providers extends Providers_base {
5
+ }
6
+ export type ProviderRequirements = Layer.Services<ReturnType<typeof providers>>;
7
+ /**
8
+ * Registers the ACME resource providers ({@link Account},
9
+ * {@link Certificate}) and the {@link IssueCertificateHttp} binding. DNS-01
10
+ * solvers come from the DNS provider, e.g. `Cloudflare.providers()` registers
11
+ * `Cloudflare.ACME.ZoneSolver`.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * export default Alchemy.Stack(
16
+ * "Certs",
17
+ * {
18
+ * providers: Layer.mergeAll(ACME.providers(), Cloudflare.providers()),
19
+ * state: Alchemy.localState(),
20
+ * },
21
+ * Effect.gen(function* () {
22
+ * const wildcard = yield* Wildcard;
23
+ * return { notAfter: wildcard.notAfter };
24
+ * }),
25
+ * );
26
+ * ```
27
+ */
28
+ export declare const providers: () => Layer.Layer<import("effect/http/HttpClient").HttpClient | import("./IssueCertificate.ts").IssueCertificate | Providers, never, never>;
29
+ export {};
30
+ //# sourceMappingURL=Providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Providers.d.ts","sourceRoot":"","sources":["../src/Providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAE7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;;AAKtC,qBAAa,SAAU,SAAQ,cAAgD;CAAG;AAElF,MAAM,MAAM,oBAAoB,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS,6IAMnB,CAAC"}
@@ -0,0 +1,31 @@
1
+ import * as Provider from "alchemy/Provider";
2
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
3
+ import * as Layer from "effect/Layer";
4
+ import { Account, AccountProvider } from "./Account.js";
5
+ import { Certificate, CertificateProvider } from "./Certificate.js";
6
+ import { IssueCertificateHttp } from "./IssueCertificateHttp.js";
7
+ export class Providers extends Provider.ProviderCollection()("ACME") {
8
+ }
9
+ /**
10
+ * Registers the ACME resource providers ({@link Account},
11
+ * {@link Certificate}) and the {@link IssueCertificateHttp} binding. DNS-01
12
+ * solvers come from the DNS provider, e.g. `Cloudflare.providers()` registers
13
+ * `Cloudflare.ACME.ZoneSolver`.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * export default Alchemy.Stack(
18
+ * "Certs",
19
+ * {
20
+ * providers: Layer.mergeAll(ACME.providers(), Cloudflare.providers()),
21
+ * state: Alchemy.localState(),
22
+ * },
23
+ * Effect.gen(function* () {
24
+ * const wildcard = yield* Wildcard;
25
+ * return { notAfter: wildcard.notAfter };
26
+ * }),
27
+ * );
28
+ * ```
29
+ */
30
+ export const providers = () => Layer.effect(Providers, Provider.collection([Account, Certificate])).pipe(Layer.provide(Layer.mergeAll(AccountProvider(), CertificateProvider())), Layer.provideMerge(IssueCertificateHttp), Layer.provideMerge(FetchHttpClient.layer), Layer.orDie);
31
+ //# sourceMappingURL=Providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Providers.js","sourceRoot":"","sources":["../src/Providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,eAAe,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,OAAO,SAAU,SAAQ,QAAQ,CAAC,kBAAkB,EAAa,CAAC,MAAM,CAAC;CAAG;AAIlF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAC5B,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CACvE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,eAAe,EAAE,EAAE,mBAAmB,EAAE,CAAC,CAAC,EACvE,KAAK,CAAC,YAAY,CAAC,oBAAoB,CAAC,EACxC,KAAK,CAAC,YAAY,CAAC,eAAe,CAAC,KAAK,CAAC,EACzC,KAAK,CAAC,KAAK,CACZ,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * from "./Account.ts";
2
+ export * from "./Certificate.ts";
3
+ export * from "./CertificateAuthority.ts";
4
+ export * from "./Client.ts";
5
+ export * from "./Dns.ts";
6
+ export * from "./DnsSolver.ts";
7
+ export * from "./Errors.ts";
8
+ export * from "./IssueCertificate.ts";
9
+ export * from "./IssueCertificateHttp.ts";
10
+ export * from "./Pki.ts";
11
+ export * from "./Providers.ts";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export * from "./Account.js";
2
+ export * from "./Certificate.js";
3
+ export * from "./CertificateAuthority.js";
4
+ export * from "./Client.js";
5
+ export * from "./Dns.js";
6
+ export * from "./DnsSolver.js";
7
+ export * from "./Errors.js";
8
+ export * from "./IssueCertificate.js";
9
+ export * from "./IssueCertificateHttp.js";
10
+ export * from "./Pki.js";
11
+ export * from "./Providers.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy.run/acme",
3
- "version": "0.0.0",
3
+ "version": "2.0.0-beta.85",
4
4
  "description": "ACME (Let's Encrypt, ZeroSSL) certificates for Alchemy, with a Cloudflare DNS-01 solver.",
5
5
  "keywords": [
6
6
  "acme",
@@ -10,12 +10,56 @@
10
10
  "letsencrypt",
11
11
  "tls"
12
12
  ],
13
+ "homepage": "https://alchemy.run",
14
+ "bugs": {
15
+ "url": "https://github.com/alchemy-run/alchemy/issues"
16
+ },
13
17
  "license": "Apache-2.0",
14
18
  "author": "Sam Goodwin <sam@alchemy.run>",
15
- "homepage": "https://alchemy.run",
16
19
  "repository": {
17
20
  "type": "git",
18
21
  "url": "git+https://github.com/alchemy-run/alchemy.git",
19
22
  "directory": "packages/acme"
23
+ },
24
+ "files": [
25
+ "lib",
26
+ "src"
27
+ ],
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "exports": {
31
+ ".": {
32
+ "types": "./lib/index.d.ts",
33
+ "bun": "./src/index.ts",
34
+ "worker": "./src/index.ts",
35
+ "default": "./lib/index.js"
36
+ },
37
+ "./*": {
38
+ "types": "./lib/*.d.ts",
39
+ "bun": "./src/*.ts",
40
+ "worker": "./src/*.ts",
41
+ "default": "./lib/*.js"
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "dependencies": {
49
+ "@distilled.cloud/acme": "1.0.0-rc.18"
50
+ },
51
+ "devDependencies": {
52
+ "@types/bun": "1.4.3",
53
+ "alchemy": "2.0.0-beta.85",
54
+ "effect": "^4.1.0",
55
+ "typescript": "^7.0.2"
56
+ },
57
+ "peerDependencies": {
58
+ "alchemy": "2.0.0-beta.85",
59
+ "effect": "^4.1.0"
60
+ },
61
+ "scripts": {
62
+ "build": "pnpm -r --filter \"{.}...\" run build:package",
63
+ "build:package": "node scripts/build.ts"
20
64
  }
21
- }
65
+ }
package/src/Account.ts ADDED
@@ -0,0 +1,254 @@
1
+ import * as Acme from "@distilled.cloud/acme";
2
+ import * as acme from "@distilled.cloud/acme/acme";
3
+ import { isResolved } from "alchemy/Diff";
4
+ import * as Provider from "alchemy/Provider";
5
+ import { Resource } from "alchemy/Resource";
6
+ import * as Data from "effect/Data";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Redacted from "effect/Redacted";
9
+ import type { CertificateAuthority } from "./CertificateAuthority.ts";
10
+ import { accountLayer, type AccountCredentials } from "./Client.ts";
11
+ import type { KeyAlgorithm } from "./Pki.ts";
12
+ import type { Providers } from "./Providers.ts";
13
+
14
+ export interface ExternalAccountBinding {
15
+ /** The key id the CA issued (ZeroSSL: `eab_kid`). */
16
+ keyId: string;
17
+ /** The base64url HMAC key the CA issued (ZeroSSL: `eab_hmac_key`). */
18
+ hmacKey: Redacted.Redacted<string> | string;
19
+ }
20
+
21
+ export interface AccountProps {
22
+ /**
23
+ * The certificate authority: a preset ({@link LetsEncrypt},
24
+ * {@link ZeroSSL}, …) or `{ directoryUrl }`. Changing it replaces the
25
+ * account.
26
+ */
27
+ ca: CertificateAuthority;
28
+ /**
29
+ * Contact URLs (`mailto:…`) the CA notifies about expiry and incidents.
30
+ * Updated in place.
31
+ */
32
+ contact?: string[];
33
+ /**
34
+ * You must agree to the CA's terms of service. Explicit on purpose —
35
+ * never defaulted.
36
+ */
37
+ termsOfServiceAgreed: true;
38
+ /**
39
+ * External Account Binding for CAs that require one (ZeroSSL, Google
40
+ * Trust Services). Changing the key id replaces the account.
41
+ */
42
+ eab?: ExternalAccountBinding;
43
+ /**
44
+ * Account key algorithm. Changing it replaces the account.
45
+ * @default "ES256"
46
+ */
47
+ keyAlgorithm?: KeyAlgorithm;
48
+ }
49
+
50
+ export interface Account extends Resource<
51
+ "ACME.Account",
52
+ AccountProps,
53
+ {
54
+ /** The CA's directory URL. */
55
+ directoryUrl: string;
56
+ /** PEM root trusted for the CA's HTTPS endpoint (private CAs only). */
57
+ trustedRoot: string | undefined;
58
+ /** The account URL the CA assigned — the `kid` on every later request. */
59
+ accountUrl: string;
60
+ /** Observed account status (`valid`, `deactivated`, `revoked`). */
61
+ status: string;
62
+ /** Observed contact URLs. */
63
+ contact: string[];
64
+ /** Account key algorithm. */
65
+ keyAlgorithm: KeyAlgorithm;
66
+ /** The account's private JWK (JSON). Sensitive; persisted in stack state. */
67
+ privateKey: Redacted.Redacted<string>;
68
+ },
69
+ never,
70
+ Providers
71
+ > {}
72
+
73
+ /**
74
+ * An ACME account with a certificate authority — the identity that every
75
+ * {@link Certificate} and every runtime {@link IssueCertificate} call
76
+ * signs with. One per CA per stack.
77
+ *
78
+ * The account key is generated on first reconcile and persisted as a
79
+ * redacted value alongside the CA's account URL. Protect the state store
80
+ * as secret material; redaction alone does not encrypt the storage backend.
81
+ *
82
+ * ### Let's Encrypt
83
+ * **Example:** An account with Let's Encrypt
84
+ * ```typescript
85
+ * export const LetsEncrypt = ACME.Account("LetsEncrypt", {
86
+ * ca: ACME.LetsEncrypt,
87
+ * contact: ["mailto:ops@example.com"],
88
+ * termsOfServiceAgreed: true,
89
+ * });
90
+ * ```
91
+ *
92
+ * ### External Account Binding
93
+ * ZeroSSL and Google Trust Services bind ACME accounts to an existing
94
+ * customer account with a key id + HMAC pair from their dashboard (ZeroSSL
95
+ * also mints them over its REST API — see `@distilled.cloud/zerossl`).
96
+ *
97
+ * **Example:** ZeroSSL
98
+ * ```typescript
99
+ * export const ZeroSsl = ACME.Account("ZeroSSL", {
100
+ * ca: ACME.ZeroSSL,
101
+ * eab: {
102
+ * keyId: Config.String("ZEROSSL_EAB_KID"),
103
+ * hmacKey: Config.Redacted("ZEROSSL_EAB_HMAC_KEY"),
104
+ * },
105
+ * termsOfServiceAgreed: true,
106
+ * });
107
+ * ```
108
+ *
109
+ * ### Private CAs
110
+ * A private CA (Pebble, step-ca) whose HTTPS certificate is not publicly
111
+ * trusted passes its root as `trustedRoot`.
112
+ *
113
+ * **Example:** Pebble
114
+ * ```typescript
115
+ * export const Pebble = ACME.Account("Pebble", {
116
+ * ca: { directoryUrl: "https://localhost:14000/dir", trustedRoot: pebbleRootPem },
117
+ * termsOfServiceAgreed: true,
118
+ * });
119
+ * ```
120
+ *
121
+ * @resource
122
+ * @product Account
123
+ * @category Certificates
124
+ */
125
+ export const Account = Resource<Account>("ACME.Account");
126
+
127
+ /** The CA answered `newAccount` without a `Location`, so the account has no URL. */
128
+ export class AccountUrlMissing extends Data.TaggedError("ACME.AccountUrlMissing")<{
129
+ readonly directoryUrl: string;
130
+ }> {}
131
+
132
+ /**
133
+ * Normalize to exactly one `Redacted` layer: a value that already came
134
+ * redacted (an SDK's sensitive field) wrapped again by the caller would
135
+ * otherwise sign with an object instead of the key.
136
+ */
137
+ const unwrap = (value: Redacted.Redacted<string> | string) => {
138
+ let inner: unknown = value;
139
+ while (Redacted.isRedacted(inner)) inner = Redacted.value(inner);
140
+ return Redacted.make(String(inner));
141
+ };
142
+
143
+ const credentialsOf = (
144
+ props: Pick<AccountProps, "ca" | "eab">,
145
+ accountKey: Redacted.Redacted<string>,
146
+ accountUrl: string | undefined,
147
+ ): AccountCredentials => ({
148
+ ca: props.ca,
149
+ accountKey,
150
+ accountUrl,
151
+ externalAccountBinding:
152
+ props.eab === undefined
153
+ ? undefined
154
+ : { keyId: props.eab.keyId, hmacKey: unwrap(props.eab.hmacKey) },
155
+ });
156
+
157
+ /** `newAccount` with `onlyReturnExisting` — the account, or `undefined`. */
158
+ const observeAccount = (credentials: AccountCredentials) =>
159
+ acme.newAccount({ onlyReturnExisting: true }).pipe(
160
+ Effect.catchTag("AcmeAccountDoesNotExist", () => Effect.undefined),
161
+ Effect.provide(accountLayer(credentials)),
162
+ );
163
+
164
+ const sameContacts = (
165
+ a: ReadonlyArray<string> | undefined,
166
+ b: ReadonlyArray<string> | undefined,
167
+ ) => {
168
+ const left = [...(a ?? [])].sort();
169
+ const right = [...(b ?? [])].sort();
170
+ return left.length === right.length && left.every((v, i) => v === right[i]);
171
+ };
172
+
173
+ export const AccountProvider = () =>
174
+ Provider.succeed(Account, {
175
+ stables: ["directoryUrl", "accountUrl", "keyAlgorithm", "privateKey"],
176
+
177
+ diff: Effect.fn(function* ({ news, olds, output }) {
178
+ if (news === undefined || !isResolved(news) || output === undefined) {
179
+ return undefined;
180
+ }
181
+ if (
182
+ news.ca.directoryUrl !== output.directoryUrl ||
183
+ (news.keyAlgorithm ?? "ES256") !== output.keyAlgorithm ||
184
+ news.eab?.keyId !== olds?.eab?.keyId
185
+ ) {
186
+ return { action: "replace" as const };
187
+ }
188
+ return undefined;
189
+ }),
190
+
191
+ read: Effect.fn(function* ({ olds, output }) {
192
+ if (output === undefined || olds === undefined) return undefined;
193
+ const credentials = credentialsOf(olds, output.privateKey, output.accountUrl);
194
+ const existing = yield* observeAccount(credentials);
195
+ if (existing === undefined) return undefined;
196
+ return {
197
+ ...output,
198
+ status: existing.status,
199
+ contact: existing.contact ?? [],
200
+ };
201
+ }),
202
+
203
+ reconcile: Effect.fn(function* ({ news, output }) {
204
+ const keyAlgorithm = news.keyAlgorithm ?? "ES256";
205
+ // Observe: the key is the identity; keep it across reconciles.
206
+ const accountKey = output?.privateKey ?? (yield* Acme.Jose.generateAccountKey(keyAlgorithm));
207
+ const credentials = credentialsOf(news, accountKey, output?.accountUrl);
208
+ let account = yield* observeAccount(credentials);
209
+
210
+ // Repeating newAccount with the same key returns the existing account.
211
+ if (account === undefined) {
212
+ account = yield* acme
213
+ .newAccount({
214
+ contact: news.contact,
215
+ termsOfServiceAgreed: true,
216
+ })
217
+ .pipe(Effect.provide(accountLayer(credentials)));
218
+ }
219
+ const accountUrl = account.location ?? output?.accountUrl;
220
+ if (accountUrl === undefined) {
221
+ return yield* new AccountUrlMissing({
222
+ directoryUrl: news.ca.directoryUrl,
223
+ });
224
+ }
225
+
226
+ // Sync: contacts are the only mutable aspect.
227
+ const withKid = accountLayer(credentialsOf(news, accountKey, accountUrl));
228
+ if (!sameContacts(news.contact, account.contact)) {
229
+ account = yield* acme
230
+ .updateAccount({ url: accountUrl, contact: news.contact ?? [] })
231
+ .pipe(Effect.provide(withKid));
232
+ }
233
+
234
+ return {
235
+ directoryUrl: news.ca.directoryUrl,
236
+ trustedRoot: news.ca.trustedRoot,
237
+ accountUrl,
238
+ status: account.status,
239
+ contact: account.contact ?? [],
240
+ keyAlgorithm,
241
+ privateKey: accountKey,
242
+ };
243
+ }),
244
+
245
+ delete: Effect.fn(function* ({ olds, output }) {
246
+ const credentials = credentialsOf(olds, output.privateKey, output.accountUrl);
247
+ // Deactivation is permanent and idempotent: a deactivated account
248
+ // answers `unauthorized`, a purged one `accountDoesNotExist`.
249
+ yield* acme.updateAccount({ url: output.accountUrl, status: "deactivated" }).pipe(
250
+ Effect.catchTag(["AcmeAccountDoesNotExist", "AcmeUnauthorized"], () => Effect.undefined),
251
+ Effect.provide(accountLayer(credentials)),
252
+ );
253
+ }),
254
+ });