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  }