Skip to content

Commit

Permalink
fix: support for did:jwk in wallet cli (#1464)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrii Holovko <[email protected]>
  • Loading branch information
aholovko authored Oct 9, 2023
1 parent 76c8b34 commit c3c85ed
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 34 deletions.
2 changes: 1 addition & 1 deletion component/wallet-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ the Wallet. Therefore, prior to engaging in the OIDC4VCI flow, it's essential to
Wallet can be created using `create` command. The following CLI arguments are supported:
```bash
--did-key-type string did key types supported: ED25519,ECDSAP256DER,ECDSAP384DER (default "ED25519")
--did-method string wallet did methods supported: ion,jwk,key (default "ion")
--did-method string wallet did methods supported: ion,jwk (default "ion")
-h, --help help for create
--leveldb-path string leveldb path
--mongodb-connection-string string mongodb connection string
Expand Down
9 changes: 7 additions & 2 deletions component/wallet-cli/cmd/create_wallet_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ func NewCreateWalletCommand() *cobra.Command {
kms: svc.KMS(),
}

slog.Info("creating wallet",
"did_key_type", flags.didKeyType,
"did_method", flags.didMethod,
)

w, err := wallet.New(
provider,
wallet.WithNewDID(flags.didMethod),
Expand All @@ -65,7 +70,7 @@ func NewCreateWalletCommand() *cobra.Command {
dids = append(dids, fmt.Sprintf("%d", i), did)
}

slog.Info("wallet created",
slog.Info("wallet created successfully",
"signature_type", w.SignatureType(),
slog.Group("did", dids...),
)
Expand All @@ -77,7 +82,7 @@ func NewCreateWalletCommand() *cobra.Command {
cmd.Flags().StringVar(&flags.serviceFlags.storageType, "storage-type", "leveldb", "storage types supported: mem,leveldb,mongodb")
cmd.Flags().StringVar(&flags.serviceFlags.levelDBPath, "leveldb-path", "", "leveldb path")
cmd.Flags().StringVar(&flags.serviceFlags.mongoDBConnectionString, "mongodb-connection-string", "", "mongodb connection string")
cmd.Flags().StringVar(&flags.didMethod, "did-method", "ion", "wallet did methods supported: ion,jwk,key")
cmd.Flags().StringVar(&flags.didMethod, "did-method", "ion", "wallet did methods supported: ion,jwk")
cmd.Flags().StringVar(&flags.didKeyType, "did-key-type", "ED25519", "did key types supported: ED25519,ECDSAP256DER,ECDSAP384DER")

return cmd
Expand Down
63 changes: 51 additions & 12 deletions component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"github.com/google/uuid"
"github.com/piprate/json-gold/ld"
"github.com/samber/lo"
"github.com/trustbloc/did-go/doc/did"
"github.com/trustbloc/did-go/method/jwk"
vdrapi "github.com/trustbloc/did-go/vdr/api"
"github.com/trustbloc/kms-go/doc/jose"
"github.com/trustbloc/kms-go/spi/crypto"
Expand Down Expand Up @@ -78,7 +80,7 @@ type Flow struct {
userPassword string
issuerState string
pin string
walletDID string
walletDID *did.DID
walletSignatureType vcs.SignatureType
vc *verifiable.Credential
}
Expand Down Expand Up @@ -128,6 +130,11 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) {
return nil, fmt.Errorf("unsupported flow type: %d", o.flowType)
}

walletDID, err := did.Parse(o.walletDID)
if err != nil {
return nil, fmt.Errorf("parse wallet did: %w", err)
}

return &Flow{
httpClient: p.HTTPClient(),
documentLoader: p.DocumentLoader(),
Expand All @@ -147,7 +154,7 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) {
userPassword: o.userPassword,
issuerState: o.issuerState,
pin: o.pin,
walletDID: o.walletDID,
walletDID: walletDID,
walletSignatureType: o.walletSignatureType,
}, nil
}
Expand All @@ -171,7 +178,6 @@ func (f *Flow) Run(ctx context.Context) error {
)

if f.flowType == FlowTypeAuthorizationCode || f.flowType == FlowTypePreAuthorizedCode {
// 1. Parse credential offer URI
credentialOfferResponse, err := f.parseCredentialOfferURI(f.credentialOfferURI)
if err != nil {
return err
Expand All @@ -191,7 +197,6 @@ func (f *Flow) Run(ctx context.Context) error {
issuerState = f.issuerState
}

// 2. Get issuer OpenID configuration
openIDConfig, err := f.wellknownService.GetWellKnownOpenIDConfiguration(credentialIssuer)
if err != nil {
return err
Expand All @@ -200,7 +205,6 @@ func (f *Flow) Run(ctx context.Context) error {
var token *oauth2.Token

if f.flowType == FlowTypeAuthorizationCode {
// 3.1. Get authorization code
oauthClient := &oauth2.Config{
ClientID: f.clientID,
Scopes: f.scopes,
Expand All @@ -218,13 +222,18 @@ func (f *Flow) Run(ctx context.Context) error {
return err
}

// 3.2. Exchange authorization code for access token
token, err = f.exchangeAuthorizationCodeForAccessToken(ctx, oauthClient, authCode)
if err != nil {
return err
}
} else if f.flowType == FlowTypePreAuthorizedCode {
// 3. Get access token
slog.Info("getting access token",
"grant_type", preAuthorizedCodeGrantType,
"client_id", f.clientID,
"pre-authorized_code", preAuthorizationGrant.PreAuthorizedCode,
"token_endpoint", openIDConfig.TokenEndpoint,
)

tokenValues := url.Values{
"grant_type": []string{preAuthorizedCodeGrantType},
"pre-authorized_code": []string{preAuthorizationGrant.PreAuthorizedCode},
Expand Down Expand Up @@ -281,7 +290,6 @@ func (f *Flow) Run(ctx context.Context) error {
)
}

// 5. Get VC
vc, err := f.getVC(token, openIDConfig.CredentialEndpoint, credentialIssuer)
if err != nil {
return err
Expand All @@ -293,6 +301,10 @@ func (f *Flow) Run(ctx context.Context) error {
}

func (f *Flow) parseCredentialOfferURI(uri string) (*oidc4ci.CredentialOfferResponse, error) {
slog.Info("parsing credential offer URI",
"uri", uri,
)

parser := &credentialoffer.Parser{
HTTPClient: f.httpClient,
VDRRegistry: f.vdrRegistry,
Expand All @@ -312,6 +324,13 @@ func (f *Flow) parseCredentialOfferURI(uri string) (*oidc4ci.CredentialOfferResp
}

func (f *Flow) getAuthorizationCode(oauthClient *oauth2.Config, issuerState string) (string, error) {
slog.Info("getting authorization code",
"client_id", oauthClient.ClientID,
"scopes", oauthClient.Scopes,
"redirect_uri", oauthClient.RedirectURL,
"authorization_endpoint", oauthClient.Endpoint.AuthURL,
)

var (
listener net.Listener
err error
Expand Down Expand Up @@ -469,6 +488,13 @@ func (f *Flow) exchangeAuthorizationCodeForAccessToken(
oauthClient *oauth2.Config,
authCode string,
) (*oauth2.Token, error) {
slog.Info("exchanging authorization code for access token",
"grant_type", "authorization_code",
"client_id", oauthClient.ClientID,
"auth_code", authCode,
"token_endpoint", oauthClient.Endpoint.TokenURL,
)

token, err := oauthClient.Exchange(ctx, authCode,
oauth2.SetAuthURLParam("code_verifier", "xalsLDydJtHwIQZukUyj6boam5vMUaJRWv-BnGCAzcZi3ZTs"),
)
Expand All @@ -484,13 +510,26 @@ func (f *Flow) getVC(
credentialEndpoint,
credentialIssuer string,
) (*verifiable.Credential, error) {
docResolution, err := f.vdrRegistry.Resolve(f.walletDID)
slog.Info("getting credential",
"credential_endpoint", credentialEndpoint,
"credential_issuer", credentialIssuer,
)

docResolution, err := f.vdrRegistry.Resolve(f.walletDID.String())
if err != nil {
return nil, err
}

signerKeyID := docResolution.DIDDocument.VerificationMethod[0].ID
kmsKeyID := strings.Split(signerKeyID, "#")[1]
verificationMethod := docResolution.DIDDocument.VerificationMethod[0]

var kmsKeyID string

switch f.walletDID.Method {
case jwk.DIDMethod:
kmsKeyID = verificationMethod.JSONWebKey().KeyID
default:
kmsKeyID = strings.Split(verificationMethod.ID, "#")[1]
}

kmsSigner, err := kmssigner.NewKMSSigner(
f.keyManager,
Expand All @@ -517,7 +556,7 @@ func (f *Flow) getVC(
signedJWT, err := jwt.NewSigned(
claims,
headers,
jwssigner.NewJWSSigner(signerKeyID, string(f.walletSignatureType), kmsSigner),
jwssigner.NewJWSSigner(verificationMethod.ID, string(f.walletSignatureType), kmsSigner),
)
if err != nil {
return nil, fmt.Errorf("create signed jwt: %w", err)
Expand Down
69 changes: 51 additions & 18 deletions component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (

"github.com/google/uuid"
"github.com/piprate/json-gold/ld"
"github.com/trustbloc/did-go/doc/did"
"github.com/trustbloc/did-go/method/jwk"
vdrapi "github.com/trustbloc/did-go/vdr/api"
"github.com/trustbloc/kms-go/spi/crypto"
"github.com/trustbloc/kms-go/spi/kms"
Expand Down Expand Up @@ -50,8 +52,7 @@ type Flow struct {
keyManager kms.KeyManager
crypto crypto.Crypto
wallet *wallet.Wallet
walletDID string
walletDIDKeyID string
walletDID *did.DID
requestURI string
linkedDomainVerification bool
}
Expand All @@ -76,22 +77,19 @@ func NewFlow(p provider, opts ...Opt) (*Flow, error) {
return nil, fmt.Errorf("invalid request uri: %w", err)
}

docResolution, err := p.VDRegistry().Resolve(o.walletDID)
walletDID, err := did.Parse(o.walletDID)
if err != nil {
return nil, fmt.Errorf("resolve wallet did: %w", err)
return nil, fmt.Errorf("parse wallet did: %w", err)
}

walletDIDKeyID := docResolution.DIDDocument.VerificationMethod[0].ID

return &Flow{
httpClient: p.HTTPClient(),
documentLoader: p.DocumentLoader(),
vdrRegistry: p.VDRegistry(),
keyManager: p.KMS(),
crypto: p.Crypto(),
wallet: p.Wallet(),
walletDID: o.walletDID,
walletDIDKeyID: walletDIDKeyID,
walletDID: walletDID,
requestURI: o.requestURI,
linkedDomainVerification: o.linkedDomainVerification,
}, nil
Expand Down Expand Up @@ -249,6 +247,8 @@ func getServiceType(serviceType interface{}) string {
}

func (f *Flow) queryWallet(pd *presexch.PresentationDefinition) ([]*verifiable.Credential, error) {
slog.Info("querying wallet")

b, err := json.Marshal(pd)
if err != nil {
return nil, fmt.Errorf("marshal presentation definition: %w", err)
Expand Down Expand Up @@ -417,8 +417,16 @@ func (f *Flow) signPresentationJWT(
return "", fmt.Errorf("resolve signer did: %w", err)
}

signerKeyID := docResolution.DIDDocument.VerificationMethod[0].ID
kmsKeyID := strings.Split(signerKeyID, "#")[1]
verificationMethod := docResolution.DIDDocument.VerificationMethod[0]

var kmsKeyID string

switch f.walletDID.Method {
case jwk.DIDMethod:
kmsKeyID = verificationMethod.JSONWebKey().KeyID
default:
kmsKeyID = strings.Split(verificationMethod.ID, "#")[1]
}

kmsSigner, err := kmssigner.NewKMSSigner(
f.keyManager,
Expand Down Expand Up @@ -446,7 +454,7 @@ func (f *Flow) signPresentationJWT(
claims,
map[string]interface{}{"typ": "JWT"},
jwssigner.NewJWSSigner(
signerKeyID,
verificationMethod.ID,
string(f.wallet.SignatureType()),
kmsSigner,
),
Expand Down Expand Up @@ -477,12 +485,21 @@ func (f *Flow) signPresentationLDP(
return "", fmt.Errorf("resolve signer did: %w", err)
}

signerKeyID := docResolution.DIDDocument.VerificationMethod[0].ID
verificationMethod := docResolution.DIDDocument.VerificationMethod[0]

var kmsKeyID string

switch f.walletDID.Method {
case jwk.DIDMethod:
kmsKeyID = verificationMethod.JSONWebKey().KeyID
default:
kmsKeyID = strings.Split(verificationMethod.ID, "#")[1]
}

signedVP, err := cryptoSigner.SignPresentation(
&vc.Signer{
Creator: signerKeyID,
KMSKeyID: strings.Split(signerKeyID, "#")[1],
Creator: verificationMethod.ID,
KMSKeyID: kmsKeyID,
SignatureType: signatureType,
SignatureRepresentation: verifiable.SignatureProofValue,
KMS: vcskms.GetAriesKeyManager(
Expand Down Expand Up @@ -514,6 +531,22 @@ func (f *Flow) createIDToken(
presentationSubmission *presexch.PresentationSubmission,
clientID, nonce string,
) (string, error) {
docResolution, err := f.vdrRegistry.Resolve(f.walletDID.String())
if err != nil {
return "", fmt.Errorf("resolve signer did: %w", err)
}

verificationMethod := docResolution.DIDDocument.VerificationMethod[0]

var kmsKeyID string

switch f.walletDID.Method {
case jwk.DIDMethod:
kmsKeyID = verificationMethod.JSONWebKey().KeyID
default:
kmsKeyID = strings.Split(verificationMethod.ID, "#")[1]
}

idToken := &IDTokenClaims{
VPToken: IDTokenVPToken{
PresentationSubmission: presentationSubmission,
Expand All @@ -522,7 +555,7 @@ func (f *Flow) createIDToken(
Exp: time.Now().Unix() + tokenLifetimeSeconds,
Iss: "https://self-issued.me/v2/openid-vc",
Aud: clientID,
Sub: f.walletDID,
Sub: f.walletDID.String(),
Nbf: time.Now().Unix(),
Iat: time.Now().Unix(),
Jti: uuid.NewString(),
Expand All @@ -531,7 +564,7 @@ func (f *Flow) createIDToken(
kmsSigner, err := kmssigner.NewKMSSigner(
f.keyManager,
f.crypto,
strings.Split(f.walletDIDKeyID, "#")[1], // KMS key ID is the fragment of the DID URL
kmsKeyID,
f.wallet.SignatureType(),
nil,
)
Expand All @@ -543,7 +576,7 @@ func (f *Flow) createIDToken(
idToken,
map[string]interface{}{"typ": "JWT"},
jwssigner.NewJWSSigner(
f.walletDIDKeyID,
verificationMethod.ID,
string(f.wallet.SignatureType()),
kmsSigner,
),
Expand Down Expand Up @@ -593,7 +626,7 @@ func (f *Flow) postAuthorizationResponse(ctx context.Context, redirectURI string
)
}

slog.Info("vc presented successfully")
slog.Info("credential presented successfully")

return nil
}
Expand Down
6 changes: 5 additions & 1 deletion component/wallet-cli/pkg/wellknown/wellknown.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"

vdrapi "github.com/trustbloc/did-go/vdr/api"
Expand All @@ -29,7 +30,10 @@ type Service struct {
func (s *Service) GetWellKnownOpenIDConfiguration(
issuerURL string,
) (*issuerv1.WellKnownOpenIDIssuerConfiguration, error) {
// GET /issuer/{profileID}/.well-known/openid-credential-issuer
slog.Info("getting OpenID credential issuer configuration",
"url", issuerURL+"/.well-known/openid-credential-issuer",
)

resp, err := s.HTTPClient.Get(issuerURL + "/.well-known/openid-credential-issuer")
if err != nil {
return nil, fmt.Errorf("get issuer well-known: %w", err)
Expand Down

0 comments on commit c3c85ed

Please sign in to comment.