#
# Copyright (C) 2014  FreeIPA Contributors see COPYING for license
#

import random
import ctypes.util
import binascii
import struct

import six
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa
from cffi import FFI

if six.PY3:
    unicode = str


_ffi = FFI()

_ffi.cdef('''
/* p11-kit/pkcs11.h */

typedef unsigned long CK_FLAGS;

struct _CK_VERSION
{
  unsigned char major;
  unsigned char minor;
};

typedef unsigned long CK_SLOT_ID;
typedef CK_SLOT_ID *CK_SLOT_ID_PTR;

typedef unsigned long CK_SESSION_HANDLE;

typedef unsigned long CK_USER_TYPE;

typedef unsigned long CK_OBJECT_HANDLE;

typedef unsigned long CK_OBJECT_CLASS;

typedef unsigned long CK_KEY_TYPE;

typedef unsigned long CK_ATTRIBUTE_TYPE;

typedef unsigned long ck_flags_t;

typedef unsigned char CK_BBOOL;

typedef unsigned long int CK_ULONG;
typedef CK_ULONG *CK_ULONG_PTR;

struct _CK_ATTRIBUTE
{
  CK_ATTRIBUTE_TYPE type;
  void *pValue;
  unsigned long ulValueLen;
};

typedef unsigned long CK_MECHANISM_TYPE;

struct _CK_MECHANISM
{
  CK_MECHANISM_TYPE mechanism;
  void *pParameter;
  unsigned long ulParameterLen;
};

struct _CK_TOKEN_INFO
{
  unsigned char label[32];
  unsigned char manufacturer_id[32];
  unsigned char model[16];
  unsigned char serial_number[16];
  ck_flags_t flags;
  unsigned long max_session_count;
  unsigned long session_count;
  unsigned long max_rw_session_count;
  unsigned long rw_session_count;
  unsigned long max_pin_len;
  unsigned long min_pin_len;
  unsigned long total_public_memory;
  unsigned long free_public_memory;
  unsigned long total_private_memory;
  unsigned long free_private_memory;
  struct _CK_VERSION hardware_version;
  struct _CK_VERSION firmware_version;
  unsigned char utc_time[16];
};

typedef struct _CK_TOKEN_INFO CK_TOKEN_INFO;
typedef CK_TOKEN_INFO *CK_TOKEN_INFO_PTR;

typedef unsigned long CK_RV;

typedef ... *CK_NOTIFY;

struct _CK_FUNCTION_LIST;

typedef CK_RV (*CK_C_Initialize) (void *init_args);
typedef CK_RV (*CK_C_Finalize) (void *pReserved);
typedef ... *CK_C_GetInfo;
typedef ... *CK_C_GetFunctionList;
CK_RV C_GetFunctionList (struct _CK_FUNCTION_LIST **function_list);
typedef CK_RV (*CK_C_GetSlotList) (CK_BBOOL tokenPresent,
                                   CK_SLOT_ID_PTR pSlotList,
                                   CK_ULONG_PTR pulCount);
typedef ... *CK_C_GetSlotInfo;
typedef CK_RV (*CK_C_GetTokenInfo) (CK_SLOT_ID slotID,
                                    CK_TOKEN_INFO_PTR pInfo);
typedef ... *CK_C_WaitForSlotEvent;
typedef ... *CK_C_GetMechanismList;
typedef ... *CK_C_GetMechanismInfo;
typedef ... *CK_C_InitToken;
typedef ... *CK_C_InitPIN;
typedef ... *CK_C_SetPIN;
typedef CK_RV (*CK_C_OpenSession) (CK_SLOT_ID slotID, CK_FLAGS flags,
                                   void *application, CK_NOTIFY notify,
                                   CK_SESSION_HANDLE *session);
typedef CK_RV (*CK_C_CloseSession) (CK_SESSION_HANDLE session);
typedef ... *CK_C_CloseAllSessions;
typedef ... *CK_C_GetSessionInfo;
typedef ... *CK_C_GetOperationState;
typedef ... *CK_C_SetOperationState;
typedef CK_RV (*CK_C_Login) (CK_SESSION_HANDLE session, CK_USER_TYPE user_type,
                             unsigned char *pin, unsigned long pin_len);
typedef CK_RV (*CK_C_Logout) (CK_SESSION_HANDLE session);
typedef CK_RV (*CK_C_CreateObject) (CK_SESSION_HANDLE session,
                                    struct _CK_ATTRIBUTE *templ,
                                    unsigned long count,
                                    CK_OBJECT_HANDLE *object);
typedef ... *CK_C_CopyObject;
typedef CK_RV (*CK_C_DestroyObject) (CK_SESSION_HANDLE session,
                                     CK_OBJECT_HANDLE object);
typedef ... *CK_C_GetObjectSize;
typedef CK_RV (*CK_C_GetAttributeValue) (CK_SESSION_HANDLE session,
                                         CK_OBJECT_HANDLE object,
                                         struct _CK_ATTRIBUTE *templ,
                                         unsigned long count);
typedef CK_RV (*CK_C_SetAttributeValue) (CK_SESSION_HANDLE session,
                                         CK_OBJECT_HANDLE object,
                                         struct _CK_ATTRIBUTE *templ,
                                         unsigned long count);
typedef CK_RV (*CK_C_FindObjectsInit) (CK_SESSION_HANDLE session,
                                       struct _CK_ATTRIBUTE *templ,
                                       unsigned long count);
typedef CK_RV (*CK_C_FindObjects) (CK_SESSION_HANDLE session,
                                   CK_OBJECT_HANDLE *object,
                                   unsigned long max_object_count,
                                   unsigned long *object_count);
typedef CK_RV (*CK_C_FindObjectsFinal) (CK_SESSION_HANDLE session);
typedef ... *CK_C_EncryptInit;
typedef ... *CK_C_Encrypt;
typedef ... *CK_C_EncryptUpdate;
typedef ... *CK_C_EncryptFinal;
typedef ... *CK_C_DecryptInit;
typedef ... *CK_C_Decrypt;
typedef ... *CK_C_DecryptUpdate;
typedef ... *CK_C_DecryptFinal;
typedef ... *CK_C_DigestInit;
typedef ... *CK_C_Digest;
typedef ... *CK_C_DigestUpdate;
typedef ... *CK_C_DigestKey;
typedef ... *CK_C_DigestFinal;
typedef ... *CK_C_SignInit;
typedef ... *CK_C_Sign;
typedef ... *CK_C_SignUpdate;
typedef ... *CK_C_SignFinal;
typedef ... *CK_C_SignRecoverInit;
typedef ... *CK_C_SignRecover;
typedef ... *CK_C_VerifyInit;
typedef ... *CK_C_Verify;
typedef ... *CK_C_VerifyUpdate;
typedef ... *CK_C_VerifyFinal;
typedef ... *CK_C_VerifyRecoverInit;
typedef ... *CK_C_VerifyRecover;
typedef ... *CK_C_DigestEncryptUpdate;
typedef ... *CK_C_DecryptDigestUpdate;
typedef ... *CK_C_SignEncryptUpdate;
typedef ... *CK_C_DecryptVerifyUpdate;
typedef CK_RV (*CK_C_GenerateKey) (CK_SESSION_HANDLE session,
                                   struct _CK_MECHANISM *mechanism,
                                   struct _CK_ATTRIBUTE *templ,
                                   unsigned long count,
                                   CK_OBJECT_HANDLE *key);
typedef CK_RV (*CK_C_GenerateKeyPair) (CK_SESSION_HANDLE session,
                                       struct _CK_MECHANISM *mechanism,
                                       struct _CK_ATTRIBUTE *
                                        public_key_template,
                                       unsigned long
                                        public_key_attribute_count,
                                       struct _CK_ATTRIBUTE *
                                        private_key_template,
                                       unsigned long
                                        private_key_attribute_count,
                                       CK_OBJECT_HANDLE *public_key,
                                       CK_OBJECT_HANDLE *private_key);
typedef CK_RV (*CK_C_WrapKey) (CK_SESSION_HANDLE session,
                               struct _CK_MECHANISM *mechanism,
                               CK_OBJECT_HANDLE wrapping_key,
                               CK_OBJECT_HANDLE key,
                               unsigned char *wrapped_key,
                               unsigned long *wrapped_key_len);
typedef CK_RV (*CK_C_UnwrapKey) (CK_SESSION_HANDLE session,
                                 struct _CK_MECHANISM *mechanism,
                                 CK_OBJECT_HANDLE unwrapping_key,
                                 unsigned char *wrapped_key,
                                 unsigned long wrapped_key_len,
                                 struct _CK_ATTRIBUTE *templ,
                                 unsigned long attribute_count,
                                 CK_OBJECT_HANDLE *key);
typedef ... *CK_C_DeriveKey;
typedef ... *CK_C_SeedRandom;
typedef ... *CK_C_GenerateRandom;
typedef ... *CK_C_GetFunctionStatus;
typedef ... *CK_C_CancelFunction;

struct _CK_FUNCTION_LIST
{
  struct _CK_VERSION version;
  CK_C_Initialize C_Initialize;
  CK_C_Finalize C_Finalize;
  CK_C_GetInfo C_GetInfo;
  CK_C_GetFunctionList C_GetFunctionList;
  CK_C_GetSlotList C_GetSlotList;
  CK_C_GetSlotInfo C_GetSlotInfo;
  CK_C_GetTokenInfo C_GetTokenInfo;
  CK_C_GetMechanismList C_GetMechanismList;
  CK_C_GetMechanismInfo C_GetMechanismInfo;
  CK_C_InitToken C_InitToken;
  CK_C_InitPIN C_InitPIN;
  CK_C_SetPIN C_SetPIN;
  CK_C_OpenSession C_OpenSession;
  CK_C_CloseSession C_CloseSession;
  CK_C_CloseAllSessions C_CloseAllSessions;
  CK_C_GetSessionInfo C_GetSessionInfo;
  CK_C_GetOperationState C_GetOperationState;
  CK_C_SetOperationState C_SetOperationState;
  CK_C_Login C_Login;
  CK_C_Logout C_Logout;
  CK_C_CreateObject C_CreateObject;
  CK_C_CopyObject C_CopyObject;
  CK_C_DestroyObject C_DestroyObject;
  CK_C_GetObjectSize C_GetObjectSize;
  CK_C_GetAttributeValue C_GetAttributeValue;
  CK_C_SetAttributeValue C_SetAttributeValue;
  CK_C_FindObjectsInit C_FindObjectsInit;
  CK_C_FindObjects C_FindObjects;
  CK_C_FindObjectsFinal C_FindObjectsFinal;
  CK_C_EncryptInit C_EncryptInit;
  CK_C_Encrypt C_Encrypt;
  CK_C_EncryptUpdate C_EncryptUpdate;
  CK_C_EncryptFinal C_EncryptFinal;
  CK_C_DecryptInit C_DecryptInit;
  CK_C_Decrypt C_Decrypt;
  CK_C_DecryptUpdate C_DecryptUpdate;
  CK_C_DecryptFinal C_DecryptFinal;
  CK_C_DigestInit C_DigestInit;
  CK_C_Digest C_Digest;
  CK_C_DigestUpdate C_DigestUpdate;
  CK_C_DigestKey C_DigestKey;
  CK_C_DigestFinal C_DigestFinal;
  CK_C_SignInit C_SignInit;
  CK_C_Sign C_Sign;
  CK_C_SignUpdate C_SignUpdate;
  CK_C_SignFinal C_SignFinal;
  CK_C_SignRecoverInit C_SignRecoverInit;
  CK_C_SignRecover C_SignRecover;
  CK_C_VerifyInit C_VerifyInit;
  CK_C_Verify C_Verify;
  CK_C_VerifyUpdate C_VerifyUpdate;
  CK_C_VerifyFinal C_VerifyFinal;
  CK_C_VerifyRecoverInit C_VerifyRecoverInit;
  CK_C_VerifyRecover C_VerifyRecover;
  CK_C_DigestEncryptUpdate C_DigestEncryptUpdate;
  CK_C_DecryptDigestUpdate C_DecryptDigestUpdate;
  CK_C_SignEncryptUpdate C_SignEncryptUpdate;
  CK_C_DecryptVerifyUpdate C_DecryptVerifyUpdate;
  CK_C_GenerateKey C_GenerateKey;
  CK_C_GenerateKeyPair C_GenerateKeyPair;
  CK_C_WrapKey C_WrapKey;
  CK_C_UnwrapKey C_UnwrapKey;
  CK_C_DeriveKey C_DeriveKey;
  CK_C_SeedRandom C_SeedRandom;
  CK_C_GenerateRandom C_GenerateRandom;
  CK_C_GetFunctionStatus C_GetFunctionStatus;
  CK_C_CancelFunction C_CancelFunction;
  CK_C_WaitForSlotEvent C_WaitForSlotEvent;
};

typedef unsigned char CK_BYTE;
typedef unsigned char CK_UTF8CHAR;
typedef CK_BYTE *CK_BYTE_PTR;

typedef CK_OBJECT_HANDLE *CK_OBJECT_HANDLE_PTR;

typedef struct _CK_ATTRIBUTE CK_ATTRIBUTE;
typedef struct _CK_ATTRIBUTE *CK_ATTRIBUTE_PTR;

typedef struct _CK_MECHANISM CK_MECHANISM;

typedef struct _CK_FUNCTION_LIST *CK_FUNCTION_LIST_PTR;


/* p11-kit/uri.h */

typedef enum {
    DUMMY   /* ..., */
} P11KitUriType;

typedef ... P11KitUri;

CK_ATTRIBUTE_PTR    p11_kit_uri_get_attributes      (P11KitUri *uri,
                                                     CK_ULONG *n_attrs);

int                 p11_kit_uri_any_unrecognized    (P11KitUri *uri);

P11KitUri*          p11_kit_uri_new                 (void);

int                 p11_kit_uri_parse               (const char *string,
                                                     P11KitUriType uri_type,
                                                     P11KitUri *uri);

void                p11_kit_uri_free                (P11KitUri *uri);


/* p11helper.c */

struct ck_rsa_pkcs_oaep_params {
    CK_MECHANISM_TYPE hash_alg;
    unsigned long mgf;
    unsigned long source;
    void *source_data;
    unsigned long source_data_len;
};

typedef struct ck_rsa_pkcs_oaep_params CK_RSA_PKCS_OAEP_PARAMS;
''')

