blob: 6955efcc588fa04690b5fedeb55f3f083f2dc1ca [file] [log] [blame]
Thea Flowerse290a3d2020-04-01 10:11:42 -07001# 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"""ECDSA (ES256) verifier and signer that use the ``cryptography`` library.
16"""
17
arithmetic1728cf2c0a92020-04-21 14:33:20 -070018from cryptography import utils
Thea Flowerse290a3d2020-04-01 10:11:42 -070019import cryptography.exceptions
20from cryptography.hazmat import backends
21from cryptography.hazmat.primitives import hashes
22from cryptography.hazmat.primitives import serialization
23from cryptography.hazmat.primitives.asymmetric import ec
24from cryptography.hazmat.primitives.asymmetric import padding
arithmetic1728cf2c0a92020-04-21 14:33:20 -070025from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
26from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
Thea Flowerse290a3d2020-04-01 10:11:42 -070027import cryptography.x509
28import pkg_resources
29
30from google.auth import _helpers
31from google.auth.crypt import base
32
33_IMPORT_ERROR_MSG = (
34 "cryptography>=1.4.0 is required to use cryptography-based ECDSA " "algorithms"
35)
36
37try: # pragma: NO COVER
38 release = pkg_resources.get_distribution("cryptography").parsed_version
39 if release < pkg_resources.parse_version("1.4.0"):
40 raise ImportError(_IMPORT_ERROR_MSG)
41except pkg_resources.DistributionNotFound: # pragma: NO COVER
42 raise ImportError(_IMPORT_ERROR_MSG)
43
44
45_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----"
46_BACKEND = backends.default_backend()
47_PADDING = padding.PKCS1v15()
48
49
50class ES256Verifier(base.Verifier):
51 """Verifies ECDSA cryptographic signatures using public keys.
52
53 Args:
54 public_key (
55 cryptography.hazmat.primitives.asymmetric.ec.ECDSAPublicKey):
56 The public key used to verify signatures.
57 """
58
59 def __init__(self, public_key):
60 self._pubkey = public_key
61
62 @_helpers.copy_docstring(base.Verifier)
63 def verify(self, message, signature):
arithmetic1728cf2c0a92020-04-21 14:33:20 -070064 # First convert (r||s) raw signature to ASN1 encoded signature.
65 sig_bytes = _helpers.to_bytes(signature)
66 if len(sig_bytes) != 64:
67 return False
68 r = utils.int_from_bytes(sig_bytes[:32], byteorder="big")
69 s = utils.int_from_bytes(sig_bytes[32:], byteorder="big")
70 asn1_sig = encode_dss_signature(r, s)
71
Thea Flowerse290a3d2020-04-01 10:11:42 -070072 message = _helpers.to_bytes(message)
73 try:
arithmetic1728cf2c0a92020-04-21 14:33:20 -070074 self._pubkey.verify(asn1_sig, message, ec.ECDSA(hashes.SHA256()))
Thea Flowerse290a3d2020-04-01 10:11:42 -070075 return True
76 except (ValueError, cryptography.exceptions.InvalidSignature):
77 return False
78
79 @classmethod
80 def from_string(cls, public_key):
81 """Construct an Verifier instance from a public key or public
82 certificate string.
83
84 Args:
85 public_key (Union[str, bytes]): The public key in PEM format or the
86 x509 public key certificate.
87
88 Returns:
89 Verifier: The constructed verifier.
90
91 Raises:
92 ValueError: If the public key can't be parsed.
93 """
94 public_key_data = _helpers.to_bytes(public_key)
95
96 if _CERTIFICATE_MARKER in public_key_data:
97 cert = cryptography.x509.load_pem_x509_certificate(
98 public_key_data, _BACKEND
99 )
100 pubkey = cert.public_key()
101
102 else:
103 pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND)
104
105 return cls(pubkey)
106
107
108class ES256Signer(base.Signer, base.FromServiceAccountMixin):
109 """Signs messages with an ECDSA private key.
110
111 Args:
112 private_key (
113 cryptography.hazmat.primitives.asymmetric.ec.ECDSAPrivateKey):
114 The private key to sign with.
115 key_id (str): Optional key ID used to identify this private key. This
116 can be useful to associate the private key with its associated
117 public key or certificate.
118 """
119
120 def __init__(self, private_key, key_id=None):
121 self._key = private_key
122 self._key_id = key_id
123
124 @property
125 @_helpers.copy_docstring(base.Signer)
126 def key_id(self):
127 return self._key_id
128
129 @_helpers.copy_docstring(base.Signer)
130 def sign(self, message):
131 message = _helpers.to_bytes(message)
arithmetic1728cf2c0a92020-04-21 14:33:20 -0700132 asn1_signature = self._key.sign(message, ec.ECDSA(hashes.SHA256()))
133
134 # Convert ASN1 encoded signature to (r||s) raw signature.
135 (r, s) = decode_dss_signature(asn1_signature)
136 return utils.int_to_bytes(r, 32) + utils.int_to_bytes(s, 32)
Thea Flowerse290a3d2020-04-01 10:11:42 -0700137
138 @classmethod
139 def from_string(cls, key, key_id=None):
140 """Construct a RSASigner from a private key in PEM format.
141
142 Args:
143 key (Union[bytes, str]): Private key in PEM format.
144 key_id (str): An optional key id used to identify the private key.
145
146 Returns:
147 google.auth.crypt._cryptography_rsa.RSASigner: The
148 constructed signer.
149
150 Raises:
151 ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode).
152 UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded
153 into a UTF-8 ``str``.
154 ValueError: If ``cryptography`` "Could not deserialize key data."
155 """
156 key = _helpers.to_bytes(key)
157 private_key = serialization.load_pem_private_key(
158 key, password=None, backend=_BACKEND
159 )
160 return cls(private_key, key_id=key_id)