mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-17 12:19:07 +02:00
start TPM support
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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 "<name>.key" file holds it.
|
||||
// either its private key or a sibling "<name>.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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build !linux
|
||||
|
||||
package tpm
|
||||
|
||||
import "io"
|
||||
|
||||
func open() (io.ReadWriteCloser, error) {
|
||||
return nil, ErrUnsupported
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user