From bd1938b0daad2319e37232c881557c5de5490a2d Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 6 Oct 2022 12:22:19 -0700 Subject: [PATCH] Add support for storing or sending attestation data to linkedca --- authority/linkedca.go | 37 ++++++++++------- authority/provisioners.go | 38 ++++++++++++++++++ authority/tls.go | 5 +++ authority/tls_test.go | 84 ++++++++++++++++++++++++++++----------- go.mod | 8 ++-- go.sum | 16 ++++---- 6 files changed, 139 insertions(+), 49 deletions(-) diff --git a/authority/linkedca.go b/authority/linkedca.go index 7179b1d7..7a950c02 100644 --- a/authority/linkedca.go +++ b/authority/linkedca.go @@ -278,6 +278,7 @@ func (c *linkedCaClient) StoreCertificateChain(p provisioner.Interface, fullchai PemCertificate: serializeCertificateChain(fullchain[0]), PemCertificateChain: serializeCertificateChain(fullchain[1:]...), Provisioner: createProvisionerIdentity(p), + AttestationData: createAttestationData(p), RaProvisioner: raProvisioner, EndpointId: endpointID, }) @@ -395,26 +396,34 @@ func createProvisionerIdentity(p provisioner.Interface) *linkedca.ProvisionerIde } } -type raProvisioner interface { - RAInfo() *provisioner.RAInfo -} - func createRegistrationAuthorityProvisioner(p provisioner.Interface) (*linkedca.RegistrationAuthorityProvisioner, string) { if rap, ok := p.(raProvisioner); ok { - info := rap.RAInfo() - typ := linkedca.Provisioner_Type_value[strings.ToUpper(info.ProvisionerType)] - return &linkedca.RegistrationAuthorityProvisioner{ - AuthorityId: info.AuthorityID, - Provisioner: &linkedca.ProvisionerIdentity{ - Id: info.ProvisionerID, - Type: linkedca.Provisioner_Type(typ), - Name: info.ProvisionerName, - }, - }, info.EndpointID + if info := rap.RAInfo(); info != nil { + typ := linkedca.Provisioner_Type_value[strings.ToUpper(info.ProvisionerType)] + return &linkedca.RegistrationAuthorityProvisioner{ + AuthorityId: info.AuthorityID, + Provisioner: &linkedca.ProvisionerIdentity{ + Id: info.ProvisionerID, + Type: linkedca.Provisioner_Type(typ), + Name: info.ProvisionerName, + }, + }, info.EndpointID + } } return nil, "" } +func createAttestationData(p provisioner.Interface) *linkedca.AttestationData { + if ap, ok := p.(attProvisioner); ok { + if data := ap.AttestationData(); data != nil { + return &linkedca.AttestationData{ + PermanentIdentifier: data.PermanentIdentifier, + } + } + } + return nil +} + func serializeCertificate(crt *x509.Certificate) string { if crt == nil { return "" diff --git a/authority/provisioners.go b/authority/provisioners.go index 33694cf9..bfa4eae5 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -25,6 +25,44 @@ import ( "github.com/smallstep/certificates/errs" ) +type raProvisioner interface { + RAInfo() *provisioner.RAInfo +} + +type attProvisioner interface { + AttestationData() *provisioner.AttestationData +} + +// wrapProvisioner wraps the given provisioner with RA information and +// attestation data. +func wrapProvisioner(p provisioner.Interface, attData *provisioner.AttestationData) *wrappedProvisioner { + var raInfo *provisioner.RAInfo + if rap, ok := p.(raProvisioner); ok { + raInfo = rap.RAInfo() + } + + return &wrappedProvisioner{ + Interface: p, + attestationData: attData, + raInfo: raInfo, + } +} + +// wrappedProvisioner implements raProvisioner and attProvisioner. +type wrappedProvisioner struct { + provisioner.Interface + attestationData *provisioner.AttestationData + raInfo *provisioner.RAInfo +} + +func (p *wrappedProvisioner) AttestationData() *provisioner.AttestationData { + return p.attestationData +} + +func (p *wrappedProvisioner) RAInfo() *provisioner.RAInfo { + return p.raInfo +} + // GetEncryptedKey returns the JWE key corresponding to the given kid argument. func (a *Authority) GetEncryptedKey(kid string) (string, error) { a.adminMutex.RLock() diff --git a/authority/tls.go b/authority/tls.go index 6ec98088..90316cc3 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -258,6 +258,11 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign } fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) + + // Wrap provisioner with extra information. + prov = wrapProvisioner(prov, attData) + + // Store certificate in the db. if err = a.storeCertificate(prov, fullchain); err != nil { if !errors.Is(err, db.ErrNotImplemented) { return nil, errs.Wrap(http.StatusInternalServerError, err, diff --git a/authority/tls_test.go b/authority/tls_test.go index 7b5a0b6c..dd52ea74 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -18,8 +18,6 @@ import ( "testing" "time" - "gopkg.in/square/go-jose.v2/jwt" - "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" @@ -59,6 +57,15 @@ func (m *certificateDurationEnforcer) Enforce(cert *x509.Certificate) error { return nil } +type certificateChainDB struct { + db.MockAuthDB + MStoreCertificateChain func(provisioner.Interface, ...*x509.Certificate) error +} + +func (d *certificateChainDB) StoreCertificateChain(p provisioner.Interface, certs ...*x509.Certificate) error { + return d.MStoreCertificateChain(p, certs...) +} + func getDefaultIssuer(a *Authority) *x509.Certificate { return a.x509CAService.(*softcas.SoftCAS).CertificateChain[len(a.x509CAService.(*softcas.SoftCAS).CertificateChain)-1] } @@ -767,7 +774,6 @@ ZYtQ9Ot36qc= aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - fmt.Println(crt.Subject) assert.Equals(t, crt.Subject.CommonName, "smallstep test") return nil }, @@ -793,6 +799,38 @@ ZYtQ9Ot36qc= extensionsCount: 6, } }, + "ok with attestation data": func(t *testing.T) *signTest { + csr := getCSR(t, priv) + aa := testAuthority(t) + aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template + aa.db = &certificateChainDB{ + MStoreCertificateChain: func(prov provisioner.Interface, certs ...*x509.Certificate) error { + p, ok := prov.(attProvisioner) + if assert.True(t, ok) { + assert.Equals(t, &provisioner.AttestationData{ + PermanentIdentifier: "1234567890", + }, p.AttestationData()) + } + if assert.Len(t, 2, certs) { + assert.Equals(t, certs[0].Subject.CommonName, "smallstep test") + assert.Equals(t, certs[1].Subject.CommonName, "smallstep Intermediate CA") + } + return nil + }, + } + + return &signTest{ + auth: aa, + csr: csr, + extraOpts: append(extraOpts, provisioner.AttestationData{ + PermanentIdentifier: "1234567890", + }), + signOpts: signOpts, + notBefore: signOpts.NotBefore.Time().Truncate(time.Second), + notAfter: signOpts.NotAfter.Time().Truncate(time.Second), + extensionsCount: 6, + } + }, } for name, genTestCase := range tests { @@ -1401,15 +1439,15 @@ func TestAuthority_Revoke(t *testing.T) { } }, "fail/nil-db": func() test { - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ @@ -1441,15 +1479,15 @@ func TestAuthority_Revoke(t *testing.T) { Err: errors.New("force"), })) - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ @@ -1481,15 +1519,15 @@ func TestAuthority_Revoke(t *testing.T) { Err: db.ErrAlreadyExists, })) - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ @@ -1520,15 +1558,15 @@ func TestAuthority_Revoke(t *testing.T) { }, })) - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ auth: _a, @@ -1612,15 +1650,15 @@ func TestAuthority_Revoke(t *testing.T) { }, })) - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ auth: a, diff --git a/go.mod b/go.mod index 5b4e432b..49a74458 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Masterminds/sprig/v3 v3.2.2 github.com/ThalesIgnite/crypto11 v1.2.5 // indirect - github.com/aws/aws-sdk-go v1.44.37 // indirect + github.com/aws/aws-sdk-go v1.44.111 // indirect github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd // indirect github.com/fatih/color v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.4.0 @@ -42,8 +42,8 @@ require ( github.com/urfave/cli v1.22.10 go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 go.step.sm/cli-utils v0.7.5 - go.step.sm/crypto v0.19.1-0.20220929182301-ae99d3fe3185 - go.step.sm/linkedca v0.19.0-rc.2 + go.step.sm/crypto v0.20.0 + go.step.sm/linkedca v0.19.0-rc.3 golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 golang.org/x/net v0.0.0-20220927171203-f486391704dc golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect @@ -59,7 +59,7 @@ require ( cloud.google.com/go/compute v1.7.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect cloud.google.com/go/kms v1.4.0 // indirect - filippo.io/edwards25519 v1.0.0-rc.1 // indirect + filippo.io/edwards25519 v1.0.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect diff --git a/go.sum b/go.sum index 59e98e5e..1c88faee 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= -filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= +filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= @@ -133,8 +133,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.44.37 h1:KvDxCX6dfJeEDC77U5GPGSP0ErecmNnhDHFxw+NIvlI= -github.com/aws/aws-sdk-go v1.44.37/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.111 h1:AcWfOgeedSQ4gQVwcIe6aLxpQNJMloZQyqnr7Dzki+s= +github.com/aws/aws-sdk-go v1.44.111/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -788,10 +788,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.19.1-0.20220929182301-ae99d3fe3185 h1:W+UhojTrFZngWTudpP3n9vPs4UNLudVSkKrWZuZg/RU= -go.step.sm/crypto v0.19.1-0.20220929182301-ae99d3fe3185/go.mod h1:972LarNeN9dgx4+zkF3fHCnTWLXzuQSIOdMaGeIslUY= -go.step.sm/linkedca v0.19.0-rc.2 h1:IcPqZ5y7MZNq1+VbYQcKoQEvX80NKRncU1WFCDyY+So= -go.step.sm/linkedca v0.19.0-rc.2/go.mod h1:MCZmPIdzElEofZbiw4eyUHayXgFTwa94cNAV34aJ5ew= +go.step.sm/crypto v0.20.0 h1:IMZUjTdol70IxOLZYHt8AHJy9uIHz/PvDOi2S+urpOs= +go.step.sm/crypto v0.20.0/go.mod h1:diT2XWIHQy0397UI3i78qCKeLLLp2wu0/DIJI66u/MU= +go.step.sm/linkedca v0.19.0-rc.3 h1:3Uu8j187wm7mby+/pz/aQ0wHKRm7w/2AsVPpvcAn4v8= +go.step.sm/linkedca v0.19.0-rc.3/go.mod h1:MCZmPIdzElEofZbiw4eyUHayXgFTwa94cNAV34aJ5ew= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=