1# Copyright 2017 Google LLC
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"""RSA verifier and signer that use the ``cryptography`` library.
16
17This 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
22import cryptography.exceptions
23import cryptography.x509
24from cryptography.hazmat import backends
25from cryptography.hazmat.primitives import hashes, serialization
26from cryptography.hazmat.primitives.asymmetric import padding
27
28from google.auth import _helpers
29from google.auth.crypt import base
30
31_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----"
32_BACKEND = backends.default_backend()
33_PADDING = padding.PKCS1v15()
34_SHA256 = hashes.SHA256()
35
36
37class RSAVerifier(base.Verifier):
38 """Verifies RSA cryptographic signatures using public keys.
39
40 Args:
41 public_key (
42 cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
43 The public key used to verify signatures.
44 """
45
46 def __init__(self, public_key):
47 self._pubkey = public_key
48
49 @_helpers.copy_docstring(base.Verifier)
50 def verify(self, message, signature):
51 message = _helpers.to_bytes(message)
52 try:
53 self._pubkey.verify(signature, message, _PADDING, _SHA256)
54 return True
55 except (ValueError, cryptography.exceptions.InvalidSignature):
56 return False
57
58 @classmethod
59 def from_string(cls, public_key):
60 """Construct an Verifier instance from a public key or public
61 certificate string.
62
63 Args:
64 public_key (Union[str, bytes]): The public key in PEM format or the
65 x509 public key certificate.
66
67 Returns:
68 Verifier: The constructed verifier.
69
70 Raises:
71 ValueError: If the public key can't be parsed.
72 """
73 public_key_data = _helpers.to_bytes(public_key)
74
75 if _CERTIFICATE_MARKER in public_key_data:
76 cert = cryptography.x509.load_pem_x509_certificate(
77 public_key_data, _BACKEND
78 )
79 pubkey = cert.public_key()
80
81 else:
82 pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND)
83
84 return cls(pubkey)
85
86
87class RSASigner(base.Signer, base.FromServiceAccountMixin):
88 """Signs messages with an RSA private key.
89
90 Args:
91 private_key (
92 cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
93 The private key to sign with.
94 key_id (str): Optional key ID used to identify this private key. This
95 can be useful to associate the private key with its associated
96 public key or certificate.
97 """
98
99 def __init__(self, private_key, key_id=None):
100 self._key = private_key
101 self._key_id = key_id
102
103 @property # type: ignore
104 @_helpers.copy_docstring(base.Signer)
105 def key_id(self):
106 return self._key_id
107
108 @_helpers.copy_docstring(base.Signer)
109 def sign(self, message):
110 message = _helpers.to_bytes(message)
111 return self._key.sign(message, _PADDING, _SHA256)
112
113 @classmethod
114 def from_string(cls, key, key_id=None):
115 """Construct a RSASigner from a private key in PEM format.
116
117 Args:
118 key (Union[bytes, str]): Private key in PEM format.
119 key_id (str): An optional key id used to identify the private key.
120
121 Returns:
122 google.auth.crypt._cryptography_rsa.RSASigner: The
123 constructed signer.
124
125 Raises:
126 ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode).
127 UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded
128 into a UTF-8 ``str``.
129 ValueError: If ``cryptography`` "Could not deserialize key data."
130 """
131 key = _helpers.to_bytes(key)
132 private_key = serialization.load_pem_private_key(
133 key, password=None, backend=_BACKEND
134 )
135 return cls(private_key, key_id=key_id)
136
137 def __getstate__(self):
138 """Pickle helper that serializes the _key attribute."""
139 state = self.__dict__.copy()
140 state["_key"] = self._key.private_bytes(
141 encoding=serialization.Encoding.PEM,
142 format=serialization.PrivateFormat.PKCS8,
143 encryption_algorithm=serialization.NoEncryption(),
144 )
145 return state
146
147 def __setstate__(self, state):
148 """Pickle helper that deserializes the _key attribute."""
149 state["_key"] = serialization.load_pem_private_key(state["_key"], None)
150 self.__dict__.update(state)