Skip to content

Commit 9044354

Browse files
author
Gasper Zejn
committed
Fix to_pem for private keys to support both PKCS#1 and PKCS#8.
1 parent de10ef4 commit 9044354

File tree

4 files changed

+49
-18
lines changed

4 files changed

+49
-18
lines changed

jose/backends/cryptography_backend.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,16 +282,23 @@ def public_key(self):
282282
return self
283283
return self.__class__(self.prepared_key.public_key(), self._algorithm)
284284

285-
def to_pem(self):
285+
def to_pem(self, pem_format='PKCS8'):
286286
if self.is_public():
287287
return self.prepared_key.public_bytes(
288288
encoding=serialization.Encoding.PEM,
289289
format=serialization.PublicFormat.SubjectPublicKeyInfo
290290
)
291291

292+
if pem_format == 'PKCS8':
293+
fmt = serialization.PrivateFormat.PKCS8
294+
elif pem_format == 'PKCS1':
295+
fmt = serialization.PrivateFormat.TraditionalOpenSSL
296+
else:
297+
raise ValueError("Invalid format specified: %r" % pem_format)
298+
292299
return self.prepared_key.private_bytes(
293300
encoding=serialization.Encoding.PEM,
294-
format=serialization.PrivateFormat.TraditionalOpenSSL,
301+
format=fmt,
295302
encryption_algorithm=serialization.NoEncryption()
296303
)
297304

jose/backends/pycrypto_backend.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,15 @@ def public_key(self):
140140
return self
141141
return self.__class__(self.prepared_key.publickey(), self._algorithm)
142142

143-
def to_pem(self):
144-
pem = self.prepared_key.exportKey('PEM', pkcs=1)
145-
146-
# pycryptodome fix
147-
begin = b'-----BEGIN RSA PUBLIC KEY-----'
148-
end = b'-----END RSA PUBLIC KEY-----'
149-
if pem.startswith(begin) and pem.strip().endswith(end):
150-
pem = b'-----BEGIN PUBLIC KEY-----' + pem.strip()[len(begin):-len(end)] + b'-----END PUBLIC KEY-----'
151-
if not pem.endswith(b'\n'):
152-
pem = pem + b'\n'
143+
def to_pem(self, pem_format='PKCS8'):
144+
if pem_format == 'PKCS8':
145+
pkcs = 8
146+
elif pem_format == 'PKCS1':
147+
pkcs = 1
148+
else:
149+
raise ValueError("Invalid pem format specified: %r" % (pem_format,))
150+
151+
pem = self.prepared_key.exportKey('PEM', pkcs=pkcs)
153152
return pem
154153

155154
def to_dict(self):

jose/backends/rsa_backend.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import rsa as pyrsa
2+
import rsa.pem as pyrsa_pem
23
import six
34

45
from jose.backends.base import Key
@@ -7,6 +8,7 @@
78
from jose.utils import base64_to_long, long_to_base64
89

910

11+
PKCS8_RSA_HEADER = b'0\x82\x04\xbd\x02\x01\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00'
1012
# Functions gcd and rsa_recover_prime_factors were copied from cryptography 1.9
1113
# to enable pure python rsa module to be in compliance with section 6.3.1 of RFC7518
1214
# which requires only private exponent (d) for private key.
@@ -103,9 +105,20 @@ def __init__(self, key, algorithm):
103105
self._prepared_key = pyrsa.PublicKey.load_pkcs1(key)
104106
except ValueError:
105107
try:
106-
self._prepared_key = pyrsa.PrivateKey.load_pkcs1(key)
107-
except ValueError as e:
108-
raise JWKError(e)
108+
self._prepared_key = pyrsa.PublicKey.load_pkcs1_openssl_pem(key)
109+
except ValueError:
110+
try:
111+
self._prepared_key = pyrsa.PrivateKey.load_pkcs1(key)
112+
except ValueError:
113+
try:
114+
# python-rsa does not support PKCS8 yet so we have to manually remove OID
115+
der = pyrsa_pem.load_pem(key, b'PRIVATE KEY')
116+
header, der = der[:22], der[22:]
117+
if header != PKCS8_RSA_HEADER:
118+
raise ValueError("Invalid PKCS8 header")
119+
self._prepared_key = pyrsa.PrivateKey._load_pkcs1_der(der)
120+
except ValueError as e:
121+
raise JWKError(e)
109122
return
110123
raise JWKError('Unable to parse an RSA_JWK from key: %s' % key)
111124

@@ -157,11 +170,17 @@ def public_key(self):
157170
return self
158171
return self.__class__(pyrsa.PublicKey(n=self._prepared_key.n, e=self._prepared_key.e), self._algorithm)
159172

160-
def to_pem(self):
173+
def to_pem(self, pem_format='PKCS8'):
161174
import rsa.pem
162175

163176
if isinstance(self._prepared_key, pyrsa.PrivateKey):
164-
pem = self._prepared_key.save_pkcs1()
177+
der = self._prepared_key.save_pkcs1(format='DER')
178+
if pem_format == 'PKCS8':
179+
pem = rsa.pem.save_pem(PKCS8_RSA_HEADER + der, pem_marker='PRIVATE KEY')
180+
elif pem_format == 'PKCS1':
181+
pem = rsa.pem.save_pem(der, pem_marker='RSA PRIVATE KEY')
182+
else:
183+
raise ValueError("Invalid pem format specified: %r" % (pem_format,))
165184
else:
166185
# this is a PKCS#8 DER header to identify rsaEncryption
167186
header = b'0\x82\x04\xbd\x02\x01\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00'

tests/algorithms/test_RSA.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,13 @@ def test_get_public_key(self, Backend):
176176
@pytest.mark.parametrize("Backend", [RSAKey, CryptographyRSAKey, PurePythonRSAKey])
177177
def test_to_pem(self, Backend):
178178
key = Backend(private_key, ALGORITHMS.RS256)
179-
assert key.to_pem().strip() == private_key.strip()
179+
assert key.to_pem(pem_format='PKCS1').strip() == private_key.strip()
180+
181+
pkcs8 = key.to_pem(pem_format='PKCS8').strip()
182+
assert pkcs8 != private_key.strip()
183+
184+
newkey = Backend(pkcs8, ALGORITHMS.RS256)
185+
assert newkey.to_pem(pem_format='PKCS1').strip() == private_key.strip()
180186

181187
def assert_parameters(self, as_dict, private):
182188
assert isinstance(as_dict, dict)

0 commit comments

Comments
 (0)