From 5d11711ee406db2eba6bf0271db49fea29d0a4f0 Mon Sep 17 00:00:00 2001 From: asraa <asraa@google.com> Date: Tue, 20 Apr 2021 16:26:00 -0400 Subject: [PATCH] add email search index (#276) Signed-off-by: Asra Ali <asraa@google.com> --- cmd/rekor-cli/app/pflags.go | 34 +++++++++++++- cmd/rekor-cli/app/pflags_test.go | 18 ++++++- cmd/rekor-cli/app/search.go | 6 ++- go.mod | 1 + go.sum | 5 ++ openapi.yaml | 3 ++ pkg/api/error.go | 1 - pkg/api/index.go | 14 +++--- pkg/generated/models/search_index.go | 20 ++++++++ pkg/generated/restapi/embedded_spec.go | 8 ++++ pkg/pki/minisign/minisign.go | 5 ++ pkg/pki/pgp/pgp.go | 17 +++++++ pkg/pki/pgp/pgp_test.go | 50 ++++++++++++++++++++ pkg/pki/pkcs7/pkcs7.go | 24 ++++++++++ pkg/pki/pkcs7/pkcs7_test.go | 65 ++++++++++++++++++++++++++ pkg/pki/pki.go | 1 + pkg/pki/ssh/ssh.go | 5 ++ pkg/pki/x509/x509.go | 18 +++++++ pkg/types/rekord/v0.0.1/entry.go | 2 + pkg/types/rpm/v0.0.1/entry.go | 2 + tests/e2e_test.go | 5 ++ 21 files changed, 293 insertions(+), 11 deletions(-) diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index f701139..66ec0b0 100644 --- a/cmd/rekor-cli/app/pflags.go +++ b/cmd/rekor-cli/app/pflags.go @@ -33,6 +33,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/go-playground/validator" "github.com/sigstore/rekor/pkg/generated/models" jar_v001 "github.com/sigstore/rekor/pkg/types/jar/v0.0.1" rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1" @@ -47,6 +48,8 @@ func addSearchPFlags(cmd *cobra.Command) error { cmd.Flags().Var(&fileOrURLFlag{}, "artifact", "path or URL to artifact file") cmd.Flags().Var(&uuidFlag{}, "sha", "the SHA256 sum of the artifact") + + cmd.Flags().Var(&emailFlag{}, "email", "email associated with the public key's subject") return nil } @@ -55,9 +58,10 @@ func validateSearchPFlags() error { publicKey := viper.GetString("public-key") sha := viper.GetString("sha") + email := viper.GetString("email") - if artifactStr == "" && publicKey == "" && sha == "" { - return errors.New("either 'sha' or 'artifact' or 'public-key' must be specified") + if artifactStr == "" && publicKey == "" && sha == "" && email == "" { + return errors.New("either 'sha' or 'artifact' or 'public-key' or 'email' must be specified") } if publicKey != "" { if viper.GetString("pki-format") == "" { @@ -530,3 +534,29 @@ func addLogIndexFlag(cmd *cobra.Command, required bool) error { } return nil } + +type emailFlag struct { + Email string `validate:"email"` +} + +func (e *emailFlag) String() string { + return e.Email +} + +func (e *emailFlag) Set(v string) error { + if v == "" { + return errors.New("flag must be specified") + } + + e.Email = v + validate := validator.New() + if err := validate.Struct(e); err != nil { + return fmt.Errorf("error parsing --email: %s", err) + } + + return nil +} + +func (e *emailFlag) Type() string { + return "email" +} diff --git a/cmd/rekor-cli/app/pflags_test.go b/cmd/rekor-cli/app/pflags_test.go index 2155aa5..ca314fa 100644 --- a/cmd/rekor-cli/app/pflags_test.go +++ b/cmd/rekor-cli/app/pflags_test.go @@ -420,6 +420,7 @@ func TestSearchPFlags(t *testing.T) { artifact string publicKey string sha string + email string pkiFormat string expectParseSuccess bool expectValidateSuccess bool @@ -516,7 +517,19 @@ func TestSearchPFlags(t *testing.T) { expectValidateSuccess: false, }, { - caseDesc: "no flags when either artifact, sha, or public key are needed", + caseDesc: "valid email", + email: "cat@foo.com", + expectParseSuccess: true, + expectValidateSuccess: true, + }, + { + caseDesc: "invalid email", + email: "SignaMeseCat", + expectParseSuccess: false, + expectValidateSuccess: true, + }, + { + caseDesc: "no flags when either artifact, sha, public key, or email are needed", expectParseSuccess: true, expectValidateSuccess: false, }, @@ -542,6 +555,9 @@ func TestSearchPFlags(t *testing.T) { if tc.sha != "" { args = append(args, "--sha", tc.sha) } + if tc.email != "" { + args = append(args, "--email", tc.email) + } if err := blankCmd.ParseFlags(args); (err == nil) != tc.expectParseSuccess { t.Errorf("unexpected result parsing '%v': %v", tc.caseDesc, err) diff --git a/cmd/rekor-cli/app/search.go b/cmd/rekor-cli/app/search.go index 160d780..41837d5 100644 --- a/cmd/rekor-cli/app/search.go +++ b/cmd/rekor-cli/app/search.go @@ -56,7 +56,7 @@ func (s *searchCmdOutput) String() string { var searchCmd = &cobra.Command{ Use: "search", Short: "Rekor search command", - Long: `Searches the Rekor index to find entries by artifact or public key`, + Long: `Searches the Rekor index to find entries by sha, artifact, public key, or e-mail`, PreRun: func(cmd *cobra.Command, args []string) { // these are bound here so that they are not overwritten by other commands if err := viper.BindPFlags(cmd.Flags()); err != nil { @@ -150,6 +150,10 @@ var searchCmd = &cobra.Command{ } } + emailStr := viper.GetString("email") + if emailStr != "" { + params.Query.Email = strfmt.Email(emailStr) + } resp, err := rekorClient.Index.SearchIndex(params) if err != nil { switch t := err.(type) { diff --git a/go.mod b/go.mod index 8d8b74b..1a2b082 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/go-openapi/strfmt v0.20.1 github.com/go-openapi/swag v0.19.15 github.com/go-openapi/validate v0.20.2 + github.com/go-playground/validator v9.31.0+incompatible github.com/google/rpmpack v0.0.0-20210107155803-d6befbf05148 github.com/google/trillian v1.3.14-0.20210413093047-5e12fb368c8f github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect diff --git a/go.sum b/go.sum index efb2331..a103dc6 100644 --- a/go.sum +++ b/go.sum @@ -416,8 +416,12 @@ github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE github.com/go-openapi/validate v0.20.2 h1:AhqDegYV3J3iQkMPJSXkvzymHKMTw0BST3RK3hTT4ts= github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -735,6 +739,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible/go.mod h1:iGYXKqDXt0cpBthCHdr9ZdsQwyGlYFh/+8xa4WzIQ34= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= diff --git a/openapi.yaml b/openapi.yaml index 0f9e240..b23a438 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -308,6 +308,9 @@ definitions: SearchIndex: type: object properties: + email: + type: string + format: email publicKey: type: object properties: diff --git a/pkg/api/error.go b/pkg/api/error.go index 78d9767..f47c18d 100644 --- a/pkg/api/error.go +++ b/pkg/api/error.go @@ -38,7 +38,6 @@ const ( entryAlreadyExists = "An equivalent entry already exists in the transparency log with UUID %v" firstSizeLessThanLastSize = "firstSize(%d) must be less than lastSize(%d)" malformedUUID = "UUID must be a 64-character hexadecimal string" - malformedHash = "Hash must be a 64-character hexadecimal string created from SHA256 algorithm" malformedPublicKey = "Public key provided could not be parsed" failedToGenerateCanonicalKey = "Error generating canonicalized public key" redisUnexpectedResult = "Unexpected result from searching index" diff --git a/pkg/api/index.go b/pkg/api/index.go index 6ed73d3..cda6bf4 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -19,11 +19,9 @@ import ( "context" "crypto/sha256" "encoding/hex" - "errors" "net/http" "strings" - "github.com/asaskevich/govalidator" "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/swag" radix "github.com/mediocregopher/radix/v4" @@ -39,10 +37,7 @@ func SearchIndexHandler(params index.SearchIndexParams) middleware.Responder { var result []string if params.Query.Hash != "" { - // validate this is only a valid sha256 hash - if !govalidator.IsSHA256(params.Query.Hash) { - return handleRekorAPIError(params, http.StatusBadRequest, errors.New("invalid hash value specified"), malformedHash) - } + // This must be a valid sha256 hash var resultUUIDs []string if err := redisClient.Do(httpReqCtx, radix.Cmd(&resultUUIDs, "LRANGE", strings.ToLower(params.Query.Hash), "0", "-1")); err != nil { return handleRekorAPIError(params, http.StatusInternalServerError, err, redisUnexpectedResult) @@ -77,6 +72,13 @@ func SearchIndexHandler(params index.SearchIndexParams) middleware.Responder { } result = append(result, resultUUIDs...) } + if params.Query.Email != "" { + var resultUUIDs []string + if err := redisClient.Do(httpReqCtx, radix.Cmd(&resultUUIDs, "LRANGE", strings.ToLower(params.Query.Email.String()), "0", "-1")); err != nil { + return handleRekorAPIError(params, http.StatusInternalServerError, err, redisUnexpectedResult) + } + result = append(result, resultUUIDs...) + } return index.NewSearchIndexOK().WithPayload(result) } diff --git a/pkg/generated/models/search_index.go b/pkg/generated/models/search_index.go index c0427f6..9163f78 100644 --- a/pkg/generated/models/search_index.go +++ b/pkg/generated/models/search_index.go @@ -36,6 +36,10 @@ import ( // swagger:model SearchIndex type SearchIndex struct { + // email + // Format: email + Email strfmt.Email `json:"email,omitempty"` + // hash // Pattern: ^[0-9a-fA-F]{64}$ Hash string `json:"hash,omitempty"` @@ -48,6 +52,10 @@ type SearchIndex struct { func (m *SearchIndex) Validate(formats strfmt.Registry) error { var res []error + if err := m.validateEmail(formats); err != nil { + res = append(res, err) + } + if err := m.validateHash(formats); err != nil { res = append(res, err) } @@ -62,6 +70,18 @@ func (m *SearchIndex) Validate(formats strfmt.Registry) error { return nil } +func (m *SearchIndex) validateEmail(formats strfmt.Registry) error { + if swag.IsZero(m.Email) { // not required + return nil + } + + if err := validate.FormatOf("email", "body", "email", m.Email.String(), formats); err != nil { + return err + } + + return nil +} + func (m *SearchIndex) validateHash(formats strfmt.Registry) error { if swag.IsZero(m.Hash) { // not required return nil diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go index 71b9d36..9b33b8d 100644 --- a/pkg/generated/restapi/embedded_spec.go +++ b/pkg/generated/restapi/embedded_spec.go @@ -481,6 +481,10 @@ func init() { "SearchIndex": { "type": "object", "properties": { + "email": { + "type": "string", + "format": "email" + }, "hash": { "type": "string", "pattern": "^[0-9a-fA-F]{64}$" @@ -1559,6 +1563,10 @@ func init() { "SearchIndex": { "type": "object", "properties": { + "email": { + "type": "string", + "format": "email" + }, "hash": { "type": "string", "pattern": "^[0-9a-fA-F]{64}$" diff --git a/pkg/pki/minisign/minisign.go b/pkg/pki/minisign/minisign.go index 0f5c71a..22bc3b0 100644 --- a/pkg/pki/minisign/minisign.go +++ b/pkg/pki/minisign/minisign.go @@ -148,3 +148,8 @@ func (k PublicKey) CanonicalValue() ([]byte, error) { b64Key := base64.StdEncoding.EncodeToString(k.key.PublicKey[:]) return []byte(b64Key), nil } + +// EmailAddresses implements the pki.PublicKey interface +func (k PublicKey) EmailAddresses() []string { + return nil +} diff --git a/pkg/pki/pgp/pgp.go b/pkg/pki/pgp/pgp.go index e645f1e..7e3c7bb 100644 --- a/pkg/pki/pgp/pgp.go +++ b/pkg/pki/pgp/pgp.go @@ -24,6 +24,7 @@ import ( "io" "net/http" + "github.com/go-playground/validator" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/packet" @@ -277,3 +278,19 @@ func (k PublicKey) KeyRing() (openpgp.KeyRing, error) { return k.key, nil } + +// EmailAddresses implements the pki.PublicKey interface +func (k PublicKey) EmailAddresses() []string { + var names []string + // Extract from cert + for _, entity := range k.key { + for _, identity := range entity.Identities { + validate := validator.New() + errs := validate.Var(identity.UserId.Email, "required,email") + if errs == nil { + names = append(names, identity.UserId.Email) + } + } + } + return names +} diff --git a/pkg/pki/pgp/pgp_test.go b/pkg/pki/pgp/pgp_test.go index b534ac2..17c0b34 100644 --- a/pkg/pki/pgp/pgp_test.go +++ b/pkg/pki/pgp/pgp_test.go @@ -24,6 +24,8 @@ import ( "net/http" "net/http/httptest" "os" + "reflect" + "sort" "testing" "go.uber.org/goleak" @@ -341,6 +343,54 @@ func TestCanonicalValuePublicKey(t *testing.T) { } } +func TestEmailAddresses(t *testing.T) { + type test struct { + caseDesc string + inputFile string + emails []string + } + + var k PublicKey + if len(k.EmailAddresses()) != 0 { + t.Errorf("EmailAddresses 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"}}, + } + + for _, tc := range tests { + var input io.Reader + var err error + input, err = os.Open(tc.inputFile) + if err != nil { + t.Errorf("%v: cannot open %v", tc.caseDesc, tc.inputFile) + } + + inputKey, err := NewPublicKey(input) + if err != nil { + t.Errorf("%v: Error reading input for TestEmailAddresses: %v", tc.caseDesc, err) + } + + emails := inputKey.EmailAddresses() + + 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) + } + } + } else { + t.Errorf("%v: Error getting email addresses from keys length, got %v, expected %v", tc.caseDesc, len(emails), len(tc.emails)) + } + + } +} + func TestVerifySignature(t *testing.T) { type test struct { caseDesc string diff --git a/pkg/pki/pkcs7/pkcs7.go b/pkg/pki/pkcs7/pkcs7.go index 98f90a0..8e84262 100644 --- a/pkg/pki/pkcs7/pkcs7.go +++ b/pkg/pki/pkcs7/pkcs7.go @@ -20,15 +20,20 @@ import ( "bytes" "crypto" "crypto/x509" + "encoding/asn1" "encoding/pem" "errors" "fmt" "io" "io/ioutil" + "strings" "github.com/sassoftware/relic/lib/pkcs7" ) +// EmailAddressOID defined by https://oidref.com/1.2.840.113549.1.9.1 +var EmailAddressOID asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 9, 1} + type Signature struct { signedData pkcs7.SignedData detached bool @@ -184,3 +189,22 @@ func (k PublicKey) CanonicalValue() ([]byte, error) { } return buf.Bytes(), nil } + +// EmailAddresses implements the pki.PublicKey interface +func (k PublicKey) EmailAddresses() []string { + var names []string + // Get email address from Subject name in raw cert. + cert, err := x509.ParseCertificate(k.rawCert) + if err != nil { + // This should not happen from a valid PublicKey, but fail gracefully. + return names + } + + for _, name := range cert.Subject.Names { + if name.Type.Equal(EmailAddressOID) { + names = append(names, strings.ToLower(name.Value.(string))) + } + } + + return names +} diff --git a/pkg/pki/pkcs7/pkcs7_test.go b/pkg/pki/pkcs7/pkcs7_test.go index 21386d7..3b77256 100644 --- a/pkg/pki/pkcs7/pkcs7_test.go +++ b/pkg/pki/pkcs7/pkcs7_test.go @@ -19,6 +19,8 @@ package pkcs7 import ( "bytes" "encoding/base64" + "reflect" + "sort" "strings" "testing" ) @@ -172,6 +174,26 @@ Cg0KTmFtZTogc2lnc3RvcmUvcGx1Z2luL0hlbHBNb2pvLmNsYXNzDQpTSEEtMjU2 LURpZ2VzdDogU3ZPNkhibVlBSzBMVEhyVCtYbmRBOExJdUptZU5ub1dyYmVHS3dv TE9Pdz0NCg0K` +const pkcsPEMEmail = `-----BEGIN PKCS7----- +MIIDCgYJKoZIhvcNAQcCoIIC+zCCAvcCAQExADALBgkqhkiG9w0BBwGgggLdMIIC +2TCCAjqgAwIBAgIUAL0Gw2SJvPW8PbXw+42XwmW8//owCgYIKoZIzj0EAwIwfTEL +MAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMQ8wDQYDVQQHDAZCb3N0b24xITAfBgNV +BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFUmVrb3IxHTAb +BgkqhkiG9w0BCQEWDnRlc3RAcmVrb3IuZGV2MCAXDTIxMDQxOTE0MTMyMFoYDzQ0 +ODUwNTMxMTQxMzIwWjB9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTUExDzANBgNV +BAcMBkJvc3RvbjEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ4w +DAYDVQQDDAVSZWtvcjEdMBsGCSqGSIb3DQEJARYOdGVzdEByZWtvci5kZXYwgZsw +EAYHKoZIzj0CAQYFK4EEACMDgYYABABN0k2SaX5iK6Ahw8m+wXbQml4E8GEL0qLA +lA0Gu8thlhvAcOLdPzNxPl2tsM7bBzTrD2H4iLM4myvpT4x2NgbjyAClvhXfJTOY +m7oTFcKq0kNf8LEV2fjBpfdrw9yiS1DV6YWHwCzc3TUrZIChGhMYnfZPVu997wzy +euVBSUMeO5Lmp6NTMFEwHQYDVR0OBBYEFJPLiMMFN5Cm6/rjOTPR2HWbbO5PMB8G +A1UdIwQYMBaAFJPLiMMFN5Cm6/rjOTPR2HWbbO5PMA8GA1UdEwEB/wQFMAMBAf8w +CgYIKoZIzj0EAwIDgYwAMIGIAkIBmRqxw8sStWknjeOgdyKkd+vFehNuVaiHAKGs +z+6KG3jPG5xN5+/Ws+OMTAp7Hv6HH5ChDO3LJ6t/sCun1otdWmICQgCUqg1ke+Rj +nVqVlz1rUR7CTL2SlG9Xg1kAkYH4vMn/otEuAhnKf+GWLNB1l/dTFNEyysvIA6yd +FG8HXGWcnVVIVaEAMQA= +-----END PKCS7-----` + func TestSignature_Verify(t *testing.T) { tests := []struct { name string @@ -245,3 +267,46 @@ func TestSignature_VerifyFail(t *testing.T) { }) } } + +func TestEmailAddresses(t *testing.T) { + tests := []struct { + name string + pkcs7 string + emails []string + }{ + { + name: "ec", + pkcs7: pkcsECDSAPEM, + emails: []string{}, + }, + { + name: "email", + pkcs7: pkcsPEMEmail, + emails: []string{"test@rekor.dev"}, + }, + } + + 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) + } + emails := pub.EmailAddresses() + + if len(emails) == len(tt.emails) { + if len(emails) > 0 { + sort.Strings(emails) + sort.Strings(tt.emails) + if !reflect.DeepEqual(emails, tt.emails) { + t.Errorf("%v: Error getting email addresses from keys, got %v, expected %v", tt.name, emails, tt.emails) + } + } + } else { + t.Errorf("%v: Error getting email addresses from keys, got %v, expected %v", tt.name, emails, tt.emails) + } + + }) + } + +} diff --git a/pkg/pki/pki.go b/pkg/pki/pki.go index 43fb9d7..4463838 100644 --- a/pkg/pki/pki.go +++ b/pkg/pki/pki.go @@ -30,6 +30,7 @@ import ( // PublicKey Generic object representing a public key (regardless of format & algorithm) type PublicKey interface { CanonicalValue() ([]byte, error) + EmailAddresses() []string } // Signature Generic object representing a signature (regardless of format & algorithm) diff --git a/pkg/pki/ssh/ssh.go b/pkg/pki/ssh/ssh.go index 05a4fb1..8474431 100644 --- a/pkg/pki/ssh/ssh.go +++ b/pkg/pki/ssh/ssh.go @@ -96,3 +96,8 @@ func (k PublicKey) CanonicalValue() ([]byte, error) { } return ssh.MarshalAuthorizedKey(k.key), nil } + +// EmailAddresses implements the pki.PublicKey interface +func (k PublicKey) EmailAddresses() []string { + return nil +} diff --git a/pkg/pki/x509/x509.go b/pkg/pki/x509/x509.go index bf0db8c..f272d44 100644 --- a/pkg/pki/x509/x509.go +++ b/pkg/pki/x509/x509.go @@ -23,13 +23,18 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" + "encoding/asn1" "encoding/pem" "errors" "fmt" "io" "io/ioutil" + "strings" ) +// EmailAddressOID defined by https://oidref.com/1.2.840.113549.1.9.1 +var EmailAddressOID asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 9, 1} + type Signature struct { signature []byte } @@ -166,3 +171,16 @@ func (k PublicKey) CanonicalValue() ([]byte, error) { } return buf.Bytes(), nil } + +// EmailAddresses implements the pki.PublicKey interface +func (k PublicKey) EmailAddresses() []string { + var names []string + if k.cert != nil { + for _, name := range k.cert.c.Subject.Names { + if name.Type.Equal(EmailAddressOID) { + names = append(names, strings.ToLower(name.Value.(string))) + } + } + } + return names +} diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go index 59f70d6..3e5f55d 100644 --- a/pkg/types/rekord/v0.0.1/entry.go +++ b/pkg/types/rekord/v0.0.1/entry.go @@ -85,6 +85,8 @@ func (v V001Entry) IndexKeys() []string { } } + result = append(result, v.keyObj.EmailAddresses()...) + if v.RekordObj.Data.Hash != nil { result = append(result, strings.ToLower(swag.StringValue(v.RekordObj.Data.Hash.Value))) } diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go index a22f2d5..b8ebbcb 100644 --- a/pkg/types/rpm/v0.0.1/entry.go +++ b/pkg/types/rpm/v0.0.1/entry.go @@ -89,6 +89,8 @@ func (v V001Entry) IndexKeys() []string { } } + result = append(result, v.keyObj.EmailAddresses()...) + if v.RPMModel.Package.Hash != nil { result = append(result, strings.ToLower(swag.StringValue(v.RPMModel.Package.Hash.Value))) } diff --git a/tests/e2e_test.go b/tests/e2e_test.go index dbfe808..60b5a71 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -300,6 +300,11 @@ func TestX509(t *testing.T) { out = runCli(t, "upload", "--artifact", artifactPath, "--signature", sigPath, "--public-key", certPath, "--pki-format", "x509") outputContains(t, out, "Created entry at") + uuid := getUUIDFromUploadOutput(t, out) + + // Search via email + out = runCli(t, "search", "--email", "test@rekor.dev") + outputContains(t, out, uuid) } -- GitLab