From 318ae8d04e18066eb255fbba049e37cb757c4035 Mon Sep 17 00:00:00 2001 From: Andres Uribe Gonzalez Date: Mon, 28 Nov 2022 12:39:55 -0500 Subject: [PATCH] Submission implementation with test --- pkg/server/router/operation.go | 12 -- pkg/server/router/presentation.go | 57 +++++++- pkg/server/router/presentation_test.go | 6 +- pkg/server/router/testutils_test.go | 6 +- pkg/server/server_presentation_test.go | 153 +++++++++++++++++++- pkg/server/server_test.go | 8 +- pkg/service/operation/model.go | 12 ++ pkg/service/presentation/model.go | 8 +- pkg/service/presentation/service.go | 90 ++++++++++-- pkg/service/presentation/storage/bolt.go | 4 +- pkg/service/presentation/storage/storage.go | 3 + pkg/service/service.go | 2 +- sip/sips/sip6/README.md | 2 +- 13 files changed, 327 insertions(+), 36 deletions(-) create mode 100644 pkg/service/operation/model.go diff --git a/pkg/server/router/operation.go b/pkg/server/router/operation.go index c1c85b1a2..54741f134 100644 --- a/pkg/server/router/operation.go +++ b/pkg/server/router/operation.go @@ -3,7 +3,6 @@ package router import ( "context" "fmt" - "github.com/TBD54566975/ssi-sdk/credential/exchange" "github.com/pkg/errors" svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework" "github.com/tbd54566975/ssi-service/pkg/service/operation" @@ -25,17 +24,6 @@ func NewOperationRouter(s svcframework.Service) (*OperationRouter, error) { return &OperationRouter{service: service}, nil } -type Operation struct { - ID string `json:"id"` - Done bool `json:"bool"` - Result *OperationResult `json:"result"` -} - -type OperationResult struct { - Error string `json:"error"` - Response exchange.PresentationSubmission `json:"response"` -} - // GetOperation godoc // @Summary Get an operation // @Description Get operation by its ID diff --git a/pkg/server/router/presentation.go b/pkg/server/router/presentation.go index 536309f22..0f8502fbe 100644 --- a/pkg/server/router/presentation.go +++ b/pkg/server/router/presentation.go @@ -4,9 +4,13 @@ import ( "context" "fmt" "github.com/TBD54566975/ssi-sdk/credential/exchange" + "github.com/TBD54566975/ssi-sdk/credential/signing" + "github.com/goccy/go-json" "github.com/pkg/errors" "github.com/sirupsen/logrus" + credint "github.com/tbd54566975/ssi-service/internal/credential" "github.com/tbd54566975/ssi-service/internal/keyaccess" + "github.com/tbd54566975/ssi-service/internal/util" "github.com/tbd54566975/ssi-service/pkg/server/framework" svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework" "github.com/tbd54566975/ssi-service/pkg/service/presentation" @@ -179,6 +183,40 @@ type CreateSubmissionRequest struct { SubmissionJWT keyaccess.JWT `json:"submissionJwt" validate:"required"` } +func (r CreateSubmissionRequest) ToServiceRequest() (*presentation.CreateSubmissionRequest, error) { + sdkVp, err := signing.ParseVerifiablePresentationFromJWT(r.SubmissionJWT.String()) + if err != nil { + return nil, errors.Wrap(err, "parsing presentation from jwt") + } + if err := sdkVp.IsValid(); err != nil { + return nil, errors.Wrap(err, "verifying vp validity") + } + + submissionData, err := json.Marshal(sdkVp.PresentationSubmission) + if err != nil { + return nil, errors.Wrap(err, "marshalling presentation_submission") + } + var s exchange.PresentationSubmission + if err := json.Unmarshal(submissionData, &s); err != nil { + return nil, errors.Wrap(err, "unmarshalling presentation submission") + } + if err := s.IsValid(); err != nil { + return nil, errors.Wrap(err, "verifying submission validity") + } + sdkVp.PresentationSubmission = s + + credContainers, err := credint.NewCredentialContainerFromArray(sdkVp.VerifiableCredential) + if err != nil { + return nil, errors.Wrap(err, "parsing verifiable credential array") + } + + return &presentation.CreateSubmissionRequest{ + Presentation: *sdkVp, + SubmissionJWT: r.SubmissionJWT, + Submission: s, + Credentials: credContainers}, nil +} + type Operation struct { ID string `json:"id"` Done bool `json:"done"` @@ -202,7 +240,24 @@ type OperationResult struct { // @Failure 500 {string} string "Internal server error" // @Router /v1/presentations/submissions [put] func (pr PresentationRouter) CreateSubmission(ctx context.Context, w http.ResponseWriter, r *http.Request) error { - var resp Operation + var request CreateSubmissionRequest + if err := framework.Decode(r, &request); err != nil { + return framework.NewRequestError(util.LoggingErrorMsg(err, "invalid create submission request"), http.StatusBadRequest) + } + + req, err := request.ToServiceRequest() + if err != nil { + return framework.NewRequestError(util.LoggingErrorMsg(err, "invalid create submission request"), http.StatusBadRequest) + } + + operation, err := pr.service.CreateSubmission(*req) + if err != nil { + return framework.NewRequestError(util.LoggingErrorMsg(err, "cannot create submission"), http.StatusInternalServerError) + } + + resp := Operation{ + ID: operation.ID, + } return framework.Respond(ctx, w, resp, http.StatusCreated) } diff --git a/pkg/server/router/presentation_test.go b/pkg/server/router/presentation_test.go index dc9acb2b5..3f51be5ed 100644 --- a/pkg/server/router/presentation_test.go +++ b/pkg/server/router/presentation_test.go @@ -35,7 +35,11 @@ func TestPresentationDefinitionService(t *testing.T) { s, err := storage.NewStorage(storage.Bolt) assert.NoError(t, err) - service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s) + keyStoreService := testKeyStoreService(t, s) + didService := testDIDService(t, s, keyStoreService) + schemaService := testSchemaService(t, s, keyStoreService, didService) + + service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s, didService.GetResolver(), schemaService) assert.NoError(t, err) t.Run("Create returns the created definition", func(t *testing.T) { diff --git a/pkg/server/router/testutils_test.go b/pkg/server/router/testutils_test.go index 90bcd5430..6bee35610 100644 --- a/pkg/server/router/testutils_test.go +++ b/pkg/server/router/testutils_test.go @@ -14,7 +14,7 @@ import ( "github.com/tbd54566975/ssi-service/pkg/storage" ) -func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service { +func testKeyStoreService(t *testing.T, db storage.ServiceStorage) *keystore.Service { serviceConfig := config.KeyStoreServiceConfig{ServiceKeyPassword: "test-password"} // create a keystore service keystoreService, err := keystore.NewKeyStoreService(serviceConfig, db) @@ -23,7 +23,7 @@ func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service { return keystoreService } -func testDIDService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service) *did.Service { +func testDIDService(t *testing.T, db storage.ServiceStorage, keyStore *keystore.Service) *did.Service { serviceConfig := config.DIDServiceConfig{ BaseServiceConfig: &config.BaseServiceConfig{ Name: "did", @@ -38,7 +38,7 @@ func testDIDService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service return didService } -func testSchemaService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service, did *did.Service) *schema.Service { +func testSchemaService(t *testing.T, db storage.ServiceStorage, keyStore *keystore.Service, did *did.Service) *schema.Service { serviceConfig := config.SchemaServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "schema"}} // create a schema service schemaService, err := schema.NewSchemaService(serviceConfig, db, keyStore, did.GetResolver()) diff --git a/pkg/server/server_presentation_test.go b/pkg/server/server_presentation_test.go index 6e3422fc0..e6537078e 100644 --- a/pkg/server/server_presentation_test.go +++ b/pkg/server/server_presentation_test.go @@ -2,13 +2,17 @@ package server import ( "fmt" + "github.com/TBD54566975/ssi-sdk/credential" "github.com/TBD54566975/ssi-sdk/credential/exchange" + "github.com/TBD54566975/ssi-sdk/credential/signing" "github.com/TBD54566975/ssi-sdk/crypto" "github.com/goccy/go-json" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tbd54566975/ssi-service/config" + "github.com/tbd54566975/ssi-service/internal/keyaccess" "github.com/tbd54566975/ssi-service/pkg/server/router" "github.com/tbd54566975/ssi-service/pkg/service/presentation" "github.com/tbd54566975/ssi-service/pkg/storage" @@ -40,7 +44,11 @@ func TestPresentationAPI(t *testing.T) { s, err := storage.NewStorage(storage.Bolt) assert.NoError(t, err) - service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s) + keyStoreService := testKeyStoreService(t, s) + didService := testDIDService(t, s, keyStoreService) + schemaService := testSchemaService(t, s, keyStoreService, didService) + + service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s, didService.GetResolver(), schemaService) assert.NoError(t, err) pRouter, err := router.NewPresentationRouter(service) @@ -136,4 +144,147 @@ func TestPresentationAPI(t *testing.T) { assert.Error(t, pRouter.DeletePresentationDefinition(newRequestContext(), w, req)) w.Flush() }) + + t.Run("full flow of presentation", func(t *testing.T) { + + definition := createPresentationDefinition(t, pRouter) + vc := credential.VerifiableCredential{ + Context: []string{credential.VerifiableCredentialsLinkedDataContext}, + ID: "7035a7ec-66c8-4aec-9191-a34e8cf1e82b", + Type: []string{credential.VerifiablePresentationType}, + Issuer: "did:key:z4oJ8bFEFv7E3omhuK5LrAtL29Nmd8heBey9HtJCSvodSb7nrfaMrd6zb7fjYSRxrfSgBSDeM6Bs59KRKFgXSDWJcfcjs", + IssuanceDate: "2022-11-07T21:28:57Z", + ExpirationDate: "2051-10-05T14:48:00.000Z", + CredentialStatus: nil, + CredentialSubject: credential.CredentialSubject{ + "additionalName": "Mclovin", + "dateOfBirth": "1987-01-02", + "familyName": "Andres", + "givenName": "Uribe", + "id": "did:web:andresuribe.com", + }, + CredentialSchema: nil, + RefreshService: nil, + TermsOfUse: nil, + Evidence: nil, + Proof: nil, + } + + signer0 := getTestVectorKey0Signer(t) + vcData, err := signing.SignVerifiableCredentialJWT(signer0, vc) + assert.NoError(t, err) + ps := exchange.PresentationSubmission{ + ID: "a30e3b91-fb77-4d22-95fa-871689c322e2", + DefinitionID: definition.PresentationDefinition.ID, + DescriptorMap: []exchange.SubmissionDescriptor{ + { + ID: "wa_driver_license", + Format: string(exchange.JWTVPTarget), + Path: "$.verifiableCredential[0]", + PathNested: nil, + }, + }, + } + vp := credential.VerifiablePresentation{ + Context: []string{credential.VerifiableCredentialsLinkedDataContext}, + ID: "a9b575c7-bac2-47e7-a925-c432815ebb4c", + Holder: "did:key:z4oJ8eRi73fvkrXBgqTHZRTropESXLc7Vet8XpJrGUSBZAT2UHvQBYpBEPdAUiyKBi2XC2iFjgtn5Gw2Qd4WXHyj1LxjU", + Type: []string{credential.VerifiablePresentationType}, + PresentationSubmission: ps, + VerifiableCredential: []interface{}{keyaccess.JWT(vcData)}, + Proof: nil, + } + + signer1 := getTestVectorKey1Signer(t) + signed, err := signing.SignVerifiablePresentationJWT(signer1, vp) + assert.NoError(t, err) + + request := router.CreateSubmissionRequest{SubmissionJWT: keyaccess.JWT(signed)} + + value := newRequestValue(t, request) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/presentations/submissions", value) + w := httptest.NewRecorder() + + err = pRouter.CreateSubmission(newRequestContext(), w, req) + + require.NoError(t, err) + var resp router.Operation + assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) + assert.Contains(t, resp.ID, "presentations/submissions/") + assert.False(t, resp.Done) + assert.Zero(t, resp.Result) + }) +} + +func createPresentationDefinition(t *testing.T, pRouter *router.PresentationRouter) router.CreatePresentationDefinitionResponse { + request := router.CreatePresentationDefinitionRequest{ + Name: "name", + Purpose: "purpose", + Format: nil, + SubmissionRequirements: nil, + InputDescriptors: []exchange.InputDescriptor{ + { + ID: "wa_driver_license", + Name: "washington state business license", + Purpose: "some testing stuff", + Format: nil, + Constraints: &exchange.Constraints{ + Fields: []exchange.Field{ + { + ID: "date_of_birth", + Path: []string{ + "$.credentialSubject.dateOfBirth", + "$.credentialSubject.dob", + "$.vc.credentialSubject.dateOfBirth", + "$.vc.credentialSubject.dob", + }, + }, + }, + }, + }, + }, + } + value := newRequestValue(t, request) + req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/presentations/definitions", value) + w := httptest.NewRecorder() + + assert.NoError(t, pRouter.CreatePresentationDefinition(newRequestContext(), w, req)) + var resp router.CreatePresentationDefinitionResponse + assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) + return resp +} + +func getTestVectorKey0Signer(t *testing.T) crypto.JWTSigner { + // The corresponding JWT is below: + // eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6a2V5Ono0b0o4YkZFRnY3RTNvbWh1SzVMckF0TDI5Tm1kOGhlQmV5OUh0SkNTdm9kU2I3bnJmYU1yZDZ6YjdmallTUnhyZlNnQlNEZU02QnM1OUtSS0ZnWFNEV0pjZmNqcyIsImp0aSI6IjcwMzVhN2VjLTY2YzgtNGFlYy05MTkxLWEzNGU4Y2YxZTgyYiIsIm5iZiI6MTY2Nzg1NjUzNywic3ViIjoiZGlkOmtleTp6NG9KOGVSaTczZnZrclhCZ3FUSFpSVHJvcEVTWExjN1ZldDhYcEpyR1VTQlpBVDJVSHZRQllwQkVQZEFVaXlLQmkyWEMyaUZqZ3RuNUd3MlFkNFdYSHlqMUx4alUiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJpZCI6IjcwMzVhN2VjLTY2YzgtNGFlYy05MTkxLWEzNGU4Y2YxZTgyYiIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6NG9KOGJGRUZ2N0Uzb21odUs1THJBdEwyOU5tZDhoZUJleTlIdEpDU3ZvZFNiN25yZmFNcmQ2emI3ZmpZU1J4cmZTZ0JTRGVNNkJzNTlLUktGZ1hTRFdKY2ZjanMiLCJpc3N1YW5jZURhdGUiOiIyMDIyLTExLTA3VDIxOjI4OjU3WiIsImV4cGlyYXRpb25EYXRlIjoiMjA1MS0xMC0wNVQxNDo0ODowMC4wMDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiYWRkaXRpb25hbE5hbWUiOiJNY2xvdmluIiwiZGF0ZU9mQmlydGgiOiIxOTg3LTAxLTAyIiwiZmFtaWx5TmFtZSI6IkFuZHJlcyIsImdpdmVuTmFtZSI6IlVyaWJlIiwiaWQiOiJkaWQ6a2V5Ono0b0o4ZVJpNzNmdmtyWEJncVRIWlJUcm9wRVNYTGM3VmV0OFhwSnJHVVNCWkFUMlVIdlFCWXBCRVBkQVVpeUtCaTJYQzJpRmpndG41R3cyUWQ0V1hIeWoxTHhqVSJ9fX0.mtrK1nDLL1Ly6iPwIpgpMbLFtoHcH52OQLbBSF-jVK7UHFZdKb8v4e_27uKZO0uszRm11kRV1NnDxoRJNNjFbw + + // AKA the issuers key + // AKA did:key:z4oJ8bFEFv7E3omhuK5LrAtL29Nmd8heBey9HtJCSvodSb7nrfaMrd6zb7fjYSRxrfSgBSDeM6Bs59KRKFgXSDWJcfcjs + knownJWK := crypto.PrivateKeyJWK{ + KTY: "EC", + CRV: "P-256", + X: "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74", + Y: "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI", + D: "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk", + } + + signer, err := crypto.NewJWTSignerFromJWK(knownJWK.KID, knownJWK) + assert.NoError(t, err) + return *signer +} + +func getTestVectorKey1Signer(t *testing.T) crypto.JWTSigner { + // AKA the submitter of the presentation + // AKA did:key:z4oJ8eRi73fvkrXBgqTHZRTropESXLc7Vet8XpJrGUSBZAT2UHvQBYpBEPdAUiyKBi2XC2iFjgtn5Gw2Qd4WXHyj1LxjU + knownJWK := crypto.PrivateKeyJWK{ + KTY: "EC", + CRV: "P-256", + X: "6HEz8SLP7NgHPGp0bElryiD7u3_cO1EmX-ngsV_yLsI", + Y: "QlIYaYyDLxLkybDan9LOSkfGvjzZsrdgAb_nQr_Li5M", + D: "7m6c2Axy9OWi7-d9hFVhmMe22vQTfQDL_pG-3WFsjzc", + } + + signer, err := crypto.NewJWTSignerFromJWK(knownJWK.KID, knownJWK) + assert.NoError(t, err) + return *signer } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 0aa39486a..ed449ee3f 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -195,7 +195,7 @@ func testKeyStore(t *testing.T, bolt *storage.BoltDB) (*router.KeyStoreRouter, * return keyStoreRouter, keyStoreService } -func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service { +func testKeyStoreService(t *testing.T, db storage.ServiceStorage) *keystore.Service { serviceConfig := config.KeyStoreServiceConfig{ BaseServiceConfig: &config.BaseServiceConfig{Name: "test-keystore"}, ServiceKeyPassword: "test-password", @@ -208,7 +208,7 @@ func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service { return keystoreService } -func testDIDService(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Service) *did.Service { +func testDIDService(t *testing.T, bolt storage.ServiceStorage, keyStore *keystore.Service) *did.Service { serviceConfig := config.DIDServiceConfig{ BaseServiceConfig: &config.BaseServiceConfig{Name: "test-did"}, Methods: []string{"key"}, @@ -232,7 +232,7 @@ func testDIDRouter(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Servic return didRouter } -func testSchemaService(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Service, did *did.Service) *schema.Service { +func testSchemaService(t *testing.T, bolt storage.ServiceStorage, keyStore *keystore.Service, did *did.Service) *schema.Service { schemaService, err := schema.NewSchemaService(config.SchemaServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "test-schema"}}, bolt, keyStore, did.GetResolver()) require.NoError(t, err) require.NotEmpty(t, schemaService) @@ -249,7 +249,7 @@ func testSchemaRouter(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Ser return schemaRouter } -func testCredentialService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service, did *did.Service, schema *schema.Service) *credential.Service { +func testCredentialService(t *testing.T, db storage.ServiceStorage, keyStore *keystore.Service, did *did.Service, schema *schema.Service) *credential.Service { serviceConfig := config.CredentialServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "credential"}} // create a credential service diff --git a/pkg/service/operation/model.go b/pkg/service/operation/model.go new file mode 100644 index 000000000..4dc1f35cb --- /dev/null +++ b/pkg/service/operation/model.go @@ -0,0 +1,12 @@ +package operation + +type Result struct { + Error string `json:"error,omitempty"` + Response interface{} `json:"response,omitempty"` +} + +type Operation struct { + ID string `json:"json"` + Done bool `json:"done"` + Result Result `json:"result,omitempty"` +} diff --git a/pkg/service/presentation/model.go b/pkg/service/presentation/model.go index 784effa94..9d540bc98 100644 --- a/pkg/service/presentation/model.go +++ b/pkg/service/presentation/model.go @@ -1,8 +1,11 @@ package presentation import ( + credsdk "github.com/TBD54566975/ssi-sdk/credential" "github.com/TBD54566975/ssi-sdk/credential/exchange" "github.com/TBD54566975/ssi-sdk/util" + "github.com/tbd54566975/ssi-service/internal/credential" + "github.com/tbd54566975/ssi-service/internal/keyaccess" ) type CreatePresentationDefinitionRequest struct { @@ -31,7 +34,10 @@ type DeletePresentationDefinitionRequest struct { } type CreateSubmissionRequest struct { - Submission exchange.PresentationSubmission `json:"submission" validate:"required"` + Presentation credsdk.VerifiablePresentation `json:"presentation" validate:"required"` + SubmissionJWT keyaccess.JWT `json:"submissionJwt,omitempty" validate:"required"` + Submission exchange.PresentationSubmission `json:"submission" validate:"required"` + Credentials []credential.Container `json:"credentials,omitempty"` } func (csr CreateSubmissionRequest) IsValid() bool { diff --git a/pkg/service/presentation/service.go b/pkg/service/presentation/service.go index ce6875fd3..d9e0d3396 100644 --- a/pkg/service/presentation/service.go +++ b/pkg/service/presentation/service.go @@ -3,19 +3,30 @@ package presentation import ( "fmt" "github.com/TBD54566975/ssi-sdk/credential/exchange" + "github.com/TBD54566975/ssi-sdk/credential/signing" + didsdk "github.com/TBD54566975/ssi-sdk/did" sdkutil "github.com/TBD54566975/ssi-sdk/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/tbd54566975/ssi-service/config" + "github.com/tbd54566975/ssi-service/internal/credential" "github.com/tbd54566975/ssi-service/internal/util" + "github.com/tbd54566975/ssi-service/pkg/jwt" "github.com/tbd54566975/ssi-service/pkg/service/framework" + "github.com/tbd54566975/ssi-service/pkg/service/operation" + opstorage "github.com/tbd54566975/ssi-service/pkg/service/operation/storage" presentationstorage "github.com/tbd54566975/ssi-service/pkg/service/presentation/storage" + "github.com/tbd54566975/ssi-service/pkg/service/schema" "github.com/tbd54566975/ssi-service/pkg/storage" ) type Service struct { - storage presentationstorage.Storage - config config.PresentationServiceConfig + storage presentationstorage.Storage + opsStorage opstorage.Storage + config config.PresentationServiceConfig + resolver *didsdk.Resolver + schema *schema.Service + verifier *credential.Verifier } func (s Service) Type() framework.Type { @@ -40,14 +51,26 @@ func (s Service) Config() config.PresentationServiceConfig { return s.config } -func NewPresentationService(config config.PresentationServiceConfig, s storage.ServiceStorage) (*Service, error) { +func NewPresentationService(config config.PresentationServiceConfig, s storage.ServiceStorage, resolver *didsdk.Resolver, schema *schema.Service) (*Service, error) { presentationStorage, err := presentationstorage.NewPresentationStorage(s) if err != nil { return nil, util.LoggingErrorMsg(err, "could not instantiate definition storage for the presentation service") } + opsStorage, err := opstorage.NewOperationStorage(s) + if err != nil { + return nil, util.LoggingErrorMsg(err, "could not instantiate storage for the operations") + } + verifier, err := credential.NewCredentialVerifier(resolver, schema) + if err != nil { + return nil, util.LoggingErrorMsg(err, "could not instantiate verifier") + } service := Service{ - storage: presentationStorage, - config: config, + storage: presentationStorage, + opsStorage: opsStorage, + config: config, + resolver: resolver, + schema: schema, + verifier: verifier, } if !service.Status().IsReady() { return nil, errors.New(service.Status().Message) @@ -104,9 +127,7 @@ func (s Service) DeletePresentationDefinition(request DeletePresentationDefiniti // CreateSubmission houses the main service logic for presentation submission creation. It validates the input, and // produces a presentation submission value that conforms with the Submission specification. -func (s Service) CreateSubmission(request CreateSubmissionRequest) (*CreateSubmissionResponse, error) { - logrus.Debugf("creating presentation submission: %+v", request) - +func (s Service) CreateSubmission(request CreateSubmissionRequest) (*operation.Operation, error) { if !request.IsValid() { return nil, util.LoggingNewErrorf("invalid create presentation submission request: %+v", request) } @@ -115,14 +136,63 @@ func (s Service) CreateSubmission(request CreateSubmissionRequest) (*CreateSubmi return nil, util.LoggingErrorMsg(err, "provided value is not a valid presentation submission") } + sdkVp, err := signing.ParseVerifiablePresentationFromJWT(request.SubmissionJWT.String()) + if err != nil { + return nil, errors.Wrap(err, "parsing vp from jwt") + } + if err := jwt.VerifyTokenFromDID(sdkVp.Holder, request.SubmissionJWT, s.resolver); err != nil { + return nil, errors.Wrap(err, "verifying token from did") + } + + if _, err := s.storage.GetSubmission(request.Submission.ID); !errors.Is(err, presentationstorage.ErrSubmissionNotFound) { + return nil, errors.Errorf("submission with id %s already present", request.Submission.ID) + } + + definition, err := s.storage.GetPresentation(request.Submission.DefinitionID) + if err != nil { + return nil, util.LoggingErrorMsg(err, "getting presentation definition") + } + + for _, cred := range request.Credentials { + if !cred.IsValid() { + return nil, util.LoggingNewErrorf("invalid credential %+v", cred) + } + if cred.CredentialJWT != nil { + if err := s.verifier.VerifyJWTCredential(*cred.CredentialJWT); err != nil { + return nil, errors.Wrapf(err, "verifying jwt credential %s", cred.CredentialJWT) + } + } else { + if cred.Credential != nil && cred.Credential.Proof != nil { + if err := s.verifier.VerifyDataIntegrityCredential(*cred.Credential); err != nil { + return nil, errors.Wrapf(err, "verifying data integrity credential %+v", cred.Credential) + } + } + } + } + + if err := exchange.VerifyPresentationSubmissionVP(definition.PresentationDefinition, request.Presentation); err != nil { + return nil, util.LoggingErrorMsg(err, "verifying presentation submission vp") + } + storedSubmission := presentationstorage.StoredSubmission{Submission: request.Submission} + // TODO(andres): IO requests should be done in parallel, once we have context wired up. if err := s.storage.StoreSubmission(storedSubmission); err != nil { return nil, util.LoggingErrorMsg(err, "could not store presentation") } - return &CreateSubmissionResponse{ - Submission: storedSubmission.Submission, + opID := fmt.Sprintf("presentations/submissions/%s", storedSubmission.Submission.ID) + storedOp := opstorage.StoredOperation{ + ID: opID, + Done: false, + } + if err := s.opsStorage.StoreOperation(storedOp); err != nil { + return nil, util.LoggingErrorMsg(err, "could not store operation") + } + + return &operation.Operation{ + ID: storedOp.ID, + Done: false, }, nil } diff --git a/pkg/service/presentation/storage/bolt.go b/pkg/service/presentation/storage/bolt.go index 2c1f88371..170571374 100644 --- a/pkg/service/presentation/storage/bolt.go +++ b/pkg/service/presentation/storage/bolt.go @@ -85,7 +85,9 @@ func (b BoltPresentationStorage) GetSubmission(id string) (*StoredSubmission, er return nil, util.LoggingNewErrorf("could not get submission definition: %s", id) } if len(jsonBytes) == 0 { - return nil, util.LoggingNewErrorf("submission definition not found with id: %s", id) + err := errors.Wrapf(ErrSubmissionNotFound, "submission definition not found with id: %s", id) + logrus.WithError(err).Error("could not get submission definition from storage") + return nil, err } var stored StoredSubmission if err := json.Unmarshal(jsonBytes, &stored); err != nil { diff --git a/pkg/service/presentation/storage/storage.go b/pkg/service/presentation/storage/storage.go index 28f02053b..fb49eef8a 100644 --- a/pkg/service/presentation/storage/storage.go +++ b/pkg/service/presentation/storage/storage.go @@ -2,6 +2,7 @@ package storage import ( "github.com/TBD54566975/ssi-sdk/credential/exchange" + "github.com/pkg/errors" "github.com/tbd54566975/ssi-service/internal/util" "github.com/tbd54566975/ssi-service/pkg/storage" ) @@ -48,3 +49,5 @@ type SubmissionStorage interface { StoreSubmission(schema StoredSubmission) error GetSubmission(id string) (*StoredSubmission, error) } + +var ErrSubmissionNotFound = errors.New("submission not found") diff --git a/pkg/service/service.go b/pkg/service/service.go index 4b2e25d72..15247a675 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -97,7 +97,7 @@ func instantiateServices(config config.ServicesConfig) ([]framework.Service, err return nil, util.LoggingErrorMsg(err, "could not instantiate the manifest service") } - presentationService, err := presentation.NewPresentationService(config.PresentationConfig, storageProvider) + presentationService, err := presentation.NewPresentationService(config.PresentationConfig, storageProvider, didResolver, schemaService) if err != nil { return nil, util.LoggingErrorMsg(err, "could not instantiate the presentation service") } diff --git a/sip/sips/sip6/README.md b/sip/sips/sip6/README.md index b2429617b..7432f50c5 100644 --- a/sip/sips/sip6/README.md +++ b/sip/sips/sip6/README.md @@ -184,7 +184,7 @@ A `Submission` object: { "iss": "did:web:andresuribe.com", "vp": { - "presentation_submission": { + "presentation_submission": { "id": "a30e3b91-fb77-4d22-95fa-871689c322e2", "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653", "descriptor_map": [