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 }