Skip to content

Commit

Permalink
Merge pull request #45 from FIWARE/did-elsi
Browse files Browse the repository at this point in the history
Did elsi
  • Loading branch information
wistefan authored Jan 13, 2025
2 parents 54ac39f + 80f46b7 commit cb364db
Show file tree
Hide file tree
Showing 18 changed files with 522 additions and 348 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.21-alpine AS build
FROM golang:1.23-alpine AS build

WORKDIR /go/src/app
COPY ./ ./
Expand All @@ -8,7 +8,7 @@ RUN apk add build-base
RUN go get -d -v ./...
RUN go build -v .

FROM golang:1.21-alpine
FROM golang:1.23-alpine

LABEL org.opencontainers.image.source="https://github.com/FIWARE/VCVerifier"

Expand Down
10 changes: 9 additions & 1 deletion common/httpUtils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package common

import "strings"
import (
"net/http"
"strings"
)

func BuildUrlString(address string, path string) string {
if strings.HasSuffix(address, "/") {
Expand All @@ -17,3 +20,8 @@ func BuildUrlString(address string, path string) string {
}
}
}

// basic interface for a generic http client
type HttpClient interface {
Do(req *http.Request) (*http.Response, error)
}
14 changes: 14 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Configuration struct {
Logging Logging `mapstructure:"logging"`
ConfigRepo ConfigRepo `mapstructure:"configRepo"`
M2M M2M `mapstructure:"m2m"`
Elsi Elsi `mapstructure:"elsi"`
}

// general configuration to run the application
Expand Down Expand Up @@ -79,6 +80,19 @@ type Verifier struct {
KeyAlgorithm string `mapstructure:"keyAlgorithm" default:"RS256"`
}

type Elsi struct {
// should the support for did:elsi be enabled
Enabled bool `mapstructure:"enabled" default:"false"`
// endpoint of the validation service to be used for JAdES signatures
ValidationEndpoint *ValidationEndpoint `mapstructure:"validationEndpoint"`
}

type ValidationEndpoint struct {
Host string `mapstructure:"host"`
ValidationPath string `mapstructure:"validationPath" default:"/validateSignature"`
HealthPath string `mapstructure:"healthPath" default:"/q/health/ready"`
}

type Policies struct {
// policies that all credentials are checked against
DefaultPolicies PolicyMap `mapstructure:"default"`
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/fiware/VCVerifier

go 1.21
go 1.23

require (
github.com/deepmap/oapi-codegen v1.12.3
Expand Down
125 changes: 125 additions & 0 deletions jades/jades_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package jades

import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"

"github.com/fiware/VCVerifier/common"
"github.com/fiware/VCVerifier/logging"
)

const TOKEN_EXTRACTION_STRATEGY = "NONE"
const DOCUMENT_NAME = "RemoteDocument"

var ErrorBadResponse = errors.New("bad_response_from_validation_endpoint")
var ErrorEmptyBodyResponse = errors.New("empty_body_response_from_validation_endpoint")
var ErrorValidationServiceNotReady = errors.New("validation_service_not_ready")

// Validator for JAdES(https://www.etsi.org/deliver/etsi_ts/119100_119199/11918201/01.01.01_60/ts_11918201v010101p.pdf) signatures
type JAdESValidator interface {
ValidateSignature(signature string) (bool, error)
}

// Validator implementation, that uses an external validation service(based on https://github.com/esig/dss)
type ExternalJAdESValidator struct {
HttpClient common.HttpClient
ValidationAddress string
HealthAddress string
}

// structs to be used with the dss-library

type SignedDocument struct {
Bytes string `json:"bytes"`
Name string `json:"name"`
}

type ValidationRequest struct {
SignedDocument SignedDocument `json:"signedDocument"`
TokenExtractionStrategy string `json:"tokenExtractionStrategy"`
}

type SimpleReport struct {
DocumentName string `json:"documentName"`
// we only need to see that all signatures are valid, then nothing else has to be mapped
ValidSignaturesCount int `json:"validSignaturesCount"`
SignaturesCount int `json:"signaturesCount"`
}

type ValidationResponse struct {
SimpleReport SimpleReport `json:"simpleReport"`
}

func (v *ExternalJAdESValidator) ValidateSignature(signature string) (success bool, err error) {
validationRequest := ValidationRequest{
SignedDocument: SignedDocument{Bytes: signature, Name: DOCUMENT_NAME},
TokenExtractionStrategy: TOKEN_EXTRACTION_STRATEGY,
}
requestBody, err := json.Marshal(validationRequest)
if err != nil {
logging.Log().Warnf("Was not able to marshal the validation request. Error: %v", err)
return success, err
}

validationHttpRequest, err := http.NewRequest("POST", v.ValidationAddress, bytes.NewBuffer(requestBody))
if err != nil {
logging.Log().Warnf("Was not able to create validation request. Err: %v", err)
return success, err
}
validationHttpRequest.Header.Set("Content-Type", "application/json")
validationHttpRequest.Header.Set("Accept", "application/json")
validationHttpResponse, err := v.HttpClient.Do(validationHttpRequest)
if err != nil {
logging.Log().Warnf("Did not receive a valid validation response. Err: %v", err)
return false, err
}

defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
logging.Log().Warnf("Was not able to close the response body. Err: %v", err)
}
}(validationHttpResponse.Body)

