Skip to content

Commit 987cc34

Browse files
author
Kit Cambridge
committed
Factor out initialization into separate class methods.
This commit splits the `Vapid` constructor into three class methods: * `Vapid.from_file` reads a private key file in PEM or DER format, and creates a `Vapid` object based on the contents. This is equivalent to calling `Vapid(private_key_file="/path")`. * `Vapid.from_pem` creates a `Vapid` object from a string containing a PEM-encoded private key. * `Vapid.from_der` creates a `Vapid` object from a string containing a DER-encoded key.
1 parent d169f22 commit 987cc34

File tree

3 files changed

+77
-41
lines changed

3 files changed

+77
-41
lines changed

python/py_vapid/__init__.py

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,65 @@ class Vapid01(object):
2929
"""
3030
_private_key = None
3131
_public_key = None
32+
_curve = ecdsa.NIST256p
3233
_hasher = hashlib.sha256
3334
_schema = "WebPush"
3435

35-
def __init__(self, private_key_file=None, private_key=None):
36-
"""Initialize VAPID using an optional file containing a private key
37-
in PEM format, or a string containing the PEM formatted private key.
36+
def __init__(self, private_key=None):
37+
"""Initialize VAPID with an optional private key.
38+
39+
:param private_key: A private key object
40+
:type private_key: ecdsa.SigningKey
41+
42+
"""
43+
self.private_key = private_key
44+
45+
@classmethod
46+
def from_pem(cls, private_key):
47+
"""Initialize VAPID using a private key in PEM format.
48+
49+
:param private_key: A private key in PEM format.
50+
:type private_key: str
51+
52+
"""
53+
key = ecdsa.SigningKey.from_pem(private_key)
54+
return cls(key)
55+
56+
@classmethod
57+
def from_der(cls, private_key):
58+
"""Initialize VAPID using a private key in DER format.
59+
60+
:param private_key: A private key in DER format and Base64-encoded.
61+
:type private_key: str
62+
63+
"""
64+
key = ecdsa.SigningKey.from_der(base64.b64decode(private_key))
65+
return cls(key)
66+
67+
@classmethod
68+
def from_file(cls, private_key_file=None):
69+
"""Initialize VAPID using a file containing a private key in PEM or
70+
DER format.
3871
3972
:param private_key_file: Name of the file containing the private key
4073
:type private_key_file: str
41-
:param private_key: A private key in PEM format
42-
:type private_key: str
4374
4475
"""
45-
if private_key_file:
46-
if not os.path.isfile(private_key_file):
47-
self.save_key(private_key_file)
48-
return
49-
private_key = open(private_key_file, 'r').read()
50-
if private_key:
51-
try:
52-
if "BEGIN EC" in private_key:
53-
self._private_key = ecdsa.SigningKey.from_pem(private_key)
54-
else:
55-
self._private_key = \
56-
ecdsa.SigningKey.from_der(
57-
base64.urlsafe_b64decode(private_key))
58-
except Exception as exc:
59-
logging.error("Could not open private key file: %s", repr(exc))
60-
raise VapidException(exc)
61-
self._public_key = self._private_key.get_verifying_key()
76+
if not os.path.isfile(private_key_file):
77+
vapid = cls()
78+
vapid.save_key(private_key_file)
79+
return vapid
80+
private_key = open(private_key_file, 'r').read()
81+
vapid = None
82+
try:
83+
if "BEGIN EC" in private_key:
84+
vapid = cls.from_pem(private_key)
85+
else:
86+
vapid = cls.from_der(private_key)
87+
except Exception as exc:
88+
logging.error("Could not open private key file: %s", repr(exc))
89+
raise VapidException(exc)
90+
return vapid
6291

6392
@property
6493
def private_key(self):
@@ -77,6 +106,7 @@ def private_key(self, value):
77106
78107
"""
79108
self._private_key = value
109+
self._public_key = None
80110

81111
@property
82112
def public_key(self):
@@ -92,8 +122,7 @@ def public_key(self):
92122

93123
def generate_keys(self):
94124
"""Generate a valid ECDSA Key Pair."""
95-
self.private_key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
96-
self._public_key = self.private_key.get_verifying_key()
125+
self.private_key = ecdsa.SigningKey.generate(curve=self._curve)
97126

98127
def save_key(self, key_file):
99128
"""Save the private key to a PEM file.

