identity

package
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package identity provides the local CA, agent keys, and SVID issuance.

This package defines narrow interfaces for KeyStore and IdentityIssuer, along with an in-memory fake implementation for testing. The KeyStore manages distinct local identities:

  • Local CA key
  • Daemon audit signing key
  • Per-agent package identity keys
  • Per-run workload key/cert

The IdentityIssuer issues SPIFFE-style workload certificates; real CA logic is implemented in a later task (B3-T03).

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrKeyNotFound      = errors.New("key not found")
	ErrKeyAlreadyExists = errors.New("key already exists")
	ErrWrongKeyType     = errors.New("operation not supported for this key type")
)

Sentinel errors returned by KeyStore implementations.

View Source
var (
	// ErrNoPublisherIdentity is returned when no publisher identity exists
	// in the keystore.
	ErrNoPublisherIdentity = errors.New("no publisher identity configured")

	// ErrPublisherIdentityExists is returned when attempting to create a
	// publisher identity that already exists.
	ErrPublisherIdentityExists = errors.New("publisher identity already exists")

	// ErrInvalidPublisherName is returned when a publisher name fails
	// validation.
	ErrInvalidPublisherName = errors.New("invalid publisher name")
)

Sentinel errors returned by publisher identity operations.

View Source
var ErrInvalidComponent = errors.New("invalid URI component")

ErrInvalidComponent is returned when a URI component contains path traversal characters or path separators.

View Source
var ErrInvalidKeyID = errors.New("invalid key ID")

ErrInvalidKeyID is returned when a KeyID fails validation. Implementations must reject any KeyID that does not satisfy the constraints enforced by ValidateKeyID.

View Source
var ErrKeychainLocked = errors.New("macOS keychain is locked or unavailable")

ErrKeychainLocked is returned when a security(1) command fails because the keychain is locked or the user is not authenticated. The caller should prompt the user to unlock the keychain and retry. There is NO silent plaintext fallback.

View Source
var ErrSymlinkDetected = errors.New("keystore file is a symlink; refusing to read/write through symlink")

ErrSymlinkDetected is returned when the keystore file is a symlink. Reading or writing through a symlink is refused to prevent TOCTOU attacks where an attacker replaces the keystore with a symlink to an arbitrary path.

View Source
var ErrWeakPermissions = errors.New("keystore file has weak permissions; expected 0600 or 0400")

ErrWeakPermissions is returned when the keystore file has permissions weaker than 0600 (e.g., world-readable 0644 or 0755).

View Source
var ErrWrongPassphrase = errors.New("wrong passphrase or corrupted keystore file")

ErrWrongPassphrase is returned when the passphrase does not decrypt the keystore file correctly (authentication tag mismatch).

Functions

func FileKeyStoreInUseWarning

func FileKeyStoreInUseWarning() string

FileKeyStoreInUseWarning returns a warning message suitable for the doctor subsystem. It should be called by the doctor when it detects that the file keystore is in use (i.e., as a P1-approved but non-ideal fallback).

func FingerprintPublicKey

func FingerprintPublicKey(pub *x509.Certificate) string

FingerprintPublicKey computes a SHA-256 fingerprint of the DER-encoded public key bytes and returns it as a hex-encoded string with colons every two characters (similar to SSH key fingerprint format).

func FormatFingerprintDisplay added in v0.2.0

func FormatFingerprintDisplay(fp string) string

FormatFingerprintDisplay formats a bare 64-hex fingerprint into display form: groups of 4 characters separated by spaces (16 groups of 4).

func LoadPublisherSigningKey added in v0.2.0

func LoadPublisherSigningKey(ks KeyStore) (*ecdsa.PrivateKey, error)

LoadPublisherSigningKey loads the publisher ECDSA private key for bundle manifest signing. Callers must not log or persist the returned key.

func ParseFingerprintDisplay added in v0.2.0

func ParseFingerprintDisplay(s string) string

ParseFingerprintDisplay removes all whitespace from a fingerprint string, converting display form back to bare 64-hex storage form. It also accepts bare hex (no spaces), returning it unchanged.

