Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Verifier to get public key/cert and identities for entry type #1210

Merged
merged 11 commits into from
Jan 11, 2023
10 changes: 10 additions & 0 deletions pkg/pki/minisign/minisign.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,13 @@ func (k PublicKey) EmailAddresses() []string {
func (k PublicKey) Subjects() []string {
return nil
}

// Identities implements the pki.PublicKey interface
func (k PublicKey) Identities() ([]string, error) {
// returns base64-encoded key (sig alg, key ID, and public key)
key, err := k.CanonicalValue()
if err != nil {
return nil, err
}
return []string{string(key)}, nil
}
10 changes: 10 additions & 0 deletions pkg/pki/minisign/minisign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"errors"
"io"
"os"
"reflect"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -288,6 +289,15 @@ func TestCanonicalValuePublicKey(t *testing.T) {
if diff := cmp.Diff(rt.key, inputKey.key); diff != "" {
t.Error(diff)
}

// Identities should be equal to the canonical value for minisign
ids, err := outputKey.Identities()
if err != nil {
t.Errorf("unexpected error getting identities: %v", err)
}
if !reflect.DeepEqual([]string{string(cvOutput)}, ids) {
t.Errorf("identities and canonical value are not equal")
}
}
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/pki/pgp/pgp.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,16 @@ func (k PublicKey) EmailAddresses() []string {
func (k PublicKey) Subjects() []string {
return k.EmailAddresses()
}

// Identities implements the pki.PublicKey interface
func (k PublicKey) Identities() ([]string, error) {
// returns the email addresses and armored public key
var identities []string
identities = append(identities, k.Subjects()...)
key, err := k.CanonicalValue()
if err != nil {
return nil, err
}
identities = append(identities, string(key))
return identities, nil
}
10 changes: 10 additions & 0 deletions pkg/pki/pgp/pgp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,16 @@ func TestEmailAddresses(t *testing.T) {
t.Errorf("%v: Error getting subjects from keys length, got %v, expected %v", tc.caseDesc, len(subjects), len(tc.subjects))
}

expectedIDs := tc.subjects
key, _ := inputKey.CanonicalValue()
expectedIDs = append(expectedIDs, string(key))
ids, err := inputKey.Identities()
if err != nil {
t.Fatalf("unexpected error getting identities: %v", err)
}
if !reflect.DeepEqual(ids, expectedIDs) {
t.Errorf("identities are not equal")
}
}
}

Expand Down
34 changes: 33 additions & 1 deletion pkg/pki/pkcs7/pkcs7.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"strings"

"github.com/sassoftware/relic/lib/pkcs7"
"github.com/sigstore/sigstore/pkg/cryptoutils"
sigsig "github.com/sigstore/sigstore/pkg/signature"
)

Expand Down Expand Up @@ -211,5 +212,36 @@ func (k PublicKey) EmailAddresses() []string {

// Subjects implements the pki.PublicKey interface
func (k PublicKey) Subjects() []string {
return k.EmailAddresses()
// combine identities in the subject and SANs
identities := k.EmailAddresses()
cert, err := x509.ParseCertificate(k.certs[0].Raw)
if err != nil {
// This should not happen from a valid PublicKey, but fail gracefully.
return identities
}
identities = append(identities, cryptoutils.GetSubjectAlternateNames(cert)...)
return identities
}

// Identities implements the pki.PublicKey interface
func (k PublicKey) Identities() ([]string, error) {
var identities []string

// pkcs7 structure may contain both a key and certificate chain
pemCert, err := cryptoutils.MarshalCertificateToPEM(k.certs[0])
if err != nil {
return nil, err
}
identities = append(identities, string(pemCert))
pemKey, err := cryptoutils.MarshalPublicKeyToPEM(k.key)
if err != nil {
return nil, err
}
identities = append(identities, string(pemKey))

// Subjects come from the certificate Subject and SANs
// SANs could include an email, IP address, DNS name, URI, or any other value in the SAN
identities = append(identities, k.Subjects()...)

return identities, nil
}
197 changes: 196 additions & 1 deletion pkg/pki/pkcs7/pkcs7_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@ package pkcs7

import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"net/url"
"reflect"
"sort"
"strings"
"testing"

"github.com/sassoftware/relic/lib/pkcs7"
"github.com/sigstore/rekor/pkg/pki/x509/testutils"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)

