github.com/hyperledger/aries-framework-go@v0.3.2/pkg/wallet/query.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package wallet 8 9 import ( 10 "encoding/json" 11 "errors" 12 "fmt" 13 "strings" 14 15 "github.com/piprate/json-gold/ld" 16 17 "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" 18 "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" 19 ) 20 21 // Query errors. 22 var ( 23 // ErrQueryNoResultFound error when no records found from query. 24 ErrQueryNoResultFound = errors.New("no result found") 25 ) 26 27 // QueryType is type of query supported by wallet implementation 28 // More details can be found here : https://w3c-ccg.github.io/universal-wallet-interop-spec/#query 29 type QueryType int 30 31 const ( 32 // QueryByExample https://w3c-ccg.github.io/vp-request-spec/#query-by-example 33 QueryByExample QueryType = iota + 1 34 // QueryByFrame https://github.com/w3c-ccg/vp-request-spec/issues/8 35 QueryByFrame 36 // PresentationExchange https://identity.foundation/presentation-exchange/ 37 PresentationExchange 38 // DIDAuth https://w3c-ccg.github.io/vp-request-spec/#did-authentication-request 39 DIDAuth 40 ) 41 42 // Name returns name of the query. 43 func (q QueryType) Name() string { 44 return []string{"", "QueryByExample", "QueryByFrame", "PresentationExchange"}[q] 45 } 46 47 // GetQueryType returns QueryType instance for given string query type. 48 func GetQueryType(name string) (QueryType, error) { 49 switch strings.ToLower(name) { 50 case "querybyexample": 51 return QueryByExample, nil 52 case "querybyframe": 53 return QueryByFrame, nil 54 case "presentationexchange": 55 return PresentationExchange, nil 56 case "didauth": 57 return DIDAuth, nil 58 default: 59 return 0, fmt.Errorf("unsupported query type, supported types - (%s, %s, %s)", 60 QueryByExample.Name(), QueryByFrame.Name(), PresentationExchange.Name()) 61 } 62 } 63 64 // Query performs wallet credential queries, currently supporting all the QueryTypes defined in QueryType. 65 type Query struct { 66 publicKeyFetcher verifiable.PublicKeyFetcher 67 documentLoader ld.DocumentLoader 68 params []*QueryParams 69 } 70 71 // NewQuery returns new wallet query instance. 72 func NewQuery(pkFetcher verifiable.PublicKeyFetcher, loader ld.DocumentLoader, queries ...*QueryParams) *Query { 73 return &Query{publicKeyFetcher: pkFetcher, documentLoader: loader, params: queries} 74 } 75 76 // PerformQuery performs credential query on given credentials. 77 // nolint:gocyclo 78 func (q *Query) PerformQuery(credentials map[string]json.RawMessage) ([]*verifiable.Presentation, error) { 79 if len(credentials) == 0 { 80 return nil, ErrQueryNoResultFound 81 } 82 83 vcs, err := q.parseCredentialContents(credentials) 84 if err != nil { 85 return nil, err 86 } 87 88 // using map to remove duplicates from results 89 credResults := make(map[*verifiable.Credential]struct{}, len(credentials)) 90 91 var results []*verifiable.Presentation 92 93 for _, param := range q.params { 94 qType, err := GetQueryType(param.Type) 95 if err != nil { 96 return nil, err 97 } 98 99 credentials, err := q.getCredentials(qType, vcs, param.Query...) 100 if err != nil { 101 return nil, err 102 } 103 104 for _, cred := range credentials { 105 credResults[cred] = struct{}{} 106 } 107 108 presentations, err := q.getPresentation(qType, vcs, param.Query...) 109 if err != nil { 110 return nil, err 111 } 112 113 results = append(results, presentations...) 114 } 115 116 if len(credResults) > 0 { 117 presentation, err := preparePresentation(credResults) 118 if err != nil { 119 return nil, err 120 } 121 122 results = append(results, presentation) 123 } 124 125 if len(results) == 0 { 126 return nil, ErrQueryNoResultFound 127 } 128 129 return results, nil 130 } 131 132 // getCredentials runs given query and returns query result as credentials. 133 func (q *Query) getCredentials(qType QueryType, vcs []*verifiable.Credential, query ...json.RawMessage) ([]*verifiable.Credential, error) { // nolint: lll 134 switch qType { 135 case QueryByExample: 136 return queryByExample(vcs, query...) 137 case QueryByFrame: 138 return queryByFrame(vcs, q.publicKeyFetcher, q.documentLoader, query...) 139 default: 140 return []*verifiable.Credential{}, nil 141 } 142 } 143 144 // getPresentation runs given query and returns query result as presentation. 145 func (q *Query) getPresentation(qType QueryType, vcs []*verifiable.Credential, query ...json.RawMessage) ([]*verifiable.Presentation, error) { // nolint: lll 146 switch qType { 147 case PresentationExchange: 148 return q.queryByPresentationExchange(vcs, query...) 149 case DIDAuth: 150 return didAuth() 151 default: 152 return []*verifiable.Presentation{}, nil 153 } 154 } 155 156 type credentialMatcher struct { 157 example *ExampleDefinition 158 frame *QueryByFrameDefinition 159 } 160 161 func (cm *credentialMatcher) MatchExample(credential *verifiable.Credential) bool { // nolint: funlen,gocognit,gocyclo 162 // Match context 163 if !contains(credential.Context, cm.example.Context) { 164 return false 165 } 166 167 // Match type 168 if !contains(credential.Types, cm.example.Type) { 169 return false 170 } 171 172 // Issuer match 173 issuerMatched := len(cm.example.TrustedIssuer) == 0 174 175 for _, ti := range cm.example.TrustedIssuer { 176 matched := strings.EqualFold(credential.Issuer.ID, ti.Issuer) 177 178 // if not matched & this trusted issuer required then return false 179 if !matched && ti.Required { 180 return false 181 } 182 183 issuerMatched = issuerMatched || matched 184 } 185 186 // if none matched then return false 187 if !issuerMatched { 188 return false 189 } 190 191 // Match Credential Schema ID 192 if schemaID, ok := cm.example.CredentialSchema["id"]; ok { 193 schemaIDMatched := false 194 195 for _, schema := range credential.Schemas { 196 if schemaID == schema.ID { 197 schemaIDMatched = true 198 } 199 } 200 201 if !schemaIDMatched { 202 return false 203 } 204 } 205 206 // Match Credential Schema Type 207 if schemaType, ok := cm.example.CredentialSchema["type"]; ok { 208 schemaTypeMatched := false 209 210 for _, schema := range credential.Schemas { 211 if strings.EqualFold(schemaType, schema.Type) { 212 schemaTypeMatched = true 213 } 214 } 215 216 if !schemaTypeMatched { 217 return false 218 } 219 } 220 221 // Match credential subject 222 if cm.example.CredentialSubject != nil { 223 credSubjID, err := verifiable.SubjectID(credential.Subject) 224 if err != nil { 225 return false 226 } 227 228 if querySubjectID, ok := cm.example.CredentialSubject["id"]; ok && credSubjID != querySubjectID { 229 return false 230 } 231 } 232 233 return true 234 } 235 236 func (cm *credentialMatcher) MatchFrame(credential *verifiable.Credential) bool { 237 // Issuer match 238 issuerMatched := len(cm.frame.TrustedIssuer) == 0 239 240 for _, ti := range cm.frame.TrustedIssuer { 241 matched := strings.EqualFold(credential.Issuer.ID, ti.Issuer) 242 243 // if not matched & this trusted issuer required then return false 244 if !matched && ti.Required { 245 return false 246 } 247 248 issuerMatched = issuerMatched || matched 249 } 250 251 // also check if VC has bbs signature 252 for _, proof := range credential.Proofs { 253 if proof["type"] == BbsBlsSignature2020 { 254 return true 255 } 256 } 257 258 return false 259 } 260 261 func preparePresentation(credentials map[*verifiable.Credential]struct{}) (*verifiable.Presentation, error) { 262 var opts []verifiable.CreatePresentationOpt 263 264 for cred := range credentials { 265 opts = append(opts, verifiable.WithCredentials(cred)) 266 } 267 268 return verifiable.NewPresentation(opts...) 269 } 270 271 // proof check is disabled while resolving credentials from raw bytes. A wallet implementation may or may not choose to 272 // show credentials as verified. If a wallet implementation chooses to show credentials as 'verified' it 273 // may to call 'wallet.Verify()' for each credential being presented. 274 // (More details can be found in issue #2677). 275 func (q *Query) parseCredentialContents(raws map[string]json.RawMessage) ([]*verifiable.Credential, error) { 276 var result []*verifiable.Credential 277 278 for _, raw := range raws { 279 vc, err := verifiable.ParseCredential(raw, verifiable.WithDisabledProofCheck(), 280 verifiable.WithJSONLDDocumentLoader(q.documentLoader)) 281 if err != nil { 282 return nil, err 283 } 284 285 result = append(result, vc) 286 } 287 288 return result, nil 289 } 290 291 func parseQueryByExample(defs ...json.RawMessage) ([]*QueryByExampleDefinition, error) { 292 definitions := make([]*QueryByExampleDefinition, len(defs)) 293 294 for i, def := range defs { 295 var query QueryByExampleDefinition 296 297 err := json.Unmarshal(def, &query) 298 if err != nil { 299 return nil, err 300 } 301 302 if query.Example == nil { 303 return nil, errors.New("invalid QueryByExample, 'example' is required") 304 } 305 306 if query.Example.Context == nil { 307 return nil, errors.New("invalid QueryByExample, 'example.context' is required") 308 } 309 310 if isEmpty(query.Example.Type) { 311 return nil, errors.New("invalid QueryByExample, 'example.type' is required") 312 } 313 314 definitions[i] = &query 315 } 316 317 return definitions, nil 318 } 319 320 func parseQueryByFrame(defs ...json.RawMessage) ([]*QueryByFrameDefinition, error) { 321 definitions := make([]*QueryByFrameDefinition, len(defs)) 322 323 for i, def := range defs { 324 var query QueryByFrameDefinition 325 326 err := json.Unmarshal(def, &query) 327 if err != nil { 328 return nil, err 329 } 330 331 if len(query.Frame) == 0 { 332 return nil, errors.New("invalid QueryByFrame, 'frame' is required") 333 } 334 335 definitions[i] = &query 336 } 337 338 return definitions, nil 339 } 340 341 func queryByExample(vcs []*verifiable.Credential, defs ...json.RawMessage) ([]*verifiable.Credential, error) { 342 definitions, err := parseQueryByExample(defs...) 343 if err != nil { 344 return nil, fmt.Errorf("failed to parse QueryByExample query: %w", err) 345 } 346 347 var result []*verifiable.Credential 348 349 for _, vc := range vcs { 350 for _, definition := range definitions { 351 matcher := &credentialMatcher{example: definition.Example} 352 353 if matcher.MatchExample(vc) { 354 result = append(result, vc) 355 } 356 } 357 } 358 359 return result, nil 360 } 361 362 func queryByFrame(vcs []*verifiable.Credential, publicKeyFetcher verifiable.PublicKeyFetcher, loader ld.DocumentLoader, 363 defs ...json.RawMessage) ([]*verifiable.Credential, error) { 364 definitions, err := parseQueryByFrame(defs...) 365 if err != nil { 366 return nil, fmt.Errorf("failed to parse QueryByFrame query: %w", err) 367 } 368 369 var result []*verifiable.Credential 370 371 for _, vc := range vcs { 372 for _, definition := range definitions { 373 matcher := &credentialMatcher{frame: definition} 374 375 // match trusted issuer 376 if !matcher.MatchFrame(vc) { 377 continue 378 } 379 380 // match frame 381 bbsVC, err := vc.GenerateBBSSelectiveDisclosure(definition.Frame, nil, 382 verifiable.WithPublicKeyFetcher(publicKeyFetcher), 383 verifiable.WithJSONLDDocumentLoader(loader)) 384 if err != nil { 385 continue 386 } 387 388 result = append(result, bbsVC) 389 } 390 } 391 392 return result, nil 393 } 394 395 // queryByPresentationExchange generates presentation submission result based on given query. 396 func (q *Query) queryByPresentationExchange(vcs []*verifiable.Credential, defs ...json.RawMessage) ([]*verifiable.Presentation, error) { // nolint:lll 397 var results []*verifiable.Presentation 398 399 for _, def := range defs { 400 var presDefinition presexch.PresentationDefinition 401 402 err := json.Unmarshal(def, &presDefinition) 403 if err != nil { 404 return nil, err 405 } 406 407 result, err := presDefinition.CreateVP(vcs, q.documentLoader, verifiable.WithDisabledProofCheck(), 408 verifiable.WithJSONLDDocumentLoader(q.documentLoader)) 409 410 if errors.Is(err, presexch.ErrNoCredentials) { 411 continue 412 } 413 414 if err != nil { 415 return nil, err 416 } 417 418 results = append(results, result) 419 } 420 421 return results, nil 422 } 423 424 // didAuth prepares presentation for DID authorization. 425 func didAuth() ([]*verifiable.Presentation, error) { 426 presentation, err := verifiable.NewPresentation() 427 if err != nil { 428 return nil, err 429 } 430 431 return []*verifiable.Presentation{presentation}, nil 432 } 433 434 func contains(slice []string, item interface{}) bool { 435 set := make(map[string]struct{}, len(slice)) 436 for _, s := range slice { 437 set[s] = struct{}{} 438 } 439 440 switch itemVal := item.(type) { 441 case string: 442 _, ok := set[itemVal] 443 444 return ok 445 case []interface{}: 446 for _, val := range itemVal { 447 _, ok := set[val.(string)] 448 if !ok { 449 return false 450 } 451 } 452 453 return true 454 case []string: 455 for _, val := range itemVal { 456 _, ok := set[val] 457 if !ok { 458 return false 459 } 460 } 461 462 return true 463 default: 464 return false 465 } 466 } 467 468 func isEmpty(item interface{}) bool { 469 switch itemVal := item.(type) { 470 case string: 471 return itemVal == "" 472 case []interface{}: 473 return len(itemVal) == 0 474 case []string: 475 return len(itemVal) == 0 476 default: 477 return itemVal == nil 478 } 479 }