From d452ee883a5a201dc20b59747997610386000d3c Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Tue, 12 Dec 2023 16:51:15 +0100 Subject: [PATCH] wip3 --- auth/api/iam/openid4vp.go | 69 ++++++++++++++------------------ auth/services/oauth/holder.go | 47 +++++++++++++++++++++- auth/services/oauth/interface.go | 3 ++ 3 files changed, 78 insertions(+), 41 deletions(-) diff --git a/auth/api/iam/openid4vp.go b/auth/api/iam/openid4vp.go index f6215f5747..76b45b5d2d 100644 --- a/auth/api/iam/openid4vp.go +++ b/auth/api/iam/openid4vp.go @@ -24,26 +24,23 @@ import ( "encoding/json" "errors" "fmt" - "net/http" - "net/url" - "strings" - "time" - "github.com/google/uuid" "github.com/labstack/echo/v4" ssi "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" "github.com/nuts-foundation/nuts-node/auth/oauth" + oauth2 "github.com/nuts-foundation/nuts-node/auth/services/oauth" "github.com/nuts-foundation/nuts-node/crypto" httpNuts "github.com/nuts-foundation/nuts-node/http" "github.com/nuts-foundation/nuts-node/network/log" "github.com/nuts-foundation/nuts-node/storage" "github.com/nuts-foundation/nuts-node/vcr/credential" "github.com/nuts-foundation/nuts-node/vcr/holder" - "github.com/nuts-foundation/nuts-node/vcr/pe" - "github.com/nuts-foundation/nuts-node/vcr/signature/proof" "github.com/nuts-foundation/nuts-node/vdr/didweb" + "net/http" + "net/url" + "strings" ) var oauthNonceKey = []string{"oauth", "nonce"} @@ -196,9 +193,12 @@ func (r Wrapper) handleAuthorizeRequestFromVerifier(ctx context.Context, walletD // check client state // if no state, post error - // todo: to get the client state we need PR #2619 first - clientRedirectURI := "todo" - clientRedirectURL, _ := url.Parse(clientRedirectURI) + var session OAuthSession + err := r.oauthClientStateStore().Get(state, &session) + if err != nil { + return r.sendAndHandleDirectPostError(ctx, oauthError(oauth.InvalidRequest, "state has expired", nil), responseURI) + } + clientRedirectURL := session.redirectURI() verifierID, ok := params[clientIDParam] if !ok { @@ -228,47 +228,36 @@ func (r Wrapper) handleAuthorizeRequestFromVerifier(ctx context.Context, walletD if err != nil { return r.sendAndHandleDirectPostError(ctx, oauthError(oauth.InvalidPresentationDefinitionURI, fmt.Sprintf("failed to retrieve presentation definition on %s", presentationDefinitionURI), clientRedirectURL), responseURI) } - // get VCs from own wallet - credentials, err := r.vcr.Wallet().List(ctx, walletDID) - if err != nil { - return r.sendAndHandleDirectPostError(ctx, oauthError(oauth.ServerError, "failed to retrieve wallet credentials", clientRedirectURL), responseURI) - } - // build VP - submissionBuilder := presentationDefinition.PresentationSubmissionBuilder() - submissionBuilder.AddWallet(walletDID, credentials) - format := pe.ChooseVPFormat(metadata.VPFormats) - presentationSubmission, signInstructions, err := submissionBuilder.Build(format) - if err != nil { - return r.sendAndHandleDirectPostError(ctx, oauthError(oauth.ServerError, fmt.Sprintf("failed to build presentation submission: %s", err.Error()), clientRedirectURL), responseURI) - } - if signInstructions.Empty() { - return r.sendAndHandleDirectPostError(ctx, oauthError(oauth.InvalidRequest, "no matching credentials", clientRedirectURL), responseURI) - } - - // at this point in the flow it would be possible to ask the user to confirm the credentials to use - expires := time.Now().Add(time.Minute * 15) //todo + // check nonce nonce, ok := params[nonceParam] if !ok { return r.sendAndHandleDirectPostError(ctx, oauthError(oauth.InvalidRequest, "missing nonce parameter", clientRedirectURL), responseURI) } - // all params checked, delegate responsibility to the holder + // at this point in the flow it would be possible to ask the user to confirm the credentials to use - // todo: support multiple wallets - vp, err := r.vcr.Wallet().BuildPresentation(ctx, signInstructions[0].VerifiableCredentials, holder.PresentationOptions{ - Format: format, - ProofOptions: proof.ProofOptions{ - Created: time.Now(), - Challenge: &nonce, - Expires: &expires, - }, - }, verifierDID, false) + // all params checked, delegate responsibility to the holder + vp, submission, err := r.auth.Holder().BuildPresentation(ctx, walletDID, *presentationDefinition, *metadata, nonce) if err != nil { - return r.sendAndHandleDirectPostError(ctx, oauthError(oauth.ServerError, fmt.Sprintf("failed to create verifiable presentation: %s", err.Error()), clientRedirectURL), responseURI) + if errors.Is(err, oauth2.ErrNoCredentials) { + return r.sendAndHandleDirectPostError(ctx, oauthError(oauth.InvalidRequest, "no credentials available", clientRedirectURL), responseURI) + } + return r.sendAndHandleDirectPostError(ctx, oauthError(oauth.ServerError, err.Error(), clientRedirectURL), responseURI) } // todo: direct post + // POST /post HTTP/1.1 + // Host: client.example.org + // Content-Type: application/x-www-form-urlencoded + // + // presentation_submission=...& + // vp_token=...& + // state=eyJhb...6-sVA + // use response_uri as address + // reuse state + + // return should contain a 302 redirect to the client return HandleAuthorizeRequest302Response{ Headers: HandleAuthorizeRequest302ResponseHeaders{ diff --git a/auth/services/oauth/holder.go b/auth/services/oauth/holder.go index 60e25ba47f..08fc071d1d 100644 --- a/auth/services/oauth/holder.go +++ b/auth/services/oauth/holder.go @@ -22,29 +22,74 @@ package oauth import ( "context" "crypto/tls" + "errors" "fmt" + "github.com/nuts-foundation/go-did/did" + "github.com/nuts-foundation/go-did/vc" "github.com/nuts-foundation/nuts-node/auth/client/iam" "github.com/nuts-foundation/nuts-node/auth/oauth" + "github.com/nuts-foundation/nuts-node/vcr/holder" + "github.com/nuts-foundation/nuts-node/vcr/pe" + "github.com/nuts-foundation/nuts-node/vcr/signature/proof" "time" ) var _ Holder = (*HolderService)(nil) +var ErrNoCredentials = errors.New("no matching credentials") + type HolderService struct { strictMode bool httpClientTimeout time.Duration httpClientTLS *tls.Config + wallet holder.Wallet } // NewHolder returns an implementation of Holder -func NewHolder(strictMode bool, httpClientTimeout time.Duration, httpClientTLS *tls.Config) *HolderService { +func NewHolder(wallet holder.Wallet, strictMode bool, httpClientTimeout time.Duration, httpClientTLS *tls.Config) *HolderService { return &HolderService{ + wallet: wallet, strictMode: strictMode, httpClientTimeout: httpClientTimeout, httpClientTLS: httpClientTLS, } } +func (v *HolderService) BuildPresentation(ctx context.Context, walletDID did.DID, presentationDefinition pe.PresentationDefinition, verifierMetadata oauth.AuthorizationServerMetadata, nonce string) (*vc.VerifiablePresentation, *pe.PresentationSubmission, error) { + // get VCs from own wallet + credentials, err := v.wallet.List(ctx, walletDID) + if err != nil { + return nil, nil, errors.New("failed to retrieve wallet credentials") + } + + expires := time.Now().Add(time.Minute * 15) //todo + // build VP + submissionBuilder := presentationDefinition.PresentationSubmissionBuilder() + submissionBuilder.AddWallet(walletDID, credentials) + format := pe.ChooseVPFormat(verifierMetadata.VPFormats) + presentationSubmission, signInstructions, err := submissionBuilder.Build(format) + if err != nil { + return nil, nil, fmt.Errorf("failed to build presentation submission: %w", err) + } + if signInstructions.Empty() { + return nil, nil, ErrNoCredentials + } + + // todo: support multiple wallets + vp, err := v.wallet.BuildPresentation(ctx, signInstructions[0].VerifiableCredentials, holder.PresentationOptions{ + Format: format, + ProofOptions: proof.ProofOptions{ + Created: time.Now(), + Challenge: &nonce, + Expires: &expires, + }, + }, &walletDID, false) + if err != nil { + return nil, nil, fmt.Errorf("failed to create verifiable presentation: %w", err) + } + return vp, &presentationSubmission, nil +} + func (v *HolderService) ClientMetadata(ctx context.Context, endpoint string) (*oauth.AuthorizationServerMetadata, error) { iamClient := iam.NewHTTPClient(v.strictMode, v.httpClientTimeout, v.httpClientTLS) // The verifier has become the client diff --git a/auth/services/oauth/interface.go b/auth/services/oauth/interface.go index c217237096..64cecbff91 100644 --- a/auth/services/oauth/interface.go +++ b/auth/services/oauth/interface.go @@ -20,6 +20,7 @@ package oauth import ( "context" + "github.com/nuts-foundation/go-did/vc" "net/url" "github.com/nuts-foundation/go-did/did" @@ -65,6 +66,8 @@ type Verifier interface { // Holder implements the OpenID4VP Holder role which acts as Authorization server in the OpenID4VP flow. type Holder interface { + // BuildPresentation builds a Verifiable Presentation based on the given presentation definition. + BuildPresentation(ctx context.Context, walletDID did.DID, presentationDefinition pe.PresentationDefinition, verifierMetadata oauth.AuthorizationServerMetadata, nonce string) (*vc.VerifiablePresentation, *pe.PresentationSubmission, error) // ClientMetadata returns the metadata of the remote verifier. ClientMetadata(ctx context.Context, endpoint string) (*oauth.AuthorizationServerMetadata, error) // PostError posts an error to the verifier. If it fails, an error is returned.