From 4c876e56c76a82a288d21e699fcca8e690a6bd89 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Mon, 3 Jun 2024 17:51:52 +0200 Subject: [PATCH] add cache to document loader --- cache_context.json | 1 + pyvckit/document_loader.py | 127 +++++++++++++++++++++++++++++++++++++ pyvckit/sign.py | 4 ++ 3 files changed, 132 insertions(+) create mode 100644 cache_context.json create mode 100644 pyvckit/document_loader.py diff --git a/cache_context.json b/cache_context.json new file mode 100644 index 0000000..6d3726b --- /dev/null +++ b/cache_context.json @@ -0,0 +1 @@ +{"https://w3id.org/security/v1": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://w3c-ccg.github.io/security-vocab/contexts/security-v1.jsonld", "document": {"@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"}}}, "https://www.w3.org/2018/credentials/v1": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://www.w3.org/2018/credentials/v1", "document": {"@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"}}}}, "https://w3id.org/security/v2": {"contentType": "application/ld+json", "contextUrl": null, "documentUrl": "https://w3c-ccg.github.io/security-vocab/contexts/security-v2.jsonld", "document": {"@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"}]}}} \ No newline at end of file diff --git a/pyvckit/document_loader.py b/pyvckit/document_loader.py new file mode 100644 index 0000000..0b00298 --- /dev/null +++ b/pyvckit/document_loader.py @@ -0,0 +1,127 @@ +""" +Remote document loader using Requests. + +.. module:: jsonld.documentloader.requests + :synopsis: Remote document loader using Requests + +.. moduleauthor:: Dave Longley +.. moduleauthor:: Mike Johnson +.. moduleauthor:: Tim McNamara +.. moduleauthor:: Olaf Conradi +""" +import string +import json +import urllib.parse as urllib_parse + +from pyld.jsonld import (JsonLdError, parse_link_header, LINK_HEADER_REL) + + +def get_cache(): + with open("cache_context.json") as f: + doc_str = f.read() + if doc_str: + # import pdb; pdb.set_trace() + return json.loads(doc_str) + return {} + + +def requests_document_loader(secure=False, **kwargs): + """ + Create a Requests document loader. + + Can be used to setup extra Requests args such as verify, cert, timeout, + or others. + + :param secure: require all requests to use HTTPS (default: False). + :param **kwargs: extra keyword args for Requests get() call. + + :return: the RemoteDocument loader function. + """ + import requests + + + def loader(url, options={}): + """ + Retrieves JSON-LD at the given URL. + + :param url: the URL to retrieve. + + :return: the RemoteDocument. + """ + # import pdb; pdb.set_trace() + cache = get_cache() + if cache.get(url): + return cache[url] + + 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 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() + } + link_header = response.headers.get('link') + if link_header: + linked_context = parse_link_header(link_header).get( + LINK_HEADER_REL) + # only 1 related link header permitted + if linked_context and 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': 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$', content_type)): + doc['contentType'] = 'application/ld+json' + doc['documentUrl'] = jsonld.prepend_base(url, linked_alternate['target']) + # import pdb; pdb.set_trace() + cache[url] = doc + f = open("cache_context.json", "w") + f.write(json.dumps(cache)) + f.close() + 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) + + return loader + diff --git a/pyvckit/sign.py b/pyvckit/sign.py index b7b6b48..5b563bc 100644 --- a/pyvckit/sign.py +++ b/pyvckit/sign.py @@ -2,6 +2,10 @@ import hashlib import nacl.signing import nacl.encoding from pyld import jsonld +from pyvckit.document_loader import requests_document_loader + + +jsonld.set_document_loader(requests_document_loader()) # https://github.com/spruceid/ssi/blob/main/ssi-jws/src/lib.rs#L75