github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/sdjwt/verifier/verifier.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 /* 8 Package verifier enables the Verifier: An entity that requests, checks and 9 extracts the claims from an SD-JWT and respective Disclosures. 10 */ 11 package verifier 12 13 import ( 14 "encoding/json" 15 "fmt" 16 "time" 17 18 "github.com/go-jose/go-jose/v3/jwt" 19 "github.com/mitchellh/mapstructure" 20 21 "github.com/hyperledger/aries-framework-go/pkg/common/utils" 22 "github.com/hyperledger/aries-framework-go/pkg/doc/jose" 23 "github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk" 24 afgjwt "github.com/hyperledger/aries-framework-go/pkg/doc/jwt" 25 "github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/common" 26 "github.com/hyperledger/aries-framework-go/pkg/doc/signature/verifier" 27 ) 28 29 // jwtParseOpts holds options for the SD-JWT parsing. 30 type parseOpts struct { 31 detachedPayload []byte 32 sigVerifier jose.SignatureVerifier 33 34 issuerSigningAlgorithms []string 35 holderSigningAlgorithms []string 36 37 holderBindingRequired bool 38 expectedAudienceForHolderBinding string 39 expectedNonceForHolderBinding string 40 41 leewayForClaimsValidation time.Duration 42 } 43 44 // ParseOpt is the SD-JWT Parser option. 45 type ParseOpt func(opts *parseOpts) 46 47 // WithJWTDetachedPayload option is for definition of JWT detached payload. 48 func WithJWTDetachedPayload(payload []byte) ParseOpt { 49 return func(opts *parseOpts) { 50 opts.detachedPayload = payload 51 } 52 } 53 54 // WithSignatureVerifier option is for definition of signature verifier. 55 func WithSignatureVerifier(signatureVerifier jose.SignatureVerifier) ParseOpt { 56 return func(opts *parseOpts) { 57 opts.sigVerifier = signatureVerifier 58 } 59 } 60 61 // WithIssuerSigningAlgorithms option is for defining secure signing algorithms (for issuer). 62 func WithIssuerSigningAlgorithms(algorithms []string) ParseOpt { 63 return func(opts *parseOpts) { 64 opts.issuerSigningAlgorithms = algorithms 65 } 66 } 67 68 // WithHolderSigningAlgorithms option is for defining secure signing algorithms (for holder). 69 func WithHolderSigningAlgorithms(algorithms []string) ParseOpt { 70 return func(opts *parseOpts) { 71 opts.holderSigningAlgorithms = algorithms 72 } 73 } 74 75 // WithHolderBindingRequired option is for enforcing holder binding. 76 func WithHolderBindingRequired(flag bool) ParseOpt { 77 return func(opts *parseOpts) { 78 opts.holderBindingRequired = flag 79 } 80 } 81 82 // WithExpectedAudienceForHolderBinding option is to pass expected audience for holder binding. 83 func WithExpectedAudienceForHolderBinding(audience string) ParseOpt { 84 return func(opts *parseOpts) { 85 opts.expectedAudienceForHolderBinding = audience 86 } 87 } 88 89 // WithExpectedNonceForHolderBinding option is to pass nonce value for holder binding. 90 func WithExpectedNonceForHolderBinding(nonce string) ParseOpt { 91 return func(opts *parseOpts) { 92 opts.expectedNonceForHolderBinding = nonce 93 } 94 } 95 96 // WithLeewayForClaimsValidation is an option for claims time(s) validation. 97 func WithLeewayForClaimsValidation(duration time.Duration) ParseOpt { 98 return func(opts *parseOpts) { 99 opts.leewayForClaimsValidation = duration 100 } 101 } 102 103 // Parse parses combined format for presentation and returns verified claims. 104 // The Verifier has to verify that all disclosed claim values were part of the original, Issuer-signed SD-JWT. 105 // 106 // At a high level, the Verifier: 107 // - receives the Combined Format for Presentation from the Holder and verifies the signature of the SD-JWT using the 108 // Issuer's public key, 109 // - verifies the Holder Binding JWT, if Holder Binding is required by the Verifier's policy, 110 // using the public key included in the SD-JWT, 111 // - calculates the digests over the Holder-Selected Disclosures and verifies that each digest 112 // is contained in the SD-JWT. 113 // 114 // Detailed algorithm: 115 // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-02.html#name-verification-by-the-verifier 116 // 117 // The Verifier will not, however, learn any claim values not disclosed in the Disclosures. 118 func Parse(combinedFormatForPresentation string, opts ...ParseOpt) (map[string]interface{}, error) { 119 defaultSigningAlgorithms := []string{"EdDSA", "RS256"} 120 pOpts := &parseOpts{ 121 issuerSigningAlgorithms: defaultSigningAlgorithms, 122 holderSigningAlgorithms: defaultSigningAlgorithms, 123 leewayForClaimsValidation: jwt.DefaultLeeway, 124 } 125 126 for _, opt := range opts { 127 opt(pOpts) 128 } 129 130 var jwtOpts []afgjwt.ParseOpt 131 jwtOpts = append(jwtOpts, 132 afgjwt.WithSignatureVerifier(pOpts.sigVerifier), 133 afgjwt.WithJWTDetachedPayload(pOpts.detachedPayload)) 134 135 // Separate the Presentation into the SD-JWT, the Disclosures (if any), and the Holder Binding JWT (if provided) 136 cfp := common.ParseCombinedFormatForPresentation(combinedFormatForPresentation) 137 138 // Validate the signature over the SD-JWT 139 signedJWT, _, err := afgjwt.Parse(cfp.SDJWT, jwtOpts...) 140 if err != nil { 141 return nil, err 142 } 143 144 // Ensure that a signing algorithm was used that was deemed secure for the application. 145 // The none algorithm MUST NOT be accepted. 146 err = verifySigningAlg(signedJWT.Headers, pOpts.issuerSigningAlgorithms) 147 if err != nil { 148 return nil, fmt.Errorf("failed to verify issuer signing algorithm: %w", err) 149 } 150 151 // TODO: Validate the Issuer of the SD-JWT and that the signing key belongs to this Issuer. 152 153 // Check that the SD-JWT is valid using nbf, iat, and exp claims, 154 // if provided in the SD-JWT, and not selectively disclosed. 155 err = verifyJWT(signedJWT, pOpts.leewayForClaimsValidation) 156 if err != nil { 157 return nil, err 158 } 159 160 // Check that there are no duplicate disclosures 161 err = checkForDuplicates(cfp.Disclosures) 162 if err != nil { 163 return nil, fmt.Errorf("check disclosures: %w", err) 164 } 165 166 // Verify that all disclosures are present in SD-JWT. 167 err = common.VerifyDisclosuresInSDJWT(cfp.Disclosures, signedJWT) 168 if err != nil { 169 return nil, err 170 } 171 172 err = verifyHolderBinding(signedJWT, cfp.HolderBinding, pOpts) 173 if err != nil { 174 return nil, fmt.Errorf("failed to verify holder binding: %w", err) 175 } 176 177 return getDisclosedClaims(cfp.Disclosures, signedJWT) 178 } 179 180 func verifyHolderBinding(sdJWT *afgjwt.JSONWebToken, holderBinding string, pOpts *parseOpts) error { 181 if pOpts.holderBindingRequired && holderBinding == "" { 182 return fmt.Errorf("holder binding is required") 183 } 184 185 if holderBinding == "" { 186 // not required and not present - nothing to do 187 return nil 188 } 189 190 signatureVerifier, err := getSignatureVerifier(utils.CopyMap(sdJWT.Payload)) 191 if err != nil { 192 return fmt.Errorf("failed to get signature verifier from presentation claims: %w", err) 193 } 194 195 holderJWT, _, err := afgjwt.Parse(holderBinding, 196 afgjwt.WithSignatureVerifier(signatureVerifier)) 197 if err != nil { 198 return fmt.Errorf("failed to parse holder binding: %w", err) 199 } 200 201 err = verifyHolderJWT(holderJWT, pOpts) 202 if err != nil { 203 return fmt.Errorf("failed to verify holder JWT: %w", err) 204 } 205 206 return nil 207 } 208 209 func verifyHolderJWT(holderJWT *afgjwt.JSONWebToken, pOpts *parseOpts) error { 210 // Ensure that a signing algorithm was used that was deemed secure for the application. 211 // The none algorithm MUST NOT be accepted. 212 err := verifySigningAlg(holderJWT.Headers, pOpts.holderSigningAlgorithms) 213 if err != nil { 214 return fmt.Errorf("failed to verify holder signing algorithm: %w", err) 215 } 216 217 err = verifyJWT(holderJWT, pOpts.leewayForClaimsValidation) 218 if err != nil { 219 return err 220 } 221 222 var bindingPayload holderBindingPayload 223 224 d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 225 Result: &bindingPayload, 226 TagName: "json", 227 Squash: true, 228 WeaklyTypedInput: true, 229 DecodeHook: utils.JSONNumberToJwtNumericDate(), 230 }) 231 if err != nil { 232 return fmt.Errorf("mapstruct verifyHodlder. error: %w", err) 233 } 234 235 if err = d.Decode(holderJWT.Payload); err != nil { 236 return fmt.Errorf("mapstruct verifyHodlder decode. error: %w", err) 237 } 238 239 if pOpts.expectedNonceForHolderBinding != "" && pOpts.expectedNonceForHolderBinding != bindingPayload.Nonce { 240 return fmt.Errorf("nonce value '%s' does not match expected nonce value '%s'", 241 bindingPayload.Nonce, pOpts.expectedNonceForHolderBinding) 242 } 243 244 if pOpts.expectedAudienceForHolderBinding != "" && pOpts.expectedAudienceForHolderBinding != bindingPayload.Audience { 245 return fmt.Errorf("audience value '%s' does not match expected audience value '%s'", 246 bindingPayload.Audience, pOpts.expectedAudienceForHolderBinding) 247 } 248 249 return nil 250 } 251 252 func getSignatureVerifier(claims map[string]interface{}) (jose.SignatureVerifier, error) { 253 cnf, err := common.GetCNF(claims) 254 if err != nil { 255 return nil, err 256 } 257 258 signatureVerifier, err := getSignatureVerifierFromCNF(cnf) 259 if err != nil { 260 return nil, err 261 } 262 263 return signatureVerifier, nil 264 } 265 266 // getSignatureVerifierFromCNF will evolve over time as we support more cnf modes and algorithms. 267 func getSignatureVerifierFromCNF(cnf map[string]interface{}) (jose.SignatureVerifier, error) { 268 jwkObj, ok := cnf["jwk"] 269 if !ok { 270 return nil, fmt.Errorf("jwk must be present in cnf") 271 } 272 273 // TODO: Add handling other methods: "jwe", "jku" and "kid" 274 275 jwkObjBytes, err := json.Marshal(jwkObj) 276 if err != nil { 277 return nil, fmt.Errorf("marshal jwk: %w", err) 278 } 279 280 j := jwk.JWK{} 281 282 err = j.UnmarshalJSON(jwkObjBytes) 283 if err != nil { 284 return nil, fmt.Errorf("unmarshal jwk: %w", err) 285 } 286 287 signatureVerifier, err := afgjwt.GetVerifier(&verifier.PublicKey{JWK: &j}) 288 if err != nil { 289 return nil, fmt.Errorf("get verifier from jwk: %w", err) 290 } 291 292 return signatureVerifier, nil 293 } 294 295 func getDisclosedClaims(disclosures []string, signedJWT *afgjwt.JSONWebToken) (map[string]interface{}, error) { 296 disclosureClaims, err := common.GetDisclosureClaims(disclosures) 297 if err != nil { 298 return nil, fmt.Errorf("failed to get verified payload: %w", err) 299 } 300 301 disclosedClaims, err := common.GetDisclosedClaims(disclosureClaims, utils.CopyMap(signedJWT.Payload)) 302 if err != nil { 303 return nil, fmt.Errorf("failed to get disclosed claims: %w", err) 304 } 305 306 return disclosedClaims, nil 307 } 308 309 func verifySigningAlg(joseHeaders jose.Headers, secureAlgs []string) error { 310 alg, ok := joseHeaders.Algorithm() 311 if !ok { 312 return fmt.Errorf("missing alg") 313 } 314 315 if alg == afgjwt.AlgorithmNone { 316 return fmt.Errorf("alg value cannot be 'none'") 317 } 318 319 if !contains(secureAlgs, alg) { 320 return fmt.Errorf("alg '%s' is not in the allowed list", alg) 321 } 322 323 return nil 324 } 325 326 func contains(values []string, val string) bool { 327 for _, v := range values { 328 if v == val { 329 return true 330 } 331 } 332 333 return false 334 } 335 336 func checkForDuplicates(values []string) error { 337 var duplicates []string 338 339 valuesMap := make(map[string]bool) 340 341 for _, val := range values { 342 if _, ok := valuesMap[val]; !ok { 343 valuesMap[val] = true 344 } else { 345 duplicates = append(duplicates, val) 346 } 347 } 348 349 if len(duplicates) > 0 { 350 return fmt.Errorf("duplicate values found %v", duplicates) 351 } 352 353 return nil 354 } 355 356 // verifyJWT checks that the JWT is valid using nbf, iat, and exp claims (if provided in the JWT). 357 func verifyJWT(signedJWT *afgjwt.JSONWebToken, leeway time.Duration) error { 358 var claims jwt.Claims 359 360 d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 361 Result: &claims, 362 TagName: "json", 363 Squash: true, 364 WeaklyTypedInput: true, 365 DecodeHook: utils.JSONNumberToJwtNumericDate(), 366 }) 367 if err != nil { 368 return fmt.Errorf("mapstruct verifyJWT. error: %w", err) 369 } 370 371 if err = d.Decode(signedJWT.Payload); err != nil { 372 return fmt.Errorf("mapstruct verifyJWT decode. error: %w", err) 373 } 374 375 // Validate checks claims in a token against expected values. 376 // It is validated using the expected.Time, or time.Now if not provided 377 expected := jwt.Expected{} 378 379 err = claims.ValidateWithLeeway(expected, leeway) 380 if err != nil { 381 return fmt.Errorf("invalid JWT time values: %w", err) 382 } 383 384 return nil 385 } 386 387 // holderBindingPayload represents expected holder binding payload. 388 type holderBindingPayload struct { 389 Nonce string `json:"nonce,omitempty"` 390 Audience string `json:"aud,omitempty"` 391 IssuedAt *jwt.NumericDate `json:"iat,omitempty"` 392 }