diff --git a/.gitignore b/.gitignore index a8e05fde..7aed8053 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ log sarif-report.json test/log .idea/ +profile.cov diff --git a/attestation/context.go b/attestation/context.go index d73655da..bef8e7bc 100644 --- a/attestation/context.go +++ b/attestation/context.go @@ -137,7 +137,7 @@ func (ctx *AttestationContext) RunAttestors() error { order := runTypeOrder() for _, k := range order { - log.Debugf("starting %s attestors...", k.String()) + log.Debugf("Starting %s attestors...", k.String()) for _, att := range attestors[k] { log.Infof("Starting %v attestor...", att.Name()) ctx.runAttestor(att) diff --git a/cryptoutil/rsa.go b/cryptoutil/rsa.go index 1dfbb504..a3d617fc 100644 --- a/cryptoutil/rsa.go +++ b/cryptoutil/rsa.go @@ -76,7 +76,12 @@ func (v *RSAVerifier) Verify(data io.Reader, sig []byte) error { Hash: v.hash, } - return rsa.VerifyPSS(v.pub, v.hash, digest, sig, pssOpts) + // AWS KMS introduces the chance that attestations get signed by PKCS1v15 instead of PSS + if err := rsa.VerifyPSS(v.pub, v.hash, digest, sig, pssOpts); err != nil { + return rsa.VerifyPKCS1v15(v.pub, v.hash, digest, sig) + } + + return nil } func (v *RSAVerifier) Bytes() ([]byte, error) { diff --git a/cryptoutil/util.go b/cryptoutil/util.go index 84d86f48..2b96280b 100644 --- a/cryptoutil/util.go +++ b/cryptoutil/util.go @@ -20,10 +20,21 @@ import ( "crypto/x509" "encoding/hex" "encoding/pem" + "errors" "fmt" "io" ) +// PEMType is a specific type for string constants used during PEM encoding and decoding +type PEMType string + +const ( + // PublicKeyPEMType is the string "PUBLIC KEY" to be used during PEM encoding and decoding + PublicKeyPEMType PEMType = "PUBLIC KEY" + // PKCS1PublicKeyPEMType is the string "RSA PUBLIC KEY" used to parse PKCS#1-encoded public keys + PKCS1PublicKeyPEMType PEMType = "RSA PUBLIC KEY" +) + type ErrUnsupportedPEM struct { t string } @@ -85,6 +96,23 @@ func PublicPemBytes(pub interface{}) ([]byte, error) { return pemBytes, err } +// UnmarshalPEMToPublicKey converts a PEM-encoded byte slice into a crypto.PublicKey +func UnmarshalPEMToPublicKey(pemBytes []byte) (crypto.PublicKey, error) { + derBytes, _ := pem.Decode(pemBytes) + if derBytes == nil { + return nil, errors.New("PEM decoding failed") + } + switch derBytes.Type { + case string(PublicKeyPEMType): + return x509.ParsePKIXPublicKey(derBytes.Bytes) + case string(PKCS1PublicKeyPEMType): + return x509.ParsePKCS1PublicKey(derBytes.Bytes) + default: + return nil, fmt.Errorf("unknown Public key PEM file type: %v. Are you passing the correct public key?", + derBytes.Type) + } +} + func TryParsePEMBlock(block *pem.Block) (interface{}, error) { if block == nil { return nil, ErrInvalidPemBlock{} @@ -147,3 +175,27 @@ func TryParseCertificate(data []byte) (*x509.Certificate, error) { return cert, nil } + +// ComputeDigest calculates the digest value for the specified message using the supplied hash function +func ComputeDigest(rawMessage io.Reader, hashFunc crypto.Hash, supportedHashFuncs []crypto.Hash) ([]byte, crypto.Hash, error) { + var cryptoSignerOpts crypto.SignerOpts = hashFunc + hashedWith := cryptoSignerOpts.HashFunc() + if !isSupportedAlg(hashedWith, supportedHashFuncs) { + return nil, crypto.Hash(0), fmt.Errorf("unsupported hash algorithm: %q not in %v", hashedWith.String(), supportedHashFuncs) + } + + digest, err := Digest(rawMessage, hashedWith) + return digest, hashedWith, err +} + +func isSupportedAlg(alg crypto.Hash, supportedAlgs []crypto.Hash) bool { + if supportedAlgs == nil { + return true + } + for _, supportedAlg := range supportedAlgs { + if alg == supportedAlg { + return true + } + } + return false +} diff --git a/go.mod b/go.mod index 08744613..3b0672df 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,16 @@ module github.com/in-toto/go-witness go 1.21 require ( + cloud.google.com/go/kms v1.15.2 + github.com/aws/aws-sdk-go-v2 v1.17.5 + github.com/aws/aws-sdk-go-v2/config v1.18.14 + github.com/aws/aws-sdk-go-v2/service/kms v1.20.4 github.com/digitorus/pkcs7 v0.0.0-20230220124406-51331ccfc40f github.com/digitorus/timestamp v0.0.0-20230220124323-d542479a2425 github.com/edwarnicke/gitoid v0.0.0-20220710194850-1be5bfda1f9d github.com/go-git/go-git/v5 v5.11.0 github.com/in-toto/archivista v0.2.0 + github.com/jellydator/ttlcache/v3 v3.1.1 github.com/mattn/go-isatty v0.0.20 github.com/open-policy-agent/opa v0.49.2 github.com/owenrumney/go-sarif v1.1.1 @@ -21,9 +26,22 @@ require ( ) require ( + cloud.google.com/go/compute v1.23.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.2 // indirect dario.cat/mergo v1.0.0 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.13.14 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.18.4 // indirect + github.com/aws/smithy-go v1.13.5 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/coreos/go-oidc/v3 v3.5.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect @@ -36,10 +54,14 @@ require ( github.com/google/flatbuffers v2.0.8+incompatible // indirect github.com/google/go-containerregistry v0.13.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/s2a-go v0.1.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/letsencrypt/boulder v0.0.0-20221109233200-85aa52084eaf // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -51,9 +73,12 @@ require ( github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/zclconf/go-cty v1.12.1 // indirect + go.opencensus.io v0.24.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/tools v0.13.0 // indirect + google.golang.org/api v0.128.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect @@ -93,7 +118,7 @@ require ( golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/protobuf v1.32.0 gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 7e9e1dbf..194cfd3f 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,21 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4= +cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/kms v1.15.2 h1:lh6qra6oC4AyWe5fUUUBe/S27k12OHAleOOOw6KakdE= +cloud.google.com/go/kms v1.15.2/go.mod h1:3hopT4+7ooWRCjc2DxgnpESFxhIraaI2IpAVUEhbT/w= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= @@ -14,6 +27,7 @@ github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRB github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= @@ -21,18 +35,53 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go v1.44.334 h1:h2bdbGb//fez6Sv6PaYv868s9liDeoYM6hYsAqTB4MU= github.com/aws/aws-sdk-go v1.44.334/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.17.5 h1:TzCUW1Nq4H8Xscph5M/skINUitxM5UBAyvm2s7XBzL4= +github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2/config v1.18.14 h1:rI47jCe0EzuJlAO5ptREe3LIBAyP5c7gR3wjyYVjuOM= +github.com/aws/aws-sdk-go-v2/config v1.18.14/go.mod h1:0pI6JQBHKwd0JnwAZS3VCapLKMO++UL2BOkWwyyzTnA= +github.com/aws/aws-sdk-go-v2/credentials v1.13.14 h1:jE34fUepssrhmYpvPpdbd+d39PHpuignDpNPNJguP60= +github.com/aws/aws-sdk-go-v2/credentials v1.13.14/go.mod h1:85ckagDuzdIOnZRwws1eLKnymJs3ZM1QwVC1XcuNGOY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23 h1:Kbiv9PGnQfG/imNI4L/heyUXvzKmcWSBeDvkrQz5pFc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.23/go.mod h1:mOtmAg65GT1HIL/HT/PynwPbS+UG0BgCZ6vhkPqnxWo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 h1:9/aKwwus0TQxppPXFmf010DFrE+ssSbzroLVYINA+xE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29/go.mod h1:Dip3sIGv485+xerzVv24emnjX5Sg88utCL8fwGmCeWg= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 h1:b/Vn141DBuLVgXbhRWIrl9g+ww7G+ScV5SzniWR13jQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23/go.mod h1:mr6c4cHC+S/MMkrjtSlG4QA36kOznDep+0fga5L/fGQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30 h1:IVx9L7YFhpPq0tTnGo8u8TpluFu7nAn9X3sUDMb11c0= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.30/go.mod h1:vsbq62AOBwQ1LJ/GWKFxX8beUEYeRp/Agitrxee2/qM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23 h1:QoOybhwRfciWUBbZ0gp9S7XaDnCuSTeK/fySB99V1ls= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.23/go.mod h1:9uPh+Hrz2Vn6oMnQYiUi/zbh3ovbnQk19YKINkQny44= +github.com/aws/aws-sdk-go-v2/service/kms v1.20.4 h1:FOY3JSIwgItCdaeuLKjtijD8Enx6BHy5nSS/V6COOeA= +github.com/aws/aws-sdk-go-v2/service/kms v1.20.4/go.mod h1:oTK4GAHgyFSGKzhReYfD19/vjtgUOPwCbm7v5MgWLW4= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.3 h1:bUeZTWfF1vBdZnoNnnq70rB/CzdZD7NR2Jg2Ax+rvjA= +github.com/aws/aws-sdk-go-v2/service/sso v1.12.3/go.mod h1:jtLIhd+V+lft6ktxpItycqHqiVXrPIRjWIsFIlzMriw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.3 h1:G/+7NUi+q+H0LG3v32jfV4OkaQIcpI92g0owbXKk6NY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.3/go.mod h1:zVwRrfdSmbRZWkUkWjOItY7SOalnFnq/Yg2LVPqDjwc= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.4 h1:j0USUNbl9c/8tBJ8setEbwxc7wva0WyoeAaFRiyTUT8= +github.com/aws/aws-sdk-go-v2/service/sts v1.18.4/go.mod h1:1mKZHLLpDMHTNSYPJ7qrcnCQdHCWsNQaT0xRvq2u80s= +github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= +github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw= github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= @@ -59,6 +108,12 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01 h1:IeaD1VDVBPlx3viJT9Md8if8IxxJnO+x0JCGb054heg= @@ -95,12 +150,26 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -109,8 +178,12 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -120,6 +193,14 @@ github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbW github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.4 h1:uGy6JWR/uMIILU8wbf+OkstIrNiMjGpEIyhx8f6W7s4= +github.com/googleapis/enterprise-certificate-proxy v0.2.4/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A= github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0/go.mod h1:YDZoGHuwE+ov0c8smSH49WLF3F2LaWnYYuDVd+EWrc0= github.com/honeycombio/beeline-go v1.10.0 h1:cUDe555oqvw8oD76BQJ8alk7FP0JZ/M/zXpNvOEDLDc= @@ -130,6 +211,8 @@ github.com/in-toto/archivista v0.2.0 h1:FViuHMVVETborvOqlmSYdROY8RmX3CO0V0MOhU/R github.com/in-toto/archivista v0.2.0/go.mod h1:qt9uN4TkHWUgR5A2wxRqQIBizSl32P2nI2AjESskkr0= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jellydator/ttlcache/v3 v3.1.1 h1:RCgYJqo3jgvhl+fEWvjNW8thxGWsgxi+TPhRir1Y9y8= +github.com/jellydator/ttlcache/v3 v3.1.1/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -159,6 +242,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -180,6 +265,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= @@ -188,6 +274,7 @@ github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJf github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= @@ -212,11 +299,17 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spiffe/go-spiffe/v2 v2.1.7 h1:VUkM1yIyg/x8X7u1uXqSRVRCdMdfRIEdFBzpqoeASGk= github.com/spiffe/go-spiffe/v2 v2.1.7/go.mod h1:QJDGdhXllxjxvd5B+2XnhhXB/+rC8gr+lNrtOryiWeE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= @@ -256,30 +349,45 @@ github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.step.sm/crypto v0.25.2 h1:NgoI3bcNF0iLI+Rwq00brlJyFfMqseLOa8L8No3Daog= go.step.sm/crypto v0.25.2/go.mod h1:4pUEuZ+4OAf2f70RgW5oRv/rJudibcAAWQg5prC3DT8= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -291,9 +399,14 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -301,9 +414,11 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -342,6 +457,10 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -353,18 +472,43 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg= +google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8= google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE= google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +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= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 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= @@ -384,12 +528,15 @@ gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/apimachinery v0.26.13 h1:gTwNkZp+qrfZuhQFMD594ggzvcr06mbgAtLBTbdc4Mg= k8s.io/apimachinery v0.26.13/go.mod h1:2/HZp0l6coXtS26du1Bk36fCuAEr/lVs9Q9NbpBtd1Y= k8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M= diff --git a/policy/policy.go b/policy/policy.go index 799a2c0b..dddda9f0 100644 --- a/policy/policy.go +++ b/policy/policy.go @@ -18,11 +18,14 @@ import ( "bytes" "context" "crypto/x509" + "fmt" + "strings" "time" "github.com/in-toto/go-witness/attestation" "github.com/in-toto/go-witness/cryptoutil" "github.com/in-toto/go-witness/log" + "github.com/in-toto/go-witness/signer/kms" "github.com/in-toto/go-witness/source" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -54,10 +57,24 @@ type PublicKey struct { // PublicKeyVerifiers returns verifiers for each of the policy's embedded public keys grouped by the key's ID func (p Policy) PublicKeyVerifiers() (map[string]cryptoutil.Verifier, error) { verifiers := make(map[string]cryptoutil.Verifier) + var verifier cryptoutil.Verifier + var err error + for _, key := range p.PublicKeys { - verifier, err := cryptoutil.NewVerifierFromReader(bytes.NewReader(key.Key)) - if err != nil { - return nil, err + for _, prefix := range kms.SupportedProviders() { + if strings.HasPrefix(key.KeyID, prefix) { + verifier, err = kms.New(kms.WithRef(key.KeyID), kms.WithHash("SHA256")).Verifier(context.TODO()) + if err != nil { + return nil, fmt.Errorf("KMS Key ID recognized but not valid: %w", err) + } + } + } + + if verifier == nil { + verifier, err = cryptoutil.NewVerifierFromReader(bytes.NewReader(key.Key)) + if err != nil { + return nil, err + } } keyID, err := verifier.KeyID() diff --git a/registry/registry.go b/registry/registry.go index 506f15a9..9f62089c 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -81,7 +81,6 @@ func (r Registry[T]) AllEntries() []Entry[T] { } return results - } // NewEntity creates a new entity with the the default options set diff --git a/signer/kms/aws/client.go b/signer/kms/aws/client.go new file mode 100644 index 00000000..f67ac5ec --- /dev/null +++ b/signer/kms/aws/client.go @@ -0,0 +1,518 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "context" + "crypto" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io" + "net/http" + "regexp" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + akms "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/log" + "github.com/in-toto/go-witness/registry" + "github.com/in-toto/go-witness/signer" + "github.com/in-toto/go-witness/signer/kms" + ttlcache "github.com/jellydator/ttlcache/v3" + "github.com/mitchellh/go-homedir" +) + +type client interface { + getHashFunc(ctx context.Context) (crypto.Hash, error) + sign(ctx context.Context, digest []byte, _ crypto.Hash) ([]byte, error) + verify(ctx context.Context, sig, message io.Reader) error + setupClient(ctx context.Context, ksp *kms.KMSSignerProvider) (err error) + fetchKeyMetadata(ctx context.Context) (*types.KeyMetadata, error) + fetchPublicKey(ctx context.Context) (crypto.PublicKey, error) +} + +func init() { + kms.AddProvider(ReferenceScheme, &awsClientOptions{}, func(ctx context.Context, ksp *kms.KMSSignerProvider) (cryptoutil.Signer, error) { + return LoadSignerVerifier(ctx, ksp) + }) +} + +const ( + cacheKey = "signer" + // ReferenceScheme schemes for various KMS services are copied from https://github.com/google/go-cloud/tree/master/secrets + ReferenceScheme = "awskms://" +) + +var ( + errKMSReference = errors.New("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)") + + // Key ID/ALIAS/ARN conforms to KMS standard documented here: https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id + // Key format examples: + // Key ID: awskms:///1234abcd-12ab-34cd-56ef-1234567890ab + // Key ID with endpoint: awskms://localhost:4566/1234abcd-12ab-34cd-56ef-1234567890ab + // Key ARN: awskms:///arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab + // Key ARN with endpoint: awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab + // Alias name: awskms:///alias/ExampleAlias + // Alias name with endpoint: awskms://localhost:4566/alias/ExampleAlias + // Alias ARN: awskms:///arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias + // Alias ARN with endpoint: awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias + uuidRE = `m?r?k?-?[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}` + arnRE = `arn:(?:aws|aws-us-gov|aws-cn):kms:[a-z0-9-]+:\d{12}:` + hostRE = `([^/]*)/` + keyIDRE = regexp.MustCompile(`^awskms://` + hostRE + `(` + uuidRE + `)$`) + keyARNRE = regexp.MustCompile(`^awskms://` + hostRE + `(` + arnRE + `key/` + uuidRE + `)$`) + aliasNameRE = regexp.MustCompile(`^awskms://` + hostRE + `((alias/.*))$`) + aliasARNRE = regexp.MustCompile(`^awskms://` + hostRE + `(` + arnRE + `(alias/.*))$`) + allREs = []*regexp.Regexp{keyIDRE, keyARNRE, aliasNameRE, aliasARNRE} + providerName = fmt.Sprintf("kms-%s", strings.TrimSuffix(ReferenceScheme, "kms://")) +) + +// ValidReference returns a non-nil error if the reference string is invalid +func ValidReference(ref string) error { + for _, re := range allREs { + if re.MatchString(ref) { + return nil + } + } + return errKMSReference +} + +// ParseReference parses an awskms-scheme URI into its constituent parts. +func ParseReference(resourceID string) (endpoint, keyID, alias string, err error) { + var v []string + for _, re := range allREs { + v = re.FindStringSubmatch(resourceID) + if len(v) >= 3 { + endpoint, keyID = v[1], v[2] + if len(v) == 4 { + alias = v[3] + } + return + } + } + err = fmt.Errorf("invalid awskms format %q", resourceID) + return +} + +type awsClient struct { + client *akms.Client + endpoint string + keyID string + alias string + keyCache *ttlcache.Cache[string, cmk] + options *awsClientOptions +} + +type awsClientOptions struct { + insecureSkipVerify bool + credentialsFile string + configFile string + profile string + verifyRemotely bool +} + +type Option func(*awsClientOptions) + +func (a *awsClientOptions) Init() []registry.Configurer { + return []registry.Configurer{ + registry.BoolConfigOption( + "remote-verify", + "verify signature using AWS KMS remote verification. If false, the public key will be pulled from AWS KMS and verification will take place locally", + true, + func(sp signer.SignerProvider, verify bool) (signer.SignerProvider, error) { + ksp, ok := sp.(*kms.KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided signer provider is not a kms signer provider") + } + + co, ok := ksp.Options[providerName].(*awsClientOptions) + if !ok { + return sp, fmt.Errorf("failed to get aws client options from aws kms signer provider") + } + + WithRemoteVerify(verify)(co) + return ksp, nil + }, + ), + registry.BoolConfigOption( + "insecure-skip-verify", + "Skip verification of the server's certificate chain and host name", + false, + func(sp signer.SignerProvider, insecure bool) (signer.SignerProvider, error) { + ksp, ok := sp.(*kms.KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided signer provider is not a kms signer provider") + } + + co, ok := ksp.Options[providerName].(*awsClientOptions) + if !ok { + return sp, fmt.Errorf("failed to get aws client options from aws kms signer provider") + } + + WithInsecureSkipVerify(insecure)(co) + return ksp, nil + }, + ), + registry.StringConfigOption( + "credentials-file", + "The shared credentials file to use with the AWS KMS signer provider", + "", + func(sp signer.SignerProvider, cred string) (signer.SignerProvider, error) { + ksp, ok := sp.(*kms.KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided signer provider is not a kms signer provider") + } + + co, ok := ksp.Options[providerName].(*awsClientOptions) + if !ok { + return sp, fmt.Errorf("failed to get aws client options from aws kms signer provider") + } + + WithCredentialsFile(cred)(co) + return ksp, nil + }, + ), + registry.StringConfigOption( + "config-file", + "The shared configuration file to use with the AWS KMS signer provider", + "", + func(sp signer.SignerProvider, config string) (signer.SignerProvider, error) { + ksp, ok := sp.(*kms.KMSSignerProvider) + + if !ok { + return sp, fmt.Errorf("provided signer provider is not a kms signer provider") + } + + co, ok := ksp.Options[providerName].(*awsClientOptions) + if !ok { + return sp, fmt.Errorf("failed to get aws client options from aws kms signer provider") + } + + WithConfigFile(config)(co) + return ksp, nil + }, + ), + registry.StringConfigOption( + "profile", + "The shared configuration profile to use with the AWS KMS signer provider", + "", + func(sp signer.SignerProvider, profile string) (signer.SignerProvider, error) { + ksp, ok := sp.(*kms.KMSSignerProvider) + + if !ok { + return sp, fmt.Errorf("provided signer provider is not a kms signer provider") + } + + co, ok := ksp.Options[providerName].(*awsClientOptions) + if !ok { + return sp, fmt.Errorf("failed to get aws client options from aws kms signer provider") + } + + WithProfile(profile)(co) + return ksp, nil + }, + ), + } +} + +func (*awsClientOptions) ProviderName() string { + return providerName +} + +func WithInsecureSkipVerify(insecure bool) Option { + return func(opts *awsClientOptions) { + opts.insecureSkipVerify = insecure + } +} + +func WithRemoteVerify(remote bool) Option { + return func(opts *awsClientOptions) { + opts.verifyRemotely = remote + } +} + +func WithCredentialsFile(cred string) Option { + return func(opts *awsClientOptions) { + opts.credentialsFile = cred + } +} + +func WithConfigFile(config string) Option { + return func(opts *awsClientOptions) { + opts.configFile = config + } +} + +func WithProfile(profile string) Option { + return func(opts *awsClientOptions) { + opts.profile = profile + } +} + +func newAWSClient(ctx context.Context, ksp *kms.KMSSignerProvider) (*awsClient, error) { + if err := ValidReference(ksp.Reference); err != nil { + return nil, err + } + a := &awsClient{} + var err error + a.endpoint, a.keyID, a.alias, err = ParseReference(ksp.Reference) + if err != nil { + return nil, err + } + + if err := a.setupClient(ctx, ksp); err != nil { + return nil, err + } + + a.keyCache = ttlcache.New[string, cmk]( + ttlcache.WithDisableTouchOnHit[string, cmk](), + ) + + return a, nil +} + +func (a *awsClient) setupClient(ctx context.Context, ksp *kms.KMSSignerProvider) (err error) { + var ok bool + for _, opt := range ksp.Options { + a.options, ok = opt.(*awsClientOptions) + if ok { + break + } + } + + if a.options == nil { + return fmt.Errorf("unable to find aws client options in aws kms signer provider") + } + + opts := []func(*config.LoadOptions) error{} + if a.endpoint != "" { + opts = append(opts, config.WithEndpointResolverWithOptions( + aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) { + return aws.Endpoint{ + URL: "https://" + a.endpoint, + }, nil + }), + )) + } + + if a.options.insecureSkipVerify { + log.Warn("InsecureSkipVerify is enabled for AWS KMS attestor") + opts = append(opts, config.WithHTTPClient(&http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // nolint: gosec + }, + })) + } + + if a.options.credentialsFile != "" { + f, err := homedir.Expand(a.options.credentialsFile) + if err != nil { + return fmt.Errorf("expanding credentials file to full path: %w", err) + } + + log.Debug("Using file ", f, " as credentials file for AWS KMS provider") + opts = append(opts, config.WithSharedCredentialsFiles([]string{f})) + } + + if a.options.configFile != "" { + f, err := homedir.Expand(a.options.configFile) + if err != nil { + return fmt.Errorf("expanding credentials file to full path: %w", err) + } + + log.Debug("Using file ", f, " as config file for AWS KMS provider") + opts = append(opts, config.WithSharedConfigFiles([]string{f})) + } + + if a.options.profile != "" { + log.Debug("using profile ", a.options.profile, " for AWS KMS provider") + opts = append(opts, config.WithSharedConfigProfile(a.options.profile)) + } + + cfg, err := config.LoadDefaultConfig(ctx, opts...) + if err != nil { + return fmt.Errorf("loading AWS config: %w", err) + } + + a.client = akms.NewFromConfig(cfg) + return +} + +func (a *awsClient) fetchCMK(ctx context.Context) (*cmk, error) { + var err error + cmk := &cmk{} + cmk.PublicKey, err = a.fetchPublicKey(ctx) + if err != nil { + return nil, err + } + cmk.KeyMetadata, err = a.fetchKeyMetadata(ctx) + if err != nil { + return nil, err + } + return cmk, nil +} + +func (a *awsClient) getHashFunc(ctx context.Context) (crypto.Hash, error) { + cmk, err := a.getCMK(ctx) + if err != nil { + return 0, err + } + return cmk.HashFunc(), nil +} + +func (a *awsClient) getCMK(ctx context.Context) (*cmk, error) { + var lerr error + loader := ttlcache.LoaderFunc[string, cmk]( + func(c *ttlcache.Cache[string, cmk], key string) *ttlcache.Item[string, cmk] { + var k *cmk + k, lerr = a.fetchCMK(ctx) + if lerr == nil { + return c.Set(cacheKey, *k, time.Second*300) + } + return nil + }, + ) + + item := a.keyCache.Get(cacheKey, ttlcache.WithLoader[string, cmk](loader)) + if lerr == nil { + cmk := item.Value() + return &cmk, nil + } + return nil, lerr +} + +func (a *awsClient) verify(ctx context.Context, sig, message io.Reader) error { + s, err := io.ReadAll(sig) + if err != nil { + return err + } + + if a.options.verifyRemotely { + return a.verifyRemotely(ctx, s, message) + } + + log.Debug("Verifying signature with AWS KMS locally") + + cmk, err := a.getCMK(ctx) + if err != nil { + return err + } + + verifier, err := cmk.Verifier() + if err != nil { + return err + } + + return verifier.Verify(message, s) +} + +func (a *awsClient) verifyRemotely(ctx context.Context, sig []byte, message io.Reader) error { + cmk, err := a.getCMK(ctx) + if err != nil { + return err + } + + // if we verify remotely, we need to compute the digest first + digest, _, err := cryptoutil.ComputeDigest(message, cmk.HashFunc(), awsSupportedHashFuncs) + if err != nil { + return err + } + + alg := cmk.KeyMetadata.SigningAlgorithms[0] + messageType := types.MessageTypeDigest + if _, err := a.client.Verify(ctx, &akms.VerifyInput{ + KeyId: &a.keyID, + Message: digest, + MessageType: messageType, + Signature: sig, + SigningAlgorithm: alg, + }); err != nil { + return fmt.Errorf("unable to verify signature: %w", err) + } + + return nil +} + +func (a *awsClient) sign(ctx context.Context, digest []byte, _ crypto.Hash) ([]byte, error) { + cmk, err := a.getCMK(ctx) + if err != nil { + return nil, err + } + alg := cmk.KeyMetadata.SigningAlgorithms[0] + + messageType := types.MessageTypeDigest + out, err := a.client.Sign(ctx, &akms.SignInput{ + KeyId: &a.keyID, + Message: digest, + MessageType: messageType, + SigningAlgorithm: alg, + }) + if err != nil { + return nil, fmt.Errorf("signing with kms: %w", err) + } + return out.Signature, nil +} + +func (a *awsClient) fetchPublicKey(ctx context.Context) (crypto.PublicKey, error) { + out, err := a.client.GetPublicKey(ctx, &akms.GetPublicKeyInput{ + KeyId: &a.keyID, + }) + if err != nil { + return nil, fmt.Errorf("getting public key: %w", err) + } + key, err := x509.ParsePKIXPublicKey(out.PublicKey) + if err != nil { + return nil, fmt.Errorf("parsing public key: %w", err) + } + return key, nil +} + +func (a *awsClient) fetchKeyMetadata(ctx context.Context) (*types.KeyMetadata, error) { + out, err := a.client.DescribeKey(ctx, &akms.DescribeKeyInput{ + KeyId: &a.keyID, + }) + if err != nil { + return nil, fmt.Errorf("getting key metadata: %w", err) + } + return out.KeyMetadata, nil +} + +type cmk struct { + KeyMetadata *types.KeyMetadata + PublicKey crypto.PublicKey +} + +func (c *cmk) HashFunc() crypto.Hash { + switch c.KeyMetadata.SigningAlgorithms[0] { + case types.SigningAlgorithmSpecRsassaPssSha256, types.SigningAlgorithmSpecRsassaPkcs1V15Sha256, types.SigningAlgorithmSpecEcdsaSha256: + return crypto.SHA256 + case types.SigningAlgorithmSpecRsassaPssSha384, types.SigningAlgorithmSpecRsassaPkcs1V15Sha384, types.SigningAlgorithmSpecEcdsaSha384: + return crypto.SHA384 + case types.SigningAlgorithmSpecRsassaPssSha512, types.SigningAlgorithmSpecRsassaPkcs1V15Sha512, types.SigningAlgorithmSpecEcdsaSha512: + return crypto.SHA512 + default: + return 0 + } +} + +func (c *cmk) Verifier() (cryptoutil.Verifier, error) { + return cryptoutil.NewVerifier(c.PublicKey, cryptoutil.VerifyWithHash(c.HashFunc())) +} diff --git a/signer/kms/aws/fakeclient.go b/signer/kms/aws/fakeclient.go new file mode 100644 index 00000000..6193bbcc --- /dev/null +++ b/signer/kms/aws/fakeclient.go @@ -0,0 +1,173 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "bytes" + "context" + "crypto" + "crypto/rand" + "crypto/rsa" + "time" + + akms "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/signer/kms" + ttlcache "github.com/jellydator/ttlcache/v3" +) + +var ( + aid = "012345678901" + arn = "arn:aws:kms:us-west-2:012345678901:key/12345678-1234-1234-1234-123456789012" +) + +func createRsaKey() (*rsa.PrivateKey, *rsa.PublicKey, error) { + privKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + return privKey, &privKey.PublicKey, nil +} + +func createTestKey() (cryptoutil.Signer, cryptoutil.Verifier, error) { + privKey, pubKey, err := createRsaKey() + if err != nil { + return nil, nil, err + } + + signer := cryptoutil.NewRSASigner(privKey, crypto.SHA256) + verifier := cryptoutil.NewRSAVerifier(pubKey, crypto.SHA256) + return signer, verifier, nil +} + +type fakeAWSClient struct { + client *akms.Client + endpoint string + keyID string + alias string + keyCache *ttlcache.Cache[string, cmk] + privateKey *rsa.PrivateKey + hash crypto.Hash +} + +func newFakeAWSClient(ctx context.Context, ksp *kms.KMSSignerProvider) (*fakeAWSClient, error) { + a, err := newAWSClient(ctx, ksp) + if err != nil { + return nil, err + } + + c := &fakeAWSClient{ + client: a.client, + endpoint: a.endpoint, + keyID: a.keyID, + alias: a.alias, + keyCache: a.keyCache, + hash: ksp.HashFunc, + } + + return c, nil +} + +func (a *fakeAWSClient) fetchCMK(ctx context.Context) (*cmk, error) { + var err error + cmk := &cmk{} + cmk.PublicKey, err = a.fetchPublicKey(ctx) + if err != nil { + return nil, err + } + cmk.KeyMetadata, err = a.fetchKeyMetadata(ctx) + if err != nil { + return nil, err + } + return cmk, nil +} + +func (a *fakeAWSClient) getCMK(ctx context.Context) (*cmk, error) { + var lerr error + loader := ttlcache.LoaderFunc[string, cmk]( + func(c *ttlcache.Cache[string, cmk], key string) *ttlcache.Item[string, cmk] { + var k *cmk + k, lerr = a.fetchCMK(ctx) + if lerr == nil { + return c.Set(cacheKey, *k, time.Second*300) + } + return nil + }, + ) + + item := a.keyCache.Get(cacheKey, ttlcache.WithLoader[string, cmk](loader)) + if lerr == nil { + cmk := item.Value() + return &cmk, nil + } + return nil, lerr +} + +// At the moment this function lies unused, but it is here for future if necessary + +func (a *fakeAWSClient) verifyRemotely(ctx context.Context, sig, digest []byte) error { + c, err := a.getCMK(ctx) + if err != nil { + return err + } + + v, err := cryptoutil.NewVerifier(c.PublicKey, cryptoutil.VerifyWithHash(a.hash)) + if err != nil { + return err + } + + return v.Verify(bytes.NewReader(digest), sig) +} + +func (a *fakeAWSClient) sign(ctx context.Context, digest []byte, _ crypto.Hash) ([]byte, error) { + _, err := a.getCMK(ctx) + if err != nil { + return nil, err + } + + signer, err := cryptoutil.NewSigner(a.privateKey, cryptoutil.SignWithHash(a.hash)) + if err != nil { + return nil, err + } + + s, err := signer.Sign(bytes.NewReader(digest)) + if err != nil { + return nil, err + } + + return s, nil +} + +func (a *fakeAWSClient) fetchPublicKey(ctx context.Context) (crypto.PublicKey, error) { + k, p, err := createRsaKey() + if err != nil { + return nil, err + } + a.privateKey = k + + return p, nil +} + +func (a *fakeAWSClient) fetchKeyMetadata(ctx context.Context) (*types.KeyMetadata, error) { + km := &types.KeyMetadata{ + KeyId: &a.keyID, + AWSAccountId: &aid, + Arn: &arn, + } + + return km, nil +} diff --git a/signer/kms/aws/signer.go b/signer/kms/aws/signer.go new file mode 100644 index 00000000..937a23ea --- /dev/null +++ b/signer/kms/aws/signer.go @@ -0,0 +1,143 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "bytes" + "context" + "crypto" + "fmt" + "io" + + "github.com/aws/aws-sdk-go-v2/service/kms/types" + "github.com/in-toto/go-witness/cryptoutil" + kms "github.com/in-toto/go-witness/signer/kms" +) + +var awsSupportedAlgorithms = []types.CustomerMasterKeySpec{ + types.CustomerMasterKeySpecRsa2048, + types.CustomerMasterKeySpecRsa3072, + types.CustomerMasterKeySpecRsa4096, + types.CustomerMasterKeySpecEccNistP256, + types.CustomerMasterKeySpecEccNistP384, + types.CustomerMasterKeySpecEccNistP521, +} + +var awsSupportedHashFuncs = []crypto.Hash{ + crypto.SHA256, + crypto.SHA384, + crypto.SHA512, +} + +// SignerVerifier is a cryptoutil.SignerVerifier that uses the AWS Key Management Service +type SignerVerifier struct { + reference string + client client + hashFunc crypto.Hash +} + +// LoadSignerVerifier generates signatures using the specified key object in AWS KMS and hash algorithm. +func LoadSignerVerifier(ctx context.Context, ksp *kms.KMSSignerProvider) (*SignerVerifier, error) { + a := &SignerVerifier{ + reference: ksp.Reference, + } + + var err error + a.client, err = newAWSClient(ctx, ksp) + if err != nil { + return nil, err + } + + for _, hashFunc := range awsSupportedHashFuncs { + if hashFunc == ksp.HashFunc { + a.hashFunc = ksp.HashFunc + } + } + + if a.hashFunc == 0 { + return nil, fmt.Errorf("unsupported hash function: %v", ksp.HashFunc) + } + + return a, nil +} + +// NOTE: This might be all wrong but setting it like so for now +// +// KeyID returns the key identifier for the key used by this signer. +func (a *SignerVerifier) KeyID() (string, error) { + return a.reference, nil +} + +// Sign signs the provided message using AWS KMS. If the message is provided, +// this method will compute the digest according to the hash function specified +// when the Signer was created. +func (a *SignerVerifier) Sign(message io.Reader) ([]byte, error) { + var err error + ctx := context.TODO() + var digest []byte + + var signerOpts crypto.SignerOpts + signerOpts, err = a.client.getHashFunc(ctx) + if err != nil { + return nil, fmt.Errorf("getting fetching default hash function: %w", err) + } + + hf := signerOpts.HashFunc() + + digest, _, err = cryptoutil.ComputeDigest(message, hf, awsSupportedHashFuncs) + if err != nil { + return nil, err + } + + return a.client.sign(ctx, digest, hf) +} + +// Verifier returns a cryptoutil.Verifier that can be used to verify signatures created by this signer. +func (a *SignerVerifier) Verifier() (cryptoutil.Verifier, error) { + return a, nil +} + +// Bytes returns the bytes of the public key that can be used to verify signatures created by the signer. +func (a *SignerVerifier) Bytes() ([]byte, error) { + ctx := context.TODO() + p, err := a.client.fetchPublicKey(ctx) + if err != nil { + return nil, err + } + + return cryptoutil.PublicPemBytes(p) +} + +// Verify verifies the signature for the given message, returning +// nil if the verification succeeded, and an error message otherwise. +func (a *SignerVerifier) Verify(message io.Reader, sig []byte) (err error) { + ctx := context.TODO() + + return a.client.verify(ctx, bytes.NewReader(sig), message) +} + +// SupportedAlgorithms returns the list of algorithms supported by the AWS KMS service +func (*SignerVerifier) SupportedAlgorithms() []string { + s := make([]string, len(awsSupportedAlgorithms)) + for i := range awsSupportedAlgorithms { + s[i] = string(awsSupportedAlgorithms[i]) + } + return s +} + +// DefaultAlgorithm returns the default algorithm for the AWS KMS service +func (*SignerVerifier) DefaultAlgorithm() string { + return string(types.CustomerMasterKeySpecEccNistP256) +} diff --git a/signer/kms/aws/signer_test.go b/signer/kms/aws/signer_test.go new file mode 100644 index 00000000..9a848935 --- /dev/null +++ b/signer/kms/aws/signer_test.go @@ -0,0 +1,374 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "bytes" + "context" + "crypto" + "fmt" + "testing" + + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/signer/kms" + "github.com/stretchr/testify/assert" +) + +func TestParseReference(t *testing.T) { + tests := []struct { + in string + wantEndpoint string + wantKeyID string + wantAlias string + wantErr bool + }{ + { + in: "awskms:///1234abcd-12ab-34cd-56ef-1234567890ab", + wantEndpoint: "", + wantKeyID: "1234abcd-12ab-34cd-56ef-1234567890ab", + wantAlias: "", + wantErr: false, + }, + { + // multi-region key + in: "awskms:///mrk-1234abcd12ab34cd56ef1234567890ab", + wantEndpoint: "", + wantKeyID: "mrk-1234abcd12ab34cd56ef1234567890ab", + wantAlias: "", + wantErr: false, + }, + { + in: "awskms:///1234ABCD-12AB-34CD-56EF-1234567890AB", + wantEndpoint: "", + wantKeyID: "1234ABCD-12AB-34CD-56EF-1234567890AB", + wantAlias: "", + wantErr: false, + }, + { + in: "awskms://localhost:4566/1234abcd-12ab-34cd-56ef-1234567890ab", + wantEndpoint: "localhost:4566", + wantKeyID: "1234abcd-12ab-34cd-56ef-1234567890ab", + wantAlias: "", + wantErr: false, + }, + { + in: "awskms:///arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + wantEndpoint: "", + wantKeyID: "arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + wantAlias: "", + wantErr: false, + }, + { + in: "awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + wantEndpoint: "localhost:4566", + wantKeyID: "arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + wantAlias: "", + wantErr: false, + }, + { + in: "awskms:///alias/ExampleAlias", + wantEndpoint: "", + wantKeyID: "alias/ExampleAlias", + wantAlias: "alias/ExampleAlias", + wantErr: false, + }, + { + in: "awskms://localhost:4566/alias/ExampleAlias", + wantEndpoint: "localhost:4566", + wantKeyID: "alias/ExampleAlias", + wantAlias: "alias/ExampleAlias", + wantErr: false, + }, + { + in: "awskms:///arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias", + wantEndpoint: "", + wantKeyID: "arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias", + wantAlias: "alias/ExampleAlias", + wantErr: false, + }, + { + in: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:alias/ExampleAlias", + wantEndpoint: "", + wantKeyID: "arn:aws-us-gov:kms:us-gov-west-1:111122223333:alias/ExampleAlias", + wantAlias: "alias/ExampleAlias", + wantErr: false, + }, + { + in: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + wantEndpoint: "", + wantKeyID: "arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + wantAlias: "", + wantErr: false, + }, + { + in: "awskms://localhost:4566/arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias", + wantEndpoint: "localhost:4566", + wantKeyID: "arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias", + wantAlias: "alias/ExampleAlias", + wantErr: false, + }, + { + // missing alias/ prefix + in: "awskms:///missingalias", + wantEndpoint: "", + wantKeyID: "", + wantAlias: "", + wantErr: true, + }, + { + // invalid UUID + in: "awskms:///1234abcd-12ab-YYYY-56ef-1234567890ab", + wantEndpoint: "", + wantKeyID: "", + wantAlias: "", + wantErr: true, + }, + { + // Currently, references without endpoints must use 3 + // slashes. It would be nice to support this format, + // but that would be harder to parse. + in: "awskms://1234abcd-12ab-34cd-56ef-1234567890ab", + wantEndpoint: "", + wantKeyID: "", + wantAlias: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + gotEndpoint, gotKeyID, gotAlias, err := ParseReference(tt.in) + if (err != nil) != tt.wantErr { + t.Errorf("ParseReference() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotEndpoint != tt.wantEndpoint { + t.Errorf("ParseReference() gotEndpoint = %v, want %v", gotEndpoint, tt.wantEndpoint) + } + if gotKeyID != tt.wantKeyID { + t.Errorf("ParseReference() gotKeyID = %v, want %v", gotKeyID, tt.wantKeyID) + } + if gotAlias != tt.wantAlias { + t.Errorf("ParseReference() gotAlias = %v, want %v", gotAlias, tt.wantAlias) + } + }) + } +} + +func TestSign(t *testing.T) { + tests := []struct { + name string + ref string + hash crypto.Hash + message string + wantErr bool + expectedErr error + }{ + { + name: "successful sign", + ref: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + hash: crypto.SHA256, + message: "foo", + wantErr: false, + }, + { + name: "SHA-512", + ref: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + hash: crypto.SHA512, + message: "foo", + wantErr: false, + }, + { + name: "bad ref", + ref: "blablabla", + hash: crypto.SHA256, + message: "foo", + wantErr: true, + expectedErr: fmt.Errorf("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)"), + }, + { + name: "unsupported hash algorithm", + ref: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + hash: crypto.RIPEMD160, + message: "foo", + wantErr: true, + expectedErr: fmt.Errorf(`unsupported hash algorithm: "RIPEMD-160" not in [SHA-256 SHA-384 SHA-512]`), + }, + { + name: "gcp ref", + ref: "gcpkms://projects/testproject-23231/locations/europe-west2/keyRings/test/cryptoKeys/test", + hash: crypto.SHA256, + message: "foobarbaz", + wantErr: true, + expectedErr: fmt.Errorf("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)"), + }, + } + + for _, tt := range tests { + fmt.Println("sign test: ", tt.name) + ctx := context.TODO() + dig, _, err := cryptoutil.ComputeDigest(bytes.NewReader([]byte(tt.message)), tt.hash, awsSupportedHashFuncs) + if tt.wantErr && err != nil { + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + ksp := kms.New(kms.WithRef(tt.ref), kms.WithHash(tt.hash.String())) + c, err := newFakeAWSClient(context.TODO(), ksp) + if tt.wantErr && err != nil { + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + s, err := c.sign(ctx, dig, tt.hash) + if tt.wantErr && err != nil { + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + if s == nil { + t.Fatal("signature is nil") + } + + if tt.wantErr { + t.Fatalf("expected test %s to fail", tt.name) + } + + } +} + +func TestVerify(t *testing.T) { + tests := []struct { + name string + ref string + hash crypto.Hash + mess []string + wantErr bool + expectedErr error + }{ + { + name: "successful sign", + hash: crypto.SHA256, + ref: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + mess: []string{"foo", "bar", "baz"}, + wantErr: false, + }, + { + name: "SHA-512", + hash: crypto.SHA512, + ref: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + mess: []string{"foo", "bar", "baz"}, + wantErr: false, + }, + { + name: "bad ref", + hash: crypto.SHA256, + ref: "blablabla", + mess: []string{"foo", "bar", "baz"}, + wantErr: true, + expectedErr: fmt.Errorf("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)"), + }, + { + name: "unsupported hash algorithm", + hash: crypto.RIPEMD160, + ref: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + mess: []string{"foo", "bar", "baz"}, + wantErr: true, + expectedErr: fmt.Errorf(`unsupported hash algorithm: "RIPEMD-160" not in [SHA-256 SHA-384 SHA-512]`), + }, + { + name: "gcp ref", + hash: crypto.SHA256, + ref: "gcpkms://projects/testproject-23231/locations/europe-west2/keyRings/test/cryptoKeys/test", + mess: []string{"foo", "bar", "baz"}, + wantErr: true, + expectedErr: fmt.Errorf("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)"), + }, + } + + for _, tt := range tests { + errFound := false + fmt.Println("verify test: ", tt.name) + ctx := context.TODO() + ksp := kms.New(kms.WithRef(tt.ref), kms.WithHash(tt.hash.String())) + c, err := newFakeAWSClient(context.TODO(), ksp) + if tt.wantErr && err != nil { + errFound = true + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + for _, mess := range tt.mess { + bs, bv, err := createTestKey() + if err != nil { + t.Fatal(err) + } + + dig, _, err := cryptoutil.ComputeDigest(bytes.NewReader([]byte(mess)), tt.hash, awsSupportedHashFuncs) + if tt.wantErr && err != nil { + errFound = true + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + sig, err := c.sign(ctx, []byte(dig), crypto.SHA256) + if tt.wantErr && err != nil { + errFound = true + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + bsig, err := bs.Sign(bytes.NewReader([]byte(dig))) + if err != nil { + t.Fatal(err) + } + + err = c.verifyRemotely(ctx, sig, dig) + if tt.wantErr && err != nil { + errFound = true + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + err = c.verifyRemotely(ctx, bsig, dig) + if err == nil { + t.Fatal("expected verification to fail") + } + + err = bv.Verify(bytes.NewReader([]byte(dig)), sig) + if err == nil { + t.Fatal("expected verification to fail") + } + + } + + if tt.wantErr && !errFound { + t.Fatalf("expected test %s to fail with error %s", tt.name, tt.expectedErr.Error()) + } + } +} diff --git a/signer/kms/gcp/client.go b/signer/kms/gcp/client.go new file mode 100644 index 00000000..1d797067 --- /dev/null +++ b/signer/kms/gcp/client.go @@ -0,0 +1,435 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcp + +import ( + "context" + "crypto" + "errors" + "fmt" + "hash/crc32" + "io" + "regexp" + "strings" + "time" + + gcpkms "cloud.google.com/go/kms/apiv1" + "cloud.google.com/go/kms/apiv1/kmspb" + "google.golang.org/api/option" + "google.golang.org/protobuf/types/known/wrapperspb" + + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/registry" + "github.com/in-toto/go-witness/signer" + "github.com/in-toto/go-witness/signer/kms" + "github.com/jellydator/ttlcache/v3" +) + +func init() { + kms.AddProvider(ReferenceScheme, &gcpClientOptions{}, func(ctx context.Context, ksp *kms.KMSSignerProvider) (cryptoutil.Signer, error) { + return LoadSignerVerifier(ctx, ksp) + }) +} + +//nolint:revive +const ( + AlgorithmECDSAP256SHA256 = "ecdsa-p256-sha256" + AlgorithmECDSAP384SHA384 = "ecdsa-p384-sha384" + AlgorithmRSAPKCS1v152048SHA256 = "rsa-pkcs1v15-2048-sha256" + AlgorithmRSAPKCS1v153072SHA256 = "rsa-pkcs1v15-3072-sha256" + AlgorithmRSAPKCS1v154096SHA256 = "rsa-pkcs1v15-4096-sha256" + AlgorithmRSAPKCS1v154096SHA512 = "rsa-pkcs1v15-4096-sha512" + AlgorithmRSAPSS2048SHA256 = "rsa-pss-2048-sha256" + AlgorithmRSAPSS3072SHA256 = "rsa-pss-3072-sha256" + AlgorithmRSAPSS4096SHA256 = "rsa-pss-4096-sha256" + AlgorithmRSAPSS4096SHA512 = "rsa-pss-4096-sha512" +) + +var algorithmMap = map[string]kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm{ + AlgorithmECDSAP256SHA256: kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256, + AlgorithmECDSAP384SHA384: kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384, + AlgorithmRSAPKCS1v152048SHA256: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, + AlgorithmRSAPKCS1v153072SHA256: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256, + AlgorithmRSAPKCS1v154096SHA256: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256, + AlgorithmRSAPKCS1v154096SHA512: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512, + AlgorithmRSAPSS2048SHA256: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256, + AlgorithmRSAPSS3072SHA256: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256, + AlgorithmRSAPSS4096SHA256: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256, + AlgorithmRSAPSS4096SHA512: kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512, +} + +var ( + errKMSReference = errors.New("kms specification should be in the format gcpkms://projects/[PROJECT_ID]/locations/[LOCATION]/keyRings/[KEY_RING]/cryptoKeys/[KEY]/cryptoKeyVersions/[VERSION]") + + re = regexp.MustCompile(`^gcpkms://projects/([^/]+)/locations/([^/]+)/keyRings/([^/]+)/cryptoKeys/([^/]+)(?:/(?:cryptoKeyVersions|versions)/([^/]+))?$`) +) + +// ReferenceScheme schemes for various KMS services are copied from https://github.com/google/go-cloud/tree/master/secrets +const ReferenceScheme = "gcpkms://" + +// ValidReference returns a non-nil error if the reference string is invalid +func ValidReference(ref string) error { + if !re.MatchString(ref) { + return errKMSReference + } + return nil +} + +func parseReference(resourceID string) (projectID, locationID, keyRing, keyName, version string, err error) { + v := re.FindStringSubmatch(resourceID) + if len(v) != 6 { + err = fmt.Errorf("invalid gcpkms format %q", resourceID) + return + } + projectID, locationID, keyRing, keyName, version = v[1], v[2], v[3], v[4], v[5] + return +} + +type gcpClient struct { + projectID string + locationID string + keyRing string + keyName string + version string + kvCache *ttlcache.Cache[string, cryptoKeyVersion] + client *gcpkms.KeyManagementClient + options *gcpClientOptions +} + +type gcpClientOptions struct { + credentialsFile string +} + +type Option func(*gcpClientOptions) + +func (a *gcpClientOptions) Init() []registry.Configurer { + return []registry.Configurer{ + registry.StringConfigOption( + "credentials-file", + "The credentials file to use with the GCP KMS signer provider", + "", + func(sp signer.SignerProvider, cred string) (signer.SignerProvider, error) { + ksp, ok := sp.(*kms.KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided signer provider is not a kms signer provider") + } + + var clientOpts *gcpClientOptions + for _, opt := range ksp.Options { + co, optsOk := opt.(*gcpClientOptions) + if !optsOk { + continue + } + clientOpts = co + } + + if clientOpts == nil { + return nil, fmt.Errorf("unable to find aws client options in aws kms signer provider") + } + + WithCredentialsFile(cred)(clientOpts) + return ksp, nil + }, + ), + } +} + +func (*gcpClientOptions) ProviderName() string { + name := fmt.Sprintf("kms-%s", strings.TrimSuffix(ReferenceScheme, "kms://")) + return name +} + +func WithCredentialsFile(cred string) Option { + return func(opts *gcpClientOptions) { + opts.credentialsFile = cred + } +} + +func newGCPClient(ctx context.Context, ksp *kms.KMSSignerProvider) (*gcpClient, error) { + if err := ValidReference(ksp.Reference); err != nil { + return nil, err + } + + if ctx == nil { + ctx = context.TODO() + } + + g := &gcpClient{ + kvCache: nil, + } + + var err error + g.projectID, g.locationID, g.keyRing, g.keyName, g.version, err = parseReference(ksp.Reference) + if err != nil { + return nil, err + } + + var ok bool + for _, opt := range ksp.Options { + g.options, ok = opt.(*gcpClientOptions) + if ok { + break + } + } + + if g.options == nil { + return nil, fmt.Errorf("unable to find gcp client options in gcp kms signer provider") + } + + var opts []option.ClientOption + if g.options.credentialsFile != "" { + opts = append(opts, option.WithCredentialsFile(g.options.credentialsFile)) + } + + g.client, err = gcpkms.NewKeyManagementClient(ctx, opts...) + if err != nil { + return nil, fmt.Errorf("new gcp kms client: %w", err) + } + + g.kvCache = ttlcache.New[string, cryptoKeyVersion]( + ttlcache.WithDisableTouchOnHit[string, cryptoKeyVersion](), + ) + + // prime the cache + g.kvCache.Get(cacheKey) + return g, nil +} + +type cryptoKeyVersion struct { + CryptoKeyVersion *kmspb.CryptoKeyVersion + Verifier cryptoutil.Verifier + PublicKey crypto.PublicKey + HashFunc crypto.Hash +} + +// use a consistent key for cache lookups +const cacheKey = "crypto_key_version" + +func (g *gcpClient) Verifier() (cryptoutil.Verifier, error) { + crv, err := g.getCKV() + if err != nil { + return nil, fmt.Errorf("transient error while getting KMS verifier: %w", err) + } + + return crv.Verifier, nil +} + +// keyVersionName returns the first key version found for a key in KMS +func (g *gcpClient) keyVersionName(ctx context.Context) (*cryptoKeyVersion, error) { + parent := fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", g.projectID, g.locationID, g.keyRing, g.keyName) + + parentReq := &kmspb.GetCryptoKeyRequest{ + Name: parent, + } + key, err := g.client.GetCryptoKey(ctx, parentReq) + if err != nil { + return nil, err + } + if key.Purpose != kmspb.CryptoKey_ASYMMETRIC_SIGN { + return nil, errors.New("specified key cannot be used to sign") + } + + // if g.version was specified, use it explicitly + var kv *kmspb.CryptoKeyVersion + if g.version != "" { + req := &kmspb.GetCryptoKeyVersionRequest{ + Name: parent + fmt.Sprintf("/cryptoKeyVersions/%s", g.version), + } + kv, err = g.client.GetCryptoKeyVersion(ctx, req) + if err != nil { + return nil, err + } + } else { + req := &kmspb.ListCryptoKeyVersionsRequest{ + Parent: parent, + Filter: "state=ENABLED", + OrderBy: "name desc", + } + iterator := g.client.ListCryptoKeyVersions(ctx, req) + + // pick the key version that is enabled with the greatest version value + kv, err = iterator.Next() + if err != nil { + return nil, fmt.Errorf("unable to find an enabled key version in GCP KMS: %w", err) + } + } + + pubKey, err := g.fetchPublicKey(ctx, kv.Name) + if err != nil { + return nil, fmt.Errorf("unable to fetch public key while creating signer: %w", err) + } + + // kv is keyVersion to use + crv := cryptoKeyVersion{ + CryptoKeyVersion: kv, + PublicKey: pubKey, + } + + switch kv.Algorithm { + case kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, + kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256, + kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256, + kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256, + kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256, + kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256, + kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256: + crv.HashFunc = crypto.SHA256 + case kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384: + crv.HashFunc = crypto.SHA384 + case kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512, + kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512: + crv.HashFunc = crypto.SHA512 + default: + return nil, errors.New("unknown algorithm specified by KMS") + } + + crv.Verifier, err = cryptoutil.NewVerifier(pubKey, cryptoutil.VerifyWithHash(crv.HashFunc)) + if err != nil { + return nil, fmt.Errorf("initializing internal verifier: %w", err) + } + + return &crv, nil +} + +func (g *gcpClient) fetchPublicKey(ctx context.Context, name string) (crypto.PublicKey, error) { + // Build the request. + pkreq := &kmspb.GetPublicKeyRequest{Name: name} + // Call the API. + pk, err := g.client.GetPublicKey(ctx, pkreq) + if err != nil { + return nil, fmt.Errorf("public key: %w", err) + } + return cryptoutil.UnmarshalPEMToPublicKey([]byte(pk.GetPem())) +} + +func (g *gcpClient) getHashFunc() (crypto.Hash, error) { + ckv, err := g.getCKV() + if err != nil { + return 0, err + } + return ckv.HashFunc, nil +} + +// getCKV gets the latest CryptoKeyVersion from the client's cache, which may trigger an actual +// call to GCP if the existing entry in the cache has expired. +func (g *gcpClient) getCKV() (*cryptoKeyVersion, error) { + var lerr error + loader := ttlcache.LoaderFunc[string, cryptoKeyVersion]( + func(c *ttlcache.Cache[string, cryptoKeyVersion], key string) *ttlcache.Item[string, cryptoKeyVersion] { + var ttl time.Duration + var data *cryptoKeyVersion + + // if we're given an explicit version, cache this value forever + if g.version != "" { + ttl = time.Second * 0 + } else { + ttl = time.Second * 300 + } + data, lerr = g.keyVersionName(context.TODO()) + if lerr == nil { + return c.Set(key, *data, ttl) + } + return nil + }, + ) + + // we get once and use consistently to ensure the cache value doesn't change underneath us + item := g.kvCache.Get(cacheKey, ttlcache.WithLoader[string, cryptoKeyVersion](loader)) + if item != nil { + v := item.Value() + return &v, nil + } + return nil, lerr +} + +func (g *gcpClient) sign(ctx context.Context, digest []byte, alg crypto.Hash, crc uint32) ([]byte, error) { + ckv, err := g.getCKV() + if err != nil { + return nil, err + } + + gcpSignReq := kmspb.AsymmetricSignRequest{ + Name: ckv.CryptoKeyVersion.Name, + Digest: &kmspb.Digest{}, + } + + if crc != 0 { + gcpSignReq.DigestCrc32C = wrapperspb.Int64(int64(crc)) + } + + switch alg { + case crypto.SHA256: + gcpSignReq.Digest.Digest = &kmspb.Digest_Sha256{ + Sha256: digest, + } + case crypto.SHA384: + gcpSignReq.Digest.Digest = &kmspb.Digest_Sha384{ + Sha384: digest, + } + case crypto.SHA512: + gcpSignReq.Digest.Digest = &kmspb.Digest_Sha512{ + Sha512: digest, + } + default: + return nil, errors.New("unsupported hash function") + } + + resp, err := g.client.AsymmetricSign(ctx, &gcpSignReq) + if err != nil { + return nil, fmt.Errorf("calling GCP AsymmetricSign: %w", err) + } + + // Optional, but recommended: perform integrity verification on result. + // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: + // https://cloud.google.com/kms/docs/data-integrity-guidelines + if crc != 0 && !resp.VerifiedDigestCrc32C { + return nil, fmt.Errorf("AsymmetricSign: request corrupted in-transit") + } + if int64(crc32.Checksum(resp.Signature, crc32.MakeTable(crc32.Castagnoli))) != resp.SignatureCrc32C.Value { + return nil, fmt.Errorf("AsymmetricSign: response corrupted in-transit") + } + + return resp.Signature, nil +} + +func (g *gcpClient) public(ctx context.Context) (crypto.PublicKey, error) { + crv, err := g.getCKV() + if err != nil { + return nil, fmt.Errorf("transient error getting info from KMS: %w", err) + } + return crv.PublicKey, nil +} + +// Seems like GCP doesn't support any remote verification, so we'll just use the local verifier +func (g *gcpClient) verify(message io.Reader, sig []byte) error { + crv, err := g.getCKV() + if err != nil { + return fmt.Errorf("transient error getting info from KMS: %w", err) + } + + if err := crv.Verifier.Verify(message, sig); err != nil { + // key could have been rotated, clear cache and try again if we're not pinned to a version + if g.version == "" { + g.kvCache.Delete(cacheKey) + crv, err = g.getCKV() + if err != nil { + return fmt.Errorf("transient error getting info from KMS: %w", err) + } + return crv.Verifier.Verify(message, sig) + } + return fmt.Errorf("failed to verify for fixed version: %w", err) + } + return nil +} diff --git a/signer/kms/gcp/fakeclient.go b/signer/kms/gcp/fakeclient.go new file mode 100644 index 00000000..cf0c9457 --- /dev/null +++ b/signer/kms/gcp/fakeclient.go @@ -0,0 +1,253 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcp + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "errors" + "fmt" + "io" + "time" + + "cloud.google.com/go/kms/apiv1/kmspb" + + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/signer/kms" + "github.com/jellydator/ttlcache/v3" +) + +func createRsaKey() (*rsa.PrivateKey, *rsa.PublicKey, error) { + privKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + return privKey, &privKey.PublicKey, nil +} + +func createTestKey() (cryptoutil.Signer, cryptoutil.Verifier, error) { + privKey, pubKey, err := createRsaKey() + if err != nil { + return nil, nil, err + } + + signer := cryptoutil.NewRSASigner(privKey, crypto.SHA256) + verifier := cryptoutil.NewRSAVerifier(pubKey, crypto.SHA256) + return signer, verifier, nil +} + +type fakeGCPClient struct { + projectID string + locationID string + keyRing string + keyName string + version string + kvCache *ttlcache.Cache[string, cryptoKeyVersion] + signer cryptoutil.Signer +} + +func newFakeGCPClient(ctx context.Context, ksp *kms.KMSSignerProvider) (*fakeGCPClient, error) { + fmt.Println(ksp.Reference) + if err := ValidReference(ksp.Reference); err != nil { + return nil, err + } + + g := &fakeGCPClient{ + kvCache: nil, + } + + var err error + g.projectID, g.locationID, g.keyRing, g.keyName, g.version, err = parseReference(ksp.Reference) + if err != nil { + return nil, err + } + + g.kvCache = ttlcache.New[string, cryptoKeyVersion]( + ttlcache.WithDisableTouchOnHit[string, cryptoKeyVersion](), + ) + + // prime the cache + g.kvCache.Get(cacheKey) + return g, nil +} + +func (g *fakeGCPClient) Verifier() (cryptoutil.Verifier, error) { + crv, err := g.getCKV() + if err != nil { + return nil, fmt.Errorf("transient error while getting KMS verifier: %w", err) + } + + return crv.Verifier, nil +} + +// keyVersionName returns the first key version found for a key in KMS +func (g *fakeGCPClient) keyVersionName(ctx context.Context) (*cryptoKeyVersion, error) { + pubKey, err := g.fetchPublicKey(ctx, "1") + if err != nil { + return nil, fmt.Errorf("unable to fetch public key while creating signer: %w", err) + } + + // kv is keyVersion to use + crv := cryptoKeyVersion{ + CryptoKeyVersion: &kmspb.CryptoKeyVersion{ + Name: "1", + Algorithm: kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, + }, + PublicKey: pubKey, + } + + g.version = "1" + + // crv.Verifier is set here to enable storing the public key & hash algorithm together, + // as well as using the in memory Verifier to perform the verify operations. + switch crv.CryptoKeyVersion.Algorithm { + case kmspb.CryptoKeyVersion_EC_SIGN_P256_SHA256: + pub, ok := pubKey.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("public key is not rsa") + } + crv.Verifier = cryptoutil.NewECDSAVerifier(pub, crypto.SHA256) + crv.HashFunc = crypto.SHA256 + case kmspb.CryptoKeyVersion_EC_SIGN_P384_SHA384: + pub, ok := pubKey.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("public key is not rsa") + } + crv.Verifier = cryptoutil.NewECDSAVerifier(pub, crypto.SHA384) + crv.HashFunc = crypto.SHA384 + case kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_2048_SHA256, + kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_3072_SHA256, + kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA256: + pub, ok := pubKey.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("public key is not rsa") + } + crv.Verifier = cryptoutil.NewRSAVerifier(pub, crypto.SHA256) + crv.HashFunc = crypto.SHA256 + case kmspb.CryptoKeyVersion_RSA_SIGN_PKCS1_4096_SHA512: + pub, ok := pubKey.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("public key is not rsa") + } + crv.Verifier = cryptoutil.NewRSAVerifier(pub, crypto.SHA384) + crv.HashFunc = crypto.SHA384 + case kmspb.CryptoKeyVersion_RSA_SIGN_PSS_2048_SHA256, + kmspb.CryptoKeyVersion_RSA_SIGN_PSS_3072_SHA256, + kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA256: + pub, ok := pubKey.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("public key is not rsa") + } + crv.Verifier = cryptoutil.NewRSAVerifier(pub, crypto.SHA256) + crv.HashFunc = crypto.SHA256 + case kmspb.CryptoKeyVersion_RSA_SIGN_PSS_4096_SHA512: + pub, ok := pubKey.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("public key is not rsa") + } + crv.Verifier = cryptoutil.NewRSAVerifier(pub, crypto.SHA512) + crv.HashFunc = crypto.SHA512 + default: + return nil, errors.New("unknown algorithm specified by KMS") + } + if err != nil { + return nil, fmt.Errorf("initializing internal verifier: %w", err) + } + + return &crv, nil +} + +func (g *fakeGCPClient) fetchPublicKey(ctx context.Context, name string) (crypto.PublicKey, error) { + priv, pub, err := createRsaKey() + if err != nil { + return nil, err + } + sign := cryptoutil.NewRSASigner(priv, crypto.SHA256) + g.signer = sign + + return pub, nil +} + +// getCKV gets the latest CryptoKeyVersion from the client's cache, which may trigger an actual +// call to GCP if the existing entry in the cache has expired. +func (g *fakeGCPClient) getCKV() (*cryptoKeyVersion, error) { + var lerr error + loader := ttlcache.LoaderFunc[string, cryptoKeyVersion]( + func(c *ttlcache.Cache[string, cryptoKeyVersion], key string) *ttlcache.Item[string, cryptoKeyVersion] { + var ttl time.Duration + var data *cryptoKeyVersion + + // if we're given an explicit version, cache this value forever + if g.version != "" { + ttl = time.Second * 0 + } else { + ttl = time.Second * 300 + } + data, lerr = g.keyVersionName(context.Background()) + if lerr == nil { + return c.Set(key, *data, ttl) + } + return nil + }, + ) + + // we get once and use consistently to ensure the cache value doesn't change underneath us + item := g.kvCache.Get(cacheKey, ttlcache.WithLoader[string, cryptoKeyVersion](loader)) + if item != nil { + v := item.Value() + return &v, nil + } + + return nil, lerr +} + +func (g *fakeGCPClient) sign(ctx context.Context, digest []byte, alg crypto.Hash, crc uint32) ([]byte, error) { + _, err := g.getCKV() + if err != nil { + return nil, err + } + + reader := bytes.NewReader(digest) + + return g.signer.Sign(reader) +} + +// Seems like GCP doesn't support any remote verification, so we'll just use the local verifier +func (g *fakeGCPClient) verify(message io.Reader, sig []byte) error { + crv, err := g.getCKV() + if err != nil { + return fmt.Errorf("transient error getting info from KMS: %w", err) + } + + if err := crv.Verifier.Verify(message, sig); err != nil { + // key could have been rotated, clear cache and try again if we're not pinned to a version + if g.version == "" { + g.kvCache.Delete(cacheKey) + crv, err = g.getCKV() + if err != nil { + return fmt.Errorf("transient error getting info from KMS: %w", err) + } + return crv.Verifier.Verify(message, sig) + } + return fmt.Errorf("failed to verify for fixed version: %w", err) + } + + return nil +} diff --git a/signer/kms/gcp/signer.go b/signer/kms/gcp/signer.go new file mode 100644 index 00000000..75d37429 --- /dev/null +++ b/signer/kms/gcp/signer.go @@ -0,0 +1,147 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcp + +import ( + "context" + "crypto" + "fmt" + "hash/crc32" + "io" + + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/log" + kms "github.com/in-toto/go-witness/signer/kms" +) + +var gcpSupportedHashFuncs = []crypto.Hash{ + crypto.SHA256, + crypto.SHA512, + crypto.SHA384, +} + +// SignerVerifier is a cryptoutil.SignerVerifier that uses the AWS Key Management Service +type SignerVerifier struct { + reference string + client *gcpClient + hashFunc crypto.Hash +} + +// LoadSignerVerifier generates signatures using the specified key object in AWS KMS and hash algorithm. +func LoadSignerVerifier(ctx context.Context, ksp *kms.KMSSignerProvider) (*SignerVerifier, error) { + g := &SignerVerifier{ + reference: ksp.Reference, + } + + var err error + g.client, err = newGCPClient(ctx, ksp) + if err != nil { + return nil, err + } + + for _, hashFunc := range gcpSupportedHashFuncs { + if hashFunc == ksp.HashFunc { + g.hashFunc = ksp.HashFunc + } + } + + if g.hashFunc == 0 { + return nil, fmt.Errorf("unsupported hash function: %v", ksp.HashFunc) + } + + return g, nil +} + +// NOTE: This might be all wrong but setting it like so for now +// +// KeyID returns the key identifier for the key used by this signer. +func (g *SignerVerifier) KeyID() (string, error) { + return g.reference, nil +} + +// Sign signs the provided message using GCP KMS. If the message is provided, +// this method will compute the digest according to the hash function specified +// when the Signer was created. +func (g *SignerVerifier) Sign(message io.Reader) ([]byte, error) { + var err error + ctx := context.Background() + var digest []byte + + var signerOpts crypto.SignerOpts + signerOpts, err = g.client.getHashFunc() + if err != nil { + return nil, fmt.Errorf("failed to get default hash function: %w", err) + } + + hf := signerOpts.HashFunc() + + digest, _, err = cryptoutil.ComputeDigest(message, hf, gcpSupportedHashFuncs) + if err != nil { + return nil, err + } + + crc32cHasher := crc32.New(crc32.MakeTable(crc32.Castagnoli)) + _, err = crc32cHasher.Write(digest) + if err != nil { + return nil, err + } + + return g.client.sign(ctx, digest, hf, crc32cHasher.Sum32()) +} + +// Verifier returns a cryptoutil.Verifier that can be used to verify signatures created by this signer. +func (g *SignerVerifier) Verifier() (cryptoutil.Verifier, error) { + return g, nil +} + +// PublicKey returns the public key that can be used to verify signatures created by +// this signer. +func (g *SignerVerifier) PublicKey(ctx context.Context) (crypto.PublicKey, error) { + return g.client.public(ctx) +} + +// Bytes returns the bytes of the public key that can be used to verify signatures created by the signer. +func (g *SignerVerifier) Bytes() ([]byte, error) { + ckv, err := g.client.getCKV() + if err != nil { + return nil, fmt.Errorf("failed to get KMS key version: %w", err) + } + + return cryptoutil.PublicPemBytes(ckv.PublicKey) +} + +// VerifySignature verifies the signature for the given message, returning +// nil if the verification succeeded, and an error message otherwise. +func (g *SignerVerifier) Verify(message io.Reader, sig []byte) (err error) { + err = g.client.verify(message, sig) + if err != nil { + log.Info(err.Error()) + } + + return err +} + +// SupportedAlgorithms returns the list of algorithms supported by the AWS KMS service +func (*SignerVerifier) SupportedAlgorithms() (result []string) { + for k := range algorithmMap { + result = append(result, k) + } + return +} + +// DefaultAlgorithm returns the default algorithm for the GCP KMS service +func (g *SignerVerifier) DefaultAlgorithm() string { + return AlgorithmECDSAP256SHA256 +} diff --git a/signer/kms/gcp/signer_test.go b/signer/kms/gcp/signer_test.go new file mode 100644 index 00000000..2adbdcc4 --- /dev/null +++ b/signer/kms/gcp/signer_test.go @@ -0,0 +1,315 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcp + +import ( + "bytes" + "context" + "crypto" + "fmt" + "testing" + + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/signer/kms" + "github.com/stretchr/testify/assert" +) + +func TestParseReference(t *testing.T) { + tests := []struct { + in string + wantProjectID string + wantLocationID string + wantKeyRing string + wantKeyName string + wantKeyVersion string + wantErr bool + }{ + { + in: "gcpkms://projects/pp/locations/ll/keyRings/rr/cryptoKeys/kk", + wantProjectID: "pp", + wantLocationID: "ll", + wantKeyRing: "rr", + wantKeyName: "kk", + wantErr: false, + }, + { + in: "gcpkms://projects/pp/locations/ll/keyRings/rr/cryptoKeys/kk/versions/1", + wantProjectID: "pp", + wantLocationID: "ll", + wantKeyRing: "rr", + wantKeyName: "kk", + wantKeyVersion: "1", + wantErr: false, + }, + { + in: "gcpkms://projects/pp/locations/ll/keyRings/rr/cryptoKeys/kk/cryptoKeyVersions/1", + wantProjectID: "pp", + wantLocationID: "ll", + wantKeyRing: "rr", + wantKeyName: "kk", + wantKeyVersion: "1", + wantErr: false, + }, + { + in: "gcpkms://projects/p1/p2/locations/l1/l2/keyRings/r1/r2/cryptoKeys/k1", + wantErr: true, + }, + { + in: "foo://bar", + wantErr: true, + }, + { + in: "", + wantErr: true, + }, + { + in: "gcpkms://projects/p1/p2/locations/l1/l2/keyRings/r1/r2/cryptoKeys/k1/versions", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + gotProjectID, gotLocationID, gotKeyRing, gotKeyName, gotKeyVersion, err := parseReference(tt.in) + if (err != nil) != tt.wantErr { + t.Errorf("parseReference() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotProjectID != tt.wantProjectID { + t.Errorf("parseReference() gotProjectID = %v, want %v", gotProjectID, tt.wantProjectID) + } + if gotLocationID != tt.wantLocationID { + t.Errorf("parseReference() gotLocationID = %v, want %v", gotLocationID, tt.wantLocationID) + } + if gotKeyRing != tt.wantKeyRing { + t.Errorf("parseReference() gotKeyRing = %v, want %v", gotKeyRing, tt.wantKeyRing) + } + if gotKeyName != tt.wantKeyName { + t.Errorf("parseReference() gotKeyName = %v, want %v", gotKeyName, tt.wantKeyName) + } + if gotKeyVersion != tt.wantKeyVersion { + t.Errorf("parseReference() gotKeyVersion = %v, want %v", gotKeyVersion, tt.wantKeyVersion) + } + }) + } +} + +func TestSign(t *testing.T) { + tests := []struct { + name string + ref string + hash crypto.Hash + message string + wantErr bool + expectedErr error + }{ + { + name: "successful sign", + ref: "gcpkms://projects/pp/locations/ll/keyRings/rr/cryptoKeys/kk", + hash: crypto.SHA256, + message: "foo", + wantErr: false, + }, + { + name: "SHA-512", + ref: "gcpkms://projects/pp/locations/ll/keyRings/rr/cryptoKeys/kk", + hash: crypto.SHA512, + message: "foo", + wantErr: false, + }, + { + name: "bad ref", + ref: "blablabla", + hash: crypto.SHA256, + message: "foo", + wantErr: true, + expectedErr: fmt.Errorf("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)"), + }, + { + name: "unsupported hash algorithm", + ref: "gcpkms://projects/pp/locations/ll/keyRings/rr/cryptoKeys/kk", + hash: crypto.RIPEMD160, + message: "foo", + wantErr: true, + expectedErr: fmt.Errorf(`unsupported hash algorithm: "RIPEMD-160" not in [SHA-256 SHA-384 SHA-512]`), + }, + { + name: "aws ref", + ref: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + hash: crypto.SHA256, + message: "foobarbaz", + wantErr: true, + expectedErr: fmt.Errorf("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)"), + }, + } + + for _, tt := range tests { + fmt.Println("sign test: ", tt.name) + ctx := context.TODO() + dig, _, err := cryptoutil.ComputeDigest(bytes.NewReader([]byte(tt.message)), tt.hash, gcpSupportedHashFuncs) + if tt.wantErr && err != nil { + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + ksp := kms.New(kms.WithRef(tt.ref), kms.WithHash(tt.hash.String())) + c, err := newFakeGCPClient(context.TODO(), ksp) + if tt.wantErr && err != nil { + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + s, err := c.sign(ctx, dig, tt.hash, 00) + if tt.wantErr && err != nil { + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + if s == nil { + t.Fatal("signature is nil") + } + + if tt.wantErr { + t.Fatalf("expected test %s to fail", tt.name) + } + + } +} + +func TestVerify(t *testing.T) { + tests := []struct { + name string + ref string + hash crypto.Hash + mess []string + wantErr bool + expectedErr error + }{ + { + name: "successful verify", + hash: crypto.SHA256, + ref: "gcpkms://projects/pp/locations/ll/keyRings/rr/cryptoKeys/kk", + mess: []string{"foo", "bar", "baz"}, + wantErr: false, + }, + { + name: "SHA-512", + hash: crypto.SHA512, + ref: "gcpkms://projects/pp/locations/ll/keyRings/rr/cryptoKeys/kk", + mess: []string{"foo", "bar", "baz"}, + wantErr: false, + }, + { + name: "bad ref", + hash: crypto.SHA256, + ref: "blablabla", + mess: []string{"foo", "bar", "baz"}, + wantErr: true, + expectedErr: fmt.Errorf("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)"), + }, + { + name: "unsupported hash algorithm", + hash: crypto.RIPEMD160, + ref: "gcpkms://projects/pp/locations/ll/keyRings/rr/cryptoKeys/kk", + mess: []string{"foo", "bar", "baz"}, + wantErr: true, + expectedErr: fmt.Errorf(`unsupported hash algorithm: "RIPEMD-160" not in [SHA-256 SHA-384 SHA-512]`), + }, + { + name: "aws ref", + hash: crypto.SHA256, + ref: "awskms:///arn:aws-us-gov:kms:us-gov-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", + mess: []string{"foo", "bar", "baz"}, + wantErr: true, + expectedErr: fmt.Errorf("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)"), + }, + } + + for _, tt := range tests { + errFound := false + fmt.Println("verify test: ", tt.name) + ctx := context.TODO() + ksp := kms.New(kms.WithRef(tt.ref), kms.WithHash(tt.hash.String())) + c, err := newFakeGCPClient(context.TODO(), ksp) + if tt.wantErr && err != nil { + errFound = true + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + for _, mess := range tt.mess { + bs, bv, err := createTestKey() + if err != nil { + t.Fatal(err) + } + + dig, _, err := cryptoutil.ComputeDigest(bytes.NewReader([]byte(mess)), tt.hash, gcpSupportedHashFuncs) + if tt.wantErr && err != nil { + errFound = true + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + sig, err := c.sign(ctx, []byte(dig), crypto.SHA256, 00) + if tt.wantErr && err != nil { + errFound = true + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + bsig, err := bs.Sign(bytes.NewReader([]byte(dig))) + if err != nil { + t.Fatal(err) + } + + r := bytes.NewReader([]byte(dig)) + + err = c.verify(r, sig) + if tt.wantErr && err != nil { + errFound = true + assert.ErrorAs(t, err, &tt.expectedErr) + continue + } else if err != nil { + t.Fatal(err) + } + + err = c.verify(r, bsig) + if err == nil { + t.Fatal("expected verification to fail") + } + + err = bv.Verify(bytes.NewReader([]byte(dig)), sig) + if err == nil { + t.Fatal("expected verification to fail") + } + + } + + if tt.wantErr && !errFound { + t.Fatalf("expected test %s to fail with error %s", tt.name, tt.expectedErr.Error()) + } + } +} diff --git a/signer/kms/signerprovider.go b/signer/kms/signerprovider.go new file mode 100644 index 00000000..4166b65f --- /dev/null +++ b/signer/kms/signerprovider.go @@ -0,0 +1,243 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kms + +import ( + "context" + "crypto" + "fmt" + "strings" + + "github.com/in-toto/go-witness/cryptoutil" + "github.com/in-toto/go-witness/registry" + "github.com/in-toto/go-witness/signer" +) + +func init() { + signer.Register("kms", func() signer.SignerProvider { return New() }, + registry.StringConfigOption( + "ref", + "The KMS Reference URI to use for connecting to the KMS service", + "", + func(sp signer.SignerProvider, ref string) (signer.SignerProvider, error) { + ksp, ok := sp.(*KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided signer provider is not a kms signer provider") + } + + WithRef(ref)(ksp) + return ksp, nil + }, + ), + registry.StringConfigOption( + "hashType", + "The hash type to use for signing", + "sha256", + func(sp signer.SignerProvider, hash string) (signer.SignerProvider, error) { + ksp, ok := sp.(*KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided signer provider is not a kms signer provider") + } + + WithHash(hash)(ksp) + return ksp, nil + }, + ), + registry.StringConfigOption( + "keyVersion", + "The key version to use for signing", + "", + func(sp signer.SignerProvider, keyVersion string) (signer.SignerProvider, error) { + ksp, ok := sp.(*KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided signer provider is not a kms signer provider") + } + + WithKeyVersion(keyVersion)(ksp) + return ksp, nil + }, + ), + ) + + signer.RegisterVerifier("kms", func() signer.VerifierProvider { return New() }, + registry.StringConfigOption( + "ref", + "The KMS Reference URI to use for connecting to the KMS service", + "", + func(sp signer.VerifierProvider, ref string) (signer.VerifierProvider, error) { + ksp, ok := sp.(*KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided verifier provider is not a kms verifier provider") + } + + WithRef(ref)(ksp) + return ksp, nil + }, + ), + registry.StringConfigOption( + "hashType", + "The hash type used for verifying", + "sha256", + func(sp signer.VerifierProvider, hash string) (signer.VerifierProvider, error) { + ksp, ok := sp.(*KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided verifier provider is not a kms verifier provider") + } + + WithHash(hash)(ksp) + return ksp, nil + }, + ), + registry.StringConfigOption( + "keyVersion", + "The key version to use for signing", + "", + func(sp signer.VerifierProvider, keyVersion string) (signer.VerifierProvider, error) { + ksp, ok := sp.(*KMSSignerProvider) + if !ok { + return sp, fmt.Errorf("provided verifier provider is not a kms verifier provider") + } + + WithKeyVersion(keyVersion)(ksp) + return ksp, nil + }, + ), + ) +} + +type KMSSignerProvider struct { + Reference string + KeyVersion string + HashFunc crypto.Hash + Options map[string]KMSClientOptions +} + +type KMSClientOptions interface { + Init() []registry.Configurer + ProviderName() string +} + +type Option func(*KMSSignerProvider) + +func WithRef(ref string) Option { + return func(ksp *KMSSignerProvider) { + ksp.Reference = ref + } +} + +func WithHash(hash string) Option { + return func(ksp *KMSSignerProvider) { // case switch to match hash type string and set hashFunc + switch hash { + case "SHA224": + ksp.HashFunc = crypto.SHA224 + case "SHA256": + ksp.HashFunc = crypto.SHA256 + case "SHA384": + ksp.HashFunc = crypto.SHA384 + case "SHA512": + ksp.HashFunc = crypto.SHA512 + default: + ksp.HashFunc = crypto.SHA256 + } + } +} + +func WithKeyVersion(keyVersion string) Option { + return func(ksp *KMSSignerProvider) { + ksp.KeyVersion = keyVersion + } +} + +func New(opts ...Option) *KMSSignerProvider { + ksp := KMSSignerProvider{} + + for _, opt := range opts { + opt(&ksp) + } + + ksp.Options = make(map[string]KMSClientOptions) + for _, opt := range providerOptionsMap { + if opt == nil { + continue + } + + ksp.Options[opt.ProviderName()] = opt + } + + return &ksp +} + +// ProviderInit is a function that initializes provider-specific SignerVerifier. +// +// It takes a provider-specific resource ID and hash function, and returns a +// SignerVerifier using that resource, or any error that was encountered. +type ProviderInit func(context.Context, *KMSSignerProvider) (cryptoutil.Signer, error) + +// AddProvider adds the provider implementation into the local cache +func AddProvider(keyResourceID string, opts KMSClientOptions, init ProviderInit) { + providersMap[keyResourceID] = init + providerOptionsMap[keyResourceID] = opts +} + +func (ksp *KMSSignerProvider) Signer(ctx context.Context) (cryptoutil.Signer, error) { + for ref, pi := range providersMap { + if strings.HasPrefix(ksp.Reference, ref) { + return pi(ctx, ksp) + } + } + return nil, &ProviderNotFoundError{ref: ksp.Reference} +} + +// NOTE: This is a temprorary implementation until we have a SignerVerifier interface +func (ksp *KMSSignerProvider) Verifier(ctx context.Context) (cryptoutil.Verifier, error) { + for ref, pi := range providersMap { + if strings.HasPrefix(ksp.Reference, ref) { + p, err := pi(ctx, ksp) + if err != nil { + return nil, err + } + + // we need to conver this into a cryptoutil.Verifier + return p.Verifier() + } + } + return nil, &ProviderNotFoundError{ref: ksp.Reference} +} + +var providersMap = map[string]ProviderInit{} + +var providerOptionsMap = map[string]KMSClientOptions{} + +// SupportedProviders returns list of initialized providers +func SupportedProviders() []string { + keys := make([]string, 0, len(providersMap)) + for key := range providersMap { + keys = append(keys, key) + } + return keys +} + +func ProviderOptions() map[string]KMSClientOptions { + return providerOptionsMap +} + +// ProviderNotFoundError indicates that no matching KMS provider was found +type ProviderNotFoundError struct { + ref string +} + +func (e *ProviderNotFoundError) Error() string { + return fmt.Sprintf("no kms provider found for key reference: %s", e.ref) +} diff --git a/signer/registry.go b/signer/registry.go index 3c828fde..b9d05d17 100644 --- a/signer/registry.go +++ b/signer/registry.go @@ -21,7 +21,10 @@ import ( "github.com/in-toto/go-witness/registry" ) -var signerRegistry = registry.New[SignerProvider]() +var ( + signerRegistry = registry.New[SignerProvider]() + verifierRegistry = registry.New[VerifierProvider]() +) type SignerProvider interface { Signer(context.Context) (cryptoutil.Signer, error) @@ -38,3 +41,21 @@ func RegistryEntries() []registry.Entry[SignerProvider] { func NewSignerProvider(name string, opts ...func(SignerProvider) (SignerProvider, error)) (SignerProvider, error) { return signerRegistry.NewEntity(name, opts...) } + +// NOTE: This is a temporary interface, and should not be used. It will be deprecated in a future release. +// The same applies to the functions that use this interface. +type VerifierProvider interface { + Verifier(context.Context) (cryptoutil.Verifier, error) +} + +func RegisterVerifier(name string, factory func() VerifierProvider, opts ...registry.Configurer) { + verifierRegistry.Register(name, factory, opts...) +} + +func VerifierRegistryEntries() []registry.Entry[VerifierProvider] { + return verifierRegistry.AllEntries() +} + +func NewVerifierProvider(name string, opts ...func(VerifierProvider) (VerifierProvider, error)) (VerifierProvider, error) { + return verifierRegistry.NewEntity(name, opts...) +} diff --git a/verify.go b/verify.go index 2aa4c74f..3ff44fb8 100644 --- a/verify.go +++ b/verify.go @@ -123,7 +123,7 @@ func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers [ return nil, fmt.Errorf("failed to verify policy signature: %w", err) } - log.Info("Policy signature verification passed") + log.Info("policy signature verified") pol := policy.Policy{} if err := json.Unmarshal(vo.policyEnvelope.Payload, &pol); err != nil { @@ -132,7 +132,7 @@ func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers [ pubKeysById, err := pol.PublicKeyVerifiers() if err != nil { - return nil, fmt.Errorf("failed to get pulic keys from policy: %w", err) + return nil, fmt.Errorf("failed to get public keys from policy: %w", err) } pubkeys := make([]cryptoutil.Verifier, 0)