_libp11_kit = _ffi.dlopen(ctypes.util.find_library('p11-kit'))


# utility

NULL = _ffi.NULL

unsigned_char = _ffi.typeof('unsigned char')
unsigned_long = _ffi.typeof('unsigned long')

sizeof = _ffi.sizeof


def new_ptr(ctype, *args):
    return _ffi.new(_ffi.getctype(ctype, '*'), *args)


def new_array(ctype, *args):
    return _ffi.new(_ffi.getctype(ctype, '[]'), *args)


# p11-kit/pkcs11.h

CK_SESSION_HANDLE = _ffi.typeof('CK_SESSION_HANDLE')

CK_OBJECT_HANDLE = _ffi.typeof('CK_OBJECT_HANDLE')

CKU_USER = 1

CKF_RW_SESSION = 0x2
CKF_SERIAL_SESSION = 0x4

CK_OBJECT_CLASS = _ffi.typeof('CK_OBJECT_CLASS')

CKO_PUBLIC_KEY = 2
CKO_PRIVATE_KEY = 3
CKO_SECRET_KEY = 4
CKO_VENDOR_DEFINED = 0x80000000

CK_KEY_TYPE = _ffi.typeof('CK_KEY_TYPE')

CKK_RSA = 0
CKK_AES = 0x1f

CKA_CLASS = 0
CKA_TOKEN = 1
CKA_PRIVATE = 2
CKA_LABEL = 3
CKA_TRUSTED = 0x86
CKA_KEY_TYPE = 0x100
CKA_ID = 0x102
CKA_SENSITIVE = 0x103
CKA_ENCRYPT = 0x104
CKA_DECRYPT = 0x105
CKA_WRAP = 0x106
CKA_UNWRAP = 0x107
CKA_SIGN = 0x108
CKA_SIGN_RECOVER = 0x109
CKA_VERIFY = 0x10a
CKA_VERIFY_RECOVER = 0x10b
CKA_DERIVE = 0x10c
CKA_MODULUS = 0x120
CKA_MODULUS_BITS = 0x121
CKA_PUBLIC_EXPONENT = 0x122
CKA_VALUE_LEN = 0x161
CKA_EXTRACTABLE = 0x162
CKA_LOCAL = 0x163
CKA_NEVER_EXTRACTABLE = 0x164
CKA_ALWAYS_SENSITIVE = 0x165
CKA_MODIFIABLE = 0x170
CKA_ALWAYS_AUTHENTICATE = 0x202
CKA_WRAP_WITH_TRUSTED = 0x210

CKM_RSA_PKCS_KEY_PAIR_GEN = 0
CKM_RSA_PKCS = 1
CKM_RSA_PKCS_OAEP = 9
CKM_SHA_1 = 0x220
CKM_AES_KEY_GEN = 0x1080

CKR_OK = 0
CKR_ATTRIBUTE_TYPE_INVALID = 0x12
CKR_USER_NOT_LOGGED_IN = 0x101
CKR_BUFFER_TOO_SMALL = 0x150

CK_BYTE = _ffi.typeof('CK_BYTE')
CK_BBOOL = _ffi.typeof('CK_BBOOL')
CK_ULONG = _ffi.typeof('CK_ULONG')
CK_BYTE_PTR = _ffi.typeof('CK_BYTE_PTR')
CK_FALSE = 0
CK_TRUE = 1

CK_OBJECT_HANDLE_PTR = _ffi.typeof('CK_OBJECT_HANDLE_PTR')

CK_ATTRIBUTE = _ffi.typeof('CK_ATTRIBUTE')

CK_MECHANISM = _ffi.typeof('CK_MECHANISM')

CK_FUNCTION_LIST_PTR = _ffi.typeof('CK_FUNCTION_LIST_PTR')

CK_SLOT_ID = _ffi.typeof('CK_SLOT_ID')

