blob: 05839b46f691fdf4c3bac1db80c956fd1bac0452 [file] [log] [blame]
Jon Wayne Parrott8713a712016-10-04 14:19:01 -07001# Copyright 2016 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"""Cryptography helpers for verifying and signing messages.
16
17Uses the ``rsa``, ``pyasn1`` and ``pyasn1_modules`` packages
18to parse PEM files storing PKCS#1 or PKCS#8 keys as well as
19certificates. There is no support for p12 files.
20
21The simplest way to verify signatures is using :func:`verify_signature`::
22
23 cert = open('certs.pem').read()
24 valid = crypt.verify_signature(message, signature, cert)
25
26If you're going to verify many messages with the same certificate, you can use
27:class:`Verifier`::
28
29 cert = open('certs.pem').read()
30 verifier = crypt.Verifier.from_string(cert)
31 valid = verifier.verify(message, signature)
32
33
34To sign messages use :class:`Signer` with a private key::
35
36 private_key = open('private_key.pem').read()
37 signer = crypt.Signer(private_key)
38 signature = signer.sign(message)
39
40"""
Jon Wayne Parrott54451fb2016-12-14 12:20:24 -080041import io
42import json
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070043
44from pyasn1.codec.der import decoder
45from pyasn1_modules import pem
46from pyasn1_modules.rfc2459 import Certificate
47from pyasn1_modules.rfc5208 import PrivateKeyInfo
48import rsa
49import six
50
51from google.auth import _helpers
52
53_POW2 = (128, 64, 32, 16, 8, 4, 2, 1)
54_CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----'
55_PKCS1_MARKER = ('-----BEGIN RSA PRIVATE KEY-----',
56 '-----END RSA PRIVATE KEY-----')
57_PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----',
58 '-----END PRIVATE KEY-----')
59_PKCS8_SPEC = PrivateKeyInfo()
Jon Wayne Parrott54451fb2016-12-14 12:20:24 -080060_JSON_FILE_PRIVATE_KEY = 'private_key'
61_JSON_FILE_PRIVATE_KEY_ID = 'private_key_id'
Jon Wayne Parrott8713a712016-10-04 14:19:01 -070062
63
64def _bit_list_to_bytes(bit_list):
65 """Converts an iterable of 1s and 0s to bytes.
66
67 Combines the list 8 at a time, treating each group of 8 bits
68 as a single byte.
69
70 Args:
71 bit_list (Sequence): Sequence of 1s and 0s.
72
73 Returns:
74 bytes: The decoded bytes.
75 """
76 num_bits = len(bit_list)
77 byte_vals = bytearray()
78 for start in six.moves.xrange(0, num_bits, 8):
79 curr_bits = bit_list[start:start + 8]
80 char_val = sum(val * digit
81 for val, digit in six.moves.zip(_POW2, curr_bits))
82 byte_vals.append(char_val)
83 return bytes(byte_vals)
84
85
86class Verifier(object):
87 """This object is used to verify cryptographic signatures.
88
89 Args:
90 public_key (rsa.key.PublicKey): The public key used to verify
91 signatures.
92 """
93
94 def __init__(self, public_key):
95 self._pubkey = public_key
96
97 def verify(self, message, signature):
98 """Verifies a message against a cryptographic signature.
99
100 Args:
101 message (Union[str, bytes]): The message to verify.
102 signature (Union[str, bytes]): The cryptography signature to check.
103
104 Returns:
105 bool: True if message was signed by the private key associated
106 with the public key that this object was constructed with.
107 """
108 message = _helpers.to_bytes(message)
109 try:
110 return rsa.pkcs1.verify(message, signature, self._pubkey)
111 except (ValueError, rsa.pkcs1.VerificationError):
112 return False
113
114 @classmethod
115 def from_string(cls, public_key):
116 """Construct an Verifier instance from a public key or public
117 certificate string.
118
119 Args:
120 public_key (Union[str, bytes]): The public key in PEM format or the
121 x509 public key certificate.
122
123 Returns:
124 Verifier: The constructed verifier.
125
126 Raises:
127 ValueError: If the public_key can't be parsed.
128 """
129 public_key = _helpers.to_bytes(public_key)
130 is_x509_cert = _CERTIFICATE_MARKER in public_key
131
132 # If this is a certificate, extract the public key info.
133 if is_x509_cert:
134 der = rsa.pem.load_pem(public_key, 'CERTIFICATE')
135 asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate())
136 if remaining != b'':
137 raise ValueError('Unused bytes', remaining)
138
139 cert_info = asn1_cert['tbsCertificate']['subjectPublicKeyInfo']
140 key_bytes = _bit_list_to_bytes(cert_info['subjectPublicKey'])
141 pubkey = rsa.PublicKey.load_pkcs1(key_bytes, 'DER')
142 else:
143 pubkey = rsa.PublicKey.load_pkcs1(public_key, 'PEM')
144 return cls(pubkey)
145
146
147def verify_signature(message, signature, certs):
148 """Verify a cryptographic signature.
149
150 Checks that the provided ``signature`` was generated from ``bytes`` using
151 the private key associated with the ``cert``.
152
153 Args:
154 message (Union[str, bytes]): The plaintext message.
155 signature (Union[str, bytes]): The cryptographic signature to check.
156 certs (Union[Sequence, str, bytes]): The certificate or certificates
157 to use to check the signature.
158
159 Returns:
160 bool: True if the signature is valid, otherwise False.
161 """
162 if isinstance(certs, (six.text_type, six.binary_type)):
163 certs = [certs]
164
165 for cert in certs:
166 verifier = Verifier.from_string(cert)
167 if verifier.verify(message, signature):
168 return True
169 return False
170
171
172class Signer(object):
173 """Signs messages with a private key.
174
175 Args:
176 private_key (rsa.key.PrivateKey): The private key to sign with.
177 key_id (str): Optional key ID used to identify this private key. This
178 can be useful to associate the private key with its associated
179 public key or certificate.
180 """
181
182 def __init__(self, private_key, key_id=None):
183 self._key = private_key
184 self.key_id = key_id
Jon Wayne Parrott924191c2017-02-15 16:43:23 -0800185 """Optional[str]: The key ID used to identify this private key."""
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700186
187 def sign(self, message):
188 """Signs a message.
189
190 Args:
191 message (Union[str, bytes]): The message to be signed.
192
193 Returns:
Jon Wayne Parrott256d2bf2016-12-13 16:12:02 -0800194 bytes: The signature of the message.
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700195 """
196 message = _helpers.to_bytes(message)
197 return rsa.pkcs1.sign(message, self._key, 'SHA-256')
198
199 @classmethod
200 def from_string(cls, key, key_id=None):
201 """Construct an Signer instance from a private key in PEM format.
202
203 Args:
204 key (str): Private key in PEM format.
205 key_id (str): An optional key id used to identify the private key.
206
207 Returns:
Jon Wayne Parrott20e6e582016-12-19 10:21:23 -0800208 google.auth.crypt.Signer: The constructed signer.
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700209
210 Raises:
211 ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in
212 PEM format.
213 """
214 key = _helpers.from_bytes(key) # PEM expects str in Python 3
215 marker_id, key_bytes = pem.readPemBlocksFromFile(
216 six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER)
217
218 # Key is in pkcs1 format.
219 if marker_id == 0:
220 private_key = rsa.key.PrivateKey.load_pkcs1(
221 key_bytes, format='DER')
222 # Key is in pkcs8.
223 elif marker_id == 1:
224 key_info, remaining = decoder.decode(
225 key_bytes, asn1Spec=_PKCS8_SPEC)
226 if remaining != b'':
227 raise ValueError('Unused bytes', remaining)
228 private_key_info = key_info.getComponentByName('privateKey')
229 private_key = rsa.key.PrivateKey.load_pkcs1(
230 private_key_info.asOctets(), format='DER')
231 else:
232 raise ValueError('No key could be detected.')
233
234 return cls(private_key, key_id=key_id)
Jon Wayne Parrottfc169292016-12-13 16:02:40 -0800235
236 @classmethod
Jon Wayne Parrott54451fb2016-12-14 12:20:24 -0800237 def from_service_account_info(cls, info):
238 """Creates a Signer instance instance from a dictionary containing
239 service account info in Google format.
240
241 Args:
242 info (Mapping[str, str]): The service account info in Google
243 format.
244
245 Returns:
Jon Wayne Parrott20e6e582016-12-19 10:21:23 -0800246 google.auth.crypt.Signer: The constructed signer.
Jon Wayne Parrott54451fb2016-12-14 12:20:24 -0800247
248 Raises:
249 ValueError: If the info is not in the expected format.
250 """
251 if _JSON_FILE_PRIVATE_KEY not in info:
252 raise ValueError(
253 'The private_key field was not found in the service account '
254 'info.')
255
256 return cls.from_string(
257 info[_JSON_FILE_PRIVATE_KEY],
258 info.get(_JSON_FILE_PRIVATE_KEY_ID))
259
260 @classmethod
Jon Wayne Parrottfc169292016-12-13 16:02:40 -0800261 def from_service_account_file(cls, filename):
262 """Creates a Signer instance from a service account .json file
263 in Google format.
264
265 Args:
266 filename (str): The path to the service account .json file.
267
268 Returns:
Jon Wayne Parrott20e6e582016-12-19 10:21:23 -0800269 google.auth.crypt.Signer: The constructed signer.
Jon Wayne Parrottfc169292016-12-13 16:02:40 -0800270 """
Jon Wayne Parrott54451fb2016-12-14 12:20:24 -0800271 with io.open(filename, 'r', encoding='utf-8') as json_file:
272 data = json.load(json_file)
273
274 return cls.from_service_account_info(data)