Skip to content

Commit

Permalink
wip3
Browse files Browse the repository at this point in the history
  • Loading branch information
woutslakhorst committed Dec 12, 2023
1 parent 71e5783 commit d452ee8
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 41 deletions.
69 changes: 29 additions & 40 deletions auth/api/iam/openid4vp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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{
Expand Down
47 changes: 46 additions & 1 deletion auth/services/oauth/holder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions auth/services/oauth/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package oauth

import (
"context"
"github.com/nuts-foundation/go-did/vc"
"net/url"

"github.com/nuts-foundation/go-did/did"
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit d452ee8

Please sign in to comment.