Skip to content

Commit

Permalink
feat(sdk): support for ldp_vp (#822)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrii Holovko <[email protected]>
  • Loading branch information
aholovko authored Oct 28, 2024
1 parent 9a9e49e commit 18186b9
Show file tree
Hide file tree
Showing 9 changed files with 593 additions and 64 deletions.
21 changes: 19 additions & 2 deletions demo/app/lib/scenarios/handle_openid_vp_flow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ SPDX-License-Identifier: Apache-2.0
*/

import 'dart:developer';
import 'dart:convert';
import 'package:app/wallet_sdk/wallet_sdk.dart';
import 'package:app/views/custom_error.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -51,8 +52,24 @@ void handleOpenIDVpFlow(BuildContext context, String qrCodeURL) async {
for (var cred in credentials) {
log('multi cred flow $submission and ${credentials.length}');
for (var inputDescriptor in submission.inputDescriptors) {
Map<String, dynamic> payload = Jwt.parseJwt(cred);
if (inputDescriptor.matchedVCsID.contains(payload['jti'])) {
Map<String, dynamic>? payload;
try {
payload = Jwt.parseJwt(cred);
} catch (jwtError) {
try {
payload = json.decode(cred) as Map<String, dynamic>;
} catch (jsonError) {
log('error while parsing cred as json: $jsonError');
continue;
}
}
var key = payload['jti'] ?? payload['id'];
if (key == null) {
log('no key found in cred payload');
continue;
}
if (inputDescriptor.matchedVCsID.contains(key)) {
log('matched vc with id $key added to list');
var credentialDisplayData = storedCredentials
.where((element) => cred.contains(element.value.rawCredential))
.map((e) => e.value)
Expand Down
196 changes: 196 additions & 0 deletions pkg/ldproof/ldproof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
Copyright Gen Digital Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package ldproof

import (
"crypto"
"encoding/base64"
"fmt"

"github.com/piprate/json-gold/ld"
diddoc "github.com/trustbloc/did-go/doc/did"
"github.com/trustbloc/did-go/doc/ld/processor"
"github.com/trustbloc/kms-go/spi/kms"
"github.com/trustbloc/vc-go/presexch"
"github.com/trustbloc/vc-go/proof"
"github.com/trustbloc/vc-go/proof/creator"
"github.com/trustbloc/vc-go/proof/ldproofs/ecdsasecp256k1signature2019"
"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"

"github.com/trustbloc/wallet-sdk/pkg/api"
)

const (
proofPurpose = "authentication"
)

var supportedLDProofTypes = map[string]proof.LDProofDescriptor{
ecdsasecp256k1signature2019.ProofType: ecdsasecp256k1signature2019.New(),
ed25519signature2018.ProofType: ed25519signature2018.New(),
ed25519signature2020.ProofType: ed25519signature2020.New(),
jsonwebsignature2020.ProofType: jsonwebsignature2020.New(),
}

// LDProof implements functionality of adding linked data proof to the verifiable presentation.
type LDProof struct {
crypto api.Crypto
documentLoader ld.DocumentLoader
ldProofDescriptor proof.LDProofDescriptor
keyType kms.KeyType
}

// New returns a new instance of LDProof.
func New(
crypto api.Crypto,
documentLoader ld.DocumentLoader,
ldpVPFormat *presexch.LdpType,
keyType kms.KeyType,
) (*LDProof, error) {
var (
proofDesc proof.LDProofDescriptor
found bool
)

for _, proofType := range ldpVPFormat.ProofType {
if proofDesc, found = supportedLDProofTypes[proofType]; found && isKeyTypeSupported(proofDesc, keyType) {
break
}
found = false
}

if !found {
return nil, fmt.Errorf("no supported linked data proof found")
}

return &LDProof{
crypto: crypto,
documentLoader: documentLoader,
ldProofDescriptor: proofDesc,
keyType: keyType,
}, nil
}

func isKeyTypeSupported(ldProof proof.LDProofDescriptor, keyType kms.KeyType) bool {
for _, vm := range ldProof.SupportedVerificationMethods() {
if vm.KMSKeyType == keyType {
return true
}
}

return false
}

// Add adds linked data proof to the verifiable presentation.
func (p *LDProof) Add(vp *verifiable.Presentation, opts ...Opt) error {
o := &options{}

for _, opt := range opts {
opt(o)
}

if err := p.validateSigningVerificationMethod(o); err != nil {
return err
}

signer, err := p.createSigner(o)
if err != nil {
return err
}

p.updatePresentationContext(vp)

proofContext := &verifiable.LinkedDataProofContext{
SignatureType: p.ldProofDescriptor.ProofType(),
ProofCreator: creator.New(creator.WithLDProofType(p.ldProofDescriptor, signer)),
KeyType: p.keyType,
SignatureRepresentation: verifiable.SignatureProofValue,
VerificationMethod: o.signingVM.ID,
Challenge: o.nonce,
Domain: o.domain,
Purpose: proofPurpose,
}

return vp.AddLinkedDataProof(
proofContext,
processor.WithDocumentLoader(p.documentLoader),
)
}

func (p *LDProof) validateSigningVerificationMethod(opts *options) error {
if opts.signingVM == nil {
return fmt.Errorf("missing signing verification method")
}

if jwk := opts.signingVM.JSONWebKey(); jwk == nil {
return fmt.Errorf("missing jwk for %s verification method", opts.signingVM.ID)
}

return nil
}

func (p *LDProof) createSigner(opts *options) (*cryptoSigner, error) {
jwk := opts.signingVM.JSONWebKey()

tb, err := jwk.Thumbprint(crypto.SHA256)
if err != nil {
return nil, fmt.Errorf("create crypto thumbprint for jwk: %w", err)
}

return &cryptoSigner{
crypto: p.crypto,
keyID: base64.RawURLEncoding.EncodeToString(tb),
}, nil
}

func (p *LDProof) updatePresentationContext(vp *verifiable.Presentation) {
vp.Context = append(vp.Context,
"https://www.w3.org/ns/credentials/examples/v2",
"https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json",
)
}

type options struct {
signingVM *diddoc.VerificationMethod
nonce string
domain string
}

// Opt is an option for adding linked data proof.
type Opt func(opts *options)

// WithSigningVM sets signing verification method.
func WithSigningVM(vm *diddoc.VerificationMethod) Opt {
return func(opts *options) {
opts.signingVM = vm
}
}

// WithNonce sets nonce.
func WithNonce(nonce string) Opt {
return func(opts *options) {
opts.nonce = nonce
}
}

// WithDomain sets domain.
func WithDomain(domain string) Opt {
return func(opts *options) {
opts.domain = domain
}
}

type cryptoSigner struct {
crypto api.Crypto
keyID string
}

func (s *cryptoSigner) Sign(data []byte) ([]byte, error) {
return s.crypto.Sign(data, s.keyID)
}
Loading

0 comments on commit 18186b9

Please sign in to comment.