Skip to content
This repository was archived by the owner on Apr 13, 2024. It is now read-only.

Commit c4e36fb

Browse files
committed
Create sign_algorithms with first PSS class
1 parent 0a57e36 commit c4e36fb

File tree

6 files changed

+131
-82
lines changed

6 files changed

+131
-82
lines changed

httpsig/sign.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1+
from __future__ import print_function
12
import base64
23
import six
34

45
from Crypto.Hash import HMAC
56
from Crypto.PublicKey import RSA
67
from Crypto.Signature import PKCS1_v1_5
7-
from Crypto.Signature import PKCS1_PSS
8-
8+
from .sign_algorithms import SIGN_ALGORITHMS
99
from .utils import *
1010

11-
1211
DEFAULT_SIGN_ALGORITHM = "hs2019"
13-
DEFAULT_SALT_LENGTH = None
1412

1513

1614
class Signer(object):
@@ -20,14 +18,15 @@ class Signer(object):
2018
2119
Password-protected keyfiles are not supported.
2220
"""
23-
def __init__(self, secret, algorithm=None, sign_algorithm=None, salt_length=None):
21+
22+
def __init__(self, secret, algorithm=None, sign_algorithm=None):
2423
if algorithm is None:
2524
algorithm = DEFAULT_SIGN_ALGORITHM
26-
if salt_length is None:
27-
salt_length = DEFAULT_SALT_LENGTH
2825

2926
assert algorithm in ALGORITHMS, "Unknown algorithm"
30-
assert sign_algorithm is None or sign_algorithm in SIGN_ALGORITHMS, "Unsupported digital signature algorithm"
27+
28+
if sign_algorithm is not None and sign_algorithm.__class__.__name__ not in SIGN_ALGORITHMS:
29+
raise HttpSigException("Unsupported digital signature algorithm")
3130

3231
if algorithm != DEFAULT_SIGN_ALGORITHM:
3332
print("Algorithm: {} is deprecated please update to {}".format(algorithm, DEFAULT_SIGN_ALGORITHM))
@@ -38,13 +37,13 @@ def __init__(self, secret, algorithm=None, sign_algorithm=None, salt_length=None
3837
self._rsa = None
3938
self._hash = None
4039
self.algorithm = algorithm
40+
self.secret = secret
4141

4242
if "-" in algorithm:
4343
self.sign_algorithm, self.hash_algorithm = algorithm.split('-')
4444
elif algorithm == "hs2019":
4545
assert sign_algorithm is not None, "Required digital signature algorithm not specified"
4646
self.sign_algorithm = sign_algorithm
47-
self.hash_algorithm = "sha512"
4847

4948
if self.sign_algorithm == 'rsa':
5049
try:
@@ -58,14 +57,6 @@ def __init__(self, secret, algorithm=None, sign_algorithm=None, salt_length=None
5857
self._hash = HMAC.new(secret,
5958
digestmod=HASHES[self.hash_algorithm])
6059

61-
elif self.sign_algorithm == "PSS":
62-
try:
63-
rsa_key = RSA.importKey(secret)
64-
self._rsa = PKCS1_PSS.new(rsa_key, saltLen=salt_length)
65-
self._hash = HASHES[self.hash_algorithm]
66-
except ValueError:
67-
raise HttpSigException("Invalid key.")
68-
6960
def _sign_rsa(self, data):
7061
if isinstance(data, six.string_types):
7162
data = data.encode("ascii")
@@ -88,6 +79,8 @@ def sign(self, data):
8879
signed = self._sign_rsa(data)
8980
elif self._hash:
9081
signed = self._sign_hmac(data)
82+
elif self.sign_algorithm.__class__.__name__ in SIGN_ALGORITHMS:
83+
signed = self.sign_algorithm.sign(self.secret, data)
9184
if not signed:
9285
raise SystemError('No valid encryptor found.')
9386
return base64.b64encode(signed).decode("ascii")
@@ -111,14 +104,15 @@ class HeaderSigner(Signer):
111104
:param sign_header: header used to include signature, defaulting to
112105
'authorization'.
113106
"""
114-
def __init__(self, key_id, secret, algorithm=None, sign_algorithm=None, salt_length=None, headers=None, sign_header='authorization'):
107+
108+
def __init__(self, key_id, secret, algorithm=None, sign_algorithm=None, headers=None, sign_header='authorization'):
115109
if algorithm is None:
116110
algorithm = DEFAULT_SIGN_ALGORITHM
117111

