Skip to content

Commit

Permalink
feat: validate jwt-vp client attestation (#1521)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrii Holovko <[email protected]>
  • Loading branch information
aholovko authored Nov 16, 2023
1 parent 8d002d2 commit 7189387
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 35 deletions.
37 changes: 21 additions & 16 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,20 @@ func buildEchoHandler(
issueCredentialSvc = issuecredentialtracing.Wrap(issueCredentialSvc, conf.Tracer)
}

var verifyCredentialSvc verifycredential.ServiceInterface

verifyCredentialSvc = verifycredential.New(&verifycredential.Config{
HTTPClient: getHTTPClient(metricsProvider.ClientCredentialVerifier),
VCStatusProcessorGetter: statustype.GetVCStatusProcessor,
StatusListVCResolver: statusListVCSvc,
DocumentLoader: documentLoader,
VDR: conf.VDR,
})

if conf.IsTraceEnabled {
verifyCredentialSvc = verifycredentialtracing.Wrap(verifyCredentialSvc, conf.Tracer)
}

oidc4ciTransactionStore, err := getOIDC4CITransactionStore(
conf.StartupParameters.transientDataParams.storeType,
redisClient,
Expand Down Expand Up @@ -668,9 +682,14 @@ func buildEchoHandler(

jsonSchemaValidator := jsonschema.NewCachingValidator()

proofChecker := defaults.NewDefaultProofChecker(vermethod.NewVDRResolver(conf.VDR))

attestationService := attestation.NewService(
&attestation.Config{
HTTPClient: getHTTPClient(metricsProvider.ClientAttestationService),
HTTPClient: getHTTPClient(metricsProvider.ClientAttestationService),
DocumentLoader: documentLoader,
ProofChecker: proofChecker,
VCStatusVerifier: verifyCredentialSvc,
},
)

Expand Down Expand Up @@ -790,7 +809,7 @@ func buildEchoHandler(
IssuerVCSPublicHost: conf.StartupParameters.apiGatewayURL, // use api gateway here, as this endpoint will be called by clients
HTTPClient: getHTTPClient(metricsProvider.ClientOIDC4CIV1),
ExternalHostURL: conf.StartupParameters.hostURLExternal, // use host external as this url will be called internally
JWTVerifier: defaults.NewDefaultProofChecker(vermethod.NewVDRResolver(conf.VDR)),
JWTVerifier: proofChecker,
ClientManager: clientManagerService,
ClientIDSchemeService: clientIDSchemeSvc,
Tracer: conf.Tracer,
Expand Down Expand Up @@ -829,20 +848,6 @@ func buildEchoHandler(
return nil, err
}

var verifyCredentialSvc verifycredential.ServiceInterface

verifyCredentialSvc = verifycredential.New(&verifycredential.Config{
HTTPClient: getHTTPClient(metricsProvider.ClientCredentialVerifier),
VCStatusProcessorGetter: statustype.GetVCStatusProcessor,
StatusListVCResolver: statusListVCSvc,
DocumentLoader: documentLoader,
VDR: conf.VDR,
})

if conf.IsTraceEnabled {
verifyCredentialSvc = verifycredentialtracing.Wrap(verifyCredentialSvc, conf.Tracer)
}

var verifyPresentationSvc verifypresentation.ServiceInterface

verifyPresentationSvc = verifypresentation.New(&verifypresentation.Config{
Expand Down
1 change: 1 addition & 0 deletions pkg/service/attestation/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ import "context"
type ServiceInterface interface {
ValidateClientAttestationJWT(ctx context.Context, clientID, clientAttestationJWT string) error
ValidateClientAttestationPoPJWT(ctx context.Context, clientID, clientAttestationPoPJWT string) error
ValidateClientAttestationVP(ctx context.Context, clientID, jwtVP string) error
}
81 changes: 77 additions & 4 deletions pkg/service/attestation/attestation_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,116 @@ Copyright Gen Digital Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

//go:generate mockgen -destination attestation_service_mocks_test.go -package attestation_test -source=attestation_service.go -mock_names httpClient=MockHTTPClient
//go:generate mockgen -destination attestation_service_mocks_test.go -package attestation_test -source=attestation_service.go -mock_names httpClient=MockHTTPClient,vcStatusVerifier=MockVCStatusVerifier

package attestation

import (
"context"
"fmt"
"net/http"
"time"

"github.com/piprate/json-gold/ld"
"github.com/trustbloc/vc-go/verifiable"
)

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

type vcStatusVerifier interface {
ValidateVCStatus(ctx context.Context, vcStatus *verifiable.TypedID, issuer *verifiable.Issuer) error
}

// Config defines configuration for Service.
type Config struct {
HTTPClient httpClient
HTTPClient httpClient
DocumentLoader ld.DocumentLoader
ProofChecker verifiable.CombinedProofChecker
VCStatusVerifier vcStatusVerifier
}

// Service implements attestation functionality for OAuth 2.0 Attestation-Based Client Authentication.
type Service struct {
httpClient httpClient
httpClient httpClient
documentLoader ld.DocumentLoader
proofChecker verifiable.CombinedProofChecker
vcStatusVerifier vcStatusVerifier
}

// NewService returns a new Service instance.
func NewService(config *Config) *Service {
return &Service{
httpClient: config.HTTPClient,
httpClient: config.HTTPClient,
documentLoader: config.DocumentLoader,
proofChecker: config.ProofChecker,
vcStatusVerifier: config.VCStatusVerifier,
}
}

// ValidateClientAttestationJWT validates Client Attestation JWT.
//
//nolint:revive
func (s *Service) ValidateClientAttestationJWT(ctx context.Context, clientID, clientAttestationJWT string) error {
// TODO: Validate Client Attestation JWT and check the status of Attestation VC.
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-attestation-based-client-auth-01#section-4.1.1
return nil
}

// ValidateClientAttestationPoPJWT validates Client Attestation Proof-of-Possession JWT.
//
//nolint:revive
func (s *Service) ValidateClientAttestationPoPJWT(ctx context.Context, clientID, clientAttestationPoPJWT string) error {
// TODO: Validate Client Attestation Proof of Possession (PoP) JWT.
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-attestation-based-client-auth-01#section-4.1.2
return nil
}

// ValidateClientAttestationVP validates Client Attestation VP in jwt_vp format.
//
//nolint:revive
func (s *Service) ValidateClientAttestationVP(ctx context.Context, clientID, jwtVP string) error {
vp, err := verifiable.ParsePresentation(
[]byte(jwtVP),
verifiable.WithPresProofChecker(s.proofChecker),
verifiable.WithPresJSONLDDocumentLoader(s.documentLoader),
)
if err != nil {
return fmt.Errorf("parse attestation vp: %w", err)
}

if len(vp.Credentials()) == 0 {
return fmt.Errorf("missing attestation vc")
}

attestationVC := vp.Credentials()[0]

// validate attestation vc
opts := []verifiable.CredentialOpt{
verifiable.WithProofChecker(s.proofChecker),
verifiable.WithJSONLDDocumentLoader(s.documentLoader),
}

if err = attestationVC.ValidateCredential(opts...); err != nil {
return fmt.Errorf("validate attestation vc: %w", err)
}

if err = attestationVC.CheckProof(opts...); err != nil {
return fmt.Errorf("check attestation vc proof: %w", err)
}

vcc := attestationVC.Contents()
if vcc.Expired != nil && time.Now().UTC().After(vcc.Expired.Time) {
return fmt.Errorf("attestation vc is expired")
}

// check attestation vc status
if err = s.vcStatusVerifier.ValidateVCStatus(ctx, vcc.Status, vcc.Issuer); err != nil {
return fmt.Errorf("validate attestation vc status: %w", err)
}

// TODO: validate attestation vc in trust registry

return nil
}
Loading

0 comments on commit 7189387

Please sign in to comment.