Skip to content

Commit 220abac

Browse files
authored
Merge 741c11e into 8a7c144
2 parents 8a7c144 + 741c11e commit 220abac

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+4794
-841
lines changed

common/credential.go

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
package common
2+
3+
import (
4+
"encoding/json"
5+
"time"
6+
)
7+
8+
// W3C Verifiable Credentials Data Model constants
9+
// See https://www.w3.org/TR/vc-data-model-2.0/
10+
const (
11+
// ContextCredentialsV1 is the W3C VC Data Model v1.1 context URI.
12+
ContextCredentialsV1 = "https://www.w3.org/2018/credentials/v1"
13+
14+
// ContextCredentialsV2 is the W3C VC Data Model v2.0 context URI.
15+
ContextCredentialsV2 = "https://www.w3.org/ns/credentials/v2"
16+
17+
// TypeVerifiableCredential is the base type for all Verifiable Credentials.
18+
TypeVerifiableCredential = "VerifiableCredential"
19+
20+
// TypeVerifiablePresentation is the base type for all Verifiable Presentations.
21+
TypeVerifiablePresentation = "VerifiablePresentation"
22+
23+
// JSONLDKeyContext is the JSON-LD @context key.
24+
JSONLDKeyContext = "@context"
25+
26+
// JSONLDKeyType is the JSON-LD type key.
27+
JSONLDKeyType = "type"
28+
29+
// JSONLDKeyID is the JSON-LD id key.
30+
JSONLDKeyID = "id"
31+
32+
// VCKeyIssuer is the issuer key in a VC JSON representation.
33+
VCKeyIssuer = "issuer"
34+
35+
// VCKeyCredentialSubject is the credentialSubject key in a VC JSON representation.
36+
VCKeyCredentialSubject = "credentialSubject"
37+
38+
// VCKeyValidFrom is the validFrom key (VC Data Model 2.0).
39+
VCKeyValidFrom = "validFrom"
40+
41+
// VCKeyValidUntil is the validUntil key (VC Data Model 2.0).
42+
VCKeyValidUntil = "validUntil"
43+
44+
// VCKeyCredentialStatus is the credentialStatus key.
45+
VCKeyCredentialStatus = "credentialStatus"
46+
47+
// VCKeyCredentialSchema is the credentialSchema key.
48+
VCKeyCredentialSchema = "credentialSchema"
49+
50+
// VCKeyEvidence is the evidence key.
51+
VCKeyEvidence = "evidence"
52+
53+
// VCKeyTermsOfUse is the termsOfUse key.
54+
VCKeyTermsOfUse = "termsOfUse"
55+
56+
// VCKeyRefreshService is the refreshService key.
57+
VCKeyRefreshService = "refreshService"
58+
59+
// VPKeyHolder is the holder key in a VP JSON representation.
60+
VPKeyHolder = "holder"
61+
62+
// VPKeyVerifiableCredential is the verifiableCredential key in a VP JSON representation.
63+
VPKeyVerifiableCredential = "verifiableCredential"
64+
65+
// VPKeyProof is the proof key in a VP/VC JSON representation.
66+
VPKeyProof = "proof"
67+
68+
// VCKeyIssuanceDate is the issuanceDate key (VC Data Model 1.1).
69+
VCKeyIssuanceDate = "issuanceDate"
70+
71+
// VCKeyExpirationDate is the expirationDate key (VC Data Model 1.1).
72+
VCKeyExpirationDate = "expirationDate"
73+
74+
// VCKeyIssued is the issued key (legacy VC date field).
75+
VCKeyIssued = "issued"
76+
)
77+
78+
// JWT standard claim keys (RFC 7519).
79+
const (
80+
JWTClaimIss = "iss" // Issuer
81+
JWTClaimSub = "sub" // Subject
82+
JWTClaimJti = "jti" // JWT ID
83+
JWTClaimNbf = "nbf" // Not Before
84+
JWTClaimIat = "iat" // Issued At
85+
JWTClaimExp = "exp" // Expiration Time
86+
)
87+
88+
// JWT-VC/VP specific claim keys.
89+
const (
90+
JWTClaimVC = "vc" // VC claim in a JWT-encoded Verifiable Credential
91+
JWTClaimVP = "vp" // VP claim in a JWT-encoded Verifiable Presentation
92+
JWTClaimVct = "vct" // Verifiable Credential Type (SD-JWT VC)
93+
JWTClaimCnf = "cnf" // Confirmation method (RFC 7800, used for cryptographic holder binding)
94+
CnfKeyJWK = "jwk" // JWK key within the cnf claim (RFC 7800 §3.2)
95+
)
96+
97+
// JSONObject is an alias for a generic JSON map.
98+
type JSONObject = map[string]interface{}
99+
100+
// CustomFields holds additional fields beyond the standard VC fields.
101+
type CustomFields map[string]interface{}
102+
103+
// Issuer identifies the entity that issued a Verifiable Credential.
104+
type Issuer struct {
105+
ID string
106+
}
107+
108+
// Subject holds the claims made about a credential subject.
109+
type Subject struct {
110+
ID string
111+
CustomFields map[string]interface{}
112+
}
113+
114+
// TypedID represents a typed identifier used for status, schema, evidence, etc.
115+
type TypedID struct {
116+
ID string
117+
Type string
118+
}
119+
120+
// CredentialContents contains the structured content of a Verifiable Credential.
121+
// Fields align with the W3C VC Data Model 2.0 specification.
122+
type CredentialContents struct {
123+
Context []string
124+
ID string
125+
Types []string
126+
Issuer *Issuer
127+
Subject []Subject
128+
ValidFrom *time.Time
129+
ValidUntil *time.Time
130+
Status *TypedID
131+
Schemas []TypedID
132+
Evidence []interface{}
133+
TermsOfUse []TypedID
134+
RefreshService []TypedID
135+
}
136+
137+
// Credential represents a Verifiable Credential.
138+
type Credential struct {
139+
contents CredentialContents
140+
customFields CustomFields
141+
// rawJSON, if set, is returned by ToRawJSON() instead of building from contents.
142+
rawJSON JSONObject
143+
}
144+
145+
// Contents returns the structured content of the credential.
146+
func (c *Credential) Contents() CredentialContents {
147+
return c.contents
148+
}
149+
150+
// CustomFields returns the custom fields of the credential.
151+
func (c *Credential) CustomFields() CustomFields {
152+
return c.customFields
153+
}
154+
155+
// ToRawJSON converts the credential to a JSON map representation.
156+
// Custom fields from the subject are placed at the top level of credentialSubject.
157+
func (c *Credential) ToRawJSON() JSONObject {
158+
if c.rawJSON != nil {
159+
return c.rawJSON
160+
}
161+
result := JSONObject{}
162+
163+
if len(c.contents.Context) > 0 {
164+
result[JSONLDKeyContext] = c.contents.Context
165+
}
166+
if c.contents.ID != "" {
167+
result[JSONLDKeyID] = c.contents.ID
168+
}
169+
if len(c.contents.Types) > 0 {
170+
result[JSONLDKeyType] = c.contents.Types
171+
}
172+
if c.contents.Issuer != nil {
173+
result[VCKeyIssuer] = c.contents.Issuer.ID
174+
}
175+
if c.contents.ValidFrom != nil {
176+
result[VCKeyValidFrom] = c.contents.ValidFrom.Format(time.RFC3339)
177+
}
178+
if c.contents.ValidUntil != nil {
179+
result[VCKeyValidUntil] = c.contents.ValidUntil.Format(time.RFC3339)
180+
}
181+
if c.contents.Status != nil {
182+
result[VCKeyCredentialStatus] = JSONObject{JSONLDKeyID: c.contents.Status.ID, JSONLDKeyType: c.contents.Status.Type}
183+
}
184+
if len(c.contents.Schemas) > 0 {
185+
result[VCKeyCredentialSchema] = typedIDsToJSON(c.contents.Schemas)
186+
}
187+
if len(c.contents.Evidence) > 0 {
188+
result[VCKeyEvidence] = c.contents.Evidence
189+
}
190+
if len(c.contents.TermsOfUse) > 0 {
191+
result[VCKeyTermsOfUse] = typedIDsToJSON(c.contents.TermsOfUse)
192+
}
193+
if len(c.contents.RefreshService) > 0 {
194+
result[VCKeyRefreshService] = typedIDsToJSON(c.contents.RefreshService)
195+
}
196+
197+
if len(c.contents.Subject) > 0 {
198+
subjects := make([]JSONObject, 0, len(c.contents.Subject))
199+
for _, s := range c.contents.Subject {
200+
subj := JSONObject{}
201+
if s.ID != "" {
202+
subj[JSONLDKeyID] = s.ID
203+
}
204+
for k, v := range s.CustomFields {
205+
subj[k] = v
206+
}
207+
subjects = append(subjects, subj)
208+
}
209+
if len(subjects) == 1 {
210+
result[VCKeyCredentialSubject] = subjects[0]
211+
} else {
212+
result[VCKeyCredentialSubject] = subjects
213+
}
214+
}
215+
216+
for k, v := range c.customFields {
217+
if _, exists := result[k]; !exists {
218+
result[k] = v
219+
}
220+
}
221+
222+
return result
223+
}
224+
225+
// MarshalJSON serializes the credential to JSON bytes.
226+
func (c *Credential) MarshalJSON() ([]byte, error) {
227+
return json.Marshal(c.ToRawJSON())
228+
}
229+
230+
// SetRawJSON stores a pre-built raw JSON map to be returned by ToRawJSON().
231+
func (c *Credential) SetRawJSON(raw JSONObject) {
232+
c.rawJSON = raw
233+
}
234+
235+
// CreateCredential constructs a Credential from CredentialContents and custom fields.
236+
func CreateCredential(contents CredentialContents, customFields CustomFields) (*Credential, error) {
237+
return &Credential{
238+
contents: contents,
239+
customFields: customFields,
240+
}, nil
241+
}
242+
243+
// PresentationOpt is a functional option for configuring a Presentation.
244+
type PresentationOpt func(*Presentation)
245+
246+
// Presentation represents a Verifiable Presentation.
247+
type Presentation struct {
248+
Context []string
249+
ID string
250+
Type []string
251+
Holder string
252+
credentials []*Credential
253+
Proof *LDProof
254+
// holderKey stores the resolved public key that signed the VP JWT.
255+
// Stored as interface{} to avoid jwx dependency in the common package.
256+
// The verifier package type-asserts to jwk.Key.
257+
holderKey interface{}
258+
}
259+
260+
// HolderKey returns the public key that signed the VP JWT, if available.
261+
func (p *Presentation) HolderKey() interface{} {
262+
return p.holderKey
263+
}
264+
265+
// SetHolderKey stores the public key that signed the VP JWT.
266+
func (p *Presentation) SetHolderKey(key interface{}) {
267+
p.holderKey = key
268+
}
269+
270+
// Credentials returns the credentials contained in the presentation.
271+
func (p *Presentation) Credentials() []*Credential {
272+
return p.credentials
273+
}
274+
275+
// AddCredentials appends one or more credentials to the presentation.
276+
func (p *Presentation) AddCredentials(credentials ...*Credential) {
277+
p.credentials = append(p.credentials, credentials...)
278+
}
279+
280+
// MarshalJSON serializes the presentation to JSON bytes.
281+
func (p *Presentation) MarshalJSON() ([]byte, error) {
282+
result := JSONObject{}
283+
284+
ctx := p.Context
285+
if len(ctx) == 0 {
286+
ctx = []string{ContextCredentialsV1}
287+
}
288+
result[JSONLDKeyContext] = ctx
289+
290+
types := p.Type
291+
if len(types) == 0 {
292+
types = []string{TypeVerifiablePresentation}
293+
}
294+
result[JSONLDKeyType] = types
295+
296+
if p.ID != "" {
297+
result[JSONLDKeyID] = p.ID
298+
}
299+
if p.Holder != "" {
300+
result[VPKeyHolder] = p.Holder
301+
}
302+
303+
if len(p.credentials) > 0 {
304+
vcs := make([]json.RawMessage, 0, len(p.credentials))
305+
for _, cred := range p.credentials {
306+
credJSON, err := cred.MarshalJSON()
307+
if err != nil {
308+
return nil, err
309+
}
310+
vcs = append(vcs, credJSON)
311+
}
312+
result[VPKeyVerifiableCredential] = vcs
313+
}
314+
315+
if p.Proof != nil {
316+
result[VPKeyProof] = p.Proof
317+
}
318+
319+
return json.Marshal(result)
320+
}
321+
322+
// NewPresentation creates a new Presentation with the given options applied.
323+
func NewPresentation(opts ...PresentationOpt) (*Presentation, error) {
324+
p := &Presentation{}
325+
for _, opt := range opts {
326+
opt(p)
327+
}
328+
return p, nil
329+
}
330+
331+
// WithCredentials returns a PresentationOpt that adds credentials to a presentation.
332+
func WithCredentials(credentials ...*Credential) PresentationOpt {
333+
return func(p *Presentation) {
334+
p.AddCredentials(credentials...)
335+
}
336+
}
337+
338+
// typedIDsToJSON converts a slice of TypedID to JSON-compatible format.
339+
func typedIDsToJSON(ids []TypedID) []JSONObject {
340+
result := make([]JSONObject, 0, len(ids))
341+
for _, id := range ids {
342+
obj := JSONObject{}
343+
if id.ID != "" {
344+
obj[JSONLDKeyID] = id.ID
345+
}
346+
if id.Type != "" {
347+
obj[JSONLDKeyType] = id.Type
348+
}
349+
result = append(result, obj)
350+
}
351+
return result
352+
}

0 commit comments

Comments
 (0)