118-
super(HeaderSigner, self).__init__(secret=secret, algorithm=algorithm, sign_algorithm=sign_algorithm, salt_length=salt_length)
112+
super(HeaderSigner, self).__init__(secret=secret, algorithm=algorithm, sign_algorithm=sign_algorithm)
119113
self.headers = headers or ['date']
120114
self.signature_template = build_signature_template(
121-
key_id, algorithm, headers, sign_header)
115+
key_id, algorithm, headers, sign_header)
122116
self.sign_header = sign_header
123117

124118
def sign(self, headers, host=None, method=None, path=None):
@@ -134,7 +128,7 @@ def sign(self, headers, host=None, method=None, path=None):
134128
headers = CaseInsensitiveDict(headers)
135129
required_headers = self.headers or ['date']
136130
signable = generate_message(
137-
required_headers, headers, host, method, path)
131+
required_headers, headers, host, method, path)
138132

139133
signature = super(HeaderSigner, self).sign(signable)
140134
headers[self.sign_header] = self.signature_template % signature

httpsig/sign_algorithms.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import base64
2+
3+
import six
4+
from Crypto.PublicKey import RSA
5+
from Crypto.Signature import PKCS1_PSS
6+
from httpsig.utils import HttpSigException, HASHES
7+
8+
DEFAULT_HASH_ALGORITHM = "sha512"
9+
10+
11+
class PSS(object):
12+
13+
def __init__(self, hash_algorithm=DEFAULT_HASH_ALGORITHM, salt_length=None, mgfunc=None):
14+
if hash_algorithm not in HASHES:
15+
raise HttpSigException("Unsupported hash algorithm")
16+
17+
if hash_algorithm != DEFAULT_HASH_ALGORITHM:
18+
raise HttpSigException(
19+
"Hash algorithm: {} is deprecated. Please use: {}".format(hash_algorithm, DEFAULT_HASH_ALGORITHM))
20+
21+
self.hash_algorithm = HASHES[hash_algorithm]
22+
self.salt_length = salt_length
23+
self.mgfunc = mgfunc
24+
25+
def _create_pss(self, key):
26+
try:
27+
rsa_key = RSA.importKey(key)
28+
pss = PKCS1_PSS.new(rsa_key, saltLen=self.salt_length, mgfunc=self.mgfunc)
29+
except ValueError:
30+
raise HttpSigException("Invalid key.")
31+
return pss
32+
33+
def sign(self, private_key, data):
34+
pss = self._create_pss(private_key)
35+
36+
if isinstance(data, six.string_types):
37+
data = data.encode("ascii")
38+
39+
h = self.hash_algorithm.new()
40+
h.update(data)
41+
return pss.sign(h)
42+
43+
def verify(self, public_key, data, signature):
44+
pss = self._create_pss(public_key)
45+
46+
h = self.hash_algorithm.new()
47+
h.update(data)
48+
return pss.verify(h, base64.b64decode(signature))
49+
50+
51+
SIGN_ALGORITHMS = frozenset([
52+
"PSS"
53+
])

httpsig/tests/test_signature.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
import unittest
66

7-
import httpsig.sign as sign
8-
from httpsig.utils import parse_authorization_header
7+
import pytest
98

9+
import httpsig.sign as sign
10+
from httpsig.sign_algorithms import PSS
11+
from httpsig.utils import parse_authorization_header, HttpSigException
1012

1113
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
1214

@@ -34,7 +36,7 @@ def setUp(self):
3436
self.key_1024 = f.read()
3537

3638
def test_default(self):
37-
hs = sign.HeaderSigner(key_id='Test', secret=self.key_2048, sign_algorithm="PSS", salt_length=0)
39+
hs = sign.HeaderSigner(key_id='Test', secret=self.key_2048, sign_algorithm=PSS(hash_algorithm="sha512", salt_length=0))
3840
unsigned = {
3941
'Date': self.header_date
4042
}
@@ -52,7 +54,7 @@ def test_default(self):
5254
self.assertEqual(params['signature'], 'T8+Cj3Zp2cBDm2r8/loPgfHUSSFXXyZJNxxbNx1NvKVz/r5T4z6pVxhl9rqk8WfYHMdlh2aT5hCrYKvhs88Jy0DDmeUP4nELWRsO1BF0oAqHfcrbEikZQL7jA6z0guVaLr0S5QRGmd1K5HUEkP/vYEOns+FRL+JrFG4dNJNESvG5iyKUoaXfoZCFdqtzLlIteEAL7dW/kaX/dE116wfpbem1eCABuGopRhuFtjqLKVjuUVwyP/zSYTqd9j+gDhinkAifTJPxbGMh0b5LZdNCqw5irT9NkTcTFRXDp8ioX8r805Z9QhjT7H+rSo350U2LsAFoQ9ttryPBOoMPCiQTlw==') # noqa: E501
5355