python/py_vapid/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def main():
3939
exit
4040
print("Generating private_key.pem")
4141
Vapid().save_key('private_key.pem')
42-
vapid = Vapid('private_key.pem')
42+
vapid = Vapid.from_file('private_key.pem')
4343
if args.gen or not os.path.exists('public_key.pem'):
4444
if not args.gen:
4545
print("No public_key.pem file found. You'll need this to access "

python/py_vapid/tests/test_vapid.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,25 @@
1010
from jose import jws
1111
from py_vapid import Vapid01, Vapid02, VapidException
1212

13+
# This is a private key in DER form.
1314
T_DER = """
1415
MHcCAQEEIPeN1iAipHbt8+/KZ2NIF8NeN24jqAmnMLFZEMocY8RboAoGCCqGSM49
1516
AwEHoUQDQgAEEJwJZq/GN8jJbo1GGpyU70hmP2hbWAUpQFKDByKB81yldJ9GTklB
1617
M5xqEwuPM7VuQcyiLDhvovthPIXx+gsQRQ==
1718
"""
19+
20+
# This is the same private key, in PEM form.
1821
T_PRIVATE = ("-----BEGIN EC PRIVATE KEY-----{}"
1922
"-----END EC PRIVATE KEY-----\n").format(T_DER)
2023

24+
# This is a public key in PEM form.
2125
T_PUBLIC = """-----BEGIN PUBLIC KEY-----
2226
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEJwJZq/GN8jJbo1GGpyU70hmP2hb
2327
WAUpQFKDByKB81yldJ9GTklBM5xqEwuPM7VuQcyiLDhvovthPIXx+gsQRQ==
2428
-----END PUBLIC KEY-----
2529
"""
2630

27-
# this is a DER RAW key ('\x04' + 2 32 octet digits)
31+
# this is a public key in uncompressed form ('\x04' + 2 * 32 octets)
2832
# Remember, this should have any padding stripped.
2933
T_PUBLIC_RAW = (
3034
"BBCcCWavxjfIyW6NRhqclO9IZj9oW1gFKUBSgwcigfNc"
@@ -33,12 +37,12 @@
3337

3438

3539
def setUp(self):
36-
ff = open('/tmp/private', 'w')
37-
ff.write(T_PRIVATE)
38-
ff.close()
39-
ff = open('/tmp/public', 'w')
40-
ff.write(T_PUBLIC)
41-
ff.close()
40+
with open('/tmp/private', 'w') as ff:
41+
ff.write(T_PRIVATE)
42+
with open('/tmp/public', 'w') as ff:
43+
ff.write(T_PUBLIC)
44+
with open('/tmp/private.der', 'w') as ff:
45+
ff.write(T_DER)
4246

4347

4448
def tearDown(self):
@@ -48,17 +52,20 @@ def tearDown(self):
4852

4953
class VapidTestCase(unittest.TestCase):
5054
def test_init(self):
51-
v1 = Vapid01(private_key_file="/tmp/private")
55+
v1 = Vapid01.from_file("/tmp/private")
5256
eq_(v1.private_key.to_pem(), T_PRIVATE.encode('utf8'))
5357
eq_(v1.public_key.to_pem(), T_PUBLIC.encode('utf8'))
54-
v2 = Vapid01(private_key=T_PRIVATE)
58+
v2 = Vapid01.from_pem(T_PRIVATE)
5559
eq_(v2.private_key.to_pem(), T_PRIVATE.encode('utf8'))
5660
eq_(v2.public_key.to_pem(), T_PUBLIC.encode('utf8'))
57-
v3 = Vapid01(private_key=T_DER)
61+
v3 = Vapid01.from_der(T_DER)
5862
eq_(v3.private_key.to_pem(), T_PRIVATE.encode('utf8'))
5963
eq_(v3.public_key.to_pem(), T_PUBLIC.encode('utf8'))
64+
v4 = Vapid01.from_file("/tmp/private.der")
65+
eq_(v4.private_key.to_pem(), T_PRIVATE.encode('utf8'))
66+
eq_(v4.public_key.to_pem(), T_PUBLIC.encode('utf8'))
6067
no_exist = '/tmp/not_exist'
61-
Vapid01(private_key_file=no_exist)
68+
Vapid01.from_file(no_exist)
6269
ok_(os.path.isfile(no_exist))
6370
os.unlink(no_exist)
6471

@@ -68,7 +75,7 @@ def repad(self, data):
6875
@patch("ecdsa.SigningKey.from_pem", side_effect=Exception)
6976
def test_init_bad_priv(self, mm):
7077
self.assertRaises(Exception,
71-
Vapid01,
78+
Vapid01.from_file,
7279
private_key_file="/tmp/private")
7380

7481
def test_private(self):
@@ -98,7 +105,7 @@ def test_same_public_key(self):
98105
os.unlink("/tmp/p2")
99106

100107
def test_validate(self):
101-
v = Vapid01("/tmp/private")
108+
v = Vapid01.from_file("/tmp/private")
102109
msg = "foobar".encode('utf8')
103110
vtoken = v.validate(msg)
104111
ok_(v.public_key.verify(base64.urlsafe_b64decode(vtoken),
@@ -108,7 +115,7 @@ def test_validate(self):
108115
ok_(v.verify_token(msg, vtoken))
109116

110117
def test_sign_01(self):
111-
v = Vapid01("/tmp/private")
118+
v = Vapid01.from_file("/tmp/private")
112119
claims = {"aud": "example.com", "sub": "[email protected]"}
113120
result = v.sign(claims, "id=previous")
114121
eq_(result['Crypto-Key'],
@@ -123,7 +130,7 @@ def test_sign_01(self):
123130
'p256ecdsa=' + T_PUBLIC_RAW)
124131

125132
def test_sign_02(self):
126-
v = Vapid02("/tmp/private")
133+
v = Vapid02.from_file("/tmp/private")
127134
claims = {"aud": "example.com",
128135
129136
"foo": "extra value"}
@@ -144,7 +151,7 @@ def test_sign_02(self):
144151
eq_(t_val[k], claims[k])
145152

146153
def test_bad_sign(self):
147-
v = Vapid01("/tmp/private")
154+
v = Vapid01.from_file("/tmp/private")
148155
self.assertRaises(VapidException,
149156
v.sign,
150157
{'aud': "p.example.com"})

0 commit comments

Comments
 (0)