func ParseURI

func ParseURI(uri string) (agentName, agentVersion, runID string, err error)

ParseURI parses a SPIFFE URI of the form spiffe://<host>[/<tenant>]/agent/<name>/<ver>/run/<run_id> and returns the agent name, version, and run ID. It accepts both local and hosted formats.

func PublisherFingerprint added in v0.2.0

func PublisherFingerprint(pub *ecdsa.PublicKey) string

PublisherFingerprint computes the SHA-256 fingerprint of an ECDSA public key as hex(sha256(DER-encoded SPKI)). This is the SAME computation as pack.PublicKeyFingerprint — it is redefined here in the identity package so that publisher identity operations do not need to import pack. Both produce the same 64-char lowercase hex string for the same key.

func SignAsPublisher added in v0.2.0

func SignAsPublisher(ks KeyStore, digest []byte) ([]byte, error)

SignAsPublisher signs the given digest using the publisher identity key stored in the keystore. The private key never leaves the keystore. Returns the ASN.1-encoded ECDSA signature. Returns ErrNoPublisherIdentity when no identity exists.

func ValidateKeyID

func ValidateKeyID(id KeyID) error

ValidateKeyID validates a KeyID against the KeyStore contract to prevent path-traversal, injection, and resource-exhaustion attacks in file-backed and DB-backed keystores (T02+). The following constraints are enforced:

  • Non-empty
  • Max 128 characters
  • Allowed charset: [a-zA-Z0-9._-] only (no path separators, no unicode, no control chars, no spaces)
  • Must not be "." or ".."

Returns ErrInvalidKeyID with an actionable message naming the violated rule.

func ValidatePublisherName added in v0.2.0

func ValidatePublisherName(name string) error

ValidatePublisherName checks that name satisfies the publisher naming rules: 1-39 characters, lowercase alphanumeric and hyphens, must start and end with alphanumeric (GitHub-style slug). The name is a display label, NOT an identity — the fingerprint is the identity.

func ValidateURIComponent

func ValidateURIComponent(component, name string) error

ValidateURIComponent validates that a URI component does not contain path traversal characters ("..") or path separators ("/"). It returns ErrInvalidComponent if the component is invalid.

func VerifyURI

func VerifyURI(uri, expectedAgentName, expectedAgentVersion string) error

VerifyURI checks that the SPIFFE URI matches the expected agent name and version. It returns nil if the URI is valid and the identity matches.

func VerifyWorkloadCert

func VerifyWorkloadCert(cert *x509.Certificate) error

VerifyWorkloadCert validates an x509 workload certificate. It checks that the certificate is currently valid (not expired, not yet active). This function does NOT verify the CA signature chain — that is done separately by the LocalCA's cert pool.

Types

type FakeKeyStore

type FakeKeyStore struct {
	// contains filtered or unexported fields
}

FakeKeyStore is an in-memory implementation of KeyStore for testing. It stores key material in a map protected by a read-write mutex.

Sign and Verify use the Go standard library's crypto/ecdsa with P-256. Workload keys are rejected for Sign operations with ErrWrongKeyType.

func NewFakeKeyStore

func NewFakeKeyStore() *FakeKeyStore

NewFakeKeyStore returns a ready-to-use FakeKeyStore.

func (*FakeKeyStore) Create

func (f *FakeKeyStore) Create(id KeyID, kt KeyType, material KeyMaterial) error

Create stores the key material under the given ID and type. It returns ErrKeyAlreadyExists if a key with that ID already exists, or ErrInvalidKeyID if the ID fails validation.

func (*FakeKeyStore) Delete

func (f *FakeKeyStore) Delete(id KeyID) error

Delete removes the key with the given ID. It returns ErrKeyNotFound if the key does not exist, or ErrInvalidKeyID if the ID fails validation.

func (*FakeKeyStore) List

func (f *FakeKeyStore) List() ([]KeyMetadata, error)

List returns metadata for all stored keys. The returned KeyMetadata entries never contain raw key material (RawBytes is always nil).

func (*FakeKeyStore) Load

func (f *FakeKeyStore) Load(id KeyID) (KeyMaterial, error)

