epicyon/jsonldsig.py

149 lines
4.2 KiB
Python
Raw Normal View History

2020-06-15 12:37:53 +00:00
__filename__ = "jsonldsig.py"
__author__ = "Bob Mottram"
__credits__ = ['Based on ' +
'https://github.com/WebOfTrustInfo/ld-signatures-python']
__license__ = "AGPL3+"
__version__ = "1.1.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
from copy import deepcopy
from datetime import datetime
import pytz
try:
from Cryptodome.PublicKey import RSA
from Cryptodome.Hash import SHA256
2020-06-15 12:52:46 +00:00
from Cryptodome.Signature import pkcs1_5 as PKCS1_v1_5
2020-06-15 12:37:53 +00:00
except ImportError:
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5
from pyld import jsonld
import base64
import json
2020-06-15 20:08:44 +00:00
def b64safeEncode(payload: {}) -> str:
2020-06-15 12:37:53 +00:00
"""
b64 url safe encoding with the padding removed.
"""
return base64.urlsafe_b64encode(payload).rstrip(b'=')
2020-06-15 20:08:44 +00:00
def b64safeDecode(payload: {}) -> str:
2020-06-15 12:37:53 +00:00
"""
b64 url safe decoding with the padding added.
"""
return base64.urlsafe_b64decode(payload + b'=' * (4 - len(payload) % 4))
2020-06-15 20:08:44 +00:00
def normalizeJson(payload: {}) -> str:
2020-06-15 12:37:53 +00:00
return json.dumps(payload, separators=(',', ':'),
sort_keys=True).encode('utf-8')
2020-06-15 20:08:44 +00:00
def signRs256(payload: {}, privateKey: str) -> str:
2020-06-15 12:37:53 +00:00
"""
Produce a RS256 signature of the payload
"""
2020-06-15 20:08:44 +00:00
key = RSA.importKey(privateKey)
2020-06-15 12:37:53 +00:00
signer = PKCS1_v1_5.new(key)
signature = signer.sign(SHA256.new(payload))
return signature
2020-06-15 20:08:44 +00:00
def verifyRs256(payload: {}, signature: str, publicKeyPem: str) -> bool:
2020-06-15 12:37:53 +00:00
"""
Verifies a RS256 signature
"""
2020-06-15 12:52:46 +00:00
key = RSA.importKey(publicKeyPem)
2020-06-15 12:37:53 +00:00
verifier = PKCS1_v1_5.new(key)
return verifier.verify(SHA256.new(payload), signature)
2020-06-15 20:08:44 +00:00
def signJws(payload: {}, privateKey: str) -> str:
2020-06-15 12:37:53 +00:00
""" Prepare payload to sign
"""
header = {
'alg': 'RS256',
'b64': False,
'crit': ['b64']
}
normalizedJson = normalizeJson(header)
encodedHeader = b64safeEncode(normalizedJson)
preparedPayload = b'.'.join([encodedHeader, payload])
2020-06-15 20:08:44 +00:00
signature = signRs256(preparedPayload, privateKey)
2020-06-15 12:37:53 +00:00
encodedSignature = b64safeEncode(signature)
jwsSignature = b'..'.join([encodedHeader, encodedSignature])
return jwsSignature
2020-06-15 20:08:44 +00:00
def verifyJws(payload: {}, jwsSignature: str, publicKeyPem: str) -> bool:
2020-06-15 12:37:53 +00:00
# remove the encoded header from the signature
2020-06-15 12:52:46 +00:00
encodedHeader, encodedSignature = jwsSignature.split(b'..')
2020-06-15 12:37:53 +00:00
signature = b64safeDecode(encodedSignature)
payload = b'.'.join([encodedHeader, payload])
2020-06-15 12:52:46 +00:00
return verifyRs256(payload, signature, publicKeyPem)
2020-06-15 12:37:53 +00:00
def jsonldNormalize(jldDocument: str):
"""
Normalize and hash the json-ld document
"""
options = {
'algorithm': 'URDNA2015',
'format': 'application/nquads'
}
normalized = jsonld.normalize(jldDocument, options=options)
normalizedHash = SHA256.new(data=normalized.encode('utf-8')).digest()
return normalizedHash
def jsonldSign(jldDocument: {}, privateKeyPem: str) -> {}:
"""
Produces a signed JSON-LD document with a Json Web Signature
"""
jldDocument = deepcopy(jldDocument)
normalizedJldHash = jsonldNormalize(jldDocument)
jwsSignature = signJws(normalizedJldHash, privateKeyPem)
# construct the signature document and add it to jsonld
signature = {
'type': 'RsaSignatureSuite2017',
'created': datetime.now(tz=pytz.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
'signatureValue': jwsSignature.decode('utf-8')
}
jldDocument.update({'signature': signature})
return jldDocument
def jsonldVerify(signedJldDocument: {}, publicKeyPem: str) -> bool:
"""
Verifies the Json Web Signature of a signed JSON-LD Document
"""
signedJldDocument = deepcopy(signedJldDocument)
signature = signedJldDocument.pop('signature')
jwsSignature = signature['signatureValue'].encode('utf-8')
normalizedJldHash = jsonldNormalize(signedJldDocument)
return verifyJws(normalizedJldHash, jwsSignature, publicKeyPem)
2020-06-15 20:32:57 +00:00
def testSignJsonld(jldDocument: {}, privateKeyPem: str) -> {}:
"""Creates a test signature
"""
2020-06-15 12:37:53 +00:00
signedJldDocument = jsonldSign(jldDocument, privateKeyPem)
2020-06-15 20:32:57 +00:00
2020-06-15 12:37:53 +00:00
# pop the created time key since its dynamic
signedJldDocument['signature'].pop('created')
2020-06-15 20:32:57 +00:00
return signedJldDocument