Skip to content

Commit

Permalink
feat: add subject URIs to index for x509 certificates (#897)
Browse files Browse the repository at this point in the history
* feat: add subject URIs to index for x509 certificates

Signed-off-by: Asra Ali <asraa@google.com>

* fix comments

Signed-off-by: Asra Ali <asraa@google.com>

* fix lint

Signed-off-by: Asra Ali <asraa@google.com>

* Address another bob comment

Signed-off-by: Asra Ali <asraa@google.com>
  • Loading branch information
asraa authored Jul 1, 2022
1 parent 66f5c06 commit 234afe7
Show file tree
Hide file tree
Showing 19 changed files with 98 additions and 29 deletions.
5 changes: 5 additions & 0 deletions pkg/pki/minisign/minisign.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,8 @@ func (k PublicKey) CanonicalValue() ([]byte, error) {
func (k PublicKey) EmailAddresses() []string {
return nil
}

// Subjects implements the pki.PublicKey interface
func (k PublicKey) Subjects() []string {
return nil
}
5 changes: 5 additions & 0 deletions pkg/pki/pgp/pgp.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,8 @@ func (k PublicKey) EmailAddresses() []string {
}
return names
}

// Subjects implements the pki.PublicKey interface
func (k PublicKey) Subjects() []string {
return k.EmailAddresses()
}
30 changes: 15 additions & 15 deletions pkg/pki/pgp/pgp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,18 +347,18 @@ func TestEmailAddresses(t *testing.T) {
type test struct {
caseDesc string
inputFile string
emails []string
subjects []string
}

var k PublicKey
if len(k.EmailAddresses()) != 0 {
t.Errorf("EmailAddresses for unitialized key should give empty slice")
if len(k.Subjects()) != 0 {
t.Errorf("Subjects for unitialized key should give empty slice")
}
tests := []test{
{caseDesc: "Valid armored public key", inputFile: "testdata/valid_armored_public.pgp", emails: []string{}},
{caseDesc: "Valid armored public key with multiple subentries", inputFile: "testdata/valid_armored_complex_public.pgp", emails: []string{"linux-packages-keymaster@google.com", "linux-packages-keymaster@google.com"}},
{caseDesc: "Valid binary public key", inputFile: "testdata/valid_binary_public.pgp", emails: []string{}},
{caseDesc: "Valid binary public key with multiple subentries", inputFile: "testdata/valid_binary_complex_public.pgp", emails: []string{"linux-packages-keymaster@google.com", "linux-packages-keymaster@google.com"}},
{caseDesc: "Valid armored public key", inputFile: "testdata/valid_armored_public.pgp", subjects: []string{}},
{caseDesc: "Valid armored public key with multiple subentries", inputFile: "testdata/valid_armored_complex_public.pgp", subjects: []string{"linux-packages-keymaster@google.com", "linux-packages-keymaster@google.com"}},
{caseDesc: "Valid binary public key", inputFile: "testdata/valid_binary_public.pgp", subjects: []string{}},
{caseDesc: "Valid binary public key with multiple subentries", inputFile: "testdata/valid_binary_complex_public.pgp", subjects: []string{"linux-packages-keymaster@google.com", "linux-packages-keymaster@google.com"}},
}

for _, tc := range tests {
Expand All @@ -374,18 +374,18 @@ func TestEmailAddresses(t *testing.T) {
t.Errorf("%v: Error reading input for TestEmailAddresses: %v", tc.caseDesc, err)
}

emails := inputKey.EmailAddresses()
subjects := inputKey.Subjects()

if len(emails) == len(tc.emails) {
if len(emails) > 0 {
sort.Strings(emails)
sort.Strings(tc.emails)
if !reflect.DeepEqual(emails, tc.emails) {
t.Errorf("%v: Error getting email addresses from keys, got %v, expected %v", tc.caseDesc, emails, tc.emails)
if len(subjects) == len(tc.subjects) {
if len(subjects) > 0 {
sort.Strings(subjects)
sort.Strings(tc.subjects)
if !reflect.DeepEqual(subjects, tc.subjects) {
t.Errorf("%v: Error getting subjects from keys, got %v, expected %v", tc.caseDesc, subjects, tc.subjects)
}
}
} else {
t.Errorf("%v: Error getting email addresses from keys length, got %v, expected %v", tc.caseDesc, len(emails), len(tc.emails))
t.Errorf("%v: Error getting subjects from keys length, got %v, expected %v", tc.caseDesc, len(subjects), len(tc.subjects))
}

}
Expand Down
5 changes: 5 additions & 0 deletions pkg/pki/pkcs7/pkcs7.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,8 @@ func (k PublicKey) EmailAddresses() []string {

return names
}

// Subjects implements the pki.PublicKey interface
func (k PublicKey) Subjects() []string {
return k.EmailAddresses()
}
2 changes: 1 addition & 1 deletion pkg/pki/pkcs7/pkcs7_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ func TestEmailAddresses(t *testing.T) {
if err != nil {
t.Fatal(err)
}
emails := pub.EmailAddresses()
emails := pub.Subjects()

if len(emails) == len(tt.emails) {
if len(emails) > 0 {
Expand Down
3 changes: 3 additions & 0 deletions pkg/pki/pki.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import (
// PublicKey Generic object representing a public key (regardless of format & algorithm)
type PublicKey interface {
CanonicalValue() ([]byte, error)
// Deprecated: EmailAddresses() will be deprecated in favor of Subjects() which will
// also return Subject URIs present in public keys.
EmailAddresses() []string
Subjects() []string
}

// Signature Generic object representing a signature (regardless of format & algorithm)
Expand Down
5 changes: 5 additions & 0 deletions pkg/pki/ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,8 @@ func (k PublicKey) CanonicalValue() ([]byte, error) {
func (k PublicKey) EmailAddresses() []string {
return nil
}

// Subjects implements the pki.PublicKey interface
func (k PublicKey) Subjects() []string {
return nil
}
5 changes: 5 additions & 0 deletions pkg/pki/tuf/tuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,8 @@ func (k PublicKey) SpecVersion() (string, error) {
func (k PublicKey) EmailAddresses() []string {
return nil
}

// Subjects implements the pki.PublicKey interface
func (k PublicKey) Subjects() []string {
return nil
}
6 changes: 5 additions & 1 deletion pkg/pki/x509/testutils/cert_test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"crypto/x509/pkix"
"encoding/asn1"
"math/big"
"net/url"
"time"
)

Expand Down Expand Up @@ -115,7 +116,7 @@ func GenerateSubordinateCa(rootTemplate *x509.Certificate, rootPriv crypto.Signe
return cert, priv, nil
}

func GenerateLeafCert(subject string, oidcIssuer string, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) {
func GenerateLeafCert(subject, oidcIssuer string, uri *url.URL, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) {
certTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
EmailAddresses: []string{subject},
Expand All @@ -131,6 +132,9 @@ func GenerateLeafCert(subject string, oidcIssuer string, parentTemplate *x509.Ce
Value: []byte(oidcIssuer),
}},
}
if uri != nil {
certTemplate.URIs = []*url.URL{uri}
}

priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
Expand Down
27 changes: 26 additions & 1 deletion pkg/pki/x509/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ func (k PublicKey) EmailAddresses() []string {
cert = k.certs[0]
}
if cert != nil {
validate := validator.New()
for _, name := range cert.EmailAddresses {
validate := validator.New()
errs := validate.Var(name, "required,email")
if errs == nil {
names = append(names, strings.ToLower(name))
Expand All @@ -192,6 +192,31 @@ func (k PublicKey) EmailAddresses() []string {
return names
}

// Subjects implements the pki.PublicKey interface
func (k PublicKey) Subjects() []string {
var names []string
var cert *x509.Certificate
if k.cert != nil {
cert = k.cert.c
} else if len(k.certs) > 0 {
cert = k.certs[0]
}
if cert != nil {
validate := validator.New()
for _, name := range cert.EmailAddresses {
if errs := validate.Var(name, "required,email"); errs == nil {
names = append(names, strings.ToLower(name))
}
}
for _, name := range cert.URIs {
if errs := validate.Var(name.String(), "required,uri"); errs == nil {
names = append(names, strings.ToLower(name.String()))
}
}
}
return names
}

func verifyCertChain(certChain []*x509.Certificate) error {
if len(certChain) == 0 {
return errors.New("no certificate chain provided")
Expand Down
14 changes: 11 additions & 3 deletions pkg/pki/x509/x509_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/x509"
"net/url"
"reflect"
"strings"
"testing"
Expand Down Expand Up @@ -200,7 +201,8 @@ func TestSignature_VerifyFail(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)
url, _ := url.Parse("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.1.1")
leafCert, leafKey, _ := testutils.GenerateLeafCert("subject@example.com", "oidc-issuer", url, subCert, subKey)

pemCertChain, err := cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, subCert, rootCert})
if err != nil {
Expand All @@ -220,7 +222,13 @@ func TestPublicKeyWithCertChain(t *testing.T) {
}

if !reflect.DeepEqual(pub.EmailAddresses(), leafCert.EmailAddresses) {
t.Fatalf("expected matching email addresses, expected %v, got %v", leafCert.EmailAddresses, pub.EmailAddresses())
t.Fatalf("expected matching subjects, expected %v, got %v", leafCert.EmailAddresses, pub.EmailAddresses())
}

expectedSubjects := leafCert.EmailAddresses
expectedSubjects = append(expectedSubjects, leafCert.URIs[0].String())
if !reflect.DeepEqual(pub.Subjects(), expectedSubjects) {
t.Fatalf("expected matching subjects, expected %v, got %v", expectedSubjects, pub.Subjects())
}

canonicalValue, err := pub.CanonicalValue()
Expand Down Expand Up @@ -274,7 +282,7 @@ func TestPublicKeyWithCertChain(t *testing.T) {
}

// Verify works with chain without intermediate
leafCert, leafKey, _ = testutils.GenerateLeafCert("subject@example.com", "oidc-issuer", rootCert, rootKey)
leafCert, leafKey, _ = testutils.GenerateLeafCert("subject@example.com", "oidc-issuer", nil, rootCert, rootKey)
pemCertChain, _ = cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, rootCert})
pub, _ = NewPublicKey(bytes.NewReader(pemCertChain))
signer, _ = signature.LoadSigner(leafKey, crypto.SHA256)
Expand Down
2 changes: 1 addition & 1 deletion pkg/types/alpine/v0.0.1/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (v V001Entry) IndexKeys() ([]string, error) {
keyHash := sha256.Sum256(key)
result = append(result, strings.ToLower(hex.EncodeToString(keyHash[:])))

result = append(result, keyObj.EmailAddresses()...)
result = append(result, keyObj.Subjects()...)

if v.AlpineModel.Package.Hash != nil {
hashKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.AlpineModel.Package.Hash.Algorithm, *v.AlpineModel.Package.Hash.Value))
Expand Down
2 changes: 1 addition & 1 deletion pkg/types/cose/v0.0.1/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (v V001Entry) IndexKeys() ([]string, error) {
keyHash := sha256.Sum256(key)
result = append(result, strings.ToLower(hex.EncodeToString(keyHash[:])))
}
result = append(result, keyObj.EmailAddresses()...)
result = append(result, keyObj.Subjects()...)

// 2. Overall envelope
result = append(result, formatKey(v.CoseObj.Message))
Expand Down
4 changes: 4 additions & 0 deletions pkg/types/cose/v0.0.1/entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ func (t testPublicKey) EmailAddresses() []string {
return nil
}

func (t testPublicKey) Subjects() []string {
return nil
}

func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/types/hashedrekord/v0.0.1/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (v V001Entry) IndexKeys() ([]string, error) {
if err != nil {
return nil, err
}
result = append(result, pub.EmailAddresses()...)
result = append(result, pub.Subjects()...)

if v.HashedRekordObj.Data.Hash != nil {
hashKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.HashedRekordObj.Data.Hash.Algorithm, *v.HashedRekordObj.Data.Hash.Value))
Expand Down
2 changes: 1 addition & 1 deletion pkg/types/helm/v0.0.1/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (v V001Entry) IndexKeys() ([]string, error) {
keyHash := sha256.Sum256(key)
result = append(result, strings.ToLower(hex.EncodeToString(keyHash[:])))

result = append(result, keyObj.EmailAddresses()...)
result = append(result, keyObj.Subjects()...)

algorithm, chartHash, err := provenance.GetChartAlgorithmHash()

Expand Down
4 changes: 2 additions & 2 deletions pkg/types/intoto/v0.0.1/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ func (v V001Entry) IndexKeys() ([]string, error) {
keyHash := sha256.Sum256(key)
result = append(result, fmt.Sprintf("sha256:%s", strings.ToLower(hex.EncodeToString(keyHash[:]))))

// add digest over any email addresses within signing certificate
result = append(result, v.keyObj.EmailAddresses()...)
// add digest over any subjects within signing certificate
result = append(result, v.keyObj.Subjects()...)
} else {
log.Logger.Errorf("could not canonicalize public key to include in index keys: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/types/rekord/v0.0.1/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (v V001Entry) IndexKeys() ([]string, error) {
result = append(result, strings.ToLower(hex.EncodeToString(keyHash[:])))
}

result = append(result, keyObj.EmailAddresses()...)
result = append(result, keyObj.Subjects()...)

if v.RekordObj.Data.Hash != nil {
hashKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.RekordObj.Data.Hash.Algorithm, *v.RekordObj.Data.Hash.Value))
Expand Down
2 changes: 1 addition & 1 deletion pkg/types/rpm/v0.0.1/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (v V001Entry) IndexKeys() ([]string, error) {
keyHash := sha256.Sum256(key)
result = append(result, strings.ToLower(hex.EncodeToString(keyHash[:])))

result = append(result, keyObj.EmailAddresses()...)
result = append(result, keyObj.Subjects()...)

if v.RPMModel.Package.Hash != nil {
hashKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.RPMModel.Package.Hash.Algorithm, *v.RPMModel.Package.Hash.Value))
Expand Down

0 comments on commit 234afe7

Please sign in to comment.