Skip to content

Commit

Permalink
Make sure JWT VCs and VPs marshal into the correct format
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul committed Oct 18, 2023
1 parent 22462b1 commit f3e17c6
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 18 deletions.
16 changes: 13 additions & 3 deletions vc/vc.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ func VCContextV1URI() ssi.URI {
const (
// JSONLDCredentialProofFormat is the format for JSON-LD based credentials.
JSONLDCredentialProofFormat string = "ldp_vc"
// JWTCredentialsProofFormat is the format for JWT based credentials.
// JWTCredentialProofFormat is the format for JWT based credentials.
// Note: various specs have not yet decided on the exact const (jwt_vc or jwt_vc_json, etc), so this is subject to change.
JWTCredentialsProofFormat = "jwt_vc"
JWTCredentialProofFormat = "jwt_vc"
)

var errCredentialSubjectWithoutID = errors.New("credential subjects have no ID")
Expand Down Expand Up @@ -99,7 +99,7 @@ func parseJWTCredential(raw string) (*VerifiableCredential, error) {
} else if jti != nil {
result.ID = jti
}
result.format = JWTCredentialsProofFormat
result.format = JWTCredentialProofFormat
result.raw = raw
result.token = token
return &result, nil
Expand Down Expand Up @@ -190,6 +190,16 @@ func (vc VerifiableCredential) Proofs() ([]Proof, error) {
}

func (vc VerifiableCredential) MarshalJSON() ([]byte, error) {
if vc.raw != "" {
// Credential instance created through ParseVerifiableCredential()
if vc.format == JWTCredentialProofFormat {
// Marshal as JSON string
return json.Marshal(vc.raw)
}
// JSON-LD, already in JSON format so return as-is
return []byte(vc.raw), nil
}
// Must be a (new) JSON-LD credential (library does not support creating JWT VCs)
type alias VerifiableCredential
tmp := alias(vc)
if data, err := json.Marshal(tmp); err != nil {
Expand Down
14 changes: 12 additions & 2 deletions vc/vc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ BND3LDTn9H7FQokEsUEi8jKwXhGvoN3JtRa51xrNDgXDb0cq1UTYB-rK4Ft9YVmR1NI_ZOF8oGc_7wAp
txJy6M1-lD7a5HTzanYTWBPAUHDZGyGKXdJw-W_x0IWChBzI8t3kpG253fg6V3tPgHeKXE94fz_QpYfg
--7kLsyBAfQGbg`

func TestVerifiableCredential_UnmarshalJSON(t *testing.T) {
// TestVerifiableCredential_JSONMarshalling tests JSON marshalling of VerifiableCredential.
// Credentials in JSON-LD format are marshalled JSON object, while JWT credentials are marshalled as JSON string.
func TestVerifiableCredential_JSONMarshalling(t *testing.T) {
t.Run("JSON-LD", func(t *testing.T) {
input := VerifiableCredential{}
raw := `{
Expand All @@ -43,6 +45,10 @@ func TestVerifiableCredential_UnmarshalJSON(t *testing.T) {
assert.Equal(t, JSONLDCredentialProofFormat, input.Format())
assert.Equal(t, raw, input.Raw())
assert.Nil(t, input.JWT())
// Should marshal into JSON object
marshalled, err := json.Marshal(input)
require.NoError(t, err)
assert.True(t, strings.HasPrefix(string(marshalled), "{"))
})
t.Run("JWT", func(t *testing.T) {
input := VerifiableCredential{}
Expand All @@ -52,9 +58,13 @@ func TestVerifiableCredential_UnmarshalJSON(t *testing.T) {
assert.Equal(t, []ssi.URI{ssi.MustParseURI("VerifiableCredential"), ssi.MustParseURI("UniversityDegreeCredential")}, input.Type)
assert.Len(t, input.CredentialSubject, 1)
assert.NotNil(t, input.CredentialSubject[0].(map[string]interface{})["degree"])
assert.Equal(t, JWTCredentialsProofFormat, input.Format())
assert.Equal(t, JWTCredentialProofFormat, input.Format())
assert.Equal(t, raw, input.Raw())
assert.NotNil(t, input.JWT())
// Should marshal into JSON string
marshalled, err := json.Marshal(input)
require.NoError(t, err)
assert.JSONEq(t, `"`+raw+`"`, string(marshalled))
})
}

Expand Down
26 changes: 14 additions & 12 deletions vc/vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,19 +155,21 @@ func (vp VerifiablePresentation) Proofs() ([]Proof, error) {
}

func (vp VerifiablePresentation) MarshalJSON() ([]byte, error) {
switch vp.format {
case JWTPresentationProofFormat:
return json.Marshal(vp.raw)
case JSONLDPresentationProofFormat:
fallthrough
default:
type alias VerifiablePresentation
tmp := alias(vp)
if data, err := json.Marshal(tmp); err != nil {
return nil, err
} else {
return marshal.NormalizeDocument(data, pluralContext, marshal.Unplural(typeKey), marshal.Unplural(verifiableCredentialKey), marshal.Unplural(proofKey))
if vp.raw != "" {
// Presentation instance created through ParseVerifiablePresentation()
if vp.format == JWTPresentationProofFormat {
// Marshal as JSON string
return json.Marshal(vp.raw)
}
// JSON-LD, already in JSON format so return as-is
return []byte(vp.raw), nil
}
type alias VerifiablePresentation
tmp := alias(vp)
if data, err := json.Marshal(tmp); err != nil {
return nil, err
} else {
return marshal.NormalizeDocument(data, pluralContext, marshal.Unplural(typeKey), marshal.Unplural(verifiableCredentialKey), marshal.Unplural(proofKey))
}
}

Expand Down
2 changes: 1 addition & 1 deletion vc/vp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func TestParseVerifiablePresentation(t *testing.T) {
// Assert contained JWT VerifiableCredential was unmarshalled
assert.Len(t, vp.VerifiableCredential, 1)
vc := vp.VerifiableCredential[0]
assert.Equal(t, JWTCredentialsProofFormat, vc.Format())
assert.Equal(t, JWTCredentialProofFormat, vc.Format())
assert.Equal(t, "http://example.edu/credentials/3732", vc.ID.String())
})
t.Run("json.UnmarshalJSON for JWT-VP wrapped inside other document", func(t *testing.T) {
Expand Down

0 comments on commit f3e17c6

Please sign in to comment.