CK_TOKEN_INFO = _ffi.typeof('CK_TOKEN_INFO')

NULL_PTR = NULL


# p11-kit/uri.h

P11_KIT_URI_OK = 0

P11_KIT_URI_FOR_OBJECT = 2

p11_kit_uri_get_attributes = _libp11_kit.p11_kit_uri_get_attributes

p11_kit_uri_any_unrecognized = _libp11_kit.p11_kit_uri_any_unrecognized

p11_kit_uri_new = _libp11_kit.p11_kit_uri_new

p11_kit_uri_parse = _libp11_kit.p11_kit_uri_parse

p11_kit_uri_free = _libp11_kit.p11_kit_uri_free


# library.c

def loadLibrary(module):
    """Load the PKCS#11 library"""
    # Load PKCS #11 library
    if module:
        # pylint: disable=no-member
        pDynLib = _ffi.dlopen(module, _ffi.RTLD_NOW | _ffi.RTLD_LOCAL)
    else:
        raise Error("PKCS#11 module name is empty")

    # Retrieve the entry point for C_GetFunctionList
    pGetFunctionList = pDynLib.C_GetFunctionList
    if pGetFunctionList == NULL:
        raise Error(
            f"Module '{module}' has no function 'C_GetFunctionList'."
        )

    # Store the handle so we can dlclose it later

    return pGetFunctionList, pDynLib


# p11helper.c

# compat TODO
CKM_AES_KEY_WRAP = 0x2109
CKM_AES_KEY_WRAP_PAD = 0x210a

# TODO
CKA_COPYABLE = 0x0017

CKG_MGF1_SHA1 = 0x00000001

CKZ_DATA_SPECIFIED = 0x00000001

CK_RSA_PKCS_OAEP_PARAMS = _ffi.typeof('CK_RSA_PKCS_OAEP_PARAMS')


true_ptr = new_ptr(CK_BBOOL, CK_TRUE)
false_ptr = new_ptr(CK_BBOOL, CK_FALSE)

MAX_TEMPLATE_LEN = 32

#
# Constants
#
CONST_RSA_PKCS_OAEP_PARAMS_ptr = new_ptr(CK_RSA_PKCS_OAEP_PARAMS, dict(
    hash_alg=CKM_SHA_1,
    mgf=CKG_MGF1_SHA1,
    source=CKZ_DATA_SPECIFIED,
    source_data=NULL,
    source_data_len=0,
))


#
# ipap11helper Exceptions
#
class P11HelperException(Exception):
    """parent class for all exceptions"""

P11HelperException.__name__ = 'Exception'


class Error(P11HelperException):
    """general error"""


class NotFound(P11HelperException):
    """key not found"""


class DuplicationError(P11HelperException):
    """key already exists"""


########################################################################
# Support functions
#

def pyobj_to_bool(pyobj):
    if pyobj:
        return true_ptr
    return false_ptr


def convert_py2bool(mapping):
    return tuple(pyobj_to_bool(py_obj) for py_obj in mapping)


def string_to_pybytes_or_none(str, len):
    if str == NULL:
        return None
    return _ffi.buffer(str, len)[:]


def unicode_to_char_array(unicode):
    """
    Convert a unicode string to the utf8 encoded char array
    :param unicode: input python unicode object
    """
    try:
        utf8_str = unicode.encode('utf-8')
    except Exception:
        raise Error("Unable to encode UTF-8")
    try:
        result = new_array(unsigned_char, utf8_str)
    except Exception:
        raise Error("Unable to get bytes from string")
    l = len(utf8_str)
    return result, l


def char_array_to_unicode(array, l):
    """
    Convert utf-8 encoded char array to unicode object
    """
    return _ffi.buffer(array, l)[:].decode('utf-8')


def int_to_bytes(value):
    try:
        return binascii.unhexlify('{0:x}'.format(value))
    except (TypeError, binascii.Error):
        return binascii.unhexlify('0{0:x}'.format(value))


def bytes_to_int(value):
    return int(binascii.hexlify(value), 16)


def check_return_value(rv, message):
    """
    Tests result value of pkc11 operations
    """
    if rv != CKR_OK:
        try:
            errmsg = "Error at %s: 0x%x\n" % (message, rv)
        except Exception:
            raise Error("An error occured during error message generation. "
                        "Please report this problem. Developers will use "
                        "a crystal ball to find out the root cause.")
        else:
            raise Error(errmsg)


def _fill_template_from_parts(attr, template_len, id, id_len, label, label_len,
                              class_, cka_wrap, cka_unwrap):
    """
    Fill template structure with pointers to attributes passed as independent
    variables.
    Variables with NULL values will be omitted from template.

    @warning input variables should not be modified when template is in use
    """
    cnt = 0
    if label != NULL:
        attr[0].type = CKA_LABEL
        attr[0].pValue = label
        attr[0].ulValueLen = label_len
        attr += 1
        cnt += 1
        assert cnt < template_len[0]
    if id != NULL:
        attr[0].type = CKA_ID
        attr[0].pValue = id
        attr[0].ulValueLen = id_len
        attr += 1
        cnt += 1
        assert cnt < template_len[0]
    if cka_wrap != NULL:
        attr[0].type = CKA_WRAP
        attr[0].pValue = cka_wrap
        attr[0].ulValueLen = sizeof(CK_BBOOL)
        attr += 1
        cnt += 1
        assert cnt < template_len[0]
    if cka_unwrap != NULL:
        attr[0].type = CKA_UNWRAP
        attr[0].pValue = cka_unwrap
        attr[0].ulValueLen = sizeof(CK_BBOOL)
        attr += 1
        cnt += 1
        assert cnt < template_len[0]

    if class_ != NULL:
        attr[0].type = CKA_CLASS
        attr[0].pValue = class_
        attr[0].ulValueLen = sizeof(CK_OBJECT_CLASS)
        attr += 1
        cnt += 1
        assert cnt < template_len[0]
    template_len[0] = cnt


def _parse_uri(uri_str):
    """
    Parse string to P11-kit representation of PKCS#11 URI.
    """
    uri = p11_kit_uri_new()
    if not uri:
        raise Error("Cannot initialize URI parser")

    try:
        result = p11_kit_uri_parse(uri_str, P11_KIT_URI_FOR_OBJECT, uri)
        if result != P11_KIT_URI_OK:
            raise Error("Cannot parse URI")

        if p11_kit_uri_any_unrecognized(uri):
            raise Error("PKCS#11 URI contains unsupported attributes")
    except Error:
        p11_kit_uri_free(uri)
        raise

    return uri


def _set_wrapping_mech_parameters(mech_type, mech):
    """
    Function set default param values for wrapping mechanism
    :param mech_type: mechanism type
    :param mech: filled structure with params based on mech type

    Warning: do not dealloc param values, it is static variables
    """
    if mech_type in (CKM_RSA_PKCS, CKM_AES_KEY_WRAP, CKM_AES_KEY_WRAP_PAD):
        mech.pParameter = NULL
        mech.ulParameterLen = 0
    elif mech_type == CKM_RSA_PKCS_OAEP:
        # Use the same configuration as openSSL
        # https://www.openssl.org/docs/crypto/RSA_public_encrypt.html
        mech.pParameter = CONST_RSA_PKCS_OAEP_PARAMS_ptr
        mech.ulParameterLen = sizeof(CK_RSA_PKCS_OAEP_PARAMS)
    else:
        raise Error("Unsupported wrapping mechanism")
    mech.mechanism = mech_type


