diff --git a/pkg/server/router/manifest_test.go b/pkg/server/router/manifest_test.go index 185ccc11e..2b92c7ed8 100644 --- a/pkg/server/router/manifest_test.go +++ b/pkg/server/router/manifest_test.go @@ -17,6 +17,9 @@ 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/manifest" + "github.com/tbd54566975/ssi-service/pkg/service/manifest/model" "github.com/tbd54566975/ssi-service/pkg/service/operation/storage" presmodel "github.com/tbd54566975/ssi-service/pkg/service/presentation/model" @@ -198,38 +201,51 @@ func TestManifestRouter(t *testing.T) { signed, err := signer.SignJSON(applicationRequest) assert.NoError(ttt, err) - submitApplicationRequest := SubmitApplicationRequest{ApplicationJWT: *signed} - sar, err := submitApplicationRequest.toServiceRequest() - assert.NoError(ttt, err) - createdApplicationResponseOp, err := manifestService.ProcessApplicationSubmission(context.Background(), *sar) - assert.NoError(ttt, err) - assert.False(ttt, createdApplicationResponseOp.Done) - - createdApplicationResponse, err := manifestService.ReviewApplication(context.Background(), model.ReviewApplicationRequest{ - ID: storage.StatusObjectID(createdApplicationResponseOp.ID), - Approved: true, - Reason: "ApprovalMan is here", - CredentialOverrides: map[string]model.CredentialOverride{ - "id1": { - Data: map[string]any{"licenseType": "Class D"}, - }, - "id2": { - Data: map[string]any{"licenseType": "Class D"}, - }, - }, - }) + // submit and review application, the key is valid + createdApplicationResponse, err := submitAndReviewApplication(signed, ttt, manifestService) assert.NoError(ttt, err) assert.NotEmpty(ttt, createdManifest) assert.NotEmpty(ttt, createdApplicationResponse.Response.ID) assert.NotEmpty(ttt, createdApplicationResponse.Response.Fulfillment) assert.Empty(ttt, createdApplicationResponse.Response.Denial) assert.Equal(ttt, len(createManifestRequest.OutputDescriptors), len(createdApplicationResponse.Credentials)) + + // attempt to submit and review application again, this time with the revoked key + err = keyStoreService.RevokeKey(context.Background(), keystore.RevokeKeyRequest{ID: kid}) + assert.NoError(ttt, err) + _, err = submitAndReviewApplication(signed, ttt, manifestService) + assert.Error(tt, err) + assert.ErrorContains(tt, err, "cannot use revoked key") }) }) }) } } +func submitAndReviewApplication(signed *keyaccess.JWT, ttt *testing.T, manifestService *manifest.Service) (*model.SubmitApplicationResponse, error) { + submitApplicationRequest := SubmitApplicationRequest{ApplicationJWT: *signed} + sar, err := submitApplicationRequest.toServiceRequest() + assert.NoError(ttt, err) + createdApplicationResponseOp, err := manifestService.ProcessApplicationSubmission(context.Background(), *sar) + assert.NoError(ttt, err) + assert.False(ttt, createdApplicationResponseOp.Done) + + createdApplicationResponse, err := manifestService.ReviewApplication(context.Background(), model.ReviewApplicationRequest{ + ID: storage.StatusObjectID(createdApplicationResponseOp.ID), + Approved: true, + Reason: "ApprovalMan is here", + CredentialOverrides: map[string]model.CredentialOverride{ + "id1": { + Data: map[string]any{"licenseType": "Class D"}, + }, + "id2": { + Data: map[string]any{"licenseType": "Class D"}, + }, + }, + }) + return createdApplicationResponse, err +} + func getValidManifestRequestRequest(issuerDID *did.CreateDIDResponse, kid string, createdManifest *model.CreateManifestResponse) model.CreateRequestRequest { return model.CreateRequestRequest{ ManifestRequest: model.Request{ diff --git a/pkg/server/router/schema_test.go b/pkg/server/router/schema_test.go index 790d6f0f6..77e91f92b 100644 --- a/pkg/server/router/schema_test.go +++ b/pkg/server/router/schema_test.go @@ -5,10 +5,14 @@ import ( "testing" credschema "github.com/TBD54566975/ssi-sdk/credential/schema" + "github.com/TBD54566975/ssi-sdk/crypto" + didsdk "github.com/TBD54566975/ssi-sdk/did" "github.com/stretchr/testify/assert" "github.com/tbd54566975/ssi-service/config" + "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/testutil" ) @@ -58,27 +62,7 @@ func TestSchemaRouter(t *testing.T) { assert.Contains(tt, err.Error(), "error getting schema") // create a schema - simpleSchema := map[string]any{ - "$schema": "https://json-schema.org/draft-07/schema", - "type": "object", - "properties": map[string]any{ - "credentialSubject": map[string]any{ - "type": "object", - "properties": map[string]any{ - "id": map[string]any{ - "type": "string", - }, - "firstName": map[string]any{ - "type": "string", - }, - "lastName": map[string]any{ - "type": "string", - }, - }, - "required": []any{"firstName", "lastName"}, - }, - }, - } + simpleSchema := getSimpleSchema() createdSchema, err := schemaService.CreateSchema(context.Background(), schema.CreateSchemaRequest{Issuer: "me", Name: "simple schema", Schema: simpleSchema}) assert.NoError(tt, err) assert.NotEmpty(tt, createdSchema) @@ -148,27 +132,7 @@ func TestSchemaSigning(t *testing.T) { assert.Equal(tt, framework.StatusReady, schemaService.Status().Status) // create a schema and don't sign it - simpleSchema := map[string]any{ - "$schema": "https://json-schema.org/draft-07/schema", - "type": "object", - "properties": map[string]any{ - "credentialSubject": map[string]any{ - "type": "object", - "properties": map[string]any{ - "id": map[string]any{ - "type": "string", - }, - "firstName": map[string]any{ - "type": "string", - }, - "lastName": map[string]any{ - "type": "string", - }, - }, - "required": []any{"firstName", "lastName"}, - }, - }, - } + simpleSchema := getSimpleSchema() createdSchema, err := schemaService.CreateSchema(context.Background(), schema.CreateSchemaRequest{Issuer: "me", Name: "simple schema", Schema: simpleSchema}) assert.NoError(tt, err) assert.NotEmpty(tt, createdSchema) @@ -176,5 +140,64 @@ func TestSchemaSigning(t *testing.T) { assert.Equal(tt, "simple schema", createdSchema.Schema.Name()) }) }) + + t.Run("Signing schema with revoked key test", func(tt *testing.T) { + db := test.ServiceStorage(t) + assert.NotEmpty(tt, db) + + serviceConfig := config.SchemaServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "schema"}} + keyStoreService := testKeyStoreService(tt, db) + didService := testDIDService(tt, db, keyStoreService) + schemaService, err := schema.NewSchemaService(serviceConfig, db, keyStoreService, didService.GetResolver()) + assert.NoError(tt, err) + assert.NotEmpty(tt, schemaService) + + // 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 := controllerDID.DID.VerificationMethod[0].ID + privateKey := "2dEPd7mA3aiuh2gky8tTPiCkyMwf8tBNUMZwRzeVxVJnJFGTbdLGUBcx51DCNyFWRjTG9bduvyLRStXSCDMFXULY" + + err = keyStoreService.StoreKey(context.Background(), keystore.StoreKeyRequest{ID: keyID, Type: crypto.Ed25519, Controller: didID, PrivateKeyBase58: privateKey}) + assert.NoError(tt, err) + + // Revoke the key + err = keyStoreService.RevokeKey(context.Background(), keystore.RevokeKeyRequest{ID: keyID}) + assert.NoError(tt, err) + + // create a schema with the revoked key, it fails + _, err = schemaService.CreateSchema(context.Background(), schema.CreateSchemaRequest{Issuer: controllerDID.DID.ID, Name: "schema (revoked key)", Schema: getEmailSchema(), FullyQualifiedVerificationMethodID: keyID}) + assert.Error(tt, err) + assert.ErrorContains(tt, err, "cannot use revoked key") + }) + } +} + +func getSimpleSchema() map[string]any { + simpleSchema := map[string]any{ + "$schema": "https://json-schema.org/draft-07/schema", + "type": "object", + "properties": map[string]any{ + "credentialSubject": map[string]any{ + "type": "object", + "properties": map[string]any{ + "id": map[string]any{ + "type": "string", + }, + "firstName": map[string]any{ + "type": "string", + }, + "lastName": map[string]any{ + "type": "string", + }, + }, + "required": []any{"firstName", "lastName"}, + }, + }, } + return simpleSchema } diff --git a/pkg/service/keystore/service.go b/pkg/service/keystore/service.go index 2975a88d6..a773de0f2 100644 --- a/pkg/service/keystore/service.go +++ b/pkg/service/keystore/service.go @@ -204,6 +204,9 @@ func (s Service) Sign(ctx context.Context, keyID string, data any) (*keyaccess.J if err != nil { return nil, sdkutil.LoggingErrorMsgf(err, "getting key with keyID<%s>", keyID) } + if gotKey.Revoked { + return nil, sdkutil.LoggingNewErrorf("cannot use revoked key<%s>", gotKey.ID) + } keyAccess, err := keyaccess.NewJWKKeyAccess(gotKey.Controller, gotKey.ID, gotKey.Key) if err != nil { return nil, sdkutil.LoggingErrorMsgf(err, "creating key access for keyID<%s>", keyID) diff --git a/pkg/service/keystore/service_test.go b/pkg/service/keystore/service_test.go index 4595aefff..16c28ba58 100644 --- a/pkg/service/keystore/service_test.go +++ b/pkg/service/keystore/service_test.go @@ -152,6 +152,11 @@ func TestRevokeKey(t *testing.T) { assert.Equal(t, privKey, keyResponse.Key) assert.True(t, keyResponse.Revoked) assert.Equal(t, "2023-06-23T00:00:00Z", keyResponse.RevokedAt) + + // attempt to "Sign()" with the revoked key, ensure it is prohibited + _, err = keyStore.Sign(context.Background(), keyID, "sampleDataAsString") + assert.Error(t, err) + assert.ErrorContains(t, err, "cannot use revoked key") } func createKeyStoreService(t *testing.T) (*Service, error) { diff --git a/pkg/service/manifest/response.go b/pkg/service/manifest/response.go index 982279240..04d5b3815 100644 --- a/pkg/service/manifest/response.go +++ b/pkg/service/manifest/response.go @@ -35,6 +35,9 @@ func (s Service) signCredentialResponse(ctx context.Context, keyStoreID string, if err != nil { return nil, sdkutil.LoggingErrorMsgf(err, "getting key for signing response with key<%s>", keyStoreID) } + if gotKey.Revoked { + return nil, sdkutil.LoggingNewErrorf("cannot use revoked key<%s>", gotKey.ID) + } keyAccess, err := keyaccess.NewJWKKeyAccess(gotKey.Controller, gotKey.ID, gotKey.Key) if err != nil { return nil, sdkutil.LoggingErrorMsgf(err, "creating key access for signing response with key<%s>", gotKey.ID) diff --git a/pkg/service/schema/service.go b/pkg/service/schema/service.go index d41b4d793..4ab277158 100644 --- a/pkg/service/schema/service.go +++ b/pkg/service/schema/service.go @@ -189,6 +189,9 @@ func (s Service) signCredentialSchema(ctx context.Context, cred credential.Verif if gotKey.Controller != issuer { return nil, sdkutil.LoggingNewErrorf("key controller<%s> does not match credential issuer<%s> for key<%s>", gotKey.Controller, issuer, fullyQualifiedVerificationMethodID) } + if gotKey.Revoked { + return nil, sdkutil.LoggingNewErrorf("cannot use revoked key<%s>", gotKey.ID) + } keyAccess, err := keyaccess.NewJWKKeyAccess(fullyQualifiedVerificationMethodID, gotKey.ID, gotKey.Key) if err != nil { return nil, errors.Wrapf(err, "creating key access for signing credential schema with key<%s>", gotKey.ID)