|
| 1 | +""" |
| 2 | +Class for performing Elliptic-curve Diffie-Hellman (ECDH) operations. |
| 3 | +""" |
| 4 | + |
| 5 | +from .util import number_to_string |
| 6 | +from .ellipticcurve import INFINITY |
| 7 | +from .keys import SigningKey, VerifyingKey |
| 8 | + |
| 9 | + |
| 10 | +__all__ = ["ECDH", "NoKeyError", "NoCurveError", "InvalidCurveError", |
| 11 | + "InvalidSharedSecretError"] |
| 12 | + |
| 13 | + |
| 14 | +class NoKeyError(Exception): |
| 15 | + """ECDH. Key not found but it is needed for operation.""" |
| 16 | + |
| 17 | + pass |
| 18 | + |
| 19 | + |
| 20 | +class NoCurveError(Exception): |
| 21 | + """ECDH. Curve not set but it is needed for operation.""" |
| 22 | + |
| 23 | + pass |
| 24 | + |
| 25 | + |
| 26 | +class InvalidCurveError(Exception): |
| 27 | + """ECDH. Raised in case the public and private keys use different curves.""" |
| 28 | + |
| 29 | + pass |
| 30 | + |
| 31 | + |
| 32 | +class InvalidSharedSecretError(Exception): |
| 33 | + """ECDH. Raised in case the shared secret we obtained is an INFINITY.""" |
| 34 | + |
| 35 | + pass |
| 36 | + |
| 37 | + |
| 38 | +class ECDH(object): |
| 39 | + """ |
| 40 | + Elliptic-curve Diffie-Hellman (ECDH). A key agreement protocol. |
| 41 | + |
| 42 | + Allows two parties, each having an elliptic-curve public-private key |
| 43 | + pair, to establish a shared secret over an insecure channel |
| 44 | + """"" |
| 45 | + |
| 46 | + def __init__(self, curve=None, private_key=None, public_key=None): |
| 47 | + """ |
| 48 | + ECDH init. |
| 49 | +
|
| 50 | + Call can be initialised without parameters, then the first operation |
| 51 | + (loading either key) will set the used curve. |
| 52 | + All parameters must be ultimately set before shared secret |
| 53 | + calculation will be allowed. |
| 54 | +
|
| 55 | + :param curve: curve for operations |
| 56 | + :type curve: Curve |
| 57 | + :param private_key: `my` private key for ECDH |
| 58 | + :type private_key: SigningKey |
| 59 | + :param public_key: `their` public key for ECDH |
| 60 | + :type public_key: VerifyingKey |
| 61 | + """ |
| 62 | + self.curve = curve |
| 63 | + self.private_key = None |
| 64 | + self.public_key = None |
| 65 | + if private_key: |
| 66 | + self.load_private_key(private_key) |
| 67 | + if public_key: |
| 68 | + self.load_received_public_key(public_key) |
| 69 | + |
| 70 | + def _get_shared_secret(self, remote_public_key): |
| 71 | + if not self.private_key: |
| 72 | + raise NoKeyError( |
| 73 | + "Private key needs to be set to create shared secret") |
| 74 | + if not self.public_key: |
| 75 | + raise NoKeyError( |
| 76 | + "Public key needs to be set to create shared secret") |
| 77 | + if not (self.private_key.curve == self.curve == remote_public_key.curve): |
| 78 | + raise InvalidCurveError( |
| 79 | + "Curves for public key and private key is not equal.") |
| 80 | + |
| 81 | + # shared secret = PUBKEYtheirs * PRIVATEKEYours |
| 82 | + result = remote_public_key.pubkey.point * self.private_key.privkey.secret_multiplier |
| 83 | + if result == INFINITY: |
| 84 | + raise InvalidSharedSecretError( |
| 85 | + "Invalid shared secret (INFINITY).") |
| 86 | + |
| 87 | + return result.x() |
| 88 | + |
| 89 | + def set_curve(self, key_curve): |
| 90 | + """ |
| 91 | + Set the working curve for ecdh operations. |
| 92 | +
|
| 93 | + :param key_curve: curve from `curves` module |
| 94 | + :type key_curve: Curve |
| 95 | + """ |
| 96 | + self.curve = key_curve |
| 97 | + |
| 98 | + def generate_private_key(self): |
| 99 | + """ |
| 100 | + Generate local private key for ecdh operation with curve that was set. |
| 101 | +
|
| 102 | + :raises NoCurveError: Curve must be set before key generation. |
| 103 | +
|
| 104 | + :return: public (verifying) key from this private key. |
| 105 | + :rtype: VerifyingKey object |
| 106 | + """ |
| 107 | + if not self.curve: |
| 108 | + raise NoCurveError("Curve must be set prior to key generation.") |
| 109 | + return self.load_private_key(SigningKey.generate(curve=self.curve)) |
| 110 | + |
| 111 | + def load_private_key(self, private_key): |
| 112 | + """ |
| 113 | + Load private key from SigningKey (keys.py) object. |
| 114 | +
|
| 115 | + Needs to have the same curve as was set with set_curve method. |
| 116 | + If curve is not set - it sets from this SigningKey |
| 117 | +
|
| 118 | + :param private_key: Initialised SigningKey class |
| 119 | + :type private_key: SigningKey |
| 120 | +
|
| 121 | + :raises InvalidCurveError: private_key curve not the same as self.curve |
| 122 | +
|
| 123 | + :return: public (verifying) key from this private key. |
| 124 | + :rtype: VerifyingKey object |
| 125 | + """ |
| 126 | + if not self.curve: |
| 127 | + self.curve = private_key.curve |
| 128 | + if self.curve != private_key.curve: |
| 129 | + raise InvalidCurveError("Curve mismatch.") |
| 130 | + self.private_key = private_key |
| 131 | + return self.private_key.get_verifying_key() |
| 132 | + |
| 133 | + def load_private_key_bytes(self, private_key): |
| 134 | + """ |
| 135 | + Load private key from byte string. |
| 136 | +
|
| 137 | + Uses current curve and checks if the provided key matches |
| 138 | + the curve of ECDH key agreement. |
| 139 | + Key loads via from_string method of SigningKey class |
| 140 | +
|
| 141 | + :param private_key: private key in bytes string format |
| 142 | + :type private_key: :term:`bytes-like object` |
| 143 | +
|
| 144 | + :raises NoCurveError: Curve must be set before loading. |
| 145 | +
|
| 146 | + :return: public (verifying) key from this private key. |
| 147 | + :rtype: VerifyingKey object |
| 148 | + """ |
| 149 | + if not self.curve: |
| 150 | + raise NoCurveError("Curve must be set prior to key load.") |
| 151 | + return self.load_private_key( |
| 152 | + SigningKey.from_string(private_key, curve=self.curve)) |
| 153 | + |
| 154 | + def load_private_key_der(self, private_key_der): |
| 155 | + """ |
| 156 | + Load private key from DER byte string. |
| 157 | +
|
| 158 | + Compares the curve of the DER-encoded key with the ECDH set curve, |
| 159 | + uses the former if unset. |
| 160 | +
|
| 161 | + Note, the only DER format supported is the RFC5915 |
| 162 | + Look at keys.py:SigningKey.from_der() |
| 163 | +
|
| 164 | + :param private_key_der: string with the DER encoding of private ECDSA key |
| 165 | + :type private_key_der: string |
| 166 | +
|
| 167 | + :raises InvalidCurveError: private_key curve not the same as self.curve |
| 168 | +
|
| 169 | + :return: public (verifying) key from this private key. |
| 170 | + :rtype: VerifyingKey object |
| 171 | + """ |
| 172 | + return self.load_private_key(SigningKey.from_der(private_key_der)) |
| 173 | + |
| 174 | + def load_private_key_pem(self, private_key_pem): |
| 175 | + """ |
| 176 | + Load private key from PEM string. |
| 177 | +
|
| 178 | + Compares the curve of the DER-encoded key with the ECDH set curve, |
| 179 | + uses the former if unset. |
| 180 | +
|
| 181 | + Note, the only PEM format supported is the RFC5915 |
| 182 | + Look at keys.py:SigningKey.from_pem() |
| 183 | + it needs to have `EC PRIVATE KEY` section |
| 184 | +
|
| 185 | + :param private_key_pem: string with PEM-encoded private ECDSA key |
| 186 | + :type private_key_pem: string |
| 187 | +
|
| 188 | + :raises InvalidCurveError: private_key curve not the same as self.curve |
| 189 | +
|
| 190 | + :return: public (verifying) key from this private key. |
| 191 | + :rtype: VerifyingKey object |
| 192 | + """ |
| 193 | + return self.load_private_key(SigningKey.from_pem(private_key_pem)) |
| 194 | + |
| 195 | + def get_public_key(self): |
| 196 | + """ |
| 197 | + Provides a public key that matches the local private key. |
| 198 | +
|
| 199 | + Needs to be sent to the remote party. |
| 200 | +
|
| 201 | + :return: public (verifying) key from local private key. |
| 202 | + :rtype: VerifyingKey object |
| 203 | + """ |
| 204 | + return self.private_key.get_verifying_key() |
| 205 | + |
| 206 | + def load_received_public_key(self, public_key): |
| 207 | + """ |
| 208 | + Load public key from VerifyingKey (keys.py) object. |
| 209 | +
|
| 210 | + Needs to have the same curve as set as current for ecdh operation. |
| 211 | + If curve is not set - it sets it from VerifyingKey. |
| 212 | +
|
| 213 | + :param public_key: Initialised VerifyingKey class |
| 214 | + :type public_key: VerifyingKey |
| 215 | +
|
| 216 | + :raises InvalidCurveError: public_key curve not the same as self.curve |
| 217 | + """ |
| 218 | + if not self.curve: |
| 219 | + self.curve = public_key.curve |
| 220 | + if self.curve != public_key.curve: |
| 221 | + raise InvalidCurveError("Curve mismatch.") |
| 222 | + self.public_key = public_key |
| 223 | + |
| 224 | + def load_received_public_key_bytes(self, public_key_str): |
| 225 | + """ |
| 226 | + Load public key from byte string. |
| 227 | +
|
| 228 | + Uses current curve and checks if key length corresponds to |
| 229 | + the current curve. |
| 230 | + Key loads via from_string method of VerifyingKey class |
| 231 | +
|
| 232 | + :param public_key_str: public key in bytes string format |
| 233 | + :type public_key_str: :term:`bytes-like object` |
| 234 | + """ |
| 235 | + return self.load_received_public_key( |
| 236 | + VerifyingKey.from_string(public_key_str, self.curve)) |
| 237 | + |
| 238 | + def load_received_public_key_der(self, public_key_der): |
| 239 | + """ |
| 240 | + Load public key from DER byte string. |
| 241 | +
|
| 242 | + Compares the curve of the DER-encoded key with the ECDH set curve, |
| 243 | + uses the former if unset. |
| 244 | +
|
| 245 | + Note, the only DER format supported is the RFC5912 |
| 246 | + Look at keys.py:VerifyingKey.from_der() |
| 247 | +
|
| 248 | + :param public_key_der: string with the DER encoding of public ECDSA key |
| 249 | + :type public_key_der: string |
| 250 | +
|
| 251 | + :raises InvalidCurveError: public_key curve not the same as self.curve |
| 252 | + """ |
| 253 | + return self.load_received_public_key(VerifyingKey.from_der(public_key_der)) |
| 254 | + |
| 255 | + def load_received_public_key_pem(self, public_key_pem): |
| 256 | + """ |
| 257 | + Load public key from PEM string. |
| 258 | +
|
| 259 | + Compares the curve of the PEM-encoded key with the ECDH set curve, |
| 260 | + uses the former if unset. |
| 261 | +
|
| 262 | + Note, the only PEM format supported is the RFC5912 |
| 263 | + Look at keys.py:VerifyingKey.from_pem() |
| 264 | +
|
| 265 | + :param public_key_pem: string with PEM-encoded public ECDSA key |
| 266 | + :type public_key_pem: string |
| 267 | +
|
| 268 | + :raises InvalidCurveError: public_key curve not the same as self.curve |
| 269 | + """ |
| 270 | + return self.load_received_public_key(VerifyingKey.from_pem(public_key_pem)) |
| 271 | + |
| 272 | + def generate_sharedsecret_bytes(self): |
| 273 | + """ |
| 274 | + Generate shared secret from local private key and remote public key. |
| 275 | +
|
| 276 | + The objects needs to have both private key and received public key |
| 277 | + before generation is allowed. |
| 278 | +
|
| 279 | + :raises InvalidCurveError: public_key curve not the same as self.curve |
| 280 | + :raises NoKeyError: public_key or private_key is not set |
| 281 | +
|
| 282 | + :return: shared secret |
| 283 | + :rtype: byte string |
| 284 | + """ |
| 285 | + return number_to_string( |
| 286 | + self.generate_sharedsecret(), |
| 287 | + self.private_key.curve.order) |
| 288 | + |
| 289 | + def generate_sharedsecret(self): |
| 290 | + """ |
| 291 | + Generate shared secret from local private key and remote public key. |
| 292 | +
|
| 293 | + The objects needs to have both private key and received public key |
| 294 | + before generation is allowed. |
| 295 | +
|
| 296 | + It's the same for local and remote party. |
| 297 | + shared secret(local private key, remote public key ) == |
| 298 | + shared secret (local public key, remote private key) |
| 299 | +
|
| 300 | + :raises InvalidCurveError: public_key curve not the same as self.curve |
| 301 | + :raises NoKeyError: public_key or private_key is not set |
| 302 | +
|
| 303 | + :return: shared secret |
| 304 | + :rtype: int |
| 305 | + """ |
| 306 | + return self._get_shared_secret(self.public_key) |
0 commit comments