Documentation
¶
Overview ¶
Package certproof answers certificate posture challenges: it finds the certificates a peer holds a private key for and signs the challenge nonce, bound to the peer's WireGuard key, with that key. Management verifies the signature and the chain against the CAs of the check, so only a holder of the key passes and a proof made for one peer cannot be replayed by another. Keys in the OS stores, the TPM and a PKCS#11 token are used through the platform, which signs; a plain PEM key file is the exception, parsed and used in the daemon's memory.
Machine stores are read by the daemon itself: the Windows LocalMachine store, the macOS System keychain, and on Linux a directory of PEM files, TSS2 key files the TPM signs with, and a PKCS#11 token. On Linux the directory and the token URI come from the daemon's environment (NB_CERT_STORE_DIR, NB_CERT_PKCS11_URI), as does the token PIN (NB_TPM_PIN); none of them is read from the profile config.
User stores cannot be read by a privileged daemon, so it starts this binary as "netbird posture cert-proof" inside the user's session and receives only signatures and chains on its stdout. On macOS uid is not what unlocks a login keychain, the session's bootstrap namespace is, which is why the helper enters it through launchctl asuser. On Windows a service opening CURRENT_USER silently reads its own empty hive, so the helper runs with the session's token instead. Its output is untrusted: sizes are capped and only proofs for requested nonces are kept.
A user proof shows that some process in that user's session could use a key whose certificate chains to the CA. It does not show which binary signed, or that the key is hardware-bound; device stores are the stronger signal.
Index ¶
- Constants
- func Collect(ctx context.Context, store Store, checks []*proto.Checks, peerKey []byte) []certposture.Proof
- func CollectChallenges(ctx context.Context, store Store, challenges []*proto.CertificateChallenge, ...) []certposture.Proof
- func CollectProofs(ctx context.Context, checks []*proto.Checks, peerKey []byte, cfg Config) []certposture.Proof
- func HasChallenges(checks []*proto.Checks) bool
- func RunHelper(ctx context.Context, in io.Reader, out io.Writer) error
- func StoreDir() string
- func UserContext(Config) string
- type Candidate
- type Collector
- type Config
- type HelperChallenge
- type HelperRequest
- type HelperResponse
- type PKCS11Config
- type Store
- type Stores
Constants ¶
const ( // PINEnv carries the user PIN of the PKCS#11 token. It is read from the daemon's // environment only, so the PIN never lands in the profile config or on a command line. PINEnv = "NB_TPM_PIN" // PKCS11URIEnv carries the RFC 7512 URI of the token. Like the PIN it lives in the // daemon's environment rather than the profile config, since it may carry pin-value. PKCS11URIEnv = "NB_CERT_PKCS11_URI" )
const (
StoreDirEnv = "NB_CERT_STORE_DIR"
)
Variables ¶
This section is empty.
Functions ¶
func Collect ¶
func Collect(ctx context.Context, store Store, checks []*proto.Checks, peerKey []byte) []certposture.Proof
Collect answers the certificate challenges in checks: for each challenge it picks a stored certificate that chains to the challenge's CAs and signs the nonce with its key. The same certificate is proven once even if several checks accept it.
func CollectChallenges ¶
func CollectChallenges(ctx context.Context, store Store, challenges []*proto.CertificateChallenge, peerKey []byte) []certposture.Proof
CollectChallenges answers challenges already extracted from the posture checks, so a caller that ships them across a process boundary reuses the same matching and signing. Only nonces of the size management issues are signed, for a peer key of the size of ours, so the keys behind the store never sign arbitrary caller-chosen data.
func CollectProofs ¶
func CollectProofs(ctx context.Context, checks []*proto.Checks, peerKey []byte, cfg Config) []certposture.Proof
CollectProofs answers the certificate challenges in checks from the PEM directory cfg names, joined by its PKCS#11 token when it names one. Only macOS and Windows keep per-user certificates out of reach of a privileged daemon, so every other platform reads its store in the daemon itself.
func HasChallenges ¶
HasChallenges reports whether any of checks asks for a certificate proof.
func RunHelper ¶
RunHelper answers the challenges on in from the store of the user running this process and writes the proofs to out. It is the child half of the console user lookup: the daemon cannot read a login keychain, so it launches this in the user's session instead. Only the signature crosses back, never the private key.
func UserContext ¶
UserContext identifies the user whose certificates a collection would include. These platforms have no per-user store, so it never changes.
Types ¶
type Candidate ¶
type Candidate struct {
Chain []*x509.Certificate
Signer crypto.Signer
Intermediates []*x509.Certificate
}
Candidate is a certificate chain the peer can sign for. Signer never exposes the key. Chain is leaf first. Intermediates holds every other certificate the store has, so a path to a challenge's CAs can be found even where Chain followed a different issuer, such as an expired copy of a renewed intermediate.
type Collector ¶
type Collector struct {
// contains filtered or unexported fields
}
Collector runs CollectProofs with a deadline and, in the normal case, one collection at a time in the process. Token, TPM and keychain calls cannot be interrupted, and on Linux there is not even a process to kill, so a collection that overruns is abandoned rather than awaited. A single wedged call would then refuse every later collection for the life of the daemon, so once an abandoned collection has been running for lostAfter deadlines another may start beside it, up to maxInFlight. The zero value is ready to use.
func (*Collector) Collect ¶
func (c *Collector) Collect(ctx context.Context, checks []*proto.Checks, peerKey []byte, cfg Config) []certposture.Proof
Collect answers the certificate challenges in checks, returning no proofs when there are no challenges, when previous collections still hold the slots, or when this one does not finish in time. Missing proofs fail the certificate check on management.
type Config ¶
type Config struct {
PKCS11 PKCS11Config
ProfileOwner string
OwnerUnknown bool
}
Config selects where the daemon looks for certificates. PKCS11 names a token whose keys sign for certificates on the token or in the PEM directory, which NB_CERT_STORE_DIR names on Linux, /etc/netbird/certs by default.
ProfileOwner is the OS account the active profile belongs to. On macOS and Windows only that account's certificate store is consulted for user certificates, so on a machine with several people signed in the result does not depend on who else is logged in. Empty means the profile has no owner, and only the user at the physical console counts. OwnerUnknown means the owner could not be determined, and no user store is consulted: guessing would let whoever sits at the console answer for the profile.
type HelperChallenge ¶
type HelperChallenge struct {
Nonce []byte `json:"nonce"`
CACertificates []string `json:"caCertificates"`
}
HelperChallenge is one certificate challenge in the form the helper needs.
type HelperRequest ¶
type HelperRequest struct {
PeerKey []byte `json:"peerKey"`
Challenges []HelperChallenge `json:"challenges"`
}
HelperRequest is the work the daemon hands to a helper running in a user session. The peer key binds every signature to this machine, so a proof cannot be replayed onto another peer.
type HelperResponse ¶
type HelperResponse struct {
Proofs []certposture.Proof `json:"proofs"`
}
HelperResponse carries the proofs the helper produced from its own keychain.
type PKCS11Config ¶
PKCS11Config names the token whose certificates the store yields. URI is an RFC 7512 PKCS#11 URI, or empty for the first token the p11-kit proxy exposes. PIN is the user PIN, and takes precedence over a pin-value or pin-source the URI carries.
func PKCS11FromEnv ¶
func PKCS11FromEnv() PKCS11Config
PKCS11FromEnv returns the token named by NB_CERT_PKCS11_URI with the PIN set in NB_TPM_PIN, either empty when unset.
type Store ¶
Store yields the certificates a peer may prove possession of. fileStore is the PEM directory implementation; OS keystores (CNG, Keychain, PKCS#11) slot in here.
func DefaultStore ¶
func DefaultStore() Store
DefaultStore is the PEM directory named by NB_CERT_STORE_DIR, or /etc/netbird/certs.