forked from indymedia/epicyon
Add jsonLD signing functions
parent
a4350228de
commit
8245c35f46
|
@ -23,6 +23,7 @@ sudo pacman -S tor python-pip python-pysocks python-pycryptodome \
|
|||
imagemagick python-pillow python-requests \
|
||||
perl-image-exiftool python-numpy python-dateutil \
|
||||
certbot flake8
|
||||
suso pip3 install pyLD
|
||||
```
|
||||
|
||||
Or on Debian:
|
||||
|
@ -34,6 +35,7 @@ sudo apt install -y \
|
|||
python3-crypto python3-cryptodome \
|
||||
python3-dateutil python3-pil.imagetk
|
||||
python3-idna python3-requests \
|
||||
python3-pyld \
|
||||
libimage-exiftool-perl python3-flake8 \
|
||||
certbot nginx
|
||||
```
|
||||
|
|
|
@ -4,7 +4,7 @@ You will need python version 3.7 or later.
|
|||
|
||||
On a Debian based system:
|
||||
|
||||
sudo apt install -y tor python3-socks imagemagick python3-numpy python3-setuptools python3-crypto python3-cryptodome python3-dateutil python3-pil.imagetk python3-idna python3-requests python3-flake8 libimage-exiftool-perl certbot nginx
|
||||
sudo apt install -y tor python3-socks imagemagick python3-numpy python3-setuptools python3-crypto python3-cryptodome python3-dateutil python3-pil.imagetk python3-idna python3-requests python3-flake8 python3-pyld libimage-exiftool-perl certbot nginx
|
||||
|
||||
The following instructions install Epicyon to the /opt directory. It's not essential that it be installed there, and it could be in any other preferred directory.
|
||||
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
__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
|
||||
from Cryptodome.Signature import pkcs1_15 as PKCS1_v1_5
|
||||
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
|
||||
|
||||
|
||||
def b64safeEncode(payload):
|
||||
"""
|
||||
b64 url safe encoding with the padding removed.
|
||||
"""
|
||||
return base64.urlsafe_b64encode(payload).rstrip(b'=')
|
||||
|
||||
|
||||
def b64safeDecode(payload):
|
||||
"""
|
||||
b64 url safe decoding with the padding added.
|
||||
"""
|
||||
return base64.urlsafe_b64decode(payload + b'=' * (4 - len(payload) % 4))
|
||||
|
||||
|
||||
def normalizeJson(payload):
|
||||
return json.dumps(payload, separators=(',', ':'),
|
||||
sort_keys=True).encode('utf-8')
|
||||
|
||||
|
||||
def signRs256(payload, private_key):
|
||||
"""
|
||||
Produce a RS256 signature of the payload
|
||||
"""
|
||||
key = RSA.importKey(private_key)
|
||||
signer = PKCS1_v1_5.new(key)
|
||||
signature = signer.sign(SHA256.new(payload))
|
||||
return signature
|
||||
|
||||
|
||||
def verifyRs256(payload, signature, public_key):
|
||||
"""
|
||||
Verifies a RS256 signature
|
||||
"""
|
||||
key = RSA.importKey(public_key)
|
||||
verifier = PKCS1_v1_5.new(key)
|
||||
return verifier.verify(SHA256.new(payload), signature)
|
||||
|
||||
|
||||
def signJws(payload, private_key):
|
||||
""" Prepare payload to sign
|
||||
"""
|
||||
header = {
|
||||
'alg': 'RS256',
|
||||
'b64': False,
|
||||
'crit': ['b64']
|
||||
}
|
||||
normalizedJson = normalizeJson(header)
|
||||
encodedHeader = b64safeEncode(normalizedJson)
|
||||
preparedPayload = b'.'.join([encodedHeader, payload])
|
||||
|
||||
signature = signRs256(preparedPayload, private_key)
|
||||
encodedSignature = b64safeEncode(signature)
|
||||
jwsSignature = b'..'.join([encodedHeader, encodedSignature])
|
||||
|
||||
return jwsSignature
|
||||
|
||||
|
||||
def verifyJws(payload, jws_signature, public_key):
|
||||
# remove the encoded header from the signature
|
||||
encodedHeader, encodedSignature = jws_signature.split(b'..')
|
||||
signature = b64safeDecode(encodedSignature)
|
||||
payload = b'.'.join([encodedHeader, payload])
|
||||
return verifyRs256(payload, signature, public_key)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def testSignJsonld(jldDocument: {}, privateKeyPem: str,
|
||||
expectedJldDocumentSigned=None):
|
||||
signedJldDocument = jsonldSign(jldDocument, privateKeyPem)
|
||||
# pop the created time key since its dynamic
|
||||
signedJldDocument['signature'].pop('created')
|
||||
|
||||
if expectedJldDocumentSigned:
|
||||
assert signedJldDocument == expectedJldDocumentSigned
|
||||
else:
|
||||
return signedJldDocument
|
56
tests.py
56
tests.py
|
@ -47,6 +47,7 @@ from follow import sendFollowRequest
|
|||
from person import createPerson
|
||||
from person import setDisplayNickname
|
||||
from person import setBio
|
||||
# from person import generateRSAKey
|
||||
from skills import setSkillLevel
|
||||
from roles import setRole
|
||||
from roles import outboxDelegate
|
||||
|
@ -70,6 +71,8 @@ from content import replaceContentDuplicates
|
|||
from content import removeTextFormatting
|
||||
from theme import setCSSparam
|
||||
from semantic import isAccusatory
|
||||
from jsonldsig import testSignJsonld
|
||||
from jsonldsig import jsonldVerify
|
||||
|
||||
testServerAliceRunning = False
|
||||
testServerBobRunning = False
|
||||
|
@ -1809,8 +1812,61 @@ def testRemoveTextFormatting():
|
|||
assert(resultStr == '<p>Text with formatting</p>')
|
||||
|
||||
|
||||
def testJsonld():
|
||||
print("testJsonld")
|
||||
jldDocument = {
|
||||
"description": "My json document",
|
||||
"numberField": 83582,
|
||||
"object": {
|
||||
"content": "Some content"
|
||||
}
|
||||
}
|
||||
# privateKeyPem, publicKeyPem = generateRSAKey()
|
||||
privateKeyPem = '-----BEGIN RSA PRIVATE KEY-----\n' \
|
||||
'MIIEowIBAAKCAQEAod9iHfIn4ugY/2byFrFjUprrFLkkH5bCrjiBq2/MdHFg99IQ\n' \
|
||||
'7li2x2mg5fkBMhU5SJIxlN8kiZMFq7JUXSA97Yo4puhVubqTSHihIh6Xn2mTjTgs\n' \
|
||||
'zNo9SBbmN3YiyBPTcr0rF4jGWZAduJ8u6i7Eky2QH+UBKyUNRZrcfoVq+7grHUIA\n' \
|
||||
'45pE7vAfEEWtgRiw32Nwlx55N3hayHax0y8gMdKEF/vfYKRLcM7rZgEASMtlCpgy\n' \
|
||||
'fsyHwFCDzl/BP8AhP9u3dM+SEundeAvF58AiXx1pKvBpxqttDNAsKWCRQ06/WI/W\n' \
|
||||
'2Rwihl9yCjobqRoFsZ/cTEi6FG9AbDAds5YjTwIDAQABAoIBAERL3rbpy8Bl0t43\n' \
|
||||
'jh7a+yAIMvVMZBxb3InrV3KAug/LInGNFQ2rKnsaawN8uu9pmwCuhfLc7yqIeJUH\n' \
|
||||
'qaadCuPlNJ/fWQQC309tbfbaV3iv78xejjBkSATZfIqb8nLeQpGflMXaNG3na1LQ\n' \
|
||||
'/tdZoiDC0ZNTaNnOSTo765oKKqhHUTQkwkGChrwG3Js5jekV4zpPMLhUafXk6ksd\n' \
|
||||
'8XLlZdCF3RUnuguXAg2xP/duxMYmTCx3eeGPkXBPQl0pahu8/6OtBoYvBrqNdQcx\n' \
|
||||
'jnEtYX9PCqDY3hAXW9GWsxNfu02DKhWigFHFNRUQtMI++438+QIfzXPslE2bTQIt\n' \
|
||||
'0OXUlwECgYEAxTKUZ7lwIBb5XKPJq53RQmX66M3ArxI1RzFSKm1+/CmxvYiN0c+5\n' \
|
||||
'2Aq62WEIauX6hoZ7yQb4zhdeNRzinLR7rsmBvIcP12FidXG37q9v3Vu70KmHniJE\n' \
|
||||
'TPbt5lHQ0bNACFxkar4Ab/JZN4CkMRgJdlcZ5boYNmcGOYCvw9izuM8CgYEA0iQ1\n' \
|
||||
'khIFZ6fCiXwVRGvEHmqSnkBmBHz8MY8fczv2Z4Gzfq3Tlh9VxpigK2F2pFt7keWc\n' \
|
||||
'53HerYFHFpf5otDhEyRwA1LyIcwbj5HopumxsB2WG+/M2as45lLfWa6KO73OtPpU\n' \
|
||||
'wGZYW+i/otdk9eFphceYtw19mxI+3lYoeI8EjYECgYBxOtTKJkmCs45lqkp/d3QT\n' \
|
||||
'2zjSempcXGkpQuG6KPtUUaCUgxdj1RISQj792OCbeQh8PDZRvOYaeIKInthkQKIQ\n' \
|
||||
'P/Z1yVvIQUvmwfBqZmQmR6k1bFLJ80UiqFr7+BiegH2RD3Q9cnIP1aly3DPrWLD+\n' \
|
||||
'OY9OQKfsfQWu+PxzyTeRMwKBgD8Zjlh5PtQ8RKcB8mTkMzSq7bHFRpzsZtH+1wPE\n' \
|
||||
'Kp40DRDp41H9wMTsiZPdJUH/EmDh4LaCs8nHuu/m3JfuPtd/pn7pBjntzwzSVFji\n' \
|
||||
'bW+jwrJK1Gk8B87pbZXBWlLMEOi5Dn/je37Fqd2c7f0DHauFHq9AxsmsteIPXwGs\n' \
|
||||
'eEKBAoGBAIzJX/5yFp3ObkPracIfOJ/U/HF1UdP6Y8qmOJBZOg5s9Y+JAdY76raK\n' \
|
||||
'0SbZPsOpuFUdTiRkSI3w/p1IuM5dPxgCGH9MHqjqogU5QwXr3vLF+a/PFhINkn1x\n' \
|
||||
'lozRZjDcF1y6xHfExotPC973UZnKEviq9/FqOsovZpvSQkzAYSZF\n' \
|
||||
'-----END RSA PRIVATE KEY-----'
|
||||
publicKeyPem = '-----BEGIN PUBLIC KEY-----\n' \
|
||||
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAod9iHfIn4ugY/2byFrFj\n' \
|
||||
'UprrFLkkH5bCrjiBq2/MdHFg99IQ7li2x2mg5fkBMhU5SJIxlN8kiZMFq7JUXSA9\n' \
|
||||
'7Yo4puhVubqTSHihIh6Xn2mTjTgszNo9SBbmN3YiyBPTcr0rF4jGWZAduJ8u6i7E\n' \
|
||||
'ky2QH+UBKyUNRZrcfoVq+7grHUIA45pE7vAfEEWtgRiw32Nwlx55N3hayHax0y8g\n' \
|
||||
'MdKEF/vfYKRLcM7rZgEASMtlCpgyfsyHwFCDzl/BP8AhP9u3dM+SEundeAvF58Ai\n' \
|
||||
'Xx1pKvBpxqttDNAsKWCRQ06/WI/W2Rwihl9yCjobqRoFsZ/cTEi6FG9AbDAds5Yj\n' \
|
||||
'TwIDAQAB\n' \
|
||||
'-----END PUBLIC KEY-----'
|
||||
|
||||
signedDocument = testSignJsonld(jldDocument, privateKeyPem)
|
||||
assert(signedDocument)
|
||||
assert(jsonldVerify(signedDocument, publicKeyPem))
|
||||
|
||||
|
||||
def runAllTests():
|
||||
print('Running tests...')
|
||||
testJsonld()
|
||||
testRemoveTextFormatting()
|
||||
testAccusatory()
|
||||
testWebLinks()
|
||||
|
|
|
@ -1264,7 +1264,7 @@
|
|||
<p class="intro">You will need python version 3.7 or later.</p>
|
||||
<p class="intro">On a Debian based system:</p>
|
||||
<div class="shell">
|
||||
<p>sudo apt install -y tor python3-socks imagemagick python3-numpy python3-setuptools python3-crypto python3-cryptodome python3-dateutil python3-pil.imagetk python3-idna python3-requests python3-flake8 libimage-exiftool-perl certbot nginx</p>
|
||||
<p>sudo apt install -y tor python3-socks imagemagick python3-numpy python3-setuptools python3-crypto python3-cryptodome python3-dateutil python3-pil.imagetk python3-idna python3-requests python3-flake8 python3-pyld libimage-exiftool-perl certbot nginx</p>
|
||||
</div>
|
||||
|
||||
<p class="intro">
|
||||
|
|
Loading…
Reference in New Issue