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

88 statements  

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 verifier and signer that use the ``cryptography`` library.""" 

16 

17from dataclasses import dataclass 

18from typing import Any, Dict, Optional, Union 

19 

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) 

29 

30from google.auth import _helpers 

31from google.auth.crypt import base 

32 

33_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" 

34_BACKEND = backends.default_backend() 

35_PADDING = padding.PKCS1v15() 

36 

37 

38@dataclass 

39class _ESAttributes: 

40 """A class that models ECDSA attributes. 

41 

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 """ 

47 

48 rs_size: int 

49 sha_algo: hashes.HashAlgorithm 

50 algorithm: str 

51 

52 @classmethod 

53 def from_key( 

54 cls, key: Union[ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey] 

55 ): 

56 return cls.from_curve(key.curve) 

57 

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") 

69 

70 

71class EsVerifier(base.Verifier): 

72 """Verifies ECDSA cryptographic signatures using public keys. 

73 

74 Args: 

75 public_key ( 

76 cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey): 

77 The public key used to verify signatures. 

78 """ 

79 

80 def __init__(self, public_key: ec.EllipticCurvePublicKey) -> None: 

81 self._pubkey = public_key 

82 self._attributes = _ESAttributes.from_key(public_key) 

83 

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) 

93 

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 

100 

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. 

105 

106 Args: 

107 public_key (Union[str, bytes]): The public key in PEM format or the 

108 x509 public key certificate. 

109 

110 Returns: 

111 google.auth.crypt.Verifier: The constructed verifier. 

112 

113 Raises: 

114 ValueError: If the public key can't be parsed. 

115 """ 

116 public_key_data = _helpers.to_bytes(public_key) 

117 

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 

123 

124 else: 

125 pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND) 

126 

127 if not isinstance(pubkey, ec.EllipticCurvePublicKey): 

128 raise TypeError("Expected public key of type EllipticCurvePublicKey") 

129 

130 return cls(pubkey) 

131 

132 

133class EsSigner(base.Signer, base.FromServiceAccountMixin): 

134 """Signs messages with an ECDSA private key. 

135 

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 """ 

144 

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) 

151 

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 

159 

160 @property # type: ignore 

161 @_helpers.copy_docstring(base.Signer) 

162 def key_id(self) -> Optional[str]: 

163 return self._key_id 

164 

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)) 

169 

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 ) 

175 

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. 

181 

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. 

185 

186 Returns: 

187 google.auth.crypt._cryptography_rsa.RSASigner: The 

188 constructed signer. 

189 

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 ) 

200 

201 if not isinstance(private_key, ec.EllipticCurvePrivateKey): 

202 raise TypeError("Expected private key of type EllipticCurvePrivateKey") 

203 

204 return cls(private_key, key_id=key_id) 

205 

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 

215 

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)