Skip to content
Snippets Groups Projects
Unverified Commit 0fcdf194 authored by Hayden B's avatar Hayden B Committed by GitHub
Browse files

Add support for providing certificate chain for X509 signature types (#747)


This allows you to create an entry for the entire certificate chain, not
just the leaf certificate. The certificate chain will be verified before
adding the entry.

Signed-off-by: default avatarHayden Blauzvern <hblauzvern@google.com>
parent f525de86
No related branches found
No related tags found
No related merge requests found
// 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 testutils
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"math/big"
"time"
)
/*
To use:
rootCert, rootKey, _ := GenerateRootCa()
subCert, subKey, _ := GenerateSubordinateCa(rootCert, rootKey)
leafCert, _, _ := GenerateLeafCert("subject", "oidc-issuer", subCert, subKey)
roots := x509.NewCertPool()
subs := x509.NewCertPool()
roots.AddCert(rootCert)
subs.AddCert(subCert)
opts := x509.VerifyOptions{
Roots: roots,
Intermediates: subs,
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageCodeSigning,
},
}
_, err := leafCert.Verify(opts)
*/
func createCertificate(template *x509.Certificate, parent *x509.Certificate, pub interface{}, priv crypto.Signer) (*x509.Certificate, error) {
certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv)
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
return cert, nil
}
func GenerateRootCa() (*x509.Certificate, *ecdsa.PrivateKey, error) {
rootTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "sigstore",
Organization: []string{"sigstore.dev"},
},
NotBefore: time.Now().Add(-5 * time.Minute),
NotAfter: time.Now().Add(5 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
}
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
cert, err := createCertificate(rootTemplate, rootTemplate, &priv.PublicKey, priv)
if err != nil {
return nil, nil, err
}
return cert, priv, nil
}
func GenerateSubordinateCa(rootTemplate *x509.Certificate, rootPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) {
subTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "sigstore-sub",
Organization: []string{"sigstore.dev"},
},
NotBefore: time.Now().Add(-2 * time.Minute),
NotAfter: time.Now().Add(2 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
BasicConstraintsValid: true,
IsCA: true,
}
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
cert, err := createCertificate(subTemplate, rootTemplate, &priv.PublicKey, rootPriv)
if err != nil {
return nil, nil, err
}
return cert, priv, nil
}
func GenerateLeafCert(subject string, oidcIssuer string, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) {
certTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
EmailAddresses: []string{subject},
NotBefore: time.Now().Add(-1 * time.Minute),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
IsCA: false,
ExtraExtensions: []pkix.Extension{{
// OID for OIDC Issuer extension
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1},
Critical: false,
Value: []byte(oidcIssuer),
}},
}
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv)
if err != nil {
return nil, nil, err
}
return cert, priv, nil
}
......@@ -69,7 +69,17 @@ func (s Signature) Verify(r io.Reader, k interface{}, opts ...sigsig.VerifyOptio
p := key.key
if p == nil {
p = key.cert.c.PublicKey
switch {
case key.cert != nil:
p = key.cert.c.PublicKey
case len(key.certs) > 0:
if err := verifyCertChain(key.certs); err != nil {
return err
}
p = key.certs[0].PublicKey
default:
return errors.New("no public key found")
}
}
verifier, err := sigsig.LoadVerifier(p, crypto.SHA256)
......@@ -81,8 +91,9 @@ func (s Signature) Verify(r io.Reader, k interface{}, opts ...sigsig.VerifyOptio
// PublicKey Public Key that follows the x509 standard
type PublicKey struct {
key interface{}
cert *cert
key interface{}
cert *cert
certs []*x509.Certificate
}
type cert struct {
......@@ -97,11 +108,20 @@ func NewPublicKey(r io.Reader) (*PublicKey, error) {
return nil, err
}
block, _ := pem.Decode(rawPub)
block, rest := pem.Decode(rawPub)
if block == nil {
return nil, errors.New("invalid public key: failure decoding PEM")
}
// Handle certificate chain, concatenated PEM-encoded certificates
if len(rest) > 0 {
certs, err := cryptoutils.UnmarshalCertificatesFromPEM(rawPub)
if err != nil {
return nil, err
}
return &PublicKey{certs: certs}, nil
}
switch block.Type {
case string(cryptoutils.PublicKeyPEMType):
key, err := x509.ParsePKIXPublicKey(block.Bytes)
......@@ -131,6 +151,8 @@ func (k PublicKey) CanonicalValue() (encoded []byte, err error) {
encoded, err = cryptoutils.MarshalPublicKeyToPEM(k.key)
case k.cert != nil:
encoded, err = cryptoutils.MarshalCertificateToPEM(k.cert.c)
case k.certs != nil:
encoded, err = cryptoutils.MarshalCertificatesToPEM(k.certs)
default:
err = fmt.Errorf("x509 public key has not been initialized")
}
......@@ -142,14 +164,23 @@ func (k PublicKey) CryptoPubKey() crypto.PublicKey {
if k.cert != nil {
return k.cert.c.PublicKey
}
if len(k.certs) > 0 {
return k.certs[0].PublicKey
}
return k.key
}
// EmailAddresses implements the pki.PublicKey interface
func (k PublicKey) EmailAddresses() []string {
var names []string
var cert *x509.Certificate
if k.cert != nil {
for _, name := range k.cert.c.EmailAddresses {
cert = k.cert.c
} else if len(k.certs) > 0 {
cert = k.certs[0]
}
if cert != nil {
for _, name := range cert.EmailAddresses {
validate := validator.New()
errs := validate.Var(name, "required,email")
if errs == nil {
......@@ -160,6 +191,31 @@ func (k PublicKey) EmailAddresses() []string {
return names
}
func verifyCertChain(certChain []*x509.Certificate) error {
if len(certChain) == 0 {
return errors.New("no certificate chain provided")
}
// No certificate chain to verify
if len(certChain) == 1 {
return nil
}
rootPool := x509.NewCertPool()
rootPool.AddCert(certChain[len(certChain)-1])
subPool := x509.NewCertPool()
for _, c := range certChain[1 : len(certChain)-1] {
subPool.AddCert(c)
}
if _, err := certChain[0].Verify(x509.VerifyOptions{
Roots: rootPool,
Intermediates: subPool,
// Allow any key usage
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}); err != nil {
return err
}
return nil
}
func CertChainToPEM(certChain []*x509.Certificate) ([]byte, error) {
var pemBytes bytes.Buffer
for _, cert := range certChain {
......
......@@ -19,10 +19,13 @@ import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/x509"
"reflect"
"strings"
"testing"
"github.com/sigstore/rekor/pkg/pki/x509/testutils"
"github.com/sigstore/rekor/pkg/signer"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
......@@ -242,3 +245,80 @@ func TestCertChain_Verify(t *testing.T) {
}
}
}
func TestPublicKeyWithCertChain(t *testing.T) {
rootCert, rootKey, _ := testutils.GenerateRootCa()
subCert, subKey, _ := testutils.GenerateSubordinateCa(rootCert, rootKey)
leafCert, leafKey, _ := testutils.GenerateLeafCert("subject@example.com", "oidc-issuer", subCert, subKey)
pemCertChain, err := cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, subCert, rootCert})
if err != nil {
t.Fatalf("unexpected error marshalling cert chain: %v", err)
}
pub, err := NewPublicKey(bytes.NewReader(pemCertChain))
if err != nil {
t.Fatalf("unexpected error generating public key: %v", err)
}
if pub.certs == nil || !pub.certs[0].Equal(leafCert) || !pub.certs[1].Equal(subCert) || !pub.certs[2].Equal(rootCert) {
t.Fatal("expected certificate chain to match provided certificate chain")
}
if !pub.CryptoPubKey().(*ecdsa.PublicKey).Equal(leafKey.Public()) {
t.Fatal("expected public keys to match")
}
if !reflect.DeepEqual(pub.EmailAddresses(), leafCert.EmailAddresses) {
t.Fatalf("expected matching email addresses, expected %v, got %v", leafCert.EmailAddresses, pub.EmailAddresses())
}
canonicalValue, err := pub.CanonicalValue()
if err != nil {
t.Fatalf("unexpected error fetching canonical value: %v", err)
}
if !reflect.DeepEqual(canonicalValue, pemCertChain) {
t.Fatalf("expected canonical value %v, got %v", pemCertChain, canonicalValue)
}
// Generate signature to verify
data := []byte("test")
signer, err := signature.LoadSigner(leafKey, crypto.SHA256)
if err != nil {
t.Fatal(err)
}
sigBytes, err := signer.SignMessage(bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
s, err := NewSignature(bytes.NewReader(sigBytes))
if err != nil {
t.Fatalf("unexpected error generating signature: %v", err)
}
err = s.Verify(bytes.NewReader(data), pub)
if err != nil {
t.Fatalf("unexpected error verifying signature, %v", err)
}
// Verify error with invalid chain
pemCertChain, _ = cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, rootCert})
pub, _ = NewPublicKey(bytes.NewReader(pemCertChain))
signer, _ = signature.LoadSigner(leafKey, crypto.SHA256)
sigBytes, _ = signer.SignMessage(bytes.NewReader(data))
s, _ = NewSignature(bytes.NewReader(sigBytes))
err = s.Verify(bytes.NewReader(data), pub)
if err == nil || !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
t.Fatalf("expected error verifying signature, got %v", err)
}
// Verify works with chain without intermediate
leafCert, leafKey, _ = testutils.GenerateLeafCert("subject@example.com", "oidc-issuer", rootCert, rootKey)
pemCertChain, _ = cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, rootCert})
pub, _ = NewPublicKey(bytes.NewReader(pemCertChain))
signer, _ = signature.LoadSigner(leafKey, crypto.SHA256)
sigBytes, _ = signer.SignMessage(bytes.NewReader(data))
s, _ = NewSignature(bytes.NewReader(sigBytes))
err = s.Verify(bytes.NewReader(data), pub)
if err != nil {
t.Fatalf("unexpected error verifying signature, %v", err)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment