diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 76caa79b..6cb5c420 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -10,9 +10,9 @@ jobs: steps: - uses: actions/setup-go@v3 with: - go-version: 1.20.x + go-version: 1.22.x - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.53.3 + version: v1.64.8 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 06e0d735..5d1dfbd8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: test-linux: strategy: matrix: - go-version: [1.20.x] + go-version: [1.22.x] runs-on: ubuntu-latest steps: - name: Install Go @@ -27,7 +27,7 @@ jobs: test-linux-tpm12: strategy: matrix: - go-version: [1.20.x] + go-version: [1.22.x] runs-on: ubuntu-latest steps: - name: Install Go @@ -43,7 +43,7 @@ jobs: test-macos: strategy: matrix: - go-version: [1.20.x] + go-version: [1.22.x] runs-on: macos-latest steps: - name: Install Go @@ -62,7 +62,7 @@ jobs: test-windows: strategy: matrix: - go-version: [1.20.x] + go-version: [1.22.x] runs-on: windows-latest steps: - name: Install Go diff --git a/attest/activation.go b/attest/activation.go index 0cd884b6..6103e306 100644 --- a/attest/activation.go +++ b/attest/activation.go @@ -23,9 +23,9 @@ const ( // activationSecretLen is the size in bytes of the generated secret // which is generated for credential activation. activationSecretLen = 32 - // symBlockSize is the block size used for symmetric ciphers used - // when generating the credential activation challenge. - symBlockSize = 16 + // defaultSymBlockSize is the block size used for symmetric ciphers + // used when generating the credential activation challenge. + defaultSymBlockSize = 16 // tpm20GeneratedMagic is a magic tag when can only be present on a // TPM structure if the structure was generated wholly by the TPM. tpm20GeneratedMagic = 0xff544347 @@ -239,7 +239,8 @@ func (p *ActivationParameters) generateChallengeTPM20(secret []byte) (*Encrypted if att.AttestedCreationInfo.Name.Digest == nil { return nil, fmt.Errorf("attestation creation info name has no digest") } - cred, encSecret, err := credactivation.Generate(att.AttestedCreationInfo.Name.Digest, p.EK, symBlockSize, secret) + + cred, encSecret, err := credactivation.Generate(att.AttestedCreationInfo.Name.Digest, p.EK, symBlockSizeForEK(p.EK), secret) if err != nil { return nil, fmt.Errorf("credactivation.Generate() failed: %v", err) } diff --git a/attest/certification.go b/attest/certification.go index d44343f2..8ae6ba05 100644 --- a/attest/certification.go +++ b/attest/certification.go @@ -17,6 +17,8 @@ package attest import ( "bytes" "crypto" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "errors" @@ -191,6 +193,23 @@ func (p *CertificationParameters) Verify(opts VerifyOpts) error { return nil } +func symBlockSizeForEK(ek crypto.PublicKey) int { + // see TCG EK Credential Profile Version 2.6, December 4, 2024, B.4.4 Storage EK Template + symBlockSize := defaultSymBlockSize // default to 16 bytes; 128 bits + switch pk := ek.(type) { + case *rsa.PublicKey: + if pk.Size() >= 384 { // RSA 3072, RSA 4096 + symBlockSize = 32 // 32 bytes; 256 bits + } + case *ecdsa.PublicKey: + if pk.Curve == elliptic.P384() || pk.Curve == elliptic.P521() { + symBlockSize = 32 // 32 bytes; 256 bits + } + } + + return symBlockSize +} + // Generate returns a credential activation challenge, which can be provided // to the TPM to verify the AK parameters given are authentic & the AK // is present on the same TPM as the EK. @@ -225,7 +244,7 @@ func (p *CertificationParameters) Generate(rnd io.Reader, verifyOpts VerifyOpts, return nil, nil, fmt.Errorf("attestation does not apply to certify data, got %x", att.Type) } - cred, encSecret, err := credactivation.Generate(activateOpts.VerifierKeyNameDigest, activateOpts.EK, symBlockSize, secret) + cred, encSecret, err := credactivation.Generate(activateOpts.VerifierKeyNameDigest, activateOpts.EK, symBlockSizeForEK(activateOpts.EK), secret) if err != nil { return nil, nil, fmt.Errorf("credactivation.Generate() failed: %v", err) } diff --git a/attest/certification_test.go b/attest/certification_test.go index d1c8acfc..f02b7055 100644 --- a/attest/certification_test.go +++ b/attest/certification_test.go @@ -22,6 +22,8 @@ package attest import ( "bytes" "crypto" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "testing" @@ -29,6 +31,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-tpm/legacy/tpm2" + "github.com/google/go-tpm/legacy/tpm2/credactivation" + "github.com/google/go-tpm/tpmutil" ) func TestSimTPM20CertificationParameters(t *testing.T) { @@ -397,3 +401,195 @@ func TestKeyActivationTPM20(t *testing.T) { }) } } + +func Test_credentialGeneration(t *testing.T) { + t.Parallel() + + packed, err := tpmutil.Pack([]byte{1, 2, 3, 4}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + digest := &tpm2.HashValue{ + Alg: tpm2.AlgSHA256, + Value: packed, + } + secret := []byte("the secret") + + t.Run("rsa-2048", func(t *testing.T) { + t.Parallel() + + k, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + pk := k.Public() + + _, _, err = credactivation.Generate(digest, pk, symBlockSizeForEK(pk), secret) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + + t.Run("rsa-3072", func(t *testing.T) { + t.Parallel() + + k, err := rsa.GenerateKey(rand.Reader, 3072) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + pk := k.Public() + + _, _, err = credactivation.Generate(digest, pk, symBlockSizeForEK(pk), secret) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + + t.Run("rsa-4096", func(t *testing.T) { + t.Parallel() + + k, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + pk := k.Public() + + _, _, err = credactivation.Generate(digest, pk, symBlockSizeForEK(pk), secret) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + + t.Run("ecdsa-P256", func(t *testing.T) { + t.Parallel() + + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + pk := k.Public() + + _, _, err = credactivation.Generate(digest, pk, symBlockSizeForEK(pk), secret) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + + t.Run("ecdsa-P384", func(t *testing.T) { + t.Parallel() + + k, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + pk := k.Public() + + _, _, err = credactivation.Generate(digest, pk, symBlockSizeForEK(pk), secret) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + + t.Run("ecdsa-P521", func(t *testing.T) { + t.Parallel() + + k, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + pk := k.Public() + + _, _, err = credactivation.Generate(digest, pk, symBlockSizeForEK(pk), secret) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) +} + +func Test_symBlockSizeForEK(t *testing.T) { + t.Parallel() + + t.Run("rsa-2048", func(t *testing.T) { + t.Parallel() + + k, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + symBlockSize := symBlockSizeForEK(k.Public()) + if symBlockSize != 16 { + t.Errorf("unexpected symBlockSize %d; expected 16", symBlockSize) + } + }) + + t.Run("rsa-3072", func(t *testing.T) { + t.Parallel() + + k, err := rsa.GenerateKey(rand.Reader, 3072) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + symBlockSize := symBlockSizeForEK(k.Public()) + if symBlockSize != 32 { + t.Errorf("unexpected symBlockSize %d; expected 32", symBlockSize) + } + }) + + t.Run("rsa-4096", func(t *testing.T) { + t.Parallel() + + k, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + symBlockSize := symBlockSizeForEK(k.Public()) + if symBlockSize != 32 { + t.Errorf("unexpected symBlockSize %d; expected 32", symBlockSize) + } + }) + + t.Run("ecdsa-P256", func(t *testing.T) { + t.Parallel() + + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + symBlockSize := symBlockSizeForEK(k.Public()) + if symBlockSize != 16 { + t.Errorf("unexpected symBlockSize %d; expected 16", symBlockSize) + } + }) + + t.Run("ecdsa-P384", func(t *testing.T) { + t.Parallel() + + k, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + symBlockSize := symBlockSizeForEK(k.Public()) + if symBlockSize != 32 { + t.Errorf("unexpected symBlockSize %d; expected 32", symBlockSize) + } + }) + + t.Run("ecdsa-P521", func(t *testing.T) { + t.Parallel() + + k, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + symBlockSize := symBlockSizeForEK(k.Public()) + if symBlockSize != 32 { + t.Errorf("unexpected symBlockSize %d; expected 32", symBlockSize) + } + }) +} diff --git a/attest/pcp_windows.go b/attest/pcp_windows.go index a13efb96..49428984 100644 --- a/attest/pcp_windows.go +++ b/attest/pcp_windows.go @@ -21,6 +21,7 @@ import ( "bytes" "crypto/x509" "encoding/binary" + "encoding/hex" "fmt" "io" "syscall" @@ -816,6 +817,9 @@ func (h *winPCP) ActivateCredential(hKey uintptr, activationBlob []byte) ([]byte return nil, err } + hexBlob := hex.EncodeToString(activationBlob) + fmt.Println("activation blob", hexBlob) + r, _, msg := nCryptSetProperty.Call(hKey, uintptr(unsafe.Pointer(&utf16ActivationStr[0])), uintptr(unsafe.Pointer(&activationBlob[0])), uintptr(len(activationBlob)), 0) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { @@ -828,10 +832,14 @@ func (h *winPCP) ActivateCredential(hKey uintptr, activationBlob []byte) ([]byte var size uint32 r, _, msg = nCryptGetProperty.Call(hKey, uintptr(unsafe.Pointer(&utf16ActivationStr[0])), uintptr(unsafe.Pointer(&secretBuff[0])), uintptr(len(secretBuff)), uintptr(unsafe.Pointer(&size)), 0) if r != 0 { + fmt.Println("secret size", size) + hexSecret := hex.EncodeToString(secretBuff) + fmt.Println("secret blob", hexSecret) + if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr } - return nil, fmt.Errorf("NCryptGetProperty returned %X (%v) for key activation", r, msg) + return nil, fmt.Errorf("NCryptGetProperty returned %X (%v) for key activation; activation blob: %s; secret size: %d; secret blob: %s", r, msg, hexBlob, size, hexSecret) } return secretBuff[:size], nil } diff --git a/go.mod b/go.mod index dabc1ab2..28be69f7 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/smallstep/go-attestation -go 1.20 +go 1.22 require ( github.com/google/go-cmp v0.5.9 - github.com/google/go-tpm v0.9.0 + github.com/google/go-tpm v0.9.6 github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba github.com/google/go-tspi v0.3.0 golang.org/x/sys v0.9.0 @@ -14,3 +14,6 @@ require ( github.com/google/certificate-transparency-go v1.1.2 // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect ) + +// required until https://github.com/google/go-tpm/pull/420 is merged +replace github.com/google/go-tpm => github.com/hslatman/go-tpm v0.0.0-20251217214657-7d0adf0a5e3b diff --git a/go.sum b/go.sum index 906fe7be..23923a6e 100644 --- a/go.sum +++ b/go.sum @@ -304,12 +304,7 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= github.com/google/go-sev-guest v0.6.1 h1:NajHkAaLqN9/aW7bCFSUplUMtDgk2+HcN7jC2btFtk0= -github.com/google/go-tpm v0.3.4-0.20230613064043-511507721cb1 h1:wGP91a6fiYbZhKlGcQD25K8XwXzoG4yHAEIjtpeV2QA= -github.com/google/go-tpm v0.3.4-0.20230613064043-511507721cb1/go.mod h1:Yj9bYgsIKoza8oMlxZqvqgUIDKFaExnuLaDdOtFCwG4= -github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= -github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= -github.com/google/go-tpm-tools v0.3.12 h1:hpWglH4RaZnGVbgOK3IThI5K++jnFvjQ94EIN34xrUU= -github.com/google/go-tpm-tools v0.3.12/go.mod h1:2OtmyPGPuaWWIOjr+IDhNQb6t5njjbSmZtzc350Q6Ro= +github.com/google/go-sev-guest v0.6.1/go.mod h1:UEi9uwoPbLdKGl1QHaq1G8pfCbQ4QP0swWX4J0k6r+Q= 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-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= @@ -317,6 +312,7 @@ github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5b github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licenseclassifier v0.0.0-20210325184830-bb04aff29e72/go.mod h1:qsqn2hxC+vURpyBRygGUuinTO42MFRLcsmQ/P8v94+M= github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ= +github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -348,6 +344,7 @@ github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -399,6 +396,8 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hslatman/go-tpm v0.0.0-20251217214657-7d0adf0a5e3b h1:2TKLuyKH5SXhCjbN5JjeTLWQfVdz83SgUAnSmCw5PqU= +github.com/hslatman/go-tpm v0.0.0-20251217214657-7d0adf0a5e3b/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= @@ -1192,6 +1191,7 @@ 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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=