Skip to content

Latest commit

 

History

History
251 lines (173 loc) · 7.25 KB

README.md

File metadata and controls

251 lines (173 loc) · 7.25 KB

🔏 sceau

NPM MIT License CI/CD Coverage Status

Code signing for NPM packages

Installation

Using your favourite package manager:

pnpm add -D sceau
yarn add -D sceau
npm install -D sceau

CLI usage

First off, you'll need a signature private key.

Keygen

You can generate one from the CLI:

$ sceau keygen

Run the following command in your terminal to use this private key:

export SCEAU_PRIVATE_KEY="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

Associated public key:
0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

This will give you an environment variable definition for the private key, and the associated public key.

Note about keys: The underlying signature is Ed25519, which stores the public key as part of the private key.

If you misplace your public key, it can be obtained from the private key: the public key is the LAST half (64 hex characters) of the private key.

Signing packages

To sign a package, run the following command:

$ sceau sign

This will:

  1. Collect a list of files to be published (based on the files option in package.json and the .npmignore file)
  2. Hash and sign each file into a manifest
  3. Inject some metadata, like:
  1. Sign the whole thing
  2. Store it in a sceau.json file next to package.json

CI usage

Links to source and build process are injected to provide transparency and traceability to a package being built.

The idea is that the signing step would occur in a public CI/CD context.

You can specify the URLs to the sources and build process either via the command-line, or via environment variables:

CLI argument Environment variable Description
--source SCEAU_SOURCE_URL Permalink to the source code
--build SCEAU_BUILD_URL Permalink to the build process

Note: those two URLs are automatically populated for you if sceau runs in a GitHub Actions context. PRs for more CI contributions are welcome!

If those are not provided, sceau will still sign your package, but the URLs will be set to unknown://local.

Sceau will write to a file, but will also print to the standard output, so this signature process can be audited by third parties.

Setting up package.json

Because sceau should run right before NPM packs your artifacts to publish them, you should run the signature step in the prepack script:

{
  "files": [
    // ...
    "sceau.json"
  ],
  "scripts": {
    "prepack": "sceau sign"
  }
}

Note that it's also required to add sceau.json to the list of files, otherwise the signature would be left behind when your package is packed.

Options

--packageDir lets you specify a path to a pacakge to sign. By default, sceau will try to look for a package to sign at the current working directory.

--file lets you choose the output file (defaults to sceau.json). It should be a path relative to the package directory (where package.json is located).

Example:

$ sceau sign --packageDir packages/my-package --file build/signature.json

This will sign package <cwd>/packages/my-package, and store the output at <cwd>/packages/my-package/build/signature.json.

--ignore lets you specify RegExp patterns to match against file paths. Files matching those patterns won't be included in the manifest and therefore won't be part of the final signature.

Note that the output file is always automatically included in those patterns.

Verifying

You can verify a package signed with sceau using the following command:

$ sceau verify --packageDir path/to/package

Options

You should provide the public key to verify a signature against, obtained from a trusted source (ideally one not under GitHub's or NPM's control, in case those were to be compromised).

$ sceau verify --publicKey 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

If the package uses a non-standard <packageDir>/sceau.json signature file, you can specify its location (relative to the package directory):

$ sceau verify --file build/signature.json

By default, calling verify on an unsigned package will only print that fact and exit cleanly. If you expect a package to be signed, you can use strict verification, that will fail on unsigned packages:

$ sceau verify --strict

Programmatic usage

Sceau can be imported as a library (ESM only).

You can access the commands using the same arguments as the CLI:

import {
  generateKeyPair,
  signCommand,
  verifyCommand,
  SCEAU_FILE_NAME,
} from 'sceau'

// Random keypair
const { publicKey, privateKey } = await generateKeyPair()

// Seeded keypair
const { publicKey, privateKey } = await generateKeyPair('baadf00d...')

await signCommand({
  file: SCEAU_FILE_NAME,
  quiet: true,
  privateKey: '...', // will default to SCEAU_PRIVATE_KEY env if unspecified
  packageDir: 'packages/my-package',
  build: 'https://url-to-build-process.example.com',
  source: 'https://url-to-sources.example.com',
})

await verifyCommand({
  file: SCEAU_FILE_NAME,
  strict: true,
  packageDir: 'packages/my-package',
  publicKey: '...', // will default to SCEAU_PUBLIC_KEY env if unspecified
})

Examples

Sceau signs itself when released.

You can verify a sceau install (using itself too):

sceau verify --strict --publicKey c30d5d28b88136c77168fb78bf117948127c4e22f987ab60cd083bbd6c7ac0c9

We use semantic-release, which injects the NPM package version just before publishing. In order to sign the final package.json file, we run sceau sign as the prepack lifecycle hook.

The private key is passed as an environment variable to the calling step (semantic-release).

Cryptography

Cryptography is provided by libsodium.

  • Hash: BLAKE2b, 64 byte output, no key, default parameters
  • Signature: Ed25519ph (SHA-512 pre-hash), with manifest header.

See signature.ts for more details.

About the name

Sceau is French for seal (the ones found on letters, not in oceans).

It's pronounced like so.

License

MIT - Made with ❤️ by François Best

Using this package at work ? Sponsor me to help with support and maintenance.