mirror of https://gitlab.com/bashrc2/epicyon
Support for new style of http signatures
parent
56e9130287
commit
3d1c440584
84
httpsig.py
84
httpsig.py
|
@ -18,6 +18,7 @@ from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
|||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import utils as hazutils
|
||||
import calendar
|
||||
import base64
|
||||
from time import gmtime, strftime
|
||||
import datetime
|
||||
|
@ -99,6 +100,89 @@ def signPostHeaders(dateStr: str, privateKeyPem: str,
|
|||
return signatureHeader
|
||||
|
||||
|
||||
def signPostHeadersNew(dateStr: str, privateKeyPem: str,
|
||||
nickname: str,
|
||||
domain: str, port: int,
|
||||
toDomain: str, toPort: int,
|
||||
path: str,
|
||||
httpPrefix: str,
|
||||
messageBodyJsonStr: str,
|
||||
algorithm: str) -> (str, str):
|
||||
"""Returns a raw signature strings that can be plugged into a header
|
||||
as "Signature-Input" and "Signature"
|
||||
used to verify the authenticity of an HTTP transmission.
|
||||
See https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures-01
|
||||
"""
|
||||
domain = getFullDomain(domain, port)
|
||||
|
||||
toDomain = getFullDomain(toDomain, toPort)
|
||||
|
||||
timeFormat = "%a, %d %b %Y %H:%M:%S %Z"
|
||||
if not dateStr:
|
||||
currTime = gmtime()
|
||||
secondsSinceEpoch = int(calendar.timegm(currTime))
|
||||
dateStr = strftime(timeFormat, currTime)
|
||||
else:
|
||||
currTime = datetime.datetime.strptime(dateStr, timeFormat)
|
||||
secondsSinceEpoch = int(currTime.timestamp())
|
||||
keyID = httpPrefix + '://' + domain + '/users/' + nickname + '#main-key'
|
||||
if not messageBodyJsonStr:
|
||||
headers = {
|
||||
'*request-target': f'post {path}',
|
||||
'*created': str(secondsSinceEpoch),
|
||||
'host': toDomain,
|
||||
'date': dateStr,
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
else:
|
||||
bodyDigest = messageContentDigest(messageBodyJsonStr)
|
||||
contentLength = len(messageBodyJsonStr)
|
||||
headers = {
|
||||
'*request-target': f'post {path}',
|
||||
'*created': str(secondsSinceEpoch),
|
||||
'host': toDomain,
|
||||
'date': dateStr,
|
||||
'digest': f'SHA-256={bodyDigest}',
|
||||
'content-type': 'application/activity+json',
|
||||
'content-length': str(contentLength)
|
||||
}
|
||||
key = load_pem_private_key(privateKeyPem.encode('utf-8'),
|
||||
None, backend=default_backend())
|
||||
# build a digest for signing
|
||||
signedHeaderKeys = headers.keys()
|
||||
signedHeaderText = ''
|
||||
for headerKey in signedHeaderKeys:
|
||||
signedHeaderText += f'{headerKey}: {headers[headerKey]}\n'
|
||||
signedHeaderText = signedHeaderText.strip()
|
||||
headerDigest = getSHA256(signedHeaderText.encode('ascii'))
|
||||
|
||||
# Sign the digest. Potentially other signing algorithms can be added here.
|
||||
signature = ''
|
||||
if algorithm == 'rsa-sha256':
|
||||
rawSignature = key.sign(headerDigest,
|
||||
padding.PKCS1v15(),
|
||||
hazutils.Prehashed(hashes.SHA256()))
|
||||
signature = base64.b64encode(rawSignature).decode('ascii')
|
||||
|
||||
sigKey = 'sig1'
|
||||
# Put it into a valid HTTP signature format
|
||||
signatureInputDict = {
|
||||
'keyId': keyID,
|
||||
}
|
||||
signatureIndexHeader = '; '.join(
|
||||
[f'{k}="{v}"' for k, v in signatureInputDict.items()])
|
||||
signatureIndexHeader += '; alg=hs2019'
|
||||
signatureIndexHeader += '; created=' + str(secondsSinceEpoch)
|
||||
signatureIndexHeader += \
|
||||
'; ' + sigKey + '=(' + ', '.join(signedHeaderKeys) + ')'
|
||||
signatureDict = {
|
||||
sigKey: signature
|
||||
}
|
||||
signatureHeader = '; '.join(
|
||||
[f'{k}=:{v}:' for k, v in signatureDict.items()])
|
||||
return signatureIndexHeader, signatureHeader
|
||||
|
||||
|
||||
def createSignedHeader(privateKeyPem: str, nickname: str,
|
||||
domain: str, port: int,
|
||||
toDomain: str, toPort: int,
|
||||
|
|
152
tests.py
152
tests.py
|
@ -13,6 +13,7 @@ import json
|
|||
from time import gmtime, strftime
|
||||
from pprint import pprint
|
||||
from httpsig import signPostHeaders
|
||||
from httpsig import signPostHeadersNew
|
||||
from httpsig import verifyPostHeaders
|
||||
from httpsig import messageContentDigest
|
||||
from cache import storePersonInCache
|
||||
|
@ -125,59 +126,59 @@ def testHttpSigNew():
|
|||
'PSXSfBDiUGhwOw76WuSSsf1D4b/vLoJ10wIDAQAB\n' + \
|
||||
'-----END RSA PUBLIC KEY-----\n'
|
||||
|
||||
# privKey = \
|
||||
# '-----BEGIN RSA PRIVATE KEY-----\n' + \
|
||||
# 'MIIEqAIBAAKCAQEAhAKYdtoeoy8zcAcR8' + \
|
||||
# '74L8cnZxKzAGwd7v36APp7Pv6Q2jdsP\n' + \
|
||||
# 'BRrwWEBnez6d0UDKDwGbc6nxfEXAy5mbh' + \
|
||||
# 'gajzrw3MOEt8uA5txSKobBpKDeBLOsd\n' + \
|
||||
# 'JKFqMGmXCQvEG7YemcxDTRPxAleIAgYYR' + \
|
||||
# 'jTSd/QBwVW9OwNFhekro3RtlinV0a75\n' + \
|
||||
# 'jfZgkne/YiktSvLG34lw2zqXBDTC5NHRO' + \
|
||||
# 'UqGTlML4PlNZS5Ri2U4aCNx2rUPRcKI\n' + \
|
||||
# 'lE0PuKxI4T+HIaFpv8+rdV6eUgOrB2xeI' + \
|
||||
# '1dSFFn/nnv5OoZJEIB+VmuKn3DCUcCZ\n' + \
|
||||
# 'SFlQPSXSfBDiUGhwOw76WuSSsf1D4b/vL' + \
|
||||
# 'oJ10wIDAQABAoIBAG/JZuSWdoVHbi56\n' + \
|
||||
# 'vjgCgkjg3lkO1KrO3nrdm6nrgA9P9qaPj' + \
|
||||
# 'xuKoWaKO1cBQlE1pSWp/cKncYgD5WxE\n' + \
|
||||
# 'CpAnRUXG2pG4zdkzCYzAh1i+c34L6oZoH' + \
|
||||
# 'sirK6oNcEnHveydfzJL5934egm6p8DW\n' + \
|
||||
# '+m1RQ70yUt4uRc0YSor+q1LGJvGQHReF0' + \
|
||||
# 'WmJBZHrhz5e63Pq7lE0gIwuBqL8SMaA\n' + \
|
||||
# 'yRXtK+JGxZpImTq+NHvEWWCu09SCq0r83' + \
|
||||
# '8ceQI55SvzmTkwqtC+8AT2zFviMZkKR\n' + \
|
||||
# 'Qo6SPsrqItxZWRty2izawTF0Bf5S2VAx7' + \
|
||||
# 'O+6t3wBsQ1sLptoSgX3QblELY5asI0J\n' + \
|
||||
# 'YFz7LJECgYkAsqeUJmqXE3LP8tYoIjMIA' + \
|
||||
# 'KiTm9o6psPlc8CrLI9CH0UbuaA2JCOM\n' + \
|
||||
# 'cCNq8SyYbTqgnWlB9ZfcAm/cFpA8tYci9' + \
|
||||
# 'm5vYK8HNxQr+8FS3Qo8N9RJ8d0U5Csw\n' + \
|
||||
# 'DzMYfRghAfUGwmlWj5hp1pQzAuhwbOXFt' + \
|
||||
# 'xKHVsMPhz1IBtF9Y8jvgqgYHLbmyiu1\n' + \
|
||||
# 'mwJ5AL0pYF0G7x81prlARURwHo0Yf52kE' + \
|
||||
# 'w1dxpx+JXER7hQRWQki5/NsUEtv+8RT\n' + \
|
||||
# 'qn2m6qte5DXLyn83b1qRscSdnCCwKtKWU' + \
|
||||
# 'ug5q2ZbwVOCJCtmRwmnP131lWRYfj67\n' + \
|
||||
# 'B/xJ1ZA6X3GEf4sNReNAtaucPEelgR2ns' + \
|
||||
# 'N0gKQKBiGoqHWbK1qYvBxX2X3kbPDkv\n' + \
|
||||
# '9C+celgZd2PW7aGYLCHq7nPbmfDV0yHcW' + \
|
||||
# 'jOhXZ8jRMjmANVR/eLQ2EfsRLdW69bn\n' + \
|
||||
# 'f3ZD7JS1fwGnO3exGmHO3HZG+6AvberKY' + \
|
||||
# 'VYNHahNFEw5TsAcQWDLRpkGybBcxqZo\n' + \
|
||||
# '81YCqlqidwfeO5YtlO7etx1xLyqa2NsCe' + \
|
||||
# 'G9A86UjG+aeNnXEIDk1PDK+EuiThIUa\n' + \
|
||||
# '/2IxKzJKWl1BKr2d4xAfR0ZnEYuRrbeDQ' + \
|
||||
# 'YgTImOlfW6/GuYIxKYgEKCFHFqJATAG\n' + \
|
||||
# 'IxHrq1PDOiSwXd2GmVVYyEmhZnbcp8Cxa' + \
|
||||
# 'EMQoevxAta0ssMK3w6UsDtvUvYvF22m\n' + \
|
||||
# 'qQKBiD5GwESzsFPy3Ga0MvZpn3D6EJQLg' + \
|
||||
# 'snrtUPZx+z2Ep2x0xc5orneB5fGyF1P\n' + \
|
||||
# 'WtP+fG5Q6Dpdz3LRfm+KwBCWFKQjg7uTx' + \
|
||||
# 'cjerhBWEYPmEMKYwTJF5PBG9/ddvHLQ\n' + \
|
||||
# 'EQeNC8fHGg4UXU8mhHnSBt3EA10qQJfRD' + \
|
||||
# 's15M38eG2cYwB1PZpDHScDnDA0=\n' + \
|
||||
# '-----END RSA PRIVATE KEY-----'
|
||||
privateKeyPem = \
|
||||
'-----BEGIN RSA PRIVATE KEY-----\n' + \
|
||||
'MIIEqAIBAAKCAQEAhAKYdtoeoy8zcAcR8' + \
|
||||
'74L8cnZxKzAGwd7v36APp7Pv6Q2jdsP\n' + \
|
||||
'BRrwWEBnez6d0UDKDwGbc6nxfEXAy5mbh' + \
|
||||
'gajzrw3MOEt8uA5txSKobBpKDeBLOsd\n' + \
|
||||
'JKFqMGmXCQvEG7YemcxDTRPxAleIAgYYR' + \
|
||||
'jTSd/QBwVW9OwNFhekro3RtlinV0a75\n' + \
|
||||
'jfZgkne/YiktSvLG34lw2zqXBDTC5NHRO' + \
|
||||
'UqGTlML4PlNZS5Ri2U4aCNx2rUPRcKI\n' + \
|
||||
'lE0PuKxI4T+HIaFpv8+rdV6eUgOrB2xeI' + \
|
||||
'1dSFFn/nnv5OoZJEIB+VmuKn3DCUcCZ\n' + \
|
||||
'SFlQPSXSfBDiUGhwOw76WuSSsf1D4b/vL' + \
|
||||
'oJ10wIDAQABAoIBAG/JZuSWdoVHbi56\n' + \
|
||||
'vjgCgkjg3lkO1KrO3nrdm6nrgA9P9qaPj' + \
|
||||
'xuKoWaKO1cBQlE1pSWp/cKncYgD5WxE\n' + \
|
||||
'CpAnRUXG2pG4zdkzCYzAh1i+c34L6oZoH' + \
|
||||
'sirK6oNcEnHveydfzJL5934egm6p8DW\n' + \
|
||||
'+m1RQ70yUt4uRc0YSor+q1LGJvGQHReF0' + \
|
||||
'WmJBZHrhz5e63Pq7lE0gIwuBqL8SMaA\n' + \
|
||||
'yRXtK+JGxZpImTq+NHvEWWCu09SCq0r83' + \
|
||||
'8ceQI55SvzmTkwqtC+8AT2zFviMZkKR\n' + \
|
||||
'Qo6SPsrqItxZWRty2izawTF0Bf5S2VAx7' + \
|
||||
'O+6t3wBsQ1sLptoSgX3QblELY5asI0J\n' + \
|
||||
'YFz7LJECgYkAsqeUJmqXE3LP8tYoIjMIA' + \
|
||||
'KiTm9o6psPlc8CrLI9CH0UbuaA2JCOM\n' + \
|
||||
'cCNq8SyYbTqgnWlB9ZfcAm/cFpA8tYci9' + \
|
||||
'm5vYK8HNxQr+8FS3Qo8N9RJ8d0U5Csw\n' + \
|
||||
'DzMYfRghAfUGwmlWj5hp1pQzAuhwbOXFt' + \
|
||||
'xKHVsMPhz1IBtF9Y8jvgqgYHLbmyiu1\n' + \
|
||||
'mwJ5AL0pYF0G7x81prlARURwHo0Yf52kE' + \
|
||||
'w1dxpx+JXER7hQRWQki5/NsUEtv+8RT\n' + \
|
||||
'qn2m6qte5DXLyn83b1qRscSdnCCwKtKWU' + \
|
||||
'ug5q2ZbwVOCJCtmRwmnP131lWRYfj67\n' + \
|
||||
'B/xJ1ZA6X3GEf4sNReNAtaucPEelgR2ns' + \
|
||||
'N0gKQKBiGoqHWbK1qYvBxX2X3kbPDkv\n' + \
|
||||
'9C+celgZd2PW7aGYLCHq7nPbmfDV0yHcW' + \
|
||||
'jOhXZ8jRMjmANVR/eLQ2EfsRLdW69bn\n' + \
|
||||
'f3ZD7JS1fwGnO3exGmHO3HZG+6AvberKY' + \
|
||||
'VYNHahNFEw5TsAcQWDLRpkGybBcxqZo\n' + \
|
||||
'81YCqlqidwfeO5YtlO7etx1xLyqa2NsCe' + \
|
||||
'G9A86UjG+aeNnXEIDk1PDK+EuiThIUa\n' + \
|
||||
'/2IxKzJKWl1BKr2d4xAfR0ZnEYuRrbeDQ' + \
|
||||
'YgTImOlfW6/GuYIxKYgEKCFHFqJATAG\n' + \
|
||||
'IxHrq1PDOiSwXd2GmVVYyEmhZnbcp8Cxa' + \
|
||||
'EMQoevxAta0ssMK3w6UsDtvUvYvF22m\n' + \
|
||||
'qQKBiD5GwESzsFPy3Ga0MvZpn3D6EJQLg' + \
|
||||
'snrtUPZx+z2Ep2x0xc5orneB5fGyF1P\n' + \
|
||||
'WtP+fG5Q6Dpdz3LRfm+KwBCWFKQjg7uTx' + \
|
||||
'cjerhBWEYPmEMKYwTJF5PBG9/ddvHLQ\n' + \
|
||||
'EQeNC8fHGg4UXU8mhHnSBt3EA10qQJfRD' + \
|
||||
's15M38eG2cYwB1PZpDHScDnDA0=\n' + \
|
||||
'-----END RSA PRIVATE KEY-----'
|
||||
sigInput = \
|
||||
'sig1=(date); alg=rsa-sha256; keyId="test-key-b"'
|
||||
sig = \
|
||||
|
@ -199,7 +200,8 @@ def testHttpSigNew():
|
|||
# 'CaB8X/I5/+HLZLGvDiezqi6/7p2Gngf5hwZ0lSdy39vyNMaaAT0tKo6nuVw0S1MVg' + \
|
||||
# '1Q7MpWYZs0soHjttq0uLIA3DIbQfLiIvK6/l0BdWTU7+2uQj7lBkQAsFZHoA96ZZg' + \
|
||||
# 'FquQrXRlmYOh+Hx5D9fJkXcXe5tmAg==:'
|
||||
boxpath = '/foo'
|
||||
nickname = 'foo'
|
||||
boxpath = '/' + nickname
|
||||
# headers = {
|
||||
# "*request-target": "get " + boxpath,
|
||||
# "*created": "1402170695",
|
||||
|
@ -214,11 +216,14 @@ def testHttpSigNew():
|
|||
# "Signature-Input": sigInput,
|
||||
# "Signature": sig
|
||||
# }
|
||||
dateStr = "Tue, 07 Jun 2014 20:51:35 GMT"
|
||||
domain = "example.com"
|
||||
port = 443
|
||||
headers = {
|
||||
"*created": "1402170695",
|
||||
"*request-target": "post /foo?param=value&pet=dog",
|
||||
"host": "example.com",
|
||||
"date": "Tue, 07 Jun 2014 20:51:35 GMT",
|
||||
"host": domain,
|
||||
"date": dateStr,
|
||||
"content-type": "application/json",
|
||||
"digest": "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=",
|
||||
"content-length": "18",
|
||||
|
@ -231,6 +236,41 @@ def testHttpSigNew():
|
|||
boxpath, False, None,
|
||||
messageBodyJsonStr, debug,
|
||||
True)
|
||||
# make a deliberate mistake
|
||||
headers['Signature'] = headers['Signature'].replace('V', 'B')
|
||||
assert not verifyPostHeaders(httpPrefix, publicKeyPem, headers,
|
||||
boxpath, False, None,
|
||||
messageBodyJsonStr, debug,
|
||||
True)
|
||||
# test signing
|
||||
bodyDigest = messageContentDigest(messageBodyJsonStr)
|
||||
contentLength = len(messageBodyJsonStr)
|
||||
headers = {
|
||||
"host": domain,
|
||||
"date": dateStr,
|
||||
"digest": f'SHA-256={bodyDigest}',
|
||||
"content-type": "application/json",
|
||||
"content-length": str(contentLength)
|
||||
}
|
||||
signatureIndexHeader, signatureHeader = \
|
||||
signPostHeadersNew(dateStr, privateKeyPem, nickname,
|
||||
domain, port,
|
||||
domain, port,
|
||||
boxpath, httpPrefix, messageBodyJsonStr,
|
||||
'rsa-sha256')
|
||||
assert signatureIndexHeader == \
|
||||
'keyId="https://example.com/users/foo#main-key"; ' + \
|
||||
'alg=hs2019; created=1402170695; ' + \
|
||||
'sig1=(*request-target, *created, host, date, ' + \
|
||||
'digest, content-type, content-length)'
|
||||
assert signatureHeader == \
|
||||
'sig1=:LQU1PcJILSp1Q30GWINusfftYYKfTtam7InSu2c+ZzfGC' + \
|
||||
'bTSevRgifZFuG2asFi8ubG/uUVHiBwIxxIz1u/JyWC3lYIFgjQF' + \
|
||||
'RFM6As2b/ytnMA0LQhNebvk05iUNsz5izSoNTp5h9J7+roWkl6l' + \
|
||||
'8d5EA7vPMTQTJZnyU1cXBlvP1MtuVAKR6MbB3Aa/iZ4XOeaNK5E' + \
|
||||
'1VuPfNFrdnizIELE3nGVoVqNNImgMY3DWhtF3vvezrcT0J2vNGZ' + \
|
||||
'cvhBfgn/xeAsNxz67SIHMgiXvLL6TFqEI1en9dl9A3ihB6ZO6+W' + \
|
||||
'gUoW7OobZNlPxAUkQCc2A6oVjCYOdpKdrMAXQp2TQQ==:'
|
||||
|
||||
|
||||
def _testHttpsigBase(withDigest):
|
||||
|
|
Loading…
Reference in New Issue