5456
def test_basic(self):
55-
hs = sign.HeaderSigner(key_id='Test', secret=self.key_2048, sign_algorithm="PSS", salt_length=0, headers=[
57+
hs = sign.HeaderSigner(key_id='Test', secret=self.key_2048, sign_algorithm=PSS(salt_length=0), headers=[
5658
'(request-target)',
5759
'host',
5860
'date',
@@ -79,7 +81,7 @@ def test_basic(self):
7981
self.assertEqual(params['signature'], 'KkF4oeOJJH9TaYjQdaU634G7AVmM5Bf3fnfJCBZ7G0H5puW5XlQTpduA+TgouKOJhbv4aRRpunPzCHUxUjEvrR3TSALqW1EOsBwCVIusE9CnrhL7vUOvciIDai/jI15RsfR9+XyTmOSFbsI07E8mmywr3nLeWX6AAFDMO2vWc21zZxrSc13vFfAkVvFhXLxO4g0bBm6Z4m5/9ytWtdE0Gf3St2kY8aZTedllRCS8cMx8GVAIw/qYGeIlGKUCZKxrFxnviN7gfxixwova6lcxpppIo+WXxEiwMJfSQBlx0WGn3A3twCv6TsIxPOVUEW4jcogDh+jGFf1aGdVyHquTRQ==') # noqa: E501
8082

8183
def test_all(self):
82-
hs = sign.HeaderSigner(key_id='Test', secret=self.key_2048, sign_algorithm="PSS", salt_length=0, headers=[
84+
hs = sign.HeaderSigner(key_id='Test', secret=self.key_2048, sign_algorithm=PSS("sha512", salt_length=0), headers=[
8385
'(request-target)',
8486
'host',
8587
'date',
@@ -129,3 +131,13 @@ def test_default_deprecated_256(self):
129131
self.assertEqual(params['keyId'], 'Test')
130132
self.assertEqual(params['algorithm'], 'rsa-sha256')
131133
self.assertEqual(params['signature'], 'jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w=') # noqa: E501
134+
135+
def test_unsupported_hash_algorithm(self):
136+
with pytest.raises(HttpSigException) as e:
137+
sign.HeaderSigner(key_id='Test', secret=self.key_2048, sign_algorithm=PSS("sha123", salt_length=0))
138+
self.assertEqual(str(e.value), "Unsupported hash algorithm")
139+
140+
def test_deprecated_hash_algorithm(self):
141+
with pytest.raises(HttpSigException) as e:
142+
sign.HeaderSigner(key_id='Test', secret=self.key_2048, sign_algorithm=PSS("sha256", salt_length=0))
143+
self.assertEqual(str(e.value), "Hash algorithm: sha256 is deprecated. Please use: sha512")

httpsig/tests/test_verify.py

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import unittest
55

66
from httpsig.sign import HeaderSigner, Signer
7+
from httpsig.sign_algorithms import PSS
78
from httpsig.verify import HeaderVerifier, Verifier
89

9-
1010
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
1111

1212

@@ -48,7 +48,7 @@ def setUp(self):
4848
def test_basic_sign(self):
4949
signer = Signer(secret=self.sign_secret, algorithm=self.algorithm, sign_algorithm=self.sign_algorithm)
5050
verifier = Verifier(
51-
secret=self.verify_secret, algorithm=self.algorithm, sign_algorithm=self.sign_algorithm)
51+
secret=self.verify_secret, algorithm=self.algorithm, sign_algorithm=self.sign_algorithm)
5252

5353
GOOD = b"this is a test"
5454
BAD = b"this is not the signature you were looking for..."
@@ -76,19 +76,19 @@ def test_signed_headers(self):
7676
METHOD = self.test_method
7777
PATH = self.test_path
7878
hs = HeaderSigner(
79-
key_id="Test",
80-
secret=self.sign_secret,
81-
algorithm=self.algorithm,
82-
sign_header=self.sign_header,
83-
headers=[
84-
'(request-target)',
85-
'host',
86-
'date',
87-
'content-type',
88-
'digest',
89-
'content-length'
90-
],
91-
sign_algorithm=self.sign_algorithm)
79+
key_id="Test",
80+
secret=self.sign_secret,
81+
algorithm=self.algorithm,
82+
sign_header=self.sign_header,
83+
headers=[
84+
'(request-target)',
85+
'host',
86+
'date',
87+
'content-type',
88+
'digest',
89+
'content-length'
90+
],
91+
sign_algorithm=self.sign_algorithm)
9292
unsigned = {
9393
'Host': HOST,
9494
'Date': self.header_date,
@@ -99,9 +99,9 @@ def test_signed_headers(self):
9999
signed = hs.sign(unsigned, method=METHOD, path=PATH)
100100

101101
hv = HeaderVerifier(
102-
headers=signed, secret=self.verify_secret,
103-
host=HOST, method=METHOD, path=PATH,
104-
sign_header=self.sign_header, sign_algorithm=self.sign_algorithm)
102+
headers=signed, secret=self.verify_secret,
103+
host=HOST, method=METHOD, path=PATH,
104+
sign_header=self.sign_header, sign_algorithm=self.sign_algorithm)
105105
self.assertTrue(hv.verify())
106106

107107
def test_incorrect_headers(self):
@@ -141,18 +141,18 @@ def test_extra_auth_headers(self):
141141
METHOD = "POST"
142142
PATH = '/foo?param=value&pet=dog'
143143
hs = HeaderSigner(
144-
key_id="Test",
145-
secret=self.sign_secret,
146-
sign_header=self.sign_header,
147-
algorithm=self.algorithm, headers=[
148-
'(request-target)',
149-
'host',
150-
'date',
151-
'content-type',
152-
'digest',
153-
'content-length'
154-
],
155-
sign_algorithm=self.sign_algorithm)
144+
key_id="Test",
145+
secret=self.sign_secret,
146+
sign_header=self.sign_header,
147+
algorithm=self.algorithm, headers=[
148+
'(request-target)',
149+
'host',
150+
'date',
151+
'content-type',
152+
'digest',
153+
'content-length'
154+
],
155+
sign_algorithm=self.sign_algorithm)
156156
unsigned = {
157157
'Host': HOST,
158158
'Date': self.header_date,
@@ -162,13 +162,13 @@ def test_extra_auth_headers(self):
162162
}
163163
signed = hs.sign(unsigned, method=METHOD, path=PATH)
164164
hv = HeaderVerifier(
165-
headers=signed,
166-
secret=self.verify_secret,
167-
method=METHOD,
168-
path=PATH,
169-
sign_header=self.sign_header,
170-
required_headers=['date', '(request-target)'],
171-
sign_algorithm=self.sign_algorithm)
165+
headers=signed,
166+
secret=self.verify_secret,
167+
method=METHOD,
168+
path=PATH,
169+
sign_header=self.sign_header,
170+
required_headers=['date', '(request-target)'],
171+
sign_algorithm=self.sign_algorithm)
172172
self.assertTrue(hv.verify())
173173

174174

@@ -190,13 +190,13 @@ class TestVerifyRSASHA1(TestVerifyHMACSHA1):
190190

191191
def setUp(self):
192192
private_key_path = os.path.join(
193-
os.path.dirname(__file__),
193+
os.path.dirname(__file__),
194194
'rsa_private_1024.pem')
195195
with open(private_key_path, 'rb') as f:
196196
private_key = f.read()
197197

198198
public_key_path = os.path.join(
199-
os.path.dirname(__file__),
199+
os.path.dirname(__file__),
200200
'rsa_public_1024.pem')
201201
with open(public_key_path, 'rb') as f:
202202
public_key = f.read()
@@ -229,21 +229,16 @@ class TestVerifyRSASHA512ChangeHeader(TestVerifyRSASHA1):
229229
class TestVerifyHS2019PSS(TestVerifyHMACSHA1):
230230

231231
def setUp(self):
232-
private_key_path = os.path.join(
233-
os.path.dirname(__file__),
234-
'rsa_private_2048.pem')
232+
private_key_path = os.path.join(os.path.dirname(__file__), 'rsa_private_2048.pem')
235233
with open(private_key_path, 'rb') as f:
236234
private_key = f.read()
237235

238-
public_key_path = os.path.join(
239-
os.path.dirname(__file__),
240-
'rsa_public_2048.pem')
236+
public_key_path = os.path.join(os.path.dirname(__file__), 'rsa_public_2048.pem')
241237
with open(public_key_path, 'rb') as f:
242238
public_key = f.read()
243239

244240
self.keyId = "Test"
245241
self.algorithm = "hs2019"
246242
self.sign_secret = private_key
247243
self.verify_secret = public_key
248-
self.sign_algorithm = "PSS"
249-
244+
self.sign_algorithm = PSS(salt_length=0)

httpsig/utils.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@
2525
'sha256': SHA256,
2626
'sha512': SHA512}
2727

28-
SIGN_ALGORITHMS = frozenset([
29-
"PSS"
30-
])
31-
3228

3329
class HttpSigException(Exception):
3430
pass

0 commit comments

Comments
 (0)