diff --git a/README.md b/README.md index 2427f6dd..dac956d5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# vcverifier +# VCVerifier -vcverifier includes in a single binary demo versions of Issuer, Verifier and Wallet (same-device only, for a cross-device wallet please see [VCWallet](https://github.com/hesusruiz/VCWallet)). +VCerifier includes in a single binary demo versions of Issuer, Verifier and Wallet (same-device only, for a cross-device wallet please see [VCWallet](https://github.com/hesusruiz/VCWallet)). This facilitates installation and allows to see how all components fit together and the protocol flows between them. diff --git a/api/api.yaml b/api/api.yaml index a228c430..ea12f262 100644 --- a/api/api.yaml +++ b/api/api.yaml @@ -10,133 +10,7 @@ tags: description: All api-endpoints, e.g. the once that can be reused by other applicatios - name: verifier description: All endpoints belonging to the verifier functionality - - name: issuer - description: All endpoints belonging to the issuer functionality - - name: core - description: All endpoints belonging to core functionality, e.g. setup and info stuff - - name: wallet - description: All endpoints belonging to the wallet functionality - - name: base - description: General endpoints, providing information or entrypoint routing paths: - /issuer/api/v1/newcredential: - get: - tags: - - issuer - - frontend - operationId: IssuerPageNewCredentialFormDisplay - summary: Returns the page to issue new credentials - responses: - '200': - description: Show the credentials input page - content: - text/html: - schema: - type: object - post: - tags: - - issuer - - frontend - operationId: IssuerPageNewCredentialFormPost - summary: Issues a new credentials and renders them to a page - requestBody: - required: true - content: - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/VCCreate' - responses: - '200': - description: Show the created credential on a rendered page - content: - text/html: - schema: - type: object - /issuer/api/v1/creddetails/{id}: - get: - tags: - - issuer - - frontend - parameters: - - $ref: '#/components/parameters/Id' - operationId: IssuerPageCredentialDetails - summary: Show the credential details on a rendered page - responses: - '200': - description: Present the credential details in a rendered html page - content: - text/html: - schema: - type: object - /issuer/api/v1/displayqrurl/{id}: - get: - tags: - - issuer - - frontend - parameters: - - $ref: '#/components/parameters/Id' - operationId: IssuerPageDisplayQRURL - summary: Show the credential encoded as a qr-code on a rendered html page - responses: - '200': - description: Present the credential qr-code in a rendered html page - content: - text/html: - schema: - type: object - - /issuer/api/v1/allcredentials: - get: - tags: - - issuer - - api - operationId: IssuerAPIAllCredentials - summary: Return the ids of all issued credentials - responses: - '200': - description: Returns the list of ids - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/CredentialId' - /issuer/api/v1/credential: - post: - tags: - - issuer - - api - operationId: CreateNewCredential - summary: Issues a new credentials and returns it - requestBody: - required: true - content: - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/VCCreate' - responses: - '200': - description: Return the created credential - content: - application/json: - schema: - $ref: '#/components/schemas/VerifiableCredential' - /issuer/api/v1/credential/{id}: - get: - tags: - - issuer - - api - parameters: - - $ref: '#/components/parameters/Id' - operationId: IssuerAPICredential - summary: Return credential with the given ID - responses: - '200': - description: The credential - content: - text/plain: - schema: - $ref: '#/components/schemas/VerifiableCredential' /verifier/api/v1/displayqr: get: tags: @@ -301,87 +175,6 @@ paths: application/json: schema: $ref: '#/components/schemas/JWKSet' - - /wallet/api/v1/selectcredential: - get: - tags: - - wallet - - frontend - operationId: WalletPageSelectCredential - summary: Present a rendered page with the available credentials - responses: - '200': - description: The rendered page - content: - text/html: - schema: - type: object - /wallet/api/v1/sendcredential: - get: - tags: - - wallet - - frontend - parameters: - - $ref: '#/components/parameters/QueryState' - - $ref: '#/components/parameters/QueryId' - - $ref: '#/components/parameters/RedirectUri' - operationId: WalletPageSendCredential - summary: Executes a post with the given credential to the redirect_uri and returns a rendered page, presenting the post-result - responses: - '200': - description: The rendered page - content: - text/html: - schema: - type: object - /core/api/v1/createdid: - get: - tags: - - core - - api - operationId: CoreAPICreateDID - summary: Wrapper for the waltid-ssikit custodian endpoint for creating did's of type key - responses: - '200': - description: Creation result from the ssikit - content: - application/json: - schema: - type: string - example: did:key:z6MkibsKombswvCMhej83YbHsyknL2uc5kVazwr4xB3JqeUX - /core/api/v1/listcredentialtemplates: - get: - tags: - - core - - api - operationId: CoreAPIListCredentialTemplates - summary: Wrapper for the waltid-ssikit signatory endpoint for listing templates - responses: - '200': - description: List of templates, no offical spec available from ssikit, format is assumed - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/CredentialTemplate' - /core/api/v1/getcredentialtemplate/{id}: - get: - tags: - - core - - api - parameters: - - $ref: '#/components/parameters/Id' - operationId: CoreAPIGetCredentialTemplate - summary: Wrapper for the waltid-ssikit signatory endpoint for retrieving a template - responses: - '200': - description: The requested template. - content: - application/json: - schema: - description: SSIKit doc says type string, but its a vc-object instead. - type: object /: get: tags: @@ -396,20 +189,6 @@ paths: text/plain: schema: type: object - /issuer: - get: - tags: - - base - - frontend - operationId: HandleIssuerHome - summary: Returns the rendered homepage for the issuer functionality - responses: - '200': - description: The issuer page - content: - text/plain: - schema: - type: object /verifier: get: tags: @@ -477,23 +256,6 @@ components: type: string example: https://my-verifier.com/verifier/api/v1/authenticationresponse schemas: - CredentialTemplate: - type: object - properties: - mutable: - type: boolean - example: false - name: - type: string - example: VerifiableId - template: - type: string - CredentialId: - type: object - properties: - id: - type: string - example: urn:uuid:e44beccc-a766-4baf-8cd0-17f86ef74a5c CredentialsType: type: array items: @@ -608,25 +370,6 @@ components: properties: credential: $ref: '#/components/schemas/VerifiableCredential' - VCCreate: - type: object - properties: - email: - type: string - format: email - example: test@test.org - firstName: - type: string - example: Test - familyName: - type: string - example: User - target: - type: string - example: did:key:myuserkey - roles: - type: string - example: GOLD_CUSTOMER,STANDARD_CUSTOMER BackendInfo: type: object properties: diff --git a/back/operations/ssikit.go b/back/operations/ssikit.go index 5745a9aa..9a7c1049 100644 --- a/back/operations/ssikit.go +++ b/back/operations/ssikit.go @@ -1,24 +1,33 @@ package operations import ( + "encoding/json" "errors" "fmt" "net/http" + model "github.com/fiware/vcverifier/model" "github.com/fiware/vcverifier/vault" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) +const verificationPath = "/v1/verify" + var logger = zap.Must(zap.NewDevelopment()) -func SSIKitCreateDID(custodianURL string, v *vault.Vault, userid string) (string, error) { +func SSIKitCreateDID(custodianURL string, v *vault.Vault, userid string, configueredDid string) (did string, err error) { defer logger.Sync() - // Create a new DID only if it does not exist - did, _ := v.GetDIDForUser(userid) - if len(did) > 0 { - return did, nil + if len(configueredDid) == 0 { + // Create a new DID only if it does not exist + did, err = v.GetDIDForUser(userid) + if len(did) > 0 { + return did, err + } + } else { + v.SetDIDForUser(userid, configueredDid) + return did, err } // Call the SSI Kit @@ -31,7 +40,7 @@ func SSIKitCreateDID(custodianURL string, v *vault.Vault, userid string) (string agent.Set("accept", "application/json") code, returnBody, reqErr := agent.Bytes() if len(reqErr) > 0 { - err := fmt.Errorf("error calling SSI Kit: %v", reqErr[0]) + err := fmt.Errorf("error calling SSI Kit at: %v", reqErr[0]) logger.Error("error calling SSI Kit", zap.Error(err)) return "", err } @@ -46,3 +55,64 @@ func SSIKitCreateDID(custodianURL string, v *vault.Vault, userid string) (string return did, nil } + +type verificationRequest struct { + Policies []model.Policy `json:"policies"` + Credentials []map[string]interface{} `json:"credentials"` +} + +type verificationResult struct { + Valid bool `json:"valid"` + PolicyResults map[string]bool `json:"policyResults` +} + +type verificationResponse struct { + Valid bool `json:"valid"` + Results []verificationResult `json:"results"` +} + +func VerifyVC(auditorURL string, policies []model.Policy, verifiableCredential map[string]interface{}) (result bool, err error) { + defer logger.Sync() + auditorAddress := auditorURL + verificationPath + // Call the SSI Kit + agent := fiber.Post(auditorURL + verificationPath) + verificationRequest := verificationRequest{policies, []map[string]interface{}{verifiableCredential}} + + agent.JSON(verificationRequest) + + logger.Debug(fmt.Sprintf("Request is %s", prettyPrintObject(verificationRequest))) + agent.ContentType("application/json") + agent.Set("accept", "application/json") + + code, returnBody, reqErr := agent.Bytes() + if len(reqErr) > 0 { + err := fmt.Errorf("error calling SSI Kit at %s: %v", auditorAddress, reqErr[0]) + logger.Error("error calling SSI Kit", zap.Error(err)) + return false, err + } + if code != http.StatusOK { + err := fmt.Errorf("error calling SSI Kit - status was %d: %s", code, string(returnBody)) + logger.Error(fmt.Sprintf("error calling SSI Kit - response was %d: %s", code, string(returnBody)), zap.Error(err)) + return false, err + } + + var vr verificationResponse + json.Unmarshal(returnBody, &vr) + + if vr.Valid { + return true, err + } else { + logger.Info("Verfication failed.") + logger.Debug(fmt.Sprintf("Detailed result is %v", prettyPrintObject(vr))) + return false, err + } +} + +func prettyPrintObject(objectInterface interface{}) string { + jsonBytes, err := json.Marshal(objectInterface) + if err != nil { + logger.Debug(fmt.Sprintf("Was not able to pretty print the object: %v", objectInterface)) + return "" + } + return string(jsonBytes) +} diff --git a/back/views/layouts/main.html b/back/views/layouts/main.html index 0593fbf0..c6d1ec26 100644 --- a/back/views/layouts/main.html +++ b/back/views/layouts/main.html @@ -9,7 +9,7 @@ - Credential Issuer + Credential Verifier diff --git a/back/views/partials/header.html b/back/views/partials/header.html index b194ebc5..f75bd2e5 100644 --- a/back/views/partials/header.html +++ b/back/views/partials/header.html @@ -2,4 +2,6 @@ FIWARE Verifiable Credentials + FIWARE + diff --git a/back/views/verifier_loginexpired.html b/back/views/verifier_loginexpired.html index cf7aadb2..854df89f 100644 --- a/back/views/verifier_loginexpired.html +++ b/back/views/verifier_loginexpired.html @@ -10,7 +10,7 @@

