pyvckit/verify_vc.py

130 lines
3.8 KiB
Python

import sys
import json
import hashlib
import multicodec
import multiformats
import nacl.signing
import nacl.encoding
from pyld import jsonld
from jwcrypto import jwk
from nacl.signing import SigningKey, VerifyKey
from collections import OrderedDict
from nacl.encoding import RawEncoder
from datetime import datetime, timezone
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
def key_to_did(public_key_bytes):
"""did-key-format :=
did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))"""
mc = multicodec.add_prefix('ed25519-pub', public_key_bytes)
# Multibase encode the hashed bytes
did = multiformats.multibase.encode(mc, 'base58btc')
return f"did:key:{did}"
def get_signing_input(payload):
header = b'{"alg":"EdDSA","crit":["b64"],"b64":false}'
header_b64 = nacl.encoding.URLSafeBase64Encoder.encode(header)
signing_input = header_b64 + b"." + payload
return header_b64, signing_input
def urdna2015_normalize(document, proof):
doc_dataset = jsonld.compact(document, "https://www.w3.org/2018/credentials/v1")
sigopts_dataset = jsonld.compact(proof, "https://w3id.org/security/v2")
doc_normalized = jsonld.normalize(
doc_dataset,
{'algorithm': 'URDNA2015', 'format': 'application/n-quads'}
)
sigopts_normalized = jsonld.normalize(
sigopts_dataset,
{'algorithm': 'URDNA2015', 'format': 'application/n-quads'}
)
return doc_normalized, sigopts_normalized
def sha256_normalized(doc_normalized, sigopts_normalized):
doc_digest = hashlib.sha256(doc_normalized.encode('utf-8')).digest()
sigopts_digest = hashlib.sha256(sigopts_normalized.encode('utf-8')).digest()
message = sigopts_digest + doc_digest
return message
def to_jws_payload(document, proof):
doc_normalized, sigopts_normalized = urdna2015_normalize(document, proof)
return sha256_normalized(doc_normalized, sigopts_normalized)
def get_message(vc):
document = vc.copy()
proof = document.pop("proof", {})
jws = proof.pop("jws", None)
proof['@context'] = 'https://w3id.org/security/v2'
if not jws:
return None, False
return jws+"==", to_jws_payload(document, proof)
def get_verify_key(vc):
did = vc["proof"]["verificationMethod"].split("#")[0]
pub = did.split(":")[-1]
mc = multiformats.multibase.decode(pub)
public_key_bytes = multicodec.remove_prefix(mc)
return VerifyKey(public_key_bytes)
def jws_split(jws):
header, sig_b64 = jws.split("..")
signature = nacl.encoding.URLSafeBase64Encoder.decode(sig_b64.encode())
return header.encode(), signature
def verify_vc(vc):
header = {"alg": "EdDSA", "crit": ["b64"], "b64": False}
jws, message = get_message(vc)
if not message:
return False
header_b64, signature = get_signing_input(message)
header_jws, signature_jws = jws_split(jws)
if header_jws != header_b64:
return False
header_jws_json = json.loads(
nacl.encoding.URLSafeBase64Encoder.decode(header_jws)
)
for k, v in header.items():
if header_jws_json.get(k) != v:
return False
verify_key = get_verify_key(vc)
data_verified = verify_key.verify(signature_jws+signature)
return data_verified == signature
def get_credential(path_credential):
with open(path_credential, "r") as f:
vc = f.read()
return json.loads(vc)
if __name__ == "__main__":
if len(sys.argv) > 1:
path_credential = sys.argv[1]
credential = get_credential(path_credential)
print(verify_vc(credential))
else:
print("You need pass a credential.")