github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/sdjwt/holder/holder.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 // Package holder enables the Holder: an entity that receives SD-JWTs from the Issuer and has control over them. 8 package holder 9 10 import ( 11 "fmt" 12 13 "github.com/go-jose/go-jose/v3/jwt" 14 15 "github.com/hyperledger/aries-framework-go/pkg/doc/jose" 16 afgjwt "github.com/hyperledger/aries-framework-go/pkg/doc/jwt" 17 "github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/common" 18 ) 19 20 // Claim defines claim. 21 type Claim struct { 22 Disclosure string 23 Name string 24 Value interface{} 25 } 26 27 // jwtParseOpts holds options for the SD-JWT parsing. 28 type parseOpts struct { 29 detachedPayload []byte 30 sigVerifier jose.SignatureVerifier 31 } 32 33 // ParseOpt is the SD-JWT Parser option. 34 type ParseOpt func(opts *parseOpts) 35 36 // WithJWTDetachedPayload option is for definition of JWT detached payload. 37 func WithJWTDetachedPayload(payload []byte) ParseOpt { 38 return func(opts *parseOpts) { 39 opts.detachedPayload = payload 40 } 41 } 42 43 // WithSignatureVerifier option is for definition of JWT detached payload. 44 func WithSignatureVerifier(signatureVerifier jose.SignatureVerifier) ParseOpt { 45 return func(opts *parseOpts) { 46 opts.sigVerifier = signatureVerifier 47 } 48 } 49 50 // Parse parses issuer SD-JWT and returns claims that can be selected. 51 // The Holder MUST perform the following (or equivalent) steps when receiving a Combined Format for Issuance: 52 // 53 // - Separate the SD-JWT and the Disclosures in the Combined Format for Issuance. 54 // 55 // - Hash all the Disclosures separately. 56 // 57 // - Find the places in the SD-JWT where the digests of the Disclosures are included. 58 // 59 // - If any of the digests cannot be found in the SD-JWT, the Holder MUST reject the SD-JWT. 60 // 61 // - Decode Disclosures and obtain plaintext of the claim values. 62 // 63 // It is up to the Holder how to maintain the mapping between the Disclosures and the plaintext claim values to 64 // be able to display them to the End-User when needed. 65 func Parse(combinedFormatForIssuance string, opts ...ParseOpt) ([]*Claim, error) { 66 pOpts := &parseOpts{ 67 sigVerifier: &NoopSignatureVerifier{}, 68 } 69 70 for _, opt := range opts { 71 opt(pOpts) 72 } 73 74 var jwtOpts []afgjwt.ParseOpt 75 jwtOpts = append(jwtOpts, 76 afgjwt.WithSignatureVerifier(pOpts.sigVerifier), 77 afgjwt.WithJWTDetachedPayload(pOpts.detachedPayload)) 78 79 cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance) 80 81 signedJWT, _, err := afgjwt.Parse(cfi.SDJWT, jwtOpts...) 82 if err != nil { 83 return nil, err 84 } 85 86 err = common.VerifyDisclosuresInSDJWT(cfi.Disclosures, signedJWT) 87 if err != nil { 88 return nil, err 89 } 90 91 return getClaims(cfi.Disclosures) 92 } 93 94 func getClaims(disclosures []string) ([]*Claim, error) { 95 disclosureClaims, err := common.GetDisclosureClaims(disclosures) 96 if err != nil { 97 return nil, fmt.Errorf("failed to get claims from disclosures: %w", err) 98 } 99 100 var claims []*Claim 101 for _, disclosure := range disclosureClaims { 102 claims = append(claims, 103 &Claim{ 104 Disclosure: disclosure.Disclosure, 105 Name: disclosure.Name, 106 Value: disclosure.Value, 107 }) 108 } 109 110 return claims, nil 111 } 112 113 // BindingPayload represents holder binding payload. 114 type BindingPayload struct { 115 Nonce string `json:"nonce,omitempty"` 116 Audience string `json:"aud,omitempty"` 117 IssuedAt *jwt.NumericDate `json:"iat,omitempty"` 118 } 119 120 // BindingInfo defines holder binding payload and signer. 121 type BindingInfo struct { 122 Payload BindingPayload 123 Signer jose.Signer 124 } 125 126 // options holds options for holder. 127 type options struct { 128 holderBindingInfo *BindingInfo 129 } 130 131 // Option is a holder option. 132 type Option func(opts *options) 133 134 // WithHolderBinding option to set optional holder binding. 135 func WithHolderBinding(info *BindingInfo) Option { 136 return func(opts *options) { 137 opts.holderBindingInfo = info 138 } 139 } 140 141 // CreatePresentation is a convenience method to assemble combined format for presentation 142 // using selected disclosures (claimsToDisclose) and optional holder binding. 143 // This call assumes that combinedFormatForIssuance has already been parsed and verified using Parse() function. 144 // 145 // For presentation to a Verifier, the Holder MUST perform the following (or equivalent) steps: 146 // - Decide which Disclosures to release to the Verifier, obtaining proper End-User consent if necessary. 147 // - If Holder Binding is required, create a Holder Binding JWT. 148 // - Create the Combined Format for Presentation from selected Disclosures and Holder Binding JWT(if applicable). 149 // - Send the Presentation to the Verifier. 150 func CreatePresentation(combinedFormatForIssuance string, claimsToDisclose []string, opts ...Option) (string, error) { 151 hOpts := &options{} 152 153 for _, opt := range opts { 154 opt(hOpts) 155 } 156 157 cfi := common.ParseCombinedFormatForIssuance(combinedFormatForIssuance) 158 159 if len(cfi.Disclosures) == 0 { 160 return "", fmt.Errorf("no disclosures found in SD-JWT") 161 } 162 163 disclosuresMap := common.SliceToMap(cfi.Disclosures) 164 165 for _, ctd := range claimsToDisclose { 166 if _, ok := disclosuresMap[ctd]; !ok { 167 return "", fmt.Errorf("disclosure '%s' not found in SD-JWT", ctd) 168 } 169 } 170 171 var err error 172 173 var hbJWT string 174 175 if hOpts.holderBindingInfo != nil { 176 hbJWT, err = CreateHolderBinding(hOpts.holderBindingInfo) 177 if err != nil { 178 return "", fmt.Errorf("failed to create holder binding: %w", err) 179 } 180 } 181 182 cf := common.CombinedFormatForPresentation{ 183 SDJWT: cfi.SDJWT, 184 Disclosures: claimsToDisclose, 185 HolderBinding: hbJWT, 186 } 187 188 return cf.Serialize(), nil 189 } 190 191 // CreateHolderBinding will create holder binding from binding info. 192 func CreateHolderBinding(info *BindingInfo) (string, error) { 193 hbJWT, err := afgjwt.NewSigned(info.Payload, nil, info.Signer) 194 if err != nil { 195 return "", err 196 } 197 198 return hbJWT.Serialize(false) 199 } 200 201 // NoopSignatureVerifier is no-op signature verifier (signature will not get checked). 202 type NoopSignatureVerifier struct { 203 } 204 205 // Verify implements signature verification. 206 func (sv *NoopSignatureVerifier) Verify(joseHeaders jose.Headers, payload, signingInput, signature []byte) error { 207 return nil 208 }