From 24f5f2a7ec602e525863ce0a2b91d9a12d8ab9f7 Mon Sep 17 00:00:00 2001 From: pascal Date: Wed, 16 Sep 2026 23:00:44 +0200 Subject: [PATCH] start TPM support --- client/internal/certproof/README.md | 85 +++++ client/internal/certproof/pkcs11store.go | 182 ++++++++++ client/internal/certproof/pkcs11store_test.go | 189 ++++++++++ client/internal/certproof/store.go | 27 +- client/internal/certproof/store_other.go | 21 +- client/internal/certproof/store_tpm_test.go | 98 +++++ client/internal/pkcs11/driver_linux.go | 336 ++++++++++++++++++ client/internal/pkcs11/driver_linux_test.go | 72 ++++ client/internal/pkcs11/driver_stub.go | 14 + client/internal/pkcs11/pkcs11.go | 235 ++++++++++++ client/internal/pkcs11/uri.go | 98 +++++ client/internal/pkcs11/uri_test.go | 87 +++++ client/internal/tpm/open_linux.go | 29 ++ client/internal/tpm/open_other.go | 9 + client/internal/tpm/signer.go | 61 ++++ client/internal/tpm/signer_test.go | 64 ++++ client/internal/tpm/tpm.go | 19 + client/internal/tpm/tpmtest/tpmtest.go | 48 +++ go.mod | 89 ++--- go.sum | 197 +++++----- .../certposture/certposture_test.go | 19 + shared/management/certposture/proof.go | 2 +- 22 files changed, 1836 insertions(+), 145 deletions(-) create mode 100644 client/internal/certproof/pkcs11store.go create mode 100644 client/internal/certproof/pkcs11store_test.go create mode 100644 client/internal/certproof/store_tpm_test.go create mode 100644 client/internal/pkcs11/driver_linux.go create mode 100644 client/internal/pkcs11/driver_linux_test.go create mode 100644 client/internal/pkcs11/driver_stub.go create mode 100644 client/internal/pkcs11/pkcs11.go create mode 100644 client/internal/pkcs11/uri.go create mode 100644 client/internal/pkcs11/uri_test.go create mode 100644 client/internal/tpm/open_linux.go create mode 100644 client/internal/tpm/open_other.go create mode 100644 client/internal/tpm/signer.go create mode 100644 client/internal/tpm/signer_test.go create mode 100644 client/internal/tpm/tpm.go create mode 100644 client/internal/tpm/tpmtest/tpmtest.go diff --git a/client/internal/certproof/README.md b/client/internal/certproof/README.md index 10a20cab8..c279fd443 100644 --- a/client/internal/certproof/README.md +++ b/client/internal/certproof/README.md @@ -17,6 +17,8 @@ to one WireGuard peer key and cannot be replayed by another peer. | Windows | `LocalMachine\MY` | the service, directly | | Windows | signed-in user's `CurrentUser\MY` | a helper launched with that session's token | | Linux and others | PEM directory, `NB_CERT_STORE_DIR` or `/etc/netbird/certs` | the daemon, directly | +| Linux | a `TSS2 PRIVATE KEY` file in that directory, signed by the TPM | the daemon, through `/dev/tpmrm0` | +| Linux | a PKCS#11 token named by `NB_CERT_PKCS11_URI`, such as tpm2-pkcs11 | the daemon, through the token's module, in builds with the `pkcs11` tag | macOS and Windows both keep per-user certificates out of reach of a privileged daemon, and both are handled the same way: the daemon reads the machine store itself and @@ -99,6 +101,89 @@ Unlike macOS, the Windows store acquires keys with `CRYPT_ACQUIRE_SILENT_FLAG`, that would need a prompt fails immediately instead of blocking. That also means a smartcard PIN can never be satisfied this way. +## Linux: keys held by the TPM + +Enrollment tooling on Linux keeps a TPM-resident key as a `TSS2 PRIVATE KEY` PEM file, +the format of draft-bottomley-tpm2-keys that tpm2-openssl, tpm2-tss-engine and +`tpm2_encodeobject` write. The file holds the key wrapped by its parent; the TPM is the +only thing that can use it. Drop it next to the certificate as usual: + +``` +openssl genpkey -provider tpm2 -algorithm EC -pkeyopt group:P-256 -out /etc/netbird/certs/device.key +openssl req -provider tpm2 -provider default -new -key /etc/netbird/certs/device.key -subj /CN=device -out device.csr +``` + +Sign the CSR with the organisation CA and store the result as `device.pem`. The store +parses the key file without touching the TPM, so the certificate is listed as a +candidate like any other, and every signature opens `/dev/tpmrm0`, loads the key under +its parent, signs, flushes and closes again. `NB_TPM_DEVICE` overrides the device path. + +What the key file may look like: + +- **Parent.** A persistent handle such as `0x81000001` is used as is. The owner + hierarchy, which both tpm2-openssl and tpm2-tss-engine default to, means the key was + created under a transient primary from the TCG default ECC P-256 template, and that + same primary is derived again before loading. +- **No authorization value.** A key created with a password needs someone to type it, + which the daemon cannot arrange, so the certificate is skipped with a log line rather + than blocking on a TPM auth failure. +- **RSA-2048 or P-256, sometimes P-384.** Those are what the PC Client profile requires + of a TPM; P-384 depends on the chip. The TPM chooses the RSA-PSS salt itself, which is + why management verifies PSS proofs with `rsa.PSSSaltLengthAuto`. + +Windows needs none of this: a certificate enrolled into the TPM sits behind the Microsoft +Platform Crypto Provider and the CNG path above signs with it unchanged. macOS has no +TPM; its Secure Enclave keys are reachable only through the keychain path. + +To exercise the path without hardware, run a software TPM and point the end-to-end test +at it: + +``` +swtpm socket --tpm2 --server type=unixio,path=/tmp/swtpm.sock --ctrl type=unixio,path=/tmp/swtpm.ctrl --flags not-need-init,startup-clear +NB_TPM_DEVICE=/tmp/swtpm.sock go test ./client/internal/certproof/ -run TestCollect_TPMKeyEndToEnd -v +``` + +## Linux: keys behind a PKCS#11 token + +Distributions that follow Red Hat's guidance reach the TPM through tpm2-pkcs11, a PKCS#11 +module whose token holds both the key and, after `tpm2_ptool addcert`, the certificate. +The store reads that token when `NB_CERT_PKCS11_URI` names it with an RFC 7512 URI: + +``` +NB_CERT_PKCS11_URI='pkcs11:token=netbird?module-path=/usr/lib/x86_64-linux-gnu/libtpm2_pkcs11.so&pin-source=file:/etc/netbird/pkcs11.pin' +``` + +`token` selects the token by label, or the first token present when absent. `module-path` +names the library to load; `module-name=tpm2_pkcs11` resolves to `libtpm2_pkcs11.so` on +the loader's search path, and with neither the p11-kit proxy is loaded, which exposes every +module the system has registered. `pin-source` points at a file holding the user PIN and +`pin-value` carries it inline; without either no login happens, and tpm2-pkcs11 then shows +no private keys at all. Every other attribute is ignored. + +Certificates and private keys are paired by `CKA_ID`, which is what `tpm2_ptool addcert` +and `pkcs11-tool` set. Chains are completed from the other certificates on the token. Each +operation opens a session, logs in, works, logs out and closes, so no token handle +outlives a call, and the PEM directory keeps working when the token does not: the two are +queried together and a failing token is logged rather than hiding file certificates. + +Two consequences of the PIN are worth knowing. It is a secret on disk, so the PIN file +should be root-only. And a wrong PIN counts against the TPM's dictionary-attack lockout, +which is shared with everything else on the machine that uses the TPM. + +The module is loaded at runtime without cgo, through `purego`, which means the binary is +dynamically linked against libc. The standard release binary stays fully static, so the +PKCS#11 store is compiled in only with `-tags pkcs11` on linux/amd64 and linux/arm64. +Without the tag, setting `NB_CERT_PKCS11_URI` logs that the build lacks the support. + +To exercise the path without hardware, initialise a SoftHSM token and run the end-to-end +test, which imports a key and certificate itself: + +``` +softhsm2-util --init-token --free --label netbird --pin 1234 --so-pin 1234 +NB_TEST_PKCS11_URI='pkcs11:token=netbird?module-path=/usr/lib/softhsm/libsofthsm2.so&pin-value=1234' \ + go test -tags pkcs11 ./client/internal/certproof/ -run PKCS11 -v +``` + ## Only the signed-in user can be validated This is the central limitation of the design, and it is deliberate. diff --git a/client/internal/certproof/pkcs11store.go b/client/internal/certproof/pkcs11store.go new file mode 100644 index 000000000..ca060ac52 --- /dev/null +++ b/client/internal/certproof/pkcs11store.go @@ -0,0 +1,182 @@ +package certproof + +import ( + "context" + "crypto" + "crypto/sha256" + "crypto/x509" + "errors" + "fmt" + "io" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/internal/pkcs11" +) + +const PKCS11URIEnv = "NB_CERT_PKCS11_URI" + +// PKCS11Store yields the identities of a PKCS#11 token, which is how tpm2-pkcs11 exposes +// TPM-held keys on Linux. Certificates and keys are paired by CKA_ID, the convention +// tpm2_ptool addcert and pkcs11-tool follow, and every signature happens on the token. +type PKCS11Store struct { + uri *pkcs11.URI +} + +// NewPKCS11Store reads the token, module and PIN source from an RFC 7512 PKCS#11 URI. +func NewPKCS11Store(uri string) (*PKCS11Store, error) { + parsed, err := pkcs11.ParseURI(uri) + if err != nil { + return nil, err + } + return &PKCS11Store{uri: parsed}, nil +} + +func (s *PKCS11Store) Candidates(_ context.Context) ([]Candidate, error) { + session, err := s.open() + if err != nil { + return nil, err + } + defer session.Close() + + certs, err := tokenCertificates(session) + if err != nil { + return nil, err + } + log.Infof("%s holds %d certificates", s, len(certs)) + + pool := make([]*x509.Certificate, 0, len(certs)) + for _, cert := range certs { + pool = append(pool, cert.cert) + } + var candidates []Candidate + for _, cert := range certs { + if _, err := privateKey(session, cert.id); err != nil { + log.Infof("%s certificate %q has no usable private key: %v", s, cert.cert.Subject, err) + continue + } + chain := buildChain(cert.cert, pool) + log.Infof("%s candidate %q issued by %q built a chain of %d certificates", s, cert.cert.Subject, cert.cert.Issuer, len(chain)) + candidates = append(candidates, Candidate{Chain: chain, Signer: &pkcs11Signer{store: s, leaf: cert.cert, id: cert.id}}) + } + return candidates, nil +} + +func (s *PKCS11Store) String() string { + if s.uri.Token == "" { + return "PKCS#11 token" + } + return fmt.Sprintf("PKCS#11 token %q", s.uri.Token) +} + +func (s *PKCS11Store) open() (*pkcs11.Session, error) { + module, err := pkcs11.Load(s.uri.Module()) + if err != nil { + return nil, err + } + pin, err := s.uri.PIN() + if err != nil { + return nil, err + } + return module.OpenSession(s.uri.Token, pin) +} + +type tokenCertificate struct { + cert *x509.Certificate + id []byte +} + +func tokenCertificates(session *pkcs11.Session) ([]tokenCertificate, error) { + objects, err := session.FindObjects( + pkcs11.Attribute{Type: pkcs11.AttrClass, Value: pkcs11.ULong(pkcs11.ClassCertificate)}, + pkcs11.Attribute{Type: pkcs11.AttrCertificateType, Value: pkcs11.ULong(pkcs11.CertificateX509)}, + ) + if err != nil { + return nil, err + } + certs := make([]tokenCertificate, 0, len(objects)) + for _, object := range objects { + der, err := session.Attribute(object, pkcs11.AttrValue) + if err != nil { + return nil, err + } + cert, err := x509.ParseCertificate(der) + if err != nil { + log.Warnf("skipping unparsable certificate on PKCS#11 token: %v", err) + continue + } + id, err := session.Attribute(object, pkcs11.AttrID) + if err != nil { + return nil, err + } + certs = append(certs, tokenCertificate{cert: cert, id: id}) + } + return certs, nil +} + +var errNoPrivateKey = errors.New("no private key shares the certificate's CKA_ID") + +func privateKey(session *pkcs11.Session, id []byte) (pkcs11.Object, error) { + if len(id) == 0 { + return 0, errNoPrivateKey + } + keys, err := session.FindObjects( + pkcs11.Attribute{Type: pkcs11.AttrClass, Value: pkcs11.ULong(pkcs11.ClassPrivateKey)}, + pkcs11.Attribute{Type: pkcs11.AttrID, Value: id}, + ) + if err != nil { + return 0, err + } + if len(keys) == 0 { + return 0, errNoPrivateKey + } + return keys[0], nil +} + +// pkcs11Signer holds only the certificate and its CKA_ID; the key is looked up in a fresh +// session at signing time so no token handle outlives a call. +type pkcs11Signer struct { + store *PKCS11Store + leaf *x509.Certificate + id []byte +} + +func (s *pkcs11Signer) Public() crypto.PublicKey { + return s.leaf.PublicKey +} + +func (s *pkcs11Signer) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + scheme, err := schemeFor(s.leaf.PublicKey, opts) + if err != nil { + return nil, err + } + session, err := s.store.open() + if err != nil { + return nil, err + } + defer session.Close() + + key, err := privateKey(session, s.id) + if err != nil { + return nil, err + } + signature, err := session.Sign(pkcs11Mechanism(scheme), key, digest) + if err != nil { + return nil, err + } + if scheme == schemeRSAPSSSHA256 { + return signature, nil + } + return ecdsaSignatureASN1(signature) +} + +// pkcs11Mechanism maps a signature scheme onto the token mechanism that consumes a digest. +func pkcs11Mechanism(scheme sigScheme) pkcs11.Mechanism { + if scheme == schemeRSAPSSSHA256 { + return pkcs11.Mechanism{ + Type: pkcs11.MechRSAPKCSPSS, + PSS: &pkcs11.PSSParams{Hash: pkcs11.MechSHA256, MGF: pkcs11.MGF1SHA256, SaltLen: sha256.Size}, + } + } + return pkcs11.Mechanism{Type: pkcs11.MechECDSA} +} diff --git a/client/internal/certproof/pkcs11store_test.go b/client/internal/certproof/pkcs11store_test.go new file mode 100644 index 000000000..0db2bfc3c --- /dev/null +++ b/client/internal/certproof/pkcs11store_test.go @@ -0,0 +1,189 @@ +package certproof + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "errors" + "math/big" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/netbirdio/netbird/client/internal/pkcs11" + "github.com/netbirdio/netbird/shared/management/certposture" + "github.com/netbirdio/netbird/shared/management/certposture/certtest" + "github.com/netbirdio/netbird/shared/management/proto" +) + +const testPKCS11URIEnv = "NB_TEST_PKCS11_URI" + +type failingStore struct{} + +func (failingStore) Candidates(context.Context) ([]Candidate, error) { + return nil, errors.New("token unplugged") +} + +func TestStores_KeepsFileCertificatesWhenTokenFails(t *testing.T) { + ca := certtest.NewCA(t, "corp") + key := certtest.ECDSAKey(t) + dir := t.TempDir() + writeFile(t, dir, "device.pem", certtest.CertPEM(ca.Issue(t, key, "device"))+certtest.KeyPEM(t, key)) + + candidates, err := Stores{failingStore{}, NewFileStore(dir)}.Candidates(context.Background()) + require.NoError(t, err) + assert.Len(t, candidates, 1, "the directory's certificate must survive a failing token") +} + +// TestCollect_PKCS11TokenEndToEnd needs an initialised token with a user PIN, named by +// NB_TEST_PKCS11_URI. With SoftHSM: +// +// softhsm2-util --init-token --free --label netbird --pin 1234 --so-pin 1234 +// NB_TEST_PKCS11_URI='pkcs11:token=netbird?module-path=/usr/lib/softhsm/libsofthsm2.so&pin-value=1234' \ +// go test -tags pkcs11 ./client/internal/certproof/ -run PKCS11 -v +// +// It imports a key and its certificate as token objects, then proves the certificate +// through the store the way the daemon would. Every run adds one more identity to the token. +func TestCollect_PKCS11TokenEndToEnd(t *testing.T) { + uri := os.Getenv(testPKCS11URIEnv) + if uri == "" { + t.Skipf("set %s to a PKCS#11 URI with a PIN to run", testPKCS11URIEnv) + } + store, err := NewPKCS11Store(uri) + require.NoError(t, err) + if _, err := pkcs11.Load(store.uri.Module()); errors.Is(err, pkcs11.ErrUnsupported) { + t.Skip(err) + } + + keys := map[string]crypto.Signer{"ecdsa": certtest.ECDSAKey(t), "rsa": certtest.RSAKey(t)} + for name, key := range keys { + t.Run(name, func(t *testing.T) { + ca := certtest.NewCA(t, "corp-"+name) + leaf := ca.Issue(t, key, "device-"+name) + importIdentity(t, uri, key, leaf) + + challenger := certposture.NewChallenger([]byte("secret")) + now := time.Now() + nonce := challenger.Nonce(peerKey, now) + checks := []*proto.Checks{{CertificateChallenge: &proto.CertificateChallenge{Nonce: nonce, CaCertificates: []string{ca.PEM}}}} + + proofs := Collect(context.Background(), store, checks, peerKey) + require.Len(t, proofs, 1, "the token-held key must prove exactly this run's certificate") + chain, err := challenger.Verify(proofs[0], peerKey, now) + require.NoError(t, err) + assert.True(t, leaf.Equal(chain[0]), "proof must carry the imported certificate") + }) + } +} + +// Attribute and key types the import needs and the store does not. +const ( + attrPrivate = 0x2 + attrIssuer = 0x81 + attrSerialNumber = 0x82 + attrKeyType = 0x100 + attrSensitive = 0x103 + attrSign = 0x108 + attrModulus = 0x120 + attrPublicExponent = 0x122 + attrPrivateExponent = 0x123 + attrPrime1 = 0x124 + attrPrime2 = 0x125 + attrExponent1 = 0x126 + attrExponent2 = 0x127 + attrCoefficient = 0x128 + attrECParams = 0x180 + keyTypeRSA = 0x0 + keyTypeEC = 0x3 +) + +var ( + ckTrue = []byte{1} + ckFalse = []byte{0} + // The P-256 named curve OID in DER, which is what CKA_EC_PARAMS carries. + oidP256 = []byte{0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07} +) + +func importIdentity(t *testing.T, uri string, key crypto.Signer, leaf *x509.Certificate) { + t.Helper() + parsed, err := pkcs11.ParseURI(uri) + require.NoError(t, err) + module, err := pkcs11.Load(parsed.Module()) + require.NoError(t, err) + pin, err := parsed.PIN() + require.NoError(t, err) + session, err := module.OpenReadWriteSession(parsed.Token, pin) + require.NoError(t, err) + defer session.Close() + + id := make([]byte, 8) + _, err = rand.Read(id) + require.NoError(t, err) + label := []byte(leaf.Subject.CommonName) + serial, err := asn1.Marshal(leaf.SerialNumber) + require.NoError(t, err) + + _, err = session.CreateObject( + attr(pkcs11.AttrClass, pkcs11.ULong(pkcs11.ClassCertificate)), + attr(pkcs11.AttrCertificateType, pkcs11.ULong(pkcs11.CertificateX509)), + attr(pkcs11.AttrToken, ckTrue), + attr(attrPrivate, ckFalse), + attr(pkcs11.AttrLabel, label), + attr(pkcs11.AttrID, id), + attr(pkcs11.AttrSubject, leaf.RawSubject), + attr(attrIssuer, leaf.RawIssuer), + attr(attrSerialNumber, serial), + attr(pkcs11.AttrValue, leaf.Raw), + ) + require.NoError(t, err, "import certificate") + + template := []pkcs11.Attribute{ + attr(pkcs11.AttrClass, pkcs11.ULong(pkcs11.ClassPrivateKey)), + attr(pkcs11.AttrToken, ckTrue), + attr(attrPrivate, ckTrue), + attr(attrSensitive, ckTrue), + attr(attrSign, ckTrue), + attr(pkcs11.AttrLabel, label), + attr(pkcs11.AttrID, id), + } + _, err = session.CreateObject(append(template, keyAttributes(t, key)...)...) + require.NoError(t, err, "import private key") +} + +func keyAttributes(t *testing.T, key crypto.Signer) []pkcs11.Attribute { + t.Helper() + switch k := key.(type) { + case *ecdsa.PrivateKey: + return []pkcs11.Attribute{ + attr(attrKeyType, pkcs11.ULong(keyTypeEC)), + attr(attrECParams, oidP256), + attr(pkcs11.AttrValue, k.D.FillBytes(make([]byte, 32))), + } + case *rsa.PrivateKey: + k.Precompute() + return []pkcs11.Attribute{ + attr(attrKeyType, pkcs11.ULong(keyTypeRSA)), + attr(attrModulus, k.N.Bytes()), + attr(attrPublicExponent, big.NewInt(int64(k.E)).Bytes()), + attr(attrPrivateExponent, k.D.Bytes()), + attr(attrPrime1, k.Primes[0].Bytes()), + attr(attrPrime2, k.Primes[1].Bytes()), + attr(attrExponent1, k.Precomputed.Dp.Bytes()), + attr(attrExponent2, k.Precomputed.Dq.Bytes()), + attr(attrCoefficient, k.Precomputed.Qinv.Bytes()), + } + } + t.Fatalf("unsupported key %T", key) + return nil +} + +func attr(typ uint, value []byte) pkcs11.Attribute { + return pkcs11.Attribute{Type: typ, Value: value} +} diff --git a/client/internal/certproof/store.go b/client/internal/certproof/store.go index fbce53789..607bda257 100644 --- a/client/internal/certproof/store.go +++ b/client/internal/certproof/store.go @@ -12,6 +12,8 @@ import ( "strings" log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/client/internal/tpm" ) const ( @@ -32,7 +34,8 @@ type Store interface { } // FileStore reads PEM files from a directory. A file holds the chain (leaf first) and -// either its private key or a sibling ".key" file holds it. +// either its private key or a sibling ".key" file holds it. The key is a plain +// PKCS#8, EC or RSA key, or a TSS2 key the TPM signs with. type FileStore struct { dir string } @@ -116,7 +119,7 @@ func parsePEM(data []byte) ([]*x509.Certificate, crypto.Signer, error) { return nil, nil, fmt.Errorf("parse certificate: %w", err) } chain = append(chain, cert) - case "PRIVATE KEY", "EC PRIVATE KEY", "RSA PRIVATE KEY": + case "PRIVATE KEY", "EC PRIVATE KEY", "RSA PRIVATE KEY", tpm.KeyPEMType: key, err := parsePrivateKey(block) if err != nil { return nil, nil, err @@ -130,6 +133,8 @@ func parsePrivateKey(block *pem.Block) (crypto.Signer, error) { var key any var err error switch block.Type { + case tpm.KeyPEMType: + return tpm.ParseKey(block.Bytes) case "EC PRIVATE KEY": key, err = x509.ParseECPrivateKey(block.Bytes) case "RSA PRIVATE KEY": @@ -154,3 +159,21 @@ func isCertFile(name string) bool { } return false } + +// Stores queries several stores and carries on when one fails, so a broken token cannot +// hide the certificates a directory holds. A failure is logged instead of returned +// because Collect treats a store error as "no proofs at all". +type Stores []Store + +func (s Stores) Candidates(ctx context.Context) ([]Candidate, error) { + var all []Candidate + for _, store := range s { + candidates, err := store.Candidates(ctx) + if err != nil { + log.Warnf("certificate store %T unavailable: %v", store, err) + continue + } + all = append(all, candidates...) + } + return all, nil +} diff --git a/client/internal/certproof/store_other.go b/client/internal/certproof/store_other.go index 3888eaf6f..07e2d4526 100644 --- a/client/internal/certproof/store_other.go +++ b/client/internal/certproof/store_other.go @@ -2,7 +2,24 @@ package certproof -// DefaultStore is the PEM directory named by NB_CERT_STORE_DIR, or /etc/netbird/certs. +import ( + "os" + + log "github.com/sirupsen/logrus" +) + +// DefaultStore is the PEM directory named by NB_CERT_STORE_DIR, or /etc/netbird/certs, +// joined by the PKCS#11 token named by NB_CERT_PKCS11_URI when that is set. func DefaultStore() Store { - return NewFileStore(StoreDir()) + files := NewFileStore(StoreDir()) + uri := os.Getenv(PKCS11URIEnv) + if uri == "" { + return files + } + token, err := NewPKCS11Store(uri) + if err != nil { + log.Warnf("ignoring %s: %v", PKCS11URIEnv, err) + return files + } + return Stores{files, token} } diff --git a/client/internal/certproof/store_tpm_test.go b/client/internal/certproof/store_tpm_test.go new file mode 100644 index 000000000..09f54e0b0 --- /dev/null +++ b/client/internal/certproof/store_tpm_test.go @@ -0,0 +1,98 @@ +package certproof + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/pem" + "os" + "testing" + "time" + + "github.com/google/go-tpm/legacy/tpm2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.step.sm/crypto/tpm/tss2" + + "github.com/netbirdio/netbird/client/internal/tpm" + "github.com/netbirdio/netbird/client/internal/tpm/tpmtest" + "github.com/netbirdio/netbird/shared/management/certposture" + "github.com/netbirdio/netbird/shared/management/certposture/certtest" + "github.com/netbirdio/netbird/shared/management/proto" +) + +func TestFileStore_TPMKeyFile(t *testing.T) { + ca := certtest.NewCA(t, "corp") + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + leaf := ca.Issue(t, key, "device") + + dir := t.TempDir() + writeFile(t, dir, "device.pem", certtest.CertPEM(leaf)) + writeFile(t, dir, "device.key", tpmtest.KeyPEM(t, &key.PublicKey)) + + // A key that needs a password can never be used silently, so its certificate is skipped. + locked := certtest.ECDSAKey(t) + withAuth := func(k *tss2.TPMKey) { k.EmptyAuth = false } + writeFile(t, dir, "locked.pem", certtest.CertPEM(ca.Issue(t, locked, "locked"))) + writeFile(t, dir, "locked.key", tpmtest.KeyPEM(t, locked.Public().(*ecdsa.PublicKey), withAuth)) + + candidates, err := NewFileStore(dir).Candidates(context.Background()) + require.NoError(t, err) + require.Len(t, candidates, 1, "only the key without an authorization value is usable") + assert.True(t, leaf.Equal(candidates[0].Chain[0]), "candidate must carry the device certificate") + assert.True(t, key.PublicKey.Equal(candidates[0].Signer.Public()), "signer must report the certificate's key") +} + +// TestCollect_TPMKeyEndToEnd runs against the TPM named by NB_TPM_DEVICE, for example a +// swtpm started with: +// +// swtpm socket --tpm2 --server type=unixio,path=/tmp/swtpm.sock \ +// --ctrl type=unixio,path=/tmp/swtpm.ctrl --flags not-need-init,startup-clear +// +// It creates a key the way tpm2-openssl does, under a transient ECC primary in the owner +// hierarchy, and proves the certificate for it through the regular file store. +func TestCollect_TPMKeyEndToEnd(t *testing.T) { + if os.Getenv(tpm.DeviceEnv) == "" { + t.Skipf("set %s to a TPM device or swtpm socket to run", tpm.DeviceEnv) + } + public, private := createTPMKey(t) + keyPEM := tpmtest.EncodePEM(t, public, private) + block, _ := pem.Decode([]byte(keyPEM)) + require.NotNil(t, block) + signer, err := tpm.ParseKey(block.Bytes) + require.NoError(t, err) + + ca := certtest.NewCA(t, "corp") + leaf := ca.Issue(t, signer, "device") + dir := t.TempDir() + writeFile(t, dir, "device.pem", certtest.CertPEM(leaf)) + writeFile(t, dir, "device.key", keyPEM) + + challenger := certposture.NewChallenger([]byte("secret")) + now := time.Now() + nonce := challenger.Nonce(peerKey, now) + checks := []*proto.Checks{{CertificateChallenge: &proto.CertificateChallenge{Nonce: nonce, CaCertificates: []string{ca.PEM}}}} + + proofs := Collect(context.Background(), NewFileStore(dir), checks, peerKey) + require.Len(t, proofs, 1, "the TPM-held key must prove the certificate") + chain, err := challenger.Verify(proofs[0], peerKey, now) + require.NoError(t, err) + assert.True(t, leaf.Equal(chain[0]), "proof must carry the device certificate") +} + +func createTPMKey(t *testing.T) (public, private []byte) { + t.Helper() + rwc, err := tpm.Open() + require.NoError(t, err) + defer func() { _ = rwc.Close() }() + + parent, _, err := tpm2.CreatePrimary(rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", tss2.ECCSRKTemplate) + require.NoError(t, err) + defer func() { _ = tpm2.FlushContext(rwc, parent) }() + + private, public, _, _, _, err = tpm2.CreateKey(rwc, parent, tpm2.PCRSelection{}, "", "", tpmtest.SigningTemplate()) + require.NoError(t, err) + return public, private +} diff --git a/client/internal/pkcs11/driver_linux.go b/client/internal/pkcs11/driver_linux.go new file mode 100644 index 000000000..f0082b6b5 --- /dev/null +++ b/client/internal/pkcs11/driver_linux.go @@ -0,0 +1,336 @@ +//go:build pkcs11 && linux && (amd64 || arm64) + +package pkcs11 + +import ( + "encoding/binary" + "errors" + "fmt" + "runtime" + "strings" + "unsafe" + + "github.com/ebitengine/purego" +) + +// ulong is CK_ULONG, an unsigned long, which is pointer-sized on the 64-bit Linux ABIs +// this file builds for. The struct layouts below assume that width and natural alignment. +type ulong = uintptr + +const ( + unavailableInformation = ^ulong(0) + + flagOSLockingOK = 0x2 + flagRWSession = 0x2 + flagSerialSession = 0x4 + userTypeUser = 0x1 + + findBatch = 32 +) + +type version struct { + major byte + minor byte +} + +type attribute struct { + typ ulong + value unsafe.Pointer + len ulong +} + +type mechanism struct { + typ ulong + parameter unsafe.Pointer + len ulong +} + +type pssParams struct { + hashAlg ulong + mgf ulong + saltLen ulong +} + +type tokenInfo struct { + label [32]byte + manufacturerID [32]byte + model [16]byte + serialNumber [16]byte + flags ulong + counters [10]ulong + hardware version + firmware version + utcTime [16]byte +} + +type initializeArgs struct { + createMutex uintptr + destroyMutex uintptr + lockMutex uintptr + unlockMutex uintptr + flags ulong + reserved unsafe.Pointer +} + +// functionList mirrors CK_FUNCTION_LIST: a CK_VERSION padded to pointer alignment, then +// the PKCS#11 v2.40 entry points in specification order. +type functionList struct { + version version + _ [6]byte + fn [68]uintptr +} + +const ( + fnInitialize = 0 + fnGetSlotList = 4 + fnGetTokenInfo = 6 + fnOpenSession = 12 + fnCloseSession = 13 + fnLogin = 18 + fnLogout = 19 + fnCreateObject = 20 + fnGetAttributeValue = 24 + fnFindObjectsInit = 26 + fnFindObjects = 27 + fnFindObjectsFinal = 28 + fnSignInit = 42 + fnSign = 43 +) + +// module holds the entry points of one loaded library, bound straight from its +// CK_FUNCTION_LIST. +type module struct { + cInitialize func(args *initializeArgs) ulong + cGetSlotList func(tokenPresent byte, slots *ulong, count *ulong) ulong + cGetTokenInfo func(slot ulong, info *tokenInfo) ulong + cOpenSession func(slot ulong, flags ulong, application unsafe.Pointer, notify uintptr, session *ulong) ulong + cCloseSession func(session ulong) ulong + cLogin func(session ulong, userType ulong, pin *byte, pinLen ulong) ulong + cLogout func(session ulong) ulong + cCreateObject func(session ulong, template *attribute, count ulong, object *ulong) ulong + cGetAttributeValue func(session ulong, object ulong, template *attribute, count ulong) ulong + cFindObjectsInit func(session ulong, template *attribute, count ulong) ulong + cFindObjects func(session ulong, objects *ulong, max ulong, count *ulong) ulong + cFindObjectsFinal func(session ulong) ulong + cSignInit func(session ulong, mech *mechanism, key ulong) ulong + cSign func(session ulong, data *byte, dataLen ulong, signature *byte, signatureLen *ulong) ulong +} + +func load(path string) (driver, error) { + lib, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_LOCAL) + if err != nil { + return nil, fmt.Errorf("open PKCS#11 module %s: %w", path, err) + } + symbol, err := purego.Dlsym(lib, "C_GetFunctionList") + if err != nil { + return nil, fmt.Errorf("%s is not a PKCS#11 module: %w", path, err) + } + var getFunctionList func(list **functionList) ulong + purego.RegisterFunc(&getFunctionList, symbol) + var list *functionList + if rv := getFunctionList(&list); rv != rvOK || list == nil { + return nil, Error{Op: "C_GetFunctionList", Code: uint(rv)} + } + + m := &module{} + for _, entry := range []struct { + fn any + index int + }{ + {&m.cInitialize, fnInitialize}, + {&m.cGetSlotList, fnGetSlotList}, + {&m.cGetTokenInfo, fnGetTokenInfo}, + {&m.cOpenSession, fnOpenSession}, + {&m.cCloseSession, fnCloseSession}, + {&m.cLogin, fnLogin}, + {&m.cLogout, fnLogout}, + {&m.cCreateObject, fnCreateObject}, + {&m.cGetAttributeValue, fnGetAttributeValue}, + {&m.cFindObjectsInit, fnFindObjectsInit}, + {&m.cFindObjects, fnFindObjects}, + {&m.cFindObjectsFinal, fnFindObjectsFinal}, + {&m.cSignInit, fnSignInit}, + {&m.cSign, fnSign}, + } { + if list.fn[entry.index] == 0 { + return nil, fmt.Errorf("%s lacks PKCS#11 entry point %d", path, entry.index) + } + purego.RegisterFunc(entry.fn, list.fn[entry.index]) + } + + args := &initializeArgs{flags: flagOSLockingOK} + if rv := m.cInitialize(args); rv != rvOK && rv != rvAlreadyInitialized { + return nil, Error{Op: "C_Initialize", Code: uint(rv)} + } + return m, nil +} + +func (m *module) tokens() ([]Token, error) { + var count ulong + if rv := m.cGetSlotList(1, nil, &count); rv != rvOK { + return nil, Error{Op: "C_GetSlotList", Code: uint(rv)} + } + if count == 0 { + return nil, nil + } + slots := make([]ulong, count) + if rv := m.cGetSlotList(1, &slots[0], &count); rv != rvOK { + return nil, Error{Op: "C_GetSlotList", Code: uint(rv)} + } + + tokens := make([]Token, 0, count) + for _, slot := range slots[:count] { + var info tokenInfo + if rv := m.cGetTokenInfo(slot, &info); rv != rvOK { + continue + } + tokens = append(tokens, Token{Slot: uint(slot), Label: strings.TrimRight(string(info.label[:]), " \x00")}) + } + return tokens, nil +} + +func (m *module) openSession(slot uint, readWrite bool) (uint, error) { + flags := ulong(flagSerialSession) + if readWrite { + flags |= flagRWSession + } + var session ulong + if rv := m.cOpenSession(ulong(slot), flags, nil, 0, &session); rv != rvOK { + return 0, Error{Op: "C_OpenSession", Code: uint(rv)} + } + return uint(session), nil +} + +func (m *module) closeSession(session uint) { + m.cCloseSession(ulong(session)) +} + +func (m *module) login(session uint, pin []byte) error { + var pinPtr *byte + if len(pin) > 0 { + pinPtr = &pin[0] + } + rv := m.cLogin(ulong(session), userTypeUser, pinPtr, ulong(len(pin))) + runtime.KeepAlive(pin) + if rv != rvOK && rv != rvUserAlreadyLoggedIn { + return Error{Op: "C_Login", Code: uint(rv)} + } + return nil +} + +func (m *module) logout(session uint) { + m.cLogout(ulong(session)) +} + +func (m *module) findObjects(session uint, template []Attribute) ([]Object, error) { + attrs := toAttributes(template) + rv := m.cFindObjectsInit(ulong(session), first(attrs), ulong(len(attrs))) + runtime.KeepAlive(template) + if rv != rvOK { + return nil, Error{Op: "C_FindObjectsInit", Code: uint(rv)} + } + defer m.cFindObjectsFinal(ulong(session)) + + var objects []Object + for { + var batch [findBatch]ulong + var count ulong + if rv := m.cFindObjects(ulong(session), &batch[0], findBatch, &count); rv != rvOK { + return nil, Error{Op: "C_FindObjects", Code: uint(rv)} + } + for _, handle := range batch[:count] { + objects = append(objects, Object(handle)) + } + if count < findBatch { + return objects, nil + } + } +} + +func (m *module) attribute(session uint, obj Object, typ uint) ([]byte, error) { + attr := attribute{typ: ulong(typ)} + if rv := m.cGetAttributeValue(ulong(session), ulong(obj), &attr, 1); rv != rvOK { + return nil, Error{Op: "C_GetAttributeValue", Code: uint(rv)} + } + if attr.len == unavailableInformation { + return nil, fmt.Errorf("attribute 0x%x is unavailable", typ) + } + if attr.len == 0 { + return nil, nil + } + value := make([]byte, attr.len) + attr.value = unsafe.Pointer(&value[0]) + rv := m.cGetAttributeValue(ulong(session), ulong(obj), &attr, 1) + runtime.KeepAlive(value) + if rv != rvOK { + return nil, Error{Op: "C_GetAttributeValue", Code: uint(rv)} + } + return value[:attr.len], nil +} + +func (m *module) sign(session uint, mech Mechanism, key Object, data []byte) ([]byte, error) { + if len(data) == 0 { + return nil, errors.New("nothing to sign") + } + native := mechanism{typ: ulong(mech.Type)} + var params *pssParams + if mech.PSS != nil { + params = &pssParams{hashAlg: ulong(mech.PSS.Hash), mgf: ulong(mech.PSS.MGF), saltLen: ulong(mech.PSS.SaltLen)} + native.parameter = unsafe.Pointer(params) + native.len = ulong(unsafe.Sizeof(*params)) + } + rv := m.cSignInit(ulong(session), &native, ulong(key)) + runtime.KeepAlive(params) + if rv != rvOK { + return nil, Error{Op: "C_SignInit", Code: uint(rv)} + } + + var size ulong + if rv := m.cSign(ulong(session), &data[0], ulong(len(data)), nil, &size); rv != rvOK { + return nil, Error{Op: "C_Sign", Code: uint(rv)} + } + signature := make([]byte, size) + rv = m.cSign(ulong(session), &data[0], ulong(len(data)), &signature[0], &size) + runtime.KeepAlive(data) + if rv != rvOK { + return nil, Error{Op: "C_Sign", Code: uint(rv)} + } + return signature[:size], nil +} + +func (m *module) createObject(session uint, template []Attribute) (Object, error) { + attrs := toAttributes(template) + var object ulong + rv := m.cCreateObject(ulong(session), first(attrs), ulong(len(attrs)), &object) + runtime.KeepAlive(template) + if rv != rvOK { + return 0, Error{Op: "C_CreateObject", Code: uint(rv)} + } + return Object(object), nil +} + +func toAttributes(template []Attribute) []attribute { + attrs := make([]attribute, len(template)) + for i, a := range template { + attrs[i].typ = ulong(a.Type) + if len(a.Value) > 0 { + attrs[i].value = unsafe.Pointer(&a.Value[0]) + attrs[i].len = ulong(len(a.Value)) + } + } + return attrs +} + +func first(attrs []attribute) *attribute { + if len(attrs) == 0 { + return nil + } + return &attrs[0] +} + +// ULong encodes an integer attribute value the way the module reads a CK_ULONG. +func ULong(v uint) []byte { + buf := make([]byte, unsafe.Sizeof(ulong(0))) + binary.NativeEndian.PutUint64(buf, uint64(v)) + return buf +} diff --git a/client/internal/pkcs11/driver_linux_test.go b/client/internal/pkcs11/driver_linux_test.go new file mode 100644 index 000000000..b47b22890 --- /dev/null +++ b/client/internal/pkcs11/driver_linux_test.go @@ -0,0 +1,72 @@ +//go:build pkcs11 && linux && (amd64 || arm64) + +package pkcs11 + +import ( + "crypto/x509" + "os" + "testing" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStructLayoutsMatchTheCABI(t *testing.T) { + assert.Equal(t, uintptr(24), unsafe.Sizeof(attribute{}), "CK_ATTRIBUTE") + assert.Equal(t, uintptr(24), unsafe.Sizeof(mechanism{}), "CK_MECHANISM") + assert.Equal(t, uintptr(24), unsafe.Sizeof(pssParams{}), "CK_RSA_PKCS_PSS_PARAMS") + assert.Equal(t, uintptr(208), unsafe.Sizeof(tokenInfo{}), "CK_TOKEN_INFO") + assert.Equal(t, uintptr(48), unsafe.Sizeof(initializeArgs{}), "CK_C_INITIALIZE_ARGS") + assert.Equal(t, uintptr(8), unsafe.Offsetof(functionList{}.fn), "entry points follow the padded CK_VERSION") + assert.Equal(t, uintptr(8+68*8), unsafe.Sizeof(functionList{}), "CK_FUNCTION_LIST v2.40") +} + +// TestTrustModule_ListsSystemCertificates drives a real module through the binding: +// p11-kit's trust module exposes the system CA store as certificate objects with no login. +func TestTrustModule_ListsSystemCertificates(t *testing.T) { + module := loadFirst(t, + "/usr/lib/pkcs11/p11-kit-trust.so", + "/usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so", + "/usr/lib/aarch64-linux-gnu/pkcs11/p11-kit-trust.so", + "/usr/lib64/pkcs11/p11-kit-trust.so", + ) + + tokens, err := module.Tokens() + require.NoError(t, err) + require.NotEmpty(t, tokens, "the trust module must present at least one token") + + parsed := 0 + for _, token := range tokens { + session, err := module.OpenSession(token.Label, nil) + require.NoError(t, err, token.Label) + objects, err := session.FindObjects( + Attribute{Type: AttrClass, Value: ULong(ClassCertificate)}, + Attribute{Type: AttrCertificateType, Value: ULong(CertificateX509)}, + ) + require.NoError(t, err, token.Label) + for _, object := range objects { + der, err := session.Attribute(object, AttrValue) + require.NoError(t, err) + _, err = x509.ParseCertificate(der) + require.NoError(t, err, "CKA_VALUE must be a DER certificate") + parsed++ + } + session.Close() + } + assert.Positive(t, parsed, "system trust anchors must be readable through the binding") +} + +func loadFirst(t *testing.T, paths ...string) *Module { + t.Helper() + for _, path := range paths { + if _, err := os.Stat(path); err != nil { + continue + } + module, err := Load(path) + require.NoError(t, err, path) + return module + } + t.Skip("p11-kit trust module not installed") + return nil +} diff --git a/client/internal/pkcs11/driver_stub.go b/client/internal/pkcs11/driver_stub.go new file mode 100644 index 000000000..ad6a5fd8f --- /dev/null +++ b/client/internal/pkcs11/driver_stub.go @@ -0,0 +1,14 @@ +//go:build !(pkcs11 && linux && (amd64 || arm64)) + +package pkcs11 + +import "encoding/binary" + +func load(string) (driver, error) { + return nil, ErrUnsupported +} + +// ULong encodes an integer attribute value; no module ever reads it in this build. +func ULong(v uint) []byte { + return binary.NativeEndian.AppendUint64(nil, uint64(v)) +} diff --git a/client/internal/pkcs11/pkcs11.go b/client/internal/pkcs11/pkcs11.go new file mode 100644 index 000000000..639cd59a5 --- /dev/null +++ b/client/internal/pkcs11/pkcs11.go @@ -0,0 +1,235 @@ +// Package pkcs11 is a minimal PKCS#11 client. It loads a module at runtime without cgo, +// opens a token session, lists objects and signs with keys the token holds. It exists so +// certificates whose keys live in a TPM behind tpm2-pkcs11 can be proven; whatever a +// certificate store does not need is left out. +package pkcs11 + +import ( + "errors" + "fmt" + "sync" +) + +// Object classes, attribute types, mechanisms and generators from PKCS#11 v2.40. +const ( + ClassCertificate = 0x1 + ClassPublicKey = 0x2 + ClassPrivateKey = 0x3 + + CertificateX509 = 0x0 + + AttrClass = 0x0 + AttrToken = 0x1 + AttrLabel = 0x3 + AttrValue = 0x11 + AttrCertificateType = 0x80 + AttrSubject = 0x101 + AttrID = 0x102 + + MechRSAPKCSPSS = 0xd + MechSHA256 = 0x250 + MechSHA384 = 0x260 + MechECDSA = 0x1041 + + MGF1SHA256 = 0x2 + MGF1SHA384 = 0x3 + + rvOK = 0x0 + rvUserAlreadyLoggedIn = 0x100 + rvAlreadyInitialized = 0x191 +) + +var ErrUnsupported = errors.New("PKCS#11 modules need a build with the pkcs11 tag on linux/amd64 or linux/arm64") + +// Error is a PKCS#11 return value other than CKR_OK. +type Error struct { + Op string + Code uint +} + +func (e Error) Error() string { + if name, ok := returnValueNames[e.Code]; ok { + return fmt.Sprintf("%s: %s", e.Op, name) + } + return fmt.Sprintf("%s: CKR 0x%x", e.Op, e.Code) +} + +var returnValueNames = map[uint]string{ + 0x2: "CKR_HOST_MEMORY", + 0x3: "CKR_SLOT_ID_INVALID", + 0x5: "CKR_GENERAL_ERROR", + 0x7: "CKR_ARGUMENTS_BAD", + 0x12: "CKR_ATTRIBUTE_TYPE_INVALID", + 0x13: "CKR_ATTRIBUTE_VALUE_INVALID", + 0x30: "CKR_DEVICE_ERROR", + 0x54: "CKR_FUNCTION_NOT_SUPPORTED", + 0x68: "CKR_KEY_FUNCTION_NOT_PERMITTED", + 0x70: "CKR_MECHANISM_INVALID", + 0x71: "CKR_MECHANISM_PARAM_INVALID", + 0x82: "CKR_OBJECT_HANDLE_INVALID", + 0xa0: "CKR_PIN_INCORRECT", + 0xa4: "CKR_PIN_LOCKED", + 0xb3: "CKR_SESSION_HANDLE_INVALID", + 0xd0: "CKR_TEMPLATE_INCOMPLETE", + 0xd1: "CKR_TEMPLATE_INCONSISTENT", + 0xe0: "CKR_TOKEN_NOT_PRESENT", + 0x101: "CKR_USER_NOT_LOGGED_IN", + 0x150: "CKR_BUFFER_TOO_SMALL", + 0x190: "CKR_CRYPTOKI_NOT_INITIALIZED", +} + +// Attribute is one entry of a PKCS#11 template. Integer-valued attributes such as the +// object class are encoded with ULong. +type Attribute struct { + Type uint + Value []byte +} + +// Mechanism selects a signing algorithm. PSS carries the parameters CKM_RSA_PKCS_PSS needs. +type Mechanism struct { + Type uint + PSS *PSSParams +} + +type PSSParams struct { + Hash uint + MGF uint + SaltLen uint +} + +// Object is a handle the token issued for one of its objects. +type Object uint + +// Token is a slot with a token present. +type Token struct { + Slot uint + Label string +} + +// Module is a loaded and initialised PKCS#11 library. A module is loaded once per path +// and never finalised: tokens such as tpm2-pkcs11 do real work in C_Initialize, and the +// process exit releases everything anyway. +type Module struct { + d driver +} + +var ( + modulesMu sync.Mutex + modules = map[string]*Module{} +) + +// Load opens the shared library at path and initialises it, or returns the module already +// loaded from that path. +func Load(path string) (*Module, error) { + modulesMu.Lock() + defer modulesMu.Unlock() + if m, ok := modules[path]; ok { + return m, nil + } + d, err := load(path) + if err != nil { + return nil, err + } + m := &Module{d: d} + modules[path] = m + return m, nil +} + +func (m *Module) Tokens() ([]Token, error) { + return m.d.tokens() +} + +// OpenSession opens a read-only session with the token carrying label, or with the first +// token when label is empty, and logs in as the user when pin is not nil. An empty, +// non-nil pin still logs in. +func (m *Module) OpenSession(label string, pin []byte) (*Session, error) { + return m.openSession(label, pin, false) +} + +// OpenReadWriteSession is OpenSession for callers that create objects on the token. +func (m *Module) OpenReadWriteSession(label string, pin []byte) (*Session, error) { + return m.openSession(label, pin, true) +} + +func (m *Module) openSession(label string, pin []byte, readWrite bool) (*Session, error) { + token, err := m.token(label) + if err != nil { + return nil, err + } + handle, err := m.d.openSession(token.Slot, readWrite) + if err != nil { + return nil, err + } + s := &Session{d: m.d, handle: handle} + if pin == nil { + return s, nil + } + if err := m.d.login(handle, pin); err != nil { + s.Close() + return nil, err + } + s.loggedIn = true + return s, nil +} + +func (m *Module) token(label string) (Token, error) { + tokens, err := m.Tokens() + if err != nil { + return Token{}, err + } + for _, token := range tokens { + if label == "" || token.Label == label { + return token, nil + } + } + if label == "" { + return Token{}, errors.New("no token present") + } + return Token{}, fmt.Errorf("no token labelled %q among %d tokens", label, len(tokens)) +} + +// Session is an open session with one token. Close logs out again if the session logged in. +type Session struct { + d driver + handle uint + loggedIn bool +} + +func (s *Session) Close() { + if s.loggedIn { + s.d.logout(s.handle) + } + s.d.closeSession(s.handle) +} + +// FindObjects returns the handles of every object matching all attributes of template. +func (s *Session) FindObjects(template ...Attribute) ([]Object, error) { + return s.d.findObjects(s.handle, template) +} + +// Attribute reads one attribute of an object. +func (s *Session) Attribute(obj Object, typ uint) ([]byte, error) { + return s.d.attribute(s.handle, obj, typ) +} + +// Sign signs data, normally a digest, with the token-held key in a single operation. +func (s *Session) Sign(mech Mechanism, key Object, data []byte) ([]byte, error) { + return s.d.sign(s.handle, mech, key, data) +} + +// CreateObject stores a new object described by template on the token. +func (s *Session) CreateObject(template ...Attribute) (Object, error) { + return s.d.createObject(s.handle, template) +} + +type driver interface { + tokens() ([]Token, error) + openSession(slot uint, readWrite bool) (uint, error) + closeSession(session uint) + login(session uint, pin []byte) error + logout(session uint) + findObjects(session uint, template []Attribute) ([]Object, error) + attribute(session uint, obj Object, typ uint) ([]byte, error) + sign(session uint, mech Mechanism, key Object, data []byte) ([]byte, error) + createObject(session uint, template []Attribute) (Object, error) +} diff --git a/client/internal/pkcs11/uri.go b/client/internal/pkcs11/uri.go new file mode 100644 index 000000000..8794b7020 --- /dev/null +++ b/client/internal/pkcs11/uri.go @@ -0,0 +1,98 @@ +package pkcs11 + +import ( + "errors" + "fmt" + "net/url" + "os" + "strings" +) + +// DefaultModule is p11-kit's proxy, which exposes every module the system has registered, +// tpm2-pkcs11 included, so a URI without module-path works on a stock p11-kit setup. +const DefaultModule = "p11-kit-proxy.so" + +// URI is the subset of an RFC 7512 PKCS#11 URI this client understands: the token label, +// the module to load and where the user PIN comes from. Unknown attributes are ignored. +type URI struct { + Token string + ModulePath string + pinValue *string + pinSource string +} + +func ParseURI(raw string) (*URI, error) { + rest, ok := strings.CutPrefix(raw, "pkcs11:") + if !ok { + return nil, errors.New("PKCS#11 URI must start with pkcs11:") + } + path, query, _ := strings.Cut(rest, "?") + + u := &URI{} + if err := eachAttribute(path, ";", func(name, value string) { + if name == "token" { + u.Token = value + } + }); err != nil { + return nil, err + } + err := eachAttribute(query, "&", func(name, value string) { + switch name { + case "module-path": + u.ModulePath = value + case "module-name": + u.ModulePath = "lib" + value + ".so" + case "pin-value": + u.pinValue = &value + case "pin-source": + u.pinSource = value + } + }) + if err != nil { + return nil, err + } + return u, nil +} + +func eachAttribute(list, sep string, fn func(name, value string)) error { + if list == "" { + return nil + } + for _, pair := range strings.Split(list, sep) { + name, value, ok := strings.Cut(pair, "=") + if !ok { + return fmt.Errorf("PKCS#11 URI attribute %q has no value", pair) + } + value, err := url.PathUnescape(value) + if err != nil { + return fmt.Errorf("PKCS#11 URI attribute %s: %w", name, err) + } + fn(name, value) + } + return nil +} + +// Module is the library to load, DefaultModule when the URI names none. +func (u *URI) Module() string { + if u.ModulePath == "" { + return DefaultModule + } + return u.ModulePath +} + +// PIN returns the user PIN, or nil when the URI carries none and no login should happen. +// A pin-source names a file whose single line is the PIN. +func (u *URI) PIN() ([]byte, error) { + if u.pinValue != nil { + return []byte(*u.pinValue), nil + } + if u.pinSource == "" { + return nil, nil + } + path := strings.TrimPrefix(strings.TrimPrefix(u.pinSource, "file://"), "file:") + pin, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read PIN: %w", err) + } + return []byte(strings.TrimRight(string(pin), "\r\n")), nil +} diff --git a/client/internal/pkcs11/uri_test.go b/client/internal/pkcs11/uri_test.go new file mode 100644 index 000000000..63fc66d9a --- /dev/null +++ b/client/internal/pkcs11/uri_test.go @@ -0,0 +1,87 @@ +package pkcs11 + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseURI(t *testing.T) { + tests := []struct { + name string + raw string + wantToken string + wantModule string + wantPIN []byte + }{ + { + name: "token with module path and pin value", + raw: "pkcs11:token=netbird?module-path=/usr/lib/libtpm2_pkcs11.so&pin-value=1234", + wantToken: "netbird", + wantModule: "/usr/lib/libtpm2_pkcs11.so", + wantPIN: []byte("1234"), + }, + { + name: "module name becomes a library file", + raw: "pkcs11:token=netbird?module-name=tpm2_pkcs11", + wantToken: "netbird", + wantModule: "libtpm2_pkcs11.so", + }, + { + name: "percent encoding and unknown attributes", + raw: "pkcs11:model=SoftHSM%20v2;token=my%20token;serial=1?max-sessions=1", + wantToken: "my token", + wantModule: DefaultModule, + }, + { + name: "bare scheme uses the p11-kit proxy and no login", + raw: "pkcs11:", + wantModule: DefaultModule, + }, + { + name: "empty pin value still logs in", + raw: "pkcs11:?pin-value=", + wantModule: DefaultModule, + wantPIN: []byte{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + uri, err := ParseURI(tt.raw) + require.NoError(t, err) + assert.Equal(t, tt.wantToken, uri.Token, "token label") + assert.Equal(t, tt.wantModule, uri.Module(), "module to load") + pin, err := uri.PIN() + require.NoError(t, err) + assert.Equal(t, tt.wantPIN, pin, "PIN, nil meaning no login") + }) + } +} + +func TestParseURI_Rejections(t *testing.T) { + for _, raw := range []string{"pkcs11", "https://example.com", "pkcs11:token", "pkcs11:token=%zz"} { + _, err := ParseURI(raw) + assert.Error(t, err, raw) + } +} + +func TestURI_PINFromFile(t *testing.T) { + path := filepath.Join(t.TempDir(), "pin") + require.NoError(t, os.WriteFile(path, []byte("secret\n"), 0o600)) + + for _, source := range []string{path, "file:" + path, "file://" + path} { + uri, err := ParseURI("pkcs11:token=netbird?pin-source=" + source) + require.NoError(t, err) + pin, err := uri.PIN() + require.NoError(t, err) + assert.Equal(t, []byte("secret"), pin, "PIN from %s must drop the trailing newline", source) + } + + uri, err := ParseURI("pkcs11:?pin-source=" + filepath.Join(t.TempDir(), "missing")) + require.NoError(t, err) + _, err = uri.PIN() + assert.Error(t, err, "a missing PIN file must fail loudly instead of logging in without a PIN") +} diff --git a/client/internal/tpm/open_linux.go b/client/internal/tpm/open_linux.go new file mode 100644 index 000000000..b6356112a --- /dev/null +++ b/client/internal/tpm/open_linux.go @@ -0,0 +1,29 @@ +package tpm + +import ( + "errors" + "fmt" + "io" + "os" + + "github.com/google/go-tpm/tpmutil" +) + +// The kernel resource manager multiplexes clients and flushes what they leave behind, +// so it is tried before the raw device. +var devicePaths = []string{"/dev/tpmrm0", "/dev/tpm0"} + +func open() (io.ReadWriteCloser, error) { + if path := os.Getenv(DeviceEnv); path != "" { + return tpmutil.OpenTPM(path) + } + var errs error + for _, path := range devicePaths { + rwc, err := tpmutil.OpenTPM(path) + if err == nil { + return rwc, nil + } + errs = errors.Join(errs, err) + } + return nil, fmt.Errorf("open TPM: %w", errs) +} diff --git a/client/internal/tpm/open_other.go b/client/internal/tpm/open_other.go new file mode 100644 index 000000000..58bc38db9 --- /dev/null +++ b/client/internal/tpm/open_other.go @@ -0,0 +1,9 @@ +//go:build !linux + +package tpm + +import "io" + +func open() (io.ReadWriteCloser, error) { + return nil, ErrUnsupported +} diff --git a/client/internal/tpm/signer.go b/client/internal/tpm/signer.go new file mode 100644 index 000000000..b36097538 --- /dev/null +++ b/client/internal/tpm/signer.go @@ -0,0 +1,61 @@ +package tpm + +import ( + "crypto" + "errors" + "fmt" + "io" + + "go.step.sm/crypto/tpm/tss2" +) + +// KeyPEMType is the PEM block type of a TPM 2.0 key file as defined by +// draft-bottomley-tpm2-keys and written by tpm2-openssl and tpm2-tss-engine. +const KeyPEMType = "TSS2 PRIVATE KEY" + +var ErrKeyNeedsAuth = errors.New("TPM key requires an authorization value") + +// ParseKey reads a TSS2 key file and returns a signer that produces every signature +// inside the TPM; only the digest goes in and only the signature comes out. A key with +// a persistent parent is loaded under it, a key whose parent is a hierarchy under the +// TCG default ECC primary that tpm2-openssl and tpm2-tss-engine derive as well. Keys +// guarded by an authorization value are rejected, since nothing can supply it without +// prompting. +func ParseKey(der []byte) (crypto.Signer, error) { + key, err := tss2.ParsePrivateKey(der) + if err != nil { + return nil, fmt.Errorf("parse TSS2 key: %w", err) + } + if !key.EmptyAuth { + return nil, ErrKeyNeedsAuth + } + public, err := key.Public() + if err != nil { + return nil, fmt.Errorf("decode TSS2 public key: %w", err) + } + return &keySigner{key: key, public: public}, nil +} + +type keySigner struct { + key *tss2.TPMKey + public crypto.PublicKey +} + +func (s *keySigner) Public() crypto.PublicKey { + return s.public +} + +func (s *keySigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + rwc, err := Open() + if err != nil { + return nil, err + } + defer func() { _ = rwc.Close() }() + + signer, err := tss2.CreateSigner(rwc, s.key) + if err != nil { + return nil, fmt.Errorf("load TSS2 key: %w", err) + } + signer.SetSRKTemplate(tss2.ECCSRKTemplate) + return signer.Sign(rand, digest, opts) +} diff --git a/client/internal/tpm/signer_test.go b/client/internal/tpm/signer_test.go new file mode 100644 index 000000000..d1be2636c --- /dev/null +++ b/client/internal/tpm/signer_test.go @@ -0,0 +1,64 @@ +package tpm + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/pem" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.step.sm/crypto/tpm/tss2" + + "github.com/netbirdio/netbird/client/internal/tpm/tpmtest" +) + +func TestParseKey_ReportsPublicKeyWithoutTouchingTPM(t *testing.T) { + key := newP256Key(t) + + signer, err := ParseKey(decodePEM(t, tpmtest.KeyPEM(t, &key.PublicKey))) + require.NoError(t, err) + assert.True(t, key.PublicKey.Equal(signer.Public()), "signer must expose the key the TPM holds") +} + +func TestParseKey_RejectsKeyWithAuthorization(t *testing.T) { + key := newP256Key(t) + withAuth := func(k *tss2.TPMKey) { k.EmptyAuth = false } + + _, err := ParseKey(decodePEM(t, tpmtest.KeyPEM(t, &key.PublicKey, withAuth))) + assert.ErrorIs(t, err, ErrKeyNeedsAuth) +} + +func TestParseKey_RejectsMalformedKey(t *testing.T) { + _, err := ParseKey([]byte("not a TSS2 key")) + assert.Error(t, err) +} + +func TestSign_FailsWhenTPMIsUnreachable(t *testing.T) { + t.Setenv(DeviceEnv, filepath.Join(t.TempDir(), "missing")) + signer, err := ParseKey(decodePEM(t, tpmtest.KeyPEM(t, &newP256Key(t).PublicKey))) + require.NoError(t, err) + + digest := sha256.Sum256([]byte("challenge")) + _, err = signer.Sign(rand.Reader, digest[:], crypto.SHA256) + assert.Error(t, err, "signing must not fall back to software when the TPM is missing") +} + +func newP256Key(t *testing.T) *ecdsa.PrivateKey { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + return key +} + +func decodePEM(t *testing.T, pemData string) []byte { + t.Helper() + block, _ := pem.Decode([]byte(pemData)) + require.NotNil(t, block) + require.Equal(t, KeyPEMType, block.Type) + return block.Bytes +} diff --git a/client/internal/tpm/tpm.go b/client/internal/tpm/tpm.go new file mode 100644 index 000000000..ca519caa9 --- /dev/null +++ b/client/internal/tpm/tpm.go @@ -0,0 +1,19 @@ +// Package tpm is the client's one door to the platform TPM 2.0. It opens the device +// and turns TPM-held key files into signers; every operation opens the TPM, runs and +// closes it, so no handle outlives a call. +package tpm + +import ( + "errors" + "io" +) + +// DeviceEnv overrides the TPM device path, which also lets tests point at a swtpm socket. +const DeviceEnv = "NB_TPM_DEVICE" + +var ErrUnsupported = errors.New("TPM is not supported on this platform") + +// Open connects to the platform TPM 2.0. The caller closes it after one operation. +func Open() (io.ReadWriteCloser, error) { + return open() +} diff --git a/client/internal/tpm/tpmtest/tpmtest.go b/client/internal/tpm/tpmtest/tpmtest.go new file mode 100644 index 000000000..c119eb55d --- /dev/null +++ b/client/internal/tpm/tpmtest/tpmtest.go @@ -0,0 +1,48 @@ +// Package tpmtest builds TSS2 key files for tests, with or without a TPM behind them. +package tpmtest + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "testing" + + "github.com/google/go-tpm/legacy/tpm2" + "github.com/stretchr/testify/require" + "go.step.sm/crypto/tpm/tss2" +) + +const p256Bytes = 32 + +// SigningTemplate is the public area of an unrestricted P-256 signing key with no fixed +// scheme, the shape tpm2-openssl creates certificate keys in. +func SigningTemplate() tpm2.Public { + return tpm2.Public{ + Type: tpm2.AlgECC, + NameAlg: tpm2.AlgSHA256, + Attributes: tpm2.FlagSign | tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagNoDA, + ECCParameters: &tpm2.ECCParams{CurveID: tpm2.CurveNISTP256}, + } +} + +// KeyPEM encodes pub as a TSS2 PRIVATE KEY over a placeholder private blob: it parses +// and reports pub, but no TPM can load it. +func KeyPEM(t *testing.T, pub *ecdsa.PublicKey, opts ...tss2.TPMOption) string { + t.Helper() + require.Equal(t, elliptic.P256(), pub.Curve, "fixture keys must be P-256") + area := SigningTemplate() + area.ECCParameters.Point = tpm2.ECPoint{ + XRaw: pub.X.FillBytes(make([]byte, p256Bytes)), + YRaw: pub.Y.FillBytes(make([]byte, p256Bytes)), + } + encoded, err := area.Encode() + require.NoError(t, err) + return EncodePEM(t, encoded, []byte("placeholder"), opts...) +} + +// EncodePEM wraps the public and private blobs TPM2_Create returned into a TSS2 PRIVATE KEY. +func EncodePEM(t *testing.T, public, private []byte, opts ...tss2.TPMOption) string { + t.Helper() + pemBytes, err := tss2.New(public, private, opts...).EncodeToMemory() + require.NoError(t, err) + return string(pemBytes) +} diff --git a/go.mod b/go.mod index 03879a58a..569f0b475 100644 --- a/go.mod +++ b/go.mod @@ -17,27 +17,27 @@ require ( github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.34.1 github.com/rs/cors v1.8.0 - github.com/sirupsen/logrus v1.9.4 + github.com/sirupsen/logrus v1.10.1 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/vishvananda/netlink v1.3.1 - golang.org/x/crypto v0.55.0 - golang.org/x/sys v0.47.0 + golang.org/x/crypto v0.57.0 + golang.org/x/sys v0.48.0 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 golang.zx2c4.com/wireguard/windows v0.5.3 - google.golang.org/grpc v1.80.0 - google.golang.org/protobuf v1.36.11 + google.golang.org/grpc v1.83.2 + google.golang.org/protobuf v1.36.12 ) require ( github.com/DeRuina/timberjack v1.4.2 github.com/Microsoft/go-winio v0.6.2 github.com/awnumar/memguard v0.23.0 - github.com/aws/aws-sdk-go-v2 v1.38.3 + github.com/aws/aws-sdk-go-v2 v1.47.0 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 - github.com/aws/aws-sdk-go-v2/config v1.31.6 - github.com/aws/aws-sdk-go-v2/credentials v1.18.10 + github.com/aws/aws-sdk-go-v2/config v1.33.4 + github.com/aws/aws-sdk-go-v2/credentials v1.20.4 github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3 github.com/c-robinson/iplib v1.0.3 github.com/caddyserver/certmagic v0.21.3 @@ -65,6 +65,7 @@ require ( github.com/godbus/dbus/v5 v5.2.2 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/go-cmp v0.7.0 + github.com/google/go-tpm v0.9.8 github.com/google/gopacket v1.1.19 github.com/google/nftables v0.3.0 github.com/gopacket/gopacket v1.4.0 @@ -72,7 +73,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 - github.com/hashicorp/go-version v1.7.0 + github.com/hashicorp/go-version v1.9.0 github.com/jackc/pgx/v5 v5.10.0 github.com/libdns/route53 v1.5.0 github.com/libp2p/go-netroute v0.4.0 @@ -122,23 +123,24 @@ require ( github.com/yusufpapurcu/wmi v1.2.4 github.com/zcalusic/sysinfo v1.1.3 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 - go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel v1.44.0 go.opentelemetry.io/otel/exporters/prometheus v0.64.0 - go.opentelemetry.io/otel/metric v1.43.0 - go.opentelemetry.io/otel/sdk/metric v1.43.0 + go.opentelemetry.io/otel/metric v1.44.0 + go.opentelemetry.io/otel/sdk/metric v1.44.0 + go.step.sm/crypto v0.91.0 go.uber.org/mock v0.6.0 go.uber.org/zap v1.27.0 goauthentik.io/api/v3 v3.2023051.3 golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f golang.org/x/mobile v0.0.0-20260816165457-f98cc9b3c733 - golang.org/x/mod v0.39.0 - golang.org/x/net v0.58.0 + golang.org/x/mod v0.41.0 + golang.org/x/net v0.59.0 golang.org/x/oauth2 v0.36.0 - golang.org/x/sync v0.22.0 - golang.org/x/term v0.45.0 + golang.org/x/sync v0.23.0 + golang.org/x/term v0.46.0 golang.org/x/time v0.15.0 - google.golang.org/api v0.276.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 + google.golang.org/api v0.297.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.7 @@ -149,38 +151,38 @@ require ( ) require ( - cloud.google.com/go/auth v0.20.0 // indirect + cloud.google.com/go/auth v0.23.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect dario.cat/mergo v1.0.2 // indirect - filippo.io/edwards25519 v1.1.1 // indirect + filippo.io/edwards25519 v1.2.0 // indirect git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect github.com/AppsFlyer/go-sundheit v0.6.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ntlmssp v0.1.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/adrg/xdg v0.5.3 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/awnumar/memcall v0.4.0 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 // indirect github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect - github.com/aws/smithy-go v1.23.0 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.10.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.38.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.50.0 // indirect + github.com/aws/smithy-go v1.28.1 // indirect github.com/beevik/etree v1.6.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect @@ -197,7 +199,7 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-ldap/ldap/v3 v3.4.13 // indirect @@ -223,10 +225,9 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/go-tpm v0.9.8 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect - github.com/googleapis/gax-go/v2 v2.21.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.20 // indirect + github.com/googleapis/gax-go/v2 v2.24.1 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -260,8 +261,8 @@ require ( github.com/magiconair/properties v1.8.10 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.42 // indirect github.com/mdelapenya/tlscert v0.2.0 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect @@ -311,16 +312,16 @@ require ( github.com/zeebo/blake3 v0.2.3 // indirect go.mongodb.org/mongo-driver v1.17.9 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect - go.opentelemetry.io/otel/sdk v1.43.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/text v0.41.0 // indirect - golang.org/x/tools v0.49.0 // indirect + golang.org/x/text v0.42.0 // indirect + golang.org/x/tools v0.50.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index e9c666526..40b2006ac 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= -cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= +cloud.google.com/go/auth v0.23.2 h1:pxSCpfiji41hpzpPdMCftEUCezpgpqmmDdYiAjCKXxo= +cloud.google.com/go/auth v0.23.2/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= @@ -11,8 +11,8 @@ cunicu.li/go-rosenpass v0.5.42 h1:fRDsGwCxd7DhDgZI1Pxeo8GtNyq8BESZJ7w2/BGGJtU= cunicu.li/go-rosenpass v0.5.42/go.mod h1:YRBeyKOe/gWpSX2kpDUec5p9t0XOLsshTguId5gTGVg= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw= -filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= @@ -29,8 +29,8 @@ github.com/DeRuina/timberjack v1.4.2 h1:4bKlzhKdsR+2oNkgef9mqb4n11ICow8VK88RfzJP github.com/DeRuina/timberjack v1.4.2/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -50,44 +50,44 @@ github.com/awnumar/memcall v0.4.0 h1:B7hgZYdfH6Ot1Goaz8jGne/7i8xD4taZie/PNSFZ29g github.com/awnumar/memcall v0.4.0/go.mod h1:8xOx1YbfyuCg3Fy6TO8DK0kZUua3V42/goA5Ru47E8w= github.com/awnumar/memguard v0.23.0 h1:sJ3a1/SWlcuKIQ7MV+R9p0Pvo9CWsMbGZvcZQtmc68A= github.com/awnumar/memguard v0.23.0/go.mod h1:olVofBrsPdITtJ2HgxQKrEYEMyIBAIciVG4wNnZhW9M= -github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk= -github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2 v1.47.0 h1:0jsHallhJCeaU0Ko48c/3FK1ctOQ7NpzggxriJOQ8MQ= +github.com/aws/aws-sdk-go-v2 v1.47.0/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= -github.com/aws/aws-sdk-go-v2/config v1.31.6 h1:a1t8fXY4GT4xjyJExz4knbuoxSCacB5hT/WgtfPyLjo= -github.com/aws/aws-sdk-go-v2/config v1.31.6/go.mod h1:5ByscNi7R+ztvOGzeUaIu49vkMk2soq5NaH5PYe33MQ= -github.com/aws/aws-sdk-go-v2/credentials v1.18.10 h1:xdJnXCouCx8Y0NncgoptztUocIYLKeQxrCgN6x9sdhg= -github.com/aws/aws-sdk-go-v2/credentials v1.18.10/go.mod h1:7tQk08ntj914F/5i9jC4+2HQTAuJirq7m1vZVIhEkWs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 h1:wbjnrrMnKew78/juW7I2BtKQwa1qlf6EjQgS69uYY14= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6/go.mod h1:AtiqqNrDioJXuUgz3+3T0mBWN7Hro2n9wll2zRUc0ww= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 h1:uF68eJA6+S9iVr9WgX1NaRGyQ/6MdIyc4JNUo6TN1FA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6/go.mod h1:qlPeVZCGPiobx8wb1ft0GHT5l+dc6ldnwInDFaMvC7Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 h1:pa1DEC6JoI0zduhZePp3zmhWvk/xxm4NB8Hy/Tlsgos= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6/go.mod h1:gxEjPebnhWGJoaDdtDkA0JX46VRg1wcTHYe63OfX5pE= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 h1:R0tNFJqfjHL3900cqhXuwQ+1K4G0xc9Yf8EDbFXCKEw= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6/go.mod h1:y/7sDdu+aJvPtGXr4xYosdpq9a6T9Z0jkXfugmti0rI= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= +github.com/aws/aws-sdk-go-v2/config v1.33.4 h1:FzvkXKSzwqHni4U7nDigHg4jjtqMpVUuHgmZfSoJVQ0= +github.com/aws/aws-sdk-go-v2/config v1.33.4/go.mod h1:VZqGZnZsCWVfK/iGPptJIyNIX3XEX6iQU2Rel4sLrr8= +github.com/aws/aws-sdk-go-v2/credentials v1.20.4 h1:hTvrJJseKbvw32kmiE0G+u/9ZqpqscjDrTigHIXP2qs= +github.com/aws/aws-sdk-go-v2/credentials v1.20.4/go.mod h1:gWp9O1ZBWwpcIrgV+mVHk4gZUurAEDkgypu/OXOlIaw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 h1:AM4hHjww+PSFtt6E+UrBrPlZkWsePCLEt9AjkfQX+yM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0/go.mod h1:3x/yXezeQjpOvBb4jEMxrS8SXvpdvJ5abv6l5c1gWM8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3 h1:Hp/VgjP0BysR3OgLlR057Vz2LcbbVnoWeJ+3qWiS/fY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3/go.mod h1:nwGV5qw7F1IZPgxCvA/ph8N2TAuz+BkRG/bXn808qMA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3 h1:MUaM4f+kj1ZIBPZfUS8cxP1GKXXZtHJjAthy93AN7SM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3/go.mod h1:6YmVmEVRI5ZZzRjCSsb9SryKH0hAlMRdgA7kG9aDvBU= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3 h1:fuSCw4Z2qfRCztMPO3GXJNSiEp6Wee+WOLwrHHUMy9c= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3/go.mod h1:6SxcHheD1pPR5+kWm1wGvjlL/YqUsh267sAfEmN4K7A= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 h1:hncKj/4gR+TPauZgTAsxOxNcvBayhUlYZ6LO/BYiQ30= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6/go.mod h1:OiIh45tp6HdJDDJGnja0mw8ihQGz3VGrUflLqSL0SmM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 h1:LHS1YAIJXJ4K9zS+1d/xa9JAA9sL2QyXIQCQFQW/X08= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6/go.mod h1:c9PCiTEuh0wQID5/KqA32J+HAgZxN9tOGXKCiYJjTZI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3 h1:bON1rJf67TSTDCKg816AAIE4xSTtoo9tl0XRkO72R+I= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3/go.mod h1:c5BBpjJcQXpfeq9iASyVKA3T6vX6B6LEXY4mL/gklDY= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 h1:nEXUSAwyUfLTgnc9cxlDWy637qsq4UWwp3sNAfl0Z3Y= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6/go.mod h1:HGzIULx4Ge3Do2V0FaiYKcyKzOqwrhUZgCI77NisswQ= github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3 h1:MmLCRqP4U4Cw9gJ4bNrCG0mWqEtBlmAVleyelcHARMU= github.com/aws/aws-sdk-go-v2/service/route53 v1.42.3/go.mod h1:AMPjK2YnRh0YgOID3PqhJA1BRNfXDfGOnSsKHtAe8yA= github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3 h1:ETkfWcXP2KNPLecaDa++5bsQhCRa5M5sLUJa5DWYIIg= github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3/go.mod h1:+/3ZTqoYb3Ur7DObD00tarKMLMuKg8iqz5CHEanqTnw= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 h1:8OLZnVJPvjnrxEwHFg9hVUof/P4sibH+Ea4KKuqAGSg= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.1/go.mod h1:27M3BpVi0C02UiQh1w9nsBEit6pLhlaH3NHna6WUbDE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 h1:gKWSTnqudpo8dAxqBqZnDoDWCiEh/40FziUjr/mo6uA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2/go.mod h1:x7+rkNmRoEN1U13A6JE2fXne9EWyJy54o3n6d4mGaXQ= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 h1:YZPjhyaGzhDQEvsffDEcpycq49nl7fiGcfJTIo8BszI= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.2/go.mod h1:2dIN8qhQfv37BdUYGgEC8Q3tteM3zFxTI1MLO2O3J3c= -github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= -github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.0 h1:ZD5qFpWcaOKdTuhBi431pIDkCgrMkMlMT6jlpSPoIRI= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.0/go.mod h1:8Nuuf+tR346PjJ3MvZPh9pekbLiLQFWJhzMXfwy7alA= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.0 h1:JGeeBcMlhg1xtOXYpeCaTQBZObtXMPQCUqBcmr65NRA= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.0/go.mod h1:XwteswG9EOMRFm73UT0t+MbTwyLxMrEXkU6e+v92Lzo= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 h1:obhahQXDEdVEv8y5bTKXR30LVaxYe1kyYM0L7l2Iq+k= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0/go.mod h1:6twZZ/aXHNy1vXUO8koUbp++MYzMASkOgEBdkbJYmO0= +github.com/aws/aws-sdk-go-v2/service/sts v1.50.0 h1:khXV3+K5D3f4e8xtplaRdSFn1bEg3gj5EBHQvbCOZbQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.50.0/go.mod h1:/8JRcdTt//hG0Q4BTmGbuOplT7ABe+5rdtqUHqXvYIM= +github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ= +github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE= github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -160,10 +160,10 @@ github.com/eko/gocache/store/go_cache/v4 v4.2.2 h1:tAI9nl6TLoJyKG1ujF0CS0n/IgTEM github.com/eko/gocache/store/go_cache/v4 v4.2.2/go.mod h1:T9zkHokzr8K9EiC7RfMbDg6HSwaV6rv3UdcNu13SGcA= github.com/eko/gocache/store/redis/v4 v4.2.2 h1:Thw31fzGuH3WzJywsdbMivOmP550D6JS7GDHhvCJPA0= github.com/eko/gocache/store/redis/v4 v4.2.2/go.mod h1:LaTxLKx9TG/YUEybQvPMij++D7PBTIJ4+pzvk0ykz0w= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -279,8 +279,8 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= -github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc= -github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc= +github.com/google/go-tpm-tools v0.4.9 h1:jZEhnE4WRFbomSssBH2gWaIViIHU1gjH1jz76+xC9bI= +github.com/google/go-tpm-tools v0.4.9/go.mod h1:Omb8zosA8qY9URn1gsrO2i4b6DFqGp29BqNx18V66c4= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= @@ -292,10 +292,10 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= -github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= -github.com/googleapis/gax-go/v2 v2.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI= -github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4= +github.com/googleapis/enterprise-certificate-proxy v0.3.20 h1:t/xL64VUoN69MuMRQuJETqYGOw4Z9mSRJK9epIEtwFk= +github.com/googleapis/enterprise-certificate-proxy v0.3.20/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w= +github.com/googleapis/gax-go/v2 v2.24.1 h1:AtqTN21IXMMWo99LiEVAiBfNNQmO40d8xUfZI640mc0= +github.com/googleapis/gax-go/v2 v2.24.1/go.mod h1:bWeBei0NVwaNZKb2y1HUBS7gLXIF3/Tu3pq7j8D2Tb0= github.com/gopacket/gopacket v1.4.0 h1:cr1OlFpzksCkZHNO0eLjaSSOrMQnpPXg0j6qHIY3y2U= github.com/gopacket/gopacket v1.4.0/go.mod h1:EpvsxINeehp5qj4YMKMLf2/dekdhKn2IIAO/ZOifS7o= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= @@ -328,8 +328,8 @@ github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0Yg github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -421,11 +421,11 @@ github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo= github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= @@ -526,8 +526,9 @@ github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq5 github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs= github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= @@ -588,8 +589,8 @@ github.com/quic-go/quic-go v0.62.0 h1:ZHDjCk5OacATwGvs8PWE97CTvX7AqZiVoW7++ZOXTf github.com/quic-go/quic-go v0.62.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g= +github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= @@ -603,8 +604,8 @@ github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTO github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q= +github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= @@ -688,26 +689,30 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs= go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.step.sm/crypto v0.91.0 h1:0mN0DwVOvUuh7VbyTnxABZuAkxwak/Grp8Y/b4YaZDU= +go.step.sm/crypto v0.91.0/go.mod h1:NxxObRBymdbyXLoTCW5Ea6sLxuy5yI/xr9+hSBlbU/Q= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -734,8 +739,8 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -750,8 +755,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= -golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -770,8 +775,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= @@ -786,8 +791,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -822,8 +827,8 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -836,8 +841,8 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -849,8 +854,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -864,8 +869,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= -golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= +golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU= +golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -878,17 +883,17 @@ golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY= -google.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw= +google.golang.org/api v0.297.0 h1:WktxTsnnx0yZNnsR6j0q6hR21RnnK81FHTOPy/ux4OE= +google.golang.org/api v0.297.0/go.mod h1:S4m8x0M6OkQpkOzGk1y9JG2sm4fFQrMh6dxzjCTszhE= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= -google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= -google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= -google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= +google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= +google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4= +google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d/go.mod h1:WRrQ7/7N19PypuT0fxLOL5Lq0waoiRri4FbtHDEKrGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= +google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -899,8 +904,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/shared/management/certposture/certposture_test.go b/shared/management/certposture/certposture_test.go index 03617d96a..7073332ff 100644 --- a/shared/management/certposture/certposture_test.go +++ b/shared/management/certposture/certposture_test.go @@ -2,6 +2,9 @@ package certposture import ( "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" "crypto/x509" "testing" "time" @@ -138,6 +141,22 @@ func TestChainPEM_RoundTrip(t *testing.T) { assert.Equal(t, ca.Cert.Raw, chain[1].Raw) } +func TestVerify_AcceptsMaximumPSSSalt(t *testing.T) { + // A TPM chooses the PSS salt itself and older firmware uses the largest salt that + // fits, so a proof from such a key carries more salt than Sign asks a software key for. + ca := certtest.NewCA(t, "root") + key := certtest.RSAKey(t).(*rsa.PrivateKey) + leaf := ca.Issue(t, key, "device") + c := NewChallenger(secret) + nonce := c.Nonce(peerKey, now) + digest := sha256.Sum256(proofMessage(nonce, peerKey)) + sig, err := rsa.SignPSS(rand.Reader, key, crypto.SHA256, digest[:], &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthAuto}) + require.NoError(t, err) + + _, err = c.Verify(Proof{Nonce: nonce, Chain: [][]byte{leaf.Raw}, SigAlg: SigAlgRSAPSSSHA256, Signature: sig}, peerKey, now) + assert.NoError(t, err, "a valid PSS signature must verify regardless of salt length") +} + func signedProof(t *testing.T, c *Challenger, ca *certtest.CA, key crypto.Signer) Proof { t.Helper() leaf := ca.Issue(t, key, "device") diff --git a/shared/management/certposture/proof.go b/shared/management/certposture/proof.go index 8844bf07b..2c2df2132 100644 --- a/shared/management/certposture/proof.go +++ b/shared/management/certposture/proof.go @@ -131,7 +131,7 @@ func verifySignature(pub crypto.PublicKey, sigAlg string, msg, sig []byte) bool return ecdsa.VerifyASN1(pub.(*ecdsa.PublicKey), d[:], sig) case SigAlgRSAPSSSHA256: d := sha256.Sum256(msg) - return rsa.VerifyPSS(pub.(*rsa.PublicKey), crypto.SHA256, d[:], sig, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}) == nil + return rsa.VerifyPSS(pub.(*rsa.PublicKey), crypto.SHA256, d[:], sig, &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthAuto}) == nil case SigAlgEd25519: return ed25519.Verify(pub.(ed25519.PublicKey), msg, sig) }