const pkcsECDSAPEM = `-----BEGIN PKCS7-----
Expand Down Expand Up @@ -292,7 +299,7 @@ func TestEmailAddresses(t *testing.T) {
if err != nil {
t.Fatal(err)
}
emails := pub.Subjects()
emails := pub.EmailAddresses()

if len(emails) == len(tt.emails) {
if len(emails) > 0 {
Expand All @@ -308,5 +315,193 @@ func TestEmailAddresses(t *testing.T) {

})
}
}

func TestSubjects(t *testing.T) {
// dynamically generate a PKCS7 structure with multiple subjects set
url, _ := url.Parse("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.1.1")
rootCert, rootKey, _ := testutils.GenerateRootCa()
leafCert, leafKey, _ := testutils.GenerateLeafCert("subject@example.com", "oidc-issuer", url, rootCert, rootKey)

b := pkcs7.NewBuilder(leafKey, []*x509.Certificate{leafCert}, crypto.SHA256)
// set content to random data, only the certificate matters
err := b.SetContentData([]byte{1, 2, 3, 4})
if err != nil {
t.Fatalf("error setting content data in pkcs7: %v", err)
}
s, err := b.Sign()
if err != nil {
t.Fatalf("error signing pkcs7: %v", err)
}
pkcs7bytes, err := s.Marshal()
if err != nil {
t.Fatalf("error marshalling pkcs7: %v", err)
}

tests := []struct {
name string
pkcs7 string
subs []string
}{
{
name: "ec",
pkcs7: pkcsECDSAPEM,
subs: []string{},
},
{
name: "email in subject",
pkcs7: pkcsPEMEmail,
subs: []string{"test@rekor.dev"},
},
{
name: "email and URI in subject alternative name",
pkcs7: string(pkcs7bytes),
subs: []string{"subject@example.com", "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.1.1"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pub, err := NewPublicKey(strings.NewReader(tt.pkcs7))
if err != nil {
t.Fatal(err)
}
subs := pub.Subjects()

if len(subs) == len(tt.subs) {
if len(subs) > 0 {
sort.Strings(subs)
sort.Strings(tt.subs)
if !reflect.DeepEqual(subs, tt.subs) {
t.Errorf("%v: Error getting subjects from keys, got %v, expected %v", tt.name, subs, tt.subs)
}
}
} else {
t.Errorf("%v: Error getting subjects from keys, got %v, expected %v", tt.name, subs, tt.subs)
}

})
}
}

func TestIdentities(t *testing.T) {
// dynamically generate a PKCS7 structure with multiple subjects set
url, _ := url.Parse("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.1.1")
rootCert, rootKey, _ := testutils.GenerateRootCa()
leafCert, leafKey, _ := testutils.GenerateLeafCert("subject@example.com", "oidc-issuer", url, rootCert, rootKey)
leafPEM, _ := cryptoutils.MarshalPublicKeyToPEM(leafKey.Public())
leafCertPEM, _ := cryptoutils.MarshalCertificateToPEM(leafCert)

b := pkcs7.NewBuilder(leafKey, []*x509.Certificate{leafCert}, crypto.SHA256)
// set content to random data, only the certificate matters
err := b.SetContentData([]byte{1, 2, 3, 4})
if err != nil {
t.Fatalf("error setting content data in pkcs7: %v", err)
}
s, err := b.Sign()
if err != nil {
t.Fatalf("error signing pkcs7: %v", err)
}
pkcs7bytes, err := s.Marshal()
if err != nil {
t.Fatalf("error marshalling pkcs7: %v", err)
}

pkcsECDSAPEMKey := `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEtLIDsiLmT6k07xkRZjTKV6ZYOjA6Q1re
Qv4ZkTlnkZZ6Ev38D1tE0DdFuuxnmLQlxqy1pEvtKnl+n+MPl3Gpz9R1NWeW9LXp
qf7Zh+zB79C4uiVFQKtw4Tb7aIDn63N3
-----END PUBLIC KEY-----
`

pkcsKeyCert := `-----BEGIN CERTIFICATE-----
MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq
MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx
MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu
ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy
A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas
taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm
MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE
FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u
Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx
Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup
Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
-----END CERTIFICATE-----
`

pkcsPEMEmailKey := `-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQATdJNkml+YiugIcPJvsF20JpeBPBh
C9KiwJQNBrvLYZYbwHDi3T8zcT5drbDO2wc06w9h+IizOJsr6U+MdjYG48gApb4V
3yUzmJu6ExXCqtJDX/CxFdn4waX3a8PcoktQ1emFh8As3N01K2SAoRoTGJ32T1bv
fe8M8nrlQUlDHjuS5qc=
-----END PUBLIC KEY-----
`

pkcsEmailCert := `-----BEGIN CERTIFICATE-----
MIIC2TCCAjqgAwIBAgIUAL0Gw2SJvPW8PbXw+42XwmW8//owCgYIKoZIzj0EAwIw
fTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMQ8wDQYDVQQHDAZCb3N0b24xITAf
BgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFUmVrb3Ix
HTAbBgkqhkiG9w0BCQEWDnRlc3RAcmVrb3IuZGV2MCAXDTIxMDQxOTE0MTMyMFoY
DzQ0ODUwNTMxMTQxMzIwWjB9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTUExDzAN
BgNVBAcMBkJvc3RvbjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
MQ4wDAYDVQQDDAVSZWtvcjEdMBsGCSqGSIb3DQEJARYOdGVzdEByZWtvci5kZXYw
gZswEAYHKoZIzj0CAQYFK4EEACMDgYYABABN0k2SaX5iK6Ahw8m+wXbQml4E8GEL
0qLAlA0Gu8thlhvAcOLdPzNxPl2tsM7bBzTrD2H4iLM4myvpT4x2NgbjyAClvhXf
JTOYm7oTFcKq0kNf8LEV2fjBpfdrw9yiS1DV6YWHwCzc3TUrZIChGhMYnfZPVu99
7wzyeuVBSUMeO5Lmp6NTMFEwHQYDVR0OBBYEFJPLiMMFN5Cm6/rjOTPR2HWbbO5P
MB8GA1UdIwQYMBaAFJPLiMMFN5Cm6/rjOTPR2HWbbO5PMA8GA1UdEwEB/wQFMAMB
Af8wCgYIKoZIzj0EAwIDgYwAMIGIAkIBmRqxw8sStWknjeOgdyKkd+vFehNuVaiH
AKGsz+6KG3jPG5xN5+/Ws+OMTAp7Hv6HH5ChDO3LJ6t/sCun1otdWmICQgCUqg1k
e+RjnVqVlz1rUR7CTL2SlG9Xg1kAkYH4vMn/otEuAhnKf+GWLNB1l/dTFNEyysvI
A6ydFG8HXGWcnVVIVQ==
-----END CERTIFICATE-----
`

tests := []struct {
name string
pkcs7 string
identities []string
}{
{
name: "ec",
pkcs7: pkcsECDSAPEM,
identities: []string{pkcsKeyCert, pkcsECDSAPEMKey},
},
{
name: "email in subject",
pkcs7: pkcsPEMEmail,
identities: []string{pkcsEmailCert, pkcsPEMEmailKey, "test@rekor.dev"},
},
{
name: "email and URI in subject alternative name",
pkcs7: string(pkcs7bytes),
identities: []string{string(leafCertPEM), string(leafPEM), "subject@example.com", "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.1.1"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pub, err := NewPublicKey(strings.NewReader(tt.pkcs7))
if err != nil {
t.Fatal(err)
}
identities, err := pub.Identities()
if err != nil {
t.Fatalf("unexpected error getting identities: %v", err)
}

if len(identities) == len(tt.identities) {
if len(identities) > 0 {
sort.Strings(identities)
sort.Strings(tt.identities)
if !reflect.DeepEqual(identities, tt.identities) {
t.Errorf("%v: Error getting identities from keys, got %v, expected %v", tt.name, identities, tt.identities)
}
}
} else {
t.Errorf("%v: Error getting identities from keys, got %v, expected %v", tt.name, identities, tt.identities)
}

})
}
}
2 changes: 2 additions & 0 deletions pkg/pki/pki.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type PublicKey interface {
// also return Subject URIs present in public keys.
EmailAddresses() []string
Subjects() []string
// Identities returns PEM-encoded public keys and subjects from either certificate or PGP keys
Identities() ([]string, error)
}

// Signature Generic object representing a signature (regardless of format & algorithm)
Expand Down
Loading