kinto-remote-settings/src/kinto_remote_settings/signer/backends/local_ecdsa.py (85 lines of code) (raw):
import base64
import hashlib
import warnings
import ecdsa
from ecdsa import NIST384p, SigningKey, VerifyingKey
from ..utils import get_first_matching_setting
from .base import SignerBase
from .exceptions import BadSignatureError
# Autograph uses this prefix prior to signing.
SIGN_PREFIX = b"Content-Signature:\x00"
class ECDSASigner(SignerBase):
def __init__(self, private_key=None, public_key=None):
if private_key is None and public_key is None:
msg = "Please, specify either a private_key or public_key location."
raise ValueError(msg)
self.private_key = private_key
self.public_key = public_key
def healthcheck(self, request):
pass
@classmethod
def generate_keypair(cls):
sk = SigningKey.generate(curve=NIST384p)
vk = sk.get_verifying_key()
return sk.to_pem(), vk.to_pem()
def load_private_key(self):
if self.private_key is None:
msg = "Please, specify the private_key location."
raise ValueError(msg)
with open(self.private_key, "rb") as key_file:
return SigningKey.from_pem(key_file.read())
def load_public_key(self):
# Check settings validity
if self.private_key:
private_key = self.load_private_key()
return private_key.get_verifying_key()
if self.public_key:
with open(self.public_key, "rb") as key_file:
return VerifyingKey.from_pem(key_file.read())
return None
def sign(self, payload):
if isinstance(payload, str): # pragma: no cover
payload = payload.encode("utf-8")
payload = SIGN_PREFIX + payload
private_key = self.load_private_key()
signature = private_key.sign(
payload, hashfunc=hashlib.sha384, sigencode=ecdsa.util.sigencode_string
)
x5u = ""
enc_signature = base64.urlsafe_b64encode(signature).decode("utf-8")
return {"signature": enc_signature, "x5u": x5u, "mode": "p384ecdsa"}
def verify(self, payload, signature_bundle):
if isinstance(payload, str): # pragma: no cover
payload = payload.encode("utf-8")
payload = SIGN_PREFIX + payload
signature = signature_bundle["signature"]
if isinstance(signature, str): # pragma: no cover
signature = signature.encode("utf-8")
signature_bytes = base64.urlsafe_b64decode(signature)
public_key = self.load_public_key()
try:
public_key.verify(
signature_bytes,
payload,
hashfunc=hashlib.sha384,
sigdecode=ecdsa.util.sigdecode_string,
)
except Exception as e:
raise BadSignatureError(e)
def load_from_settings(settings, prefix="", *, prefixes=None):
if prefixes is None:
prefixes = [prefix]
if prefix != "":
message = (
"signer.load_from_settings `prefix` parameter is deprecated, please "
"use `prefixes` instead."
)
warnings.warn(message, DeprecationWarning)
private_key = get_first_matching_setting("ecdsa.private_key", settings, prefixes)
public_key = get_first_matching_setting("ecdsa.public_key", settings, prefixes)
try:
return ECDSASigner(private_key=private_key, public_key=public_key)
except ValueError:
msg = (
"Please specify either kinto.signer.ecdsa.private_key or "
"kinto.signer.ecdsa.public_key in the settings."
)
raise ValueError(msg)