Skip to content

Commit

Permalink
Revert validFrom and validUntil (#111)
Browse files Browse the repository at this point in the history
* Revert "add clock skew to ValidAt (#100)"

This reverts commit 6d13fd4.

* Revert "Add validFrom and validUntil aliases for issuanceDate and expirationDate (#97)"

This reverts commit c2e3cb8.

* keep VC.ValidAt()
  • Loading branch information
gerardsn authored Mar 28, 2024
1 parent d998041 commit cd573eb
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 92 deletions.
48 changes: 12 additions & 36 deletions vc/vc.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ func parseJWTCredential(raw string) (*VerifiableCredential, error) {
}
// parse nbf
if _, ok := token.Get(jwt.NotBeforeKey); ok {
nbf := token.NotBefore()
result.IssuanceDate = &nbf
result.IssuanceDate = token.NotBefore()
}
// parse sub
if token.Subject() != "" {
Expand Down Expand Up @@ -140,18 +139,10 @@ type VerifiableCredential struct {
Type []ssi.URI `json:"type"`
// Issuer refers to the party that issued the credential
Issuer ssi.URI `json:"issuer"`
// IssuanceDate is a rfc3339 formatted datetime. It is required, but may be replaced by alias ValidFrom
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 (vc data model v2) alternative for IssuanceDate.
// The jwt-vc 'nbf' field will unmarshal to IssuanceDate, which may not match with the JSON-LD definition of certain VCs.
ValidFrom *time.Time `json:"validFrom,omitempty"`
// ExpirationDate is a rfc3339 formatted datetime. Has alias ValidUntil. It is optional
// IssuanceDate is a rfc3339 formatted datetime.
IssuanceDate time.Time `json:"issuanceDate"`
// ExpirationDate is a rfc3339 formatted datetime. It is optional
ExpirationDate *time.Time `json:"expirationDate,omitempty"`
// ValidFrom is a rfc3339 formatted datetime. It is optional, and is mutually exclusive with ExpirationDate (not enforced).
// It's a forwards compatible (vc data model v2) alternative for ExpirationDate.
// The jwt-vc 'exp' field will unmarshal to ExpirationDate, which may not match with the JSON-LD definition of certain VCs.
ValidUntil *time.Time `json:"validUntil,omitempty"`
// CredentialStatus holds information on how the credential can be revoked. It must be extracted using the UnmarshalCredentialStatus method and a custom type.
CredentialStatus []any `json:"credentialStatus,omitempty"`
// CredentialSubject holds the actual data for the credential. It must be extracted using the UnmarshalCredentialSubject method and a custom type.
Expand Down Expand Up @@ -186,27 +177,19 @@ func (vc VerifiableCredential) JWT() jwt.Token {
// ValidAt checks that t is within the validity window of the credential.
// The skew parameter allows compensating for some clock skew (set to 0 for strict validation).
// Return true if
// - t+skew >= IssuanceDate and ValidFrom
// - t-skew <= ExpirationDate and ValidUntil
// For any value that is missing, the evaluation defaults to true
// - t+skew >= IssuanceDate
// - t-skew <= ExpirationDate
// For any value that is missing, the evaluation defaults to true.
func (vc VerifiableCredential) ValidAt(t time.Time, skew time.Duration) bool {
// IssuanceDate is a required field, but will default to the zero value when missing. (when ValidFrom != nil)
// t > IssuanceDate
if vc.IssuanceDate != nil && t.Add(skew).Before(*vc.IssuanceDate) {
return false
}
// t > ValidFrom
if vc.ValidFrom != nil && t.Add(skew).Before(*vc.ValidFrom) {
if t.Add(skew).Before(vc.IssuanceDate) {
return false
}
// t < ExpirationDate
if vc.ExpirationDate != nil && t.Add(-skew).After(*vc.ExpirationDate) {
return false
}
// t < ValidUntil
if vc.ValidUntil != nil && t.Add(-skew).After(*vc.ValidUntil) {
return false
}
// valid
return true
}
Expand Down Expand Up @@ -400,24 +383,17 @@ func CreateJWTVerifiableCredential(ctx context.Context, template VerifiableCrede
"credentialSubject": template.CredentialSubject,
}
claims := map[string]interface{}{
jwt.IssuerKey: template.Issuer.String(),
jwt.SubjectKey: subjectDID.String(),
"vc": vcMap,
jwt.NotBeforeKey: template.IssuanceDate,
jwt.IssuerKey: template.Issuer.String(),
jwt.SubjectKey: subjectDID.String(),
"vc": vcMap,
}
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")
}
if template.CredentialStatus != nil {
vcMap["credentialStatus"] = template.CredentialStatus
}
Expand Down
85 changes: 29 additions & 56 deletions vc/vc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestVerifiableCredential_JSONMarshalling(t *testing.T) {
input := VerifiableCredential{}
actual, err := json.Marshal(input)
require.NoError(t, err)
const expected = "{\"@context\":null,\"credentialSubject\":null,\"issuer\":\"\",\"proof\":null,\"type\":null}"
const expected = "{\"@context\":null,\"credentialSubject\":null,\"issuanceDate\":\"0001-01-01T00:00:00Z\",\"issuer\":\"\",\"proof\":null,\"type\":null}"
assert.JSONEq(t, expected, string(actual))
})
})
Expand Down Expand Up @@ -402,7 +402,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 @@ -415,22 +415,15 @@ 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, captureFn(&claims, &headers))
_, err := CreateJWTVerifiableCredential(ctx, template, func(_ context.Context, c map[string]interface{}, h map[string]interface{}) (string, error) {
claims = c
headers = h
return jwtCredential, nil
})
assert.NoError(t, err)
assert.Equal(t, issuerDID.String(), claims[jwt.IssuerKey])
assert.Equal(t, subjectDID.String(), claims[jwt.SubjectKey])
Expand All @@ -446,54 +439,34 @@ func TestCreateJWTVerifiableCredential(t *testing.T) {
assert.Equal(t, map[string]interface{}{"typ": "JWT"}, headers)
})
t.Run("only mandatory properties", func(t *testing.T) {
minimumTemplate := VerifiableCredential{CredentialSubject: template.CredentialSubject}
minimumTemplate := template
minimumTemplate.ExpirationDate = nil
minimumTemplate.ID = nil
var claims map[string]interface{}
_, err := CreateJWTVerifiableCredential(ctx, minimumTemplate, captureFn(&claims, nil))
_, err := CreateJWTVerifiableCredential(ctx, minimumTemplate, func(_ context.Context, c map[string]interface{}, _ map[string]interface{}) (string, error) {
claims = c
return jwtCredential, 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) {
lll := time.Date(1999, 0, 0, 0, 0, 0, 0, time.UTC)
hhh := time.Date(2001, 0, 0, 0, 0, 0, 0, time.UTC)
skew := time.Hour * 24 * 365 * 3 // 3 years, time difference is 2 years

// no validity period is always true; includes missing IssuanceDate(.IsZero() == true)
assert.True(t, VerifiableCredential{}.ValidAt(time.Now(), 0))
low := time.Now()
mid := low.Add(time.Second)
high := mid.Add(time.Second)
skew := 3 * time.Second // > high - low

// test bounds
assert.True(t, VerifiableCredential{}.ValidAt(time.Now(), 0)) // valid when timestamps are missing
assert.True(t, VerifiableCredential{IssuanceDate: low, ExpirationDate: &high}.ValidAt(mid, 0)) // valid if in the middle
assert.False(t, VerifiableCredential{IssuanceDate: low, ExpirationDate: &mid}.ValidAt(high, 0)) // too high
assert.False(t, VerifiableCredential{IssuanceDate: mid, ExpirationDate: &high}.ValidAt(low, 0)) // too low

// with skew everything becomes valid
assert.True(t, VerifiableCredential{}.ValidAt(time.Now(), skew))

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

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

// invalid made valid
assert.True(t, VerifiableCredential{IssuanceDate: &hhh, ValidFrom: &lll}.ValidAt(lll, skew))
assert.True(t, VerifiableCredential{IssuanceDate: &lll, ValidFrom: &hhh}.ValidAt(lll, skew))
assert.True(t, VerifiableCredential{ExpirationDate: &hhh, ValidUntil: &lll}.ValidAt(hhh, skew))
assert.True(t, VerifiableCredential{ExpirationDate: &lll, ValidUntil: &hhh}.ValidAt(hhh, skew))
assert.True(t, VerifiableCredential{IssuanceDate: low, ExpirationDate: &high}.ValidAt(mid, skew))
assert.True(t, VerifiableCredential{IssuanceDate: low, ExpirationDate: &mid}.ValidAt(high, skew))
assert.True(t, VerifiableCredential{IssuanceDate: mid, ExpirationDate: &high}.ValidAt(low, skew))
}

0 comments on commit cd573eb

Please sign in to comment.