blob: 1305cc8436be814e67cf6680e8c1315389ea53e1 [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
185
186 def sign(self, message):
187 """Signs a message.
188
189 Args:
190 message (Union[str, bytes]): The message to be signed.
191
192 Returns:
Jon Wayne Parrott256d2bf2016-12-13 16:12:02 -0800193 bytes: The signature of the message.
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700194 """
195 message = _helpers.to_bytes(message)
196 return rsa.pkcs1.sign(message, self._key, 'SHA-256')
197
198 @classmethod
199 def from_string(cls, key, key_id=None):
200 """Construct an Signer instance from a private key in PEM format.
201
202 Args:
203 key (str): Private key in PEM format.
204 key_id (str): An optional key id used to identify the private key.
205
206 Returns:
Jon Wayne Parrott20e6e582016-12-19 10:21:23 -0800207 google.auth.crypt.Signer: The constructed signer.
Jon Wayne Parrott8713a712016-10-04 14:19:01 -0700208
209 Raises:
210 ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in
211 PEM format.
212 """
213 key = _helpers.from_bytes(key) # PEM expects str in Python 3
214 marker_id, key_bytes = pem.readPemBlocksFromFile(
215 six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER)
216
217 # Key is in pkcs1 format.
218 if marker_id == 0:
219 private_key = rsa.key.PrivateKey.load_pkcs1(
220 key_bytes, format='DER')
221 # Key is in pkcs8.
222 elif marker_id == 1:
223 key_info, remaining = decoder.decode(
224 key_bytes, asn1Spec=_PKCS8_SPEC)
225 if remaining != b'':
226 raise ValueError('Unused bytes', remaining)
227 private_key_info = key_info.getComponentByName('privateKey')
228 private_key = rsa.key.PrivateKey.load_pkcs1(
229 private_key_info.asOctets(), format='DER')
230 else:
231 raise ValueError('No key could be detected.')
232
233 return cls(private_key, key_id=key_id)
Jon Wayne Parrottfc169292016-12-13 16:02:40 -0800234
235 @classmethod
Jon Wayne Parrott54451fb2016-12-14 12:20:24 -0800236 def from_service_account_info(cls, info):
237 """Creates a Signer instance instance from a dictionary containing
238 service account info in Google format.
239
240 Args:
241 info (Mapping[str, str]): The service account info in Google
242 format.
243
244 Returns:
Jon Wayne Parrott20e6e582016-12-19 10:21:23 -0800245 google.auth.crypt.Signer: The constructed signer.
Jon Wayne Parrott54451fb2016-12-14 12:20:24 -0800246
247 Raises:
248 ValueError: If the info is not in the expected format.
249 """
250 if _JSON_FILE_PRIVATE_KEY not in info:
251 raise ValueError(
252 'The private_key field was not found in the service account '
253 'info.')
254
255 return cls.from_string(
256 info[_JSON_FILE_PRIVATE_KEY],
257 info.get(_JSON_FILE_PRIVATE_KEY_ID))
258
259 @classmethod
Jon Wayne Parrottfc169292016-12-13 16:02:40 -0800260 def from_service_account_file(cls, filename):
261 """Creates a Signer instance from a service account .json file
262 in Google format.
263
264 Args:
265 filename (str): The path to the service account .json file.
266
267 Returns:
Jon Wayne Parrott20e6e582016-12-19 10:21:23 -0800268 google.auth.crypt.Signer: The constructed signer.
Jon Wayne Parrottfc169292016-12-13 16:02:40 -0800269 """
Jon Wayne Parrott54451fb2016-12-14 12:20:24 -0800270 with io.open(filename, 'r', encoding='utf-8') as json_file:
271 data = json.load(json_file)
272
273 return cls.from_service_account_info(data)