Skip to content

Commit

Permalink
feat: trustlist v2 (with type filter) (#1430)
Browse files Browse the repository at this point in the history
* feat: trustlist v2 (with type filter)

Signed-off-by: Stas D <[email protected]>

* fix: bdd

---------

Signed-off-by: Stas D <[email protected]>
  • Loading branch information
skynet2 authored Sep 18, 2023
1 parent b028fbe commit c99658f
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 34 deletions.
14 changes: 8 additions & 6 deletions component/profile/reader/file/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,23 +208,25 @@ func NewVerifierReader(config *Config) (*VerifierReader, error) {
return &r, nil
}

func (p *VerifierReader) setTrustList(verifier *profileapi.Verifier) {
func (p *VerifierReader) setTrustList(
verifier *profileapi.Verifier,
) {
if verifier == nil || verifier.Checks == nil || len(createdIssuers) == 0 ||
len(verifier.Checks.Credential.IssuerTrustList) == 0 {
return
}

var updatedList []string
for _, item := range verifier.Checks.Credential.IssuerTrustList {
issuer, ok := createdIssuers[item]
updated := make(map[string]profileapi.TrustList)
for k, v := range verifier.Checks.Credential.IssuerTrustList {
issuer, ok := createdIssuers[k]
if !ok {
continue
}

updatedList = append(updatedList, issuer.SigningDID.DID)
updated[issuer.SigningDID.DID] = v
}

verifier.Checks.Credential.IssuerTrustList = updatedList
verifier.Checks.Credential.IssuerTrustList = updated
}

// GetProfile returns profile with given id.
Expand Down
7 changes: 6 additions & 1 deletion pkg/profile/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,12 @@ type CredentialChecks struct {
CredentialExpiry bool `json:"credentialExpiry,omitempty"`
Strict bool `json:"strict,omitempty"`
LinkedDomain bool `json:"linkedDomain,omitempty"`
IssuerTrustList []string `json:"issuerTrustList,omitempty"`
IssuerTrustList map[string]TrustList `json:"issuerTrustList,omitempty"`
}

// TrustList contains list of configuration that verifier is trusted to accept.
type TrustList struct {
CredentialTypes []string `json:"credentialTypes,omitempty"`
}

// SigningDID contains information about profile signing did.
Expand Down
31 changes: 29 additions & 2 deletions pkg/service/verifypresentation/verifypresentation_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,22 +253,49 @@ func (s *Service) checkCredentialExpiry(ctx context.Context, lazy []*LazyCredent
return nil
}

func (s *Service) checkIssuerTrustList(_ context.Context, lazy []*LazyCredential, trustList []string) error {
func (s *Service) checkIssuerTrustList(
_ context.Context,
lazy []*LazyCredential,
trustList map[string]profileapi.TrustList,
) error {
for _, input := range lazy {
var issuerID string
var credTypes []string

switch cred := input.Raw().(type) {
case *verifiable.Credential:
issuerID = cred.Issuer.ID
credTypes = cred.Types
case map[string]interface{}:
issuerID = fmt.Sprint(cred["issuer"])
cr, ok := cred["type"].([]interface{})
if ok {
for _, t := range cr {
credTypes = append(credTypes, fmt.Sprint(t))
}
}
default:
return fmt.Errorf("can not validate issuer trust list. unexpected type %v",
reflect.TypeOf(input.Raw()).String())
}

if !lo.Contains(trustList, issuerID) {
var finalCredType string
if len(credTypes) > 0 {
finalCredType = credTypes[len(credTypes)-1]
}

cfg, ok := trustList[issuerID]
if !ok {
return fmt.Errorf("issuer with id: %v is not a member of trustlist", issuerID)
}

if len(cfg.CredentialTypes) == 0 { // we are trusting to all credential types
continue
}

if !lo.Contains(cfg.CredentialTypes, finalCredType) {
return fmt.Errorf("credential type: %v is not a member of trustlist configuration", finalCredType)
}
}

return nil
Expand Down
165 changes: 152 additions & 13 deletions pkg/service/verifypresentation/verifypresentation_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ func TestService_VerifyPresentation(t *testing.T) {
Format: nil,
CredentialExpiry: true,
Strict: true,
IssuerTrustList: []string{"https://example.edu/issuers/14"},
IssuerTrustList: map[string]profileapi.TrustList{
"https://example.edu/issuers/14": {},
},
},
},
},
Expand All @@ -144,6 +146,133 @@ func TestService_VerifyPresentation(t *testing.T) {
want: nil,
wantErr: false,
},
{
name: "OK with credential type",
fields: fields{
getVDR: func() vdrapi.Registry {
return signedVPResult.VDR
},
getVcVerifier: func() vcVerifier {
mockVerifier := NewMockVcVerifier(gomock.NewController(t))
mockVerifier.EXPECT().ValidateCredentialProof(
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any()).Times(1).Return(nil)
mockVerifier.EXPECT().ValidateVCStatus(
context.Background(),
gomock.Any(),
gomock.Any()).Times(1).Return(nil)
mockVerifier.EXPECT().ValidateLinkedDomain(
context.Background(),
gomock.Any()).Times(1).Return(nil)
return mockVerifier
},
},
args: args{
getPresentation: func() *verifiable.Presentation {
return signedVPResult.Presentation
},
profile: &profileapi.Verifier{
SigningDID: &profileapi.SigningDID{DID: "did:key:abc"},
Checks: &profileapi.VerificationChecks{
Presentation: &profileapi.PresentationChecks{
Proof: true,
Format: nil,
},
Credential: profileapi.CredentialChecks{
Proof: true,
Status: true,
LinkedDomain: true,
Format: nil,
CredentialExpiry: true,
Strict: true,
IssuerTrustList: map[string]profileapi.TrustList{
"https://example.edu/issuers/14": {
CredentialTypes: []string{
"UniversityDegreeCredential",
},
},
},
},
},
},
opts: &Options{
Domain: crypto.Domain,
Challenge: crypto.Challenge,
},
},
want: nil,
wantErr: false,
},
{
name: "Err credential type not in trust list",
fields: fields{
getVDR: func() vdrapi.Registry {
return signedVPResult.VDR
},
getVcVerifier: func() vcVerifier {
mockVerifier := NewMockVcVerifier(gomock.NewController(t))
mockVerifier.EXPECT().ValidateCredentialProof(
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any(),
gomock.Any()).Times(1).Return(nil)
mockVerifier.EXPECT().ValidateVCStatus(
context.Background(),
gomock.Any(),
gomock.Any()).Times(1).Return(nil)
mockVerifier.EXPECT().ValidateLinkedDomain(
context.Background(),
gomock.Any()).Times(1).Return(nil)
return mockVerifier
},
},
args: args{
getPresentation: func() *verifiable.Presentation {
return signedVPResult.Presentation
},
profile: &profileapi.Verifier{
SigningDID: &profileapi.SigningDID{DID: "did:key:abc"},
Checks: &profileapi.VerificationChecks{
Presentation: &profileapi.PresentationChecks{
Proof: true,
Format: nil,
},
Credential: profileapi.CredentialChecks{
Proof: true,
Status: true,
LinkedDomain: true,
Format: nil,
CredentialExpiry: true,
Strict: true,
IssuerTrustList: map[string]profileapi.TrustList{
"https://example.edu/issuers/14": {
CredentialTypes: []string{
"DrivingLicense",
},
},
},
},
},
},
opts: &Options{
Domain: crypto.Domain,
Challenge: crypto.Challenge,
},
},
want: []PresentationVerificationCheckResult{
{
Check: "issuerTrustList",
Error: "credential type: UniversityDegreeCredential is not a member of trustlist configuration",
},
},
wantErr: false,
},
{
name: "OK no checks",
fields: fields{
Expand Down Expand Up @@ -176,7 +305,6 @@ func TestService_VerifyPresentation(t *testing.T) {
want: nil,
wantErr: false,
},

{
name: "Error credentials",
fields: fields{
Expand Down Expand Up @@ -214,11 +342,13 @@ func TestService_VerifyPresentation(t *testing.T) {
Format: nil,
},
Credential: profileapi.CredentialChecks{
Proof: true,
Status: true,
LinkedDomain: true,
Format: nil,
IssuerTrustList: []string{"random"},
Proof: true,
Status: true,
LinkedDomain: true,
Format: nil,
IssuerTrustList: map[string]profileapi.TrustList{
"random": {},
},
},
},
},
Expand Down Expand Up @@ -823,15 +953,22 @@ func TestCredentialStrict(t *testing.T) {
func TestCheckTrustList(t *testing.T) {
s := New(&Config{})

t.Run("from credentials", func(t *testing.T) {
cred := &verifiable.Credential{Issuer: verifiable.Issuer{
ID: "123432123",
}}
t.Run("from credentials v1 trust list", func(t *testing.T) {
cred := &verifiable.Credential{
Types: []string{
"VerifiableCredential",
"UniversityDegreeCredential",
},
Issuer: verifiable.Issuer{
ID: "123432123",
}}

err := s.checkIssuerTrustList(
context.TODO(),
[]*LazyCredential{NewLazyCredential(cred)},
[]string{"a"},
map[string]profileapi.TrustList{
"a": {},
},
)

assert.ErrorContains(t, err, "issuer with id: 123432123 is not a member of trustlist")
Expand All @@ -843,7 +980,9 @@ func TestCheckTrustList(t *testing.T) {
err := s.checkIssuerTrustList(
context.TODO(),
[]*LazyCredential{NewLazyCredential(cred)},
[]string{"a"},
map[string]profileapi.TrustList{
"a": {},
},
)

assert.ErrorContains(t, err,
Expand Down
25 changes: 16 additions & 9 deletions test/bdd/features/oidc4vc_api.feature
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,36 @@ Feature: OIDC4VC REST API
# LDP issuer, LDP verifier, no limit disclosure and schema match in PD query.
| i_myprofile_cmtr_p256_ldp/v1.0 | CrudeProductCredential | crudeProductCredentialTemplateID | v_myprofile_ldp/v1.0 | lp403pb9-schema-match | schema_id |

Scenario: OIDC credential issuance and verification Pre Auth flow with trustlist (Success)
Scenario Outline: OIDC credential issuance and verification Pre Auth flow with trustlist (Success)
Given Organization "test_org" has been authorized with client id "f13d1va9lp403pb9lyj89vk55" and secret "ejqxi9jb1vew2jbdnogpjcgrz"
And Issuer with id "bank_issuer_sdjwt_v5/v1.0" is authorized as a Profile user
And User holds credential "CrudeProductCredential" with templateID "crudeProductCredentialTemplateID"
And Issuer with id "<issuerProfile>" is authorized as a Profile user
And User holds credential "<credentialType>" with templateID "<credentialTemplate>"

When User interacts with Wallet to initiate credential issuance using pre authorization code flow
Then credential is issued
Then User interacts with Verifier and initiate OIDC4VP interaction under "v_myprofile_jwt_whitelist/v1.0" profile for organization "test_org" with presentation definition ID "3c8b1d9a-limit-disclosure-optional-fields" and fields "unit_of_measure_barrel,api_gravity,category,supplier_address"
Then User interacts with Verifier and initiate OIDC4VP interaction under "<verifierProfile>" profile for organization "test_org" with presentation definition ID "<presentationDefinitionID>" and fields "<fields>"
And Verifier from organization "test_org" retrieves interactions claims
Then we wait 2 seconds
And Verifier form organization "test_org" requests deleted interactions claims
Examples:
| issuerProfile | credentialType | credentialTemplate | verifierProfile | presentationDefinitionID | fields |
| bank_issuer_sdjwt_v5/v1.0 | CrudeProductCredential | crudeProductCredentialTemplateID | v_myprofile_jwt_whitelist/v1.0 | 3c8b1d9a-limit-disclosure-optional-fields | unit_of_measure_barrel,api_gravity,category,supplier_address |
| bank_issuer/v1.0 | UniversityDegreeCredential | universityDegreeTemplateID | v_myprofile_jwt_whitelist/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id |

# Error cases

Scenario: OIDC credential issuance and verification Pre Auth flow with trustlist (Fail)
Scenario Outline: OIDC credential issuance and verification Pre Auth flow with trustlist (Fail)
Given Organization "test_org" has been authorized with client id "f13d1va9lp403pb9lyj89vk55" and secret "ejqxi9jb1vew2jbdnogpjcgrz"
And Issuer with id "bank_issuer/v1.0" is authorized as a Profile user
And User holds credential "CrudeProductCredential" with templateID "crudeProductCredentialTemplateID"
And Issuer with id "<issuerProfile>" is authorized as a Profile user
And User holds credential "<credentialType>" with templateID "<credentialTemplate>"

When User interacts with Wallet to initiate credential issuance using pre authorization code flow
Then credential is issued
Then User interacts with Verifier and initiate OIDC4VP interaction under "v_myprofile_jwt_whitelist/v1.0" profile for organization "test_org" with presentation definition ID "3c8b1d9a-limit-disclosure-optional-fields" and fields "unit_of_measure_barrel,api_gravity,category,supplier_address" and receives "is not a member of trustlist" error

Then User interacts with Verifier and initiate OIDC4VP interaction under "<verifierProfile>" profile for organization "test_org" with presentation definition ID "<presentationDefinitionID>" and fields "<fields>" and receives "is not a member of trustlist" error
Examples:
| issuerProfile | credentialType | credentialTemplate | verifierProfile | presentationDefinitionID | fields |
| bank_issuer_sdjwt_v5/v1.0 | UniversityDegreeCredential | universityDegreeTemplateID | v_myprofile_jwt_whitelist/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id |
| i_myprofile_ud_es256k_jwt/v1.0 | PermanentResidentCard | permanentResidentCardTemplateID | v_myprofile_jwt_whitelist/v1.0 | 32f54163-no-limit-disclosure-optional-fields | lpr_category_id,registration_city,commuter_classification |

Scenario: OIDC credential issuance and verification Pre Auth flow (Invalid Claims)
Given Organization "test_org" has been authorized with client id "f13d1va9lp403pb9lyj89vk55" and secret "ejqxi9jb1vew2jbdnogpjcgrz"
Expand Down
11 changes: 8 additions & 3 deletions test/bdd/fixtures/profile/profiles.json
Original file line number Diff line number Diff line change
Expand Up @@ -1930,9 +1930,14 @@
"format": [
"jwt"
],
"issuerTrustList" : [
"bank_issuer_sdjwt_v5"
],
"issuerTrustList": {
"bank_issuer" : {},
"bank_issuer_sdjwt_v5": {
"credentialTypes": [
"CrudeProductCredential"
]
}
},
"proof": true,
"status": true,
"strict": true
Expand Down

0 comments on commit c99658f

Please sign in to comment.