Skip to content

Commit

Permalink
add key revocation check when creating a credential, expose key revoc…
Browse files Browse the repository at this point in the history
…ation endpoint, tests
  • Loading branch information
vlad-tim committed Jun 23, 2023
1 parent 0e4d0fc commit d7b70d6
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 26 deletions.
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 {
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
91 changes: 67 additions & 24 deletions pkg/service/keystore/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,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.NotEmpty(t, 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 +185,5 @@ 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)

// 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
}

0 comments on commit d7b70d6

Please sign in to comment.