########################################################################
# P11_Helper object
#
class P11_Helper:
    @property
    def p11(self):
        return self.p11_ptr[0]

    @property
    def session(self):
        return self.session_ptr[0]

    def _find_key(self, template, template_len):
        """
        Find keys matching specified template.
        Function returns list of key handles via objects parameter.

        :param template: PKCS#11 template for attribute matching
        """
        result_objects = []
        result_object_ptr = new_ptr(CK_OBJECT_HANDLE)
        objectCount_ptr = new_ptr(CK_ULONG)

        rv = self.p11.C_FindObjectsInit(self.session, template, template_len)
        check_return_value(rv, "Find key init")

        rv = self.p11.C_FindObjects(self.session, result_object_ptr, 1,
                                    objectCount_ptr)
        check_return_value(rv, "Find key")

        while objectCount_ptr[0] > 0:
            result_objects.append(result_object_ptr[0])

            rv = self.p11.C_FindObjects(self.session, result_object_ptr, 1,
                                        objectCount_ptr)
            check_return_value(rv, "Check for duplicated key")

        rv = self.p11.C_FindObjectsFinal(self.session)
        check_return_value(rv, "Find objects final")

        return result_objects

    def _id_exists(self, id, id_len, class_):
        """
        Test if object with specified label, id and class exists

        :param id: key ID, (if value is NULL, will not be used to find key)
        :param id_len: key ID length
        :param class_ key: class

        :return: True if object was found, False if object doesnt exists
        """
        object_count_ptr = new_ptr(CK_ULONG)
        result_object_ptr = new_ptr(CK_OBJECT_HANDLE)
        class_ptr = new_ptr(CK_OBJECT_CLASS, class_)
        class_sec_ptr = new_ptr(CK_OBJECT_CLASS, CKO_SECRET_KEY)

        template_pub_priv = new_array(CK_ATTRIBUTE, (
            (CKA_ID, id, id_len),
            (CKA_CLASS, class_ptr, sizeof(CK_OBJECT_CLASS)),
        ))

        template_sec = new_array(CK_ATTRIBUTE, (
            (CKA_ID, id, id_len),
            (CKA_CLASS, class_sec_ptr, sizeof(CK_OBJECT_CLASS)),
        ))

        template_id = new_array(CK_ATTRIBUTE, (
            (CKA_ID, id, id_len),
        ))

        #
        # Only one secret key with same ID is allowed
        #
        if class_ == CKO_SECRET_KEY:
            rv = self.p11.C_FindObjectsInit(self.session, template_id, 1)
            check_return_value(rv, "id, label exists init")

            rv = self.p11.C_FindObjects(self.session, result_object_ptr, 1,
                                        object_count_ptr)
            check_return_value(rv, "id, label exists")

            rv = self.p11.C_FindObjectsFinal(self.session)
            check_return_value(rv, "id, label exists final")

            if object_count_ptr[0] > 0:
                return True
            return False

        #
        # Public and private keys can share one ID, but
        #

        # test if secret key with same ID exists
        rv = self.p11.C_FindObjectsInit(self.session, template_sec, 2)
        check_return_value(rv, "id, label exists init")

        rv = self.p11.C_FindObjects(self.session, result_object_ptr, 1,
                                    object_count_ptr)
        check_return_value(rv, "id, label exists")

        rv = self.p11.C_FindObjectsFinal(self.session)
        check_return_value(rv, "id, label exists final")

        if object_count_ptr[0] > 0:
            # object found
            return True

        # test if pub/private key with same id exists
        object_count_ptr[0] = 0

        rv = self.p11.C_FindObjectsInit(self.session, template_pub_priv, 2)
        check_return_value(rv, "id, label exists init")

        rv = self.p11.C_FindObjects(self.session, result_object_ptr, 1,
                                    object_count_ptr)
        check_return_value(rv, "id, label exists")

        rv = self.p11.C_FindObjectsFinal(self.session)
        check_return_value(rv, "id, label exists final")

        if object_count_ptr[0] > 0:
            # Object found
            return True

        # Object not found
        return False

    def __init__(self, token_label, user_pin, library_path):
        self.p11_ptr = new_ptr(CK_FUNCTION_LIST_PTR)
        self.session_ptr = new_ptr(CK_SESSION_HANDLE)

        self.session_ptr[0] = 0
        self.p11_ptr[0] = NULL
        self.module_handle = None

        # Parse method args
        if isinstance(user_pin, unicode):
            user_pin = user_pin.encode()
        self.token_label = token_label

        try:
            pGetFunctionList, module_handle = loadLibrary(library_path)
        except Exception:
            raise Error(f"Could not load the library '{library_path}'.")

        self.module_handle = module_handle

        #
        # Load the function list
        #
        pGetFunctionList(self.p11_ptr)

        #
        # Initialize
        #
        rv = self.p11.C_Initialize(NULL)
        check_return_value(rv, "initialize")

        #
        # Get Slot
        #
        slot = self.get_slot()
        if slot is None:
            raise Error("No slot for label {} found".format(self.token_label))

        #
        # Start session
        #
        rv = self.p11.C_OpenSession(slot,
                                    CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL,
                                    NULL, self.session_ptr)
        check_return_value(rv, "open session")

        #
        # Login
        #
        rv = self.p11.C_Login(self.session, CKU_USER, user_pin, len(user_pin))
        check_return_value(rv, "log in")

    def get_slot(self):
        """Get slot where then token is located
        :return: slot number or None when slot not found
        """
        object_count_ptr = new_ptr(CK_ULONG)

        # get slots ID
        slots = None
        for _i in range(0, 10):
            # try max N times, then die to avoid infinite iteration
            rv = self.p11.C_GetSlotList(CK_TRUE, NULL, object_count_ptr)
            check_return_value(rv, "get slots IDs - prepare")

            result_ids_ptr = new_array(CK_SLOT_ID, object_count_ptr[0])

            rv = self.p11.C_GetSlotList(
                CK_TRUE, result_ids_ptr, object_count_ptr)
            if rv == CKR_BUFFER_TOO_SMALL:
                continue
            check_return_value(rv, "get slots IDs")
            slots = result_ids_ptr
            break  # we have slots !!!

        if slots is None:
            raise Error("Failed to get slots")

        for slot in slots:
            token_info_ptr = new_ptr(CK_TOKEN_INFO)
            rv = self.p11.C_GetTokenInfo(slot, token_info_ptr)
            check_return_value(rv, 'get token info')

            # softhsm always returns label 32 bytes long with padding made of
            # whitespaces (#32), so we have to rstrip() padding and compare
            # Label was created by softhsm-util so it is not our fault that
            # there are #32 as padding (cffi initializes structures with
            # zeroes)
            # In case that this is not valid anymore, keep in mind backward
            # compatibility

            if self.token_label == char_array_to_unicode(
                    token_info_ptr[0].label, 32).rstrip():
                return slot
        return None

    def finalize(self):
        """
        Finalize operations with pkcs11 library
        """
        if self.p11 == NULL:
            return

        #
        # Logout
        #
        rv = self.p11.C_Logout(self.session)
        check_return_value(rv, "log out")

        #
        # End session
        #
        rv = self.p11.C_CloseSession(self.session)
        check_return_value(rv, "close session")

        #
        # Finalize
        #
        self.p11.C_Finalize(NULL)

        self.p11_ptr[0] = NULL
        self.session_ptr[0] = 0
        self.module_handle = None

    #################################################################
    # Methods working with keys
    #

    def generate_master_key(self, label, id, key_length=16, cka_copyable=True,
                            cka_decrypt=False, cka_derive=False,
                            cka_encrypt=False, cka_extractable=True,
                            cka_modifiable=True, cka_private=True,
                            cka_sensitive=True, cka_sign=False,
                            cka_unwrap=True, cka_verify=False, cka_wrap=True,
                            cka_wrap_with_trusted=False):
        """
        Generate master key

        :return: master key handle
        """
        if isinstance(id, unicode):
            id = id.encode()

        attrs = (
            cka_copyable,
            cka_decrypt,
            cka_derive,
            cka_encrypt,
            cka_extractable,
            cka_modifiable,
            cka_private,
            cka_sensitive,
            cka_sign,
            cka_unwrap,
            cka_verify,
            cka_wrap,
            cka_wrap_with_trusted,
        )

        key_length_ptr = new_ptr(CK_ULONG, key_length)
        master_key_ptr = new_ptr(CK_OBJECT_HANDLE)

        label_unicode = label
        id_length = len(id)
        id_ = new_array(CK_BYTE, id)
        # TODO check long overflow

        label, label_length = unicode_to_char_array(label_unicode)

        # TODO param?
        mechanism_ptr = new_ptr(CK_MECHANISM, (
            CKM_AES_KEY_GEN, NULL_PTR, 0
        ))

        if key_length not in (16, 24, 32):
            raise Error("generate_master_key: key length allowed values are: "
                        "16, 24 and 32")

        if self._id_exists(id_, id_length, CKO_SECRET_KEY):
            raise DuplicationError("Master key with same ID already exists")

        # Process keyword boolean arguments
        (_cka_copyable_ptr, cka_decrypt_ptr, cka_derive_ptr, cka_encrypt_ptr,
         cka_extractable_ptr, cka_modifiable_ptr, cka_private_ptr,
         cka_sensitive_ptr, cka_sign_ptr, cka_unwrap_ptr, cka_verify_ptr,
         cka_wrap_ptr, cka_wrap_with_trusted_ptr,) = convert_py2bool(attrs)

        symKeyTemplate = new_array(CK_ATTRIBUTE, (
            (CKA_ID, id_, id_length),
            (CKA_LABEL, label, label_length),
            (CKA_TOKEN, true_ptr, sizeof(CK_BBOOL)),
            (CKA_VALUE_LEN, key_length_ptr, sizeof(CK_ULONG)),
            # TODO Softhsm doesn't support it
            # (CKA_COPYABLE, cka_copyable_ptr, sizeof(CK_BBOOL)),
            (CKA_DECRYPT, cka_decrypt_ptr, sizeof(CK_BBOOL)),
            (CKA_DERIVE, cka_derive_ptr, sizeof(CK_BBOOL)),
            (CKA_ENCRYPT, cka_encrypt_ptr, sizeof(CK_BBOOL)),
            (CKA_EXTRACTABLE, cka_extractable_ptr, sizeof(CK_BBOOL)),
            (CKA_MODIFIABLE, cka_modifiable_ptr, sizeof(CK_BBOOL)),
            (CKA_PRIVATE, cka_private_ptr, sizeof(CK_BBOOL)),
            (CKA_SENSITIVE, cka_sensitive_ptr, sizeof(CK_BBOOL)),
            (CKA_SIGN, cka_sign_ptr, sizeof(CK_BBOOL)),
            (CKA_UNWRAP, cka_unwrap_ptr, sizeof(CK_BBOOL)),
            (CKA_VERIFY, cka_verify_ptr, sizeof(CK_BBOOL)),
            (CKA_WRAP, cka_wrap_ptr, sizeof(CK_BBOOL)),
            (CKA_WRAP_WITH_TRUSTED, cka_wrap_with_trusted_ptr,
             sizeof(CK_BBOOL)),
        ))

        rv = self.p11.C_GenerateKey(self.session, mechanism_ptr,
                                    symKeyTemplate,
                                    (sizeof(symKeyTemplate) //
                                     sizeof(CK_ATTRIBUTE)), master_key_ptr)
        check_return_value(rv, "generate master key")

        return master_key_ptr[0]

    def generate_replica_key_pair(self, label, id, modulus_bits=2048,
                                  pub_cka_copyable=True, pub_cka_derive=False,
                                  pub_cka_encrypt=False,
                                  pub_cka_modifiable=True,
                                  pub_cka_private=True, pub_cka_trusted=False,
                                  pub_cka_verify=False,
                                  pub_cka_verify_recover=False,
                                  pub_cka_wrap=True,
                                  priv_cka_always_authenticate=False,
                                  priv_cka_copyable=True,
                                  priv_cka_decrypt=False,
                                  priv_cka_derive=False,
                                  priv_cka_extractable=False,
                                  priv_cka_modifiable=True,
                                  priv_cka_private=True,
                                  priv_cka_sensitive=True,
                                  priv_cka_sign=False,
                                  priv_cka_sign_recover=False,
                                  priv_cka_unwrap=True,
                                  priv_cka_wrap_with_trusted=False):
        """
        Generate replica keys

        :returns: tuple (public_key_handle, private_key_handle)
        """
        if isinstance(id, unicode):
            id = id.encode()

        attrs_pub = (
            pub_cka_copyable,
            pub_cka_derive,
            pub_cka_encrypt,
            pub_cka_modifiable,
            pub_cka_private,
            pub_cka_trusted,
            pub_cka_verify,
            pub_cka_verify_recover,
            pub_cka_wrap,
        )

        attrs_priv = (
            priv_cka_always_authenticate,
            priv_cka_copyable,
            priv_cka_decrypt,
            priv_cka_derive,
            priv_cka_extractable,
            priv_cka_modifiable,
            priv_cka_private,
            priv_cka_sensitive,
            priv_cka_sign,
            priv_cka_sign_recover,
            priv_cka_unwrap,
            priv_cka_wrap_with_trusted,
        )

        label_unicode = label
        id_ = new_array(CK_BYTE, id)
        id_length = len(id)

        label, label_length = unicode_to_char_array(label_unicode)

        public_key_ptr = new_ptr(CK_OBJECT_HANDLE)
        private_key_ptr = new_ptr(CK_OBJECT_HANDLE)
        mechanism_ptr = new_ptr(CK_MECHANISM,
                                (CKM_RSA_PKCS_KEY_PAIR_GEN, NULL_PTR, 0))

        if self._id_exists(id_, id_length, CKO_PRIVATE_KEY):
            raise DuplicationError("Private key with same ID already exists")

        if self._id_exists(id_, id_length, CKO_PUBLIC_KEY):
            raise DuplicationError("Public key with same ID already exists")

        modulus_bits_ptr = new_ptr(CK_ULONG, modulus_bits)

        # Process keyword boolean arguments
        (_pub_cka_copyable_ptr, pub_cka_derive_ptr, pub_cka_encrypt_ptr,
         pub_cka_modifiable_ptr, pub_cka_private_ptr, pub_cka_trusted_ptr,
         pub_cka_verify_ptr, pub_cka_verify_recover_ptr, pub_cka_wrap_ptr,
         ) = convert_py2bool(attrs_pub)
        (priv_cka_always_authenticate_ptr, _priv_cka_copyable_ptr,
         priv_cka_decrypt_ptr, priv_cka_derive_ptr, priv_cka_extractable_ptr,
         priv_cka_modifiable_ptr, priv_cka_private_ptr, priv_cka_sensitive_ptr,
         priv_cka_sign_ptr, _priv_cka_sign_recover_ptr, priv_cka_unwrap_ptr,
         priv_cka_wrap_with_trusted_ptr,) = convert_py2bool(attrs_priv)

        # 65537 (RFC 6376 section 3.3.1)
        public_exponent = new_array(CK_BYTE, (1, 0, 1))
        publicKeyTemplate = new_array(CK_ATTRIBUTE, (
            (CKA_ID, id_, id_length),
            (CKA_LABEL, label, label_length),
            (CKA_TOKEN, true_ptr, sizeof(CK_BBOOL)),
            (CKA_MODULUS_BITS, modulus_bits_ptr, sizeof(CK_ULONG)),
            (CKA_PUBLIC_EXPONENT, public_exponent, 3),
            # TODO Softhsm doesn't support it
            # (CKA_COPYABLE, pub_cka_copyable_p, sizeof(CK_BBOOL)),
            (CKA_DERIVE, pub_cka_derive_ptr, sizeof(CK_BBOOL)),
            (CKA_ENCRYPT, pub_cka_encrypt_ptr, sizeof(CK_BBOOL)),
            (CKA_MODIFIABLE, pub_cka_modifiable_ptr, sizeof(CK_BBOOL)),
            (CKA_PRIVATE, pub_cka_private_ptr, sizeof(CK_BBOOL)),
            (CKA_TRUSTED, pub_cka_trusted_ptr, sizeof(CK_BBOOL)),
            (CKA_VERIFY, pub_cka_verify_ptr, sizeof(CK_BBOOL)),
            (CKA_VERIFY_RECOVER, pub_cka_verify_recover_ptr, sizeof(CK_BBOOL)),
            (CKA_WRAP, pub_cka_wrap_ptr, sizeof(CK_BBOOL)),
        ))

        privateKeyTemplate = new_array(CK_ATTRIBUTE, (
            (CKA_ID, id_, id_length),
            (CKA_LABEL, label, label_length),
            (CKA_TOKEN, true_ptr, sizeof(CK_BBOOL)),
            (CKA_ALWAYS_AUTHENTICATE, priv_cka_always_authenticate_ptr,
             sizeof(CK_BBOOL)),
            # TODO Softhsm doesn't support it
            # (CKA_COPYABLE, priv_cka_copyable_ptr, sizeof(CK_BBOOL)),
            (CKA_DECRYPT, priv_cka_decrypt_ptr, sizeof(CK_BBOOL)),
            (CKA_DERIVE,  priv_cka_derive_ptr, sizeof(CK_BBOOL)),
            (CKA_EXTRACTABLE, priv_cka_extractable_ptr, sizeof(CK_BBOOL)),
            (CKA_MODIFIABLE, priv_cka_modifiable_ptr, sizeof(CK_BBOOL)),
            (CKA_PRIVATE, priv_cka_private_ptr, sizeof(CK_BBOOL)),
            (CKA_SENSITIVE, priv_cka_sensitive_ptr, sizeof(CK_BBOOL)),
            (CKA_SIGN, priv_cka_sign_ptr, sizeof(CK_BBOOL)),
            (CKA_SIGN_RECOVER, priv_cka_sign_ptr, sizeof(CK_BBOOL)),
            (CKA_UNWRAP, priv_cka_unwrap_ptr, sizeof(CK_BBOOL)),
            (CKA_WRAP_WITH_TRUSTED, priv_cka_wrap_with_trusted_ptr,
             sizeof(CK_BBOOL)),
        ))

        rv = self.p11.C_GenerateKeyPair(self.session, mechanism_ptr,
                                        publicKeyTemplate,
                                        (sizeof(publicKeyTemplate) //
                                         sizeof(CK_ATTRIBUTE)),
                                        privateKeyTemplate,
                                        (sizeof(privateKeyTemplate) //
                                         sizeof(CK_ATTRIBUTE)),
                                        public_key_ptr,
                                        private_key_ptr)
        check_return_value(rv, "generate key pair")

        return public_key_ptr[0], private_key_ptr[0]

    def find_keys(self, objclass=CKO_VENDOR_DEFINED, label=None, id=None,
                  cka_wrap=None, cka_unwrap=None, uri=None):
        """
        Find key
        """
        if isinstance(id, unicode):
            id = id.encode()
        if isinstance(uri, unicode):
            uri = uri.encode()

        class_ = objclass
        class_ptr = new_ptr(CK_OBJECT_CLASS, class_)
        ckawrap = NULL
        ckaunwrap = NULL
        if id is not None:
            id_ = new_array(CK_BYTE, id)
            id_length = len(id)
        else:
            id_ = NULL
            id_length = 0
        label_unicode, label = label, NULL
        cka_wrap_bool = cka_wrap
        cka_unwrap_bool = cka_unwrap
        label_length = 0
        uri_str = uri
        uri = NULL
        template = new_array(CK_ATTRIBUTE, MAX_TEMPLATE_LEN)
        template_len_ptr = new_ptr(CK_ULONG, MAX_TEMPLATE_LEN)

        # TODO check long overflow

        if label_unicode is not None:
            label, label_length = unicode_to_char_array(label_unicode)

        if cka_wrap_bool is not None:
            if cka_wrap_bool:
                ckawrap = true_ptr
            else:
                ckawrap = false_ptr

        if cka_unwrap_bool is not None:
            if cka_unwrap_bool:
                ckaunwrap = true_ptr
            else:
                ckaunwrap = false_ptr

        if class_ == CKO_VENDOR_DEFINED:
            class_ptr = NULL

        try:
            if uri_str is None:
                _fill_template_from_parts(template, template_len_ptr, id_,
                                          id_length, label, label_length,
                                          class_ptr, ckawrap, ckaunwrap)
            else:
                uri = _parse_uri(uri_str)
                template = (p11_kit_uri_get_attributes(uri, template_len_ptr))
                # Do not deallocate URI while you are using the template.
                # Template contains pointers to values inside URI!

            result_list = self._find_key(template, template_len_ptr[0])

            return result_list
        finally:
            if uri != NULL:
                p11_kit_uri_free(uri)

    def delete_key(self, key_handle):
        """
        delete key
        """
        # TODO check long overflow
        rv = self.p11.C_DestroyObject(self.session, key_handle)
        check_return_value(rv, "object deletion")

    def _export_RSA_public_key(self, object):
        """
        export RSA public key
        """
        class_ptr = new_ptr(CK_OBJECT_CLASS, CKO_PUBLIC_KEY)
        key_type_ptr = new_ptr(CK_KEY_TYPE, CKK_RSA)

        obj_template = new_array(CK_ATTRIBUTE, (
            (CKA_MODULUS, NULL_PTR, 0),
            (CKA_PUBLIC_EXPONENT, NULL_PTR, 0),
            (CKA_CLASS, class_ptr, sizeof(CK_OBJECT_CLASS)),
            (CKA_KEY_TYPE, key_type_ptr, sizeof(CK_KEY_TYPE)),
        ))

        rv = self.p11.C_GetAttributeValue(self.session, object, obj_template,
                                          (sizeof(obj_template) //
                                           sizeof(CK_ATTRIBUTE)))
        check_return_value(rv, "get RSA public key values - prepare")

        # Set proper size for attributes
        modulus = new_array(CK_BYTE,
                            obj_template[0].ulValueLen * sizeof(CK_BYTE))
        obj_template[0].pValue = modulus
        exponent = new_array(CK_BYTE,
                             obj_template[1].ulValueLen * sizeof(CK_BYTE))
        obj_template[1].pValue = exponent

        rv = self.p11.C_GetAttributeValue(self.session, object, obj_template,
                                          (sizeof(obj_template) //
                                           sizeof(CK_ATTRIBUTE)))
        check_return_value(rv, "get RSA public key values")

        # Check if the key is RSA public key
        if class_ptr[0] != CKO_PUBLIC_KEY:
            raise Error("export_RSA_public_key: required public key class")

        if key_type_ptr[0] != CKK_RSA:
            raise Error("export_RSA_public_key: required RSA key type")

        try:
            n = bytes_to_int(string_to_pybytes_or_none(
                modulus, obj_template[0].ulValueLen))
        except Exception:
            raise Error("export_RSA_public_key: internal error: unable to "
                        "convert modulus")

        try:
            e = bytes_to_int(string_to_pybytes_or_none(
                exponent, obj_template[1].ulValueLen))
        except Exception:
            raise Error("export_RSA_public_key: internal error: unable to "
                        "convert exponent")

        # set modulus and exponent
        rsa_ = rsa.RSAPublicNumbers(e, n)

        try:
            pkey = rsa_.public_key(default_backend())
        except Exception:
            raise Error("export_RSA_public_key: internal error: "
                        "EVP_PKEY_set1_RSA failed")

        try:
            ret = pkey.public_bytes(
                format=serialization.PublicFormat.SubjectPublicKeyInfo,
                encoding=serialization.Encoding.DER,
            )
        except Exception:
            ret = None

        return ret

    def export_public_key(self, key_handle):
        """
        Export public key

        Export public key in SubjectPublicKeyInfo (RFC5280) DER encoded format
        """
        object = key_handle
        class_ptr = new_ptr(CK_OBJECT_CLASS, CKO_PUBLIC_KEY)
        key_type_ptr = new_ptr(CK_KEY_TYPE, CKK_RSA)
        # TODO check long overflow

        obj_template = new_array(CK_ATTRIBUTE, (
            (CKA_CLASS, class_ptr, sizeof(CK_OBJECT_CLASS)),
            (CKA_KEY_TYPE, key_type_ptr, sizeof(CK_KEY_TYPE)),
        ))

        rv = self.p11.C_GetAttributeValue(self.session, object, obj_template,
                                          (sizeof(obj_template) //
                                           sizeof(CK_ATTRIBUTE)))
        check_return_value(rv, "export_public_key: get RSA public key values")

        if class_ptr[0] != CKO_PUBLIC_KEY:
            raise Error("export_public_key: required public key class")

        if key_type_ptr[0] == CKK_RSA:
            return self._export_RSA_public_key(object)
        else:
            raise Error("export_public_key: unsupported key type")

    def _import_RSA_public_key(self, label, label_length, id, id_length, pkey,
                               cka_copyable, cka_derive, cka_encrypt,
                               cka_modifiable, cka_private, cka_trusted,
                               cka_verify, cka_verify_recover, cka_wrap):
        """
        Import RSA public key
        """
        class_ptr = new_ptr(CK_OBJECT_CLASS, CKO_PUBLIC_KEY)
        keyType_ptr = new_ptr(CK_KEY_TYPE, CKK_RSA)
        cka_token = true_ptr

        if not isinstance(pkey, rsa.RSAPublicKey):
            raise Error("Required RSA public key")

        rsa_ = pkey.public_numbers()

        # convert BIGNUM to binary array
        modulus = new_array(CK_BYTE, int_to_bytes(rsa_.n))
        modulus_len = sizeof(modulus) - 1
        if modulus_len == 0:
            raise Error("import_RSA_public_key: BN_bn2bin modulus error")

        exponent = new_array(CK_BYTE, int_to_bytes(rsa_.e))
        exponent_len = sizeof(exponent) - 1
        if exponent_len == 0:
            raise Error("import_RSA_public_key: BN_bn2bin exponent error")

        template = new_array(CK_ATTRIBUTE, (
            (CKA_ID, id, id_length),
            (CKA_CLASS, class_ptr, sizeof(CK_OBJECT_CLASS)),
            (CKA_KEY_TYPE, keyType_ptr, sizeof(CK_KEY_TYPE)),
            (CKA_TOKEN, cka_token, sizeof(CK_BBOOL)),
            (CKA_LABEL, label, label_length),
            (CKA_MODULUS, modulus, modulus_len),
            (CKA_PUBLIC_EXPONENT, exponent, exponent_len),
            # TODO Softhsm doesn't support it
            # (CKA_COPYABLE, cka_copyable, sizeof(CK_BBOOL)),
            (CKA_DERIVE, cka_derive, sizeof(CK_BBOOL)),
            (CKA_ENCRYPT, cka_encrypt, sizeof(CK_BBOOL)),
            (CKA_MODIFIABLE, cka_modifiable, sizeof(CK_BBOOL)),
            (CKA_PRIVATE, cka_private, sizeof(CK_BBOOL)),
            (CKA_TRUSTED, cka_trusted, sizeof(CK_BBOOL)),
            (CKA_VERIFY, cka_verify, sizeof(CK_BBOOL)),
            (CKA_VERIFY_RECOVER, cka_verify_recover, sizeof(CK_BBOOL)),
            (CKA_WRAP, cka_wrap, sizeof(CK_BBOOL)),
        ))
        object_ptr = new_ptr(CK_OBJECT_HANDLE)

        rv = self.p11.C_CreateObject(self.session, template,
                                     (sizeof(template) //
                                      sizeof(CK_ATTRIBUTE)), object_ptr)
        check_return_value(rv, "create public key object")

        return object_ptr[0]

    def import_public_key(self, label, id, data, cka_copyable=True,
                          cka_derive=False, cka_encrypt=False,
                          cka_modifiable=True, cka_private=True,
                          cka_trusted=False, cka_verify=True,
                          cka_verify_recover=True, cka_wrap=False):
        """
        Import RSA public key
        """
        if isinstance(id, unicode):
            id = id.encode()
        if isinstance(data, unicode):
            data = data.encode()

        label_unicode = label
        id_ = new_array(CK_BYTE, id)
        id_length = len(id)

        attrs_pub = (
            cka_copyable,
            cka_derive,
            cka_encrypt,
            cka_modifiable,
            cka_private,
            cka_trusted,
            cka_verify,
            cka_verify_recover,
            cka_wrap,
        )

        label, label_length = unicode_to_char_array(label_unicode)

        if self._id_exists(id_, id_length, CKO_PUBLIC_KEY):
            raise DuplicationError("Public key with same ID already exists")

        # Process keyword boolean arguments
        (cka_copyable_ptr, cka_derive_ptr, cka_encrypt_ptr, cka_modifiable_ptr,
         cka_private_ptr, cka_trusted_ptr, cka_verify_ptr,
         cka_verify_recover_ptr, cka_wrap_ptr,) = convert_py2bool(attrs_pub)

        # decode from ASN1 DER
        try:
            pkey = serialization.load_der_public_key(data, default_backend())
        except Exception:
            raise Error("import_public_key: d2i_PUBKEY error")
        if isinstance(pkey, rsa.RSAPublicKey):
            ret = self._import_RSA_public_key(label, label_length, id_,
                                              id_length, pkey,
                                              cka_copyable_ptr,
                                              cka_derive_ptr,
                                              cka_encrypt_ptr,
                                              cka_modifiable_ptr,
                                              cka_private_ptr,
                                              cka_trusted_ptr,
                                              cka_verify_ptr,
                                              cka_verify_recover_ptr,
                                              cka_wrap_ptr)
        elif isinstance(pkey, dsa.DSAPublicKey):
            raise Error("DSA is not supported")
        elif isinstance(pkey, ec.EllipticCurvePublicKey):
            raise Error("EC is not supported")
        else:
            raise Error("Unsupported key type")

        return ret

    def export_wrapped_key(self, key, wrapping_key, wrapping_mech):
        """
        Export wrapped key
        """
        object_key = key
        object_wrapping_key = wrapping_key
        wrapped_key_len_ptr = new_ptr(CK_ULONG, 0)
        wrapping_mech_ptr = new_ptr(CK_MECHANISM, (wrapping_mech, NULL, 0))
        # currently we don't support parameter in mechanism

        # TODO check long overflow
        # TODO export method

        # fill mech parameters
        _set_wrapping_mech_parameters(wrapping_mech_ptr.mechanism,
                                      wrapping_mech_ptr)

        rv = self.p11.C_WrapKey(self.session, wrapping_mech_ptr,
                                object_wrapping_key, object_key, NULL,
                                wrapped_key_len_ptr)
        check_return_value(rv, "key wrapping: get buffer length")

        wrapped_key = new_array(CK_BYTE, wrapped_key_len_ptr[0])

        rv = self.p11.C_WrapKey(self.session, wrapping_mech_ptr,
                                object_wrapping_key, object_key, wrapped_key,
                                wrapped_key_len_ptr)
        check_return_value(rv, "key wrapping: wrapping")

        result = string_to_pybytes_or_none(wrapped_key, wrapped_key_len_ptr[0])

        return result

    def import_wrapped_secret_key(self, label, id, data, unwrapping_key,
                                  wrapping_mech, key_type, cka_copyable=True,
                                  cka_decrypt=False, cka_derive=False,
                                  cka_encrypt=False, cka_extractable=True,
                                  cka_modifiable=True, cka_private=True,
                                  cka_sensitive=True, cka_sign=False,
                                  cka_unwrap=True, cka_verify=False,
                                  cka_wrap=True, cka_wrap_with_trusted=False):
        """
        Import wrapped secret key
        """
        if isinstance(id, unicode):
            id = id.encode()
        if isinstance(data, unicode):
            data = data.encode()

        wrapped_key = new_array(CK_BYTE, data)
        wrapped_key_len = len(data)
        unwrapping_key_object = unwrapping_key
        unwrapped_key_object_ptr = new_ptr(CK_OBJECT_HANDLE, 0)
        label_unicode = label
        id_ = new_array(CK_BYTE, id)
        id_length = len(id)
        wrapping_mech_ptr = new_ptr(CK_MECHANISM, (wrapping_mech, NULL, 0))
        key_class_ptr = new_ptr(CK_OBJECT_CLASS, CKO_SECRET_KEY)
        key_type_ptr = new_ptr(CK_KEY_TYPE, key_type)

        attrs = (
            cka_copyable,
            cka_decrypt,
            cka_derive,
            cka_encrypt,
            cka_extractable,
            cka_modifiable,
            cka_private,
            cka_sensitive,
            cka_sign,
            cka_unwrap,
            cka_verify,
            cka_wrap,
            cka_wrap_with_trusted,
        )

        _set_wrapping_mech_parameters(wrapping_mech_ptr.mechanism,
                                      wrapping_mech_ptr)

        label, label_length = unicode_to_char_array(label_unicode)

        if self._id_exists(id_, id_length, key_class_ptr[0]):
            raise DuplicationError("Secret key with same ID already exists")

        # Process keyword boolean arguments
        (_cka_copyable_ptr, cka_decrypt_ptr, cka_derive_ptr, cka_encrypt_ptr,
         cka_extractable_ptr, cka_modifiable_ptr, cka_private_ptr,
         cka_sensitive_ptr, cka_sign_ptr, cka_unwrap_ptr, cka_verify_ptr,
         cka_wrap_ptr, cka_wrap_with_trusted_ptr,) = convert_py2bool(attrs)

        template = new_array(CK_ATTRIBUTE, (
            (CKA_CLASS, key_class_ptr, sizeof(CK_OBJECT_CLASS)),
            (CKA_KEY_TYPE, key_type_ptr, sizeof(CK_KEY_TYPE)),
            (CKA_ID, id_, id_length),
            (CKA_LABEL, label, label_length),
            (CKA_TOKEN, true_ptr, sizeof(CK_BBOOL)),
            # TODO Softhsm doesn't support it
            # (CKA_COPYABLE, cka_copyable_ptr, sizeof(CK_BBOOL)),
            (CKA_DECRYPT, cka_decrypt_ptr, sizeof(CK_BBOOL)),
            (CKA_DERIVE, cka_derive_ptr, sizeof(CK_BBOOL)),
            (CKA_ENCRYPT, cka_encrypt_ptr, sizeof(CK_BBOOL)),
            (CKA_EXTRACTABLE, cka_extractable_ptr, sizeof(CK_BBOOL)),
            (CKA_MODIFIABLE, cka_modifiable_ptr, sizeof(CK_BBOOL)),
            (CKA_PRIVATE, cka_private_ptr, sizeof(CK_BBOOL)),
            (CKA_SENSITIVE, cka_sensitive_ptr, sizeof(CK_BBOOL)),
            (CKA_SIGN, cka_sign_ptr, sizeof(CK_BBOOL)),
            (CKA_UNWRAP, cka_unwrap_ptr, sizeof(CK_BBOOL)),
            (CKA_VERIFY, cka_verify_ptr, sizeof(CK_BBOOL)),
            (CKA_WRAP, cka_wrap_ptr, sizeof(CK_BBOOL)),
            (CKA_WRAP_WITH_TRUSTED, cka_wrap_with_trusted_ptr,
             sizeof(CK_BBOOL)),
        ))

        rv = self.p11.C_UnwrapKey(self.session, wrapping_mech_ptr,
                                  unwrapping_key_object, wrapped_key,
                                  wrapped_key_len, template,
                                  sizeof(template) // sizeof(CK_ATTRIBUTE),
                                  unwrapped_key_object_ptr)
        check_return_value(rv, "import_wrapped_key: key unwrapping")

        return unwrapped_key_object_ptr[0]

    def import_wrapped_private_key(self, label, id, data, unwrapping_key,
                                   wrapping_mech, key_type,
                                   cka_always_authenticate=False,
                                   cka_copyable=True, cka_decrypt=False,
                                   cka_derive=False, cka_extractable=True,
                                   cka_modifiable=True, cka_private=True,
                                   cka_sensitive=True, cka_sign=True,
                                   cka_sign_recover=True, cka_unwrap=False,
                                   cka_wrap_with_trusted=False):
        """
        Import wrapped private key
        """
        if isinstance(id, unicode):
            id = id.encode()
        if isinstance(data, unicode):
            data = data.encode()

        wrapped_key = new_array(CK_BYTE, data)
        wrapped_key_len = len(data)
        unwrapping_key_object = unwrapping_key
        unwrapped_key_object_ptr = new_ptr(CK_OBJECT_HANDLE, 0)
        label_unicode = label
        id_ = new_array(CK_BYTE, id)
        id_length = len(id)
        wrapping_mech_ptr = new_ptr(CK_MECHANISM, (wrapping_mech, NULL, 0))
        key_class_ptr = new_ptr(CK_OBJECT_CLASS, CKO_PRIVATE_KEY)
        key_type_ptr = new_ptr(CK_KEY_TYPE, key_type)

        attrs_priv = (
            cka_always_authenticate,
            cka_copyable,
            cka_decrypt,
            cka_derive,
            cka_extractable,
            cka_modifiable,
            cka_private,
            cka_sensitive,
            cka_sign,
            cka_sign_recover,
            cka_unwrap,
            cka_wrap_with_trusted,
        )

        label, label_length = unicode_to_char_array(label_unicode)

        if self._id_exists(id_, id_length, CKO_SECRET_KEY):
            raise DuplicationError("Secret key with same ID already exists")

        # Process keyword boolean arguments
        (cka_always_authenticate_ptr, _cka_copyable_ptr, cka_decrypt_ptr,
         cka_derive_ptr, cka_extractable_ptr, cka_modifiable_ptr,
         cka_private_ptr, cka_sensitive_ptr, cka_sign_ptr,
         _cka_sign_recover_ptr, cka_unwrap_ptr, cka_wrap_with_trusted_ptr,
         ) = convert_py2bool(attrs_priv)

        template = new_array(CK_ATTRIBUTE, (
            (CKA_CLASS, key_class_ptr, sizeof(CK_OBJECT_CLASS)),
            (CKA_KEY_TYPE, key_type_ptr, sizeof(CK_KEY_TYPE)),
            (CKA_ID, id_, id_length),
            (CKA_LABEL, label, label_length),
            (CKA_TOKEN, true_ptr, sizeof(CK_BBOOL)),
            (CKA_ALWAYS_AUTHENTICATE, cka_always_authenticate_ptr,
             sizeof(CK_BBOOL)),
            # TODO Softhsm doesn't support it
            # (CKA_COPYABLE, cka_copyable_ptr, sizeof(CK_BBOOL)),
            (CKA_DECRYPT, cka_decrypt_ptr, sizeof(CK_BBOOL)),
            (CKA_DERIVE, cka_derive_ptr, sizeof(CK_BBOOL)),
            (CKA_EXTRACTABLE, cka_extractable_ptr, sizeof(CK_BBOOL)),
            (CKA_MODIFIABLE,  cka_modifiable_ptr, sizeof(CK_BBOOL)),
            (CKA_PRIVATE, cka_private_ptr, sizeof(CK_BBOOL)),
            (CKA_SENSITIVE, cka_sensitive_ptr, sizeof(CK_BBOOL)),
            (CKA_SIGN, cka_sign_ptr, sizeof(CK_BBOOL)),
            (CKA_SIGN_RECOVER, cka_sign_ptr, sizeof(CK_BBOOL)),
            (CKA_UNWRAP, cka_unwrap_ptr, sizeof(CK_BBOOL)),
            (CKA_WRAP_WITH_TRUSTED, cka_wrap_with_trusted_ptr,
             sizeof(CK_BBOOL)),
        ))

        rv = self.p11.C_UnwrapKey(self.session, wrapping_mech_ptr,
                                  unwrapping_key_object, wrapped_key,
                                  wrapped_key_len, template,
                                  sizeof(template) // sizeof(CK_ATTRIBUTE),
                                  unwrapped_key_object_ptr)
        check_return_value(rv, "import_wrapped_key: key unwrapping")

        return unwrapped_key_object_ptr[0]

    def set_attribute(self, key_object, attr, value):
        """
        Set object attributes
        """
        object = key_object
        attribute_ptr = new_ptr(CK_ATTRIBUTE)

        attribute_ptr.type = attr
        if attr in (CKA_ALWAYS_AUTHENTICATE,
                    CKA_ALWAYS_SENSITIVE,
                    CKA_COPYABLE,
                    CKA_ENCRYPT,
                    CKA_EXTRACTABLE,
                    CKA_DECRYPT,
                    CKA_DERIVE,
                    CKA_LOCAL,
                    CKA_MODIFIABLE,
                    CKA_NEVER_EXTRACTABLE,
                    CKA_PRIVATE,
                    CKA_SENSITIVE,
                    CKA_SIGN,
                    CKA_SIGN_RECOVER,
                    CKA_TOKEN,
                    CKA_TRUSTED,
                    CKA_UNWRAP,
                    CKA_VERIFY,
                    CKA_VERIFY_RECOVER,
                    CKA_WRAP,
                    CKA_WRAP_WITH_TRUSTED):
            attribute_ptr.pValue = true_ptr if value else false_ptr
            attribute_ptr.ulValueLen = sizeof(CK_BBOOL)
        elif attr == CKA_ID:
            if not isinstance(value, bytes):
                raise Error("Bytestring value expected")
            attribute_ptr.pValue = new_array(CK_BYTE, value)
            attribute_ptr.ulValueLen = len(value)
        elif attr == CKA_LABEL:
            if not isinstance(value, unicode):
                raise Error("Unicode value expected")
            label, label_length = unicode_to_char_array(value)
            attribute_ptr.pValue = label
            attribute_ptr.ulValueLen = label_length
        elif attr == CKA_KEY_TYPE:
            if not isinstance(value, int):
                raise Error("Integer value expected")
            attribute_ptr.pValue = new_ptr(unsigned_long, value)
            attribute_ptr.ulValueLen = sizeof(unsigned_long)
        else:
            raise Error("Unknown attribute")

        template = new_array(CK_ATTRIBUTE, (attribute_ptr[0],))

        rv = self.p11.C_SetAttributeValue(self.session, object, template,
                                          (sizeof(template) //
                                           sizeof(CK_ATTRIBUTE)))
        check_return_value(rv, "set_attribute")

    def get_attribute(self, key_object, attr):
        object = key_object
        attribute_ptr = new_ptr(CK_ATTRIBUTE)

        attribute_ptr.type = attr
        attribute_ptr.pValue = NULL_PTR
        attribute_ptr.ulValueLen = 0
        template = new_array(CK_ATTRIBUTE, (attribute_ptr[0],))

        rv = self.p11.C_GetAttributeValue(self.session, object, template,
                                          (sizeof(template) //
                                           sizeof(CK_ATTRIBUTE)))
        if rv == CKR_ATTRIBUTE_TYPE_INVALID or template[0].ulValueLen == -1:
            raise NotFound("attribute does not exist")
        check_return_value(rv, "get_attribute init")
        value = new_array(unsigned_char, template[0].ulValueLen)
        template[0].pValue = value

        rv = self.p11.C_GetAttributeValue(self.session, object, template,
                                          (sizeof(template) //
                                           sizeof(CK_ATTRIBUTE)))
        check_return_value(rv, "get_attribute")

        if attr in (CKA_ALWAYS_AUTHENTICATE,
                    CKA_ALWAYS_SENSITIVE,
                    CKA_COPYABLE,
                    CKA_ENCRYPT,
                    CKA_EXTRACTABLE,
                    CKA_DECRYPT,
                    CKA_DERIVE,
                    CKA_LOCAL,
                    CKA_MODIFIABLE,
                    CKA_NEVER_EXTRACTABLE,
                    CKA_PRIVATE,
                    CKA_SENSITIVE,
                    CKA_SIGN,
                    CKA_SIGN_RECOVER,
                    CKA_TOKEN,
                    CKA_TRUSTED,
                    CKA_UNWRAP,
                    CKA_VERIFY,
                    CKA_VERIFY_RECOVER,
                    CKA_WRAP,
                    CKA_WRAP_WITH_TRUSTED):
            ret = bool(_ffi.cast(_ffi.getctype(CK_BBOOL, '*'), value)[0])
        elif attr == CKA_LABEL:
            ret = char_array_to_unicode(value, template[0].ulValueLen)
        elif attr in (CKA_MODULUS, CKA_PUBLIC_EXPONENT, CKA_ID):
            ret = string_to_pybytes_or_none(value, template[0].ulValueLen)
        elif attr == CKA_KEY_TYPE:
            ret = _ffi.cast(_ffi.getctype(unsigned_long, '*'), value)[0]
        else:
            raise Error("Unknown attribute")

        return ret