The login session has expired

Click the button to try again

- Home + Home
diff --git a/back/views/verifier_present_qr.html b/back/views/verifier_present_qr.html index 822d0c8e..937e9b5e 100644 --- a/back/views/verifier_present_qr.html +++ b/back/views/verifier_present_qr.html @@ -27,7 +27,7 @@

Or click this button to authenticate with the browser

} const data = await response.text() if (data === "expired") { - location = "{{.verifierPrefix}}/loginexpired" + location = "{{.frontendPrefix}}/loginexpired" return } if (data === "pending") { @@ -35,7 +35,7 @@

Or click this button to authenticate with the browser

} console.log("Received:", data) - location = "{{.verifierPrefix}}/receivecredential/{{.state}}" + location = "{{.frontendPrefix}}/receivecredential/{{.state}}" return } catch (error) { window.alert("A server error happened.") diff --git a/back/views/verifier_receivedcredential.html b/back/views/verifier_receivedcredential.html index 1be1a375..d5c8649c 100644 --- a/back/views/verifier_receivedcredential.html +++ b/back/views/verifier_receivedcredential.html @@ -12,7 +12,7 @@

Received credential:

- Access protected service + Try to access a service.
diff --git a/back/views/verifier_request_service.html b/back/views/verifier_request_service.html new file mode 100644 index 00000000..13b3253f --- /dev/null +++ b/back/views/verifier_request_service.html @@ -0,0 +1,36 @@ +{{define "verifier_request_service"}} {{template "partials/header" .}} + +
+ +
+

Enter URL:

+
+
+ + + + +
+ + + {{if .returnBody}} +
+

Response

+

With status code {{.code}} and returned:

+

{{.returnBody}}

+
+ {{end}} + +
+ +{{template "partials/footer" .}} {{end}} \ No newline at end of file diff --git a/back/www/img/logo.png b/back/www/img/logo.png new file mode 100644 index 00000000..88974aee Binary files /dev/null and b/back/www/img/logo.png differ diff --git a/configs/server.yaml b/configs/server.yaml index d59da7e5..857ddc1c 100644 --- a/configs/server.yaml +++ b/configs/server.yaml @@ -11,6 +11,7 @@ store: verifier: id: PacketDelivery + did: did:key:z6Mkv4Lh9zBTPLoFhLHHMFJA7YAeVw5HFYZV8rkdfY9fNtm3 name: PacketDelivery password: ThePassword store: @@ -18,6 +19,7 @@ verifier: dataSourceName: "file:verifier.sqlite?mode=rwc&cache=shared&_fk=1" uri_prefix: /verifier jwks_uri: /.well-known/jwks_uri + tirAddress: "https://api-pilot.ebsi.eu/trusted-issuers-registry/v2/issuers/" protectedResource: url: "https://www.google.com" diff --git a/frontend.go b/frontend.go new file mode 100644 index 00000000..51b3ef2c --- /dev/null +++ b/frontend.go @@ -0,0 +1,241 @@ +package main + +import ( + "encoding/base64" + "fmt" + "time" + + "github.com/gofiber/fiber/v2" + qrcode "github.com/skip2/go-qrcode" + "go.uber.org/zap" +) + +const frontendPrefix = "" + +type Frontend struct { + server *Server +} + +// setupVerifier creates and setups the Issuer routes +func setupFrontend(s *Server) { + + frontend := &Frontend{s} + + // Define the prefix for Verifier routes + frontendRoutes := s.Group(frontendPrefix) + + // Pages + frontendRoutes.Get("/", frontend.HandleVerifierHome) + // Display a QR code for mobile wallet or a link for enterprise wallet + frontendRoutes.Get("/displayqr", frontend.VerifierPageDisplayQRSIOP) + + // Error page when login session has expired without the user sending the credential + frontendRoutes.Get("/loginexpired", frontend.VerifierPageLoginExpired) + + // Page displaying the received credential, after successful login + frontendRoutes.Get("/receivecredential/:state", frontend.VerifierPageReceiveCredential) + + // Allow simulation of accessing protected resources, after successful login + frontendRoutes.Get("/accessprotectedservice", frontend.VerifierPageAccessProtectedService) + frontendRoutes.Post("/accessService", frontend.VerifierPageAccessServicePost) + frontendRoutes.Get("/accessService", frontend.VerifierPageAccessServiceGet) +} + +func (f *Frontend) HandleVerifierHome(c *fiber.Ctx) error { + + // Get the list of credentials + credsSummary, err := f.server.Operations.GetAllCredentials() + if err != nil { + return err + } + + // Render template + m := fiber.Map{ + "verifierPrefix": frontendPrefix, + "prefix": frontendPrefix, + "credlist": credsSummary, + } + return c.Render("verifier_home", m) +} + +func (f *Frontend) VerifierPageDisplayQRSIOP(c *fiber.Ctx) error { + + // Generate the state that will be used for checking expiration and also successful logon + state := generateNonce() + + // Create an entry in storage that will expire. + // The entry is identified by the nonce + f.server.storage.Set(state, []byte("pending"), 200*time.Second) + + // This is the endpoint inside the QR that the wallet will use to send the VC/VP + redirect_uri := c.Protocol() + "://" + c.Hostname() + verifierPrefix + "/authenticationresponse" + + // Create the Authentication Request + authRequest := createAuthenticationRequest(f.server.verifierDID, redirect_uri, state) + f.server.logger.Info("AuthRequest", authRequest) + + // Create the QR code for cross-device SIOP + png, err := qrcode.Encode(authRequest, qrcode.Medium, 256) + if err != nil { + return err + } + + // Convert the image data to a dataURL + base64Img := base64.StdEncoding.EncodeToString(png) + base64Img = "data:image/png;base64," + base64Img + + // Render the page + m := fiber.Map{ + "verifierPrefix": verifierPrefix, + "frontendPrefix": frontendPrefix, + "qrcode": base64Img, + "prefix": frontendPrefix, + "state": state, + } + return c.Render("verifier_present_qr", m) +} + +func (f *Frontend) VerifierPageLoginExpired(c *fiber.Ctx) error { + m := fiber.Map{ + "prefix": frontendPrefix, + } + return c.Render("verifier_loginexpired", m) +} + +func (f *Frontend) VerifierPageReceiveCredential(c *fiber.Ctx) error { + + f.server.logger.Infof("Received credential") + + // Get the state as a path parameter + state := c.Params("state") + + // get the credential from the storage + rawCred, _ := f.server.storage.Get(state) + if len(rawCred) == 0 { + // Render an error + m := fiber.Map{ + "error": "No credential found", + "frontendPrefix": frontendPrefix, + } + return c.Render("displayerror", m) + } + + claims := string(rawCred) + + // Create an access token from the credential + accessToken, err := f.server.verifierVault.CreateAccessToken(claims, f.server.cfg.String("verifier.id")) + if err != nil { + return err + } + + // Set it in a cookie + cookie := new(fiber.Cookie) + cookie.Name = "dbsamvf" + cookie.Value = string(accessToken) + cookie.Expires = time.Now().Add(1 * time.Hour) + + // Set cookie + c.Cookie(cookie) + + // Set also the access token in the Authorization field of the response header + bearer := "Bearer " + string(accessToken) + c.Set("Authorization", bearer) + + // Render + m := fiber.Map{ + "verifierPrefix": frontendPrefix, + "frontendPrefix": frontendPrefix, + "claims": claims, + "prefix": frontendPrefix, + } + return c.Render("verifier_receivedcredential", m) +} + +func (f *Frontend) VerifierPageAccessServiceGet(c *fiber.Ctx) error { + + protected := f.server.cfg.String("verifier.protectedResource.url") + // Render + m := fiber.Map{ + "protectedService": protected, + "frontendPrefix": frontendPrefix, + "verifierPrefix": verifierPrefix, + } + return c.Render("verifier_request_service", m) +} + +func (f *Frontend) VerifierPageAccessServicePost(c *fiber.Ctx) error { + var code int + var returnBody []byte + var errors []error + + // Get the access token from the cookie + accessToken := c.Cookies("dbsamvf") + service := &AccessServiceForm{} + if err := c.BodyParser(service); err != nil { + f.server.logger.Infof("Error parsing: %s", err) + return err + } + f.server.logger.Errorw(string(c.Body())) + // Prepare to GET to the url + agent := fiber.Get(service.Url) + + // Set the Authentication header + agent.Set("Authorization", "Bearer "+accessToken) + + agent.Set("accept", "application/json") + code, returnBody, errors = agent.Bytes() + if len(errors) > 0 { + f.server.logger.Errorw("error calling backend at "+service.Url, zap.Errors("errors", errors)) + return fmt.Errorf("error calling backend as %s: %v", service.Url, errors[0]) + } + + // Render + m := fiber.Map{ + "protectedService": service.Url, + "verifierPrefix": frontendPrefix, + "frontendPrefix": frontendPrefix, + "code": code, + "returnBody": string(returnBody), + } + return c.Render("verifier_request_service", m) +} + +func (f *Frontend) VerifierPageAccessProtectedService(c *fiber.Ctx) error { + + var code int + var returnBody []byte + var errors []error + + // Get the access token from the cookie + accessToken := c.Cookies("dbsamvf") + + // Check if the user has configured a protected service to access + protected := f.server.cfg.String("verifier.protectedResource.url") + if len(protected) > 0 { + + // Prepare to GET to the url + agent := fiber.Get(protected) + + // Set the Authentication header + agent.Set("Authorization", "Bearer "+accessToken) + + agent.Set("accept", "application/json") + code, returnBody, errors = agent.Bytes() + if len(errors) > 0 { + f.server.logger.Errorw("error calling SSI Kit", zap.Errors("errors", errors)) + return fmt.Errorf("error calling SSI Kit: %v", errors[0]) + } + + } + + // Render + m := fiber.Map{ + "verifierPrefix": frontendPrefix, + "accesstoken": accessToken, + "frontendPrefix": frontendPrefix, + "protected": protected, + "code": code, + "returnBody": string(returnBody), + } + return c.Render("verifier_protectedservice", m) +} diff --git a/go.mod b/go.mod index 2ab9e924..ce8c77a8 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,9 @@ require ( require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/benbjohnson/clock v1.3.0 // indirect + github.com/deepmap/oapi-codegen v1.12.3 // indirect github.com/goccy/go-yaml v1.9.6 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.13 // indirect @@ -43,7 +45,7 @@ require ( github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/go-openapi/inflect v0.19.0 // indirect github.com/gofiber/template v1.7.2 - github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/golang-jwt/jwt/v4 v4.4.3 github.com/google/certificate-transparency-go v1.1.4 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index 2f1b1b31..a1b22c8d 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,7 @@ github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuN github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= @@ -116,6 +117,8 @@ github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g github.com/apache/beam v2.28.0+incompatible/go.mod h1:/8NX3Qi8vGstDLLaeaU7+lzVEu/ACaQhYjeefzQ0y1o= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ= github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= @@ -149,6 +152,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= @@ -216,6 +220,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/deepmap/oapi-codegen v1.12.3 h1:+DDYKeIwlKChzHjhVtlISegatFevDDazBhtk/dnp4V4= +github.com/deepmap/oapi-codegen v1.12.3/go.mod h1:ao2aFwsl/muMHbez870+KelJ1yusV01RznwAFFrVjDc= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -250,6 +256,8 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fiware/dsba-pdp v0.0.0-20230111112802-540544166240 h1:8YP/LLW4jrDR20YJFirqn+XO7wZdVJlGZBAipQMqOH4= +github.com/fiware/dsba-pdp v0.0.0-20230111112802-540544166240/go.mod h1:HpYuQF4RWRFxAFkqItyaCszNbOarnCOFuqKxWMGvFEI= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -316,6 +324,8 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -532,6 +542,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= @@ -812,6 +823,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= diff --git a/main.go b/main.go index c338296d..877af66b 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,6 @@ import ( "github.com/hesusruiz/vcutils/yaml" "flag" - "log" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" @@ -34,15 +33,11 @@ const defaultStoreDriverName = "sqlite3" const defaultStoreDataSourceName = "file:issuer.sqlite?mode=rwc&cache=shared&_fk=1" const defaultPassword = "ThePassword" -const corePrefix = "/core/api/v1" -const issuerPrefix = "/issuer/api/v1" const verifierPrefix = "/verifier/api/v1" -const walletPrefix = "/wallet/api/v1" var ( prod = flag.Bool("prod", false, "Enable prefork in Production") configFile = flag.String("config", LookupEnvOrString("CONFIG_FILE", defaultConfigFile), "path to configuration file") - password = flag.String("pass", LookupEnvOrString("PASSWORD", defaultPassword), "admin password for the server") ) type SSIKitConfig struct { @@ -60,7 +55,6 @@ type Server struct { WebAuthn *handlers.WebAuthnHandler Operations *operations.Manager verifierVault *vault.Vault - issuerDID string verifierDID string logger *zap.SugaredLogger storage *memory.Storage @@ -130,7 +124,7 @@ func BackendServer() { s.logger.Infof("SSIKit is configured at: %v", s.ssiKit) - s.verifierDID, err = operations.SSIKitCreateDID(s.ssiKit.custodianUrl, s.verifierVault, cfg.String("verifier.id")) + s.verifierDID, err = operations.SSIKitCreateDID(s.ssiKit.custodianUrl, s.verifierVault, cfg.String("verifier.id"), s.cfg.String("verifier.did")) if err != nil { panic(err) } @@ -149,11 +143,6 @@ func BackendServer() { s.storage = memory.New() defer s.storage.Close() - // ########################## - // Application Home pages - s.Get("/", s.HandleHome) - s.Get("/verifier", s.HandleVerifierHome) - // Info base path s.Get("/info", s.GetBackendInfo) @@ -163,28 +152,17 @@ func BackendServer() { } setupVerifier(s) - setupCoreRoutes(s) + setupFrontend(s) // Setup static files s.Static("/static", cfg.String("server.staticDir", defaultStaticDir)) + s.logger.Infof("Start %s", cfg.String("server.listenAddress")) // Start the server - log.Fatal(s.Listen(cfg.String("server.listenAddress"))) - -} - -func setupCoreRoutes(s *Server) { - // ######################################## - // Core routes - coreRoutes := s.Group(corePrefix) - - // Create DID - coreRoutes.Get("/createdid", s.CoreAPICreateDID) - // List Templates - coreRoutes.Get("/listcredentialtemplates", s.CoreAPIListCredentialTemplates) - // Get one template - coreRoutes.Get("/getcredentialtemplate/:id", s.CoreAPIGetCredentialTemplate) - + err = s.Listen(cfg.String("server.listenAddress")) + if err != nil { + s.logger.Warn("Failed to start.", err) + } } func fromMap(configMap map[string]any) (skc *SSIKitConfig) { @@ -232,23 +210,6 @@ func (s *Server) HandleStop(c *fiber.Ctx) error { return nil } -func (s *Server) HandleVerifierHome(c *fiber.Ctx) error { - - // Get the list of credentials - credsSummary, err := s.Operations.GetAllCredentials() - if err != nil { - return err - } - - // Render template - m := fiber.Map{ - "verifierPrefix": verifierPrefix, - "prefix": verifierPrefix, - "credlist": credsSummary, - } - return c.Render("verifier_home", m) -} - func generateNonce() string { b := make([]byte, 16) io.ReadFull(rand.Reader, b) @@ -256,8 +217,6 @@ func generateNonce() string { return nonce } -var sameDevice = false - type jwkSet struct { Keys []*jwk.JWK `json:"keys"` } diff --git a/model/verifiablecredential.go b/model/verifiablecredential.go new file mode 100644 index 00000000..37585875 --- /dev/null +++ b/model/verifiablecredential.go @@ -0,0 +1,10 @@ +package model + +type Policy struct { + Policy string `json:"policy"` + Argument *TirArgument `json:"argument,omitempty"` +} + +type TirArgument struct { + RegistryAddress string `json:"registryAddress"` +} diff --git a/verifier.go b/verifier.go index 98491a99..c41317b7 100644 --- a/verifier.go +++ b/verifier.go @@ -4,9 +4,11 @@ import ( "encoding/base64" "encoding/json" "errors" - "fmt" + "net/http" "time" + "github.com/fiware/vcverifier/back/operations" + model "github.com/fiware/vcverifier/model" "github.com/gofiber/fiber/v2" "github.com/hesusruiz/vcutils/yaml" qrcode "github.com/skip2/go-qrcode" @@ -15,13 +17,26 @@ import ( ) type Verifier struct { - server *Server + server *Server + tirAddress *string } // setupVerifier creates and setups the Issuer routes func setupVerifier(s *Server) { - verifier := &Verifier{s} + configuredAddress := s.cfg.String("verifier.tirAddress") + + var tirAddress *string + + if configuredAddress == "" { + s.logger.Warn("No trusted issuer registry configured. Will not use the tir check") + tirAddress = nil + } else { + s.logger.Infof("Will use tir at %s to verify the credentials.", configuredAddress) + tirAddress = &configuredAddress + } + + verifier := &Verifier{s, tirAddress} // Define the prefix for Verifier routes verifierRoutes := s.Group(verifierPrefix) @@ -32,23 +47,6 @@ func setupVerifier(s *Server) { jwks_uri := s.cfg.String("verifier.uri_prefix") + s.cfg.String("verifier.jwks_uri") s.Get(jwks_uri, s.VerifierAPIJWKS) - // Pages - - // Display a QR code for mobile wallet or a link for enterprise wallet - verifierRoutes.Get("/displayqr", verifier.VerifierPageDisplayQRSIOP) - - // Error page when login session has expired without the user sending the credential - verifierRoutes.Get("/loginexpired", verifier.VerifierPageLoginExpired) - - // For same-device logins (e.g., with the enterprise wallet) - verifierRoutes.Get("/startsiopsamedevice", verifier.VerifierPageStartSIOPSameDevice) - - // Page displaying the received credential, after successful login - verifierRoutes.Get("/receivecredential/:state", verifier.VerifierPageReceiveCredential) - - // Allow simulation of accessing protected resources, after successful login - verifierRoutes.Get("/accessprotectedservice", verifier.VerifierPageAccessProtectedService) - // APIs // Used by the login page from the browser, to check successful login or expiration @@ -62,183 +60,17 @@ func setupVerifier(s *Server) { // Used by the wallet (both enterprise and mobile) to send the VC/VP as Authentication Response verifierRoutes.Post("/authenticationresponse", verifier.VerifierAPIAuthenticationResponse) + s.logger.Info("Routes are setup") } -func (v *Verifier) VerifierPageDisplayQRSIOP(c *fiber.Ctx) error { - - // Generate the state that will be used for checking expiration and also successful logon - state := generateNonce() - - // Create an entry in storage that will expire. - // The entry is identified by the nonce - v.server.storage.Set(state, []byte("pending"), 200*time.Second) - - // This is the endpoint inside the QR that the wallet will use to send the VC/VP - redirect_uri := c.Protocol() + "://" + c.Hostname() + verifierPrefix + "/authenticationresponse" - - // Create the Authentication Request - authRequest := createAuthenticationRequest(v.server.verifierDID, redirect_uri, state) - v.server.logger.Info("AuthRequest", authRequest) - - // Create the QR code for cross-device SIOP - png, err := qrcode.Encode(authRequest, qrcode.Medium, 256) - if err != nil { - return err - } - - // Convert the image data to a dataURL - base64Img := base64.StdEncoding.EncodeToString(png) - base64Img = "data:image/png;base64," + base64Img - - // Render the page - m := fiber.Map{ - "issuerPrefix": issuerPrefix, - "verifierPrefix": verifierPrefix, - "walletPrefix": walletPrefix, - "qrcode": base64Img, - "prefix": verifierPrefix, - "state": state, - } - return c.Render("verifier_present_qr", m) -} - -func (v *Verifier) VerifierPageLoginExpired(c *fiber.Ctx) error { - m := fiber.Map{ - "prefix": verifierPrefix, - } - return c.Render("verifier_loginexpired", m) -} - -func (v *Verifier) VerifierPageStartSIOPSameDevice(c *fiber.Ctx) error { - - state := c.Query("state") - - const scope = "dsba.credentials.presentation.PacketDeliveryService" - const response_type = "vp_token" - redirect_uri := c.Protocol() + "://" + c.Hostname() + verifierPrefix + "/authenticationresponse" - - // template := "https://hesusruiz.github.io/faster/?scope={{scope}}" + - // "&response_type={{response_type}}" + - // "&response_mode=post" + - // "&client_id={{client_id}}" + - // "&redirect_uri={{redirect_uri}}" + - // "&state={{state}}" + - // "&nonce={{nonce}}" - - walletUri := c.Protocol() + "://" + c.Hostname() + walletPrefix + "/selectcredential" - template := walletUri + "/?scope={{scope}}" + - "&response_type={{response_type}}" + - "&response_mode=post" + - "&client_id={{client_id}}" + - "&redirect_uri={{redirect_uri}}" + - "&state={{state}}" + - "&nonce={{nonce}}" - - t := fasttemplate.New(template, "{{", "}}") - str := t.ExecuteString(map[string]interface{}{ - "scope": scope, - "response_type": response_type, - "client_id": v.server.verifierDID, - "redirect_uri": redirect_uri, - "state": state, - "nonce": generateNonce(), - }) - fmt.Println(str) - - return c.Redirect(str) -} - -func (v *Verifier) VerifierPageReceiveCredential(c *fiber.Ctx) error { - - // Get the state as a path parameter - state := c.Params("state") - - // get the credential from the storage - rawCred, _ := v.server.storage.Get(state) - if len(rawCred) == 0 { - // Render an error - m := fiber.Map{ - "error": "No credential found", - } - return c.Render("displayerror", m) - } - - claims := string(rawCred) - - // Create an access token from the credential - accessToken, err := v.server.verifierVault.CreateAccessToken(claims, v.server.cfg.String("verifier.id")) - if err != nil { - return err - } - - // Set it in a cookie - cookie := new(fiber.Cookie) - cookie.Name = "dbsamvf" - cookie.Value = string(accessToken) - cookie.Expires = time.Now().Add(1 * time.Hour) - - // Set cookie - c.Cookie(cookie) - - // Set also the access token in the Authorization field of the response header - bearer := "Bearer " + string(accessToken) - c.Set("Authorization", bearer) - - // Render - m := fiber.Map{ - "issuerPrefix": issuerPrefix, - "verifierPrefix": verifierPrefix, - "walletPrefix": walletPrefix, - "claims": claims, - "prefix": verifierPrefix, - } - return c.Render("verifier_receivedcredential", m) -} - -func (v *Verifier) VerifierPageAccessProtectedService(c *fiber.Ctx) error { - - var code int - var returnBody []byte - var errors []error - - // Get the access token from the cookie - accessToken := c.Cookies("dbsamvf") - - // Check if the user has configured a protected service to access - protected := v.server.cfg.String("verifier.protectedResource.url") - if len(protected) > 0 { - - // Prepare to GET to the url - agent := fiber.Get(protected) - - // Set the Authentication header - agent.Set("Authorization", "Bearer "+accessToken) - - agent.Set("accept", "application/json") - code, returnBody, errors = agent.Bytes() - if len(errors) > 0 { - v.server.logger.Errorw("error calling SSI Kit", zap.Errors("errors", errors)) - return fmt.Errorf("error calling SSI Kit: %v", errors[0]) - } - - } - - // Render - m := fiber.Map{ - "issuerPrefix": issuerPrefix, - "verifierPrefix": verifierPrefix, - "walletPrefix": walletPrefix, - "accesstoken": accessToken, - "protected": protected, - "code": code, - "returnBody": string(returnBody), - } - return c.Render("verifier_protectedservice", m) +type AccessServiceForm struct { + Url string `form:"requestUrl,omitempty"` } func (v *Verifier) VerifierAPIPoll(c *fiber.Ctx) error { + v.server.logger.Debug("VerifierAPIPoll") // get the state state := c.Params("state") @@ -254,7 +86,7 @@ func (v *Verifier) VerifierAPIPoll(c *fiber.Ctx) error { // retrieve token for the given session("state"-paramter) func (v *Verifier) VerifierAPIToken(c *fiber.Ctx) error { - v.server.logger.Info("Get the token") + v.server.logger.Debug("Get the token") // get the state state := c.Params("state") @@ -283,6 +115,8 @@ func (v *Verifier) VerifierAPIToken(c *fiber.Ctx) error { func (v *Verifier) VerifierAPIStartSIOP(c *fiber.Ctx) error { + v.server.logger.Debug("Start siop") + // Get the state state := c.Query("state") @@ -311,16 +145,20 @@ func (v *Verifier) VerifierAPIStartSIOP(c *fiber.Ctx) error { return c.SendString(str) } +type verficationMsg struct { + Msg string `json:"message"` +} + // VerifierAPIAuthenticationResponseVP receives a VP, extracts the VC and display a page func (v *Verifier) VerifierAPIAuthenticationResponseVP(c *fiber.Ctx) error { + v.server.logger.Debug("Received VP") + // Get the state, which indicates the login session to which this request belongs state := c.Query("state") // We should receive the Verifiable Presentation in the body as JSON body := c.Body() - fmt.Println(string(body)) - fmt.Println(string(state)) // Decode into a map vp, err := yaml.ParseJson(string(body)) @@ -329,21 +167,54 @@ func (v *Verifier) VerifierAPIAuthenticationResponseVP(c *fiber.Ctx) error { return err } - credential := vp.String("credential") + credential := []byte(vp.String("credential")) // Validate the credential + res, err := v.verifyCredential(credential) + if err != nil { + v.server.logger.Errorw("Was not able to verify credential.", zap.Error(err)) + return err + } + if !res { + v.server.logger.Info("Credential is not valid.") + return c.Status(http.StatusUnauthorized).JSON(verficationMsg{"Credential is invalid."}) + } // Set the credential in storage, and wait for the polling from client - v.server.storage.Set(state, []byte(credential), 10*time.Second) - + err = v.server.storage.Set(state, credential, 10*time.Second) + if err != nil { + v.server.logger.Warnf("Was not able to store for %s", state) + } return c.SendString("ok") } -type verifiableCredential struct { +func (v *Verifier) verifyCredential(credential []byte) (result bool, err error) { + v.server.logger.Debug("Verify Credential") + + var vcToVerify map[string]interface{} + + json.Unmarshal(credential, &vcToVerify) + + policies := []model.Policy{ + {Policy: "SignaturePolicy"}, + {Policy: "IssuedDateBeforePolicy"}, + {Policy: "ValidFromBeforePolicy"}, + {Policy: "ExpirationDateAfterPolicy"}, + } + if v.tirAddress != nil { + policies = append(policies, model.Policy{Policy: "TrustedIssuerRegistryPolicy", Argument: &model.TirArgument{RegistryAddress: *v.tirAddress}}) + } + + return operations.VerifyVC(v.server.ssiKit.auditorUrl, policies, vcToVerify) +} + +type VerifiableCredential struct { Credential *json.RawMessage `json:"credential"` } func (v *Verifier) VerifierAPIAuthenticationResponse(c *fiber.Ctx) error { + v.server.logger.Debug("Auth") + // Get the state state := c.Query("state") @@ -352,10 +223,20 @@ func (v *Verifier) VerifierAPIAuthenticationResponse(c *fiber.Ctx) error { v.server.logger.Infof("Authenticate for state '%s' with %s", state, body) // Decode into a map - vc := &verifiableCredential{} + vc := &VerifiableCredential{} json.Unmarshal(body, vc) // Validate the credential + res, err := v.verifyCredential(*vc.Credential) + if err != nil { + v.server.logger.Errorw("Was not able to verify credential.", zap.Error(err)) + return err + } + if !res { + v.server.logger.Info("Credential is not valid.") + return c.Status(http.StatusUnauthorized).JSON(verficationMsg{"Credential is invalid."}) + } + v.server.logger.Infof("Store credential %s", *vc.Credential) // Set the credential in storage, and wait for the polling from client v.server.storage.Set(state, *vc.Credential, 10*time.Second) @@ -366,10 +247,6 @@ func (v *Verifier) VerifierAPIAuthenticationResponse(c *fiber.Ctx) error { func (v *Verifier) VerifierPageDisplayQR(c *fiber.Ctx) error { - if sameDevice { - return v.VerifierPageStartSIOPSameDevice(c) - } - // Generate the state that will be used for checking expiration state := generateNonce() @@ -387,9 +264,7 @@ func (v *Verifier) VerifierPageDisplayQR(c *fiber.Ctx) error { // Render index m := fiber.Map{ - "issuerPrefix": issuerPrefix, "verifierPrefix": verifierPrefix, - "walletPrefix": walletPrefix, "qrcode": qrCode1, "prefix": verifierPrefix, "state": state,