From 776ff31cae2ae8acf671023d22238eb8d472a5c5 Mon Sep 17 00:00:00 2001 From: Carl Tashian Date: Thu, 10 Sep 2026 09:13:44 -0700 Subject: [PATCH 1/4] Add CLAUDE.md agent guidance Document the package map, KMS backend registry and build tags (cgo and platform requirements per backend), TPM simulator testing, generated files, test/lint commands, and error and testing conventions for agents working in this repository. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01JuSZFSx1cTEnqY55oquacV --- CLAUDE.md | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..71aa0583 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,149 @@ +# CLAUDE.md + +This file provides guidance to Claude Code when working with code in this repository. + +## Overview + +`go.step.sm/crypto` is Smallstep's shared cryptography library (Apache-2.0). It is a +library only, no binaries: X.509 and SSH certificate templating, key generation and +PEM/JWK handling, a pluggable KMS abstraction (software, AWS, GCP, Azure, PKCS #11, +YubiKey, ssh-agent, TPM, macOS Keychain, Windows CAPI), and TPM 2.0 attestation. +[`step`](https://github.com/smallstep/cli) and [`step-ca`](https://github.com/smallstep/certificates) +are the main consumers; changes here ship to them by bumping the dependency, so keep +exported APIs backward compatible. `go.mod` requires Go 1.26; CI tests `stable` and +`oldstable`. + +## Commands + +```bash +make bootstrap # install golangci-lint, govulncheck, gotestsum into $(go env GOPATH)/bin +go build ./... # compiles everything, ~15s cold +make test # defaulttest + simulatortest + combined coverage.out (what CI runs) +make defaulttest # gotestsum ./... with coverage (~4000 tests, ~40s on a laptop) +make simulatortest # CGO_ENABLED=1, -tags tpmsimulator, ./tpm and ./kms/tpmkms only (~10s) +make race # gotestsum -race ./... +make fmt # goimports -local go.step.sm/crypto +make lint # golangci-lint (config curled from smallstep/workflows) + govulncheck +make generate # go generate ./... (only the azurekms mock) +``` + +Single test / single package: + +```bash +go test -run TestNewCertificate ./x509util/ +CGO_ENABLED=1 go test -tags tpmsimulator -run TestTPM_CreateAK ./tpm/ +``` + +`make test` and `make race` need `gotestsum` on `PATH`; `make lint` needs network +access to fetch `.golangci.yml`. Coverage output (`*.out`) is gitignored. There is no `go.work`, no submodules, and nothing in +the default test run needs cloud credentials or hardware. CI (`.github/workflows/ci.yml`) +calls the shared `smallstep/workflows` `goCI.yml` with `V=1 make test`, CodeQL, and +`libpcsclite-dev` installed for the cgo YubiKey backend. + +Regenerating the NSS column map: `make -C nssdb columns` (needs an NSS source +checkout inside `nssdb/generate/`, see `nssdb/README.md`). + +## Generated Code - Do Not Edit + +| Pattern | Generator | +|---------|-----------| +| `kms/azurekms/internal/mock/key_vault_client.go` | mockgen, `go:generate` directive in `kms/azurekms/key_vault_test.go` | +| `nssdb/columns.go` | `nssdb/generate/main.go` via `make -C nssdb columns` | + +## Package Map + +``` +crypto/ +├── x509util/ # X.509 certs/CSRs from JSON templates (DefaultLeafTemplate, etc.), name/extension helpers +├── sshutil/ # SSH certs from JSON templates, same template engine +├── keyutil/ # key generation (RSA/EC/Ed25519/X25519/ML-DSA), key type checks +├── pemutil/ # parse/serialize keys, certs, CSRs to PEM; PKCS #8, encrypted keys +├── jose/ # go-jose/v3 wrapper: JWT, JWK, JWKSet parse/generate/validate, JWE +├── randutil/ # random strings, salts +├── tlsutil/ # tls.Config helpers, renewers +├── fingerprint/ # X.509 / SSH fingerprint encodings +├── minica/ # tiny in-memory CA, handy for tests +├── x25519/ # X25519 keys + XEdDSA signatures +├── mldsa/ # temporary shim: alias of crypto/mldsa on go1.27, stubs below (remove at go1.27 floor) +├── fipsutil/ # reports FIPS 140-3 mode (Go-version build tags) +├── nssdb/ # write certs/keys into NSS sqlite databases (cert9.db/key4.db) +├── kms/ # KMS abstraction, see below +├── tpm/ # TPM 2.0 abstraction over go-tpm / go-attestation, see below +├── internal/ # darwin/ (cgo CoreFoundation+Security), templates/ (shared FuncMap), testutil/, utils/ +├── examples/ # cloudkms-attestation sample program +└── tools.go # pins go.uber.org/mock/mockgen for go generate +``` + +### kms + +`kms/apiv1` defines the interfaces (`KeyManager`, `CertificateManager`, +`Decrypter`, `Attester`, `SearchableKeyManager`, ...) plus `Options` and the request +types. Each backend registers a constructor in `init()` via `apiv1.Register(apiv1.Type, +fn)`; `kms.New(ctx, opts)` looks the type up from `opts.Type` or the URI scheme. +`kms/kms.go` only imports `softkms`, so a program must blank-import every backend it +wants (`_ "go.step.sm/crypto/kms/pkcs11"`). Backends are configured by URI +(`pkcs11:token=...?pin-value=...`, `tpmkms:device=/dev/tpmrm0`, `yubikey:pin-value=...`); +`kms/uri` parses them with `;`-separated opaque params and `?` query params. + +| Backend | URI scheme | Needs | Opt-out tag | +|---------|-----------|-------|-------------| +| `softkms` | `softkms:` | nothing (default, always linked) | - | +| `awskms` / `cloudkms` / `azurekms` | `awskms:` / `cloudkms:` / `azurekms:` | pure Go cloud SDKs | `noawskms` / `nocloudkms` / `noazurekms` | +| `sshagentkms` | `sshagentkms:` | `SSH_AUTH_SOCK` at runtime | `nosshagentkms` | +| `tpmkms` | `tpmkms:` | pure Go; a TPM at runtime | `notpmkms` | +| `pkcs11` | `pkcs11:` | **cgo** (crypto11 / miekg/pkcs11) | `nopkcs11` | +| `yubikey` | `yubikey:` | **cgo** (piv-go; `libpcsclite-dev` on Linux) | `noyubikey` | +| `mackms` | `mackms:` | **darwin && cgo** (CoreFoundation, Security frameworks) | `nomackms` | +| `capi` | `capi:` | **windows** only | `nocapi` | +| `platform` | `kms:` | dispatches to mackms (darwin), capi (windows), tpmkms (else); `backend=` overrides | - | + +When a backend is excluded by tag or platform, its `no_*.go` / `*_no_cgo.go` stub still +registers the type so `kms.New` returns a clear "not supported" error instead of +"unsupported kms type". Add both halves when adding a backend. + +### tpm + +`tpm.TPM` wraps `google/go-tpm` and `smallstep/go-attestation`: EK/AK/application key +lifecycle, info/caps, `tpm/storage` persistence (`dirstore`, `filestore`, `tpmstore`, +`feedthrough`, `blackhole`), `tpm/tss2` (TPM 2.0 key file ASN.1 per draft-bottomley), +`tpm/attestation` (HTTP client to an attestation CA), `tpm/skae` (subject key attestation +evidence extension), `tpm/simulator` (go-tpm-tools simulator, compiled only with +`-tags tpmsimulator`). Platform-specific device open/close/socket code lives in +`tpm/internal/`. `kms/tpmkms` is the KMS backend built on top. + +## Build Tags + +| Tag | Effect | +|-----|--------| +| `tpmsimulator` | compile the software TPM and simulator-backed tests (`tpm`, `kms/tpmkms`, `kms/platform`, `tpm/tss2`, `tpm/rand`, `tpm/attestation`); needs `CGO_ENABLED=1` | +| `tpm` | `tpm/tss2` tests against a real TPM device | +| `softhsm2` / `yubihsm2` / `opensc` | `kms/pkcs11` tests against that module (skipped if the `.so`/`.dylib` is not found) | +| `no` | drop a KMS backend, see table above | +| `go1.27`, `go1.24`, `go1.26` | ML-DSA (`crypto/mldsa`), FIPS reporting, and CSR signature-algorithm handling differ by toolchain; keep the `_go1XX.go` / `_stub.go` / `_other.go` pairs in sync | + +Tests that touch real resources in the default run: `kms/mackms` on macOS creates and +deletes keys in the login keychain; `kms/sshagentkms` spawns a real `ssh-agent` +(skipped with `-short`); `kms/yubikey` and `kms/pkcs11` compile under cgo but skip +without hardware. Cloud backend tests use fakes and never call out. + +## Conventions + +- **Errors**: mixed. Older packages (`x509util`, `sshutil`, `pemutil`, `jose`, `kms/uri`, + the cloud backends) use `github.com/pkg/errors` (`errors.Wrap`, `errors.Errorf`); + newer code (`tpm/`, `mackms`, `platform`, `mldsa`) uses `fmt.Errorf("...: %w", err)`. + Match the file you are editing; use `fmt.Errorf` with `%w` in new packages. + `apiv1.NotFoundError` / `apiv1.AlreadyExistsError` are the sentinel types backends return. +- **No logging framework**; this is a library. Do not add one. +- **Tests**: `testify` (`assert`/`require`), table-driven `t.Run` loops, fixtures under + each package's `testdata/`. `gomock` is used only for the Azure Key Vault client. + `minica` and `internal/testutil` are the shared helpers. +- **Templates**: `x509util` and `sshutil` render JSON via `text/template` with the + `internal/templates` FuncMap (sprig plus `toTime`, `formatTime`, etc.); the `fail` + function surfaces user-visible template errors. The `Default*Template` constants are + consumed verbatim by `step-ca` provisioners, so changing them is a behavior change. +- **Imports**: `goimports -local go.step.sm/crypto`; lint rules come from the shared + `smallstep/workflows/.golangci.yml`, not a file in this repo. +- **Releases**: pushing a `v*` tag runs CI and creates a GitHub release. Tag only from + `master`; `go.mod` retracts `v0.77.3` - `v0.77.7` because they were tagged from a branch. + Consumers pin by version, so local cross-repo work uses + `replace go.step.sm/crypto => ../crypto` in the consumer's `go.mod` (never commit it). From 3f9ed2f88c56570a2456dd75ab0ac4ffea4fff74 Mon Sep 17 00:00:00 2001 From: Carl Tashian Date: Thu, 10 Sep 2026 09:19:58 -0700 Subject: [PATCH 2/4] Move guidance to AGENTS.md, import it from CLAUDE.md AGENTS.md is the vendor-neutral file read by other coding agents. Claude Code does not read it natively, so CLAUDE.md becomes a one-line @AGENTS.md import that loads the same content. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01JuSZFSx1cTEnqY55oquacV --- AGENTS.md | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 150 +----------------------------------------------------- 2 files changed, 150 insertions(+), 149 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..4e628711 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,149 @@ +# AGENTS.md + +Guidance for AI coding agents working in this repository. Claude Code loads it through the one-line `@AGENTS.md` import in `CLAUDE.md`. + +## Overview + +`go.step.sm/crypto` is Smallstep's shared cryptography library (Apache-2.0). It is a +library only, no binaries: X.509 and SSH certificate templating, key generation and +PEM/JWK handling, a pluggable KMS abstraction (software, AWS, GCP, Azure, PKCS #11, +YubiKey, ssh-agent, TPM, macOS Keychain, Windows CAPI), and TPM 2.0 attestation. +[`step`](https://github.com/smallstep/cli) and [`step-ca`](https://github.com/smallstep/certificates) +are the main consumers; changes here ship to them by bumping the dependency, so keep +exported APIs backward compatible. `go.mod` requires Go 1.26; CI tests `stable` and +`oldstable`. + +## Commands + +```bash +make bootstrap # install golangci-lint, govulncheck, gotestsum into $(go env GOPATH)/bin +go build ./... # compiles everything, ~15s cold +make test # defaulttest + simulatortest + combined coverage.out (what CI runs) +make defaulttest # gotestsum ./... with coverage (~4000 tests, ~40s on a laptop) +make simulatortest # CGO_ENABLED=1, -tags tpmsimulator, ./tpm and ./kms/tpmkms only (~10s) +make race # gotestsum -race ./... +make fmt # goimports -local go.step.sm/crypto +make lint # golangci-lint (config curled from smallstep/workflows) + govulncheck +make generate # go generate ./... (only the azurekms mock) +``` + +Single test / single package: + +```bash +go test -run TestNewCertificate ./x509util/ +CGO_ENABLED=1 go test -tags tpmsimulator -run TestTPM_CreateAK ./tpm/ +``` + +`make test` and `make race` need `gotestsum` on `PATH`; `make lint` needs network +access to fetch `.golangci.yml`. Coverage output (`*.out`) is gitignored. There is no `go.work`, no submodules, and nothing in +the default test run needs cloud credentials or hardware. CI (`.github/workflows/ci.yml`) +calls the shared `smallstep/workflows` `goCI.yml` with `V=1 make test`, CodeQL, and +`libpcsclite-dev` installed for the cgo YubiKey backend. + +Regenerating the NSS column map: `make -C nssdb columns` (needs an NSS source +checkout inside `nssdb/generate/`, see `nssdb/README.md`). + +## Generated Code - Do Not Edit + +| Pattern | Generator | +|---------|-----------| +| `kms/azurekms/internal/mock/key_vault_client.go` | mockgen, `go:generate` directive in `kms/azurekms/key_vault_test.go` | +| `nssdb/columns.go` | `nssdb/generate/main.go` via `make -C nssdb columns` | + +## Package Map + +``` +crypto/ +├── x509util/ # X.509 certs/CSRs from JSON templates (DefaultLeafTemplate, etc.), name/extension helpers +├── sshutil/ # SSH certs from JSON templates, same template engine +├── keyutil/ # key generation (RSA/EC/Ed25519/X25519/ML-DSA), key type checks +├── pemutil/ # parse/serialize keys, certs, CSRs to PEM; PKCS #8, encrypted keys +├── jose/ # go-jose/v3 wrapper: JWT, JWK, JWKSet parse/generate/validate, JWE +├── randutil/ # random strings, salts +├── tlsutil/ # tls.Config helpers, renewers +├── fingerprint/ # X.509 / SSH fingerprint encodings +├── minica/ # tiny in-memory CA, handy for tests +├── x25519/ # X25519 keys + XEdDSA signatures +├── mldsa/ # temporary shim: alias of crypto/mldsa on go1.27, stubs below (remove at go1.27 floor) +├── fipsutil/ # reports FIPS 140-3 mode (Go-version build tags) +├── nssdb/ # write certs/keys into NSS sqlite databases (cert9.db/key4.db) +├── kms/ # KMS abstraction, see below +├── tpm/ # TPM 2.0 abstraction over go-tpm / go-attestation, see below +├── internal/ # darwin/ (cgo CoreFoundation+Security), templates/ (shared FuncMap), testutil/, utils/ +├── examples/ # cloudkms-attestation sample program +└── tools.go # pins go.uber.org/mock/mockgen for go generate +``` + +### kms + +`kms/apiv1` defines the interfaces (`KeyManager`, `CertificateManager`, +`Decrypter`, `Attester`, `SearchableKeyManager`, ...) plus `Options` and the request +types. Each backend registers a constructor in `init()` via `apiv1.Register(apiv1.Type, +fn)`; `kms.New(ctx, opts)` looks the type up from `opts.Type` or the URI scheme. +`kms/kms.go` only imports `softkms`, so a program must blank-import every backend it +wants (`_ "go.step.sm/crypto/kms/pkcs11"`). Backends are configured by URI +(`pkcs11:token=...?pin-value=...`, `tpmkms:device=/dev/tpmrm0`, `yubikey:pin-value=...`); +`kms/uri` parses them with `;`-separated opaque params and `?` query params. + +| Backend | URI scheme | Needs | Opt-out tag | +|---------|-----------|-------|-------------| +| `softkms` | `softkms:` | nothing (default, always linked) | - | +| `awskms` / `cloudkms` / `azurekms` | `awskms:` / `cloudkms:` / `azurekms:` | pure Go cloud SDKs | `noawskms` / `nocloudkms` / `noazurekms` | +| `sshagentkms` | `sshagentkms:` | `SSH_AUTH_SOCK` at runtime | `nosshagentkms` | +| `tpmkms` | `tpmkms:` | pure Go; a TPM at runtime | `notpmkms` | +| `pkcs11` | `pkcs11:` | **cgo** (crypto11 / miekg/pkcs11) | `nopkcs11` | +| `yubikey` | `yubikey:` | **cgo** (piv-go; `libpcsclite-dev` on Linux) | `noyubikey` | +| `mackms` | `mackms:` | **darwin && cgo** (CoreFoundation, Security frameworks) | `nomackms` | +| `capi` | `capi:` | **windows** only | `nocapi` | +| `platform` | `kms:` | dispatches to mackms (darwin), capi (windows), tpmkms (else); `backend=` overrides | - | + +When a backend is excluded by tag or platform, its `no_*.go` / `*_no_cgo.go` stub still +registers the type so `kms.New` returns a clear "not supported" error instead of +"unsupported kms type". Add both halves when adding a backend. + +### tpm + +`tpm.TPM` wraps `google/go-tpm` and `smallstep/go-attestation`: EK/AK/application key +lifecycle, info/caps, `tpm/storage` persistence (`dirstore`, `filestore`, `tpmstore`, +`feedthrough`, `blackhole`), `tpm/tss2` (TPM 2.0 key file ASN.1 per draft-bottomley), +`tpm/attestation` (HTTP client to an attestation CA), `tpm/skae` (subject key attestation +evidence extension), `tpm/simulator` (go-tpm-tools simulator, compiled only with +`-tags tpmsimulator`). Platform-specific device open/close/socket code lives in +`tpm/internal/`. `kms/tpmkms` is the KMS backend built on top. + +## Build Tags + +| Tag | Effect | +|-----|--------| +| `tpmsimulator` | compile the software TPM and simulator-backed tests (`tpm`, `kms/tpmkms`, `kms/platform`, `tpm/tss2`, `tpm/rand`, `tpm/attestation`); needs `CGO_ENABLED=1` | +| `tpm` | `tpm/tss2` tests against a real TPM device | +| `softhsm2` / `yubihsm2` / `opensc` | `kms/pkcs11` tests against that module (skipped if the `.so`/`.dylib` is not found) | +| `no` | drop a KMS backend, see table above | +| `go1.27`, `go1.24`, `go1.26` | ML-DSA (`crypto/mldsa`), FIPS reporting, and CSR signature-algorithm handling differ by toolchain; keep the `_go1XX.go` / `_stub.go` / `_other.go` pairs in sync | + +Tests that touch real resources in the default run: `kms/mackms` on macOS creates and +deletes keys in the login keychain; `kms/sshagentkms` spawns a real `ssh-agent` +(skipped with `-short`); `kms/yubikey` and `kms/pkcs11` compile under cgo but skip +without hardware. Cloud backend tests use fakes and never call out. + +## Conventions + +- **Errors**: mixed. Older packages (`x509util`, `sshutil`, `pemutil`, `jose`, `kms/uri`, + the cloud backends) use `github.com/pkg/errors` (`errors.Wrap`, `errors.Errorf`); + newer code (`tpm/`, `mackms`, `platform`, `mldsa`) uses `fmt.Errorf("...: %w", err)`. + Match the file you are editing; use `fmt.Errorf` with `%w` in new packages. + `apiv1.NotFoundError` / `apiv1.AlreadyExistsError` are the sentinel types backends return. +- **No logging framework**; this is a library. Do not add one. +- **Tests**: `testify` (`assert`/`require`), table-driven `t.Run` loops, fixtures under + each package's `testdata/`. `gomock` is used only for the Azure Key Vault client. + `minica` and `internal/testutil` are the shared helpers. +- **Templates**: `x509util` and `sshutil` render JSON via `text/template` with the + `internal/templates` FuncMap (sprig plus `toTime`, `formatTime`, etc.); the `fail` + function surfaces user-visible template errors. The `Default*Template` constants are + consumed verbatim by `step-ca` provisioners, so changing them is a behavior change. +- **Imports**: `goimports -local go.step.sm/crypto`; lint rules come from the shared + `smallstep/workflows/.golangci.yml`, not a file in this repo. +- **Releases**: pushing a `v*` tag runs CI and creates a GitHub release. Tag only from + `master`; `go.mod` retracts `v0.77.3` - `v0.77.7` because they were tagged from a branch. + Consumers pin by version, so local cross-repo work uses + `replace go.step.sm/crypto => ../crypto` in the consumer's `go.mod` (never commit it). diff --git a/CLAUDE.md b/CLAUDE.md index 71aa0583..43c994c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,149 +1 @@ -# CLAUDE.md - -This file provides guidance to Claude Code when working with code in this repository. - -## Overview - -`go.step.sm/crypto` is Smallstep's shared cryptography library (Apache-2.0). It is a -library only, no binaries: X.509 and SSH certificate templating, key generation and -PEM/JWK handling, a pluggable KMS abstraction (software, AWS, GCP, Azure, PKCS #11, -YubiKey, ssh-agent, TPM, macOS Keychain, Windows CAPI), and TPM 2.0 attestation. -[`step`](https://github.com/smallstep/cli) and [`step-ca`](https://github.com/smallstep/certificates) -are the main consumers; changes here ship to them by bumping the dependency, so keep -exported APIs backward compatible. `go.mod` requires Go 1.26; CI tests `stable` and -`oldstable`. - -## Commands - -```bash -make bootstrap # install golangci-lint, govulncheck, gotestsum into $(go env GOPATH)/bin -go build ./... # compiles everything, ~15s cold -make test # defaulttest + simulatortest + combined coverage.out (what CI runs) -make defaulttest # gotestsum ./... with coverage (~4000 tests, ~40s on a laptop) -make simulatortest # CGO_ENABLED=1, -tags tpmsimulator, ./tpm and ./kms/tpmkms only (~10s) -make race # gotestsum -race ./... -make fmt # goimports -local go.step.sm/crypto -make lint # golangci-lint (config curled from smallstep/workflows) + govulncheck -make generate # go generate ./... (only the azurekms mock) -``` - -Single test / single package: - -```bash -go test -run TestNewCertificate ./x509util/ -CGO_ENABLED=1 go test -tags tpmsimulator -run TestTPM_CreateAK ./tpm/ -``` - -`make test` and `make race` need `gotestsum` on `PATH`; `make lint` needs network -access to fetch `.golangci.yml`. Coverage output (`*.out`) is gitignored. There is no `go.work`, no submodules, and nothing in -the default test run needs cloud credentials or hardware. CI (`.github/workflows/ci.yml`) -calls the shared `smallstep/workflows` `goCI.yml` with `V=1 make test`, CodeQL, and -`libpcsclite-dev` installed for the cgo YubiKey backend. - -Regenerating the NSS column map: `make -C nssdb columns` (needs an NSS source -checkout inside `nssdb/generate/`, see `nssdb/README.md`). - -## Generated Code - Do Not Edit - -| Pattern | Generator | -|---------|-----------| -| `kms/azurekms/internal/mock/key_vault_client.go` | mockgen, `go:generate` directive in `kms/azurekms/key_vault_test.go` | -| `nssdb/columns.go` | `nssdb/generate/main.go` via `make -C nssdb columns` | - -## Package Map - -``` -crypto/ -├── x509util/ # X.509 certs/CSRs from JSON templates (DefaultLeafTemplate, etc.), name/extension helpers -├── sshutil/ # SSH certs from JSON templates, same template engine -├── keyutil/ # key generation (RSA/EC/Ed25519/X25519/ML-DSA), key type checks -├── pemutil/ # parse/serialize keys, certs, CSRs to PEM; PKCS #8, encrypted keys -├── jose/ # go-jose/v3 wrapper: JWT, JWK, JWKSet parse/generate/validate, JWE -├── randutil/ # random strings, salts -├── tlsutil/ # tls.Config helpers, renewers -├── fingerprint/ # X.509 / SSH fingerprint encodings -├── minica/ # tiny in-memory CA, handy for tests -├── x25519/ # X25519 keys + XEdDSA signatures -├── mldsa/ # temporary shim: alias of crypto/mldsa on go1.27, stubs below (remove at go1.27 floor) -├── fipsutil/ # reports FIPS 140-3 mode (Go-version build tags) -├── nssdb/ # write certs/keys into NSS sqlite databases (cert9.db/key4.db) -├── kms/ # KMS abstraction, see below -├── tpm/ # TPM 2.0 abstraction over go-tpm / go-attestation, see below -├── internal/ # darwin/ (cgo CoreFoundation+Security), templates/ (shared FuncMap), testutil/, utils/ -├── examples/ # cloudkms-attestation sample program -└── tools.go # pins go.uber.org/mock/mockgen for go generate -``` - -### kms - -`kms/apiv1` defines the interfaces (`KeyManager`, `CertificateManager`, -`Decrypter`, `Attester`, `SearchableKeyManager`, ...) plus `Options` and the request -types. Each backend registers a constructor in `init()` via `apiv1.Register(apiv1.Type, -fn)`; `kms.New(ctx, opts)` looks the type up from `opts.Type` or the URI scheme. -`kms/kms.go` only imports `softkms`, so a program must blank-import every backend it -wants (`_ "go.step.sm/crypto/kms/pkcs11"`). Backends are configured by URI -(`pkcs11:token=...?pin-value=...`, `tpmkms:device=/dev/tpmrm0`, `yubikey:pin-value=...`); -`kms/uri` parses them with `;`-separated opaque params and `?` query params. - -| Backend | URI scheme | Needs | Opt-out tag | -|---------|-----------|-------|-------------| -| `softkms` | `softkms:` | nothing (default, always linked) | - | -| `awskms` / `cloudkms` / `azurekms` | `awskms:` / `cloudkms:` / `azurekms:` | pure Go cloud SDKs | `noawskms` / `nocloudkms` / `noazurekms` | -| `sshagentkms` | `sshagentkms:` | `SSH_AUTH_SOCK` at runtime | `nosshagentkms` | -| `tpmkms` | `tpmkms:` | pure Go; a TPM at runtime | `notpmkms` | -| `pkcs11` | `pkcs11:` | **cgo** (crypto11 / miekg/pkcs11) | `nopkcs11` | -| `yubikey` | `yubikey:` | **cgo** (piv-go; `libpcsclite-dev` on Linux) | `noyubikey` | -| `mackms` | `mackms:` | **darwin && cgo** (CoreFoundation, Security frameworks) | `nomackms` | -| `capi` | `capi:` | **windows** only | `nocapi` | -| `platform` | `kms:` | dispatches to mackms (darwin), capi (windows), tpmkms (else); `backend=` overrides | - | - -When a backend is excluded by tag or platform, its `no_*.go` / `*_no_cgo.go` stub still -registers the type so `kms.New` returns a clear "not supported" error instead of -"unsupported kms type". Add both halves when adding a backend. - -### tpm - -`tpm.TPM` wraps `google/go-tpm` and `smallstep/go-attestation`: EK/AK/application key -lifecycle, info/caps, `tpm/storage` persistence (`dirstore`, `filestore`, `tpmstore`, -`feedthrough`, `blackhole`), `tpm/tss2` (TPM 2.0 key file ASN.1 per draft-bottomley), -`tpm/attestation` (HTTP client to an attestation CA), `tpm/skae` (subject key attestation -evidence extension), `tpm/simulator` (go-tpm-tools simulator, compiled only with -`-tags tpmsimulator`). Platform-specific device open/close/socket code lives in -`tpm/internal/`. `kms/tpmkms` is the KMS backend built on top. - -## Build Tags - -| Tag | Effect | -|-----|--------| -| `tpmsimulator` | compile the software TPM and simulator-backed tests (`tpm`, `kms/tpmkms`, `kms/platform`, `tpm/tss2`, `tpm/rand`, `tpm/attestation`); needs `CGO_ENABLED=1` | -| `tpm` | `tpm/tss2` tests against a real TPM device | -| `softhsm2` / `yubihsm2` / `opensc` | `kms/pkcs11` tests against that module (skipped if the `.so`/`.dylib` is not found) | -| `no` | drop a KMS backend, see table above | -| `go1.27`, `go1.24`, `go1.26` | ML-DSA (`crypto/mldsa`), FIPS reporting, and CSR signature-algorithm handling differ by toolchain; keep the `_go1XX.go` / `_stub.go` / `_other.go` pairs in sync | - -Tests that touch real resources in the default run: `kms/mackms` on macOS creates and -deletes keys in the login keychain; `kms/sshagentkms` spawns a real `ssh-agent` -(skipped with `-short`); `kms/yubikey` and `kms/pkcs11` compile under cgo but skip -without hardware. Cloud backend tests use fakes and never call out. - -## Conventions - -- **Errors**: mixed. Older packages (`x509util`, `sshutil`, `pemutil`, `jose`, `kms/uri`, - the cloud backends) use `github.com/pkg/errors` (`errors.Wrap`, `errors.Errorf`); - newer code (`tpm/`, `mackms`, `platform`, `mldsa`) uses `fmt.Errorf("...: %w", err)`. - Match the file you are editing; use `fmt.Errorf` with `%w` in new packages. - `apiv1.NotFoundError` / `apiv1.AlreadyExistsError` are the sentinel types backends return. -- **No logging framework**; this is a library. Do not add one. -- **Tests**: `testify` (`assert`/`require`), table-driven `t.Run` loops, fixtures under - each package's `testdata/`. `gomock` is used only for the Azure Key Vault client. - `minica` and `internal/testutil` are the shared helpers. -- **Templates**: `x509util` and `sshutil` render JSON via `text/template` with the - `internal/templates` FuncMap (sprig plus `toTime`, `formatTime`, etc.); the `fail` - function surfaces user-visible template errors. The `Default*Template` constants are - consumed verbatim by `step-ca` provisioners, so changing them is a behavior change. -- **Imports**: `goimports -local go.step.sm/crypto`; lint rules come from the shared - `smallstep/workflows/.golangci.yml`, not a file in this repo. -- **Releases**: pushing a `v*` tag runs CI and creates a GitHub release. Tag only from - `master`; `go.mod` retracts `v0.77.3` - `v0.77.7` because they were tagged from a branch. - Consumers pin by version, so local cross-repo work uses - `replace go.step.sm/crypto => ../crypto` in the consumer's `go.mod` (never commit it). +@AGENTS.md From bb1d94a29fe7408a08fa5dacd21008066a7402ee Mon Sep 17 00:00:00 2001 From: Carl Tashian Date: Thu, 10 Sep 2026 09:39:23 -0700 Subject: [PATCH 3/4] Move Claude Code stub to .claude/CLAUDE.md Per review: keep the repo root to AGENTS.md and put the Claude Code import stub under .claude/, importing @../AGENTS.md. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01JuSZFSx1cTEnqY55oquacV --- .claude/CLAUDE.md | 1 + AGENTS.md | 2 +- CLAUDE.md | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 .claude/CLAUDE.md delete mode 100644 CLAUDE.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 00000000..dba71e97 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +@../AGENTS.md diff --git a/AGENTS.md b/AGENTS.md index 4e628711..9f94b14f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md -Guidance for AI coding agents working in this repository. Claude Code loads it through the one-line `@AGENTS.md` import in `CLAUDE.md`. +Guidance for AI coding agents working in this repository. Claude Code loads it through the one-line `@../AGENTS.md` import in `.claude/CLAUDE.md`. ## Overview diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 43c994c2..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md From 1648762afc9d48cb2b850620bc7aa0e7b2218c7b Mon Sep 17 00:00:00 2001 From: Carl Tashian Date: Tue, 15 Sep 2026 09:48:04 -0700 Subject: [PATCH 4/4] Update tooling notes for go tool (crypto#1131) and review feedback Tools now come from go.mod tool directives, so drop the bootstrap step and route commands through `go tool`; name step-kms-plugin as a consumer; define stable/oldstable. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01RKCkbjJakXqQXEyeCQcVjf --- AGENTS.md | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9f94b14f..3f882af2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,22 +8,22 @@ Guidance for AI coding agents working in this repository. Claude Code loads it t library only, no binaries: X.509 and SSH certificate templating, key generation and PEM/JWK handling, a pluggable KMS abstraction (software, AWS, GCP, Azure, PKCS #11, YubiKey, ssh-agent, TPM, macOS Keychain, Windows CAPI), and TPM 2.0 attestation. -[`step`](https://github.com/smallstep/cli) and [`step-ca`](https://github.com/smallstep/certificates) -are the main consumers; changes here ship to them by bumping the dependency, so keep -exported APIs backward compatible. `go.mod` requires Go 1.26; CI tests `stable` and -`oldstable`. +[`step`](https://github.com/smallstep/cli), [`step-ca`](https://github.com/smallstep/certificates), +and [`step-kms-plugin`](https://github.com/smallstep/step-kms-plugin) are the main +consumers; changes here ship to them by bumping the dependency, so keep exported APIs +backward compatible. `go.mod` requires Go 1.26; CI tests on `stable` (the newest Go +release) and `oldstable` (the previous minor release), so code must build on both. ## Commands ```bash -make bootstrap # install golangci-lint, govulncheck, gotestsum into $(go env GOPATH)/bin go build ./... # compiles everything, ~15s cold make test # defaulttest + simulatortest + combined coverage.out (what CI runs) -make defaulttest # gotestsum ./... with coverage (~4000 tests, ~40s on a laptop) +make defaulttest # go tool gotestsum ./... with coverage (~4000 tests, ~40s on a laptop) make simulatortest # CGO_ENABLED=1, -tags tpmsimulator, ./tpm and ./kms/tpmkms only (~10s) -make race # gotestsum -race ./... -make fmt # goimports -local go.step.sm/crypto -make lint # golangci-lint (config curled from smallstep/workflows) + govulncheck +make race # go tool gotestsum -race ./... +make fmt # go tool goimports -local go.step.sm/crypto +make lint # go tool golangci-lint (config curled from smallstep/workflows) + go tool govulncheck make generate # go generate ./... (only the azurekms mock) ``` @@ -34,9 +34,12 @@ go test -run TestNewCertificate ./x509util/ CGO_ENABLED=1 go test -tags tpmsimulator -run TestTPM_CreateAK ./tpm/ ``` -`make test` and `make race` need `gotestsum` on `PATH`; `make lint` needs network -access to fetch `.golangci.yml`. Coverage output (`*.out`) is gitignored. There is no `go.work`, no submodules, and nothing in -the default test run needs cloud credentials or hardware. CI (`.github/workflows/ci.yml`) +Tooling (`golangci-lint`, `goimports`, `govulncheck`, `gotestsum`) comes from the +`tool` block in `go.mod` and runs through `go tool`; there is nothing to install and +`make bootstrap` is a no-op, but the first invocation needs network access to download +the tool modules, and `make lint` always needs network to fetch `.golangci.yml`. +Coverage output (`*.out`) is gitignored. There is no `go.work`, no submodules, and +nothing in the default test run needs cloud credentials or hardware. CI (`.github/workflows/ci.yml`) calls the shared `smallstep/workflows` `goCI.yml` with `V=1 make test`, CodeQL, and `libpcsclite-dev` installed for the cgo YubiKey backend. @@ -141,8 +144,8 @@ without hardware. Cloud backend tests use fakes and never call out. `internal/templates` FuncMap (sprig plus `toTime`, `formatTime`, etc.); the `fail` function surfaces user-visible template errors. The `Default*Template` constants are consumed verbatim by `step-ca` provisioners, so changing them is a behavior change. -- **Imports**: `goimports -local go.step.sm/crypto`; lint rules come from the shared - `smallstep/workflows/.golangci.yml`, not a file in this repo. +- **Imports**: `make fmt` (`go tool goimports -local go.step.sm/crypto`); lint rules + come from the shared `smallstep/workflows/.golangci.yml`, not a file in this repo. - **Releases**: pushing a `v*` tag runs CI and creates a GitHub release. Tag only from `master`; `go.mod` retracts `v0.77.3` - `v0.77.7` because they were tagged from a branch. Consumers pin by version, so local cross-repo work uses