From c4103cf604a433af1cf1d59559cfb5cfc671969e Mon Sep 17 00:00:00 2001 From: Andrii Soluk Date: Thu, 4 Mar 2021 17:29:53 +0200 Subject: [PATCH] feat: Sign credential - support BBS+ (API) (#2601) Signed-off-by: Andrii Soluk --- pkg/controller/command/verifiable/command.go | 157 +++++++++++++++++- .../command/verifiable/command_test.go | 74 ++++++++- pkg/controller/command/verifiable/models.go | 4 +- .../rest/verifiable/operation_test.go | 4 +- .../middleware/presentproof/middlewares.go | 12 +- pkg/doc/presexch/definition_test.go | 14 +- .../presentproof/presentproof_sdk_steps.go | 19 +-- 7 files changed, 242 insertions(+), 42 deletions(-) diff --git a/pkg/controller/command/verifiable/command.go b/pkg/controller/command/verifiable/command.go index 85e5352a1a..f12d1a9768 100644 --- a/pkg/controller/command/verifiable/command.go +++ b/pkg/controller/command/verifiable/command.go @@ -13,14 +13,18 @@ import ( "io" "strings" + "github.com/piprate/json-gold/ld" + "github.com/hyperledger/aries-framework-go/pkg/common/log" "github.com/hyperledger/aries-framework-go/pkg/controller/command" "github.com/hyperledger/aries-framework-go/pkg/controller/internal/cmdutil" ariescrypto "github.com/hyperledger/aries-framework-go/pkg/crypto" "github.com/hyperledger/aries-framework-go/pkg/doc/did" + "github.com/hyperledger/aries-framework-go/pkg/doc/presexch" "github.com/hyperledger/aries-framework-go/pkg/doc/signature/jsonld" verifiablesigner "github.com/hyperledger/aries-framework-go/pkg/doc/signature/signer" "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite" + "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/bbsblssignature2020" "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/ed25519signature2018" "github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite/jsonwebsignature2020" "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" @@ -123,6 +127,9 @@ const ( // JSONWebSignature2020 json web signature suite. JSONWebSignature2020 = "JsonWebSignature2020" + // BbsBlsSignature2020 BBS signature suite. + BbsBlsSignature2020 = "BbsBlsSignature2020" + // Ed25519KeyType ed25519 key type. Ed25519KeyType = "Ed25519" @@ -133,6 +140,8 @@ const ( Ed25519VerificationKey = "Ed25519VerificationKey" ) +const bbsContext = "https://w3id.org/security/bbs/v1" + type provable interface { AddLinkedDataProof(context *verifiable.LinkedDataProofContext, jsonldOpts ...jsonld.ProcessorOpts) error } @@ -144,6 +153,7 @@ type keyResolver interface { type kmsSigner struct { keyHandle interface{} crypto ariescrypto.Crypto + bbs bool } func newKMSSigner(keyManager kms.KeyManager, c ariescrypto.Crypto, creator string) (*kmsSigner, error) { @@ -161,7 +171,24 @@ func newKMSSigner(keyManager kms.KeyManager, c ariescrypto.Crypto, creator strin return &kmsSigner{keyHandle: keyHandler, crypto: c}, nil } +func (s *kmsSigner) textToLines(txt string) [][]byte { + lines := strings.Split(txt, "\n") + linesBytes := make([][]byte, 0, len(lines)) + + for i := range lines { + if strings.TrimSpace(lines[i]) != "" { + linesBytes = append(linesBytes, []byte(lines[i])) + } + } + + return linesBytes +} + func (s *kmsSigner) Sign(data []byte) ([]byte, error) { + if s.bbs { + return s.crypto.SignMulti(s.textToLines(string(data)), s.keyHandle) + } + v, err := s.crypto.Sign(data, s.keyHandle) if err != nil { return nil, err @@ -832,13 +859,22 @@ func (o *Command) addLinkedDataProof(p provable, opts *ProofOptions) error { signatureSuite = ed25519signature2018.New(suite.WithSigner(s)) case JSONWebSignature2020: signatureSuite = jsonwebsignature2020.New(suite.WithSigner(s)) + case BbsBlsSignature2020: + s.bbs = true + signatureSuite = bbsblssignature2020.New(suite.WithSigner(s)) default: return fmt.Errorf("signature type unsupported %s", opts.SignatureType) } + signatureRepresentation := verifiable.SignatureJWS + + if opts.SignatureRepresentation == nil { + opts.SignatureRepresentation = &signatureRepresentation + } + signingCtx := &verifiable.LinkedDataProofContext{ VerificationMethod: opts.VerificationMethod, - SignatureRepresentation: verifiable.SignatureJWS, + SignatureRepresentation: *opts.SignatureRepresentation, SignatureType: opts.SignatureType, Suite: signatureSuite, Created: opts.Created, @@ -847,7 +883,12 @@ func (o *Command) addLinkedDataProof(p provable, opts *ProofOptions) error { Purpose: opts.proofPurpose, } - err = p.AddLinkedDataProof(signingCtx) + bbsLoader, err := bbsJSONLDDocumentLoader() + if err != nil { + return err + } + + err = p.AddLinkedDataProof(signingCtx, jsonld.WithDocumentLoader(bbsLoader)) if err != nil { return fmt.Errorf("failed to add linked data proof: %w", err) } @@ -1038,3 +1079,115 @@ func getProofPurpose(method did.VerificationRelationship) (string, error) { return "assertionMethod", nil } + +// TODO: context should not be loaded here, the loader should be defined once for the whole system. +func bbsJSONLDDocumentLoader() (*ld.CachingDocumentLoader, error) { + loader := presexch.CachingJSONLDLoader() + + reader, err := ld.DocumentFromReader(strings.NewReader(contextBBSContent)) + if err != nil { + return nil, err + } + + loader.AddDocument(bbsContext, reader) + + return loader, nil +} + +const contextBBSContent = `{ + "@context": { + "@version": 1.1, + "id": "@id", + "type": "@type", + "ldssk": "https://w3id.org/security#", + "BbsBlsSignature2020": { + "@id": "https://w3id.org/security#BbsBlsSignature2020", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "proofValue": "sec:proofValue", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "BbsBlsSignatureProof2020": { + "@id": "https://w3id.org/security#BbsBlsSignatureProof2020", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "challenge": "sec:challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "xsd:dateTime" + }, + "domain": "sec:domain", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + "id": "@id", + "type": "@type", + "sec": "https://w3id.org/security#", + "assertionMethod": { + "@id": "sec:assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "sec:authenticationMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": { + "@id": "sec:verificationMethod", + "@type": "@id" + } + } + }, + "Bls12381G2Key2020": "ldssk:Bls12381G2Key2020" + } +}` diff --git a/pkg/controller/command/verifiable/command_test.go b/pkg/controller/command/verifiable/command_test.go index 5cc7a45de1..7601de3e65 100644 --- a/pkg/controller/command/verifiable/command_test.go +++ b/pkg/controller/command/verifiable/command_test.go @@ -69,6 +69,35 @@ const vc = ` } ` +const bbsVc = `{ + "@context":[ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://w3id.org/security/bbs/v1" + ], + "credentialSubject":{ + "degree":{ + "degree":"MIT", + "degreeSchool":"MIT school", + "type":"BachelorDegree" + }, + "id":"did:example:b34ca6cd37bbf23", + "name":"Jayden Doe", + "spouse":"did:example:c276e12ec21ebfeb1f712ebc6f1" + }, + "description":"Government of Example Permanent Resident Card.", + "expirationDate":"2022-03-04T11:53:29.728412319+02:00", + "id":"https://issuer.oidp.uscis.gov/credentials/83627465", + "identifier":"83627465", + "issuanceDate":"2021-03-04T11:53:29.728412269+02:00", + "issuer":"did:example:489398593", + "name":"Permanent Resident Card", + "type":[ + "VerifiableCredential", + "UniversityDegreeCredential" + ] +}` + //nolint:lll const vcWithDIDNotAvailble = `{ "@context":[ @@ -2073,6 +2102,47 @@ func TestCommand_SignCredential(t *testing.T) { require.Contains(t, vc.Proofs[0]["type"], "JsonWebSignature2020") }) + t.Run("test sign credential with proof options - success (BbsBlsSignature2020)", func(t *testing.T) { + createdTime := time.Now().AddDate(-1, 0, 0) + signatureRepresentation := verifiable.SignatureProofValue + + req := SignCredentialRequest{ + Credential: []byte(bbsVc), + ProofOptions: &ProofOptions{ + Domain: "issuer.example.com", + Challenge: "sample-random-test-value", + SignatureRepresentation: &signatureRepresentation, + Created: &createdTime, + SignatureType: BbsBlsSignature2020, + }, + } + + reqBytes, err := json.Marshal(req) + require.NoError(t, err) + + var b bytes.Buffer + err = cmd.SignCredential(&b, bytes.NewBuffer(reqBytes)) + require.NoError(t, err) + + // verify response + var response SignCredentialResponse + err = json.NewDecoder(&b).Decode(&response) + require.NoError(t, err) + require.NotEmpty(t, response) + + vc, err := verifiable.ParseCredential(response.VerifiableCredential, verifiable.WithDisabledProofCheck()) + + require.NoError(t, err) + require.NotNil(t, vc) + require.NotEmpty(t, vc.Proofs) + require.Len(t, vc.Proofs, 1) + require.Equal(t, vc.Proofs[0]["challenge"], req.Challenge) + require.Equal(t, vc.Proofs[0]["domain"], req.Domain) + require.Equal(t, vc.Proofs[0]["proofPurpose"], "assertionMethod") + require.Contains(t, vc.Proofs[0]["created"], strconv.Itoa(req.Created.Year())) + require.Contains(t, vc.Proofs[0]["type"], "BbsBlsSignature2020") + }) + t.Run("test sign credential with proof options - success (ed25519 jsonwebsignature)", func(t *testing.T) { createdTime := time.Now().AddDate(-1, 0, 0) req := SignCredentialRequest{ @@ -2556,9 +2626,7 @@ func signVCWithBBS(r *require.Assertions, vc *verifiable.Credential) string { pubKeyBytes, err := pubKey.Marshal() r.NoError(err) - methodID := fingerprint.KeyFingerprint(0xeb, pubKeyBytes) - didKey := fmt.Sprintf("did:key:%s", methodID) - keyID := fmt.Sprintf("%s#%s", didKey, methodID) + didKey, keyID := fingerprint.CreateDIDKeyByCode(fingerprint.BLS12381g2PubKeyMultiCodec, pubKeyBytes) bbsSigner, err := newBBSSigner(privKey) r.NoError(err) diff --git a/pkg/controller/command/verifiable/models.go b/pkg/controller/command/verifiable/models.go index ef81828205..b63e45530b 100644 --- a/pkg/controller/command/verifiable/models.go +++ b/pkg/controller/command/verifiable/models.go @@ -10,6 +10,7 @@ import ( "encoding/json" "time" + docverifiable "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" "github.com/hyperledger/aries-framework-go/pkg/store/verifiable" ) @@ -40,7 +41,8 @@ type IDArg struct { // ProofOptions is model to allow the dynamic proofing options by the user. type ProofOptions struct { // VerificationMethod is the URI of the verificationMethod used for the proof. - VerificationMethod string `json:"verificationMethod,omitempty"` + VerificationMethod string `json:"verificationMethod,omitempty"` + SignatureRepresentation *docverifiable.SignatureRepresentation `json:"signatureRepresentation,omitempty"` // Created date of the proof. If omitted current system time will be used. Created *time.Time `json:"created,omitempty"` // Domain is operational domain of a digital proof. diff --git a/pkg/controller/rest/verifiable/operation_test.go b/pkg/controller/rest/verifiable/operation_test.go index eea2638f09..f4f976c551 100644 --- a/pkg/controller/rest/verifiable/operation_test.go +++ b/pkg/controller/rest/verifiable/operation_test.go @@ -1400,9 +1400,7 @@ func signVCWithBBS(r *require.Assertions, vc *verifiableapi.Credential) string { pubKeyBytes, err := pubKey.Marshal() r.NoError(err) - methodID := fingerprint.KeyFingerprint(0xeb, pubKeyBytes) - didKey := fmt.Sprintf("did:key:%s", methodID) - keyID := fmt.Sprintf("%s#%s", didKey, methodID) + didKey, keyID := fingerprint.CreateDIDKeyByCode(fingerprint.BLS12381g2PubKeyMultiCodec, pubKeyBytes) bbsSigner, err := newBBSSigner(privKey) r.NoError(err) diff --git a/pkg/didcomm/protocol/middleware/presentproof/middlewares.go b/pkg/didcomm/protocol/middleware/presentproof/middlewares.go index 2ce4f5df4f..bdee6d3bd7 100644 --- a/pkg/didcomm/protocol/middleware/presentproof/middlewares.go +++ b/pkg/didcomm/protocol/middleware/presentproof/middlewares.go @@ -138,8 +138,6 @@ func defaultPdOptions() *pdOptions { // AddBBSProofFn add BBS+ proof to the Presentation. func AddBBSProofFn(p Provider) func(presentation *verifiable.Presentation) error { - const bls12381g2pub = 0xeb - km, cr := p.KMS(), p.Crypto() return func(presentation *verifiable.Presentation) error { @@ -153,19 +151,15 @@ func AddBBSProofFn(p Provider) func(presentation *verifiable.Presentation) error return err } - methodID := fingerprint.KeyFingerprint(bls12381g2pub, pubKey) - didKey := fmt.Sprintf("%s#%s", fmt.Sprintf("did:key:%s", methodID), methodID) + _, didKey := fingerprint.CreateDIDKeyByCode(fingerprint.BLS12381g2PubKeyMultiCodec, pubKey) presentation.Context = append(presentation.Context, bbsContext) return presentation.AddLinkedDataProof(&verifiable.LinkedDataProofContext{ SignatureType: "BbsBlsSignature2020", SignatureRepresentation: verifiable.SignatureProofValue, - Suite: bbsblssignature2020.New( - suite.WithSigner(newBBSSigner(km, cr, kid)), - suite.WithVerifier(bbsblssignature2020.NewG2PublicKeyVerifier()), - ), - VerificationMethod: didKey, + Suite: bbsblssignature2020.New(suite.WithSigner(newBBSSigner(km, cr, kid))), + VerificationMethod: didKey, }, jsonld.WithDocumentLoader(bbsLoader)) } } diff --git a/pkg/doc/presexch/definition_test.go b/pkg/doc/presexch/definition_test.go index c4a2862419..3a8ccf4662 100644 --- a/pkg/doc/presexch/definition_test.go +++ b/pkg/doc/presexch/definition_test.go @@ -499,11 +499,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { require.NoError(t, vc.AddLinkedDataProof(&verifiable.LinkedDataProofContext{ SignatureType: "BbsBlsSignature2020", SignatureRepresentation: verifiable.SignatureProofValue, - Suite: bbsblssignature2020.New( - suite.WithSigner(signer), - suite.WithVerifier(bbsblssignature2020.NewG2PublicKeyVerifier()), - ), - VerificationMethod: "did:example:123456#key1", + Suite: bbsblssignature2020.New(suite.WithSigner(signer)), + VerificationMethod: "did:example:123456#key1", }, jsonld.WithDocumentLoader(createTestJSONLDDocumentLoader()))) vp, err := pd.CreateVP([]*verifiable.Credential{vc}, @@ -623,11 +620,8 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { require.NoError(t, vc.AddLinkedDataProof(&verifiable.LinkedDataProofContext{ SignatureType: "BbsBlsSignature2020", SignatureRepresentation: verifiable.SignatureProofValue, - Suite: bbsblssignature2020.New( - suite.WithSigner(signer), - suite.WithVerifier(bbsblssignature2020.NewG2PublicKeyVerifier()), - ), - VerificationMethod: "did:example:123456#key1", + Suite: bbsblssignature2020.New(suite.WithSigner(signer)), + VerificationMethod: "did:example:123456#key1", }, jsonld.WithDocumentLoader(createTestJSONLDDocumentLoader()))) vp, err := pd.CreateVP([]*verifiable.Credential{vc}, diff --git a/test/bdd/pkg/presentproof/presentproof_sdk_steps.go b/test/bdd/pkg/presentproof/presentproof_sdk_steps.go index e1f4f93c31..77eaf96a0d 100644 --- a/test/bdd/pkg/presentproof/presentproof_sdk_steps.go +++ b/test/bdd/pkg/presentproof/presentproof_sdk_steps.go @@ -352,8 +352,6 @@ func (a *SDKSteps) acceptRequestPresentation(prover, verifier string) error { } func (a *SDKSteps) acceptRequestPresentationBBS(prover, _, proof string) error { // nolint: funlen - const bls12381g2pub = 0xeb - PIID, err := a.getActionID(prover) if err != nil { return err @@ -367,8 +365,7 @@ func (a *SDKSteps) acceptRequestPresentationBBS(prover, _, proof string) error { return err } - methodID := fingerprint.KeyFingerprint(bls12381g2pub, pubKey) - didKey := fmt.Sprintf("%s#%s", fmt.Sprintf("did:key:%s", methodID), methodID) + _, didKey := fingerprint.CreateDIDKeyByCode(fingerprint.BLS12381g2PubKeyMultiCodec, pubKey) vc := &verifiable.Credential{ ID: "https://issuer.oidp.uscis.gov/credentials/83627465", @@ -416,11 +413,8 @@ func (a *SDKSteps) acceptRequestPresentationBBS(prover, _, proof string) error { err = vc.AddLinkedDataProof(&verifiable.LinkedDataProofContext{ SignatureType: "BbsBlsSignature2020", SignatureRepresentation: verifiable.SignatureProofValue, - Suite: bbsblssignature2020.New( - suite.WithSigner(newBBSSigner(km, cr, kid)), - suite.WithVerifier(bbsblssignature2020.NewG2PublicKeyVerifier()), - ), - VerificationMethod: didKey, + Suite: bbsblssignature2020.New(suite.WithSigner(newBBSSigner(km, cr, kid))), + VerificationMethod: didKey, }, jsonld.WithDocumentLoader(createTestJSONLDDocumentLoader())) if err != nil { @@ -440,11 +434,8 @@ func (a *SDKSteps) acceptRequestPresentationBBS(prover, _, proof string) error { return presentation.AddLinkedDataProof(&verifiable.LinkedDataProofContext{ SignatureType: "BbsBlsSignature2020", SignatureRepresentation: verifiable.SignatureProofValue, - Suite: bbsblssignature2020.New( - suite.WithSigner(newBBSSigner(km, cr, kid)), - suite.WithVerifier(bbsblssignature2020.NewG2PublicKeyVerifier()), - ), - VerificationMethod: didKey, + Suite: bbsblssignature2020.New(suite.WithSigner(newBBSSigner(km, cr, kid))), + VerificationMethod: didKey, }, jsonld.WithDocumentLoader(createTestJSONLDDocumentLoader())) } }