Skip to content

Commit

Permalink
feat: add support for ldp proof (trustbloc#1593)
Browse files Browse the repository at this point in the history
* feat: some work on ldp

* feat: more temp changes

* feat: issuer from local

* feat: more logic

* feat: clenaup

* feat: ldp proof

* fix: generate

* feat: ldp proof parser

* fix: profo builde

* fix: proof builder

* fix: jwt type

* fix: mocks

* feat: add more tests

* feat: improve coverage
  • Loading branch information
skynet2 authored Feb 7, 2024
1 parent 01cbd2c commit 0f972c9
Show file tree
Hide file tree
Showing 20 changed files with 880 additions and 288 deletions.
355 changes: 178 additions & 177 deletions api/spec/openapi.gen.go

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions cmd/vc-rest/startcmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -826,18 +826,22 @@ func buildEchoHandler(
oidc4civ1.RegisterHandlers(e, oidc4civ1.NewController(&oidc4civ1.Config{
OAuth2Provider: oauthProvider,
StateStore: oidc4ciStateStore,
HTTPClient: getHTTPClient(metricsProvider.ClientOIDC4CIV1),
IssuerInteractionClient: issuerInteractionClient,
ProfileService: issuerProfileSvc,
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: proofChecker,
ClientManager: clientManagerService,
ClientIDSchemeService: clientIDSchemeSvc,
JWTVerifier: proofChecker,
CWTVerifier: proofChecker,
Tracer: conf.Tracer,
IssuerVCSPublicHost: conf.StartupParameters.apiGatewayURL, // use api gateway here, as this endpoint will be called by clients
ExternalHostURL: conf.StartupParameters.hostURLExternal, // use host external as this url will be called internally
AckService: ackService,
JWEEncrypterCreator: jweEncrypterCreator,
CWTVerifier: proofChecker,
DocumentLoader: documentLoader,
Vdr: conf.VDR,
ProofChecker: proofChecker,
LDPProofParser: oidc4civ1.NewDefaultLDPProofParser(),
}))

oidc4vpv1.RegisterHandlers(e, oidc4vpv1.NewController(&oidc4vpv1.Config{
Expand Down
3 changes: 2 additions & 1 deletion component/wallet-cli/pkg/oidc4vci/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ type ProofClaims struct {
type CredentialRequest struct {
Format verifiable.OIDCFormat `json:"format,omitempty"`
Types []string `json:"types"`
Proof Proof `json:"proof,omitempty"`
Proof Proof `json:"proof,omitempty"`
}

type Proof struct {
JWT string `json:"jwt"`
CWT string `json:"cwt"`
ProofType string `json:"proof_type"`
LdpVp any `json:"ldp_vp,omitempty"`
}

type CredentialResponse struct {
Expand Down
11 changes: 10 additions & 1 deletion component/wallet-cli/pkg/oidc4vci/oidc4vci_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,16 @@ func (f *Flow) receiveVC(
Nonce: token.Extra("c_nonce").(string),
}

proof, err := f.proofBuilder.Build(claims, nil, f.signer)
proof, err := f.proofBuilder.Build(context.TODO(), &CreateProofRequest{
Signer: f.signer,
CustomHeaders: map[string]interface{}{},
WalletKeyID: f.walletKeyID,
WalletKeyType: f.walletKeyType,
Claims: claims,
VDR: f.vdrRegistry,
WalletDID: f.wallet.DIDs()[0].ID,
CredentialIssuer: credentialIssuer,
})
if err != nil {
return nil, fmt.Errorf("build proof: %w", err)
}
Expand Down
108 changes: 85 additions & 23 deletions component/wallet-cli/pkg/oidc4vci/proof.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package oidc4vci

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

"github.com/fxamacker/cbor/v2"
"github.com/piprate/json-gold/ld"
"github.com/samber/lo"
vdrapi "github.com/trustbloc/did-go/vdr/api"
"github.com/trustbloc/kms-go/doc/jose"
"github.com/trustbloc/kms-go/spi/kms"
"github.com/trustbloc/vc-go/cwt"
"github.com/trustbloc/vc-go/dataintegrity"
"github.com/trustbloc/vc-go/dataintegrity/suite/ecdsa2019"
"github.com/trustbloc/vc-go/jwt"
"github.com/trustbloc/vc-go/proof"
"github.com/trustbloc/vc-go/proof/creator"
Expand All @@ -22,15 +31,15 @@ import (
"github.com/trustbloc/vc-go/proof/ldproofs/ed25519signature2018"
"github.com/trustbloc/vc-go/proof/ldproofs/ed25519signature2020"
"github.com/trustbloc/vc-go/proof/ldproofs/jsonwebsignature2020"
"github.com/trustbloc/vc-go/verifiable"
cwt2 "github.com/trustbloc/vc-go/verifiable/cwt"
"github.com/veraison/go-cose"
)

type ProofBuilder interface {
Build(
claims *ProofClaims,
customerHeaders map[string]interface{},
signer jose.Signer,
ctx context.Context,
req *CreateProofRequest,
) (*Proof, error)
}

Expand Down Expand Up @@ -70,18 +79,17 @@ func (b *CWTProofBuilder) newProofCreator(signer jose.Signer) (*creator.ProofCre
}

func (b *CWTProofBuilder) Build(
claims *ProofClaims,
_ map[string]interface{},
signer jose.Signer,
ctx context.Context,
req *CreateProofRequest,
) (*Proof, error) {
encoded, err := cbor.Marshal(claims)
encoded, err := cbor.Marshal(req.Claims)
if err != nil {
return nil, fmt.Errorf("marshal proof claims: %w", err)
}

proofCreator, descriptors := b.newProofCreator(signer)
proofCreator, descriptors := b.newProofCreator(req.Signer)

algo, _ := signer.Headers().Algorithm()
algo, _ := req.Signer.Headers().Algorithm()
var targetAlgo cose.Algorithm
for _, d := range descriptors {
if d.JWTAlgorithm() == algo && d.CWTAlgorithm() != 0 {
Expand All @@ -93,7 +101,7 @@ func (b *CWTProofBuilder) Build(
return nil, fmt.Errorf("unsupported cosg algorithm: %s", algo)
}

keyID, _ := signer.Headers().KeyID()
keyID, _ := req.Signer.Headers().KeyID()
msg := &cose.Sign1Message{
Headers: cose.Headers{
Protected: cose.ProtectedHeader{
Expand Down Expand Up @@ -136,19 +144,17 @@ type JWTProofBuilder struct {
}

type JWTProofFn func(
claims *ProofClaims,
headers map[string]interface{},
proofSigner jose.Signer,
ctx context.Context,
req *CreateProofRequest,
) (string, error)

func NewJWTProofBuilder() *JWTProofBuilder {
return &JWTProofBuilder{
proofFn: func(
claims *ProofClaims,
headers map[string]interface{},
proofSigner jose.Signer,
ctx context.Context,
req *CreateProofRequest,
) (string, error) {
signedJWT, jwtErr := jwt.NewJoseSigned(claims, headers, proofSigner)
signedJWT, jwtErr := jwt.NewJoseSigned(req.Claims, req.CustomHeaders, req.Signer)
if jwtErr != nil {
return "", fmt.Errorf("create signed jwt: %w", jwtErr)
}
Expand All @@ -172,18 +178,17 @@ func (b *JWTProofBuilder) WithCustomProofFn(
}

func (b *JWTProofBuilder) Build(
claims *ProofClaims,
customHeaders map[string]interface{},
signer jose.Signer,
ctx context.Context,
req *CreateProofRequest,
) (*Proof, error) {
headers := map[string]interface{}{
jose.HeaderType: jwtProofTypeHeader,
}
for k, v := range customHeaders {
headers[k] = v
for k, v := range headers {
req.CustomHeaders[k] = v
}

jws, err := b.proofFn(claims, headers, signer)
jws, err := b.proofFn(ctx, req)
if err != nil {
return nil, fmt.Errorf("build proof: %w", err)
}
Expand All @@ -193,3 +198,60 @@ func (b *JWTProofBuilder) Build(
ProofType: "jwt",
}, nil
}

type LDPProofBuilder struct {
proofFn JWTProofFn
}

func NewLDPProofBuilder() *LDPProofBuilder {
return &LDPProofBuilder{}
}

func (b *LDPProofBuilder) Build(
_ context.Context,
req *CreateProofRequest,
) (*Proof, error) {
pres, err := verifiable.NewPresentation()
if err != nil {
return nil, fmt.Errorf("new presentation: %w", err)
}

signerSuite := ecdsa2019.NewSignerInitializer(&ecdsa2019.SignerInitializerOptions{
SignerGetter: ecdsa2019.WithStaticSigner(req.Signer),
LDDocumentLoader: ld.NewDefaultDocumentLoader(http.DefaultClient),
})

signer, err := dataintegrity.NewSigner(&dataintegrity.Options{
DIDResolver: req.VDR,
}, signerSuite)
if err != nil {
return nil, fmt.Errorf("new signer: %w", err)
}

pres.Holder = req.WalletDID
if err = pres.AddDataIntegrityProof(&verifiable.DataIntegrityProofContext{
SigningKeyID: fmt.Sprintf("%s#%s", req.WalletDID, req.WalletKeyID),
CryptoSuite: ecdsa2019.SuiteType,
Created: lo.ToPtr(time.Now().UTC()),
Domain: req.CredentialIssuer,
Challenge: req.Claims.Nonce,
}, signer); err != nil {
return nil, fmt.Errorf("add data integrity proof: %w", err)
}

return &Proof{
LdpVp: pres,
ProofType: "ldp_vp",
}, nil
}

type CreateProofRequest struct {
Signer jose.Signer
CustomHeaders map[string]interface{}
WalletKeyID string
WalletDID string
WalletKeyType kms.KeyType
Claims *ProofClaims
VDR vdrapi.Registry
CredentialIssuer string
}
4 changes: 4 additions & 0 deletions docs/v1/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1923,6 +1923,10 @@ components:
cwt:
type: string
description: REQUIRED if proof_type equals cwt. Signed CWT as proof of key possession.
ldp_vp:
type: object
nullable: true
description: REQUIRED if proof_type equals ldp_vp. Linked Data Proof as proof of key possession.
required:
- proof_type
AckRequest:
Expand Down
10 changes: 5 additions & 5 deletions pkg/doc/vc/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func (c *Crypto) NewJWTSigned(claims interface{}, signerData *vc.Signer) (string
return "", fmt.Errorf("get jwt algo name: %w", err)
}

signer, _, err := c.getSigner(signerData.KMSKeyID, signerData.KMS, signerData.SignatureType)
signer, _, err := c.GetSigner(signerData.KMSKeyID, signerData.KMS, signerData.SignatureType)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -268,7 +268,7 @@ func (c *Crypto) signCredentialJWT(
signatureType = signOpts.SignatureType
}

s, _, err := c.getSigner(signerData.KMSKeyID, signerData.KMS, signatureType)
s, _, err := c.GetSigner(signerData.KMSKeyID, signerData.KMS, signatureType)
if err != nil {
return nil, fmt.Errorf("getting signer for JWS: %w", err)
}
Expand Down Expand Up @@ -387,7 +387,7 @@ func (c *Crypto) SignPresentation(signerData *vc.Signer, vp *verifiable.Presenta
func (c *Crypto) getLinkedDataProofContext(signerData *vc.Signer, km keyManager,
signatureType vcsverifiable.SignatureType, proofPurpose string,
signRep verifiable.SignatureRepresentation, opts *signingOpts) (*verifiable.LinkedDataProofContext, error) {
s, _, err := c.getSigner(signerData.KMSKeyID, km, signatureType)
s, _, err := c.GetSigner(signerData.KMSKeyID, km, signatureType)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -434,11 +434,11 @@ func (c *Crypto) getLinkedDataProofContext(signerData *vc.Signer, km keyManager,
return signingCtx, nil
}

// getSigner returns signer and verification method based on profile and signing opts
// GetSigner returns signer and verification method based on profile and signing opts
// verificationMethod from opts takes priority to create signer and verification method.
//
//nolint:unparam
func (c *Crypto) getSigner(
func (c *Crypto) GetSigner(
kmsKeyID string,
km keyManager,
signatureType vcsverifiable.SignatureType,
Expand Down
2 changes: 1 addition & 1 deletion pkg/doc/vc/crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -993,7 +993,7 @@ func TestCrypto_NewJWTSigned(t *testing.T) {
wantErr: true,
},
{
name: "Error getSigner",
name: "Error GetSigner",
fields: fields{
vdr: &vdrmock.VDRegistry{ResolveValue: createDIDDoc("did:trustbloc:abc")},
documentLoader: testutil.DocumentLoader(t),
Expand Down
2 changes: 1 addition & 1 deletion pkg/doc/vc/crypto/dataIntegrity.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (c *Crypto) signCredentialLDPDataIntegrity(signerData *vc.Signer,
signatureType = signOpts.SignatureType
}

ariesSigner, _, err := c.getSigner(signerData.KMSKeyID, signerData.KMS, signatureType)
ariesSigner, _, err := c.GetSigner(signerData.KMSKeyID, signerData.KMS, signatureType)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 0f972c9

Please sign in to comment.