Load retrieves the key material for the given ID. It returns ErrKeyNotFound if the key does not exist, or ErrInvalidKeyID if the ID fails validation.

func (*FakeKeyStore) Sign

func (f *FakeKeyStore) Sign(id KeyID, digest []byte) ([]byte, error)

Sign computes an ECDSA signature over the given digest using the key identified by id. Only signing key types (CA, AuditSigning, PackageIdentity) are supported; Workload keys return ErrWrongKeyType. Returns ErrKeyNotFound if the key does not exist, or ErrInvalidKeyID if the ID fails validation.

func (*FakeKeyStore) Verify

func (f *FakeKeyStore) Verify(id KeyID, digest []byte, signature []byte) bool

Verify checks an ECDSA signature over the given digest against the key identified by id. It returns false if the key is not found, is a non-signing key type, or if the ID fails validation.

type FileKeyStore

type FileKeyStore struct {
	// contains filtered or unexported fields
}

FileKeyStore is an encrypted, passphrase-protected KeyStore implementation that stores keys in a single JSON file on disk. The file is encrypted with AES-256-GCM and the encryption key is derived from a passphrase via PBKDF2-HMAC-SHA256.

Permission rule: the store file must be 0600 or 0400. Weaker permissions cause the store to refuse loading with ErrWeakPermissions.

WARNING: FileKeyStore is a P1-approved fallback for environments where the macOS Keychain is unavailable. It is NOT a plaintext fallback — all data is encrypted at rest.

func NewFileKeyStore

func NewFileKeyStore(dir, passphrase string) (*FileKeyStore, error)

NewFileKeyStore opens or creates an encrypted file keystore at the given directory. If the store file already exists, it is decrypted and loaded. If the directory does not exist, it is created. The passphrase is used to derive the encryption key.

The store file is created with 0600 permissions. If an existing file has permissions weaker than 0600 (e.g., 0644 or 0755), ErrWeakPermissions is returned — no data is loaded.

If the existing file cannot be decrypted (wrong passphrase or corruption), ErrWrongPassphrase is returned — no data is loaded.

func (*FileKeyStore) Close

func (f *FileKeyStore) Close() error

Close synchronises the in-memory state to disk and releases resources. After Close, the store must not be used.

func (*FileKeyStore) Create

func (f *FileKeyStore) Create(id KeyID, kt KeyType, material KeyMaterial) error

Create stores key material under the given ID and type. It returns ErrKeyAlreadyExists if a key with that ID already exists, or ErrInvalidKeyID if the ID fails validation.

func (*FileKeyStore) Delete

func (f *FileKeyStore) Delete(id KeyID) error

Delete removes the key with the given ID. Returns ErrKeyNotFound if the key does not exist, or ErrInvalidKeyID if the ID fails validation.

func (*FileKeyStore) List

func (f *FileKeyStore) List() ([]KeyMetadata, error)

List returns metadata for all stored keys. The returned KeyMetadata entries never contain raw key material (RawBytes is always nil).

func (*FileKeyStore) Load

func (f *FileKeyStore) Load(id KeyID) (KeyMaterial, error)

Load retrieves key material for the given ID. Returns ErrKeyNotFound if the key does not exist, or ErrInvalidKeyID if the ID fails validation.

func (*FileKeyStore) Sign

func (f *FileKeyStore) Sign(id KeyID, digest []byte) ([]byte, error)

Sign computes an ECDSA signature over the given digest using the key identified by id. Only signing key types (CA, AuditSigning, PackageIdentity) are supported; Workload keys return ErrWrongKeyType.

func (*FileKeyStore) Verify

func (f *FileKeyStore) Verify(id KeyID, digest []byte, signature []byte) bool

Verify checks an ECDSA signature over the given digest against the key identified by id. Returns false if the key is not found, is a non-signing key type, or if the ID fails validation.

type IdentityIssuer

