diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go index f70113931613d12179577d8c0e5d5380ad5f6a7f..66ec0b0c23323b0e4a97d9ca4d831389bd3584b9 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 2155aa51995d7871438b480455d12885c20da0b7..ca314fa23e844496c0c9b385b35202f8d7cdd70c 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 160d780abd3de3f635d5d43ab897c6dceb99b93b..41837d5409749f6431234f34ac5943b0159fef9f 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 8d8b74b85db6312708334ea5c8c4831c0393e585..1a2b082e5bc817222822f3a7f37cbe6ee10d7efd 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 efb2331e82a7560a4472e95ebb03d5f0c49efc2a..a103dc6918abdc859dc947c67d4fdfe569d72c48 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 0f9e240214c869626eb3bb39db7245eb6f778ab9..b23a4389bea3435c3b50c76b5c704539ad0b0e58 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 78d9767f077ba10373bc6964d1abae595c1a810c..f47c18ded3af7904bd5dbec1a73e2c8a2452cfdc 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 6ed73d3981f85a9b8f64406b919552ce1c987f82..cda6bf433675f311dc2093616ba11e43de3659de 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 c0427f6465814dc6d1e07a3198dae96be4f7cc04..9163f78f9926b75752ff021d26da096ef8fbd211 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 71b9d369d2536db3fe9e55e5dddfbe146e8c0062..9b33b8d1fb5ff0e6cd0eea43f731377f3863f064 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 0f5c71a9af5bff0aacc374f2d5fc3f0223712e0f..22bc3b056200786bc542c57184c063eab6c617d1 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 e645f1ed77fe15e6a2eded993ee2173ce84f6c38..7e3c7bba01cb01c9e3b98abc058271fa139c3313 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 b534ac2bedaac118dcfcaf8a7a562690d239d284..17c0b340684fea39905c4e35d325da023a32152a 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 98f90a084b7d6496ec5494d4a2ac35b23525cfae..8e842621bb051d15995c3fae3bd2a74b5461365a 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 21386d727ec4633d5a2f5897358f4531fd41ed8c..3b77256b62c97d777711c387abb58e1b6641d779 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 43fb9d7b1086c8ddf58dcc00b8302d38b0953e39..446383875991d55e32a75c0deffec9cca8efdcb3 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 05a4fb14763580f3dd91842ac3448533eebdea53..847443185232b68edc192c4486dd571e8684b086 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 bf0db8ca8dd7de3af6095aa609af7214cb043ede..f272d4437caf1baa3ebdbcf1bc0c852efeb20bb3 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 59f70d6beebc56caefc27c6b47f559f73265a02b..3e5f55d2dd27cc0f179f8135ae4ac122698aa427 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 a22f2d5c9290c2606aa15758071d3ee56f61dcb2..b8ebbcb7bc0c5312ae0ab306202dc935a4c3ac06 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 dbfe808ef4655042b8e3acbf77c953f85dcfb3f3..60b5a71fb983184e572994e403ea0f38f39e7657 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) }