if validationHttpResponse.StatusCode != 200 {
logging.Log().Warnf("Did not receive an OK from the validation endpoint. Was: %s", logging.PrettyPrintObject(validationHttpResponse))
return false, ErrorBadResponse
}

if validationHttpResponse.Body == nil {
logging.Log().Warnf("Received an empty body from the validation endpoint.")
return false, ErrorEmptyBodyResponse
}
validationResponse := &ValidationResponse{}
err = json.NewDecoder(validationHttpResponse.Body).Decode(validationResponse)
if err != nil {
logging.Log().Warnf("Was not able to decode the validation response. Error: %v", err)
return false, err
}
// if all signatures in the report are valid, the the validation was successful
if validationResponse.SimpleReport.SignaturesCount == 0 ||
(validationResponse.SimpleReport.SignaturesCount != validationResponse.SimpleReport.ValidSignaturesCount) {
logging.Log().Infof("Signature was invalid.")
return false, err
}
return true, err
}

// health check function, to signal the external service beeing ready
func (v *ExternalJAdESValidator) IsReady() error {
healthRequest, err := http.NewRequest("GET", v.HealthAddress, nil)
if err != nil {
return err
}
response, err := v.HttpClient.Do(healthRequest)
if err != nil {
return err
}
if response.StatusCode != 200 {
return ErrorValidationServiceNotReady
}
return nil
}
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func main() {
logger.Infof("Configuration is: %s", logging.PrettyPrintObject(configuration))

verifier.InitVerifier(&configuration)
verifier.InitPresentationParser(&configuration, Health())

router := getRouter()

Expand Down
16 changes: 9 additions & 7 deletions openapi/api_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,13 @@ import (
"github.com/fiware/VCVerifier/common"
"github.com/fiware/VCVerifier/logging"
"github.com/fiware/VCVerifier/verifier"
"github.com/piprate/json-gold/ld"
"github.com/trustbloc/vc-go/proof/defaults"
"github.com/trustbloc/vc-go/verifiable"

"github.com/gin-gonic/gin"
)

var apiVerifier verifier.Verifier
var presentationOptions = []verifiable.PresentationOpt{
verifiable.WithPresProofChecker(defaults.NewDefaultProofChecker(verifier.JWTVerfificationMethodResolver{})),
verifiable.WithPresJSONLDDocumentLoader(NewCachingDocumentLoader(ld.NewDefaultDocumentLoader(http.DefaultClient)))}
var presentationParser verifier.PresentationParser

var ErrorMessagNoGrantType = ErrorMessage{"no_grant_type_provided", "Token requests require a grant_type."}
var ErrorMessageUnsupportedGrantType = ErrorMessage{"unsupported_grant_type", "Provided grant_type is not supported by the implementation."}
Expand All @@ -50,6 +46,13 @@ func getApiVerifier() verifier.Verifier {
return apiVerifier
}

func getPresentationParser() verifier.PresentationParser {
if presentationParser == nil {
presentationParser = verifier.GetPresentationParser()
}
return presentationParser
}

// GetToken - Token endpoint to exchange the authorization code with the actual JWT.
func GetToken(c *gin.Context) {

Expand Down Expand Up @@ -255,8 +258,7 @@ func extractVpFromToken(c *gin.Context, vpToken string) (parsedPresentation *ver
return
}

parsedPresentation, err = verifiable.ParsePresentation(tokenBytes,
presentationOptions...)
parsedPresentation, err = getPresentationParser().ParsePresentation(tokenBytes)
if err != nil {
logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
c.AbortWithStatusJSON(400, ErrorMessageUnableToDecodeToken)
Expand Down
17 changes: 10 additions & 7 deletions openapi/api_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ func TestGetToken(t *testing.T) {
for _, tc := range tests {

t.Run(tc.testName, func(t *testing.T) {
presentationOptions = []verifiable.PresentationOpt{verifiable.WithPresDisabledProofCheck(), verifiable.WithDisabledJSONLDChecks()}
presentationParser = &verifier.ConfigurablePresentationParser{
PresentationOpts: []verifiable.PresentationOpt{verifiable.WithPresDisabledProofCheck(), verifiable.WithDisabledJSONLDChecks()}}

recorder := httptest.NewRecorder()
testContext, _ := gin.CreateTestContext(recorder)
Expand Down Expand Up @@ -184,7 +185,8 @@ func TestStartSIOPSameDevice(t *testing.T) {
for _, tc := range tests {

t.Run(tc.testName, func(t *testing.T) {
presentationOptions = []verifiable.PresentationOpt{verifiable.WithPresDisabledProofCheck(), verifiable.WithDisabledJSONLDChecks()}
presentationParser = &verifier.ConfigurablePresentationParser{
PresentationOpts: []verifiable.PresentationOpt{verifiable.WithPresDisabledProofCheck(), verifiable.WithDisabledJSONLDChecks()}}

recorder := httptest.NewRecorder()
testContext, _ := gin.CreateTestContext(recorder)
Expand Down Expand Up @@ -248,10 +250,10 @@ func TestVerifierAPIAuthenticationResponse(t *testing.T) {

t.Run(tc.testName, func(t *testing.T) {

//presentationOptions = []verifiable.PresentationOpt{verifiable.WithPresDisabledProofCheck(), verifiable.WithDisabledJSONLDChecks()}
presentationOptions = []verifiable.PresentationOpt{
verifiable.WithPresProofChecker(defaults.NewDefaultProofChecker(verifier.JWTVerfificationMethodResolver{})),
verifiable.WithPresJSONLDDocumentLoader(ld.NewDefaultDocumentLoader(http.DefaultClient))}
presentationParser = &verifier.ConfigurablePresentationParser{
PresentationOpts: []verifiable.PresentationOpt{
verifiable.WithPresProofChecker(defaults.NewDefaultProofChecker(verifier.JWTVerfificationMethodResolver{})),
verifiable.WithPresJSONLDDocumentLoader(ld.NewDefaultDocumentLoader(http.DefaultClient))}}

recorder := httptest.NewRecorder()
testContext, _ := gin.CreateTestContext(recorder)
Expand Down Expand Up @@ -338,7 +340,8 @@ func TestVerifierAPIStartSIOP(t *testing.T) {
logging.Log().Info("TestVerifierAPIStartSIOP +++++++++++++++++ Running test: ", tc.testName)

t.Run(tc.testName, func(t *testing.T) {
presentationOptions = []verifiable.PresentationOpt{verifiable.WithPresDisabledProofCheck(), verifiable.WithDisabledJSONLDChecks()}
presentationParser = &verifier.ConfigurablePresentationParser{
PresentationOpts: []verifiable.PresentationOpt{verifiable.WithPresDisabledProofCheck(), verifiable.WithDisabledJSONLDChecks()}}

recorder := httptest.NewRecorder()
testContext, _ := gin.CreateTestContext(recorder)
Expand Down
6 changes: 3 additions & 3 deletions tir/authorizationClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ type HttpGetClient interface {
}

type AuthorizingHttpClient struct {
httpClient HttpClient
httpClient common.HttpClient
tokenProvider TokenProvider
clientId string
}

type NoAuthHttpClient struct {
httpClient HttpClient
httpClient common.HttpClient
}

func (ac AuthorizingHttpClient) FillMetadataCache(context.Context) {
Expand Down Expand Up @@ -65,7 +65,7 @@ func (nac NoAuthHttpClient) Get(tirAddress string, tirPath string) (resp *http.R
}

// excutes get requests via "DO" and close the connection afterwards, to avoid EOFs on keep-alive race-conditions
func closingGet(httpClient HttpClient, url string) (resp *http.Response, err error) {
func closingGet(httpClient common.HttpClient, url string) (resp *http.Response, err error) {
req, _ := http.NewRequest("GET", url, nil)
req.Close = true
return httpClient.Do(req)
Expand Down
4 changes: 0 additions & 4 deletions tir/tirClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ const DID_V4_Path = "v4/identifiers"
var ErrorTirNoResponse = errors.New("no_response_from_tir")
var ErrorTirEmptyResponse = errors.New("empty_response_from_tir")

type HttpClient interface {
Do(req *http.Request) (*http.Response, error)
}

type TirClient interface {
IsTrustedParticipant(tirEndpoints []string, did string) (trusted bool)
GetTrustedIssuer(tirEndpoints []string, did string) (exists bool, trustedIssuer TrustedIssuer, err error)
Expand Down
1 change: 0 additions & 1 deletion tir/tokenProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ func getCredential(vcPath string) (vc *verifiable.Credential, err error) {

// file system interfaces

// Interface to the http-client
type fileAccessor interface {
ReadFile(filename string) ([]byte, error)
}
Expand Down
4 changes: 3 additions & 1 deletion openapi/caching_client.go → verifier/caching_client.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package openapi
package verifier

import (
"time"
Expand All @@ -9,6 +9,8 @@ import (
"github.com/piprate/json-gold/ld"
)

// loader for json-ld documents, used by the presentation parser. Caches the ld-documents for faster access

type CachingDocumentLoader struct {
defaultLoader ld.DocumentLoader
contextCache common.Cache
Expand Down
Loading

0 comments on commit cb364db

Please sign in to comment.