Skip to content

Commit f230907

Browse files
committed
Merge pull request googleapis#115 from dhermes/convert-pkcs12-to-pem
Adding protected method to convert PKCS12 key to PEM.
2 parents 2864ebc + 4d02099 commit f230907

File tree

3 files changed

+99
-1
lines changed

3 files changed

+99
-1
lines changed

oauth2client/crypt.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,29 @@ def from_string(key, password=b'notasecret'):
138138
pkey = crypto.load_pkcs12(key, password).get_privatekey()
139139
return OpenSSLSigner(pkey)
140140

141+
142+
def pkcs12_key_as_pem(private_key_text, private_key_password):
143+
"""Convert the contents of a PKCS12 key to PEM using OpenSSL.
144+
145+
Args:
146+
private_key_text: String. Private key.
147+
private_key_password: String. Password for PKCS12.
148+
149+
Returns:
150+
String. PEM contents of ``private_key_text``.
151+
"""
152+
decoded_body = base64.b64decode(private_key_text)
153+
if isinstance(private_key_password, six.string_types):
154+
private_key_password = private_key_password.encode('ascii')
155+
156+
pkcs12 = crypto.load_pkcs12(decoded_body, private_key_password)
157+
return crypto.dump_privatekey(crypto.FILETYPE_PEM,
158+
pkcs12.get_privatekey())
141159
except ImportError:
142160
OpenSSLVerifier = None
143161
OpenSSLSigner = None
162+
def pkcs12_key_as_pem(*args, **kwargs):
163+
raise NotImplementedError('pkcs12_key_as_pem requires OpenSSL.')
144164

145165

146166
try:

tests/test_crypt.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Copyright 2014 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import mock
16+
import os
17+
import sys
18+
import unittest
19+
20+
try:
21+
reload
22+
except NameError:
23+
# For Python3 (though importlib should be used, silly 3.3).
24+
from imp import reload
25+
26+
from oauth2client.client import HAS_OPENSSL
27+
from oauth2client.client import SignedJwtAssertionCredentials
28+
from oauth2client import crypt
29+
30+
31+
def datafile(filename):
32+
f = open(os.path.join(os.path.dirname(__file__), 'data', filename), 'rb')
33+
data = f.read()
34+
f.close()
35+
return data
36+
37+
38+
class Test_pkcs12_key_as_pem(unittest.TestCase):
39+
40+
def _make_signed_jwt_creds(self, private_key_file='privatekey.p12',
41+
private_key=None):
42+
private_key = private_key or datafile(private_key_file)
43+
return SignedJwtAssertionCredentials(
44+
45+
private_key,
46+
scope='read+write',
47+
48+
49+
def test_succeeds(self):
50+
self.assertEqual(True, HAS_OPENSSL)
51+
52+
credentials = self._make_signed_jwt_creds()
53+
pem_contents = crypt.pkcs12_key_as_pem(credentials.private_key,
54+
credentials.private_key_password)
55+
pkcs12_key_as_pem = datafile('pem_from_pkcs12.pem')
56+
pkcs12_key_as_pem = crypt._parse_pem_key(pkcs12_key_as_pem)
57+
self.assertEqual(pem_contents, pkcs12_key_as_pem)
58+
59+
def test_without_openssl(self):
60+
openssl_mod = sys.modules['OpenSSL']
61+
try:
62+
sys.modules['OpenSSL'] = None
63+
reload(crypt)
64+
self.assertRaises(NotImplementedError, crypt.pkcs12_key_as_pem,
65+
'FOO', 'BAR')
66+
finally:
67+
sys.modules['OpenSSL'] = openssl_mod
68+
reload(crypt)
69+
70+
def test_with_nonsense_key(self):
71+
credentials = self._make_signed_jwt_creds(private_key=b'NOT_A_KEY')
72+
self.assertRaises(crypt.crypto.Error, crypt.pkcs12_key_as_pem,
73+
credentials.private_key, credentials.private_key_password)

tests/test_jwt.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,21 @@
2323
__author__ = '[email protected] (Joe Gregorio)'
2424

2525
import os
26+
import mock
2627
import sys
2728
import tempfile
2829
import time
2930
import unittest
3031

3132
from .http_mock import HttpMockSequence
32-
from oauth2client import crypt
33+
from oauth2client import client
3334
from oauth2client.client import Credentials
3435
from oauth2client.client import SignedJwtAssertionCredentials
3536
from oauth2client.client import VerifyJwtTokenError
3637
from oauth2client.client import verify_id_token
3738
from oauth2client.client import HAS_OPENSSL
3839
from oauth2client.client import HAS_CRYPTO
40+
from oauth2client import crypt
3941
from oauth2client.file import Storage
4042

4143

@@ -47,6 +49,7 @@ def datafile(filename):
4749

4850

4951
class CryptTests(unittest.TestCase):
52+
5053
def setUp(self):
5154
self.format = 'p12'
5255
self.signer = crypt.OpenSSLSigner
@@ -291,6 +294,7 @@ def setUp(self):
291294

292295

293296
class PKCSSignedJwtAssertionCredentialsPyCryptoTests(unittest.TestCase):
297+
294298
def test_for_failure(self):
295299
crypt.Signer = crypt.PyCryptoSigner
296300
private_key = datafile('privatekey.p12')
@@ -311,5 +315,6 @@ def test_true(self):
311315
self.assertEqual(True, HAS_OPENSSL)
312316
self.assertEqual(True, HAS_CRYPTO)
313317

318+
314319
if __name__ == '__main__':
315320
unittest.main()

0 commit comments

Comments
 (0)