type IdentityIssuer interface {
	// IssueWorkloadCert generates a workload certificate and key for the
	// given agent run. It returns the PEM-encoded certificate, PEM-encoded
	// private key, and the SPIFFE URI for the identity.
	IssueWorkloadCert(agentName, agentVersion, runID string, ttl time.Duration) (certPEM, keyPEM []byte, spiffeURI string, err error)
}

IdentityIssuer issues SPIFFE-style workload certificates. The full SPIFFE URI builder and verifier is implemented in B3-T03; this interface is defined now so that consumers can depend on it.

type KeyID

type KeyID string

KeyID is a unique identifier for a key stored in the KeyStore.

func PublisherSigningKeyID added in v0.2.0

func PublisherSigningKeyID() KeyID

PublisherSigningKeyID returns the keystore key ID for the publisher identity.

type KeyMaterial

type KeyMaterial struct {
	Type  KeyType
	Bytes []byte
}

KeyMaterial holds the raw cryptographic material for a stored key. For signing keys (CA, AuditSigning, PackageIdentity) Bytes contains PEM-encoded ECDSA P-256 private key bytes. For workload keys Bytes contains the concatenation of the PEM-encoded certificate and PEM-encoded private key.

type KeyMetadata

type KeyMetadata struct {
	ID        KeyID     `json:"id"`
	Type      KeyType   `json:"type"`
	CreatedAt time.Time `json:"created_at"`
	// RawBytes is never populated by List; it exists only so contract tests
	// can verify no raw material leaks through the metadata API.
	RawBytes []byte `json:"-"`
}

KeyMetadata holds non-sensitive information about a stored key returned by List. It never contains raw key material, prefixes, or hashes.

type KeyStore

type KeyStore interface {
	Create(id KeyID, kt KeyType, material KeyMaterial) error
	Load(id KeyID) (KeyMaterial, error)
	Sign(id KeyID, digest []byte) ([]byte, error)
	Verify(id KeyID, digest []byte, signature []byte) bool
	Delete(id KeyID) error
	List() ([]KeyMetadata, error)
}

KeyStore is a narrow interface for storing, loading, and using cryptographic keys. Implementations must never expose raw key material through the List() method.

Operations:

  • Create stores key material under the given ID and type.
  • Load retrieves key material by ID.
  • Sign computes a signature over the given digest using the key's private key (only valid for signing key types).
  • Verify checks a signature against the given digest using the key's public key (only valid for signing key types).
  • Delete removes a key by ID.
  • List returns metadata for all stored keys, never raw material.

type KeyType

type KeyType string

KeyType represents the purpose/type of a cryptographic key.

const (
	// KeyTypeCA is the local certificate authority signing key. It is an
	// ECDSA P-256 key used to sign workload certificates.
	KeyTypeCA KeyType = "ca"

	// KeyTypeAuditSigning is the daemon audit signing key. It is an ECDSA
	// P-256 key used to sign audit checkpoint records.
	KeyTypeAuditSigning KeyType = "audit_signing"

	// KeyTypePackageIdentity is a per-agent package identity key. It is an
	// ECDSA P-256 key used to sign package manifests.
	KeyTypePackageIdentity KeyType = "package_identity"

	// KeyTypeWorkload is a per-run workload key/certificate pair. It
	// contains an x509 certificate and associated private key, used for
	// mTLS between gateway/harness and the daemon.
	KeyTypeWorkload KeyType = "workload"

	// KeyTypePublisher is the publisher identity signing key. It is an
	// ECDSA P-256 key used to sign shared agent bundles for distribution.
	// Unlike package identity keys (per-agent AIDs), the publisher key is
	// a single long-lived identity that spans all agents published by the
	// same operator.
	KeyTypePublisher KeyType = "publisher"
)

type KeychainKeyStore

type KeychainKeyStore struct {
	// contains filtered or unexported fields
}

KeychainKeyStore implements the KeyStore interface backed by the macOS system keychain. Key material is stored as generic-password items in the user's default keychain (usually login.keychain-db).

A special manifest entry (account "_index") keeps a JSON array of all key IDs so that List() can be implemented efficiently without parsing the text output of security(1).

func NewKeychainKeyStore

func NewKeychainKeyStore(service string) (*KeychainKeyStore, error)

