Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/auth/crypt/es.py: 48%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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.
15"""ECDSA verifier and signer that use the ``cryptography`` library."""
17from dataclasses import dataclass
18from typing import Any, Dict, Optional, Union
20import cryptography.exceptions
21import cryptography.x509
22from cryptography.hazmat import backends
23from cryptography.hazmat.primitives import hashes, serialization
24from cryptography.hazmat.primitives.asymmetric import ec, padding
25from cryptography.hazmat.primitives.asymmetric.utils import (
26 decode_dss_signature,
27 encode_dss_signature,
28)
30from google.auth import _helpers
31from google.auth.crypt import base
33_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----"
34_BACKEND = backends.default_backend()
35_PADDING = padding.PKCS1v15()
38@dataclass
39class _ESAttributes:
40 """A class that models ECDSA attributes.
42 Attributes:
43 rs_size (int): Size for ASN.1 r and s size.
44 sha_algo (hashes.HashAlgorithm): Hash algorithm.
45 algorithm (str): Algorithm name.
46 """
48 rs_size: int
49 sha_algo: hashes.HashAlgorithm
50 algorithm: str
52 @classmethod
53 def from_key(
54 cls, key: Union[ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey]
55 ):
56 return cls.from_curve(key.curve)
58 @classmethod
59 def from_curve(cls, curve: ec.EllipticCurve):
60 # ECDSA raw signature has (r||s) format where r,s are two
61 # integers of size 32 bytes for P-256 curve and 48 bytes
62 # for P-384 curve. For P-256 curve, we use SHA256 hash algo,
63 # and for P-384 curve we use SHA384 algo.
64 if isinstance(curve, ec.SECP384R1):
65 return cls(48, hashes.SHA384(), "ES384")
66 else:
67 # default to ES256
68 return cls(32, hashes.SHA256(), "ES256")
71class EsVerifier(base.Verifier):
72 """Verifies ECDSA cryptographic signatures using public keys.
74 Args:
75 public_key (
76 cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
77 The public key used to verify signatures.
78 """
80 def __init__(self, public_key: ec.EllipticCurvePublicKey) -> None:
81 self._pubkey = public_key
82 self._attributes = _ESAttributes.from_key(public_key)
84 @_helpers.copy_docstring(base.Verifier)
85 def verify(self, message: bytes, signature: bytes) -> bool:
86 # First convert (r||s) raw signature to ASN1 encoded signature.
87 sig_bytes = _helpers.to_bytes(signature)
88 if len(sig_bytes) != self._attributes.rs_size * 2:
89 return False
90 r = int.from_bytes(sig_bytes[: self._attributes.rs_size], byteorder="big")
91 s = int.from_bytes(sig_bytes[self._attributes.rs_size :], byteorder="big")
92 asn1_sig = encode_dss_signature(r, s)
94 message = _helpers.to_bytes(message)
95 try:
96 self._pubkey.verify(asn1_sig, message, ec.ECDSA(self._attributes.sha_algo))
97 return True
98 except (ValueError, cryptography.exceptions.InvalidSignature):
99 return False
101 @classmethod
102 def from_string(cls, public_key: Union[str, bytes]) -> "EsVerifier":
103 """Construct a Verifier instance from a public key or public
104 certificate string.
106 Args:
107 public_key (Union[str, bytes]): The public key in PEM format or the
108 x509 public key certificate.
110 Returns:
111 google.auth.crypt.Verifier: The constructed verifier.
113 Raises:
114 ValueError: If the public key can't be parsed.
115 """
116 public_key_data = _helpers.to_bytes(public_key)
118 if _CERTIFICATE_MARKER in public_key_data:
119 cert = cryptography.x509.load_pem_x509_certificate(
120 public_key_data, _BACKEND
121 )
122 pubkey = cert.public_key() # type: Any
124 else:
125 pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND)
127 if not isinstance(pubkey, ec.EllipticCurvePublicKey):
128 raise TypeError("Expected public key of type EllipticCurvePublicKey")
130 return cls(pubkey)
133class EsSigner(base.Signer, base.FromServiceAccountMixin):
134 """Signs messages with an ECDSA private key.
136 Args:
137 private_key (
138 cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
139 The private key to sign with.
140 key_id (str): Optional key ID used to identify this private key. This
141 can be useful to associate the private key with its associated
142 public key or certificate.
143 """
145 def __init__(
146 self, private_key: ec.EllipticCurvePrivateKey, key_id: Optional[str] = None
147 ) -> None:
148 self._key = private_key
149 self._key_id = key_id
150 self._attributes = _ESAttributes.from_key(private_key)
152 @property
153 def algorithm(self) -> str:
154 """Name of the algorithm used to sign messages.
155 Returns:
156 str: The algorithm name.
157 """
158 return self._attributes.algorithm
160 @property # type: ignore
161 @_helpers.copy_docstring(base.Signer)
162 def key_id(self) -> Optional[str]:
163 return self._key_id
165 @_helpers.copy_docstring(base.Signer)
166 def sign(self, message: bytes) -> bytes:
167 message = _helpers.to_bytes(message)
168 asn1_signature = self._key.sign(message, ec.ECDSA(self._attributes.sha_algo))
170 # Convert ASN1 encoded signature to (r||s) raw signature.
171 (r, s) = decode_dss_signature(asn1_signature)
172 return r.to_bytes(self._attributes.rs_size, byteorder="big") + s.to_bytes(
173 self._attributes.rs_size, byteorder="big"
174 )
176 @classmethod
177 def from_string(
178 cls, key: Union[bytes, str], key_id: Optional[str] = None
179 ) -> "EsSigner":
180 """Construct a RSASigner from a private key in PEM format.
182 Args:
183 key (Union[bytes, str]): Private key in PEM format.
184 key_id (str): An optional key id used to identify the private key.
186 Returns:
187 google.auth.crypt._cryptography_rsa.RSASigner: The
188 constructed signer.
190 Raises:
191 ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode).
192 UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded
193 into a UTF-8 ``str``.
194 ValueError: If ``cryptography`` "Could not deserialize key data."
195 """
196 key_bytes = _helpers.to_bytes(key)
197 private_key = serialization.load_pem_private_key(
198 key_bytes, password=None, backend=_BACKEND
199 )
201 if not isinstance(private_key, ec.EllipticCurvePrivateKey):
202 raise TypeError("Expected private key of type EllipticCurvePrivateKey")
204 return cls(private_key, key_id=key_id)
206 def __getstate__(self) -> Dict[str, Any]:
207 """Pickle helper that serializes the _key attribute."""
208 state = self.__dict__.copy()
209 state["_key"] = self._key.private_bytes(
210 encoding=serialization.Encoding.PEM,
211 format=serialization.PrivateFormat.PKCS8,
212 encryption_algorithm=serialization.NoEncryption(),
213 )
214 return state
216 def __setstate__(self, state: Dict[str, Any]) -> None:
217 """Pickle helper that deserializes the _key attribute."""
218 state["_key"] = serialization.load_pem_private_key(state["_key"], None)
219 self.__dict__.update(state)