From bf7f0b6be28fe02d7b6971e050a1589c15e909e4 Mon Sep 17 00:00:00 2001 From: IronBlood Date: Sun, 19 Apr 2026 23:14:36 +0800 Subject: [PATCH 1/5] feat: email is not required any more --- README.md | 5 +--- src/cli.ts | 39 +++++----------------------- src/lib.ts | 76 +++++------------------------------------------------- 3 files changed, 14 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index a82e50e..ce16fcb 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,6 @@ certcli \ -p /path/to/account.pub \ -k /path/to/account.key \ -s /path/to/domain.csr \ - -e test@example.com \ -c /path/to/domain.crt ``` @@ -66,15 +65,13 @@ Options: -k, --priv_key Path to your account private key [string] [required] -p, --pub_key Path to your account public key [string] [required] -s, --csr Path to the certificate signing request [string] [required] - -e, --email Contact email [string] [required] -c, --cert_file Path to save the certificate file [string] -d, --dry_run Perform a dry run [count] -h, --help Show help [boolean] Examples: certcli -k account.key -p account.pub -s Get a certificate and save to - domain.csr -e test@example.com -c `domain.crt` - domain.crt + domain.csr -c domain.crt `domain.crt` https_proxy=http://localhost:8080 Using a HTTP proxy if the certcli ... connectivity to Let's Encrypt API is not stable diff --git a/src/cli.ts b/src/cli.ts index bf1c466..af8e90b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -19,7 +19,6 @@ import { createOrder, createOrderCmdAsync, validateRegistrationAsync, - validateUpdateAsync, validateOrderAsync, buildAuthorizationAsync, validateAuthorizationAsync, @@ -50,7 +49,7 @@ const argv = yargs(hideBin(process.argv)) .usage("Usage: $0 [options]") .example([ [ - "$0 -k account.key -p account.pub -s domain.csr -e test@example.com -c domain.crt", + "$0 -k account.key -p account.pub -s domain.csr -c domain.crt", "Get a certificate and save to `domain.crt`" ], [ @@ -76,12 +75,6 @@ const argv = yargs(hideBin(process.argv)) type: "string", demandOption: true, }) - .option("email", { - alias: "e", - describe: "Contact email", - type: "string", - demandOption: true, - }) .option("cert_file", { alias: "c", describe: "Path to save the certificate file", @@ -152,8 +145,6 @@ async function main() { console.log(` - ${chalk.green(argv.priv_key)}`); console.log(chalk.cyan("Public Key:")); console.log(` - ${chalk.green(argv.pub_key)}`); - console.log(chalk.cyan("Email:")); - console.log(` - ${chalk.yellow(argv.email)}`); console.log(chalk.cyan("Domains:")); domains.forEach((domain) => { console.log(` - ${chalk.magenta(domain)}`); @@ -185,7 +176,6 @@ async function main() { const stage_account = ora(`Validating account info...`).start(); const account = await createAccountAsync({ - email: argv.email, pubkey, }); stage_account.succeed(`Account validated`); @@ -213,37 +203,20 @@ async function main() { const stage_term = ora("Accepting terms...").start(); const { account_uri, - update_protected_b64, - cmd: registration_cmd, + order_protected_b64, + cmd: order_sig_cmd, } = await validateRegistrationAsync({ directory, account, openssl_order_signature, + order, registration_protected_b64, }); const openssl_registration_signature = await opensslSignAsync({ - content: registration_cmd, - priv_key: argv.priv_key, - }); - stage_term.succeed("Terms accepted"); - - const stage_validate = ora(`Updating yout account email ${argv.email}...`).start(); - const { - order_protected_b64, - cmd: order_sig_cmd, - } = await validateUpdateAsync({ - account, - account_uri, - directory, - openssl_registration_signature, - order, - update_protected_b64, - }); - const openssl_validate_order_signature = await opensslSignAsync({ content: order_sig_cmd, priv_key: argv.priv_key, }); - stage_validate.succeed("Email updated"); + stage_term.succeed("Terms accepted"); const stage_update_order = ora("Creating your certificate order").start(); const { @@ -251,7 +224,7 @@ async function main() { order_response, } = await validateOrderAsync({ directory, - openssl_validate_order_signature, + openssl_registration_signature, order, order_protected_b64, }); diff --git a/src/lib.ts b/src/lib.ts index dedcb40..31b6c81 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -109,25 +109,13 @@ interface Account { termsOfServiceAgreed: boolean; }; registration_payload_b64: string; - update_payload_json: { - contact: string[]; - }; - update_payload_b64: string; } export async function createAccountAsync({ - email, pubkey, }: { - email: string; pubkey: string; }): Promise { - // validate email - const email_re = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; - if (!email_re.test(email)) { - throw "Account email doesn't look valid."; - } - // parse account public key if (pubkey === "") { throw "You need to include an account public key."; @@ -147,9 +135,6 @@ export async function createAccountAsync({ let registration_payload_json = { termsOfServiceAgreed: true, }; - let update_payload_json = { - contact: [ "mailto:" + email ], - }; return { pubkey, @@ -158,8 +143,6 @@ export async function createAccountAsync({ thumbprint: b64(hash), registration_payload_json, registration_payload_b64: b64(JSON.stringify(registration_payload_json)), - update_payload_json, - update_payload_b64: b64(JSON.stringify(update_payload_json)), }; } @@ -213,11 +196,13 @@ export async function validateRegistrationAsync({ account, directory, openssl_order_signature, + order, registration_protected_b64, }: { account: Account, directory: DirectoryResponse, openssl_order_signature: string; + order: ReturnType; registration_protected_b64: string, }) { const signature = hex2b64(openssl_order_signature); @@ -247,54 +232,6 @@ export async function validateRegistrationAsync({ const nonce = await getNonceAsync(directory); - const update_protected_json = { - url: account_uri, - alg: account.alg, - nonce, - kid: account_uri, - }; - const update_protected_b64 = b64(JSON.stringify(update_protected_json)); - - return { - account_uri, - update_protected_b64, - cmd: `${update_protected_b64}.${account.update_payload_b64}` - }; -} - -// Step: 3b -export async function validateUpdateAsync({ - account, - account_uri, - directory, - openssl_registration_signature, - order, - update_protected_b64, -}: { - account: Account; - account_uri: string; - directory: DirectoryResponse; - openssl_registration_signature: string; - order: ReturnType; - update_protected_b64: string; -}) { - const signature = hex2b64(openssl_registration_signature); - const response = await fetch(account_uri, { - method: "POST", - headers: FETCH_HEADERS, - body: JSON.stringify({ - protected: update_protected_b64, - payload: account.update_payload_b64, - signature, - }), - }); - - if (response.status != 200) { - throw "Account contact update failed. Please start back at Step 1."; - } - - const nonce = await getNonceAsync(directory); - const order_protected_json = { url: directory.newOrder, alg: account.alg, @@ -305,26 +242,27 @@ export async function validateUpdateAsync({ const order_protected_b64 = b64(JSON.stringify(order_protected_json)); return { + account_uri, order_protected_b64, cmd: `${order_protected_b64}.${order.order_payload_b64}`, }; } /* - * Step 3c: Create New Order (POST /newOrder) + * Step 3b: Create New Order (POST /newOrder) */ export async function validateOrderAsync({ directory, - openssl_validate_order_signature, + openssl_registration_signature, order, order_protected_b64, }: { directory: DirectoryResponse; - openssl_validate_order_signature: string; + openssl_registration_signature: string; order: ReturnType; order_protected_b64: string; }) { - const signature = hex2b64(openssl_validate_order_signature); + const signature = hex2b64(openssl_registration_signature); const response = await fetch(directory.newOrder, { method: "POST", headers: FETCH_HEADERS, From 1025afb56546a9360f608dc54ce0a81da256e860 Mon Sep 17 00:00:00 2001 From: IronBlood Date: Mon, 20 Apr 2026 11:56:03 +0800 Subject: [PATCH 2/5] feat: sleep a few seconds to before finalize order --- src/cli.ts | 3 +++ src/utils.ts | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 src/utils.ts diff --git a/src/cli.ts b/src/cli.ts index af8e90b..d1463bb 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -44,6 +44,7 @@ import { import { opensslSignAsync, } from "./openssl_exec"; +import { sleep } from "./utils"; const argv = yargs(hideBin(process.argv)) .usage("Usage: $0 [options]") @@ -383,6 +384,7 @@ async function main() { } const stage_finalize_order = ora(`Finalizing order...`).start(); + await sleep(10_000); const { finalize_payload_b64, finalize_protected_b64, @@ -398,6 +400,7 @@ async function main() { content: finalize_order_cmd, priv_key: argv.priv_key, }); + await sleep(1000); const { recheck_order_protected_b64, cmd: foo, diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..2c9f4e5 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,5 @@ +import { setTimeout } from "node:timers/promises"; + +export async function sleep(delay: number) { + await setTimeout(delay); +} From 249673e3867c78daf3056d5bbefbc994dd067e1f Mon Sep 17 00:00:00 2001 From: IronBlood Date: Mon, 20 Apr 2026 12:12:44 +0800 Subject: [PATCH 3/5] feat: remove debug info --- src/cli.ts | 3 --- src/lib.ts | 1 - 2 files changed, 4 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index d1463bb..9a7ab5d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -378,9 +378,6 @@ async function main() { if (obj.status === "valid") continue; - - // FIXME - console.log(obj); } const stage_finalize_order = ora(`Finalizing order...`).start(); diff --git a/src/lib.ts b/src/lib.ts index 31b6c81..39442cb 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -473,7 +473,6 @@ export async function checkAuthorizationAsync({ const status = auth_obj.status; if (status === "pending") { - console.log(auth_obj); const nonce = await getNonceAsync(directory); const recheck_auth_protected_json = { url: challenge.url, From 5d587c63f48fc70459e503400bef50e16452f666 Mon Sep 17 00:00:00 2001 From: IronBlood Date: Mon, 20 Apr 2026 13:17:24 +0800 Subject: [PATCH 4/5] ci: provenance --- .github/workflows/release.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07b994c..afec231 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,9 +2,17 @@ name: Release on: workflow_dispatch +permissions: + contents: read + jobs: release: runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write + id-token: write steps: - uses: actions/checkout@v4 with: @@ -24,4 +32,5 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true run: npx semantic-release --branches main From e9590dddb7729913ce8991e2468e7effa1fe3b6c Mon Sep 17 00:00:00 2001 From: IronBlood Date: Mon, 20 Apr 2026 13:17:47 +0800 Subject: [PATCH 5/5] ci: add github-actions --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index aff82a1..4db08e3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,7 @@ updates: directory: "/" schedule: interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly"