Thea Flowers | e290a3d | 2020-04-01 10:11:42 -0700 | [diff] [blame] | 1 | # 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 | |
arithmetic1728 | cf2c0a9 | 2020-04-21 14:33:20 -0700 | [diff] [blame^] | 18 | from cryptography import utils |
Thea Flowers | e290a3d | 2020-04-01 10:11:42 -0700 | [diff] [blame] | 19 | import cryptography.exceptions |
| 20 | from cryptography.hazmat import backends |
| 21 | from cryptography.hazmat.primitives import hashes |
| 22 | from cryptography.hazmat.primitives import serialization |
| 23 | from cryptography.hazmat.primitives.asymmetric import ec |
| 24 | from cryptography.hazmat.primitives.asymmetric import padding |
arithmetic1728 | cf2c0a9 | 2020-04-21 14:33:20 -0700 | [diff] [blame^] | 25 | from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature |
| 26 | from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature |
Thea Flowers | e290a3d | 2020-04-01 10:11:42 -0700 | [diff] [blame] | 27 | import cryptography.x509 |
| 28 | import pkg_resources |
| 29 | |
| 30 | from google.auth import _helpers |
| 31 | from 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 | |
| 37 | try: # 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) |
| 41 | except 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 | |
| 50 | class 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): |
arithmetic1728 | cf2c0a9 | 2020-04-21 14:33:20 -0700 | [diff] [blame^] | 64 | # 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 Flowers | e290a3d | 2020-04-01 10:11:42 -0700 | [diff] [blame] | 72 | message = _helpers.to_bytes(message) |
| 73 | try: |
arithmetic1728 | cf2c0a9 | 2020-04-21 14:33:20 -0700 | [diff] [blame^] | 74 | self._pubkey.verify(asn1_sig, message, ec.ECDSA(hashes.SHA256())) |
Thea Flowers | e290a3d | 2020-04-01 10:11:42 -0700 | [diff] [blame] | 75 | 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 | |
| 108 | class 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) |
arithmetic1728 | cf2c0a9 | 2020-04-21 14:33:20 -0700 | [diff] [blame^] | 132 | 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 Flowers | e290a3d | 2020-04-01 10:11:42 -0700 | [diff] [blame] | 137 | |
| 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) |