Skip to content

Commit 17c3414

Browse files
committed
Adding cryptography Signer.
Fixes #183. NOTE: This change is incomplete. It needs unit tests and a Verifier implementation.
1 parent 9281ca0 commit 17c3414

File tree

4 files changed

+147
-49
lines changed

4 files changed

+147
-49
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Copyright 2017 Google Inc.
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+
"""Verifier and signer that use the ``cryptography`` library.
16+
17+
This is a much faster implementation than the default (in
18+
``google.auth.crypt._python_rsa``), which depends on the pure-Python
19+
``rsa`` library.
20+
"""
21+
22+
from cryptography.hazmat import backends
23+
from cryptography.hazmat.primitives.asymmetric import padding
24+
from cryptography.hazmat.primitives import hashes
25+
from cryptography.hazmat.primitives import serialization
26+
27+
from google.auth import _helpers
28+
from google.auth.crypt import base
29+
30+
31+
_BACKEND = backends.default_backend()
32+
_PADDING = padding.PKCS1v15()
33+
_SHA256 = hashes.SHA256()
34+
35+
36+
class CryptographySigner(base.Signer, base._FromServiceAccountMixin):
37+
"""Signs messages with a cryptography ``PKey`` private key.
38+
39+
Args:
40+
private_key (cryptography.hazmat.backends.openssl.rsa._RSAPrivateKey):
41+
The private key to sign with.
42+
key_id (str): Optional key ID used to identify this private key. This
43+
can be useful to associate the private key with its associated
44+
public key or certificate.
45+
"""
46+
47+
def __init__(self, private_key, key_id=None):
48+
self._key = private_key
49+
self._key_id = key_id
50+
51+
@property
52+
@_helpers.copy_docstring(base.Signer)
53+
def key_id(self):
54+
return self._key_id
55+
56+
@_helpers.copy_docstring(base.Signer)
57+
def sign(self, message):
58+
message = _helpers.to_bytes(message)
59+
return self._key.sign(
60+
message, _PADDING, _SHA256)
61+
62+
@classmethod
63+
def from_string(cls, key, key_id=None):
64+
"""Construct a CryptographySigner from a private key in PEM format.
65+
66+
Args:
67+
key (Union[bytes, str]): Private key in PEM format.
68+
key_id (str): An optional key id used to identify the private key.
69+
70+
Returns:
71+
google.auth.crypt._cryptography_rsa.CryptographySigner: The
72+
constructed signer.
73+
74+
Raises:
75+
ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode).
76+
UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded
77+
into a UTF-8 ``str``.
78+
ValueError: If ``cryptography`` "Could not deserialize key data."
79+
"""
80+
message = _helpers.to_bytes(key)
81+
private_key = serialization.load_pem_private_key(
82+
key, password=None, backend=_BACKEND)
83+
return cls(private_key, key_id=key_id)

google/auth/crypt/_python_rsa.py

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121

2222
from __future__ import absolute_import
2323

24-
import io
25-
import json
26-
2724
from pyasn1.codec.der import decoder
2825
from pyasn1_modules import pem
2926
from pyasn1_modules.rfc2459 import Certificate
@@ -41,8 +38,6 @@
4138
_PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----',
4239
'-----END PRIVATE KEY-----')
4340
_PKCS8_SPEC = PrivateKeyInfo()
44-
_JSON_FILE_PRIVATE_KEY = 'private_key'
45-
_JSON_FILE_PRIVATE_KEY_ID = 'private_key_id'
4641

4742

4843
def _bit_list_to_bytes(bit_list):
@@ -119,7 +114,7 @@ def from_string(cls, public_key):
119114
return cls(pubkey)
120115

121116

