Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add key revocation checks, for credentials creation #546

Merged
merged 2 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ When you're ready you may:

| Requirement | Tested Version | Installation Instructions |
|----------------|----------------|---------------------------------------------------------------------------------------------------|
| Go | 1.20.5 | [go.dev](https://go.dev/doc/tutorial/compile-install) |
| Go | 1.20.5 | [go.dev](https://go.dev/doc/install) |
| Mage | 1.13.0-6 | [magefile.org](https://magefile.org/) |
| golangci-lint | 1.52.2 | [golangci-lint.run](https://golangci-lint.run/usage/install/#local-installation) |

Expand Down
64 changes: 64 additions & 0 deletions pkg/server/router/credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/tbd54566975/ssi-service/pkg/service/credential"
"github.com/tbd54566975/ssi-service/pkg/service/did"
"github.com/tbd54566975/ssi-service/pkg/service/framework"
"github.com/tbd54566975/ssi-service/pkg/service/keystore"
"github.com/tbd54566975/ssi-service/pkg/service/schema"
"github.com/tbd54566975/ssi-service/pkg/storage"
"github.com/tbd54566975/ssi-service/pkg/testutil"
Expand Down Expand Up @@ -199,6 +200,69 @@ func TestCredentialRouter(t *testing.T) {
assert.Contains(tt, err.Error(), fmt.Sprintf("credential not found with id: %s", cred.ID))
})

t.Run("Credential Service Test Revoked Key", func(tt *testing.T) {
s := test.ServiceStorage(t)
assert.NotEmpty(tt, s)

// Initialize services
serviceConfig := config.CredentialServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "credential"}}
keyStoreService := testKeyStoreService(tt, s)
didService := testDIDService(tt, s, keyStoreService)
schemaService := testSchemaService(tt, s, keyStoreService, didService)
credService, err := credential.NewCredentialService(serviceConfig, s, keyStoreService, didService.GetResolver(), schemaService)
assert.NoError(tt, err)
assert.NotEmpty(tt, credService)

// Create a DID
controllerDID, err := didService.CreateDIDByMethod(context.Background(), did.CreateDIDRequest{Method: didsdk.KeyMethod, KeyType: crypto.Ed25519})
assert.NoError(tt, err)
assert.NotEmpty(tt, controllerDID)
didID := controllerDID.DID.ID

// Create a key controlled by the DID
keyID := "MyKeyId"
privateKey := "2dEPd7mA3aiuh2gky8tTPiCkyMwf8tBNUMZwRzeVxVJnJFGTbdLGUBcx51DCNyFWRjTG9bduvyLRStXSCDMFXULY"

err = keyStoreService.StoreKey(context.Background(), keystore.StoreKeyRequest{ID: keyID, Type: crypto.Ed25519, Controller: didID, PrivateKeyBase58: privateKey})
assert.NoError(tt, err)

// Create a crendential
subject := "did:test:42"
createdCred, err := credService.CreateCredential(context.Background(), credential.CreateCredentialRequest{
Issuer: didID,
IssuerKID: keyID,
Subject: subject,
Data: map[string]any{
"firstName": "Satoshi",
"lastName": "Nakamoto",
},
Expiry: time.Now().Add(24 * time.Hour).Format(time.RFC3339),
})
assert.NoError(tt, err)
assert.NotEmpty(tt, createdCred)
assert.NotEmpty(tt, createdCred.CredentialJWT)

// Revoke the key
err = keyStoreService.RevokeKey(context.Background(), keystore.RevokeKeyRequest{ID: keyID})
assert.NoError(tt, err)

// Create a crendential with the revoked key, it fails
subject = "did:test:43"
createdCred, err = credService.CreateCredential(context.Background(), credential.CreateCredentialRequest{
Issuer: didID,
IssuerKID: keyID,
Subject: subject,
Data: map[string]any{
"firstName": "John",
"lastName": "Doe",
},
Expiry: time.Now().Add(24 * time.Hour).Format(time.RFC3339),
})
assert.Empty(tt, createdCred)
assert.Error(tt, err)
assert.ErrorContains(tt, err, "cannot use revoked key")
})

t.Run("Credential Status List Test", func(tt *testing.T) {
s := test.ServiceStorage(t)
assert.NotEmpty(tt, s)
Expand Down
1 change: 1 addition & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ func KeyStoreAPI(rg *gin.RouterGroup, service svcframework.Service) (err error)
keyStoreAPI := rg.Group(KeyStorePrefix)
keyStoreAPI.PUT("", keyStoreRouter.StoreKey)
keyStoreAPI.GET("/:id", keyStoreRouter.GetKeyDetails)
keyStoreAPI.DELETE("/:id", keyStoreRouter.RevokeKey)
return
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/service/credential/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ func (s Service) signCredentialJWT(ctx context.Context, issuerKID string, cred c
if gotKey.Controller != cred.Issuer.(string) {
return nil, sdkutil.LoggingNewErrorf("key controller<%s> does not match credential issuer<%s> for key<%s>", gotKey.Controller, cred.Issuer, issuerKID)
}
if gotKey.Revoked {
vlad-tim marked this conversation as resolved.
Show resolved Hide resolved
return nil, sdkutil.LoggingNewErrorf("cannot use revoked key<%s>", gotKey.ID)
}
keyAccess, err := keyaccess.NewJWKKeyAccess(issuerKID, gotKey.ID, gotKey.Key)
if err != nil {
return nil, errors.Wrapf(err, "creating key access for signing credential with key<%s>", gotKey.ID)
Expand Down
1 change: 0 additions & 1 deletion pkg/service/keystore/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ func (s Service) GetKey(ctx context.Context, request GetKeyRequest) (*GetKeyResp
}, nil
}

// TODO(gabe): expose this endpoint https://github.com/TBD54566975/ssi-service/issues/451
func (s Service) RevokeKey(ctx context.Context, request RevokeKeyRequest) error {
logrus.Debugf("revoking key: %+v", request)

Expand Down
94 changes: 72 additions & 22 deletions pkg/service/keystore/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"context"
"os"
"testing"
"time"

"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/TBD54566975/ssi-sdk/crypto/jwx"
"github.com/benbjohnson/clock"
"github.com/mr-tron/base58"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -94,6 +96,72 @@ func TestEncryptDecryptAllKeyTypes(t *testing.T) {
}

func TestStoreAndGetKey(t *testing.T) {
keyStore, err := createKeyStoreService(t)
assert.NoError(t, err)
assert.NotEmpty(t, keyStore)

// store the key
_, privKey, err := crypto.GenerateEd25519Key()
assert.NoError(t, err)
err = keyStore.StoreKey(context.Background(), StoreKeyRequest{
ID: "test-id",
Type: crypto.Ed25519,
Controller: "test-controller",
PrivateKeyBase58: base58.Encode(privKey),
})
assert.NoError(t, err)

// get it back
keyResponse, err := keyStore.GetKey(context.Background(), GetKeyRequest{ID: "test-id"})
assert.NoError(t, err)
assert.NotEmpty(t, keyResponse)
assert.Equal(t, privKey, keyResponse.Key)

// make sure can create a signer properly
signer, err := jwx.NewJWXSigner("test-id", "kid", keyResponse.Key)
assert.NoError(t, err)
assert.NotEmpty(t, signer)
}

func TestRevokeKey(t *testing.T) {
keyStore, err := createKeyStoreService(t)
assert.NoError(t, err)
assert.NotEmpty(t, keyStore)

// store the key
_, privKey, err := crypto.GenerateEd25519Key()
assert.NoError(t, err)
keyID := "test-revocation-id"
err = keyStore.StoreKey(context.Background(), StoreKeyRequest{
ID: keyID,
Type: crypto.Ed25519,
Controller: "test-revocation-controller",
PrivateKeyBase58: base58.Encode(privKey),
})
assert.NoError(t, err)

// get it back
keyResponse, err := keyStore.GetKey(context.Background(), GetKeyRequest{ID: keyID})
assert.NoError(t, err)
assert.NotEmpty(t, keyResponse)
assert.Equal(t, privKey, keyResponse.Key)
assert.False(t, keyResponse.Revoked)
assert.Empty(t, keyResponse.RevokedAt)

// revoke the key
err = keyStore.RevokeKey(context.Background(), RevokeKeyRequest{ID: keyID})
assert.NoError(t, err)

// get the key after revocation
keyResponse, err = keyStore.GetKey(context.Background(), GetKeyRequest{ID: keyID})
assert.NoError(t, err)
assert.NotEmpty(t, keyResponse)
assert.Equal(t, privKey, keyResponse.Key)
assert.True(t, keyResponse.Revoked)
assert.Equal(t, "2023-06-23T00:00:00Z", keyResponse.RevokedAt)
}

func createKeyStoreService(t *testing.T) (*Service, error) {
file, err := os.CreateTemp("", "bolt")
require.NoError(t, err)
name := file.Name()
Expand All @@ -119,28 +187,10 @@ func TestStoreAndGetKey(t *testing.T) {
MasterKeyPassword: "test-password",
},
s)
assert.NoError(t, err)
assert.NotEmpty(t, keyStore)

// store the key
_, privKey, err := crypto.GenerateEd25519Key()
assert.NoError(t, err)
err = keyStore.StoreKey(context.Background(), StoreKeyRequest{
ID: "test-id",
Type: crypto.Ed25519,
Controller: "test-controller",
PrivateKeyBase58: base58.Encode(privKey),
})
assert.NoError(t, err)

// get it back
keyResponse, err := keyStore.GetKey(context.Background(), GetKeyRequest{ID: "test-id"})
assert.NoError(t, err)
assert.NotEmpty(t, keyResponse)
assert.Equal(t, privKey, keyResponse.Key)
mockClock := clock.NewMock()
mockClock.Set(time.Date(2023, 06, 23, 0, 0, 0, 0, time.UTC))
keyStore.storage.Clock = mockClock

// make sure can create a signer properly
signer, err := jwx.NewJWXSigner("test-id", "kid", keyResponse.Key)
assert.NoError(t, err)
assert.NotEmpty(t, signer)
return keyStore, err
}
5 changes: 4 additions & 1 deletion pkg/service/keystore/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/TBD54566975/ssi-sdk/crypto/jwx"
sdkutil "github.com/TBD54566975/ssi-sdk/util"
"github.com/benbjohnson/clock"
"github.com/goccy/go-json"
"github.com/google/tink/go/aead"
"github.com/google/tink/go/core/registry"
Expand Down Expand Up @@ -63,13 +64,15 @@ type Storage struct {
db storage.ServiceStorage
encrypter Encrypter
decrypter Decrypter
Clock clock.Clock
}

func NewKeyStoreStorage(db storage.ServiceStorage, e Encrypter, d Decrypter) (*Storage, error) {
s := &Storage{
db: db,
encrypter: e,
decrypter: d,
Clock: clock.New(),
}

return s, nil
Expand Down Expand Up @@ -290,7 +293,7 @@ func (kss *Storage) RevokeKey(ctx context.Context, id string) error {
}

key.Revoked = true
key.RevokedAt = time.Now().UTC().Format(time.RFC3339)
key.RevokedAt = kss.Clock.Now().Format(time.RFC3339)
return kss.StoreKey(ctx, *key)
}

Expand Down