| 
									
										
										
										
											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 21:37:10 +00:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Normalize with URDNA2015 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-06-15 12:37:53 +00:00
										 |  |  |     return json.dumps(payload, separators=(',', ':'), | 
					
						
							|  |  |  |                       sort_keys=True).encode('utf-8') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-15 21:34:32 +00:00
										 |  |  | def signRs256(payload: {}, privateKeyPem: str) -> str: | 
					
						
							| 
									
										
										
										
											2020-06-15 12:37:53 +00:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Produce a RS256 signature of the payload | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-06-15 21:34:32 +00:00
										 |  |  |     key = RSA.importKey(privateKeyPem) | 
					
						
							| 
									
										
										
										
											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 21:34:32 +00:00
										 |  |  | def signJws(payload: {}, privateKeyPem: str) -> str: | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Prepare payload to sign | 
					
						
							| 
									
										
										
										
											2020-06-15 12:37:53 +00:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     header = { | 
					
						
							|  |  |  |         'alg': 'RS256', | 
					
						
							|  |  |  |         'b64': False, | 
					
						
							|  |  |  |         'crit': ['b64'] | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     normalizedJson = normalizeJson(header) | 
					
						
							|  |  |  |     encodedHeader = b64safeEncode(normalizedJson) | 
					
						
							|  |  |  |     preparedPayload = b'.'.join([encodedHeader, payload]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-15 21:34:32 +00:00
										 |  |  |     signature = signRs256(preparedPayload, privateKeyPem) | 
					
						
							| 
									
										
										
										
											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 21:34:32 +00:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Verifies a signature using the given public key | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											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) -> {}: | 
					
						
							| 
									
										
										
										
											2020-06-15 21:37:10 +00:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Creates a test signature | 
					
						
							| 
									
										
										
										
											2020-06-15 20:32:57 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											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 |