Skip to content

Commit

Permalink
Refactor git commit verification into its own interface. (#167)
Browse files Browse the repository at this point in the history
This pulls out git commit verification logic (e.g. everything but Rekor
lookups) into it's own interface + struct so that it can be more easily
configured with user settings.

The only new change in behavior is the inclusion of the system cert pool
in the default cert pool used for validation.

Signed-off-by: Billy Lynch <billy@chainguard.dev>
  • Loading branch information
wlynch committed Oct 20, 2022
1 parent 07419a9 commit 6a3b4aa
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 62 deletions.
7 changes: 6 additions & 1 deletion command_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,17 @@ func commandVerify(cfg *config.Config) error {
return fmt.Errorf("failed to read signature data (detached: %T): %w", detached, err)
}

cv, err := git.NewCertVerifier()
if err != nil {
return fmt.Errorf("error creating git cert verifier: %w", err)
}

rekor, err := newRekorClient(cfg.Rekor)
if err != nil {
return fmt.Errorf("failed to create rekor client: %w", err)
}

summary, err := git.Verify(ctx, rekor, data, sig, detached)
summary, err := git.Verify(ctx, cv, rekor, data, sig, detached)
if err != nil {
if summary != nil && summary.Cert != nil {
emitBadSig(summary.Cert)
Expand Down
14 changes: 7 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ require (
github.com/sigstore/cosign v1.13.0
github.com/sigstore/fulcio v0.6.0
github.com/sigstore/rekor v0.12.2
github.com/sigstore/sigstore v1.4.4
github.com/sigstore/sigstore v1.4.5-0.20221018191222-7d467ad58b13
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1
)

require (
bitbucket.org/creachadair/shell v0.0.7 // indirect
cloud.google.com/go/compute v1.10.0 // indirect
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go v67.0.0+incompatible // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect
Expand Down Expand Up @@ -235,17 +235,17 @@ require (
go.uber.org/zap v1.23.0 // indirect
golang.org/x/exp v0.0.0-20220823124025-807a23277127 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220927171203-f486391704dc // indirect
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 // indirect
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/api v0.98.0 // indirect
google.golang.org/api v0.99.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220929141241-1ce7b20da813 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e // indirect
google.golang.org/grpc v1.50.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
Expand Down
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible h1:bmmC38SlE8/E81nNADlgmVGurPWMHDX2YNXVQMrBpEE=
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v67.0.0+incompatible h1:SVBwznSETB0Sipd0uyGJr7khLhJOFRUEUb+0JgkCvDo=
github.com/Azure/azure-sdk-for-go v67.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
Expand Down Expand Up @@ -595,6 +597,7 @@ github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4
github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-rod/rod v0.111.0 h1:aMNNdz10GYPYec9z1WsFqwAdRYVsuufVTOrah7whG3I=
github.com/go-rod/rod v0.112.0 h1:U9Yc+quw4hxZ6GrdbWFBeylvaYElEKM9ijFW2LYkGlA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
Expand Down Expand Up @@ -1384,6 +1387,8 @@ github.com/sigstore/rekor v0.12.2 h1:Ooe/aguLdb6BPVim2OaT+icfCsGFuS0UDaaf/hU5Qj0
github.com/sigstore/rekor v0.12.2/go.mod h1:7zGUsdw+hVtHLysHi7g4OSBJSQReNn7mllfjF2UCbo0=
github.com/sigstore/sigstore v1.4.4 h1:lVsnNTY8DUmy2hnwCPtimWfEqv+DIwleORkF8KyFsMs=
github.com/sigstore/sigstore v1.4.4/go.mod h1:wIqu9sN72+pds31MMu89GchxXHy17k+VZWc+HY1ZXMA=
github.com/sigstore/sigstore v1.4.5-0.20221018191222-7d467ad58b13 h1:EpuVyHdMHBQShtUcgfYV87vZWwAgzylFtTCUijUoJHk=
github.com/sigstore/sigstore v1.4.5-0.20221018191222-7d467ad58b13/go.mod h1:vBwrSR/g+waIs+llNXtdt4UZGQHvxu/JSrrEK5Ufq68=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
Expand Down Expand Up @@ -1856,6 +1861,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 h1:MgJ6t2zo8v0tbmLCueaCbF1RM+TtB0rs3Lv8DGtOIpY=
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -1885,6 +1892,8 @@ golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7Lm
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 h1:3VPzK7eqH25j7GYw5w6g/GzNRc0/fYtrxz27z1gD4W0=
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
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=
Expand Down Expand Up @@ -2232,6 +2241,8 @@ google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
google.golang.org/api v0.98.0 h1:yxZrcxXESimy6r6mdL5Q6EnZwmewDJK2dVg3g75s5Dg=
google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
google.golang.org/api v0.99.0 h1:tsBtOIklCE2OFxhmcYSVqGwSAN/Y897srxmcvAQnwK8=
google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
Expand Down Expand Up @@ -2352,6 +2363,8 @@ google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljW
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220929141241-1ce7b20da813 h1:buul04Ikd79A5tP8nGhKEyMfr+/HplsO6nqSUapWZ/M=
google.golang.org/genproto v0.0.0-20220929141241-1ce7b20da813/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e h1:halCgTFuLWDRD61piiNSxPsARANGD3Xl16hPrLgLiIg=
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
Expand Down Expand Up @@ -2396,6 +2409,8 @@ google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.50.0 h1:fPVVDxY9w++VjTZsYvXWqEf9Rqar/e+9zYfxKK+W+YU=
google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY=
google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
Expand Down
22 changes: 19 additions & 3 deletions pkg/git/signature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package git

import (
"context"
"crypto"
"crypto/x509"
"fmt"
Expand Down Expand Up @@ -67,9 +68,24 @@ func TestSignVerify(t *testing.T) {
if err != nil {
t.Fatalf("Sign() = %v", err)
}
if _, err := VerifySignature(data, sig, detached, roots, ca.ChainPool()); err != nil {
t.Fatalf("Verify() = %v", err)
}

// Deprecated, included for completeness
t.Run("VerifySignature", func(t *testing.T) {
if _, err := VerifySignature(data, sig, detached, roots, ca.ChainPool()); err != nil {
t.Fatalf("Verify() = %v", err)
}
})

t.Run("CertVerifier.Verify", func(t *testing.T) {
cv, err := NewCertVerifier(WithRootPool(roots))
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
if _, err := cv.Verify(ctx, data, sig, detached); err != nil {
t.Fatalf("Verify() = %v", err)
}
})
})
}
}
163 changes: 163 additions & 0 deletions pkg/git/verifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
//
// Copyright 2022 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 git

import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"time"

cms "github.com/github/smimesign/ietf-cms"
"github.com/sigstore/sigstore/pkg/fulcioroots"
)

// Verifier verifies git commit signature data.
type Verifier interface {
Verify(ctx context.Context, data, sig []byte, detached bool) (*x509.Certificate, error)
}

// CertVerifier is the default implementation of Verifier.
// It verifies git commits against a given CertPool. By default, the system
// CertPool + Fulcio roots are used for validation.
type CertVerifier struct {
roots *x509.CertPool
intermediates *x509.CertPool
}

type CertVerifierOption func(*CertVerifier) error

func NewCertVerifier(opts ...CertVerifierOption) (*CertVerifier, error) {
// Setup default cert pool - system pool + fulcio roots.
pool, err := x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("error getting system cert pool: %w", err)
}

roots := pool.Clone()
if err := fulcioroots.GetWithCertPool(roots); err != nil {
return nil, fmt.Errorf("getting fulcio root certificate: %w", err)
}

intermediates := pool.Clone()
if err := fulcioroots.GetIntermediatesWithCertPool(intermediates); err != nil {
return nil, fmt.Errorf("getting fulcio intermediate certificates: %w", err)
}

v := &CertVerifier{
roots: roots,
intermediates: intermediates,
}

for _, o := range opts {
if err := o(v); err != nil {
return nil, err
}
}

return v, err
}

// WithRootPool sets the base CertPool for the verifier.
// NOTE: this option is order sensitive - setting this will
// wipe out any previous cert pool configuration.
func WithRootPool(pool *x509.CertPool) CertVerifierOption {
return func(v *CertVerifier) error {
v.roots = pool
return nil
}
}

// WithRootPool sets the base CertPool for the verifier.
// NOTE: this option is order sensitive - setting this will
// wipe out any previous cert pool configuration.
func WithIntermediatePool(pool *x509.CertPool) CertVerifierOption {
return func(v *CertVerifier) error {
v.intermediates = pool
return nil
}
}

// AddIntermediateCert adds the given cert to the root pool.
func AddRootCert(certs ...*x509.Certificate) CertVerifierOption {
return func(v *CertVerifier) error {
for _, c := range certs {
v.roots.AddCert(c)
}
return nil
}
}

// AddIntermediateCert adds the given cert to the intermediate pool.
func AddIntermediateCert(certs ...*x509.Certificate) CertVerifierOption {
return func(v *CertVerifier) error {
for _, c := range certs {
v.intermediates.AddCert(c)
}
return nil
}
}

// Verify verifies for a given Git data + signature pair.
//
// Data should be the Git data that was signed (i.e. everything in the commit
// besides the signature). Note: passing in the commit object itself will not
// work.
//
// Signatures should be CMS/PKCS7 formatted.
func (v *CertVerifier) Verify(ctx context.Context, data, sig []byte, detached bool) (*x509.Certificate, error) {
// Try decoding as PEM
var der []byte
if blk, _ := pem.Decode(sig); blk != nil {
der = blk.Bytes
} else {
der = sig
}
// Parse signature
sd, err := cms.ParseSignedData(der)
if err != nil {
return nil, fmt.Errorf("failed to parse signature: %w", err)
}

// Generate verification options.
certs, err := sd.GetCertificates()
if err != nil {
return nil, fmt.Errorf("error getting signature certs: %w", err)
}
cert := certs[0]

opts := x509.VerifyOptions{
Roots: v.roots,
Intermediates: v.intermediates,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
// cosign hack: ignore the current time for now - we'll use the tlog to
// verify whether the commit was signed at a valid time.
CurrentTime: cert.NotBefore.Add(1 * time.Minute),
}

if detached {
if _, err := sd.VerifyDetached(data, opts); err != nil {
return nil, fmt.Errorf("failed to verify detached signature: %w", err)
}
} else {
if _, err := sd.Verify(opts); err != nil {
return nil, fmt.Errorf("failed to verify attached signature: %w", err)
}
}

return cert, nil
}
Loading

0 comments on commit 6a3b4aa

Please sign in to comment.