diff --git a/MANIFEST.in b/MANIFEST.in index ef5132a345..0c6c839874 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,11 @@ include aries_cloudagent/config/default_logging_config.ini include aries_cloudagent/commands/default_version_upgrade_config.yml +include aries_cloudagent/vc/ld_proofs/resources/credentials_context.jsonld +include aries_cloudagent/vc/ld_proofs/resources/did_documents_context.jsonld +include aries_cloudagent/vc/ld_proofs/resources/status_list_context.jsonld +include aries_cloudagent/vc/ld_proofs/resources/security-v1-context.jsonld +include aries_cloudagent/vc/ld_proofs/resources/security-v2-context.jsonld +include aries_cloudagent/vc/ld_proofs/resources/ed25519-2020-context.jsonld include requirements.txt include requirements.dev.txt include requirements.indy.txt diff --git a/aries_cloudagent/vc/ld_proofs/document_downloader.py b/aries_cloudagent/vc/ld_proofs/document_downloader.py new file mode 100644 index 0000000000..f2861e1b5f --- /dev/null +++ b/aries_cloudagent/vc/ld_proofs/document_downloader.py @@ -0,0 +1,186 @@ +"""Quick and dirty fix to use as alternative to pyld downloader. + +Allows keeping some context in local filesystem. +""" +import logging +import re +import string +from typing import Dict, Optional +import urllib.parse as urllib_parse +import importlib + +import requests +from pyld import jsonld +from pyld.jsonld import JsonLdError, parse_link_header, LINK_HEADER_REL + +logger = logging.getLogger(__name__) + + +def _load_jsonld_file( + original_url, filename: str, resource_path: str = f"{__package__}.resources" +): + """Load context from package. + + Given a URL and filename, + load a context in the format used by pyld document loader. + """ + return { + "contentType": "application/ld+json", + "contextUrl": None, + "documentUrl": original_url, + "document": (importlib.resources.files(resource_path) / filename).read_text(), + } + + +class StaticCacheJsonLdDownloader: + """Context downloader with filesystem static cache for common contexts.""" + + CONTEXT_FILE_MAPPING = { + "https://www.w3.org/2018/credentials/v1": "credentials_context.jsonld", + "https://w3id.org/vc/status-list/2021/v1": "status_list_context.jsonld", + "https://www.w3.org/ns/did/v1": "did_documents_context.jsonld", + "https://w3id.org/security/v1": "security-v1-context.jsonld", + "https://w3id.org/security/v2": "security-v2-context.jsonld", + "https://w3id.org/security/suites/ed25519-2020/v1": "ed25519-2020-context.jsonld", + } + + def __init__(self, document_downloader=None, document_parser=None): + """Load static document on initialization.""" + self.documents_downloader = document_downloader or JsonLdDocumentDownloader() + self.document_parser = document_parser or JsonLdDocumentParser() + + self.cache = { + url: self.document_parser.parse(_load_jsonld_file(url, filename), None) + for url, filename in StaticCacheJsonLdDownloader.CONTEXT_FILE_MAPPING.items() + } + + def load(self, url, options=None): + """Load a jsonld document from URL. + + Prioritize local static cache before attempting to download from the URL. + """ + cached = self.cache.get(url) + + if cached is not None: + logger.info("Local cache hit for context: %s", url) + return cached + + logger.debug("Context %s not in static cache, resolving from URL.", url) + return self._live_load(url, options) + + def _live_load(self, url, options=None): + doc, link_header = self.documents_downloader.download(url, options) + return self.document_parser.parse(doc, link_header) + + +class JsonLdDocumentDownloader: + """JsonLd documents downloader.""" + + def download(self, url: str, options: Dict, **kwargs): + """Retrieves JSON-LD at the given URL. + + This was lifted from pyld.documentloader.requests. + + :param url: the URL to retrieve. + :param options: + + :return: the RemoteDocument. + + """ + options = options or {} + + try: + # validate URL + pieces = urllib_parse.urlparse(url) + if ( + not all([pieces.scheme, pieces.netloc]) + or pieces.scheme not in ["http", "https"] + or set(pieces.netloc) + > set(string.ascii_letters + string.digits + "-.:") + ): + raise JsonLdError( + 'URL could not be dereferenced; only "http" and "https" ' + "URLs are supported.", + "jsonld.InvalidUrl", + {"url": url}, + code="loading document failed", + ) + if options.get("secure") and pieces.scheme != "https": + raise JsonLdError( + "URL could not be dereferenced; secure mode enabled and " + 'the URL\'s scheme is not "https".', + "jsonld.InvalidUrl", + {"url": url}, + code="loading document failed", + ) + headers = options.get("headers") + if headers is None: + headers = {"Accept": "application/ld+json, application/json"} + response = requests.get(url, headers=headers, **kwargs) + + content_type = response.headers.get("content-type") + if not content_type: + content_type = "application/octet-stream" + doc = { + "contentType": content_type, + "contextUrl": None, + "documentUrl": response.url, + "document": response.json(), + } + + return doc, response.headers.get("link") + except Exception as cause: + raise JsonLdError( + "Could not retrieve a JSON-LD document from the URL.", + "jsonld.LoadDocumentError", + code="loading document failed", + cause=cause, + ) + + +class JsonLdDocumentParser: + """JsonLd documents parser.""" + + def parse(self, doc: Dict, link_header: Optional[str]): + """Parse a jsonld document after retrieval. + + This was lifted from pyld.documentloader.requests. + """ + try: + if link_header: + linked_context = parse_link_header(link_header).get(LINK_HEADER_REL) + # only 1 related link header permitted + if linked_context and doc["content_type"] != "application/ld+json": + if isinstance(linked_context, list): + raise JsonLdError( + "URL could not be dereferenced, " + "it has more than one " + "associated HTTP Link Header.", + "jsonld.LoadDocumentError", + {"url": doc["url"]}, + code="multiple context link headers", + ) + doc["contextUrl"] = linked_context["target"] + linked_alternate = parse_link_header(link_header).get("alternate") + # if not JSON-LD, alternate may point there + if ( + linked_alternate + and linked_alternate.get("type") == "application/ld+json" + and not re.match( + r"^application\/(\w*\+)?json$", doc["content_type"] + ) + ): + doc["contentType"] = "application/ld+json" + doc["documentUrl"] = jsonld.prepend_base( + doc["url"], linked_alternate["target"] + ) + return doc + except JsonLdError as e: + raise e + except Exception as cause: + raise JsonLdError( + "Could not retrieve a JSON-LD document from the URL.", + "jsonld.LoadDocumentError", + code="loading document failed", + cause=cause, + ) diff --git a/aries_cloudagent/vc/ld_proofs/document_loader.py b/aries_cloudagent/vc/ld_proofs/document_loader.py index 3c35d7d985..029da19763 100644 --- a/aries_cloudagent/vc/ld_proofs/document_loader.py +++ b/aries_cloudagent/vc/ld_proofs/document_loader.py @@ -8,6 +8,7 @@ from pydid.did_url import DIDUrl from pyld.documentloader import requests +from .document_downloader import StaticCacheJsonLdDownloader from ...cache.base import BaseCache from ...core.profile import Profile from ...resolver.did_resolver import DIDResolver @@ -33,7 +34,8 @@ def __init__(self, profile: Profile, cache_ttl: int = 300) -> None: self.profile = profile self.resolver = profile.inject(DIDResolver) self.cache = profile.inject_or(BaseCache) - self.requests_loader = requests.requests_document_loader() + self.online_request_loader = requests.requests_document_loader() + self.requests_loader = StaticCacheJsonLdDownloader().load self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1) self.cache_ttl = cache_ttl self._event_loop = asyncio.get_event_loop() diff --git a/aries_cloudagent/vc/ld_proofs/resources/__init__.py b/aries_cloudagent/vc/ld_proofs/resources/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/vc/ld_proofs/resources/credentials_context.jsonld b/aries_cloudagent/vc/ld_proofs/resources/credentials_context.jsonld new file mode 100644 index 0000000000..0124a3c41c --- /dev/null +++ b/aries_cloudagent/vc/ld_proofs/resources/credentials_context.jsonld @@ -0,0 +1,237 @@ +{ + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "VerifiableCredential": { + "@id": "https://www.w3.org/2018/credentials#VerifiableCredential", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "credentialSchema": { + "@id": "cred:credentialSchema", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "cred": "https://www.w3.org/2018/credentials#", + + "JsonSchemaValidator2018": "cred:JsonSchemaValidator2018" + } + }, + "credentialStatus": {"@id": "cred:credentialStatus", "@type": "@id"}, + "credentialSubject": {"@id": "cred:credentialSubject", "@type": "@id"}, + "evidence": {"@id": "cred:evidence", "@type": "@id"}, + "expirationDate": {"@id": "cred:expirationDate", "@type": "xsd:dateTime"}, + "holder": {"@id": "cred:holder", "@type": "@id"}, + "issued": {"@id": "cred:issued", "@type": "xsd:dateTime"}, + "issuer": {"@id": "cred:issuer", "@type": "@id"}, + "issuanceDate": {"@id": "cred:issuanceDate", "@type": "xsd:dateTime"}, + "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"}, + "refreshService": { + "@id": "cred:refreshService", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "cred": "https://www.w3.org/2018/credentials#", + + "ManualRefreshService2018": "cred:ManualRefreshService2018" + } + }, + "termsOfUse": {"@id": "cred:termsOfUse", "@type": "@id"}, + "validFrom": {"@id": "cred:validFrom", "@type": "xsd:dateTime"}, + "validUntil": {"@id": "cred:validUntil", "@type": "xsd:dateTime"} + } + }, + + "VerifiablePresentation": { + "@id": "https://www.w3.org/2018/credentials#VerifiablePresentation", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + + "holder": {"@id": "cred:holder", "@type": "@id"}, + "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"}, + "verifiableCredential": {"@id": "cred:verifiableCredential", "@type": "@id", "@container": "@graph"} + } + }, + + "EcdsaSecp256k1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256k1Signature2019", + "@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", + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "jws": "sec:jws", + "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"} + } + }, + + "EcdsaSecp256r1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256r1Signature2019", + "@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", + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "jws": "sec:jws", + "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"} + } + }, + + "Ed25519Signature2018": { + "@id": "https://w3id.org/security#Ed25519Signature2018", + "@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", + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "jws": "sec:jws", + "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"} + } + }, + + "RsaSignature2018": { + "@id": "https://w3id.org/security#RsaSignature2018", + "@context": { + "@version": 1.1, + "@protected": true, + + "challenge": "sec:challenge", + "created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"}, + "domain": "sec:domain", + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "jws": "sec:jws", + "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"} + } + }, + + "proof": {"@id": "https://w3id.org/security#proof", "@type": "@id", "@container": "@graph"} + } +} diff --git a/aries_cloudagent/vc/ld_proofs/resources/did_documents_context.jsonld b/aries_cloudagent/vc/ld_proofs/resources/did_documents_context.jsonld new file mode 100644 index 0000000000..55ab11cf93 --- /dev/null +++ b/aries_cloudagent/vc/ld_proofs/resources/did_documents_context.jsonld @@ -0,0 +1,58 @@ +{ + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + + "alsoKnownAs": { + "@id": "https://www.w3.org/ns/activitystreams#alsoKnownAs", + "@type": "@id" + }, + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + }, + "service": { + "@id": "https://www.w3.org/ns/did#service", + "@type": "@id", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "serviceEndpoint": { + "@id": "https://www.w3.org/ns/did#serviceEndpoint", + "@type": "@id" + } + } + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } +} \ No newline at end of file diff --git a/aries_cloudagent/vc/ld_proofs/resources/ed25519-2020-context.jsonld b/aries_cloudagent/vc/ld_proofs/resources/ed25519-2020-context.jsonld new file mode 100644 index 0000000000..b74da8c0b7 --- /dev/null +++ b/aries_cloudagent/vc/ld_proofs/resources/ed25519-2020-context.jsonld @@ -0,0 +1,93 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + "@protected": true, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "Ed25519VerificationKey2020": { + "@id": "https://w3id.org/security#Ed25519VerificationKey2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "controller": { + "@id": "https://w3id.org/security#controller", + "@type": "@id" + }, + "revoked": { + "@id": "https://w3id.org/security#revoked", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "publicKeyMultibase": { + "@id": "https://w3id.org/security#publicKeyMultibase", + "@type": "https://w3id.org/security#multibase" + } + } + }, + "Ed25519Signature2020": { + "@id": "https://w3id.org/security#Ed25519Signature2020", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": { + "@id": "https://w3id.org/security#proofValue", + "@type": "https://w3id.org/security#multibase" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + } + } +} diff --git a/aries_cloudagent/vc/ld_proofs/resources/security-v1-context.jsonld b/aries_cloudagent/vc/ld_proofs/resources/security-v1-context.jsonld new file mode 100644 index 0000000000..7529505260 --- /dev/null +++ b/aries_cloudagent/vc/ld_proofs/resources/security-v1-context.jsonld @@ -0,0 +1,50 @@ +{ + "@context": { + "id": "@id", + "type": "@type", + + "dc": "http://purl.org/dc/terms/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016", + "Ed25519Signature2018": "sec:Ed25519Signature2018", + "EncryptedMessage": "sec:EncryptedMessage", + "GraphSignature2012": "sec:GraphSignature2012", + "LinkedDataSignature2015": "sec:LinkedDataSignature2015", + "LinkedDataSignature2016": "sec:LinkedDataSignature2016", + "CryptographicKey": "sec:Key", + + "authenticationTag": "sec:authenticationTag", + "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm", + "cipherAlgorithm": "sec:cipherAlgorithm", + "cipherData": "sec:cipherData", + "cipherKey": "sec:cipherKey", + "created": {"@id": "dc:created", "@type": "xsd:dateTime"}, + "creator": {"@id": "dc:creator", "@type": "@id"}, + "digestAlgorithm": "sec:digestAlgorithm", + "digestValue": "sec:digestValue", + "domain": "sec:domain", + "encryptionKey": "sec:encryptionKey", + "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "initializationVector": "sec:initializationVector", + "iterationCount": "sec:iterationCount", + "nonce": "sec:nonce", + "normalizationAlgorithm": "sec:normalizationAlgorithm", + "owner": {"@id": "sec:owner", "@type": "@id"}, + "password": "sec:password", + "privateKey": {"@id": "sec:privateKey", "@type": "@id"}, + "privateKeyPem": "sec:privateKeyPem", + "publicKey": {"@id": "sec:publicKey", "@type": "@id"}, + "publicKeyBase58": "sec:publicKeyBase58", + "publicKeyPem": "sec:publicKeyPem", + "publicKeyWif": "sec:publicKeyWif", + "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"}, + "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"}, + "salt": "sec:salt", + "signature": "sec:signature", + "signatureAlgorithm": "sec:signingAlgorithm", + "signatureValue": "sec:signatureValue" + } +} diff --git a/aries_cloudagent/vc/ld_proofs/resources/security-v2-context.jsonld b/aries_cloudagent/vc/ld_proofs/resources/security-v2-context.jsonld new file mode 100644 index 0000000000..5f43a0c73d --- /dev/null +++ b/aries_cloudagent/vc/ld_proofs/resources/security-v2-context.jsonld @@ -0,0 +1,59 @@ +{ + "@context": [{ + "@version": 1.1 + }, "https://w3id.org/security/v1", { + "AesKeyWrappingKey2019": "sec:AesKeyWrappingKey2019", + "DeleteKeyOperation": "sec:DeleteKeyOperation", + "DeriveSecretOperation": "sec:DeriveSecretOperation", + "EcdsaSecp256k1Signature2019": "sec:EcdsaSecp256k1Signature2019", + "EcdsaSecp256r1Signature2019": "sec:EcdsaSecp256r1Signature2019", + "EcdsaSecp256k1VerificationKey2019": "sec:EcdsaSecp256k1VerificationKey2019", + "EcdsaSecp256r1VerificationKey2019": "sec:EcdsaSecp256r1VerificationKey2019", + "Ed25519Signature2018": "sec:Ed25519Signature2018", + "Ed25519VerificationKey2018": "sec:Ed25519VerificationKey2018", + "EquihashProof2018": "sec:EquihashProof2018", + "ExportKeyOperation": "sec:ExportKeyOperation", + "GenerateKeyOperation": "sec:GenerateKeyOperation", + "KmsOperation": "sec:KmsOperation", + "RevokeKeyOperation": "sec:RevokeKeyOperation", + "RsaSignature2018": "sec:RsaSignature2018", + "RsaVerificationKey2018": "sec:RsaVerificationKey2018", + "Sha256HmacKey2019": "sec:Sha256HmacKey2019", + "SignOperation": "sec:SignOperation", + "UnwrapKeyOperation": "sec:UnwrapKeyOperation", + "VerifyOperation": "sec:VerifyOperation", + "WrapKeyOperation": "sec:WrapKeyOperation", + "X25519KeyAgreementKey2019": "sec:X25519KeyAgreementKey2019", + + "allowedAction": "sec:allowedAction", + "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, + "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"}, + "capability": {"@id": "sec:capability", "@type": "@id"}, + "capabilityAction": "sec:capabilityAction", + "capabilityChain": {"@id": "sec:capabilityChain", "@type": "@id", "@container": "@list"}, + "capabilityDelegation": {"@id": "sec:capabilityDelegationMethod", "@type": "@id", "@container": "@set"}, + "capabilityInvocation": {"@id": "sec:capabilityInvocationMethod", "@type": "@id", "@container": "@set"}, + "caveat": {"@id": "sec:caveat", "@type": "@id", "@container": "@set"}, + "challenge": "sec:challenge", + "ciphertext": "sec:ciphertext", + "controller": {"@id": "sec:controller", "@type": "@id"}, + "delegator": {"@id": "sec:delegator", "@type": "@id"}, + "equihashParameterK": {"@id": "sec:equihashParameterK", "@type": "xsd:integer"}, + "equihashParameterN": {"@id": "sec:equihashParameterN", "@type": "xsd:integer"}, + "invocationTarget": {"@id": "sec:invocationTarget", "@type": "@id"}, + "invoker": {"@id": "sec:invoker", "@type": "@id"}, + "jws": "sec:jws", + "keyAgreement": {"@id": "sec:keyAgreementMethod", "@type": "@id", "@container": "@set"}, + "kmsModule": {"@id": "sec:kmsModule"}, + "parentCapability": {"@id": "sec:parentCapability", "@type": "@id"}, + "plaintext": "sec:plaintext", + "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"}, + "proofPurpose": {"@id": "sec:proofPurpose", "@type": "@vocab"}, + "proofValue": "sec:proofValue", + "referenceId": "sec:referenceId", + "unwrappedKey": "sec:unwrappedKey", + "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"}, + "verifyData": "sec:verifyData", + "wrappedKey": "sec:wrappedKey" + }] +} diff --git a/aries_cloudagent/vc/ld_proofs/resources/status_list_context.jsonld b/aries_cloudagent/vc/ld_proofs/resources/status_list_context.jsonld new file mode 100644 index 0000000000..4027c1d499 --- /dev/null +++ b/aries_cloudagent/vc/ld_proofs/resources/status_list_context.jsonld @@ -0,0 +1,55 @@ +{ + "@context": { + "@protected": true, + + "StatusList2021Credential": { + "@id": + "https://w3id.org/vc/status-list#StatusList2021Credential", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "description": "http://schema.org/description", + "name": "http://schema.org/name" + } + }, + + "StatusList2021": { + "@id": + "https://w3id.org/vc/status-list#StatusList2021", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "statusPurpose": + "https://w3id.org/vc/status-list#statusPurpose", + "encodedList": "https://w3id.org/vc/status-list#encodedList" + } + }, + + "StatusList2021Entry": { + "@id": + "https://w3id.org/vc/status-list#StatusList2021Entry", + "@context": { + "@protected": true, + + "id": "@id", + "type": "@type", + + "statusPurpose": + "https://w3id.org/vc/status-list#statusPurpose", + "statusListIndex": + "https://w3id.org/vc/status-list#statusListIndex", + "statusListCredential": { + "@id": + "https://w3id.org/vc/status-list#statusListCredential", + "@type": "@id" + } + } + } + } +} diff --git a/aries_cloudagent/vc/ld_proofs/tests/test_document_downloader.py b/aries_cloudagent/vc/ld_proofs/tests/test_document_downloader.py new file mode 100644 index 0000000000..ba3a9cf41b --- /dev/null +++ b/aries_cloudagent/vc/ld_proofs/tests/test_document_downloader.py @@ -0,0 +1,22 @@ +from unittest.mock import Mock + +from aries_cloudagent.vc.ld_proofs.document_downloader import ( + StaticCacheJsonLdDownloader, +) + + +def test_load_cache_hit(): + downloader = Mock() + context_loader = StaticCacheJsonLdDownloader(document_downloader=downloader) + + context_loader.load("https://www.w3.org/2018/credentials/v1") + downloader.download.assert_not_called() + + +def test_load_cache_miss_triggers_download(): + downloader = Mock() + downloader.download = Mock(return_value=(None, None)) + context_loader = StaticCacheJsonLdDownloader(document_downloader=downloader) + + context_loader.load("https://www.w3.org/2018/very_unlikely_name/v1") + downloader.download.assert_called()