diff --git a/acme/challenge_tpmsimulator_test.go b/acme/challenge_tpmsimulator_test.go new file mode 100644 index 00000000..4ce8e1db --- /dev/null +++ b/acme/challenge_tpmsimulator_test.go @@ -0,0 +1,225 @@ +//go:build tpmsimulator +// +build tpmsimulator + +package acme + +import ( + "context" + "crypto" + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/json" + "encoding/pem" + "net/url" + "testing" + + "github.com/fxamacker/cbor/v2" + "github.com/google/go-attestation/attest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.step.sm/crypto/jose" + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/minica" + "go.step.sm/crypto/tpm" + "go.step.sm/crypto/tpm/simulator" + tpmstorage "go.step.sm/crypto/tpm/storage" +) + +func newSimulatedTPM(t *testing.T) *tpm.TPM { + t.Helper() + tmpDir := t.TempDir() + tpm, err := tpm.New(withSimulator(t), tpm.WithStore(tpmstorage.NewDirstore(tmpDir))) // TODO: provide in-memory storage implementation instead + require.NoError(t, err) + return tpm +} + +func withSimulator(t *testing.T) tpm.NewTPMOption { + t.Helper() + var sim simulator.Simulator + t.Cleanup(func() { + if sim == nil { + return + } + err := sim.Close() + require.NoError(t, err) + }) + sim = simulator.New() + err := sim.Open() + require.NoError(t, err) + return tpm.WithSimulator(sim) +} + +func generateKeyID(t *testing.T, pub crypto.PublicKey) []byte { + t.Helper() + b, err := x509.MarshalPKIXPublicKey(pub) + require.NoError(t, err) + hash := sha256.Sum256(b) + return hash[:] +} + +func mustAttestTPM(t *testing.T, keyAuthorization string) ([]byte, crypto.Signer, *x509.Certificate) { + t.Helper() + aca, err := minica.New( + minica.WithGetSignerFunc( + func() (crypto.Signer, error) { + return keyutil.GenerateSigner("RSA", "", 2048) + }, + ), + ) + require.NoError(t, err) + + // prepare simulated TPM and create an AK + ctpm := newSimulatedTPM(t) + eks, err := ctpm.GetEKs(context.Background()) + require.NoError(t, err) + ak, err := ctpm.CreateAK(context.Background(), "first-ak") + require.NoError(t, err) + require.NotNil(t, ak) + + // extract the AK public key + ap, err := ak.AttestationParameters(context.Background()) + require.NoError(t, err) + akp, err := attest.ParseAKPublic(attest.TPMVersion20, ap.Public) + require.NoError(t, err) + + // create template and sign certificate for the AK public key + keyID := generateKeyID(t, eks[0].Public()) + template := &x509.Certificate{ + Subject: pkix.Name{ + CommonName: "testakcert", + }, + PublicKey: akp.Public, + URIs: []*url.URL{ + {Scheme: "urn", Opaque: "ek:sha256:" + base64.StdEncoding.EncodeToString(keyID)}, + }, + } + akCert, err := aca.Sign(template) + require.NoError(t, err) + require.NotNil(t, akCert) + + // create a new key attested by the AK, while including + // the key authorization bytes as qualifying data. + keyAuthSum := sha256.Sum256([]byte(keyAuthorization)) + config := tpm.AttestKeyConfig{ + Algorithm: "RSA", + Size: 2048, + QualifyingData: keyAuthSum[:], + } + key, err := ctpm.AttestKey(context.Background(), "first-ak", "first-key", config) + require.NoError(t, err) + require.NotNil(t, key) + require.Equal(t, "first-key", key.Name()) + require.NotEqual(t, 0, len(key.Data())) + require.Equal(t, "first-ak", key.AttestedBy()) + require.True(t, key.WasAttested()) + require.True(t, key.WasAttestedBy(ak)) + + signer, err := key.Signer(context.Background()) + require.NoError(t, err) + + // prepare the attestation object with the AK certificate chain, + // the attested key, its metadata and the signature signed by the + // AK. + params, err := key.CertificationParameters(context.Background()) + require.NoError(t, err) + attObj, err := cbor.Marshal(struct { + Format string `json:"fmt"` + AttStatement map[string]interface{} `json:"attStmt,omitempty"` + }{ + Format: "tpm", + AttStatement: map[string]interface{}{ + "ver": "2.0", + "x5c": []interface{}{akCert.Raw, aca.Intermediate.Raw}, + "alg": int64(-257), // RSA + "sig": params.CreateSignature, + "certInfo": params.CreateAttestation, + "pubArea": params.Public, + }, + }) + require.NoError(t, err) + + // marshal the ACME payload + payload, err := json.Marshal(struct { + AttObj string `json:"attObj"` + }{ + AttObj: base64.RawURLEncoding.EncodeToString(attObj), + }) + require.NoError(t, err) + + return payload, signer, aca.Root +} + +func Test_deviceAttest01ValidateWithTPMSimulator(t *testing.T) { + type args struct { + ctx context.Context + ch *Challenge + db DB + jwk *jose.JSONWebKey + payload []byte + } + type test struct { + args args + wantErr *Error + } + tests := map[string]func(t *testing.T) test{ + "ok": func(t *testing.T) test { + jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token") + payload, signer, root := mustAttestTPM(t, keyAuth) // TODO: value(s) for AK cert? + caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: root.Raw}) + ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot)) + return test{ + args: args{ + ctx: ctx, + jwk: jwk, + ch: &Challenge{ + ID: "chID", + AuthorizationID: "azID", + Token: "token", + Type: "device-attest-01", + Status: StatusPending, + Value: "device.id.12345678", + }, + payload: payload, + db: &MockDB{ + MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) { + assert.Equal(t, "azID", id) + return &Authorization{ID: "azID"}, nil + }, + MockUpdateAuthorization: func(ctx context.Context, az *Authorization) error { + fingerprint, err := keyutil.Fingerprint(signer.Public()) + assert.NoError(t, err) + assert.Equal(t, "azID", az.ID) + assert.Equal(t, fingerprint, az.Fingerprint) + return nil + }, + MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { + assert.Equal(t, "chID", updch.ID) + assert.Equal(t, "token", updch.Token) + assert.Equal(t, StatusValid, updch.Status) + assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) + assert.Equal(t, "device.id.12345678", updch.Value) + return nil + }, + }, + }, + wantErr: nil, + } + }, + } + for name, run := range tests { + t.Run(name, func(t *testing.T) { + tc := run(t) + + if err := deviceAttest01Validate(tc.args.ctx, tc.args.ch, tc.args.db, tc.args.jwk, tc.args.payload); err != nil { + assert.Error(t, tc.wantErr) + assert.EqualError(t, err, tc.wantErr.Error()) + return + } + + assert.Nil(t, tc.wantErr) + }) + } +} diff --git a/go.mod b/go.mod index 357605ef..170e4d08 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( cloud.google.com/go/security v1.13.0 github.com/Masterminds/sprig/v3 v3.2.3 github.com/ThalesIgnite/crypto11 v1.2.5 // indirect - github.com/aws/aws-sdk-go v1.44.225 // indirect + github.com/aws/aws-sdk-go v1.44.231 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/fxamacker/cbor/v2 v2.4.0 github.com/go-chi/chi v4.1.2+incompatible @@ -38,7 +38,7 @@ require ( github.com/urfave/cli v1.22.12 go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 go.step.sm/cli-utils v0.7.5 - go.step.sm/crypto v0.28.0 + go.step.sm/crypto v0.28.1-0.20230329145110-4ccd51b601c7 go.step.sm/linkedca v0.19.0 golang.org/x/crypto v0.7.0 golang.org/x/net v0.8.0 @@ -88,7 +88,9 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/certificate-transparency-go v1.1.4 // indirect + github.com/google/go-tpm-tools v0.3.10 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -121,11 +123,13 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/peterbourgon/diskv/v3 v3.0.1 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/schollz/jsonstore v1.1.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/spf13/cast v1.4.1 // indirect diff --git a/go.sum b/go.sum index df8ad46b..b88894a6 100644 --- a/go.sum +++ b/go.sum @@ -160,8 +160,8 @@ github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.225 h1:JNJpUg+M1cm4jtKnyex//Mw1Rv8QN/kWT3dtr+oLdW4= -github.com/aws/aws-sdk-go v1.44.225/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.231 h1:wH/ihcZzBv8F443PyRoUogWnEdDp1KYtSew7ji9LNIY= +github.com/aws/aws-sdk-go v1.44.231/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= @@ -379,6 +379,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v1.1.2-0.20210422104406-9f33727a7a18/go.mod h1:6CKh9dscIRoqc2kC6YUFICHZMT9NrClyPrRVFrdw1QQ= github.com/google/certificate-transparency-go v1.1.2-0.20210512142713-bed466244fa6/go.mod h1:aF2dp7Dh81mY8Y/zpzyXps4fQW5zQbDu2CxfpJB6NkI= @@ -405,19 +407,22 @@ github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOm github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 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.4.1 h1:IjxtGAvzR+zSyAqMc1FWfYKCg1cwPkBly9+Xog3YMZc= github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw= github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo= github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4= github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0= github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= -github.com/google/go-tpm-tools v0.3.9 h1:66nkOHZtqmHXVnqonQvPDmiPRn8lcKW3FXzynJiBphg= github.com/google/go-tpm-tools v0.3.9/go.mod h1:22JvWmHcD5w55cs+nMeqDGDxgNS15/2pDq2cLqnc3rc= +github.com/google/go-tpm-tools v0.3.10 h1:hz9EoyG4Ewa0leT3OvxlWprq14Lw0RBmfFcH9H9+Yas= +github.com/google/go-tpm-tools v0.3.10/go.mod h1:HQfQboO+M8pRtBfO5U3KMhwzfC/XC3TaMCgRfTpII8Q= github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= 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/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= @@ -769,10 +774,13 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= +github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= @@ -850,6 +858,8 @@ github.com/ryboe/q v1.0.19/go.mod h1:IoEB3Q2/p6n1qbhIQVuNyakxtnV4rNJ/XJPK+jsEa0M github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= +github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -1018,8 +1028,10 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.step.sm/cli-utils v0.7.5 h1:jyp6X8k8mN1B0uWJydTid0C++8tQhm2kaaAdXKQQzdk= go.step.sm/cli-utils v0.7.5/go.mod h1:taSsY8haLmXoXM3ZkywIyRmVij/4Aj0fQbNTlJvv71I= go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= -go.step.sm/crypto v0.28.0 h1:7IMd1dgN1yeyWo6OPcdtzb3c6njkEw6t4TE6mqj+f+M= -go.step.sm/crypto v0.28.0/go.mod h1:V9h+hJFNwndIPsjEfLfhSs9dx9LjdelnN20OgAla3y4= +go.step.sm/crypto v0.28.1-0.20230329130243-a1ebfc5bcff7 h1:2vVWEooAKQArOoGClfst8+/Mu8bu/EHthn3kCu26OGk= +go.step.sm/crypto v0.28.1-0.20230329130243-a1ebfc5bcff7/go.mod h1:PFmyUJUvF5YDVokruSlTDcENBD/QBDpBiV2zInfsgV0= +go.step.sm/crypto v0.28.1-0.20230329145110-4ccd51b601c7 h1:8h176gNghpnGnVY99fHHoFZaPOeDR9Q472KuxSCeeRA= +go.step.sm/crypto v0.28.1-0.20230329145110-4ccd51b601c7/go.mod h1:PFmyUJUvF5YDVokruSlTDcENBD/QBDpBiV2zInfsgV0= go.step.sm/linkedca v0.19.0 h1:xuagkR35wrJI2gnu6FAM+q3VmjwsHScvGcJsfZ0GdsI= go.step.sm/linkedca v0.19.0/go.mod h1:b7vWPrHfYLEOTSUZitFEcztVCpTc+ileIN85CwEAluM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=