github.com/hyperledger/aries-framework-go@v0.3.2/pkg/didcomm/protocol/middleware/presentproof/middlewares.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package presentproof
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"strings"
    14  
    15  	"github.com/google/uuid"
    16  	"github.com/piprate/json-gold/ld"
    17  
    18  	"github.com/hyperledger/aries-framework-go/pkg/crypto"
    19  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    20  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
    21  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/presentproof"
    22  	"github.com/hyperledger/aries-framework-go/pkg/doc/presexch"
    23  	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/jsonld"
    24  	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite"
    25  	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/bbsblssignature2020"
    26  	"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
    27  	vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
    28  	"github.com/hyperledger/aries-framework-go/pkg/kms"
    29  	storeverifiable "github.com/hyperledger/aries-framework-go/pkg/store/verifiable"
    30  	"github.com/hyperledger/aries-framework-go/pkg/vdr/fingerprint"
    31  )
    32  
    33  const (
    34  	stateNamePresentationReceived = "presentation-received"
    35  	stateNameRequestReceived      = "request-received"
    36  	myDIDKey                      = "myDID"
    37  	theirDIDKey                   = "theirDID"
    38  	namesKey                      = "names"
    39  
    40  	mimeTypeApplicationLdJSON = "application/ld+json"
    41  	mimeTypeAll               = "*"
    42  
    43  	peDefinitionFormat = "dif/presentation-exchange/definitions@v1.0"
    44  	peSubmissionFormat = "dif/presentation-exchange/submission@v1.0"
    45  	bbsContext         = "https://w3id.org/security/bbs/v1"
    46  )
    47  
    48  // Metadata is an alias to the original Metadata.
    49  type Metadata presentproof.Metadata
    50  
    51  // Provider contains dependencies for the SavePresentation middleware function.
    52  type Provider interface {
    53  	VerifiableStore() storeverifiable.Store
    54  	VDRegistry() vdrapi.Registry
    55  	KMS() kms.KeyManager
    56  	Crypto() crypto.Crypto
    57  	JSONLDDocumentLoader() ld.DocumentLoader
    58  }
    59  
    60  // SavePresentation the helper function for the present proof protocol which saves the presentations.
    61  func SavePresentation(p Provider) presentproof.Middleware {
    62  	vdr := p.VDRegistry()
    63  	store := p.VerifiableStore()
    64  	documentLoader := p.JSONLDDocumentLoader()
    65  
    66  	return func(next presentproof.Handler) presentproof.Handler {
    67  		return presentproof.HandlerFunc(func(metadata presentproof.Metadata) error {
    68  			if metadata.StateName() != stateNamePresentationReceived {
    69  				return next.Handle(metadata)
    70  			}
    71  
    72  			msg := metadata.Message()
    73  
    74  			attachments, err := getAttachments(msg)
    75  			if err != nil {
    76  				return fmt.Errorf("get attachments: %w", err)
    77  			}
    78  
    79  			presentations, err := toVerifiablePresentation(vdr, attachments, documentLoader)
    80  			if err != nil {
    81  				return fmt.Errorf("to verifiable presentation: %w", err)
    82  			}
    83  
    84  			if len(presentations) == 0 {
    85  				return errors.New("presentations were not provided")
    86  			}
    87  
    88  			var names []string
    89  			properties := metadata.Properties()
    90  
    91  			// nolint: errcheck
    92  			myDID, _ := properties[myDIDKey].(string)
    93  			// nolint: errcheck
    94  			theirDID, _ := properties[theirDIDKey].(string)
    95  			if myDID == "" || theirDID == "" {
    96  				return errors.New("myDID or theirDID is absent")
    97  			}
    98  
    99  			for i, presentation := range presentations {
   100  				names = append(names, getName(i, presentation.ID, metadata))
   101  
   102  				err := store.SavePresentation(names[i], presentation,
   103  					storeverifiable.WithMyDID(myDID),
   104  					storeverifiable.WithTheirDID(theirDID),
   105  				)
   106  				if err != nil {
   107  					return fmt.Errorf("save presentation: %w", err)
   108  				}
   109  			}
   110  
   111  			properties[namesKey] = names
   112  
   113  			return next.Handle(metadata)
   114  		})
   115  	}
   116  }
   117  
   118  func getAttachments(msg service.DIDCommMsg) ([]decorator.AttachmentData, error) {
   119  	if strings.HasPrefix(msg.Type(), presentproof.SpecV3) {
   120  		presentation := presentproof.PresentationV3{}
   121  		if err := msg.Decode(&presentation); err != nil {
   122  			return nil, fmt.Errorf("decode: %w", err)
   123  		}
   124  
   125  		return filterByMediaType(presentation.Attachments, mimeTypeAll), nil
   126  	}
   127  
   128  	presentation := presentproof.PresentationV2{}
   129  	if err := msg.Decode(&presentation); err != nil {
   130  		return nil, fmt.Errorf("decode: %w", err)
   131  	}
   132  
   133  	return filterByMimeType(presentation.PresentationsAttach, mimeTypeAll), nil
   134  }
   135  
   136  type presentationExchangePayload struct {
   137  	Challenge              string                           `json:"challenge"`
   138  	Domain                 string                           `json:"domain"`
   139  	PresentationDefinition *presexch.PresentationDefinition `json:"presentation_definition"`
   140  }
   141  
   142  // OptPD represents option function for the PresentationDefinition middleware.
   143  type OptPD func(o *pdOptions)
   144  
   145  // WithAddProofFn allows providing function that will sign the Presentation.
   146  func WithAddProofFn(sign func(presentation *verifiable.Presentation) error) OptPD {
   147  	return func(o *pdOptions) {
   148  		o.addProof = sign
   149  	}
   150  }
   151  
   152  type pdOptions struct {
   153  	addProof func(presentation *verifiable.Presentation) error
   154  }
   155  
   156  func defaultPdOptions() *pdOptions {
   157  	return &pdOptions{
   158  		addProof: func(presentation *verifiable.Presentation) error {
   159  			return nil
   160  		},
   161  	}
   162  }
   163  
   164  // AddBBSProofFn add BBS+ proof to the Presentation.
   165  func AddBBSProofFn(p Provider) func(presentation *verifiable.Presentation) error {
   166  	km, cr := p.KMS(), p.Crypto()
   167  	documentLoader := p.JSONLDDocumentLoader()
   168  
   169  	return func(presentation *verifiable.Presentation) error {
   170  		kid, pubKey, err := km.CreateAndExportPubKeyBytes(kms.BLS12381G2Type)
   171  		if err != nil {
   172  			return err
   173  		}
   174  
   175  		_, didKey := fingerprint.CreateDIDKeyByCode(fingerprint.BLS12381g2PubKeyMultiCodec, pubKey)
   176  
   177  		presentation.Context = append(presentation.Context, bbsContext)
   178  
   179  		return presentation.AddLinkedDataProof(&verifiable.LinkedDataProofContext{
   180  			SignatureType:           "BbsBlsSignature2020",
   181  			SignatureRepresentation: verifiable.SignatureProofValue,
   182  			Suite:                   bbsblssignature2020.New(suite.WithSigner(newBBSSigner(km, cr, kid))),
   183  			VerificationMethod:      didKey,
   184  		}, jsonld.WithDocumentLoader(documentLoader))
   185  	}
   186  }
   187  
   188  // PresentationDefinition the helper function for the present proof protocol that creates VP based on credentials that
   189  // were provided in the attachments according to the requested presentation definition.
   190  func PresentationDefinition(p Provider, opts ...OptPD) presentproof.Middleware { // nolint: funlen,gocyclo,gocognit
   191  	vdr := p.VDRegistry()
   192  	documentLoader := p.JSONLDDocumentLoader()
   193  
   194  	options := defaultPdOptions()
   195  
   196  	for i := range opts {
   197  		opts[i](options)
   198  	}
   199  
   200  	return func(next presentproof.Handler) presentproof.Handler {
   201  		return presentproof.HandlerFunc(func(metadata presentproof.Metadata) error {
   202  			if metadata.StateName() != stateNameRequestReceived {
   203  				return next.Handle(metadata)
   204  			}
   205  
   206  			var (
   207  				msg         = metadata.Message()
   208  				attachments []decorator.AttachmentData
   209  				src         []byte
   210  				fmtIdx      int
   211  				err         error
   212  			)
   213  
   214  			if strings.HasPrefix(msg.Type(), presentproof.SpecV3) { // nolint: nestif
   215  				request := presentproof.RequestPresentationV3{}
   216  				if err = msg.Decode(&request); err != nil {
   217  					return fmt.Errorf("decode: %w", err)
   218  				}
   219  
   220  				if metadata.PresentationV3() == nil ||
   221  					!hasFormat(toFormats(request.Attachments), peDefinitionFormat) ||
   222  					hasFormat(toFormats(metadata.PresentationV3().Attachments), peSubmissionFormat) {
   223  					return next.Handle(metadata)
   224  				}
   225  
   226  				src, err = getAttachmentByFormatV2(toFormats(request.Attachments),
   227  					request.Attachments, peDefinitionFormat)
   228  
   229  				attachments = filterByMediaType(metadata.PresentationV3().Attachments, mimeTypeApplicationLdJSON)
   230  			} else {
   231  				request := presentproof.RequestPresentationV2{}
   232  				if err = msg.Decode(&request); err != nil {
   233  					return fmt.Errorf("decode: %w", err)
   234  				}
   235  
   236  				if metadata.Presentation() == nil ||
   237  					!hasFormat(request.Formats, peDefinitionFormat) ||
   238  					hasFormat(metadata.Presentation().Formats, peSubmissionFormat) {
   239  					return next.Handle(metadata)
   240  				}
   241  
   242  				src, fmtIdx, err = getAttachmentByFormat(request.Formats,
   243  					request.RequestPresentationsAttach, peDefinitionFormat)
   244  				attachments = filterByMimeType(metadata.Presentation().PresentationsAttach, mimeTypeApplicationLdJSON)
   245  			}
   246  
   247  			if err != nil {
   248  				return fmt.Errorf("get attachment by format: %w", err)
   249  			}
   250  
   251  			var payload *presentationExchangePayload
   252  
   253  			if err = json.Unmarshal(src, &payload); err != nil {
   254  				return fmt.Errorf("unmarshal definition: %w", err)
   255  			}
   256  
   257  			credentials, err := parseCredentials(vdr, attachments, documentLoader)
   258  			if err != nil {
   259  				return fmt.Errorf("parse credentials: %w", err)
   260  			}
   261  
   262  			if len(credentials) > 0 { // nolint: nestif
   263  				presentation, err := payload.PresentationDefinition.CreateVP(credentials, documentLoader,
   264  					verifiable.WithPublicKeyFetcher(verifiable.NewVDRKeyResolver(vdr).PublicKeyFetcher()),
   265  					verifiable.WithJSONLDDocumentLoader(documentLoader))
   266  				if err != nil {
   267  					return fmt.Errorf("create VP: %w", err)
   268  				}
   269  
   270  				signFn := metadata.GetAddProofFn()
   271  				if signFn == nil {
   272  					signFn = options.addProof
   273  				}
   274  
   275  				err = signFn(presentation)
   276  				if err != nil {
   277  					return fmt.Errorf("add proof: %w", err)
   278  				}
   279  
   280  				if strings.HasPrefix(msg.Type(), presentproof.SpecV3) {
   281  					metadata.PresentationV3().Attachments = []decorator.AttachmentV2{{
   282  						ID:        uuid.New().String(),
   283  						MediaType: mimeTypeApplicationLdJSON,
   284  						Data:      decorator.AttachmentData{JSON: presentation},
   285  					}}
   286  				} else {
   287  					newID := uuid.New().String()
   288  
   289  					formats := metadata.Presentation().Formats
   290  					if len(formats) > fmtIdx {
   291  						formats[fmtIdx].AttachID = newID
   292  					}
   293  
   294  					metadata.Presentation().PresentationsAttach = []decorator.Attachment{{
   295  						ID:       newID,
   296  						MimeType: mimeTypeApplicationLdJSON,
   297  						Data:     decorator.AttachmentData{JSON: presentation},
   298  					}}
   299  				}
   300  			}
   301  
   302  			return next.Handle(metadata)
   303  		})
   304  	}
   305  }
   306  
   307  func contains(s []string, e string) bool {
   308  	for _, a := range s {
   309  		if a == e {
   310  			return true
   311  		}
   312  	}
   313  
   314  	return false
   315  }
   316  
   317  func filterByMediaType(attachments []decorator.AttachmentV2, mediaType string) []decorator.AttachmentData {
   318  	var result []decorator.AttachmentData
   319  
   320  	for i := range attachments {
   321  		if attachments[i].MediaType != mediaType && mediaType != mimeTypeAll {
   322  			continue
   323  		}
   324  
   325  		result = append(result, attachments[i].Data)
   326  	}
   327  
   328  	return result
   329  }
   330  
   331  func filterByMimeType(attachments []decorator.Attachment, mimeType string) []decorator.AttachmentData {
   332  	var result []decorator.AttachmentData
   333  
   334  	for i := range attachments {
   335  		if attachments[i].MimeType != mimeType && mimeType != mimeTypeAll {
   336  			continue
   337  		}
   338  
   339  		result = append(result, attachments[i].Data)
   340  	}
   341  
   342  	return result
   343  }
   344  
   345  func parseCredentials(vdr vdrapi.Registry, attachments []decorator.AttachmentData,
   346  	documentLoader ld.DocumentLoader) ([]*verifiable.Credential, error) {
   347  	var credentials []*verifiable.Credential
   348  
   349  	for i := range attachments {
   350  		src, err := attachments[i].Fetch()
   351  		if err != nil {
   352  			return nil, err
   353  		}
   354  
   355  		var types struct {
   356  			Type interface{} `json:"type"`
   357  		}
   358  
   359  		err = json.Unmarshal(src, &types)
   360  		if err != nil {
   361  			return nil, err
   362  		}
   363  
   364  		var credentialTypes []string
   365  
   366  		switch v := types.Type.(type) {
   367  		case string:
   368  			credentialTypes = []string{v}
   369  		case []interface{}:
   370  			for _, e := range v {
   371  				if val, ok := e.(string); ok {
   372  					credentialTypes = append(credentialTypes, val)
   373  				}
   374  			}
   375  		}
   376  
   377  		if !contains(credentialTypes, verifiable.VCType) {
   378  			continue
   379  		}
   380  
   381  		credential, err := verifiable.ParseCredential(src,
   382  			verifiable.WithPublicKeyFetcher(
   383  				verifiable.NewVDRKeyResolver(vdr).PublicKeyFetcher(),
   384  			),
   385  			verifiable.WithJSONLDDocumentLoader(documentLoader),
   386  		)
   387  		if err != nil {
   388  			return nil, err
   389  		}
   390  
   391  		credentials = append(credentials, credential)
   392  	}
   393  
   394  	return credentials, nil
   395  }
   396  
   397  func getAttachmentByFormat(fms []presentproof.Format, attachments []decorator.Attachment, name string,
   398  ) ([]byte, int, error) {
   399  	for fmtIdx, format := range fms {
   400  		if format.Format == name {
   401  			for i := range attachments {
   402  				if attachments[i].ID == format.AttachID {
   403  					data, err := attachments[i].Data.Fetch()
   404  					return data, fmtIdx, err
   405  				}
   406  			}
   407  		}
   408  	}
   409  
   410  	return nil, 0, errors.New("not found")
   411  }
   412  
   413  func getAttachmentByFormatV2(fms []presentproof.Format, attachs []decorator.AttachmentV2, name string) ([]byte, error) {
   414  	for _, format := range fms {
   415  		if format.Format == name {
   416  			for i := range attachs {
   417  				if attachs[i].ID == format.AttachID {
   418  					data, err := attachs[i].Data.Fetch()
   419  					return data, err
   420  				}
   421  			}
   422  		}
   423  	}
   424  
   425  	return nil, errors.New("not found")
   426  }
   427  
   428  func hasFormat(formats []presentproof.Format, format string) bool {
   429  	for _, fm := range formats {
   430  		if fm.Format == format {
   431  			return true
   432  		}
   433  	}
   434  
   435  	return false
   436  }
   437  
   438  func toFormats(attachments []decorator.AttachmentV2) []presentproof.Format {
   439  	var result []presentproof.Format
   440  	for i := range attachments {
   441  		result = append(result, presentproof.Format{AttachID: attachments[i].ID, Format: attachments[i].Format})
   442  	}
   443  
   444  	return result
   445  }
   446  
   447  func getName(idx int, id string, metadata presentproof.Metadata) string {
   448  	name := id
   449  	if len(metadata.PresentationNames()) > idx {
   450  		name = metadata.PresentationNames()[idx]
   451  	}
   452  
   453  	if name != "" {
   454  		return name
   455  	}
   456  
   457  	return uuid.New().String()
   458  }
   459  
   460  func toVerifiablePresentation(vdr vdrapi.Registry, data []decorator.AttachmentData,
   461  	documentLoader ld.DocumentLoader) ([]*verifiable.Presentation, error) {
   462  	var presentations []*verifiable.Presentation
   463  
   464  	for i := range data {
   465  		raw, err := data[i].Fetch()
   466  		if err != nil {
   467  			return nil, fmt.Errorf("fetch: %w", err)
   468  		}
   469  
   470  		presentation, err := verifiable.ParsePresentation(raw,
   471  			verifiable.WithPresPublicKeyFetcher(
   472  				verifiable.NewVDRKeyResolver(vdr).PublicKeyFetcher(),
   473  			),
   474  			verifiable.WithPresJSONLDDocumentLoader(documentLoader),
   475  		)
   476  		if err != nil {
   477  			return nil, fmt.Errorf("parse presentation: %w", err)
   478  		}
   479  
   480  		presentations = append(presentations, presentation)
   481  	}
   482  
   483  	return presentations, nil
   484  }
   485  
   486  type bbsSigner struct {
   487  	km    kms.KeyManager
   488  	cr    crypto.Crypto
   489  	keyID string
   490  }
   491  
   492  func newBBSSigner(km kms.KeyManager, cr crypto.Crypto, keyID string) *bbsSigner {
   493  	return &bbsSigner{km: km, cr: cr, keyID: keyID}
   494  }
   495  
   496  func (s *bbsSigner) Sign(data []byte) ([]byte, error) {
   497  	kh, err := s.km.Get(s.keyID)
   498  	if err != nil {
   499  		return nil, err
   500  	}
   501  
   502  	return s.cr.SignMulti(s.textToLines(string(data)), kh)
   503  }
   504  
   505  func (s *bbsSigner) Alg() string {
   506  	return "Bls12381G2Key2020"
   507  }
   508  
   509  func (s *bbsSigner) textToLines(txt string) [][]byte {
   510  	lines := strings.Split(txt, "\n")
   511  	linesBytes := make([][]byte, 0, len(lines))
   512  
   513  	for i := range lines {
   514  		if strings.TrimSpace(lines[i]) != "" {
   515  			linesBytes = append(linesBytes, []byte(lines[i]))
   516  		}
   517  	}
   518  
   519  	return linesBytes
   520  }