blob: 8d5ac7c747ea293543afb284abc519faf17702d3 [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"""
41
42from pyasn1.codec.der import decoder
43from pyasn1_modules import pem
44from pyasn1_modules.rfc2459 import Certificate
45from pyasn1_modules.rfc5208 import PrivateKeyInfo
46import rsa
47import six
48
49from google.auth import _helpers
50
51_POW2 = (128, 64, 32, 16, 8, 4, 2, 1)
52_CERTIFICATE_MARKER = b'-----BEGIN CERTIFICATE-----'
53_PKCS1_MARKER = ('-----BEGIN RSA PRIVATE KEY-----',
54 '-----END RSA PRIVATE KEY-----')
55_PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----',
56 '-----END PRIVATE KEY-----')
57_PKCS8_SPEC = PrivateKeyInfo()
58
59
60def _bit_list_to_bytes(bit_list):
61 """Converts an iterable of 1s and 0s to bytes.
62
63 Combines the list 8 at a time, treating each group of 8 bits
64 as a single byte.
65
66 Args:
67 bit_list (Sequence): Sequence of 1s and 0s.
68
69 Returns:
70 bytes: The decoded bytes.
71 """
72 num_bits = len(bit_list)
73 byte_vals = bytearray()
74 for start in six.moves.xrange(0, num_bits, 8):
75 curr_bits = bit_list[start:start + 8]
76 char_val = sum(val * digit
77 for val, digit in six.moves.zip(_POW2, curr_bits))
78 byte_vals.append(char_val)
79 return bytes(byte_vals)
80
81
82class Verifier(object):
83 """This object is used to verify cryptographic signatures.
84
85 Args:
86 public_key (rsa.key.PublicKey): The public key used to verify
87 signatures.
88 """
89
90 def __init__(self, public_key):
91 self._pubkey = public_key
92
93 def verify(self, message, signature):
94 """Verifies a message against a cryptographic signature.
95
96 Args:
97 message (Union[str, bytes]): The message to verify.
98 signature (Union[str, bytes]): The cryptography signature to check.
99
100 Returns:
101 bool: True if message was signed by the private key associated
102 with the public key that this object was constructed with.
103 """
104 message = _helpers.to_bytes(message)
105 try:
106 return rsa.pkcs1.verify(message, signature, self._pubkey)
107 except (ValueError, rsa.pkcs1.VerificationError):
108 return False
109
110 @classmethod
111 def from_string(cls, public_key):
112 """Construct an Verifier instance from a public key or public
113 certificate string.
114
115 Args:
116 public_key (Union[str, bytes]): The public key in PEM format or the
117 x509 public key certificate.
118
119 Returns:
120 Verifier: The constructed verifier.
121
122 Raises:
123 ValueError: If the public_key can't be parsed.
124 """
125 public_key = _helpers.to_bytes(public_key)
126 is_x509_cert = _CERTIFICATE_MARKER in public_key
127
128 # If this is a certificate, extract the public key info.
129 if is_x509_cert:
130 der = rsa.pem.load_pem(public_key, 'CERTIFICATE')
131 asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate())
132 if remaining != b'':
133 raise ValueError('Unused bytes', remaining)
134
135 cert_info = asn1_cert['tbsCertificate']['subjectPublicKeyInfo']
136 key_bytes = _bit_list_to_bytes(cert_info['subjectPublicKey'])
137 pubkey = rsa.PublicKey.load_pkcs1(key_bytes, 'DER')
138 else:
139 pubkey = rsa.PublicKey.load_pkcs1(public_key, 'PEM')
140 return cls(pubkey)
141
142
143def verify_signature(message, signature, certs):
144 """Verify a cryptographic signature.
145
146 Checks that the provided ``signature`` was generated from ``bytes`` using
147 the private key associated with the ``cert``.
148
149 Args:
150 message (Union[str, bytes]): The plaintext message.
151 signature (Union[str, bytes]): The cryptographic signature to check.
152 certs (Union[Sequence, str, bytes]): The certificate or certificates
153 to use to check the signature.
154
155 Returns:
156 bool: True if the signature is valid, otherwise False.
157 """
158 if isinstance(certs, (six.text_type, six.binary_type)):
159 certs = [certs]
160
161 for cert in certs:
162 verifier = Verifier.from_string(cert)
163 if verifier.verify(message, signature):
164 return True
165 return False
166
167
168class Signer(object):
169 """Signs messages with a private key.
170
171 Args:
172 private_key (rsa.key.PrivateKey): The private key to sign with.
173 key_id (str): Optional key ID used to identify this private key. This
174 can be useful to associate the private key with its associated
175 public key or certificate.
176 """
177
178 def __init__(self, private_key, key_id=None):
179 self._key = private_key
180 self.key_id = key_id
181
182 def sign(self, message):
183 """Signs a message.
184
185 Args:
186 message (Union[str, bytes]): The message to be signed.
187
188 Returns:
189 bytes: The signature of the message for the given key.
190 """
191 message = _helpers.to_bytes(message)
192 return rsa.pkcs1.sign(message, self._key, 'SHA-256')
193
194 @classmethod
195 def from_string(cls, key, key_id=None):
196 """Construct an Signer instance from a private key in PEM format.
197
198 Args:
199 key (str): Private key in PEM format.
200 key_id (str): An optional key id used to identify the private key.
201
202 Returns:
203 Signer: The constructed signer.
204
205 Raises:
206 ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in
207 PEM format.
208 """
209 key = _helpers.from_bytes(key) # PEM expects str in Python 3
210 marker_id, key_bytes = pem.readPemBlocksFromFile(
211 six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER)
212
213 # Key is in pkcs1 format.
214 if marker_id == 0:
215 private_key = rsa.key.PrivateKey.load_pkcs1(
216 key_bytes, format='DER')
217 # Key is in pkcs8.
218 elif marker_id == 1:
219 key_info, remaining = decoder.decode(
220 key_bytes, asn1Spec=_PKCS8_SPEC)
221 if remaining != b'':
222 raise ValueError('Unused bytes', remaining)
223 private_key_info = key_info.getComponentByName('privateKey')
224 private_key = rsa.key.PrivateKey.load_pkcs1(
225 private_key_info.asOctets(), format='DER')
226 else:
227 raise ValueError('No key could be detected.')
228
229 return cls(private_key, key_id=key_id)
Jon Wayne Parrottfc169292016-12-13 16:02:40 -0800230
231 @classmethod
232 def from_service_account_file(cls, filename):
233 """Creates a Signer instance from a service account .json file
234 in Google format.
235
236 Args:
237 filename (str): The path to the service account .json file.
238
239 Returns:
240 Signer: The constructed signer.
241 """
242 from google.auth import _service_account_info
243 _, signer = _service_account_info.from_filename(filename)
244 return signer