diff --git a/aries_cloudagent/vc/vc_ld/external_suite.py b/aries_cloudagent/vc/vc_ld/external_suite.py new file mode 100644 index 0000000000..a78388a196 --- /dev/null +++ b/aries_cloudagent/vc/vc_ld/external_suite.py @@ -0,0 +1,44 @@ +"""Plugin hook for providing an external signature suite implementation. + +This enables greater control over where JSON-LD credentials are signed without +requiring knowledge of the complexities of the JSON-LD/VC-LDP subsystem. +""" + +from abc import ABC, abstractmethod +from typing import Optional + +from ...core.error import BaseError +from ...core.profile import Profile +from ...wallet.did_info import DIDInfo +from ..ld_proofs.suites.linked_data_proof import LinkedDataProof + + +class ExternalSuiteError(BaseError): + """Raised when an error occurs in an external signature suite provider.""" + + +class ExternalSuiteNotFoundError(ExternalSuiteError): + """Raised when an external signature suite provider is not found. + + This should be raised to prevent falling back to built in suites, if desired. + """ + + +class ExternalSuiteProvider(ABC): + """Plugin hook for providing an external signature suite implementation.""" + + @abstractmethod + async def get_suite( + self, + profile: Profile, + proof_type: str, + proof: dict, + verification_method: str, + did_info: DIDInfo, + ) -> Optional[LinkedDataProof]: + """Get a signature suite for the given proof type and verification method. + + Implementing classes should raise ExternalSuiteNotFoundError if preventing + fallback to built-in suites is desired. Otherwise, return None to indicate + that the implementing class does not support the given proof type. + """ diff --git a/aries_cloudagent/vc/vc_ld/manager.py b/aries_cloudagent/vc/vc_ld/manager.py index 8a306cceea..5e670355f2 100644 --- a/aries_cloudagent/vc/vc_ld/manager.py +++ b/aries_cloudagent/vc/vc_ld/manager.py @@ -1,17 +1,18 @@ """Manager for performing Linked Data Proof signatures over JSON-LD formatted W3C VCs.""" from typing import Dict, List, Optional, Type, Union, cast + from pyld import jsonld from pyld.jsonld import JsonLdProcessor from ...core.profile import Profile +from ...storage.vc_holder.base import VCHolder +from ...storage.vc_holder.vc_record import VCRecord from ...wallet.base import BaseWallet from ...wallet.default_verification_key_strategy import BaseVerificationKeyStrategy from ...wallet.did_info import DIDInfo from ...wallet.error import WalletNotFoundError from ...wallet.key_type import BLS12381G2, ED25519, KeyType -from ...storage.vc_holder.base import VCHolder -from ...storage.vc_holder.vc_record import VCRecord from ..ld_proofs.constants import ( SECURITY_CONTEXT_BBS_URL, SECURITY_CONTEXT_ED25519_2020_URL, @@ -29,11 +30,12 @@ from ..ld_proofs.validation_result import DocumentVerificationResult from ..vc_ld.models.presentation import VerifiablePresentation from ..vc_ld.validation_result import PresentationVerificationResult +from .external_suite import ExternalSuiteNotFoundError, ExternalSuiteProvider from .issue import issue as ldp_issue -from .prove import sign_presentation from .models.credential import VerifiableCredential from .models.linked_data_proof import LDProof from .models.options import LDProofVCOptions +from .prove import sign_presentation from .verify import verify_credential, verify_presentation SignatureTypes = Union[ @@ -177,11 +179,25 @@ async def _get_suite( self, *, proof_type: str, - verification_method: Optional[str] = None, - proof: Optional[dict] = None, - did_info: Optional[DIDInfo] = None, + verification_method: str, + proof: dict, + did_info: DIDInfo, ): """Get signature suite for issuance of verification.""" + # Try to get suite from external provider first + try: + if (provider := self.profile.inject_or(ExternalSuiteProvider)) and ( + suite := await provider.get_suite( + self.profile, proof_type, proof, verification_method, did_info + ) + ): + return suite + except ExternalSuiteNotFoundError as error: + raise VcLdpManagerError( + f"Unable to get signature suite for proof type {proof_type} " + "using external provider." + ) from error + # Get signature class based on proof type SignatureClass = PROOF_TYPE_SIGNATURE_SUITE_MAPPING[proof_type] diff --git a/docs/features/JsonLdCredentials.md b/docs/features/JsonLdCredentials.md index 2f0831294f..a5c087f848 100644 --- a/docs/features/JsonLdCredentials.md +++ b/docs/features/JsonLdCredentials.md @@ -70,16 +70,17 @@ For the remainder of this guide, we will be using the example `UniversityDegreeC ### Signature Suite -Before issuing a credential you must determine a signature suite to use. ACA-Py currently supports two signature suites for issuing credentials: +Before issuing a credential you must determine a signature suite to use. ACA-Py currently supports three signature suites for issuing credentials: - [`Ed25519Signature2018`](https://w3c-ccg.github.io/lds-ed25519-2018/) - Very well supported. No zero knowledge proofs or selective disclosure. +- [`Ed25519Signature2020`](https://w3c.github.io/vc-di-eddsa/#ed25519signature2020-0) - Updated version of 2018 suite. - [`BbsBlsSignature2020`](https://w3c-ccg.github.io/ldp-bbs2020/) - Newer, but supports zero knowledge proofs and selective disclosure. Generally you should always use `BbsBlsSignature2020` as it allows the holder to derive a new credential during the proving, meaning it doesn't have to disclose all fields and doesn't have to reveal the signature. -### Did Method +### DID Method -Besides the JSON-LD context, we need a did to use for issuing the credential. ACA-Py currently supports two did methods for issuing credentials: +Besides the JSON-LD context, we need a DID to use for issuing the credential. ACA-Py currently supports two did methods for issuing credentials: - `did:sov` - Can only be used for `Ed25519Signature2018` signature suite. - `did:key` - Can be used for both `Ed25519Signature2018` and `BbsBlsSignature2020` signature suites. @@ -227,3 +228,11 @@ These endpoints include: - `POST /vc/presentations/verify` -> verifies a presentation To learn more about using these endpoints, please refer to the available [postman collection](../demo/AriesPostmanDemo.md#experimenting-with-the-vc-api-endpoints). + +## External Suite Provider + +It is possible to extend the signature suite support, including outsourcing signing JSON-LD Credentials to some other component (KMS, HSM, etc.), using the [`ExternalSuiteProvider` interface](https://github.com/hyperledger/aries-cloudagent-python/blob/d3ee92b1b86aff076b52f31eaecea59c18005079/aries_cloudagent/vc/vc_ld/external_suite.py#L27). This interface can be implemented and registered via plugin. The plugged in provider will be used by ACA-Py's LDP-VC subsystem to create a `LinkedDataProof` object, which is responsible for signing normalized credential values. + +This interface enables taking advantage of ACA-Py's JSON-LD processing to construct and format the credential while exposing a simple interface to a plugin to make it responsible for signatures. This can also be combined with plugged in DID Methods, `VerificationKeyStrategy`, and other pluggable components. + +See this example project here for more details on the interface and its usage: https://github.com/dbluhm/acapy-ld-signer