# Key Classes
KEY_CLASS_PUBLIC_KEY = CKO_PUBLIC_KEY
KEY_CLASS_PRIVATE_KEY = CKO_PRIVATE_KEY
KEY_CLASS_SECRET_KEY = CKO_SECRET_KEY

# Key types
KEY_TYPE_RSA = CKK_RSA
KEY_TYPE_AES = CKK_AES

# Wrapping mech type
MECH_RSA_PKCS = CKM_RSA_PKCS
MECH_RSA_PKCS_OAEP = CKM_RSA_PKCS_OAEP
MECH_AES_KEY_WRAP = CKM_AES_KEY_WRAP
MECH_AES_KEY_WRAP_PAD = CKM_AES_KEY_WRAP_PAD


def gen_key_id(key_id_len=16):
    """
    Generate random softhsm KEY_ID
    :param key_id_len: this should be 16
    :return: random softhsm KEY_ID in bytes representation
    """
    return struct.pack(
        "B" * key_id_len,  # key_id must be bytes
        *(random.randint(0, 255) for _ in range(key_id_len))
    )


def generate_master_key(p11, keylabel=u"dnssec-master", key_length=16,
                        disable_old_keys=True):
    assert isinstance(p11, P11_Helper)

    key_id = None
    while True:
        # check if key with this ID exist in LDAP or softHSM
        # id is 16 Bytes long
        key_id = gen_key_id()
        keys = p11.find_keys(KEY_CLASS_SECRET_KEY,
                             label=keylabel,
                             id=key_id)
        if not keys:
            break  # we found unique id

    p11.generate_master_key(keylabel,
                            key_id,
                            key_length=key_length,
                            cka_wrap=True,
                            cka_unwrap=True)

    if disable_old_keys:
        # set CKA_WRAP=False for old master keys
        master_keys = p11.find_keys(KEY_CLASS_SECRET_KEY,
                                    label=keylabel,
                                    cka_wrap=True)

        for handle in master_keys:
            # don't disable wrapping for new key
            # compare IDs not handle
            if key_id != p11.get_attribute(handle, CKA_ID):
                p11.set_attribute(handle, CKA_WRAP, False)
