From cd573eb009a9ee802870ab6b3eb05e3b4e4d5154 Mon Sep 17 00:00:00 2001 From: Gerard Snaauw <33763579+gerardsn@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:53:06 +0100 Subject: [PATCH] Revert validFrom and validUntil (#111) * Revert "add clock skew to ValidAt (#100)" This reverts commit 6d13fd4c647507beb2cfaef528e7cf3784def881. * Revert "Add validFrom and validUntil aliases for issuanceDate and expirationDate (#97)" This reverts commit c2e3cb83d49c66e83e5d557c35151b17179afe7f. * keep VC.ValidAt() --- vc/vc.go | 48 ++++++++--------------------- vc/vc_test.go | 85 ++++++++++++++++++--------------------------------- 2 files changed, 41 insertions(+), 92 deletions(-) diff --git a/vc/vc.go b/vc/vc.go index da9786a..67bda75 100644 --- a/vc/vc.go +++ b/vc/vc.go @@ -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() != "" { @@ -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. @@ -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 } @@ -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 } diff --git a/vc/vc_test.go b/vc/vc_test.go index 10727e6..0a27bd9 100644 --- a/vc/vc_test.go +++ b/vc/vc_test.go @@ -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)) }) }) @@ -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{}{ @@ -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]) @@ -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)) }