diff --git a/vc/vc.go b/vc/vc.go index 0580753..d070d31 100644 --- a/vc/vc.go +++ b/vc/vc.go @@ -138,10 +138,18 @@ 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. + // IssuanceDate is a rfc3339 formatted datetime. Has alias ValidFrom IssuanceDate time.Time `json:"issuanceDate"` - // ExpirationDate is a rfc3339 formatted datetime. It is optional + // 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. + ValidFrom *time.Time `json:"validFrom,omitempty"` + // ExpirationDate is a rfc3339 formatted datetime. Has alias ValidUntil. 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 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 is optional CredentialStatus *CredentialStatus `json:"credentialStatus,omitempty"` // CredentialSubject holds the actual data for the credential. It must be extracted using the UnmarshalCredentialSubject method and a custom type. @@ -173,6 +181,32 @@ func (vc VerifiableCredential) JWT() jwt.Token { return token } +// ValidAt returns true if +// - t >= IssuanceDate and ValidFrom +// - t <= ExpirationDate and ValidUntil +// For any value that is missing, the evaluation defaults to true +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) { + return false + } + // t > ValidFrom + if vc.ValidFrom != nil && t.Before(*vc.ValidFrom) { + return false + } + // t < ExpirationDate + if vc.ExpirationDate != nil && t.After(*vc.ExpirationDate) { + return false + } + // t < ValidUntil + if vc.ValidUntil != nil && t.After(*vc.ValidUntil) { + return false + } + // valid + return true +} + // CredentialStatus defines the method on how to determine a credential is revoked. type CredentialStatus struct { ID ssi.URI `json:"id"` diff --git a/vc/vc_test.go b/vc/vc_test.go index bf0f91c..8f6fa46 100644 --- a/vc/vc_test.go +++ b/vc/vc_test.go @@ -379,3 +379,21 @@ func TestCreateJWTVerifiableCredential(t *testing.T) { assert.Nil(t, claims[jwt.JwtIDKey]) }) } + +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) + + // no validity period is always true; includes missing IssuanceDate(.IsZero() == true) + assert.True(t, VerifiableCredential{}.ValidAt(time.Now())) + + // valid on bounds + 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{ExpirationDate: &hhh, ValidUntil: &lll}.ValidAt(hhh)) + assert.False(t, VerifiableCredential{ExpirationDate: &lll, ValidUntil: &hhh}.ValidAt(hhh)) +}