Skip to content

Commit

Permalink
Populate intermediate certs from the OCI chain annotation
Browse files Browse the repository at this point in the history
This adds support for verifying OCI signatures with chains
that include more than one certificate in the chain, a root and
subordinate.

Signed-off-by: Hayden Blauzvern <[email protected]>
  • Loading branch information
haydentherapper committed Mar 22, 2022
1 parent 3e7fbf5 commit 0eee964
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 0 deletions.
28 changes: 28 additions & 0 deletions pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,20 @@ func VerifyImageSignature(ctx context.Context, sig oci.Signature, h v1.Hash, co
if cert == nil {
return bundleVerified, errors.New("no certificate found on signature")
}
// Create a certificate pool for intermediate CA certificates, excluding the root
chain, err := sig.Chain()
if err != nil {
return bundleVerified, err
}
if len(chain) > 1 {
pool := x509.NewCertPool()
for _, cert := range chain[:len(chain)-1] {
pool.AddCert(cert)
}
co.IntermediateCerts = pool
} else {
co.IntermediateCerts = nil
}
verifier, err = ValidateAndUnpackCert(cert, co)
if err != nil {
return bundleVerified, err
Expand Down Expand Up @@ -515,6 +529,20 @@ func verifyImageAttestations(ctx context.Context, atts oci.Signatures, h v1.Hash
if cert == nil {
return errors.New("no certificate found on attestation")
}
// Create a certificate pool for intermediate CA certificates, excluding the root
chain, err := att.Chain()
if err != nil {
return err
}
if len(chain) > 1 {
pool := x509.NewCertPool()
for _, cert := range chain[:len(chain)-1] {
pool.AddCert(cert)
}
co.IntermediateCerts = pool
} else {
co.IntermediateCerts = nil
}
verifier, err = ValidateAndUnpackCert(cert, co)
if err != nil {
return err
Expand Down
125 changes: 125 additions & 0 deletions pkg/cosign/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@ package cosign
import (
"context"
"crypto"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"io"
"strings"
"testing"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/pkg/errors"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
Expand Down Expand Up @@ -63,6 +68,15 @@ func (m *mockAttestation) Annotations() (map[string]string, error) {
func (m *mockAttestation) Payload() ([]byte, error) {
return json.Marshal(m.payload)
}

func appendSlices(slices [][]byte) []byte {
var tmp []byte
for _, s := range slices {
tmp = append(tmp, s...)
}
return tmp
}

func Test_verifyOCIAttestation(t *testing.T) {
stmt, err := json.Marshal(in_toto.ProvenanceStatement{})
if err != nil {
Expand Down Expand Up @@ -94,6 +108,117 @@ func Test_verifyOCIAttestation(t *testing.T) {
}
}

func TestVerifyImageSignature(t *testing.T) {
rootCert, rootKey, _ := test.GenerateRootCa()
subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey)
leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey)
pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw})
pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw})
pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw})

rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)

payload := []byte{1, 2, 3, 4}
h := sha256.Sum256(payload)
signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256)

ociSig, _ := static.NewSignature(payload,
base64.StdEncoding.EncodeToString(signature),
static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub, pemRoot})))
verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool})
if err != nil {
t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err)
}
// TODO: Create fake bundle and test verification
if verified == true {
t.Fatalf("expected verified=false, got verified=true")
}
}

func TestVerifyImageSignatureMultipleSubs(t *testing.T) {
rootCert, rootKey, _ := test.GenerateRootCa()
subCert1, subKey1, _ := test.GenerateSubordinateCa(rootCert, rootKey)
subCert2, subKey2, _ := test.GenerateSubordinateCa(subCert1, subKey1)
subCert3, subKey3, _ := test.GenerateSubordinateCa(subCert2, subKey2)
leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert3, subKey3)
pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw})
pemSub1 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert1.Raw})
pemSub2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert2.Raw})
pemSub3 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert3.Raw})
pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw})

rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)

payload := []byte{1, 2, 3, 4}
h := sha256.Sum256(payload)
signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256)

ociSig, _ := static.NewSignature(payload,
base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub3, pemSub2, pemSub1, pemRoot})))
verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool})
if err != nil {
t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err)
}
// TODO: Create fake bundle and test verification
if verified == true {
t.Fatalf("expected verified=false, got verified=true")
}
}

func TestVerifyImageSignatureWithOnlyRoot(t *testing.T) {
rootCert, rootKey, _ := test.GenerateRootCa()
leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey)
pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw})
pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw})

rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)

payload := []byte{1, 2, 3, 4}
h := sha256.Sum256(payload)
signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256)

ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, pemRoot))
verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool})
if err != nil {
t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err)
}
// TODO: Create fake bundle and test verification
if verified == true {
t.Fatalf("expected verified=false, got verified=true")
}
}

func TestVerifyImageSignatureWithMissingSub(t *testing.T) {
rootCert, rootKey, _ := test.GenerateRootCa()
subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey)
leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey)
pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw})
pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw})

rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)

payload := []byte{1, 2, 3, 4}
h := sha256.Sum256(payload)
signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256)

ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, pemRoot))
verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{RootCerts: rootPool})
if err == nil {
t.Fatal("expected error while verifying signature")
}
if !strings.Contains(err.Error(), "certificate signed by unknown authority") {
t.Fatal("expected error while verifying signature")
}
// TODO: Create fake bundle and test verification
if verified == true {
t.Fatalf("expected verified=false, got verified=true")
}
}

func TestValidateAndUnpackCertSuccess(t *testing.T) {
subject := "email@email"
oidcIssuer := "https://accounts.google.com"
Expand Down

0 comments on commit 0eee964

Please sign in to comment.