NewKeychainKeyStore creates a new KeychainKeyStore for the given service name. On non-Darwin platforms it returns an error; on Darwin it returns a ready-to-use store.

func (*KeychainKeyStore) Create

func (k *KeychainKeyStore) Create(id KeyID, kt KeyType, material KeyMaterial) error

Create stores key material under the given ID and type. It returns ErrKeyAlreadyExists if a key with that ID already exists, or ErrInvalidKeyID if the ID fails validation.

func (*KeychainKeyStore) Delete

func (k *KeychainKeyStore) Delete(id KeyID) error

Delete removes the key with the given ID. Returns ErrKeyNotFound if the key does not exist, or ErrInvalidKeyID if the ID fails validation.

func (*KeychainKeyStore) List

func (k *KeychainKeyStore) List() ([]KeyMetadata, error)

List returns metadata for all stored keys. The returned KeyMetadata entries never contain raw key material (RawBytes is always nil).

func (*KeychainKeyStore) Load

func (k *KeychainKeyStore) Load(id KeyID) (KeyMaterial, error)

Load retrieves key material for the given ID. Returns ErrKeyNotFound if the key does not exist, or ErrInvalidKeyID if the ID fails validation.

func (*KeychainKeyStore) Sign

func (k *KeychainKeyStore) Sign(id KeyID, digest []byte) ([]byte, error)

Sign computes an ECDSA signature over the given digest using the key identified by id. Only signing key types (CA, AuditSigning, PackageIdentity) are supported; Workload keys return ErrWrongKeyType.

func (*KeychainKeyStore) Verify

func (k *KeychainKeyStore) Verify(id KeyID, digest []byte, signature []byte) bool

Verify checks an ECDSA signature over the given digest against the key identified by id. Returns false if the key is not found, is a non-signing key type, or if the ID fails validation.

type LocalCA

type LocalCA struct {
	// contains filtered or unexported fields
}

LocalCA manages a local certificate authority and related cryptographic identities. It creates and stores a CA signing key (ECDSA P-256) in a KeyStore, issues per-run workload certificates signed by that CA, manages per-agent package identity keys (AIDs), and manages a daemon audit signing key. Package identity keys are never returned to callers — only their public key fingerprints are exposed.

func NewLocalCA

func NewLocalCA(store KeyStore, td *TrustDomain) (*LocalCA, error)

NewLocalCA creates a new LocalCA that uses the given KeyStore and trust domain. It ensures the CA signing key exists in the store (creating it if necessary) and initializes the in-memory CA certificate.

func (*LocalCA) EnsureDaemonAuditSigningKey

func (ca *LocalCA) EnsureDaemonAuditSigningKey() error

EnsureDaemonAuditSigningKey creates the daemon audit signing key in the keystore if it does not already exist. This is idempotent — subsequent calls are no-ops. The key is ECDSA P-256.

func (*LocalCA) EnsurePackageIdentityKey

func (ca *LocalCA) EnsurePackageIdentityKey(agentName string) (fingerprint string, err error)

EnsurePackageIdentityKey creates a package identity key (AID) for the given agent if it does not already exist. It returns the public key fingerprint (SHA-256 of the DER-encoded public key, hex with colons). The private key is stored in the KeyStore and is never returned to callers — only the fingerprint is exposed.

func (*LocalCA) IssueWorkloadCert

func (ca *LocalCA) IssueWorkloadCert(agentName, agentVersion, runID string, ttl time.Duration) (*x509.Certificate, *ecdsa.PrivateKey, string, error)

IssueWorkloadCert generates a workload certificate and ephemeral key pair for the given agent run. The certificate is signed by the local CA and contains the SPIFFE URI as a URI SAN. Returns the certificate, private key, SPIFFE URI, and any error.

func (*LocalCA) RenewWorkloadCert

func (ca *LocalCA) RenewWorkloadCert(agentName, agentVersion, runID string, ttl time.Duration) (*x509.Certificate, *ecdsa.PrivateKey, string, error)

RenewWorkloadCert issues a new workload certificate for the same agent identity, replacing any existing workload key. It is intended to be called before the current certificate expires (e.g., at 80% of TTL).

