github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/verifiable/credential_jwt.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  SPDX-License-Identifier: Apache-2.0
     4  */
     5  
     6  package verifiable
     7  
     8  import (
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"time"
    13  
    14  	josejwt "github.com/go-jose/go-jose/v3/jwt"
    15  
    16  	"github.com/hyperledger/aries-framework-go/pkg/doc/jwt"
    17  	jsonutil "github.com/hyperledger/aries-framework-go/pkg/doc/util/json"
    18  )
    19  
    20  const (
    21  	vcIssuanceDateField   = "issuanceDate"
    22  	vcIDField             = "id"
    23  	vcExpirationDateField = "expirationDate"
    24  	vcIssuerField         = "issuer"
    25  	vcIssuerIDField       = "id"
    26  )
    27  
    28  // JWTCredClaims is JWT Claims extension by Verifiable Credential (with custom "vc" claim).
    29  type JWTCredClaims struct {
    30  	*jwt.Claims
    31  
    32  	VC map[string]interface{} `json:"vc,omitempty"`
    33  }
    34  
    35  // newJWTCredClaims creates JWT Claims of VC with an option to minimize certain fields of VC
    36  // which is put into "vc" claim.
    37  func newJWTCredClaims(vc *Credential, minimizeVC bool) (*JWTCredClaims, error) {
    38  	subjectID, err := SubjectID(vc.Subject)
    39  	if err != nil {
    40  		return nil, fmt.Errorf("get VC subject id: %w", err)
    41  	}
    42  
    43  	// currently jwt encoding supports only single subject (by the spec)
    44  	jwtClaims := &jwt.Claims{
    45  		Issuer:    vc.Issuer.ID,                           // iss
    46  		NotBefore: josejwt.NewNumericDate(vc.Issued.Time), // nbf
    47  		ID:        vc.ID,                                  // jti
    48  		Subject:   subjectID,                              // sub
    49  	}
    50  
    51  	if vc.Expired != nil {
    52  		jwtClaims.Expiry = josejwt.NewNumericDate(vc.Expired.Time) // exp
    53  	}
    54  
    55  	if vc.Issued != nil {
    56  		jwtClaims.IssuedAt = josejwt.NewNumericDate(vc.Issued.Time)
    57  	}
    58  
    59  	var raw *rawCredential
    60  
    61  	if minimizeVC {
    62  		vcCopy := *vc
    63  		vcCopy.Expired = nil
    64  		vcCopy.Issuer.ID = ""
    65  		vcCopy.Issued = nil
    66  		vcCopy.ID = ""
    67  
    68  		raw, err = vcCopy.raw()
    69  	} else {
    70  		raw, err = vc.raw()
    71  	}
    72  
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	// If a Credential was parsed from JWT, we don't want the original JWT included when marshaling back to JWT claims.
    78  	raw.JWT = ""
    79  
    80  	vcMap, err := jsonutil.MergeCustomFields(raw, raw.CustomFields)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	credClaims := &JWTCredClaims{
    86  		Claims: jwtClaims,
    87  		VC:     vcMap,
    88  	}
    89  
    90  	return credClaims, nil
    91  }
    92  
    93  // JWTCredClaimsUnmarshaller unmarshals verifiable credential bytes into JWT claims with extra "vc" claim.
    94  type JWTCredClaimsUnmarshaller func(vcJWTBytes string) (*JWTCredClaims, error)
    95  
    96  // decodeCredJWT parses JWT from the specified bytes array in compact format using unmarshaller.
    97  // It returns decoded Verifiable Credential refined by JWT Claims in raw byte array form, and the claims object itself.
    98  func decodeCredJWT(rawJWT string, unmarshaller JWTCredClaimsUnmarshaller) ([]byte, error) {
    99  	credClaims, err := unmarshaller(rawJWT)
   100  	if err != nil {
   101  		return nil, fmt.Errorf("unmarshal VC JWT claims: %w", err)
   102  	}
   103  
   104  	// Apply VC-related claims from JWT.
   105  	credClaims.refineFromJWTClaims()
   106  
   107  	vcData, err := json.Marshal(credClaims.VC)
   108  	if err != nil {
   109  		return nil, errors.New("failed to marshal 'vc' claim of JWT")
   110  	}
   111  
   112  	return vcData, nil
   113  }
   114  
   115  func (jcc *JWTCredClaims) refineFromJWTClaims() {
   116  	vcMap := jcc.VC
   117  	claims := jcc.Claims
   118  
   119  	if iss := claims.Issuer; iss != "" {
   120  		refineVCIssuerFromJWTClaims(vcMap, iss)
   121  	}
   122  
   123  	if nbf := claims.NotBefore; nbf != nil {
   124  		nbfTime := nbf.Time().UTC()
   125  		vcMap[vcIssuanceDateField] = nbfTime.Format(time.RFC3339)
   126  	}
   127  
   128  	if jti := claims.ID; jti != "" {
   129  		vcMap[vcIDField] = jti
   130  	}
   131  
   132  	if iat := claims.IssuedAt; iat != nil {
   133  		iatTime := iat.Time().UTC()
   134  		vcMap[vcIssuanceDateField] = iatTime.Format(time.RFC3339)
   135  	}
   136  
   137  	if exp := claims.Expiry; exp != nil {
   138  		expTime := exp.Time().UTC()
   139  		vcMap[vcExpirationDateField] = expTime.Format(time.RFC3339)
   140  	}
   141  }
   142  
   143  func refineVCIssuerFromJWTClaims(vcMap map[string]interface{}, iss string) {
   144  	// Issuer of Verifiable Credential could be either string (id) or struct (with "id" field).
   145  	if _, exists := vcMap[vcIssuerField]; !exists {
   146  		vcMap[vcIssuerField] = iss
   147  		return
   148  	}
   149  
   150  	switch issuer := vcMap[vcIssuerField].(type) {
   151  	case string:
   152  		vcMap[vcIssuerField] = iss
   153  	case map[string]interface{}:
   154  		issuer[vcIssuerIDField] = iss
   155  	}
   156  }