blob: 30198eabee44ffd8a71ed82b2c82a2aabe7e9233 [file] [log] [blame]
#
# Copyright (c) 2023 Project CHIP Authors
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import abc
import hashlib
from ctypes import (CFUNCTYPE, POINTER, _Pointer, c_bool, c_char, c_size_t, c_uint8, c_uint32, c_void_p, cast, memmove, py_object,
string_at)
from typing import TYPE_CHECKING
from chip import native
from ecdsa import ECDH, NIST256p, SigningKey # type: ignore
# WORKAROUND: Create a subscriptable pointer type (with square brackets) to ensure compliance of type hinting with ctypes
if not TYPE_CHECKING:
class pointer_fix:
@classmethod
def __class_getitem__(cls, item):
return POINTER(item)
_Pointer = pointer_fix
_pychip_P256Keypair_ECDSA_sign_msg_func = CFUNCTYPE(
c_bool, py_object, POINTER(c_uint8), c_size_t, POINTER(c_uint8), POINTER(c_size_t))
_pychip_P256Keypair_ECDH_derive_secret_func = CFUNCTYPE(c_bool, py_object, POINTER(c_uint8), POINTER(c_uint8), POINTER(c_size_t))
P256_PUBLIC_KEY_LENGTH = 2 * 32 + 1
@ _pychip_P256Keypair_ECDSA_sign_msg_func
def _pychip_ECDSA_sign_msg(self_: 'P256Keypair', message_buf: _Pointer[c_uint8], message_size: int, signature_buf: _Pointer[c_uint8], signature_buf_size: _Pointer[c_size_t]) -> bool:
res = self_.ECDSA_sign_msg(string_at(message_buf, message_size)[:])
memmove(signature_buf, res, len(res))
signature_buf_size.contents.value = len(res)
return True
@ _pychip_P256Keypair_ECDH_derive_secret_func
def _pychip_ECDH_derive_secret(self_: 'P256Keypair', remote_pubkey: _Pointer[c_uint8], out_secret_buf: _Pointer[c_uint8], out_secret_buf_size: _Pointer[c_uint32]) -> bool:
res = self_.ECDH_derive_secret(string_at(remote_pubkey, P256_PUBLIC_KEY_LENGTH)[:])
memmove(out_secret_buf, res, len(res))
out_secret_buf_size.contents.value = len(res)
return True
class P256Keypair:
"""Represented a P256Keypair, should live longer than the one using it.
Users are expected to hold a reference to the Keypair object.
"""
def __init__(self):
self._native_obj = None
def __copy__(self):
raise NotImplementedError("P256Keypair should not be copied.")
def __deepcopy__(self, _=None):
raise NotImplementedError("P256Keypair should not be copied.")
def _create_native_object(self) -> c_void_p:
handle = native.GetLibraryHandle()
if not handle.pychip_NewP256Keypair.argtypes:
setter = native.NativeLibraryHandleMethodArguments(handle)
setter.Set("pychip_NewP256Keypair", c_void_p, [py_object,
_pychip_P256Keypair_ECDSA_sign_msg_func, _pychip_P256Keypair_ECDH_derive_secret_func])
setter.Set("pychip_P256Keypair_UpdatePubkey", native.PyChipError, [c_void_p, POINTER(c_char), c_size_t])
setter.Set("pychip_DeleteP256Keypair", None, [c_void_p])
self._native_obj = handle.pychip_NewP256Keypair(
py_object(self), _pychip_ECDSA_sign_msg, _pychip_ECDH_derive_secret)
self.UpdatePublicKey()
return self._native_obj
def __del__(self):
if self._native_obj is not None:
handle = native.GetLibraryHandle()
handle.pychip_DeleteP256Keypair(c_void_p(self._native_obj))
self._native_obj = None
@property
def native_object(self) -> c_void_p:
if self._native_obj is None:
return self._create_native_object()
return self._native_obj
def UpdatePublicKey(self) -> None:
''' Update the PublicKey in the underlying C++ object.
This function should be called when the implementation
generates a new keypair.
'''
handle = native.GetLibraryHandle()
handle.pychip_P256Keypair_UpdatePubkey(cast(self._native_obj, c_void_p),
self.public_key, len(self.public_key)).raise_on_error()
@abc.abstractproperty
def public_key(self) -> bytes:
''' Returns the public key of the key pair
The return value should conform with the uncompressed format of
Section 2.3.3 of the SECG SEC 1 ("Elliptic Curve Cryptography")
standard. (i.e. 0x04 || X || Y)
For P256Keypair, the output length should be exactly 65 bytes.
'''
raise NotImplementedError()
@abc.abstractmethod
def ECDSA_sign_msg(self, message: bytes) -> bytes:
raise NotImplementedError()
@abc.abstractmethod
def ECDH_derive_secret(self, remote_pubkey: bytes) -> bytes:
''' Derive shared secret from the local private key and remote public key.
remote_pubkey will be a public key conforms with the uncompressed
format of section 2.3.3 of the SECG SEC 1 standard.
'''
raise NotImplementedError()
class TestP256Keypair(P256Keypair):
''' The P256Keypair for testing purpose. It is not safe for any productions use
'''
def __init__(self, private_key: SigningKey = None):
super().__init__()
if private_key is None:
self._key = SigningKey.generate(NIST256p)
else:
self._key = private_key
self._pubkey = self._key.verifying_key.to_string(encoding='uncompressed')
@property
def public_key(self) -> bytes:
return self._pubkey
def ECDSA_sign_msg(self, message: bytes) -> bytes:
return self._key.sign_deterministic(message, hashfunc=hashlib.sha256)
def ECDH_derive_secret(self, remote_pubkey: bytes) -> bytes:
ecdh = ECDH(curve=NIST256p)
ecdh.load_private_key(self._key)
ecdh.load_received_public_key_bytes(remote_pubkey[1:])
return ecdh.ecdh1.generate_sharedsecret_bytes()