import binascii
|
|
|
|
from . import ecdsa
|
|
from . import der
|
|
from . import rfc6979
|
|
from .curves import NIST192p, find_curve
|
|
from .util import string_to_number, number_to_string, randrange
|
|
from .util import sigencode_string, sigdecode_string
|
|
from .util import oid_ecPublicKey, encoded_oid_ecPublicKey
|
|
from .six import PY3, b
|
|
from hashlib import sha1
|
|
|
|
class BadSignatureError(Exception):
|
|
pass
|
|
class BadDigestError(Exception):
|
|
pass
|
|
|
|
class VerifyingKey:
|
|
def __init__(self, _error__please_use_generate=None):
|
|
if not _error__please_use_generate:
|
|
raise TypeError("Please use SigningKey.generate() to construct me")
|
|
|
|
@classmethod
|
|
def from_public_point(klass, point, curve=NIST192p, hashfunc=sha1):
|
|
self = klass(_error__please_use_generate=True)
|
|
self.curve = curve
|
|
self.default_hashfunc = hashfunc
|
|
self.pubkey = ecdsa.Public_key(curve.generator, point)
|
|
self.pubkey.order = curve.order
|
|
return self
|
|
|
|
@classmethod
|
|
def from_string(klass, string, curve=NIST192p, hashfunc=sha1,
|
|
validate_point=True):
|
|
order = curve.order
|
|
assert len(string) == curve.verifying_key_length, \
|
|
(len(string), curve.verifying_key_length)
|
|
xs = string[:curve.baselen]
|
|
ys = string[curve.baselen:]
|
|
assert len(xs) == curve.baselen, (len(xs), curve.baselen)
|
|
assert len(ys) == curve.baselen, (len(ys), curve.baselen)
|
|
x = string_to_number(xs)
|
|
y = string_to_number(ys)
|
|
if validate_point:
|
|
assert ecdsa.point_is_valid(curve.generator, x, y)
|
|
from . import ellipticcurve
|
|
point = ellipticcurve.Point(curve.curve, x, y, order)
|
|
return klass.from_public_point(point, curve, hashfunc)
|
|
|
|
@classmethod
|
|
def from_pem(klass, string):
|
|
return klass.from_der(der.unpem(string))
|
|
|
|
@classmethod
|
|
def from_der(klass, string):
|
|
# [[oid_ecPublicKey,oid_curve], point_str_bitstring]
|
|
s1,empty = der.remove_sequence(string)
|
|
if empty != b(""):
|
|
raise der.UnexpectedDER("trailing junk after DER pubkey: %s" %
|
|
binascii.hexlify(empty))
|
|
s2,point_str_bitstring = der.remove_sequence(s1)
|
|
# s2 = oid_ecPublicKey,oid_curve
|
|
oid_pk, rest = der.remove_object(s2)
|
|
oid_curve, empty = der.remove_object(rest)
|
|
if empty != b(""):
|
|
raise der.UnexpectedDER("trailing junk after DER pubkey objects: %s" %
|
|
binascii.hexlify(empty))
|
|
assert oid_pk == oid_ecPublicKey, (oid_pk, oid_ecPublicKey)
|
|
curve = find_curve(oid_curve)
|
|
point_str, empty = der.remove_bitstring(point_str_bitstring)
|
|
if empty != b(""):
|
|
raise der.UnexpectedDER("trailing junk after pubkey pointstring: %s" %
|
|
binascii.hexlify(empty))
|
|
assert point_str.startswith(b("\x00\x04"))
|
|
return klass.from_string(point_str[2:], curve)
|
|
|
|
def to_string(self):
|
|
# VerifyingKey.from_string(vk.to_string()) == vk as long as the
|
|
# curves are the same: the curve itself is not included in the
|
|
# serialized form
|
|
order = self.pubkey.order
|
|
x_str = number_to_string(self.pubkey.point.x(), order)
|
|
y_str = number_to_string(self.pubkey.point.y(), order)
|
|
return x_str + y_str
|
|
|
|
def to_pem(self):
|
|
return der.topem(self.to_der(), "PUBLIC KEY")
|
|
|
|
def to_der(self):
|
|
order = self.pubkey.order
|
|
x_str = number_to_string(self.pubkey.point.x(), order)
|
|
y_str = number_to_string(self.pubkey.point.y(), order)
|
|
point_str = b("\x00\x04") + x_str + y_str
|
|
return der.encode_sequence(der.encode_sequence(encoded_oid_ecPublicKey,
|
|
self.curve.encoded_oid),
|
|
der.encode_bitstring(point_str))
|
|
|
|
def verify(self, signature, data, hashfunc=None, sigdecode=sigdecode_string):
|
|
hashfunc = hashfunc or self.default_hashfunc
|
|
digest = hashfunc(data).digest()
|
|
return self.verify_digest(signature, digest, sigdecode)
|
|
|
|
def verify_digest(self, signature, digest, sigdecode=sigdecode_string):
|
|
if len(digest) > self.curve.baselen:
|
|
raise BadDigestError("this curve (%s) is too short "
|
|
"for your digest (%d)" % (self.curve.name,
|
|
8*len(digest)))
|
|
number = string_to_number(digest)
|
|
r, s = sigdecode(signature, self.pubkey.order)
|
|
sig = ecdsa.Signature(r, s)
|
|
if self.pubkey.verifies(number, sig):
|
|
return True
|
|
raise BadSignatureError
|
|
|
|
class SigningKey:
|
|
def __init__(self, _error__please_use_generate=None):
|
|
if not _error__please_use_generate:
|
|
raise TypeError("Please use SigningKey.generate() to construct me")
|
|
|
|
@classmethod
|
|
def generate(klass, curve=NIST192p, entropy=None, hashfunc=sha1):
|
|
secexp = randrange(curve.order, entropy)
|
|
return klass.from_secret_exponent(secexp, curve, hashfunc)
|
|
|
|
# to create a signing key from a short (arbitrary-length) seed, convert
|
|
# that seed into an integer with something like
|
|
# secexp=util.randrange_from_seed__X(seed, curve.order), and then pass
|
|
# that integer into SigningKey.from_secret_exponent(secexp, curve)
|
|
|
|
@classmethod
|
|
def from_secret_exponent(klass, secexp, curve=NIST192p, hashfunc=sha1):
|
|
self = klass(_error__please_use_generate=True)
|
|
self.curve = curve
|
|
self.default_hashfunc = hashfunc
|
|
self.baselen = curve.baselen
|
|
n = curve.order
|
|
assert 1 <= secexp < n
|
|
pubkey_point = curve.generator*secexp
|
|
pubkey = ecdsa.Public_key(curve.generator, pubkey_point)
|
|
pubkey.order = n
|
|
self.verifying_key = VerifyingKey.from_public_point(pubkey_point, curve,
|
|
hashfunc)
|
|
self.privkey = ecdsa.Private_key(pubkey, secexp)
|
|
self.privkey.order = n
|
|
return self
|
|
|
|
@classmethod
|
|
def from_string(klass, string, curve=NIST192p, hashfunc=sha1):
|
|
assert len(string) == curve.baselen, (len(string), curve.baselen)
|
|
secexp = string_to_number(string)
|
|
return klass.from_secret_exponent(secexp, curve, hashfunc)
|
|
|
|
@classmethod
|
|
def from_pem(klass, string, hashfunc=sha1):
|
|
# the privkey pem file has two sections: "EC PARAMETERS" and "EC
|
|
# PRIVATE KEY". The first is redundant.
|
|
if PY3 and isinstance(string, str):
|
|
string = string.encode()
|
|
privkey_pem = string[string.index(b("-----BEGIN EC PRIVATE KEY-----")):]
|
|
return klass.from_der(der.unpem(privkey_pem), hashfunc)
|
|
@classmethod
|
|
def from_der(klass, string, hashfunc=sha1):
|
|
# SEQ([int(1), octetstring(privkey),cont[0], oid(secp224r1),
|
|
# cont[1],bitstring])
|
|
s, empty = der.remove_sequence(string)
|
|
if empty != b(""):
|
|
raise der.UnexpectedDER("trailing junk after DER privkey: %s" %
|
|
binascii.hexlify(empty))
|
|
one, s = der.remove_integer(s)
|
|
if one != 1:
|
|
raise der.UnexpectedDER("expected '1' at start of DER privkey,"
|
|
" got %d" % one)
|
|
privkey_str, s = der.remove_octet_string(s)
|
|
tag, curve_oid_str, s = der.remove_constructed(s)
|
|
if tag != 0:
|
|
raise der.UnexpectedDER("expected tag 0 in DER privkey,"
|
|
" got %d" % tag)
|
|
curve_oid, empty = der.remove_object(curve_oid_str)
|
|
if empty != b(""):
|
|
raise der.UnexpectedDER("trailing junk after DER privkey "
|
|
"curve_oid: %s" % binascii.hexlify(empty))
|
|
curve = find_curve(curve_oid)
|
|
|
|
# we don't actually care about the following fields
|
|
#
|
|
#tag, pubkey_bitstring, s = der.remove_constructed(s)
|
|
#if tag != 1:
|
|
# raise der.UnexpectedDER("expected tag 1 in DER privkey, got %d"
|
|
# % tag)
|
|
#pubkey_str = der.remove_bitstring(pubkey_bitstring)
|
|
#if empty != "":
|
|
# raise der.UnexpectedDER("trailing junk after DER privkey "
|
|
# "pubkeystr: %s" % binascii.hexlify(empty))
|
|
|
|
# our from_string method likes fixed-length privkey strings
|
|
if len(privkey_str) < curve.baselen:
|
|
privkey_str = b("\x00")*(curve.baselen-len(privkey_str)) + privkey_str
|
|
return klass.from_string(privkey_str, curve, hashfunc)
|
|
|
|
def to_string(self):
|
|
secexp = self.privkey.secret_multiplier
|
|
s = number_to_string(secexp, self.privkey.order)
|
|
return s
|
|
|
|
def to_pem(self):
|
|
# TODO: "BEGIN ECPARAMETERS"
|
|
return der.topem(self.to_der(), "EC PRIVATE KEY")
|
|
|
|
def to_der(self):
|
|
# SEQ([int(1), octetstring(privkey),cont[0], oid(secp224r1),
|
|
# cont[1],bitstring])
|
|
encoded_vk = b("\x00\x04") + self.get_verifying_key().to_string()
|
|
return der.encode_sequence(der.encode_integer(1),
|
|
der.encode_octet_string(self.to_string()),
|
|
der.encode_constructed(0, self.curve.encoded_oid),
|
|
der.encode_constructed(1, der.encode_bitstring(encoded_vk)),
|
|
)
|
|
|
|
def get_verifying_key(self):
|
|
return self.verifying_key
|
|
|
|
def sign_deterministic(self, data, hashfunc=None, sigencode=sigencode_string):
|
|
hashfunc = hashfunc or self.default_hashfunc
|
|
digest = hashfunc(data).digest()
|
|
|
|
return self.sign_digest_deterministic(digest, hashfunc=hashfunc, sigencode=sigencode)
|
|
|
|
def sign_digest_deterministic(self, digest, hashfunc=None, sigencode=sigencode_string):
|
|
"""
|
|
Calculates 'k' from data itself, removing the need for strong
|
|
random generator and producing deterministic (reproducible) signatures.
|
|
See RFC 6979 for more details.
|
|
"""
|
|
secexp = self.privkey.secret_multiplier
|
|
k = rfc6979.generate_k(
|
|
self.curve.generator.order(), secexp, hashfunc, digest)
|
|
|
|
return self.sign_digest(digest, sigencode=sigencode, k=k)
|
|
|
|
def sign(self, data, entropy=None, hashfunc=None, sigencode=sigencode_string, k=None):
|
|
"""
|
|
hashfunc= should behave like hashlib.sha1 . The output length of the
|
|
hash (in bytes) must not be longer than the length of the curve order
|
|
(rounded up to the nearest byte), so using SHA256 with nist256p is
|
|
ok, but SHA256 with nist192p is not. (In the 2**-96ish unlikely event
|
|
of a hash output larger than the curve order, the hash will
|
|
effectively be wrapped mod n).
|
|
|
|
Use hashfunc=hashlib.sha1 to match openssl's -ecdsa-with-SHA1 mode,
|
|
or hashfunc=hashlib.sha256 for openssl-1.0.0's -ecdsa-with-SHA256.
|
|
"""
|
|
|
|
hashfunc = hashfunc or self.default_hashfunc
|
|
h = hashfunc(data).digest()
|
|
return self.sign_digest(h, entropy, sigencode, k)
|
|
|
|
def sign_digest(self, digest, entropy=None, sigencode=sigencode_string, k=None):
|
|
if len(digest) > self.curve.baselen:
|
|
raise BadDigestError("this curve (%s) is too short "
|
|
"for your digest (%d)" % (self.curve.name,
|
|
8*len(digest)))
|
|
number = string_to_number(digest)
|
|
r, s = self.sign_number(number, entropy, k)
|
|
return sigencode(r, s, self.privkey.order)
|
|
|
|
def sign_number(self, number, entropy=None, k=None):
|
|
# returns a pair of numbers
|
|
order = self.privkey.order
|
|
# privkey.sign() may raise RuntimeError in the amazingly unlikely
|
|
# (2**-192) event that r=0 or s=0, because that would leak the key.
|
|
# We could re-try with a different 'k', but we couldn't test that
|
|
# code, so I choose to allow the signature to fail instead.
|
|
|
|
# If k is set, it is used directly. In other cases
|
|
# it is generated using entropy function
|
|
if k is not None:
|
|
_k = k
|
|
else:
|
|
_k = randrange(order, entropy)
|
|
|
|
assert 1 <= _k < order
|
|
sig = self.privkey.sign(number, _k)
|
|
return sig.r, sig.s
|