Skip to content

Commit

Permalink
Merge pull request #5 from IdentityPython/fix_python3
Browse files Browse the repository at this point in the history
Python 3 compatability
  • Loading branch information
johanlundberg authored Jan 17, 2023
2 parents ce26ccc + 679003b commit f9d522e
Show file tree
Hide file tree
Showing 11 changed files with 628 additions and 493 deletions.
18 changes: 15 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
language: python
python:
- "2.7"
matrix:
include:
- os: linux
dist: xenial
python: 2.7
- os: linux
dist: bionic
python: 2.7
- os: linux
dist: xenial
python: 3.5
- os: linux
dist: bionic
python: 3.6
install:
- "pip install -r requirements.txt"
- "./setup.py develop"
Expand All @@ -14,6 +26,6 @@ script:
- coverage combine
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq python-dev libyaml-dev libsofthsm softhsm opensc libengine-pkcs11-openssl
- sudo apt-get install -qq swig softhsm2 opensc libengine-pkcs11-openssl
after_success:
coveralls
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

[![Code Health](https://landscape.io/github/leifj/pyeleven/master/landscape.png)](https://landscape.io/github/leifj/pyeleven/master)
[![Travis](https://travis-ci.org/leifj/pyeleven.svg?branch=master)](https://travis-ci.org/leifj/pyeleven)
[![Coverage Status](https://coveralls.io/repos/leifj/pyeleven/badge.png)](https://coveralls.io/r/leifj/pyeleven)
[![Travis](https://travis-ci.org/identitypython/pyeleven.svg?branch=master)](https://travis-ci.org/leifj/pyeleven)
[![Coverage Status](https://coveralls.io/repos/identitypython/pyeleven/badge.png)](https://coveralls.io/r/leifj/pyeleven)

Python PKCS11 REST Proxy
========================
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
six
flask
pykcs11
retrying
retrying
47 changes: 24 additions & 23 deletions src/pyeleven/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from base64 import b64decode
from flask import Flask, request, jsonify
from .pk11 import pkcs11, load_library, slots_for_label
from .utils import mechanism, intarray2bytes
import os
import logging
from .pool import allocation
from retrying import retry
import os
from base64 import b64decode, b64encode

import six
from PyKCS11 import PyKCS11Error
from flask import Flask, request, jsonify
from retrying import retry

from pyeleven.pk11 import pkcs11, load_library, slots_for_label
from pyeleven.pool import allocation
from pyeleven.utils import mechanism, intarray2bytes, PKCS11Exception

__author__ = 'leifj'

Expand All @@ -28,20 +31,15 @@ def library_name():
return str(app.config['PKCS11MODULE'])


#print app.config

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)


@app.route("/info")
def _info():
return jsonify(dict(library=library_name()))


class PKCS11Exception(Exception):
pass


def retryable_errors(ex):
return isinstance(ex, IOError) or isinstance(ex, PKCS11Exception)

Expand All @@ -52,16 +50,16 @@ def _do_sign(label, keyname, mech, data, include_cert=True, require_cert=False):
include_cert = True

with pkcs11(library_name(), label, pin()) as si:
logging.debug('Looking for key with keyname {!r}'.format(keyname))
logger.debug('Looking for key with keyname {!r}'.format(keyname))
key, cert = si.find_key(keyname, find_cert=include_cert)
if key is None:
logging.warning('Found no key using label {!r}, keyname {!r}'.format(label, keyname))
logger.warning('Found no key using label {!r}, keyname {!r}'.format(label, keyname))
raise PKCS11Exception("Key %s not found" % keyname)
if require_cert and cert is None:
logging.warning('Found no certificate using label {!r}, keyname {!r}'.format(label, keyname))
logger.warning('Found no certificate using label {!r}, keyname {!r}'.format(label, keyname))
raise PKCS11Exception("Certificate for %s is required but missing" % keyname)
logging.debug('Signing {!s} bytes using key {!r}'.format(len(data), keyname))
result = dict(slot=label, signed=intarray2bytes(si.session.sign(key, data, mech)).encode('base64'))
logger.debug('Signing {!s} bytes using key {!r}'.format(len(data), keyname))
result = dict(slot=label, signed=b64encode(intarray2bytes(si.session.sign(key, data, mech))).decode('utf-8'))
if cert and include_cert:
result['cert'] = cert
return result
Expand All @@ -74,13 +72,16 @@ def _sign(slot_or_label, keyname):
if not type(msg) is dict:
raise ValueError("request must be a dict")

logging.debug('Signing data with slot_or_label {!r} and keyname {!r}\n'.format(slot_or_label, keyname))
logger.debug('Signing data with slot_or_label {!r} and keyname {!r}\n'.format(slot_or_label, keyname))
msg.setdefault('mech', 'RSAPKCS1')
if 'data' not in msg:
raise ValueError("missing 'data' in request")
data = b64decode(msg['data'])
if six.PY3:
data = data.decode('utf-8')
mech = mechanism(msg['mech'])
return jsonify(_do_sign(slot_or_label, keyname, mech, data, require_cert=True))
result = _do_sign(slot_or_label, keyname, mech, data, require_cert=True)
return jsonify(result)


@app.route("/<slot_or_label>/<keyname>/rawsign", methods=['POST'])
Expand Down Expand Up @@ -134,7 +135,7 @@ def _slot_keys(slot_or_label):
attrs = si.get_object_friendly_attrs(this)
res['objects'].append(attrs)
except Exception as ex:
logging.error('Failed fetching attributes for object, error: {!s}'.format(ex))
logger.error('Failed fetching attributes for object, error: {!s}'.format(ex))
return jsonify(res)


Expand All @@ -150,8 +151,8 @@ def _token():
lst = token_labels.setdefault(ti.label.strip(), [])
lst.append(slot)
slots.append(slot)
except Exception, ex:
logging.warning(ex)
except Exception as ex:
logger.warning(ex)
r['labels'] = token_labels
r['slots'] = slots
return jsonify(r)
Expand Down
74 changes: 50 additions & 24 deletions src/pyeleven/pk11.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import six
import threading
from .pool import ObjectPool, allocation
from .utils import intarray2bytes, cert_der2pem
from random import Random

from pyeleven.pool import ObjectPool, allocation
from pyeleven.utils import intarray2bytes, cert_der2pem, PKCS11Exception
from random import SystemRandom
from operator import eq, lt
import time
import logging
import PyKCS11
Expand All @@ -16,7 +19,7 @@

__author__ = 'leifj'

all_attributes = PyKCS11.CKA.keys()
all_attributes = list(PyKCS11.CKA.keys())

# remove the CKR_ATTRIBUTE_SENSITIVE attributes since we can't get
all_attributes.remove(PyKCS11.LowLevel.CKA_PRIVATE_EXPONENT)
Expand All @@ -27,6 +30,7 @@
all_attributes.remove(PyKCS11.LowLevel.CKA_COEFFICIENT)
all_attributes = [e for e in all_attributes if isinstance(e, int)]

logger = logging.getLogger(__name__)
thread_data = threading.local()


Expand Down Expand Up @@ -60,9 +64,15 @@ def reset():
def load_library(lib_name):
modules = _modules()
if lib_name not in modules:
logging.debug('loading load_library {!r}'.format(lib_name))
logger.debug('loading load_library {!r}'.format(lib_name))
lib = PyKCS11.PyKCS11Lib()
assert type(lib_name) == str # lib.load does not like unicode
# lib.load needs to be str for current python version
if six.PY2:
if not isinstance(lib_name, six.binary_type):
lib_name = lib_name.encode()
else:
if not isinstance(lib_name, six.text_type):
lib_name = lib_name.decode('utf-8')
lib.load(lib_name)
lib.lib.C_Initialize()
modules[lib_name] = lib
Expand All @@ -83,10 +93,13 @@ def priority(self):

def __str__(self):
return "SessionInfo[session=%s,slot=%d,use_count=%d,keys=%d]" % (
self.session, self.slot, self.use_count, len(self.keys))
self.session, self.slot, self.use_count, len(self.keys))

def __eq__(self, other):
return eq(self.use_count, other.use_count)

def __cmp__(self, other):
return cmp(self.use_count, other.use_count)
def __lt__(self, other):
return lt(self.use_count, other.use_count)

def find_object(self, template):
for o in self.session.findObjects(template):
Expand Down Expand Up @@ -114,22 +127,24 @@ def get_object_friendly_attrs(self, o):
return res

def find_key(self, keyname, find_cert=True):
if keyname is None:
raise PKCS11Exception('keyname can not be None')
if keyname not in self.keys:
key = self.find_object([(CKA_LABEL, keyname), (CKA_CLASS, CKO_PRIVATE_KEY), (CKA_KEY_TYPE, CKK_RSA)])
if key is None:
logging.debug('Private RSA key with CKA_LABEL {!r} not found'.format(keyname))
logger.debug('Private RSA key with CKA_LABEL {!r} not found'.format(keyname))
return None, None
cert_pem = None
if find_cert:
key_a = self.get_object_attributes(key, attrs = [CKA_ID])
logging.debug('Looking for certificate with CKA_ID {!r}'.format(key_a[CKA_ID]))
logger.debug('Looking for certificate with CKA_ID {!r}'.format(key_a[CKA_ID]))
cert = self.find_object([(CKA_ID, key_a[CKA_ID]), (CKA_CLASS, CKO_CERTIFICATE)])
if cert is not None:
cert_a = self.get_object_attributes(cert)
cert_pem = cert_der2pem(intarray2bytes(cert_a[CKA_VALUE]))
logging.debug('Certificate found:\n{!r}'.format(cert))
logger.debug('Certificate found:\n{!r}'.format(cert))
else:
logging.warning('Found no certificate for key with keyname {!r}'.format(keyname))
logger.warning('Found no certificate for key with keyname {!r}'.format(keyname))
self.keys[keyname] = (key, cert_pem)

return self.keys[keyname]
Expand All @@ -143,12 +158,12 @@ def open(lib, slot, pin=None):
try:
session.login(pin)
except PyKCS11.PyKCS11Error as ex:
logging.debug('Login failed: {!r}'.format(ex))
logger.debug('Login failed: {!r}'.format(ex))
if 'CKR_USER_ALREADY_LOGGED_IN' not in str(ex):
raise
si = SessionInfo(session=session, slot=slot)
sessions[slot] = si
logging.debug('opened session for {!r}:{:d}'.format(lib, slot))
logger.debug('opened session for {!r}:{:d}'.format(lib, slot))
return sessions[slot]

@staticmethod
Expand All @@ -162,14 +177,26 @@ def close(self):


def _find_slot(label, lib):
"""
:param label: Token label
:type label: str
:param lib: PyKCS11Lib
:type lib: PyKCS11.PyKCS11Lib
:return: Slots with given token label
:rtype: list
"""
slots = []

for slot in lib.getSlotList():
try:
token_info = lib.getTokenInfo(slot)
if label == token_info.label.strip():
slots.append(int(slot))
except Exception, ex:
pass
except PyKCS11.PyKCS11Error as ex:
logger.warning(ex)

if not slots:
raise PKCS11Exception('No slot for token label \"{}\" found'.format(label))
return slots


Expand All @@ -181,7 +208,7 @@ def slots_for_label(label, lib):
return _find_slot(label, lib)


seed = Random(time.time())
seed = SystemRandom()


def pkcs11(library_name, label, pin=None, max_slots=None):
Expand All @@ -208,23 +235,22 @@ def _get(*args, **kwargs):
def _refill(): # if sd is getting a bit light - fill it back up
if len(sd) < max_slots:
for slot in slots_for_label(label, lib):
logging.debug('found slot {!r} for label {!r} during refill'.format(slot, label))
logger.debug('found slot {!r} for label {!r} during refill'.format(slot, label))
sd[slot] = True

random_slot = None
retry=10
retry = 10
while retry > 0:
_refill()
k = sd.keys()
k = list(sd.keys())
random_slot = seed.choice(k)
try:
return SessionInfo.open(lib, random_slot, pin)
except Exception, ex: # on first suspicion of failure - force the slot to be recreated
except Exception as ex: # on first suspicion of failure - force the slot to be recreated
if random_slot in sd:
del sd[random_slot]
SessionInfo.close_slot(random_slot)
time.sleep(0.2) # TODO - make retry delay configurable
logging.error('Failed opening session (retry: {!r}): {!s}'.format(retry, ex))
logger.error('Failed opening session (retry: {!r}): {!s}'.format(retry, ex))
retry -= 1
if not retry:
raise
Expand Down
15 changes: 9 additions & 6 deletions src/pyeleven/pool.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# -*- coding:utf-8 -*-
try:
import Queue as Q
except ImportError:
import queue as Q

from contextlib import contextmanager

import six

if six.PY2:
import Queue as Q
else:
import queue as Q


class ObjectPool(object):
"""A simple thread safe object pool"""
Expand All @@ -25,7 +28,7 @@ def alloc(self):
n = self.maxSize - self.queue.qsize()
for i in range(0, n): # try to allocate enough objects to fill to maxSize
obj = self.create(*self.args, **self.kwargs)
#print "allocated %s" % obj
#print("allocated %s" % obj)
self.queue.put(obj)
return self.queue.get()

Expand All @@ -41,7 +44,7 @@ def allocation(pool):
obj = pool.alloc()
try:
yield obj
except Exception, e:
except Exception as e:
pool.invalidate(obj)
obj = None
raise e
Expand Down
Loading

0 comments on commit f9d522e

Please sign in to comment.