func (*LocalCA) VerifyWorkloadCert

func (ca *LocalCA) VerifyWorkloadCert(cert *x509.Certificate) error

VerifyWorkloadCert validates a workload certificate against the local CA. It checks the CA signature chain and certificate validity period.

type LocalIdentityIssuer

type LocalIdentityIssuer struct {
	// contains filtered or unexported fields
}

LocalIdentityIssuer implements the IdentityIssuer interface by wrapping a LocalCA and KeyStore to issue SPIFFE-style workload certificates for agent run identities. It returns PEM-encoded certificate and private key bytes along with the SPIFFE URI.

func NewLocalIdentityIssuer

func NewLocalIdentityIssuer(store KeyStore, td *TrustDomain) (*LocalIdentityIssuer, error)

NewLocalIdentityIssuer creates a new LocalIdentityIssuer backed by the given KeyStore and trust domain. It initializes the underlying LocalCA.

func (*LocalIdentityIssuer) IssueWorkloadCert

func (l *LocalIdentityIssuer) IssueWorkloadCert(agentName, agentVersion, runID string, ttl time.Duration) ([]byte, []byte, string, error)

IssueWorkloadCert generates a workload certificate and key for the given agent run. It returns PEM-encoded certificate, PEM-encoded private key, and the SPIFFE URI. This implements the IdentityIssuer interface defined in keystore.go.

type PublisherIdentity added in v0.2.0

type PublisherIdentity struct {
	// Name is the display label for this publisher identity (a GitHub-style
	// slug). It is a label, NOT the identity — the Fingerprint is the
	// canonical identity.
	Name string `json:"name"`

	// Fingerprint is the hex-encoded SHA-256 of the DER-encoded SPKI of the
	// ECDSA P-256 public key (64 lowercase hex characters).
	Fingerprint string `json:"fingerprint"`

	// PublicKeyPEM is the PEM-encoded SPKI public key.
	PublicKeyPEM string `json:"public_key_pem"`

	// CreatedAt is the time the publisher identity was created.
	CreatedAt time.Time `json:"created_at"`
}

PublisherIdentity holds the public-facing information about a publisher identity. It never contains private key material.

func CreatePublisherIdentity added in v0.2.0

func CreatePublisherIdentity(ks KeyStore, name string) (*PublisherIdentity, error)

CreatePublisherIdentity generates a new ECDSA P-256 key pair, stores the private key in the keystore, and returns the public identity information. The private key is never returned to callers. The publisher name is stored alongside the key material for display purposes.

If a publisher identity already exists in the keystore, it returns ErrPublisherIdentityExists.

func LoadPublisherIdentity added in v0.2.0

func LoadPublisherIdentity(ks KeyStore) (*PublisherIdentity, error)

LoadPublisherIdentity loads the publisher identity from the keystore. It returns the public identity information only — the private key is never exposed. Returns ErrNoPublisherIdentity when no identity exists.

type TrustDomain

type TrustDomain struct {
	Host     string
	IsHosted bool
	TenantID string
}

TrustDomain represents a SPIFFE trust domain with configurable host and optional tenant identification. It supports two modes:

  • Local (P1): spiffe://local.agentpaas/agent/<name>/<ver>/run/<run_id>
  • Hosted (P2): spiffe://tenant.agentpaas.ai/<tenant>/agent/<name>/<ver>/run/<run_id>

The same URI path schema is used for both modes; only the trust domain prefix (host and optional tenant segment) differs.

func (*TrustDomain) BuildURI

func (td *TrustDomain) BuildURI(agentName, agentVersion, runID string) (string, error)

BuildURI constructs a SPIFFE URI for the given agent identity within this trust domain. For local mode the URI is:

spiffe://local.agentpaas/agent/<name>/<ver>/run/<run_id>

For hosted mode the URI includes the tenant segment:

spiffe://tenant.agentpaas.ai/<tenant>/agent/<name>/<ver>/run/<run_id>

It returns an error if any component contains path traversal characters ("..") or path separators ("/").

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL