ecdsa_backend.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import hashlib
  2. import ecdsa
  3. from jose.backends.base import Key
  4. from jose.constants import ALGORITHMS
  5. from jose.exceptions import JWKError
  6. from jose.utils import base64_to_long, long_to_base64
  7. class ECDSAECKey(Key):
  8. """
  9. Performs signing and verification operations using
  10. ECDSA and the specified hash function
  11. This class requires the ecdsa package to be installed.
  12. This is based off of the implementation in PyJWT 0.3.2
  13. """
  14. SHA256 = hashlib.sha256
  15. SHA384 = hashlib.sha384
  16. SHA512 = hashlib.sha512
  17. CURVE_MAP = {
  18. SHA256: ecdsa.curves.NIST256p,
  19. SHA384: ecdsa.curves.NIST384p,
  20. SHA512: ecdsa.curves.NIST521p,
  21. }
  22. CURVE_NAMES = (
  23. (ecdsa.curves.NIST256p, "P-256"),
  24. (ecdsa.curves.NIST384p, "P-384"),
  25. (ecdsa.curves.NIST521p, "P-521"),
  26. )
  27. def __init__(self, key, algorithm):
  28. if algorithm not in ALGORITHMS.EC:
  29. raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm)
  30. self.hash_alg = {
  31. ALGORITHMS.ES256: self.SHA256,
  32. ALGORITHMS.ES384: self.SHA384,
  33. ALGORITHMS.ES512: self.SHA512,
  34. }.get(algorithm)
  35. self._algorithm = algorithm
  36. self.curve = self.CURVE_MAP.get(self.hash_alg)
  37. if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)):
  38. self.prepared_key = key
  39. return
  40. if isinstance(key, dict):
  41. self.prepared_key = self._process_jwk(key)
  42. return
  43. if isinstance(key, str):
  44. key = key.encode("utf-8")
  45. if isinstance(key, bytes):
  46. # Attempt to load key. We don't know if it's
  47. # a Signing Key or a Verifying Key, so we try
  48. # the Verifying Key first.
  49. try:
  50. key = ecdsa.VerifyingKey.from_pem(key)
  51. except ecdsa.der.UnexpectedDER:
  52. key = ecdsa.SigningKey.from_pem(key)
  53. except Exception as e:
  54. raise JWKError(e)
  55. self.prepared_key = key
  56. return
  57. raise JWKError("Unable to parse an ECKey from key: %s" % key)
  58. def _process_jwk(self, jwk_dict):
  59. if not jwk_dict.get("kty") == "EC":
  60. raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get("kty"))
  61. if not all(k in jwk_dict for k in ["x", "y", "crv"]):
  62. raise JWKError("Mandatory parameters are missing")
  63. if "d" in jwk_dict:
  64. # We are dealing with a private key; the secret exponent is enough
  65. # to create an ecdsa key.
  66. d = base64_to_long(jwk_dict.get("d"))
  67. return ecdsa.keys.SigningKey.from_secret_exponent(d, self.curve)
  68. else:
  69. x = base64_to_long(jwk_dict.get("x"))
  70. y = base64_to_long(jwk_dict.get("y"))
  71. if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y):
  72. raise JWKError(f"Point: {x}, {y} is not a valid point")
  73. point = ecdsa.ellipticcurve.Point(self.curve.curve, x, y, self.curve.order)
  74. return ecdsa.keys.VerifyingKey.from_public_point(point, self.curve)
  75. def sign(self, msg):
  76. return self.prepared_key.sign(
  77. msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string, allow_truncate=False
  78. )
  79. def verify(self, msg, sig):
  80. try:
  81. return self.prepared_key.verify(
  82. sig, msg, hashfunc=self.hash_alg, sigdecode=ecdsa.util.sigdecode_string, allow_truncate=False
  83. )
  84. except Exception:
  85. return False
  86. def is_public(self):
  87. return isinstance(self.prepared_key, ecdsa.VerifyingKey)
  88. def public_key(self):
  89. if self.is_public():
  90. return self
  91. return self.__class__(self.prepared_key.get_verifying_key(), self._algorithm)
  92. def to_pem(self):
  93. return self.prepared_key.to_pem()
  94. def to_dict(self):
  95. if not self.is_public():
  96. public_key = self.prepared_key.get_verifying_key()
  97. else:
  98. public_key = self.prepared_key
  99. crv = None
  100. for key, value in self.CURVE_NAMES:
  101. if key == self.prepared_key.curve:
  102. crv = value
  103. if not crv:
  104. raise KeyError(f"Can't match {self.prepared_key.curve}")
  105. # Calculate the key size in bytes. Section 6.2.1.2 and 6.2.1.3 of
  106. # RFC7518 prescribes that the 'x', 'y' and 'd' parameters of the curve
  107. # points must be encoded as octed-strings of this length.
  108. key_size = self.prepared_key.curve.baselen
  109. data = {
  110. "alg": self._algorithm,
  111. "kty": "EC",
  112. "crv": crv,
  113. "x": long_to_base64(public_key.pubkey.point.x(), size=key_size).decode("ASCII"),
  114. "y": long_to_base64(public_key.pubkey.point.y(), size=key_size).decode("ASCII"),
  115. }
  116. if not self.is_public():
  117. data["d"] = long_to_base64(self.prepared_key.privkey.secret_multiplier, size=key_size).decode("ASCII")
  118. return data