Skip to content

Commit

Permalink
make issuanceDate optional
Browse files Browse the repository at this point in the history
  • Loading branch information
gerardsn committed Nov 29, 2023
1 parent 2011611 commit 406915e
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 23 deletions.
20 changes: 14 additions & 6 deletions vc/vc.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ func parseJWTCredential(raw string) (*VerifiableCredential, error) {
}
// parse nbf
if _, ok := token.Get(jwt.NotBeforeKey); ok {
result.IssuanceDate = token.NotBefore()
nbf := token.NotBefore()
result.IssuanceDate = &nbf
}
// parse sub
if token.Subject() != "" {
Expand Down Expand Up @@ -139,7 +140,7 @@ type VerifiableCredential struct {
// Issuer refers to the party that issued the credential
Issuer ssi.URI `json:"issuer"`
// IssuanceDate is a rfc3339 formatted datetime. Has alias ValidFrom
IssuanceDate time.Time `json:"issuanceDate"`
IssuanceDate *time.Time `json:"issuanceDate,omitempty"`
// ValidFrom is a rfc3339 formatted datetime. It is optional, and is mutually exclusive with IssuanceDate (not enforced).
// It's a forwards compatible alternative for IssuanceDate.
// The jwt-vc 'nbf' field will unmarshal to IssuanceDate, which may not match with the JSON-LD definition of certain VCs.
Expand Down Expand Up @@ -188,7 +189,7 @@ func (vc VerifiableCredential) JWT() jwt.Token {
func (vc VerifiableCredential) ValidAt(t time.Time) bool {
// IssuanceDate is a required field, but will default to the zero value when missing. (when ValidFrom != nil)
// t > IssuanceDate
if !vc.IssuanceDate.IsZero() && t.Before(vc.IssuanceDate) {
if vc.IssuanceDate != nil && t.Before(*vc.IssuanceDate) {
return false
}
// t > ValidFrom
Expand Down Expand Up @@ -347,9 +348,8 @@ func CreateJWTVerifiableCredential(ctx context.Context, template VerifiableCrede
jws.TypeKey: "JWT",
}
claims := map[string]interface{}{
jwt.NotBeforeKey: template.IssuanceDate,
jwt.IssuerKey: template.Issuer.String(),
jwt.SubjectKey: subjectDID.String(),
jwt.IssuerKey: template.Issuer.String(),
jwt.SubjectKey: subjectDID.String(),
"vc": map[string]interface{}{
"@context": template.Context,
"type": template.Type,
Expand All @@ -359,9 +359,17 @@ func CreateJWTVerifiableCredential(ctx context.Context, template VerifiableCrede
if template.ID != nil {
claims[jwt.JwtIDKey] = template.ID.String()
}
if template.IssuanceDate != nil {
claims[jwt.NotBeforeKey] = *template.IssuanceDate
}
if template.ExpirationDate != nil {
claims[jwt.ExpirationKey] = *template.ExpirationDate
}
if template.ValidFrom != nil || template.ValidUntil != nil {
// parseJWTCredential maps ValidFrom/ValidUntil to IssuanceDate/ExpirationDate,
// so a template using ValidFrom/ValidUntil would not match the final VC
return nil, errors.New("cannot use validFrom/validUntil to generate JWT-VCs")
}
token, err := signer(ctx, claims, headers)
if err != nil {
return nil, fmt.Errorf("unable to sign JWT credential: %w", err)
Expand Down
53 changes: 36 additions & 17 deletions vc/vc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestVerifiableCredential_JSONMarshalling(t *testing.T) {
input := VerifiableCredential{}
marshalled, err := json.Marshal(input)
require.NoError(t, err)
assert.Equal(t, "{\"@context\":null,\"credentialSubject\":null,\"issuanceDate\":\"0001-01-01T00:00:00Z\",\"issuer\":\"\",\"proof\":null,\"type\":null}", string(marshalled))
assert.Equal(t, "{\"@context\":null,\"credentialSubject\":null,\"issuer\":\"\",\"proof\":null,\"type\":null}", string(marshalled))
})
})
t.Run("JWT", func(t *testing.T) {
Expand Down Expand Up @@ -334,7 +334,7 @@ func TestCreateJWTVerifiableCredential(t *testing.T) {
VerifiableCredentialTypeV1URI(),
ssi.MustParseURI("https://example.com/custom"),
},
IssuanceDate: issuanceDate,
IssuanceDate: &issuanceDate,
ExpirationDate: &expirationDate,
CredentialSubject: []interface{}{
map[string]interface{}{
Expand All @@ -343,15 +343,22 @@ func TestCreateJWTVerifiableCredential(t *testing.T) {
},
Issuer: issuerDID.URI(),
}
captureFn := func(claims *map[string]any, headers *map[string]any) func(_ context.Context, c map[string]interface{}, h map[string]interface{}) (string, error) {
return func(_ context.Context, c map[string]interface{}, h map[string]interface{}) (string, error) {
if claims != nil {
*claims = c
}
if headers != nil {
*headers = h
}
return jwtCredential, nil
}
}
ctx := context.Background()
t.Run("all properties", func(t *testing.T) {
var claims map[string]interface{}
var headers map[string]interface{}
_, err := CreateJWTVerifiableCredential(ctx, template, func(_ context.Context, c map[string]interface{}, h map[string]interface{}) (string, error) {
claims = c
headers = h
return jwtCredential, nil
})
_, err := CreateJWTVerifiableCredential(ctx, template, captureFn(&claims, &headers))
assert.NoError(t, err)
assert.Equal(t, issuerDID.String(), claims[jwt.IssuerKey])
assert.Equal(t, subjectDID.String(), claims[jwt.SubjectKey])
Expand All @@ -366,18 +373,30 @@ func TestCreateJWTVerifiableCredential(t *testing.T) {
assert.Equal(t, map[string]interface{}{"typ": "JWT"}, headers)
})
t.Run("only mandatory properties", func(t *testing.T) {
minimumTemplate := template
minimumTemplate.ExpirationDate = nil
minimumTemplate.ID = nil
minimumTemplate := VerifiableCredential{CredentialSubject: template.CredentialSubject}
var claims map[string]interface{}
_, err := CreateJWTVerifiableCredential(ctx, minimumTemplate, func(_ context.Context, c map[string]interface{}, _ map[string]interface{}) (string, error) {
claims = c
return jwtCredential, nil
})
_, err := CreateJWTVerifiableCredential(ctx, minimumTemplate, captureFn(&claims, nil))
assert.NoError(t, err)
assert.Nil(t, claims[jwt.NotBeforeKey])
assert.Nil(t, claims[jwt.ExpirationKey])
assert.Nil(t, claims[jwt.JwtIDKey])
})
t.Run("error - cannot use validFrom", func(t *testing.T) {
template := VerifiableCredential{
CredentialSubject: template.CredentialSubject,
ValidFrom: &issuanceDate,
}
_, err := CreateJWTVerifiableCredential(ctx, template, captureFn(nil, nil))
assert.EqualError(t, err, "cannot use validFrom/validUntil to generate JWT-VCs")
})
t.Run("error - cannot use validUntil", func(t *testing.T) {
template := VerifiableCredential{
CredentialSubject: template.CredentialSubject,
ValidUntil: &expirationDate,
}
_, err := CreateJWTVerifiableCredential(ctx, template, captureFn(nil, nil))
assert.EqualError(t, err, "cannot use validFrom/validUntil to generate JWT-VCs")
})
}

func TestVerifiableCredential_ValidAt(t *testing.T) {
Expand All @@ -388,12 +407,12 @@ func TestVerifiableCredential_ValidAt(t *testing.T) {
assert.True(t, VerifiableCredential{}.ValidAt(time.Now()))

// valid on bounds
assert.True(t, VerifiableCredential{IssuanceDate: lll, ValidFrom: &lll}.ValidAt(lll))
assert.True(t, VerifiableCredential{IssuanceDate: &lll, ValidFrom: &lll}.ValidAt(lll))
assert.True(t, VerifiableCredential{ExpirationDate: &lll, ValidUntil: &lll}.ValidAt(lll))

// invalid
assert.False(t, VerifiableCredential{IssuanceDate: hhh, ValidFrom: &lll}.ValidAt(lll))
assert.False(t, VerifiableCredential{IssuanceDate: lll, ValidFrom: &hhh}.ValidAt(lll))
assert.False(t, VerifiableCredential{IssuanceDate: &hhh, ValidFrom: &lll}.ValidAt(lll))
assert.False(t, VerifiableCredential{IssuanceDate: &lll, ValidFrom: &hhh}.ValidAt(lll))
assert.False(t, VerifiableCredential{ExpirationDate: &hhh, ValidUntil: &lll}.ValidAt(hhh))
assert.False(t, VerifiableCredential{ExpirationDate: &lll, ValidUntil: &hhh}.ValidAt(hhh))
}

0 comments on commit 406915e

Please sign in to comment.