Files
mpc/cmd/mpcd/hsm_signer_config.go
zeekay 39fcdca875 mpcd: author missing buildHSMSignerConfig helper (refs #227)
Commit af4139d ("deployment-mode flag + audit dispatcher") referenced
buildHSMSignerConfig at the HSM signer wiring site but the helper file
was never staged — same pattern as the missing airgap files repaired
under #208. The current main.go works around the gap by passing nil to
hsm.NewSigner, which loses the operator-supplied --hsm-signer-key-id and
prevents per-provider config promotion.

cmd/mpcd/hsm_signer_config.go:
  - validates provider name against the luxfi/hsm v1.1.3 factory list
    (aws/gcp/azure, zymbit, yubihsm, nitrokey, pkcs11, kmip, coldcard,
    foundation, keystone, ngrave, gridplus/lattice, ledger, trezor,
    tr31, mldsa, local) and the documented aliases.
  - promotes --hsm-signer-key-id into the canonical config key for each
    cloud KMS provider (aws.key_id, gcp.crypto_key, azure.key_name).
  - leaves vendor-specific surface (slot/pin/library, vault URL,
    transport) to the factory's MPC_HSM_<P>_* env-var fallback so the
    CLI flag surface stays minimal.

cmd/mpcd/main.go: route --hsm-signer through the helper and surface
config errors before reaching hsm.NewSigner.

cmd/mpcd/hsm_signer_config_test.go: covers cloud-KMS key-id promotion,
hardware-wallet empty-map case, alias acceptance, case-insensitive
provider names, and rejection of empty + unknown providers. Helper
+ tests pass under `go test -race -count=1` in isolation.

go.sum: refresh github.com/luxfi/edwards25519 v0.1.0 hashes to match
proxy.golang.org. Pre-existing checksum drift blocked all `go build`
attempts on this branch and on main.

Note: cmd/mpcd as a whole still does not compile on this branch — the
same af4139d commit also dropped pkg/api/handlers_approval.go without
adding the Server.approval field, and pkg/policy/fhe_threshold_decryptor.go
references symbols that never landed. Both are out of scope for #227 and
will be repaired under follow-up tickets in the same #208-style cleanup.
2026-04-28 01:14:50 -07:00

131 lines
4.8 KiB
Go

// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main
import (
"fmt"
"strings"
"github.com/urfave/cli/v3"
)
// buildHSMSignerConfig assembles the per-provider configuration map that
// hsm.NewSigner consumes. The luxfi/hsm factory reads each provider's
// config keys from the supplied map first, then falls back to its
// documented MPC_HSM_<PROVIDER>_* environment variables. The job of this
// helper is therefore narrow:
//
// 1. Reject unknown provider names early — a typo at startup is much
// cheaper to surface here than as a "no signer wired" failure when
// the first co-signing intent arrives.
// 2. Promote the operator-facing CLI flags (--hsm-signer-key-id) into
// the canonical config keys for the providers that consume a single
// opaque key reference (cloud KMS).
// 3. Leave the remaining provider-specific knobs (slot/pin/library for
// PKCS#11, work-dir for airgap wallets, vault URL for Azure) to the
// factory's environment-variable fallback. We do NOT mint new CLI
// flags for every provider's tuning surface — there is one and only
// one place those values live, the documented MPC_HSM_<P>_* names.
//
// The returned map is always non-nil so the factory cannot observe an
// ambiguous "provider configured but no overrides" state.
func buildHSMSignerConfig(c *cli.Command, provider string) (map[string]string, error) {
provider = strings.TrimSpace(strings.ToLower(provider))
if provider == "" {
return nil, fmt.Errorf("hsm signer: provider not specified")
}
if !isSupportedHSMSignerProvider(provider) {
return nil, fmt.Errorf("hsm signer: unsupported provider %q (supported: %s)",
provider, strings.Join(supportedHSMSignerProviders(), ", "))
}
cfg := map[string]string{}
keyID := strings.TrimSpace(c.String("hsm-signer-key-id"))
switch provider {
case "aws":
// AWS KMS signer reads region from config or AWS_REGION; the key
// ARN is identified at sign-time via the operator-supplied
// hsm-signer-key-id. We pass it through under the canonical key
// name AWSKMSSigner consults.
if keyID != "" {
cfg["key_id"] = keyID
}
case "gcp":
// GCP KMS signer derives project/location/keyring/key from env
// (GCP_PROJECT_ID, GCP_KMS_LOCATION, GCP_KMS_KEYRING, GCP_KMS_KEY).
// hsm-signer-key-id, when supplied, becomes the crypto key name
// override.
if keyID != "" {
cfg["crypto_key"] = keyID
}
case "azure":
// Azure Key Vault signer reads vault_url from env (AZURE_VAULT_URL).
// hsm-signer-key-id maps to the key name within the vault.
if keyID != "" {
cfg["key_name"] = keyID
}
case "zymbit", "yubihsm", "yubico", "yubi", "nitrokey",
"coldcard", "foundation", "keystone", "ngrave",
"gridplus", "lattice", "ledger", "trezor",
"pkcs11", "kmip", "tr31",
"mldsa", "pq", "post-quantum",
"local":
// Each of these providers reads its full configuration surface
// from the documented MPC_HSM_<PROVIDER>_* environment variables.
// The factory handles env fallback per key, so we deliberately
// do not duplicate that surface as CLI flags. Operators wire
// vendor-specific knobs (slots, pins, library paths, vault URLs,
// transport implementations) through env vars provisioned out of
// band, typically via KMS-synced secrets.
default:
// isSupportedHSMSignerProvider guards this path; this is dead
// code retained as a defensive switch fall-through so the
// compiler proves exhaustiveness when the provider list grows.
return nil, fmt.Errorf("hsm signer: provider %q passed validation but has no config branch", provider)
}
return cfg, nil
}
// supportedHSMSignerProviders lists the canonical signer provider names
// accepted by luxfi/hsm.NewSigner. Kept in lockstep with that factory's
// switch — adding an entry here without a matching factory case is a
// configuration lie.
func supportedHSMSignerProviders() []string {
return []string{
"aws", "gcp", "azure",
"zymbit",
"yubihsm", "nitrokey",
"pkcs11", "kmip",
"coldcard", "foundation", "keystone", "ngrave",
"gridplus", "ledger", "trezor",
"tr31", "mldsa",
"local",
}
}
// isSupportedHSMSignerProvider returns true when the given provider name
// (already lower-cased + trimmed) is one luxfi/hsm.NewSigner will accept.
// Aliases honored by the factory (yubico/yubi for yubihsm, lattice for
// gridplus, pq/post-quantum for mldsa) are accepted here so the surface
// stays consistent between the helper and the factory.
func isSupportedHSMSignerProvider(provider string) bool {
switch provider {
case "aws", "gcp", "azure",
"zymbit",
"yubihsm", "yubico", "yubi",
"nitrokey",
"pkcs11", "kmip",
"coldcard", "foundation", "keystone", "ngrave",
"gridplus", "lattice",
"ledger", "trezor",
"tr31",
"mldsa", "pq", "post-quantum",
"local":
return true
}
return false
}