pyUmbral/umbral/openssl.py

188 lines
6.1 KiB
Python

"""
Copyright (C) 2018 NuCypher
This file is part of pyUmbral.
pyUmbral is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyUmbral is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
"""
import typing
from contextlib import contextmanager
from cryptography.hazmat.backends.openssl import backend
@typing.no_type_check
def _get_new_BN(set_consttime_flag=True):
"""
Returns a new and initialized OpenSSL BIGNUM.
The set_consttime_flag is set to True by default. When this instance of a
CurveBN object has BN_FLG_CONSTTIME set, OpenSSL will use constant time
operations whenever this CurveBN is passed.
"""
new_bn = backend._lib.BN_new()
backend.openssl_assert(new_bn != backend._ffi.NULL)
new_bn = backend._ffi.gc(new_bn, backend._lib.BN_clear_free)
if set_consttime_flag:
backend._lib.BN_set_flags(new_bn, backend._lib.BN_FLG_CONSTTIME)
return new_bn
@typing.no_type_check
def _get_ec_group_by_curve_nid(curve_nid: int):
"""
Returns the group of a given curve via its OpenSSL nid. This must be freed
after each use otherwise it leaks memory.
"""
group = backend._lib.EC_GROUP_new_by_curve_name(curve_nid)
backend.openssl_assert(group != backend._ffi.NULL)
return group
@typing.no_type_check
def _get_ec_order_by_group(ec_group):
"""
Returns the order of a given curve via its OpenSSL EC_GROUP.
"""
ec_order = _get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_GROUP_get_order(ec_group, ec_order, bn_ctx)
backend.openssl_assert(res == 1)
return ec_order
@typing.no_type_check
def _get_ec_generator_by_group(ec_group):
"""
Returns the generator point of a given curve via its OpenSSL EC_GROUP.
"""
generator = backend._lib.EC_GROUP_get0_generator(ec_group)
backend.openssl_assert(generator != backend._ffi.NULL)
generator = backend._ffi.gc(generator, backend._lib.EC_POINT_clear_free)
return generator
@typing.no_type_check
def _get_ec_group_degree(ec_group):
"""
Returns the number of bits needed to represent the order of the finite
field upon the curve is based.
"""
return backend._lib.EC_GROUP_get_degree(ec_group)
@typing.no_type_check
def _bn_is_on_curve(check_bn, curve: 'Curve'):
"""
Checks if a given OpenSSL BIGNUM is within the provided curve's order.
Returns True if the provided BN is on the curve, or False if the BN is zero
or not on the curve.
"""
zero = backend._int_to_bn(0)
zero = backend._ffi.gc(zero, backend._lib.BN_clear_free)
check_sign = backend._lib.BN_cmp(check_bn, zero)
range_check = backend._lib.BN_cmp(check_bn, curve.order)
return check_sign == 1 and range_check == -1
@typing.no_type_check
def _int_to_bn(py_int: int, curve: 'Curve'=None, set_consttime_flag=True):
"""
Converts the given Python int to an OpenSSL BIGNUM. If a curve is
provided, it will check if the Python integer is within the order of that
curve. If it's not within the order, it will raise a ValueError.
If set_consttime_flag is set to True, OpenSSL will use constant time
operations when using this CurveBN.
"""
conv_bn = backend._int_to_bn(py_int)
conv_bn = backend._ffi.gc(conv_bn, backend._lib.BN_clear_free)
if curve:
on_curve = _bn_is_on_curve(conv_bn, curve)
if not on_curve:
raise ValueError("The Python integer given is not on the provided curve.")
if set_consttime_flag:
backend._lib.BN_set_flags(conv_bn, backend._lib.BN_FLG_CONSTTIME)
return conv_bn
@typing.no_type_check
def _get_new_EC_POINT(curve: 'Curve'):
"""
Returns a new and initialized OpenSSL EC_POINT given the group of a curve.
If __curve_nid is provided, it retrieves the group from the curve provided.
"""
new_point = backend._lib.EC_POINT_new(curve.ec_group)
backend.openssl_assert(new_point != backend._ffi.NULL)
new_point = backend._ffi.gc(new_point, backend._lib.EC_POINT_clear_free)
return new_point
@typing.no_type_check
def _get_EC_POINT_via_affine(affine_x, affine_y, curve: 'Curve'):
"""
Returns an EC_POINT given the group of a curve and the affine coordinates
provided.
"""
new_point = _get_new_EC_POINT(curve)
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_POINT_set_affine_coordinates_GFp(
curve.ec_group, new_point, affine_x, affine_y, bn_ctx
)
backend.openssl_assert(res == 1)
return new_point
@typing.no_type_check
def _get_affine_coords_via_EC_POINT(ec_point, curve: 'Curve'):
"""
Returns the affine coordinates of a given point on the provided ec_group.
"""
affine_x = _get_new_BN()
affine_y = _get_new_BN()
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.EC_POINT_get_affine_coordinates_GFp(
curve.ec_group, ec_point, affine_x, affine_y, bn_ctx
)
backend.openssl_assert(res == 1)
return (affine_x, affine_y)
@typing.no_type_check
@contextmanager
def _tmp_bn_mont_ctx(modulus):
"""
Initializes and returns a BN_MONT_CTX for Montgomery ops.
Requires a modulus to place in the Montgomery structure.
"""
bn_mont_ctx = backend._lib.BN_MONT_CTX_new()
backend.openssl_assert(bn_mont_ctx != backend._ffi.NULL)
# Don't set the garbage collector. Only free it when the context is done
# or else you'll get a null pointer error.
try:
with backend._tmp_bn_ctx() as bn_ctx:
res = backend._lib.BN_MONT_CTX_set(bn_mont_ctx, modulus, bn_ctx)
backend.openssl_assert(res == 1)
yield bn_mont_ctx
finally:
backend._lib.BN_MONT_CTX_free(bn_mont_ctx)