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  }