diff --git a/.gitignore b/.gitignore index 8a197863b3c..0edc15b07ca 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,8 @@ *.libfuzzer *fuzz.a +# Root metadata +*.sigstore/root/ + bin* dist/ diff --git a/.sigstore/keys.json b/.sigstore/keys.json new file mode 100644 index 00000000000..0747aecf8b5 --- /dev/null +++ b/.sigstore/keys.json @@ -0,0 +1,57 @@ +[ + { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + } + }, + { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + } + }, + { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + } + }, + { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + } + }, + { + "keytype": "ecdsa-sha2-nistp256", + "scheme": "ecdsa-sha2-nistp256", + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + } + } +] diff --git a/cmd/cosign/cli/fulcio/fulcio.go b/cmd/cosign/cli/fulcio/fulcio.go index 5c46e9e2b69..64dd28f0a36 100644 --- a/cmd/cosign/cli/fulcio/fulcio.go +++ b/cmd/cosign/cli/fulcio/fulcio.go @@ -16,6 +16,7 @@ package fulcio import ( + "bytes" "context" "crypto" "crypto/ecdsa" @@ -27,6 +28,7 @@ import ( "fmt" "io/ioutil" "os" + "strings" "sync" "github.com/go-openapi/runtime" @@ -36,6 +38,7 @@ import ( "golang.org/x/term" "github.com/sigstore/cosign/pkg/cosign" + "github.com/sigstore/cosign/pkg/cosign/tuf" fulcioClient "github.com/sigstore/fulcio/pkg/generated/client" "github.com/sigstore/fulcio/pkg/generated/client/operations" "github.com/sigstore/fulcio/pkg/generated/models" @@ -53,6 +56,7 @@ const ( // This is the root in the fulcio project. //go:embed fulcio.pem var rootPem string +var fulcioTargetStr = `fulcio.crt.pem` type oidcConnector interface { OIDConnect(string, string, string) (*oauthflow.OIDCIDToken, error) @@ -203,8 +207,24 @@ func initRoots() *x509.CertPool { if !cp.AppendCertsFromPEM(raw) { panic("error creating root cert pool") } - } else if !cp.AppendCertsFromPEM([]byte(rootPem)) { - panic("error creating root cert pool") + } else { + // First try retrieving from TUF root. Otherwise use rootPem. + ctx := context.Background() // TODO: pass in context? + buf := tuf.ByteDestination{Buffer: &bytes.Buffer{}} + err := tuf.GetTarget(ctx, fulcioTargetStr, &buf) + if err != nil { + // The user may not have initialized the local root metadata. Log the error and use the embedded root. + fmt.Println("using embedded fulcio certificate. did you run `cosign init`? error retrieving target: ", err) + if !cp.AppendCertsFromPEM([]byte(rootPem)) { + panic("error creating root cert pool") + } + } else { + // TODO: Remove the string replace when SigStore root is updated. + replaced := strings.ReplaceAll(buf.String(), "\n ", "\n") + if !cp.AppendCertsFromPEM([]byte(replaced)) { + panic("error creating root cert pool") + } + } } return cp } diff --git a/cmd/cosign/cli/init.go b/cmd/cosign/cli/init.go new file mode 100644 index 00000000000..e65667ef788 --- /dev/null +++ b/cmd/cosign/cli/init.go @@ -0,0 +1,74 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// 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 cli + +import ( + "context" + "flag" + + "github.com/peterbourgon/ff/v3/ffcli" + ctuf "github.com/sigstore/cosign/pkg/cosign/tuf" +) + +func Init() *ffcli.Command { + var ( + flagset = flag.NewFlagSet("cosign init", flag.ExitOnError) + // TODO: Support HTTP mirrors as well + mirror = flagset.String("mirror", "sigstore-tuf-root", "GCS bucket to a SigStore TUF repository.") + root = flagset.String("root", ".sigstore/keys.json", "path to trusted initial root.") + threshold = flagset.Int("threshold", 3, "threshold of root key signers") + ) + return &ffcli.Command{ + Name: "init", + ShortUsage: "cosign init -mirror -out ", + ShortHelp: `Initializes SigStore root to retrieve trusted certificate and key targets for verification.`, + LongHelp: `Initializes SigStore root to retrieve trusted certificate and key targets for verification. + +The following options are used by default: + - Initial root keys are pulled from .sigstore/keys. If it does not exist, uses root keys provided in the release. + - SigStore current TUF repository is pulled from the GCS mirror at . + - Resulting trusted metadata is written to .sigstore/root. + +To provide an out-of-band trusted root.json, copy the file into a directory named .sigstore/root/. + +The resulting updated TUF repository will be written to .sigstore/root/. + +Trusted keys and certificate used in cosign verification (e.g. verifying Fulcio issued certificates +with Fulcio root CA) are pulled form the trusted metadata. + +EXAMPLES + # initialize root with distributed root keys, default mirror, and default out path. + cosign init + + # initialize with an out-of-band root key file. + cosign init + + # initialize with an out-of-band root key file and custom repository mirror. + cosign init-mirror <> + `, + FlagSet: flagset, + Exec: func(ctx context.Context, args []string) error { + // Initialize the remote repository. + remote, err := ctuf.GcsRemoteStore(ctx, *mirror, nil, nil) + if err != nil { + return err + } + + // Initialize and update the local SigStore root. + return ctuf.Init(context.Background(), *root, remote, *threshold) + }, + } +} diff --git a/cmd/cosign/main.go b/cmd/cosign/main.go index a99cb151007..163fb8ce79e 100644 --- a/cmd/cosign/main.go +++ b/cmd/cosign/main.go @@ -68,6 +68,8 @@ func main() { cli.Copy(), cli.Clean(), cli.Triangulate(), + // Init + cli.Init(), // Version cli.Version()}, Exec: func(context.Context, []string) error { diff --git a/go.mod b/go.mod index 9e657301339..226f6e2086d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/sigstore/cosign go 1.16 require ( + cloud.google.com/go/storage v1.16.0 github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20210303052042-6bc126869bf4 diff --git a/go.sum b/go.sum index 6d5298e3596..2136cc6f463 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,7 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.15.0/go.mod h1:mjjQMoxxyGH7Jr8K5qrx6N2O0AHsczI61sMNn03GIZI= +cloud.google.com/go/storage v1.16.0 h1:1UwAux2OZP4310YXg5ohqBEpV16Y93uZG4+qOX7K2Kg= cloud.google.com/go/storage v1.16.0/go.mod h1:ieKBmUyzcftN5tbxwnXClMKH00CfcQ+xL6NN0r5QfmE= code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= @@ -167,7 +168,6 @@ github.com/ReneKroon/ttlcache/v2 v2.7.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/ThalesIgnite/crypto11 v1.2.4 h1:3MebRK/U0mA2SmSthXAIZAdUA9w8+ZuKem2O6HuR1f8= github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= @@ -455,7 +455,6 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6Uezg github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= @@ -786,9 +785,11 @@ 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/licenseclassifier v0.0.0-20210325184830-bb04aff29e72/go.mod h1:qsqn2hxC+vURpyBRygGUuinTO42MFRLcsmQ/P8v94+M= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE= github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -978,6 +979,7 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -1084,7 +1086,6 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyex github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -1140,6 +1141,7 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -1159,6 +1161,7 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= @@ -1168,6 +1171,7 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-policy-agent/opa v0.31.0 h1:sVxgdUZz426hpPfeIP+XGIwy8yfVkETerRojY3nQTc4= @@ -1309,7 +1313,6 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1342,8 +1345,6 @@ github.com/sigstore/fulcio v0.1.1/go.mod h1:HAsi0o0xMmBIauM9QkJ4dyvmeEzK1ZGcmH33 github.com/sigstore/rekor v0.3.0 h1:OBEvo/Rv8NKKtiWq0WRHgXFpVPe1fGiqz93dfBh/Myo= github.com/sigstore/rekor v0.3.0/go.mod h1:cL9B3+/gp3BG+/bhkSHBA3MQZMten5xM6BhJYd5b5zU= github.com/sigstore/sigstore v0.0.0-20210713222344-1fee53516622/go.mod h1:aOSeNrlcHsfUD8Q1hwWd8KloNqBnxEZlu4k47cFg5rg= -github.com/sigstore/sigstore v0.0.0-20210726180807-7e34e36ecda1 h1:4pct+K5MTh3G4AbiSjYpYT3MVVI5WdDdJZEr9bTkLb8= -github.com/sigstore/sigstore v0.0.0-20210726180807-7e34e36ecda1/go.mod h1:/za/jqA/1XazvjIfvvtDkIAJZWKqkbcT5VTpHR7hnfQ= github.com/sigstore/sigstore v0.0.0-20210729211320-56a91f560f44 h1:V7tcgdv69z2dAn31YzOjc6tGuZHpjC3kcpYT+XJmw4s= github.com/sigstore/sigstore v0.0.0-20210729211320-56a91f560f44/go.mod h1:rJpRn7XmR/YrfNGDU9jh+vy5WMeSv5YKfNDBwnFg+Qg= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -1412,7 +1413,6 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1428,14 +1428,13 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 h1:iGnD/q9160NWqKZZ5vY4p0dMiYMRknzctfSkqA4nBDw= github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613/go.mod h1:g6AnIpDSYMcphz193otpSIzN+11Rs+AAIIC6rm1enug= -github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.0.0-20210630170422-22a94818d17b/go.mod h1:L+uU/NRFK/7h0NYAnsmvsX9EghDB5QVCcHCIrK2h5nw= -github.com/theupdateframework/go-tuf v0.0.0-20210722233521-90e262754396 h1:j4odVZMwglHp54CYsNHd0wls+lkQzxloQU9AQjQu0W4= github.com/theupdateframework/go-tuf v0.0.0-20210722233521-90e262754396/go.mod h1:L+uU/NRFK/7h0NYAnsmvsX9EghDB5QVCcHCIrK2h5nw= github.com/theupdateframework/go-tuf v0.0.0-20210804171843-477a5d73800a h1:jH3DSl+6QKbX+koCvBf3cP+1mLRANxk36/hUtvA6HVg= github.com/theupdateframework/go-tuf v0.0.0-20210804171843-477a5d73800a/go.mod h1:aDPMGsrpdPQqJa0ryp7LovT6qSqZ/zKmUDTHZK+wIf4= @@ -2207,6 +2206,7 @@ gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= diff --git a/pkg/cosign/tlog.go b/pkg/cosign/tlog.go index 8376aa31f17..d634c5c878f 100644 --- a/pkg/cosign/tlog.go +++ b/pkg/cosign/tlog.go @@ -15,6 +15,8 @@ package cosign import ( + "bytes" + "context" "encoding/base64" "encoding/hex" "fmt" @@ -29,6 +31,7 @@ import ( "github.com/pkg/errors" cremote "github.com/sigstore/cosign/pkg/cosign/remote" + "github.com/sigstore/cosign/pkg/cosign/tuf" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/client/pubkey" @@ -41,6 +44,19 @@ import ( // rekor.pub should be updated whenever the Rekor public key is rotated & the bundle annotation should be up-versioned //go:embed rekor.pub var rekorPub string +var rekorTargetStr = `rekor.pub` + +func GetRekorPub() string { + ctx := context.Background() // TODO: pass in context? + buf := tuf.ByteDestination{Buffer: &bytes.Buffer{}} + err := tuf.GetTarget(ctx, rekorTargetStr, &buf) + if err != nil { + // The user may not have initialized the local root metadata. Log the error and use the embedded root. + fmt.Println("using embedded rekor public key. did you run `cosign init`? error retrieving target: ", err) + return rekorPub + } + return buf.String() +} // TLogUpload will upload the signature, public key and payload to the transparency log. func TLogUpload(rekorClient *client.Rekor, signature, payload []byte, pemBytes []byte) (*models.LogEntryAnon, error) { diff --git a/pkg/cosign/tuf/client.go b/pkg/cosign/tuf/client.go new file mode 100644 index 00000000000..4937038864a --- /dev/null +++ b/pkg/cosign/tuf/client.go @@ -0,0 +1,211 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// 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 tuf + +import ( + "bytes" + "context" + "encoding/json" + "io" + "os" + "path" + "sync" + + "github.com/pkg/errors" + + "github.com/theupdateframework/go-tuf/client" + tuf_leveldbstore "github.com/theupdateframework/go-tuf/client/leveldbstore" + "github.com/theupdateframework/go-tuf/data" + "github.com/theupdateframework/go-tuf/util" +) + +const ( + TufRootEnv = "TUF_ROOT" + defaultLocalStore = ".sigstore/root/" +) + +// Global TUF client. Stores local targets in .sigstore/root. +// Could be in memory local store, but that would mean re-download each time cosign is run. +var rootClient *client.Client +var rootClientMu = &sync.Mutex{} + +func CosignRoot() string { + rootDir := os.Getenv(TufRootEnv) + if rootDir == "" { + return defaultLocalStore + } + return rootDir +} + +func CosignTargets() string { + return path.Join(CosignRoot(), "targets") +} + +// Target destinations compatible with go-tuf. +type targetDestination struct { + *os.File +} + +func (t *targetDestination) Delete() error { + t.Close() + return nil +} + +type ByteDestination struct { + *bytes.Buffer +} + +func (b *ByteDestination) Delete() error { + b.Reset() + return nil +} + +func getRootKeys(rootPath string) ([]*data.Key, error) { + rootFile, err := os.Open(rootPath) + if err != nil { + return nil, err + } + var rootKeys []*data.Key + if err := json.NewDecoder(rootFile).Decode(&rootKeys); err != nil { + return nil, err + } + return rootKeys, nil +} + +// Gets the global TUF client if the directory exists. +// This will not make a remote call. +func RootClient(ctx context.Context, local string, remote client.RemoteStore) (*client.Client, error) { + rootClientMu.Lock() + defer rootClientMu.Unlock() + if rootClient == nil { + // Instantiate the global TUF client from the local store. This does not do a download. + local, err := tuf_leveldbstore.FileLocalStore(local) + if err != nil { + return nil, errors.Wrap(err, "initializing local store") + } + rootClient = client.NewClient(local, remote) + } + return rootClient, nil +} + +func updateMetadataAndDownloadTargets(c *client.Client) error { + // Download initial targets and store in .sigstore/root/targets/. + targetFiles, err := c.Update() + if err != nil && !client.IsLatestSnapshot(err) { + return errors.Wrap(err, "updating tuf metadata") + } + + // Download targets, if they don't already exist and match the updated metadata. + if err := os.MkdirAll(CosignTargets(), 0700); err != nil { + return errors.Wrap(err, "creating targets dir") + } + for name := range targetFiles { + if err := downloadTarget(name, c, nil); err != nil { + return err + } + } + return nil +} + +func downloadTarget(name string, c *client.Client, out client.Destination) error { + f, err := os.Create(path.Join(CosignTargets(), name)) + if err != nil { + return errors.Wrap(err, "creating target file") + } + defer f.Close() + dest := targetDestination{f} + + if err := c.Download(name, &dest); err != nil { + return errors.Wrap(err, "downloading target") + } + if out != nil { + _, err = io.Copy(out, dest) + } + return err +} + +// Instantiates the global TUF client. Downloads all initial targets and stores in .sigstore/root/targets/. +func Init(ctx context.Context, rootFile string, remote client.RemoteStore, threshold int) error { + rootClient, err := RootClient(ctx, CosignRoot(), remote) + if err != nil { + return errors.Wrap(err, "initializing root client") + } + rootKeys, err := getRootKeys(rootFile) + if err != nil { + return errors.Wrap(err, "retrieving root keys") + } + if err := rootClient.Init(rootKeys, threshold); err != nil { + return errors.Wrap(err, "initializing tuf client") + } + // Download initial targets and store in .sigstore/root/targets/. + if err := os.MkdirAll(CosignRoot(), 0755); err != nil { + return errors.Wrap(err, "creating targets dir") + } + if err := updateMetadataAndDownloadTargets(rootClient); err != nil { + return errors.Wrap(err, "updating local metadata and targets") + } + + return nil +} + +func getTargetHelper(name string, out client.Destination, c *client.Client) error { + // Get valid target metadata. Does a local verification. + validMeta, err := c.Target(name) + if err != nil { + return errors.Wrap(err, "missing target metadata") + } + + // Get local target. + localTarget, err := os.Open(path.Join(CosignTargets(), name)) + if err != nil { + // If the file does not exist, download the target and copy to out. + return downloadTarget(name, c, out) + } + + // Otherwise, the file exists in the local store. + localMeta, err := util.GenerateTargetFileMeta(localTarget) + if err != nil { + return errors.Wrap(err, "generating local target metadata") + } + + // If local target meta does not match valid meta, update and re-download. + if err := util.TargetFileMetaEqual(validMeta, localMeta); err != nil { + if err := updateMetadataAndDownloadTargets(c); err != nil { + return errors.Wrap(err, "updating target metadata") + } + // Try again with updated metadata. + return getTargetHelper(name, out, c) + } + + // Target metadata equal, copy the file into out. + if _, err := localTarget.Seek(0, io.SeekStart); err != nil { + return err + } + if _, err := io.Copy(out, localTarget); err != nil { + return errors.Wrap(err, "copying target") + } + return localTarget.Close() +} + +func GetTarget(ctx context.Context, name string, out client.Destination) error { + // Reads the root in CosignRoot() directory. Does not use a remote. + c, err := RootClient(ctx, CosignRoot(), nil) + if err != nil { + return errors.Wrap(err, "retrieving root") + } + + return getTargetHelper(name, out, c) +} diff --git a/pkg/cosign/tuf/client_test.go b/pkg/cosign/tuf/client_test.go new file mode 100644 index 00000000000..38a96df8cfd --- /dev/null +++ b/pkg/cosign/tuf/client_test.go @@ -0,0 +1,172 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// 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 tuf + +import ( + "bytes" + "context" + "encoding/json" + "io" + "io/ioutil" + "os" + "path" + "testing" + + "github.com/theupdateframework/go-tuf" + "github.com/theupdateframework/go-tuf/client" + tuf_leveldbstore "github.com/theupdateframework/go-tuf/client/leveldbstore" +) + +// Only test the client without the remote fetch. + +// Set up a temporary file system store +func generateTestRepo(t *testing.T, files map[string][]byte) (*fakeRemoteStore, tuf.LocalStore, []byte) { + store := tuf.MemoryStore(nil, files) + repo, err := tuf.NewRepo(store) + if err := repo.Init(false); err != nil { + t.Fatalf("unexpected error") + } + if err != nil { + t.Fatalf("unexpected error") + } + for _, role := range []string{"root", "snapshot", "targets", "timestamp"} { + _, err := repo.GenKey(role) + if err != nil { + t.Fatalf("unexpected error") + } + } + for file := range files { + repo.AddTarget(file, nil) + } + repo.Snapshot(tuf.CompressionTypeNone) + repo.Timestamp() + repo.Commit() + keys, err := repo.RootKeys() + if err != nil { + t.Fatalf("unexpected error") + } + meta, err := store.GetMeta() + if err != nil { + t.Fatalf("unexpected error") + } + remote := newFakeRemoteStore(meta, files) + + keyJSON, err := json.Marshal(keys) + if err != nil { + t.Fatalf("unexpected error") + } + return remote, store, keyJSON +} + +func newFakeFile(b []byte) *fakeFile { + return &fakeFile{buf: bytes.NewReader(b), size: int64(len(b))} +} + +type fakeFile struct { + buf *bytes.Reader + bytesRead int + size int64 +} + +func (f *fakeFile) Read(p []byte) (int, error) { + n, err := f.buf.Read(p) + f.bytesRead += n + return n, err +} + +func (f *fakeFile) Close() error { + f.buf.Seek(0, io.SeekStart) + return nil +} + +func newFakeRemoteStore(meta map[string]json.RawMessage, targets map[string][]byte) *fakeRemoteStore { + remote := &fakeRemoteStore{meta: make(map[string]*fakeFile), + targets: make(map[string]*fakeFile)} + for name, data := range meta { + remote.meta[name] = newFakeFile(data) + } + for name, data := range targets { + remote.targets[name] = newFakeFile(data) + } + return remote +} + +type fakeRemoteStore struct { + meta map[string]*fakeFile + targets map[string]*fakeFile +} + +func (f *fakeRemoteStore) GetMeta(name string) (io.ReadCloser, int64, error) { + return f.get(name, f.meta) +} + +func (f *fakeRemoteStore) GetTarget(path string) (io.ReadCloser, int64, error) { + return f.get(path, f.targets) +} + +func (f *fakeRemoteStore) get(name string, store map[string]*fakeFile) (io.ReadCloser, int64, error) { + file, ok := store[name] + if !ok { + return nil, 0, client.ErrNotFound{File: name} + } + return file, file.size, nil +} + +// Correct metadata, retrieve target +func TestValidMetadata(t *testing.T) { + targetFiles := map[string][]byte{ + "foo.txt": []byte("foo")} + remote, store, keys := generateTestRepo(t, targetFiles) + + // Set up local with initial root.json + tmp := t.TempDir() + local, err := tuf_leveldbstore.FileLocalStore(tmp) + if err != nil { + t.Fatalf("unexpected error") + } + meta, _ := store.GetMeta() + root := meta["root.json"] + local.SetMeta("root.json", root) + db := path.Join(tmp, "tuf.db") + if err := os.Setenv(TufRootEnv, db); err != nil { + t.Fatalf("error setting env") + } + defer os.Unsetenv(TufRootEnv) + + // Set up client + tmp1 := t.TempDir() + rootFile, err := ioutil.TempFile(tmp1, "root.json") + if err != nil { + t.Fatalf("failed to create temp root key file: %v", err) + } + defer rootFile.Close() + if _, err := rootFile.Write(keys); err != nil { + t.Fatalf("failed to write key file: %v", err) + } + if err := Init(context.Background(), rootFile.Name(), remote, 1); err != nil { + t.Fatalf("unexpected error initializing root client %v", err) + } + + target := "foo.txt" + buf := ByteDestination{Buffer: &bytes.Buffer{}} + err = getTargetHelper(target, &buf, rootClient) + if err != nil { + t.Fatalf("retrieving target %v", err) + } + if !bytes.Equal(buf.Bytes(), targetFiles[target]) { + t.Fatalf("error retrieving target, expected %s got %s", buf.String(), targetFiles[target]) + } +} diff --git a/pkg/cosign/tuf/store.go b/pkg/cosign/tuf/store.go new file mode 100644 index 00000000000..d8d1216e314 --- /dev/null +++ b/pkg/cosign/tuf/store.go @@ -0,0 +1,77 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// 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 tuf + +import ( + "context" + "io" + "path" + + "cloud.google.com/go/storage" + "github.com/theupdateframework/go-tuf/client" +) + +type GcsRemoteOptions struct { + MetadataPath string + TargetsPath string +} + +type gcsRemoteStore struct { + bucket string + ctx context.Context + client *storage.Client + opts *GcsRemoteOptions +} + +// A remote store for TUF metadata on GCS. +func GcsRemoteStore(ctx context.Context, bucket string, opts *GcsRemoteOptions, client *storage.Client) (client.RemoteStore, error) { + if opts == nil { + opts = &GcsRemoteOptions{} + } + if opts.TargetsPath == "" { + opts.TargetsPath = "targets" + } + store := gcsRemoteStore{ctx: ctx, bucket: bucket, opts: opts, client: client} + if client == nil { + var err error + store.client, err = storage.NewClient(ctx) + if err != nil { + return nil, err + } + } + return &store, nil +} + +func (h *gcsRemoteStore) GetMeta(name string) (io.ReadCloser, int64, error) { + return h.get(path.Join(h.opts.MetadataPath, name)) +} + +func (h *gcsRemoteStore) GetTarget(name string) (io.ReadCloser, int64, error) { + return h.get(path.Join(h.opts.TargetsPath, name)) +} + +func (h *gcsRemoteStore) get(s string) (io.ReadCloser, int64, error) { + obj := h.client.Bucket(h.bucket).Object(s) + attrs, err := obj.Attrs(h.ctx) + if err != nil { + return nil, 0, err + } + rc, err := obj.NewReader(h.ctx) + if err != nil { + return nil, 0, err + } + return rc, attrs.Size, nil +} diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 784c0e4e47c..601fe71efa3 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -261,7 +261,7 @@ func (sp *SignedPayload) VerifyBundle() (bool, error) { if sp.bundleVerified { return true, nil } - rekorPubKey, err := PemToECDSAKey([]byte(rekorPub)) + rekorPubKey, err := PemToECDSAKey([]byte(GetRekorPub())) if err != nil { return false, errors.Wrap(err, "pem to ecdsa") }