122-
class RSASigner(base.Signer):
117+
class RSASigner(base.Signer, base._FromServiceAccountMixin):
123118
"""Signs messages with an RSA private key.
124119
125120
Args:
@@ -179,43 +174,3 @@ def from_string(cls, key, key_id=None):
179174
raise ValueError('No key could be detected.')
180175

181176
return cls(private_key, key_id=key_id)
182-
183-
@classmethod
184-
def from_service_account_info(cls, info):
185-
"""Creates a Signer instance instance from a dictionary containing
186-
service account info in Google format.
187-
188-
Args:
189-
info (Mapping[str, str]): The service account info in Google
190-
format.
191-
192-
Returns:
193-
google.auth.crypt.Signer: The constructed signer.
194-
195-
Raises:
196-
ValueError: If the info is not in the expected format.
197-
"""
198-
if _JSON_FILE_PRIVATE_KEY not in info:
199-
raise ValueError(
200-
'The private_key field was not found in the service account '
201-
'info.')
202-
203-
return cls.from_string(
204-
info[_JSON_FILE_PRIVATE_KEY],
205-
info.get(_JSON_FILE_PRIVATE_KEY_ID))
206-
207-
@classmethod
208-
def from_service_account_file(cls, filename):
209-
"""Creates a Signer instance from a service account .json file
210-
in Google format.
211-
212-
Args:
213-
filename (str): The path to the service account .json file.
214-
215-
Returns:
216-
google.auth.crypt.Signer: The constructed signer.
217-
"""
218-
with io.open(filename, 'r', encoding='utf-8') as json_file:
219-
data = json.load(json_file)
220-
221-
return cls.from_service_account_info(data)

google/auth/crypt/base.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@
1515
"""Base classes for cryptographic signers and verifiers."""
1616

1717
import abc
18+
import io
19+
import json
1820

1921
import six
2022

2123

24+
_JSON_FILE_PRIVATE_KEY = 'private_key'
25+
_JSON_FILE_PRIVATE_KEY_ID = 'private_key_id'
26+
27+
2228
@six.add_metaclass(abc.ABCMeta)
2329
class Verifier(object):
2430
"""Abstract base class for crytographic signature verifiers."""
@@ -62,3 +68,51 @@ def sign(self, message):
6268
# pylint: disable=missing-raises-doc,redundant-returns-doc
6369
# (pylint doesn't recognize that this is abstract)
6470
raise NotImplementedError('Sign must be implemented')
71+
72+
73+
class _FromServiceAccountMixin(object):
74+
"""Mix-in to enable factory constructors for a Signer.
75+
76+
Assumes this class will be mixed in with a class that has
77+
implemented a ``from_string`` class method.
78+
"""
79+
80+
@classmethod
81+
def from_service_account_info(cls, info):
82+
"""Creates a Signer instance instance from a dictionary containing
83+
service account info in Google format.
84+
85+
Args:
86+
info (Mapping[str, str]): The service account info in Google
87+
format.
88+
89+
Returns:
90+
google.auth.crypt.Signer: The constructed signer.
91+
92+
Raises:
93+
ValueError: If the info is not in the expected format.
94+
"""
95+
if _JSON_FILE_PRIVATE_KEY not in info:
96+
raise ValueError(
97+
'The private_key field was not found in the service account '
98+
'info.')
99+
100+
return cls.from_string(
101+
info[_JSON_FILE_PRIVATE_KEY],
102+
info.get(_JSON_FILE_PRIVATE_KEY_ID))
103+
104+
@classmethod
105+
def from_service_account_file(cls, filename):
106+
"""Creates a Signer instance from a service account .json file
107+
in Google format.
108+
109+
Args:
110+
filename (str): The path to the service account .json file.
111+
112+
Returns:
113+
google.auth.crypt.Signer: The constructed signer.
114+
"""
115+
with io.open(filename, 'r', encoding='utf-8') as json_file:
116+
data = json.load(json_file)
117+
118+
return cls.from_service_account_info(data)

google/auth/crypt/rsa.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@
1414

1515
"""RSA cryptography signer and verifier."""
1616

17-
from google.auth.crypt import _python_rsa
17+
try:
18+
from google.auth.crypt import _cryptography_rsa
1819

19-
RSASigner = _python_rsa.RSASigner
20-
RSAVerifier = _python_rsa.RSAVerifier
20+
RSASigner = _cryptography_rsa.CryptographySigner
21+
RSAVerifier = NotImplemented
22+
except ImportError:
23+
from google.auth.crypt import _python_rsa
24+
25+
RSASigner = _python_rsa.RSASigner
26+
RSAVerifier = _python_rsa.RSAVerifier

0 commit comments

Comments
 (0)