github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/cm/credentialresponse.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package cm 8 9 import ( 10 "encoding/json" 11 "errors" 12 "fmt" 13 14 "github.com/PaesslerAG/jsonpath" 15 "github.com/google/uuid" 16 17 "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" 18 "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" 19 ) 20 21 const ( 22 // CredentialResponseAttachmentFormat defines the format type of Credential Response when used as an 23 // attachment in the WACI issuance flow. 24 // Refer to https://identity.foundation/waci-presentation-exchange/#issuance-2 for more info. 25 CredentialResponseAttachmentFormat = "dif/credential-manifest/response@v1.0" 26 // CredentialResponsePresentationContext defines the context type of Credential Response when used as part of 27 // a presentation attachment in the WACI issuance flow. 28 // Refer to https://identity.foundation/waci-presentation-exchange/#issuance-2 for more info. 29 CredentialResponsePresentationContext = "https://identity.foundation/credential-manifest/response/v1" 30 ) 31 32 // CredentialResponse represents a Credential Response object as defined in 33 // https://identity.foundation/credential-manifest/#credential-response. 34 type CredentialResponse struct { 35 ID string `json:"id,omitempty"` // mandatory property 36 ManifestID string `json:"manifest_id,omitempty"` // mandatory property 37 ApplicationID string `json:"application_id,omitempty"` 38 OutputDescriptorMappingObjects []OutputDescriptorMap `json:"descriptor_map,omitempty"` // mandatory property 39 } 40 41 // OutputDescriptorMap represents an Output Descriptor Mapping Object as defined in 42 // https://identity.foundation/credential-manifest/#credential-response. 43 // It has the same format as the InputDescriptorMapping object from the presexch package, but has a different meaning 44 // here. 45 type OutputDescriptorMap presexch.InputDescriptorMapping 46 47 // UnmarshalJSON is the custom unmarshal function gets called automatically when the standard json.Unmarshal is called. 48 // It also ensures that the given data is a valid CredentialResponse object per the specification. 49 func (cf *CredentialResponse) UnmarshalJSON(data []byte) error { 50 err := cf.standardUnmarshal(data) 51 if err != nil { 52 return err 53 } 54 55 err = cf.validate() 56 if err != nil { 57 return fmt.Errorf("invalid Credential Response: %w", err) 58 } 59 60 return nil 61 } 62 63 // ResolveDescriptorMaps resolves Verifiable Credentials based on this Credential Response's descriptor maps. 64 // This function looks at each OutputDescriptorMap's path property and checks for that path in the given JSON data, 65 // which is expected to be from 66 // the attachment of an Issue Credential message (i.e. issuecredential.IssueCredentialV3.Attachments[i].Data.JSON). 67 // See the TestCredentialResponse_ResolveDescriptorMap method for examples. 68 // If a VC is found at that path's location, then it is added to the array of VCs that will be returned by this method. 69 // Once all OutputDescriptorMaps are done being scanned, the array of VCs will be returned. 70 func (cf *CredentialResponse) ResolveDescriptorMaps(jsonDataFromAttachment interface{}, 71 parseCredentialOpts ...verifiable.CredentialOpt) ([]verifiable.Credential, error) { 72 // The jsonpath library needs a map[string]interface{}. 73 // The issuecredential.IssueCredentialV3.Attachments[i].Data.JSON object (expected to be passed in here) is of type 74 // interface{}, but the Go unmarshaler should have set it to a map[string]interface{}. 75 jsonDataFromAttachmentAsMap, ok := jsonDataFromAttachment.(map[string]interface{}) 76 if !ok { 77 return nil, errors.New("the given JSON data could not be asserted as a map[string]interface{}") 78 } 79 80 verifiableCredentials := make([]verifiable.Credential, len(cf.OutputDescriptorMappingObjects)) 81 82 for i, descriptorMap := range cf.OutputDescriptorMappingObjects { 83 vc, err := resolveDescriptorMap(descriptorMap, jsonDataFromAttachmentAsMap, parseCredentialOpts) 84 if err != nil { 85 return nil, fmt.Errorf("failed to resolve descriptor map at index %d: %w", i, err) 86 } 87 88 verifiableCredentials[i] = *vc 89 } 90 91 return verifiableCredentials, nil 92 } 93 94 func (cf *CredentialResponse) standardUnmarshal(data []byte) error { 95 // The type alias below is used as to allow the standard json.Unmarshal to be called within a custom unmarshal 96 // function without causing infinite recursion. See https://stackoverflow.com/a/43178272 for more information. 97 type credentialResponseWithoutMethods *CredentialResponse 98 99 err := json.Unmarshal(data, credentialResponseWithoutMethods(cf)) 100 if err != nil { 101 return err 102 } 103 104 return nil 105 } 106 107 func (cf *CredentialResponse) validate() error { 108 if cf.ID == "" { 109 return errors.New("missing ID") 110 } 111 112 if cf.ManifestID == "" { 113 return errors.New("missing manifest ID") 114 } 115 116 return nil 117 } 118 119 // presentCredentialResponseOpts holds options for the PresentCredentialResponse method. 120 type presentCredentialResponseOpts struct { 121 existingPresentation verifiable.Presentation 122 existingPresentationSet bool 123 } 124 125 // PresentCredentialResponseOpt is an option for the PresentCredentialResponse method. 126 type PresentCredentialResponseOpt func(opts *presentCredentialResponseOpts) 127 128 // WithExistingPresentationForPresentCredentialResponse is an option for the PresentCredentialResponse method 129 // that allows Credential Response data to be added to an existing Presentation. The existing Presentation 130 // should not already have Credential Response data. 131 func WithExistingPresentationForPresentCredentialResponse( 132 presentation *verifiable.Presentation) PresentCredentialResponseOpt { 133 return func(opts *presentCredentialResponseOpts) { 134 opts.existingPresentation = *presentation 135 opts.existingPresentationSet = true 136 } 137 } 138 139 // PresentCredentialResponse creates a basic Presentation (without proofs) with Credential Response data based 140 // on credentialManifest. The WithExistingPresentationForPresentCredentialResponse can be used to add the Credential 141 // Response data to an existing Presentation object instead. Note that any existing proofs are not updated. 142 // Note also the following assumptions/limitations of this method: 143 // 1. The format of all credentials is assumed to be ldp_vc. 144 // 2. The location of the Verifiable Credentials is assumed to be an array at the root under a field called 145 // "verifiableCredential". 146 // 3. The Verifiable Credentials in the presentation is assumed to be in the same order as the Output Descriptors in 147 // the Credential Manifest. 148 func PresentCredentialResponse(credentialManifest *CredentialManifest, 149 opts ...PresentCredentialResponseOpt) (*verifiable.Presentation, error) { 150 if credentialManifest == nil { 151 return nil, errors.New("credential manifest argument cannot be nil") 152 } 153 154 appliedOptions := getPresentCredentialResponseOpts(opts) 155 156 var presentation verifiable.Presentation 157 158 if appliedOptions.existingPresentationSet { 159 presentation = appliedOptions.existingPresentation 160 } else { 161 newPresentation, err := verifiable.NewPresentation() 162 if err != nil { 163 return nil, err 164 } 165 166 presentation = *newPresentation 167 } 168 169 presentation.Context = append(presentation.Context, 170 "https://identity.foundation/credential-manifest/response/v1") 171 presentation.Type = append(presentation.Type, "CredentialResponse") 172 173 outputDescriptorMappingObjects := make([]OutputDescriptorMap, len(credentialManifest.OutputDescriptors)) 174 175 for i := range credentialManifest.OutputDescriptors { 176 outputDescriptorMappingObjects[i].ID = credentialManifest.OutputDescriptors[i].ID 177 outputDescriptorMappingObjects[i].Format = "ldp_vc" 178 outputDescriptorMappingObjects[i].Path = fmt.Sprintf("$.verifiableCredential[%d]", i) 179 } 180 181 response := CredentialResponse{ 182 ID: uuid.New().String(), 183 ManifestID: credentialManifest.ID, 184 OutputDescriptorMappingObjects: outputDescriptorMappingObjects, 185 } 186 187 if presentation.CustomFields == nil { 188 presentation.CustomFields = make(map[string]interface{}) 189 } 190 191 presentation.CustomFields["credential_response"] = response 192 193 return &presentation, nil 194 } 195 196 func getPresentCredentialResponseOpts(opts []PresentCredentialResponseOpt) *presentCredentialResponseOpts { 197 processedOptions := &presentCredentialResponseOpts{} 198 199 for _, opt := range opts { 200 opt(processedOptions) 201 } 202 203 return processedOptions 204 } 205 206 func resolveDescriptorMap(descriptorMap OutputDescriptorMap, jsonDataFromAttachmentAsMap map[string]interface{}, 207 parseCredentialOpts []verifiable.CredentialOpt) (*verifiable.Credential, error) { 208 vcRaw, err := jsonpath.Get(descriptorMap.Path, jsonDataFromAttachmentAsMap) 209 if err != nil { 210 return nil, err 211 } 212 213 vcBytes, err := json.Marshal(vcRaw) 214 if err != nil { 215 return nil, err 216 } 217 218 vc, err := verifiable.ParseCredential(vcBytes, parseCredentialOpts...) 219 if err != nil { 220 return nil, fmt.Errorf("failed to parse credential: %w", err) 221 } 222 223 return vc, nil 224 }