├── .github └── workflows │ └── python-test.yml ├── .gitignore ├── .gitlab-ci.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.MD ├── SECURITY.md ├── docs ├── api.attributes.rst ├── api.bindings.rst ├── api.daemon.rst ├── api.encryption.rst ├── api.extensions.rst ├── api.helpers.rst ├── api.keys.rst ├── api.mechanisms.rst ├── api.misc.rst ├── api.rst ├── api.sessions.rst ├── api.sigver.rst ├── conf.py ├── examples.rst ├── getting_started.rst ├── index.rst ├── problems.rst └── requirements.txt ├── pycryptoki ├── .coveragerc ├── __init__.py ├── attributes.py ├── audit_handling.py ├── backup.py ├── ca_extensions │ ├── __init__.py │ ├── bip32.py │ ├── cpv4.py │ ├── derive_wrap.py │ ├── hsm_info.py │ ├── object_handler.py │ ├── per_key_auth.py │ ├── session.py │ ├── stc.py │ └── utilization_metrics.py ├── common_utils.py ├── conversions.py ├── cryptoki │ ├── __init__.py │ ├── _ck_func_list.py │ ├── c_defs.py │ ├── ck_defs.py │ ├── func_defs.py │ ├── helpers.py │ └── retcodes.py ├── cryptoki_helpers.py ├── daemon │ ├── __init__.py │ └── rpyc_pycryptoki.py ├── default_templates.py ├── defaults.py ├── defines.py ├── encryption.py ├── exceptions.py ├── hsm_management.py ├── key_generator.py ├── key_management.py ├── key_usage.py ├── lookup_dicts.py ├── luna_threading.py ├── mechanism │ ├── __init__.py │ ├── aes.py │ ├── des.py │ ├── dh.py │ ├── eddsa.py │ ├── generic.py │ ├── helpers.py │ ├── kdf.py │ ├── rc.py │ ├── rsa.py │ ├── sha.py │ └── shake.py ├── misc.py ├── object_attr_lookup.py ├── partition_management.py ├── ptk_defines.py ├── pycryptoki_client.py ├── pycryptoki_warnings.py ├── pylintrc ├── return_values.py ├── session_management.py ├── sign_verify.py ├── string_helpers.py ├── test_functions.py ├── token_management.py └── utilities.py ├── requirements.txt ├── setup.py ├── test_requirements.txt ├── tests ├── __init__.py ├── functional │ ├── __init__.py │ ├── conftest.py │ ├── cpv4_util.py │ ├── test_audit_handling.py │ ├── test_cka_start_and_end.py │ ├── test_cpv4.py │ ├── test_digest_data.py │ ├── test_encrypt_decrypt.py │ ├── test_get_token_info.py │ ├── test_hsm_management.py │ ├── test_key_management.py │ ├── test_key_usage.py │ ├── test_keys.py │ ├── test_misc.py │ ├── test_object_create.py │ ├── test_session_management.py │ ├── test_sign_verify.py │ ├── test_supporting_operations.py │ ├── test_usage_limit_and_count.py │ ├── test_wrap_unwrap.py │ ├── testdata │ │ └── sha1pkcs_plain.der │ └── util.py └── unittests │ ├── __init__.py │ ├── test_attr_conversions.py │ ├── test_attributes.py │ ├── test_auto_c_array.py │ ├── test_daemon.py │ ├── test_dll_singleton.py │ ├── test_encryption.py │ ├── test_mechanisms.py │ └── test_str_helpers.py └── tox.ini /.github/workflows/python-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | python-version: ["3.7", "3.8", "3.9", "3.10"] 22 | 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | cache: 'pip' 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install flake8 pytest black==19.10b0 click==8.0.2 34 | if [ -f requirements.txt ]; then pip install -r requirements.txt -r test_requirements.txt; fi 35 | - name: Autoformat w/ black 36 | run: | 37 | black -l 100 . --check --diff 38 | - name: Lint with flake8 39 | run: | 40 | # stop the build if there are Python syntax errors or undefined names 41 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 42 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 43 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore F40,W503,E203 44 | - name: Test with pytest 45 | run: | 46 | pytest tests/unittests 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | .gitreview 3 | .hypothesis 4 | .pytest_cache 5 | .venv 6 | _docs 7 | coverage.xml 8 | junit*.xml 9 | .python-version 10 | ### PyCharm ### 11 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 12 | 13 | *.iml 14 | 15 | ## Directory-based project format: 16 | .idea/ 17 | # if you remove the above rule, at least ignore the following: 18 | 19 | # User-specific stuff: 20 | # .idea/workspace.xml 21 | # .idea/tasks.xml 22 | # .idea/dictionaries 23 | 24 | # Sensitive or high-churn files: 25 | # .idea/dataSources.ids 26 | # .idea/dataSources.xml 27 | # .idea/sqlDataSources.xml 28 | # .idea/dynamic.xml 29 | # .idea/uiDesigner.xml 30 | 31 | # Gradle: 32 | # .idea/gradle.xml 33 | # .idea/libraries 34 | 35 | # Mongo Explorer plugin: 36 | # .idea/mongoSettings.xml 37 | 38 | ## File-based project format: 39 | *.ipr 40 | *.iws 41 | 42 | ## Plugin-specific files: 43 | 44 | # IntelliJ 45 | /out/ 46 | 47 | # mpeltonen/sbt-idea plugin 48 | .idea_modules/ 49 | 50 | # JIRA plugin 51 | atlassian-ide-plugin.xml 52 | 53 | # Crashlytics plugin (for Android Studio and IntelliJ) 54 | com_crashlytics_export_strings.xml 55 | crashlytics.properties 56 | crashlytics-build.properties 57 | 58 | 59 | ### Python ### 60 | # Byte-compiled / optimized / DLL files 61 | __pycache__/ 62 | *.py[cod] 63 | 64 | # C extensions 65 | *.so 66 | 67 | # Distribution / packaging 68 | .Python 69 | env/ 70 | build/ 71 | develop-eggs/ 72 | dist/ 73 | downloads/ 74 | eggs/ 75 | .eggs/ 76 | lib/ 77 | lib64/ 78 | parts/ 79 | sdist/ 80 | var/ 81 | *.egg-info/ 82 | .installed.cfg 83 | *.egg 84 | 85 | # PyInstaller 86 | # Usually these files are written by a python script from a template 87 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 88 | *.manifest 89 | *.spec 90 | 91 | # Installer logs 92 | pip-log.txt 93 | pip-delete-this-directory.txt 94 | 95 | # Unit test / coverage reports 96 | htmlcov/ 97 | .tox/ 98 | .coverage 99 | .coverage.* 100 | .cache 101 | nosetests.xml 102 | coverage.xml 103 | *,cover 104 | 105 | # Translations 106 | *.mo 107 | *.pot 108 | 109 | # Django stuff: 110 | *.log 111 | 112 | # Sphinx documentation 113 | docs/_build/ 114 | _docbuild 115 | 116 | # PyBuilder 117 | target/ 118 | 119 | #eclipse 120 | .project 121 | .pydevproject 122 | .settings 123 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: aa1569.lab.hsm:5443/py_tox_tester:latest 2 | 3 | # Change pip's cache directory to be inside the project directory since we can 4 | # only cache local items. 5 | variables: 6 | PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" 7 | PYTHON_VERSIONS: "2.7.15 3.6.12 3.7.9 3.8.5 3.9.13 3.10.4" 8 | 9 | # Pip's cache doesn't store the python packages 10 | # https://pip.pypa.io/en/stable/reference/pip_install/#caching 11 | # 12 | # If you want to also cache the installed packages, you have to install 13 | # them in a virtualenv and cache it as well. 14 | cache: 15 | paths: 16 | - .cache/pip 17 | 18 | stages: 19 | - lint 20 | - test 21 | - build 22 | - deploy 23 | 24 | before_script: 25 | - export PATH="$HOME/.pyenv/bin/:$PATH" 26 | - eval "$(pyenv init -)" 27 | - pyenv global ${PYTHON_VERSIONS[@]} 28 | - python -V 29 | - source /.venv/bin/activate 30 | 31 | lint: 32 | needs: [] 33 | stage: lint 34 | interruptible: true 35 | script: 36 | - pip install black==19.10b0 click==8.0.2 37 | - black --version 38 | - black -l 100 . --check --diff 39 | 40 | test: 41 | needs: [] 42 | stage: test 43 | script: 44 | - tox -e clean 45 | - tox -p -e py27,py36,py37,py38,py39,py310 46 | - tox -e report 47 | interruptible: true 48 | artifacts: 49 | when: always 50 | paths: 51 | - junit*.xml 52 | reports: 53 | junit: junit*.xml 54 | cobertura: coverage.xml 55 | 56 | 57 | build: 58 | stage: build 59 | interruptible: true 60 | script: 61 | - | 62 | for py in ${PYTHON_VERSIONS}; do 63 | bin="python$( echo $py | cut -d'.' -f1-2 )" 64 | $bin -m pip install wheel 65 | $bin setup.py bdist_wheel 66 | done 67 | 68 | artifacts: 69 | paths: 70 | - dist/*.whl 71 | 72 | deploy: 73 | tags: 74 | - hsmtest 75 | stage: deploy 76 | dependencies: 77 | - build 78 | only: 79 | - release 80 | before_script: 81 | - cp $DEPLOYMENT_PRIVKEY ~/.ssh/id_ecdsa 82 | - chmod 600 ~/.ssh/id_ecdsa 83 | script: /root/deploy.sh 84 | 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | ## Filing issues 4 | 5 | File issues using the standard Github issue tracker for the repo. 6 | 7 | ## How to become a contributor and submit your own code 8 | 9 | ### Contributing A Patch 10 | 11 | * Submit an issue describing your proposed change to the repo in question. 12 | * The repo owner will respond to your issue promptly. 13 | * If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). 14 | * Fork the desired repo, develop and test your code changes. 15 | * Submit a pull request. 16 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ## Pycryptoki 2 | [![Doc Status](https://readthedocs.org/projects/pycryptoki/badge/?version=latest)](http://pycryptoki.readthedocs.io/en/latest/) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6236/badge)](https://bestpractices.coreinfrastructure.org/projects/6236) 3 | 4 | Pycryptoki is a python wrapper around the PKCS11 library. 5 | 6 | ## Documentation 7 | 8 | Latest API documentation can be found on [readthedocs](http://pycryptoki.readthedocs.io/en/latest/index.html). 9 | 10 | 11 | ## Installation 12 | 13 | pip install git+https://github.com/ThalesGroup/pycryptoki 14 | 15 | ## Key Generation Example 16 | 17 | ```py 18 | from pycryptoki.default_templates import * 19 | from pycryptoki.defines import * 20 | from pycryptoki.key_generator import * 21 | from pycryptoki.session_management import * 22 | from pycryptoki.encryption import * 23 | 24 | 25 | c_initialize_ex() 26 | auth_session = c_open_session_ex(0) # HSM slot # in this example is 0 27 | login_ex(auth_session, 0, 'userpin') # 0 is still the slot number, ‘userpin’ should be replaced by your password (None if PED or no challenge) 28 | 29 | # Get some default templates 30 | # They are simple python dictionaries, and can be modified to suit needs. 31 | pub_template, priv_template = get_default_key_pair_template(CKM_RSA_PKCS_KEY_PAIR_GEN) 32 | 33 | # Modifying template would look like: 34 | pub_template[CKA_LABEL] = "RSA PKCS Pub Key" 35 | pub_template[CKA_MODULUS_BITS] = 2048 # 2048 key size 36 | 37 | pubkey, privkey = c_generate_key_pair_ex(auth_session, CKM_RSA_PKCS_KEY_PAIR_GEN, pub_template, priv_template) 38 | print("Generated Private key at %s and Public key at %s" % (privkey, pubkey)) 39 | 40 | c_logout_ex(auth_session) 41 | c_close_session_ex(auth_session) 42 | c_finalize_ex() 43 | ``` 44 | ## Verbose logging 45 | 46 | If you want to see what calls to the C library are being performed, set pycryptoki logging to `DEBUG`: 47 | 48 | ```py 49 | import logging 50 | logging.basicConfig(level=logging.DEBUG) 51 | ``` 52 | 53 | ## Tests 54 | 55 | Test requirements can be installed via `pip install -r test_requirements.txt`. 56 | 57 | Unittests can be run on any environment via: 58 | ``` 59 | py.test tests/unittests 60 | ``` 61 | 62 | Functional tests require an HSM to test against, and will actively test the integration 63 | with the libCryptoki library. This *will* create and destroy objects on the HSM, so don't run 64 | on a production HSM! 65 | 66 | ``` 67 | py.test tests/functional --slot= [--reset] [--password=] [--copassword=] [--user=] [--loglevel=] 68 | ``` 69 | 70 | ### Adding new tests 71 | 72 | Tests for new functionality should be added as the new commands are added. Ideally functional 73 | tests for things that hit an HSM, and unittests for more complicated pure-python handling. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Vulnerabilities 2 | 3 | Vulnerabilities discovered in pycryptoki can be reported on 4 | github.com/ThalesGroup/pycryptoki issue tracker. 5 | 6 | Note that Pycryptoki is primarily a test tool, so it often allows you to use old or 7 | dated cryptography. This is not an explicit vulnerability. -------------------------------------------------------------------------------- /docs/api.attributes.rst: -------------------------------------------------------------------------------- 1 | Attributes and Conversions 2 | ========================== 3 | 4 | .. contents:: 5 | 6 | 7 | 8 | .. automodule:: pycryptoki.attributes 9 | :members: 10 | :member-order: bysource 11 | 12 | .. data:: pycryptoki.attributes.KEY_TRANSFORMS 13 | :annotation: CK_ATTRIBUTE Types mapped to Python->C transformation functions 14 | 15 | .. _conversions: 16 | 17 | Conversions 18 | ----------- 19 | 20 | .. automodule:: pycryptoki.conversions 21 | :members: 22 | :member-order: bysource 23 | 24 | -------------------------------------------------------------------------------- /docs/api.bindings.rst: -------------------------------------------------------------------------------- 1 | Python/C Bindings 2 | ================= 3 | 4 | .. automodule:: pycryptoki.cryptoki 5 | :members: 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /docs/api.daemon.rst: -------------------------------------------------------------------------------- 1 | Pycryptoki Daemon Package 2 | ========================= 3 | 4 | Start ``pycryptoki.daemon.rpyc_pycryptoki.py`` on your remote client, then connect to it 5 | using :class:`~pycryptoki.pycryptoki_client.RemotePycryptokiClient`. You can then 6 | use the RemotePycryptokiClient as if it were local:: 7 | 8 | pycryptoki = RemotePycryptokiClient('10.2.96.130', port=8001) 9 | pycryptoki.c_initialize_ex() # Executed on the daemon! 10 | session = pycryptoki.c_open_session_ex(0) 11 | #etc 12 | 13 | 14 | daemon.rpyc_pycryptoki 15 | --------------- 16 | 17 | .. automodule:: pycryptoki.daemon.rpyc_pycryptoki 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | pycryptoki.pycryptoki_client 23 | ---------------------------- 24 | 25 | .. automodule:: pycryptoki.pycryptoki_client 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | -------------------------------------------------------------------------------- /docs/api.encryption.rst: -------------------------------------------------------------------------------- 1 | Encryption/Decryption 2 | ===================== 3 | 4 | .. contents:: 5 | 6 | Encryption 7 | ---------- 8 | 9 | .. autofunction:: pycryptoki.encryption.c_encrypt 10 | 11 | .. autofunction:: pycryptoki.encryption.c_encrypt_ex 12 | 13 | Decryption 14 | ---------- 15 | 16 | .. autofunction:: pycryptoki.encryption.c_decrypt 17 | 18 | 19 | .. autofunction:: pycryptoki.encryption.c_decrypt_ex 20 | 21 | Key Wrapping/Unwrapping 22 | ----------------------- 23 | 24 | .. autofunction:: pycryptoki.encryption.c_wrap_key 25 | 26 | .. autofunction:: pycryptoki.encryption.c_wrap_key_ex 27 | 28 | .. autofunction:: pycryptoki.encryption.c_unwrap_key 29 | 30 | .. autofunction:: pycryptoki.encryption.c_unwrap_key_ex 31 | 32 | 33 | Multipart Helper 34 | ---------------- 35 | 36 | .. autofunction:: pycryptoki.encryption.do_multipart_operation 37 | -------------------------------------------------------------------------------- /docs/api.extensions.rst: -------------------------------------------------------------------------------- 1 | Extensions to the PKCS11 API 2 | ============================ 3 | 4 | Thales-specific Extensions to the PKCS11 API. 5 | 6 | 7 | .. contents:: 8 | 9 | 10 | Derive Key And Wrap 11 | ------------------- 12 | 13 | .. automodule:: pycryptoki.ca_extensions.derive_wrap 14 | :members: 15 | :undoc-members: 16 | 17 | 18 | HSM Info 19 | -------- 20 | 21 | 22 | .. automodule:: pycryptoki.ca_extensions.hsm_info 23 | :members: 24 | :undoc-members: 25 | :member-order: bysource 26 | 27 | 28 | Object Commands 29 | --------------- 30 | 31 | 32 | .. automodule:: pycryptoki.ca_extensions.object_handler 33 | :members: 34 | :undoc-members: 35 | :member-order: bysource 36 | 37 | 38 | Per Key Authorization 39 | --------------------- 40 | 41 | .. automodule:: pycryptoki.ca_extensions.per_key_auth 42 | :members: 43 | :undoc-members: 44 | :member-order: bysource 45 | 46 | 47 | Session Commands 48 | ---------------- 49 | 50 | 51 | .. automodule:: pycryptoki.ca_extensions.session 52 | :members: 53 | :undoc-members: 54 | :member-order: bysource 55 | 56 | 57 | Utilization Metrics 58 | ------------------- 59 | 60 | .. automodule:: pycryptoki.ca_extensions.utilization_metrics 61 | :members: 62 | :undoc-members: 63 | :member-order: bysource 64 | -------------------------------------------------------------------------------- /docs/api.helpers.rst: -------------------------------------------------------------------------------- 1 | Pycryptoki Helpers 2 | ================== 3 | 4 | These are various helper modules and functions. They contain constant definitions, 5 | C parameter structs, configuration parsing, and default templates. 6 | 7 | 8 | .. contents:: 9 | 10 | 11 | lookup_dicts 12 | ------------ 13 | 14 | .. automodule:: pycryptoki.lookup_dicts 15 | :members: 16 | :undoc-members: 17 | 18 | default_templates 19 | ----------------- 20 | 21 | .. automodule:: pycryptoki.default_templates 22 | :members: 23 | :undoc-members: 24 | :show-inheritance: 25 | 26 | defaults 27 | -------- 28 | 29 | .. automodule:: pycryptoki.defaults 30 | :members: 31 | :undoc-members: 32 | :show-inheritance: 33 | -------------------------------------------------------------------------------- /docs/api.keys.rst: -------------------------------------------------------------------------------- 1 | Key Generation and Management 2 | ============================= 3 | 4 | 5 | .. contents:: 6 | 7 | 8 | Key Generation 9 | -------------- 10 | 11 | .. automodule:: pycryptoki.key_generator 12 | :members: 13 | :undoc-members: 14 | :show-inheritance: 15 | 16 | Key Management 17 | -------------- 18 | 19 | .. automodule:: pycryptoki.key_management 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | Key Usage 25 | --------- 26 | 27 | .. automodule:: pycryptoki.key_usage 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /docs/api.mechanisms.rst: -------------------------------------------------------------------------------- 1 | Mechanisms 2 | ---------- 3 | 4 | 5 | .. contents:: 6 | 7 | .. automodule:: pycryptoki.mechanism 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | :member-order: bysource 12 | 13 | 14 | Helpers 15 | ======= 16 | 17 | .. automodule:: pycryptoki.mechanism.helpers 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | 23 | AES Mechanisms 24 | ============== 25 | 26 | .. automodule:: pycryptoki.mechanism.aes 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | Generic Mechanisms 32 | ================== 33 | 34 | .. automodule:: pycryptoki.mechanism.generic 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | RC Mechanisms 40 | ============= 41 | 42 | .. automodule:: pycryptoki.mechanism.rc 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | RSA Mechanisms 48 | ============== 49 | 50 | .. automodule:: pycryptoki.mechanism.rsa 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | -------------------------------------------------------------------------------- /docs/api.misc.rst: -------------------------------------------------------------------------------- 1 | Miscellaneous 2 | ============= 3 | 4 | 5 | .. contents:: 6 | 7 | 8 | RNG, Digest, Creating Objects 9 | ----------------------------- 10 | 11 | .. automodule:: pycryptoki.misc 12 | :members: 13 | :undoc-members: 14 | :show-inheritance: 15 | :member-order: bysource 16 | 17 | 18 | Find Objects, Attribute Setting/Getting 19 | --------------------------------------- 20 | 21 | .. automodule:: pycryptoki.object_attr_lookup 22 | :members: 23 | :undoc-members: 24 | :show-inheritance: 25 | :member-order: bysource 26 | 27 | HSM Management 28 | -------------- 29 | 30 | .. automodule:: pycryptoki.hsm_management 31 | :members: 32 | :undoc-members: 33 | :show-inheritance: 34 | :member-order: bysource 35 | 36 | 37 | Audit Functions 38 | --------------- 39 | 40 | .. automodule:: pycryptoki.audit_handling 41 | :members: 42 | :undoc-members: 43 | :show-inheritance: 44 | :member-order: bysource 45 | 46 | Backup Functions 47 | ---------------- 48 | 49 | .. automodule:: pycryptoki.backup 50 | :members: 51 | :undoc-members: 52 | :show-inheritance: 53 | :member-order: bysource 54 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | There are some general guidelines to using pycryptoki: 5 | 6 | 7 | 1. If you want to perform a PKCS11 operation as a multi-part operation, provide the input data 8 | as a list or a tuple. 9 | 2. Data should always be passed into ``c_`` functions as raw byte data (bytestrings). 10 | Conversions are available to convert hex data or binary data to bytes at 11 | :ref:`pycryptoki.conversions` 12 | 3. Returned encrypted/decrypted data is always raw bytestrings. 13 | 14 | 15 | .. toctree:: 16 | 17 | Session/Token Management 18 | Key Generation/Management 19 | Encryption/Decryption 20 | Sign/Verify 21 | Attributes 22 | Mechanisms 23 | Miscellaneous 24 | Helpers 25 | Extensions 26 | Bindings 27 | RPYC Daemon 28 | -------------------------------------------------------------------------------- /docs/api.sessions.rst: -------------------------------------------------------------------------------- 1 | Session/Token Management 2 | ======================== 3 | 4 | Modules for Token and session creation and management. 5 | 6 | .. contents:: 7 | 8 | 9 | Session Management 10 | ------------------ 11 | 12 | .. automodule:: pycryptoki.session_management 13 | :members: 14 | :undoc-members: 15 | :show-inheritance: 16 | :member-order: bysource 17 | 18 | 19 | Token Management 20 | ---------------- 21 | 22 | .. automodule:: pycryptoki.token_management 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | :member-order: bysource 27 | -------------------------------------------------------------------------------- /docs/api.sigver.rst: -------------------------------------------------------------------------------- 1 | Sign/Verify operations 2 | ====================== 3 | 4 | .. contents:: 5 | 6 | 7 | Sign 8 | ---- 9 | 10 | .. autofunction:: pycryptoki.sign_verify.c_sign 11 | 12 | .. autofunction:: pycryptoki.sign_verify.c_sign_ex 13 | 14 | 15 | Verify 16 | ------ 17 | 18 | .. autofunction:: pycryptoki.sign_verify.c_verify 19 | 20 | .. autofunction:: pycryptoki.sign_verify.c_verify_ex 21 | 22 | .. autofunction:: pycryptoki.sign_verify.do_multipart_verify 23 | 24 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | 2 | Getting Started 3 | =============== 4 | 5 | To use pycryptoki, you must have SafeNet LunaClient installed. 6 | 7 | Installation 8 | ------------ 9 | 10 | Pycryptoki can be installed on any machine that has Python installed. Python versions >= 2.7 11 | are supported.:: 12 | 13 | pip install git+https://github.com/ThalesGroup/pycryptoki 14 | 15 | 16 | Pycryptoki will attempt to auto-locate the SafeNet Cryptoki shared library when pycryptoki 17 | is first called. It will use the configuration files as defined by the LunaClient documentation to 18 | determine which library to use. 19 | 20 | 21 | Simple Example 22 | -------------- 23 | 24 | This example will print out information about the given token slot. 25 | 26 | 27 | .. code-block:: python 28 | 29 | from pycryptoki.session_management import (c_initialize_ex, 30 | c_get_info_ex, 31 | get_firmware_version, 32 | c_get_token_info_ex, 33 | c_finalize_ex) 34 | 35 | 36 | c_initialize_ex() 37 | print("C_GetInfo: ") 38 | print("\n".join("\t{}: {}".format(x, y) for x, y in c_get_info_ex().items())) 39 | token_info = c_get_token_info_ex(0) 40 | print("C_GetTokenInfo:") 41 | print("\n".join("\t{}: {}".format(x, y) for x, y in token_info.items())) 42 | print("Firmware version: {}".format(get_firmware_version(0))) 43 | 44 | c_finalize_ex() 45 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Pycryptoki 2 | ========== 3 | 4 | 5 | Overview 6 | -------- 7 | 8 | Pycryptoki is an open-source Python wrapper around Safenet's C PKCS11 library. Using python's ctypes library, 9 | we can simplify memory management, and provide easy, pythonic access to a PKCS11 shared library. 10 | 11 | The primary function of pycryptoki is to *simplify* PKCS11 calls. Rather than needing to calculate 12 | data sizes, buffers, or other low-level memory manipulation, you simply need to pass in data. 13 | 14 | It's highly recommended that you have the `PKCS11 `_ documentation 15 | handy, as pycryptoki uses that as the underlying C interface. Session management, object management, 16 | and other concepts are unchanged from PKCS11. 17 | 18 | See :ref:`usertypes` for the mapping of Luna users to PKCS11 constants. 19 | 20 | .. code-block:: python 21 | 22 | from pycryptoki.default_templates import * 23 | from pycryptoki.defines import * 24 | from pycryptoki.key_generator import * 25 | from pycryptoki.session_management import * 26 | 27 | 28 | c_initialize_ex() 29 | auth_session = c_open_session_ex(0) # HSM slot # in this example is 0 30 | login_ex(auth_session, 0, 'userpin') # 0 is still the slot number, ‘userpin’ should be replaced by your password (None if PED or no challenge) 31 | 32 | # Get some default templates 33 | # They are simple python dictionaries, and can be modified to suit needs. 34 | pub_template, priv_template = get_default_key_pair_template(CKM_RSA_PKCS_KEY_PAIR_GEN) 35 | 36 | # Modifying template would look like: 37 | pub_template[CKA_LABEL] = b"RSA PKCS Pub Key" 38 | pub_template[CKA_MODULUS_BITS] = 2048 # 2048 key size 39 | 40 | pubkey, privkey = c_generate_key_pair_ex(auth_session, CKM_RSA_PKCS_KEY_PAIR_GEN, pub_template, priv_template) 41 | print("Generated Private key at %s and Public key at %s" % (privkey, pubkey)) 42 | 43 | c_logout_ex(auth_session) 44 | c_close_session_ex(auth_session) 45 | c_finalize_ex() 46 | 47 | 48 | 49 | .. toctree:: 50 | :maxdepth: 4 51 | :includehidden: 52 | 53 | Getting Started 54 | Examples 55 | Frequent Issues 56 | API Reference 57 | 58 | -------------------------------------------------------------------------------- /docs/problems.rst: -------------------------------------------------------------------------------- 1 | Frequent Issues 2 | =============== 3 | 4 | .. contents:: 5 | 6 | 7 | Wrong data type 8 | --------------- 9 | 10 | Any cryptographic function working on data (ex. ``c_encrypt``, ``c_unwrap``) will expect a 11 | bytestring. A string object in Python2 is by default a *bytestring*, but in Python3 is a 12 | *unicode* string. 13 | 14 | For example:: 15 | 16 | c_encrypt(session, key, "this is some test data", mechanism) 17 | 18 | Will work in Python 2, but NOT Python 3. Instead, use the :ref:`pycryptoki.conversions` 19 | module to ensure that any data you pass into the cryptoki library is of the correct form. 20 | 21 | Another 'gotcha' is that hex data represented as a string that is then used in an encrypt call would 22 | result in 2x the length of expected data:: 23 | 24 | from pycryptoki.conversions import to_bytestring, from_hex 25 | hex_data = "deadbeef" 26 | assert len(hex_data) == 8 27 | raw_data = list(from_hex(hex_data)) 28 | assert len(raw_data) == 4 29 | print (raw_data) 30 | # Prints: [222, 173, 190, 239] 31 | 32 | Another example:: 33 | 34 | from pycryptoki.conversions import to_bytestring, from_hex 35 | some_hex_data = "06abde23df89" 36 | data_to_encrypt = to_bytestring(from_hex(some_hex_data)) 37 | c_encrypt(session, key, data_to_encrypt, mechanism) 38 | 39 | .. note:: 40 | See this article for more details about the differences between unicode and bytestrings in 41 | python: http://lucumr.pocoo.org/2014/1/5/unicode-in-2-and-3/ 42 | 43 | Internal Initialization Vectors 44 | ------------------------------- 45 | 46 | When you use an internal IV for AES mechanisms, the IV is appended to the cipher text. This needs to 47 | be stripped off and used to create the mechanism for decryption:: 48 | 49 | from pycryptoki.encryption import c_encrypt_ex 50 | 51 | data_to_encrypt = b"a" * 64 52 | mech = Mechanism(CKM_AES_KW, 53 | params={"iv": []}) # Uses an internal IV 54 | 55 | enc_data = c_encrypt_ex(session, key, data_to_encrypt, mech) 56 | iv = enc_data[-16:] # Strip off the last 16 bytes of the encrypted data. 57 | decrypt_mech = Mechanism(CKM_AES_KW, 58 | params={"iv": iv}) 59 | decrypted_data = c_decrypt_ex(session, key, enc_data[:-16], decrypt_mech) 60 | 61 | 62 | PKCS11 Calling Conventions 63 | -------------------------- 64 | 65 | .. _Calling Convention: https://www.cryptsoft.com/pkcs11doc/v220/group__SEC__11__2__CONVENTIONS__FOR__FUNCTIONS__RETURNING__OUTPUT__IN__A__VARIABLE__LENGTH__BUFFER.html#SECTION_11_2 66 | 67 | `The PKCS11 library has two main methods for returning data to the caller `_: 68 | 69 | 1. Allocate a large enough buffer for the resulting data and make the PKCS11 call with that buffer. 70 | 2. Call the function with a NULL pointer for the buffer. The PKCS11 library will then place the 71 | required buffer size in ``*pulBufLen``. 72 | 73 | 74 | Pycryptoki will let you perform either method for any function that returns data in a variable-length 75 | buffer with the ``output_buffer`` keyword argument. This argument takes either an integer, or a list 76 | of integers. The integer specifies the *size* of the buffer to use for the returned output. This means 77 | if you use a very small integer, you could get back ``CKR_BUFFER_TOO_SMALL`` (and you could also 78 | allocate a buffer that is incredibly large -- limited by the memory of your system). 79 | 80 | 81 | By default, pycryptoki will use method #2 (querying the library for buffer size):: 82 | 83 | data = b"deadbeef" 84 | c_decrypt_ex(session, key, data, mechanism) 85 | 86 | 87 | Will result in the raw underlying PKCS11 calls: 88 | 89 | 90 | .. code-block:: none 91 | 92 | DEBUG: Cryptoki call: C_DecryptInit(8, , c_ulong(26)) 93 | DEBUG: Cryptoki call: C_Decrypt(8, , c_ulong(2056), None, ) 94 | DEBUG: Allocating buffer of size: 2048 95 | DEBUG: Cryptoki call: C_Decrypt(8, , c_ulong(2056), , ) 96 | 97 | 98 | .. note:: 99 | ``None`` in python is the equivalent to ``NULL`` in C. 100 | 101 | An example using a pre-allocated buffer:: 102 | 103 | 104 | data = b"deadbeef" 105 | c_decrypt_ex(session, key, data, mechanism, output_buffer=0xffff) 106 | 107 | 108 | And the resulting PKCS11 calls: 109 | 110 | .. code-block:: none 111 | 112 | DEBUG: Cryptoki call: C_DecryptInit(8, , c_ulong(26)) 113 | DEBUG: Allocating buffer of size: 2048 114 | DEBUG: Cryptoki call: C_Decrypt(8, , c_ulong(2056), , ) 115 | 116 | 117 | For multi-part operations, ``output_buffer`` should be a list of integers of equal size to the 118 | number of parts in the operation:: 119 | 120 | data = [b"a" * 8, b"b" * 8, b"c" * 8, b"d" * 8] 121 | output_buffer = [0xffff] * len(data) # Equivalent to: [0xffff, 0xffff, 0xffff, 0xffff] 122 | c_encrypt_ex(session, key, data, mechanism, output_buffer=output_buffer) 123 | 124 | 125 | For a multi-part operation that returns data in the ``C_*Final`` function, the output buffer will be 126 | equivalent to the largest buffer size specified in the output_buffer list. 127 | 128 | 129 | .. _usertypes: 130 | 131 | Luna & PKCS11 User types 132 | ------------------------ 133 | 134 | Following is a table showing the mapping between Luna user types & their PKCS11 constant equivalents 135 | 136 | Note that the user type is context-dependent -- some users are only valid on the Admin partition, 137 | and some are only valid on a User partition, and some are dual-use. 138 | 139 | 140 | +----------------------------+----------------------------+-----------------+ 141 | | Luna User | PKCS11 User | Partition | 142 | +============================+============================+=================+ 143 | | Security Officer | CKU_SO | Admin Partition | 144 | +----------------------------+----------------------------+-----------------+ 145 | | Administrator | CKU_USER | Admin Partition | 146 | +----------------------------+----------------------------+-----------------+ 147 | | Auditor | CKU_AUDIT | Admin Partition | 148 | +----------------------------+----------------------------+-----------------+ 149 | | Partition Security Officer | CKU_SO | User Partition | 150 | +----------------------------+----------------------------+-----------------+ 151 | | Crypto Officer | CKU_USER | User Partition | 152 | +----------------------------+----------------------------+-----------------+ 153 | | Crypto User | CKU_LIMITED_USER | User Partition | 154 | +----------------------------+----------------------------+-----------------+ 155 | | Limited Crypto Officer | CKU_LIMITED_CRYPTO_OFFICER | User Partition | 156 | +----------------------------+----------------------------+-----------------+ 157 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | -------------------------------------------------------------------------------- /pycryptoki/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = tests/* 3 | branch = True 4 | -------------------------------------------------------------------------------- /pycryptoki/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.getLogger(__name__).addHandler(logging.NullHandler()) 4 | -------------------------------------------------------------------------------- /pycryptoki/audit_handling.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods responsible for managing a user's session and login/c_logout 3 | """ 4 | import logging 5 | from ctypes import cast, c_ulong, byref 6 | 7 | from .cryptoki import CK_ULONG, CA_TimeSync, CA_InitAudit, CK_SLOT_ID, CA_GetTime, CK_CHAR_PTR 8 | from .exceptions import make_error_handle_function 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def ca_init_audit(slot, audit_pin, audit_label): 14 | """ 15 | 16 | :param slot: 17 | :param audit_pin: 18 | :param audit_label: 19 | 20 | """ 21 | if audit_pin == "": 22 | ret = CA_InitAudit(CK_SLOT_ID(slot), None, CK_ULONG(0), cast(audit_label, CK_CHAR_PTR)) 23 | else: 24 | ret = CA_InitAudit( 25 | CK_SLOT_ID(slot), 26 | cast(audit_pin, CK_CHAR_PTR), 27 | CK_ULONG(len(audit_pin)), 28 | cast(audit_label, CK_CHAR_PTR), 29 | ) 30 | return ret 31 | 32 | 33 | ca_init_audit_ex = make_error_handle_function(ca_init_audit) 34 | 35 | 36 | def ca_time_sync(h_session, ultime): 37 | """ 38 | 39 | :param int h_session: Session handle 40 | :param ultime: 41 | 42 | """ 43 | 44 | ret = CA_TimeSync(h_session, CK_ULONG(ultime)) 45 | return ret 46 | 47 | 48 | ca_time_sync_ex = make_error_handle_function(ca_time_sync) 49 | 50 | 51 | def ca_get_time(h_session): 52 | """ 53 | 54 | :param int h_session: Session handle 55 | 56 | """ 57 | 58 | hsm_time = c_ulong() 59 | 60 | ret = CA_GetTime(h_session, byref(hsm_time)) 61 | return ret, hsm_time 62 | 63 | 64 | ca_get_time_ex = make_error_handle_function(ca_get_time) 65 | -------------------------------------------------------------------------------- /pycryptoki/ca_extensions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThalesGroup/pycryptoki/ae98be3b9271e90fe9d38905884bfae79bd7dbe0/pycryptoki/ca_extensions/__init__.py -------------------------------------------------------------------------------- /pycryptoki/ca_extensions/bip32.py: -------------------------------------------------------------------------------- 1 | """ 2 | BIP32 Functions 3 | """ 4 | from ctypes import string_at, byref 5 | 6 | from pycryptoki.attributes import Attributes 7 | from pycryptoki.common_utils import AutoCArray 8 | from pycryptoki.cryptoki.func_defs import CA_Bip32ExportPublicKey, CA_Bip32ImportPublicKey 9 | from pycryptoki.cryptoki.c_defs import CK_ULONG, CK_BYTE 10 | from pycryptoki.defines import CKR_OK, CKG_BIP32_MAX_SERIALIZED_LEN 11 | from pycryptoki.exceptions import make_error_handle_function 12 | 13 | 14 | def ca_bip32_import_public_key(session, key_data, attributes): 15 | """ 16 | Imports a BIP32 Key to the HSM. 17 | 18 | :param int session: Session handle. 19 | :param bytes key_data: Key data, in bytes (base58 encoded) 20 | :param dict attributes: Attributes for the key. 21 | :return: retcode, key_handle. 22 | """ 23 | attrs = Attributes(attributes).get_c_struct() 24 | c_key_data = AutoCArray(ctype=CK_BYTE, data=key_data) 25 | key_handle = CK_ULONG() 26 | ret = CA_Bip32ImportPublicKey( 27 | session, c_key_data.array, len(c_key_data), attrs, len(attributes), byref(key_handle) 28 | ) 29 | if ret != CKR_OK: 30 | return ret, None 31 | 32 | return ret, key_handle.value 33 | 34 | 35 | ca_bip32_import_public_key_ex = make_error_handle_function(ca_bip32_import_public_key) 36 | 37 | 38 | def ca_bip32_export_public_key(session, key): 39 | """ 40 | Exports a BIP32 Key from the HSM. 41 | 42 | :param int session: Session handle. 43 | :return: retcode, key data (base58 encoded bytestring) 44 | """ 45 | c_key_data = AutoCArray(ctype=CK_BYTE, size=CK_ULONG(CKG_BIP32_MAX_SERIALIZED_LEN)) 46 | ret = CA_Bip32ExportPublicKey(session, key, c_key_data.array, c_key_data.size) 47 | if ret != CKR_OK: 48 | return ret, None 49 | 50 | return ret, string_at(c_key_data.array, c_key_data.size.contents.value) 51 | 52 | 53 | ca_bip32_export_public_key_ex = make_error_handle_function(ca_bip32_export_public_key) 54 | -------------------------------------------------------------------------------- /pycryptoki/ca_extensions/cpv4.py: -------------------------------------------------------------------------------- 1 | """ 2 | cpv4 ca extensions 3 | """ 4 | import logging 5 | from collections import namedtuple 6 | from copy import deepcopy 7 | from ctypes import c_uint32, byref, create_string_buffer, c_ubyte, pointer, c_uint, cast, string_at 8 | from _ctypes import POINTER 9 | 10 | from pycryptoki.conversions import from_bytestring 11 | from pycryptoki.defines import CKR_OK 12 | from pycryptoki.cryptoki import ( 13 | CA_MigrateKeys, 14 | CA_MigrationStartSessionNegotiation, 15 | CA_MigrationContinueSessionNegotiation, 16 | CA_MigrationCloseSession, 17 | CK_ULONG, 18 | CK_BYTE_PTR, 19 | CK_SESSION_HANDLE, 20 | CK_OBJECT_MIGRATION_DATA, 21 | ) 22 | 23 | from pycryptoki.attributes import to_byte_array 24 | 25 | from pycryptoki.exceptions import make_error_handle_function 26 | 27 | 28 | LOG = logging.getLogger(__name__) 29 | MIGRATION_KEYS = ["object_type", "source_handle"] 30 | MIGRATION_DATA = namedtuple("MIGRATION_DATA", deepcopy(MIGRATION_KEYS)) 31 | PCPROT_MAX_BUFFER_SIZE = 64000 32 | 33 | 34 | def get_mig_data_c_struct(mig_data_list): 35 | """ 36 | Build an array of :class:`~pycryptoki.cryptoki.CK_OBJECT_MIGRATION_DATA` Structs & return it. 37 | 38 | :return: :class:`~pycryptoki.cryptoki.CK_OBJECT_MIGRATION_DATA` array 39 | """ 40 | ret_struct = (CK_OBJECT_MIGRATION_DATA * len(mig_data_list))() 41 | for index, mig_data in enumerate(mig_data_list): 42 | object_type, source_handle = mig_data 43 | ret_struct[index] = CK_OBJECT_MIGRATION_DATA( 44 | objectType=object_type, sourceHandle=source_handle 45 | ) 46 | return ret_struct 47 | 48 | 49 | def ca_migrate_keys( 50 | source_session, target_session, migration_flags, num_objects, objects_to_migrate 51 | ): 52 | """ 53 | Runs CA_MigrateKeys command 54 | 55 | :param int source_session: session opened on Source partition 56 | :param int target_session: session opened on Target partition 57 | :param int migration_flags: Flags 58 | :param int num_objects: Number of objects to migrate. 59 | :param objects_to_migrate: a list of tuples (objectType, sourceHandle) or list of 60 | `~pycryptoki.ca_extensions.cpv4.MIGRATION_DATA` namedtuples 61 | """ 62 | objects_to_migrate = ( 63 | objects_to_migrate if isinstance(objects_to_migrate, list) else [objects_to_migrate] 64 | ) 65 | c_mig_data = get_mig_data_c_struct(objects_to_migrate) 66 | 67 | ret = CA_MigrateKeys(source_session, target_session, migration_flags, num_objects, c_mig_data) 68 | 69 | return ret, [(data.rv, data.targetHandle) for data in c_mig_data] 70 | 71 | 72 | ca_migrate_keys_ex = make_error_handle_function(ca_migrate_keys) 73 | 74 | 75 | def ca_migration_start_session_negotiation(target_session, input_data=None): 76 | """ 77 | Runs CA_MigrationStartSessionNegotiation command 78 | 79 | :param int target_session: target slot session 80 | :param bytes input_data: Input data for negotiating Migration 81 | """ 82 | output_data = (c_ubyte * PCPROT_MAX_BUFFER_SIZE)() 83 | output_data_len = CK_ULONG(PCPROT_MAX_BUFFER_SIZE) 84 | out_step = CK_ULONG() 85 | 86 | if input_data is None: 87 | input_data_len = 0 88 | else: 89 | input_data, input_data_len = to_byte_array(from_bytestring(input_data)) 90 | input_data = cast(input_data, POINTER(c_ubyte)) 91 | 92 | ret = CA_MigrationStartSessionNegotiation( 93 | target_session, 94 | input_data_len, 95 | input_data, 96 | byref(out_step), 97 | byref(output_data_len), 98 | output_data, 99 | ) 100 | 101 | if ret != CKR_OK: 102 | return ret, {} 103 | 104 | return ret, {"output": string_at(output_data, output_data_len.value), "step": out_step.value} 105 | 106 | 107 | ca_migration_start_session_negotiation_ex = make_error_handle_function( 108 | ca_migration_start_session_negotiation 109 | ) 110 | 111 | 112 | def ca_migration_continue_session_negotiation( 113 | target_session, input_step, input_data, input_session_ouid=None 114 | ): 115 | """ 116 | Runs CA_MigrationContinueSessionNegotiation 117 | 118 | :param int target_session: Session handle 119 | :param int input_step: TBD 120 | :param bytes input_data: TBD 121 | :param bytes session_ouid: Session OUID. 122 | :return: Retcode, Dictionary. 123 | """ 124 | output_step = CK_ULONG() 125 | output_data = (c_ubyte * PCPROT_MAX_BUFFER_SIZE)() 126 | output_data_len = CK_ULONG(PCPROT_MAX_BUFFER_SIZE) 127 | status = CK_ULONG() 128 | output_session_ouid = None 129 | output_session_ouid_len = None 130 | input_session_ouid_len = CK_ULONG() 131 | 132 | if input_session_ouid is None: 133 | output_session_ouid = (c_ubyte * PCPROT_MAX_BUFFER_SIZE)() 134 | output_session_ouid_len = CK_ULONG(PCPROT_MAX_BUFFER_SIZE) 135 | output_session_ouid_len = pointer(output_session_ouid_len) 136 | else: 137 | input_session_ouid, input_session_ouid_len = to_byte_array( 138 | from_bytestring(input_session_ouid) 139 | ) 140 | input_session_ouid = cast(input_session_ouid, POINTER(c_ubyte)) 141 | 142 | input_data, input_len = to_byte_array(from_bytestring(input_data)) 143 | input_data = cast(input_data, POINTER(c_ubyte)) 144 | 145 | ret = CA_MigrationContinueSessionNegotiation( 146 | target_session, 147 | input_step, 148 | input_len, 149 | input_data, 150 | input_session_ouid_len, 151 | input_session_ouid, 152 | byref(output_step), 153 | byref(output_data_len), 154 | output_data, 155 | byref(status), 156 | output_session_ouid_len, 157 | output_session_ouid, 158 | ) 159 | if ret != CKR_OK: 160 | return ret, {} 161 | 162 | ret_dict = { 163 | "output": string_at(output_data, output_data_len.value), 164 | "step": output_step.value, 165 | "status": status.value, 166 | } 167 | 168 | if input_session_ouid is None: 169 | ret_dict["session_ouid"] = string_at( 170 | output_session_ouid, output_session_ouid_len.contents.value 171 | ) 172 | 173 | return (ret, ret_dict) 174 | 175 | 176 | ca_migration_continue_session_negotiation_ex = make_error_handle_function( 177 | ca_migration_continue_session_negotiation 178 | ) 179 | 180 | 181 | def ca_migration_close_session(target_session, session_ouid): 182 | """ 183 | Runs CA_MigrationCloseSession 184 | 185 | :param int target_session: Session handle 186 | :param bytes session_ouid: Session OUID (in bytestring, not hex). 187 | :return: Retcode 188 | """ 189 | session_ouid, session_ouid_len = to_byte_array(from_bytestring(session_ouid)) 190 | session_ouid = cast(session_ouid, POINTER(c_ubyte)) 191 | 192 | return CA_MigrationCloseSession(target_session, session_ouid_len, session_ouid) 193 | 194 | 195 | ca_migration_close_session_ex = make_error_handle_function(ca_migration_close_session) 196 | -------------------------------------------------------------------------------- /pycryptoki/ca_extensions/derive_wrap.py: -------------------------------------------------------------------------------- 1 | """ 2 | derive and wrap extended method 3 | """ 4 | import logging 5 | from ctypes import c_ubyte, string_at 6 | 7 | from pycryptoki.defines import CKR_OK 8 | from pycryptoki.common_utils import AutoCArray 9 | from pycryptoki.attributes import Attributes 10 | from pycryptoki.cryptoki import CA_DeriveKeyAndWrap, CK_OBJECT_HANDLE, CK_ULONG 11 | from pycryptoki.mechanism import parse_mechanism 12 | from pycryptoki.exceptions import make_error_handle_function 13 | 14 | LOG = logging.getLogger(__name__) 15 | 16 | 17 | def ca_derive_key_and_wrap( 18 | h_session, 19 | derive_mechanism, 20 | h_base_key, 21 | derive_template, 22 | wrapping_key, 23 | wrap_mechanism, 24 | output_buffer=2048, 25 | ): 26 | """ 27 | Derive a key from the base key and wrap it off the HSM using the wrapping key 28 | 29 | :param int h_session: The session to use 30 | :param int h_base_key: The base key 31 | :param dict derive_template: A python template of attributes to set on derived key 32 | :param derive_mechanism: See the :py:func:`~pycryptoki.mechanism.parse_mechanism` function 33 | for possible values. 34 | :param int wrapping_key: The wrapping key based on the encryption flavor 35 | :param wrap_mechanism: See the :py:func:`~pycryptoki.mechanism.parse_mechanism` function 36 | for possible values. 37 | :param output_buffer: The size of the wrapped key, defaulted to a cert size 38 | :returns: (Retcode, python bytestring representing wrapped key) 39 | :rtype: tuple 40 | """ 41 | # derive key parameters preparation 42 | derive_mech = parse_mechanism(derive_mechanism) 43 | c_template = Attributes(derive_template).get_c_struct() 44 | # wrapping key parameter preparation 45 | wrap_mech = parse_mechanism(wrap_mechanism) 46 | # derive key and wrap function requires the size and in that way is 47 | # inconsistent with wrap function 48 | size = CK_ULONG(output_buffer) 49 | wrapped_key = AutoCArray(ctype=c_ubyte, size=size) 50 | 51 | ret = CA_DeriveKeyAndWrap( 52 | h_session, 53 | derive_mech, 54 | CK_OBJECT_HANDLE(h_base_key), 55 | c_template, 56 | CK_ULONG(len(derive_template)), 57 | wrap_mech, 58 | CK_OBJECT_HANDLE(wrapping_key), 59 | wrapped_key.array, 60 | wrapped_key.size, 61 | ) 62 | 63 | if ret != CKR_OK: 64 | return ret, None 65 | 66 | return ret, string_at(wrapped_key.array, wrapped_key.size.contents.value) 67 | 68 | 69 | ca_derive_key_and_wrap_ex = make_error_handle_function(ca_derive_key_and_wrap) 70 | -------------------------------------------------------------------------------- /pycryptoki/ca_extensions/hsm_info.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods responsible for retrieving hsm info from the K7 card 3 | """ 4 | import logging 5 | from ctypes import c_ulong, byref, cast, POINTER 6 | from pycryptoki.cryptoki import ( 7 | CK_ULONG, 8 | CA_GetNumberOfAllowedContainers, 9 | CA_RetrieveLicenseList, 10 | CA_GetHSMStorageInformation, 11 | CA_GetTSV, 12 | CA_GetCVFirmwareVersion, 13 | ) 14 | from pycryptoki.exceptions import make_error_handle_function 15 | from pycryptoki.defines import CKR_OK 16 | 17 | LOG = logging.getLogger(__name__) 18 | 19 | 20 | def ca_retrieve_license_list(slot): 21 | """Gets the license info for a given slot id 22 | 23 | :param int slot_id: Slot index to get the license id's 24 | :returns: (A python list representing the license id's) 25 | :rtype: list 26 | """ 27 | 28 | license_len = c_ulong() 29 | ret = CA_RetrieveLicenseList(slot, byref(license_len), None) 30 | if ret == CKR_OK: 31 | licenses = (c_ulong * license_len.value)() 32 | ret = CA_RetrieveLicenseList(slot, license_len, cast(licenses, POINTER(c_ulong))) 33 | LOG.info("Getting license id. slot=%s", slot) 34 | if ret != CKR_OK: 35 | return ret, [] 36 | else: 37 | return ret, [] 38 | return ret, [(licenses[x], licenses[x + 1]) for x in range(0, license_len.value, 2)] 39 | 40 | 41 | ca_retrieve_license_list_ex = make_error_handle_function(ca_retrieve_license_list) 42 | 43 | 44 | def ca_retrieve_allowed_containers(slot): 45 | """Gets the maximum allowed container number for a given slot id 46 | 47 | :param int slot_id: Slot index to get the maximum allowed container number 48 | :returns: (ret code, A unsigned integer representing the maximum allowed container number) 49 | :rtype: unsigned integer 50 | """ 51 | 52 | allowed_partition_number = c_ulong() 53 | ret = CA_GetNumberOfAllowedContainers(slot, byref(allowed_partition_number)) 54 | LOG.info("Getting allowed maximum container number. slot=%s", slot) 55 | return ret, allowed_partition_number 56 | 57 | 58 | ca_retrieve_allowed_containers_ex = make_error_handle_function(ca_retrieve_allowed_containers) 59 | 60 | 61 | def ca_retrieve_hsm_storage_info(slot): 62 | """Gets the hsm storage info for a given slot id 63 | 64 | :param int slot_id: Slot index to get the hsm storage info 65 | :returns: (ret code, hsm_storage_info dictionary) 66 | :rtype: dictionary 67 | """ 68 | 69 | hsm_storage_info = {} 70 | 71 | container_overhead = c_ulong() 72 | total_hsm_storage = c_ulong() 73 | used_hsm_storage = c_ulong() 74 | free_hsm_storage = c_ulong() 75 | ret = CA_GetHSMStorageInformation( 76 | slot, 77 | byref(container_overhead), 78 | byref(total_hsm_storage), 79 | byref(used_hsm_storage), 80 | byref(free_hsm_storage), 81 | ) 82 | LOG.info("Getting allowed maximum container number. slot=%s", slot) 83 | 84 | if ret == CKR_OK: 85 | hsm_storage_info["ContainerOverhead"] = container_overhead 86 | hsm_storage_info["TotalHsmStorage"] = total_hsm_storage 87 | hsm_storage_info["UsedHsmStorage"] = used_hsm_storage 88 | hsm_storage_info["FreeHsmStorage"] = free_hsm_storage 89 | return ret, hsm_storage_info 90 | 91 | 92 | ca_retrieve_hsm_storage_info_ex = make_error_handle_function(ca_retrieve_hsm_storage_info) 93 | 94 | 95 | def ca_get_tsv(slot): 96 | """Get the TSV(Module State Vector) for a given slot id 97 | 98 | :param int slot_id: Slot index to get the TSV(Module State Vector) 99 | :returns: (ret code, TSV) 100 | :rtype: tuple 101 | """ 102 | 103 | tsv = c_ulong() 104 | ret = CA_GetTSV(slot, byref(tsv)) 105 | LOG.info("Getting Module state vector. slot=%s", slot) 106 | return ret, tsv 107 | 108 | 109 | ca_get_tsv_ex = make_error_handle_function(ca_get_tsv) 110 | 111 | 112 | def ca_get_cv_firmware_version(slot_id): 113 | """ 114 | Cryptovisor specific ca extension function to get cv fw version 115 | 116 | :param slot_id: slot id 117 | :return: tuple of return code and cv fw version 118 | """ 119 | major = CK_ULONG() 120 | minor = CK_ULONG() 121 | sub_minor = CK_ULONG() 122 | ret = CA_GetCVFirmwareVersion(CK_ULONG(slot_id), byref(major), byref(minor), byref(sub_minor)) 123 | if ret != CKR_OK: 124 | return ret, None 125 | cv_fwv = {} 126 | cv_fwv["major"] = major.value 127 | cv_fwv["minor"] = minor.value 128 | cv_fwv["sub_minor"] = sub_minor.value 129 | 130 | return ret, cv_fwv 131 | 132 | 133 | ca_get_cv_firmware_version_ex = make_error_handle_function(ca_get_cv_firmware_version) 134 | -------------------------------------------------------------------------------- /pycryptoki/ca_extensions/object_handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to work with objects, specifically dealing with ca_extension functions 3 | """ 4 | 5 | import logging 6 | from ctypes import byref, cast, c_ubyte 7 | from _ctypes import POINTER 8 | 9 | from pycryptoki.attributes import to_byte_array 10 | from pycryptoki.ca_extensions.session import ca_get_session_info_ex 11 | from pycryptoki.cryptoki import CK_ULONG, CK_SLOT_ID, CA_GetObjectHandle, CA_DestroyMultipleObjects 12 | from pycryptoki.defines import CKR_OK 13 | from pycryptoki.exceptions import make_error_handle_function 14 | from pycryptoki.common_utils import AutoCArray 15 | 16 | 17 | LOG = logging.getLogger(__name__) 18 | 19 | 20 | def ca_get_object_handle(slot, session, objectouid): 21 | """ 22 | Calls CA_GetObjectHandle to get the object handle from OUID 23 | 24 | :param slot: partition slot number 25 | :param session: session id that was opened to run the function 26 | :param objectouid: OUID, a string of the hex value that maps to object handle 27 | :return: a tuple containing the return code and the object handle mapping the given OUID 28 | """ 29 | objecttype = CK_ULONG() 30 | objecthandle = CK_ULONG() 31 | # ulContainerNumber is required which is of type CK_ULONG 32 | container_number = ca_get_session_info_ex(session)["containerNumber"] 33 | ouid, size_ouid = to_byte_array(int(objectouid, 16)) 34 | c_ouid = cast(ouid, POINTER(c_ubyte)) 35 | 36 | ret = CA_GetObjectHandle( 37 | CK_SLOT_ID(slot), container_number, c_ouid, byref(objecttype), byref(objecthandle) 38 | ) 39 | if ret != CKR_OK: 40 | return ret, None 41 | 42 | return ret, objecthandle.value 43 | 44 | 45 | ca_get_object_handle_ex = make_error_handle_function(ca_get_object_handle) 46 | 47 | 48 | def ca_destroy_multiple_objects(h_session, objects): 49 | """Delete multiple objects corresponding to given object handles 50 | 51 | :param int h_session: Session handle 52 | :param list objects: The handles of the objects to delete 53 | :returns: Return code 54 | """ 55 | handles_count = len(objects) 56 | handles = AutoCArray(data=objects, ctype=CK_ULONG) 57 | ret = CA_DestroyMultipleObjects(h_session, handles_count, handles.array, byref(CK_ULONG())) 58 | return ret 59 | 60 | 61 | ca_destroy_multiple_objects_ex = make_error_handle_function(ca_destroy_multiple_objects) 62 | -------------------------------------------------------------------------------- /pycryptoki/ca_extensions/per_key_auth.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to work with PKA / Per key authorization 3 | """ 4 | from ctypes import cast 5 | from _ctypes import POINTER 6 | from pycryptoki.attributes import to_byte_array 7 | from pycryptoki.cryptoki import ( 8 | CA_SetAuthorizationData, 9 | CA_ResetAuthorizationData, 10 | CA_AuthorizeKey, 11 | CA_AssignKey, 12 | CA_IncrementFailedAuthCount, 13 | CK_ULONG, 14 | ) 15 | from pycryptoki.cryptoki import CK_SESSION_HANDLE, CK_OBJECT_HANDLE, CK_UTF8CHAR 16 | from pycryptoki.exceptions import make_error_handle_function 17 | 18 | 19 | def ca_set_authorization_data(h_session, h_object, old_auth_data, new_auth_data): 20 | """ 21 | User changes authorization data on key object (private, secret) 22 | 23 | :param h_session: session handle 24 | :param object: key handle to update 25 | :param old_auth_data: byte list, e.g. [11, 12, 13, ..] 26 | :param new_auth_data: byte list, e.g. [11, 12, 13, ..] 27 | :return: Ret code 28 | """ 29 | old_auth_data_ptr, old_auth_data_length = to_byte_array(old_auth_data) 30 | old_auth_data_ptr = cast(old_auth_data_ptr, POINTER(CK_UTF8CHAR)) 31 | 32 | new_auth_data_ptr, new_auth_data_length = to_byte_array(new_auth_data) 33 | new_auth_data_ptr = cast(new_auth_data_ptr, POINTER(CK_UTF8CHAR)) 34 | 35 | h_object = CK_OBJECT_HANDLE(h_object) 36 | h_session = CK_SESSION_HANDLE(h_session) 37 | 38 | return CA_SetAuthorizationData( 39 | h_session, 40 | h_object, 41 | old_auth_data_ptr, 42 | old_auth_data_length, 43 | new_auth_data_ptr, 44 | new_auth_data_length, 45 | ) 46 | 47 | 48 | ca_set_authorization_data_ex = make_error_handle_function(ca_set_authorization_data) 49 | 50 | 51 | def ca_reset_authorization_data(h_session, h_object, auth_data): 52 | """ 53 | CO resets auth data on unassigned key 54 | 55 | :param h_session: session handle 56 | :param object: key handle to update 57 | :param auth_data: byte list, e.g. [11, 12, 13, ..] 58 | :return: Ret code 59 | """ 60 | auth_data_ptr, auth_data_length = to_byte_array(auth_data) 61 | auth_data_ptr = cast(auth_data_ptr, POINTER(CK_UTF8CHAR)) 62 | 63 | h_object = CK_OBJECT_HANDLE(h_object) 64 | h_session = CK_SESSION_HANDLE(h_session) 65 | 66 | return CA_ResetAuthorizationData(h_session, h_object, auth_data_ptr, auth_data_length) 67 | 68 | 69 | ca_reset_authorization_data_ex = make_error_handle_function(ca_reset_authorization_data) 70 | 71 | 72 | def ca_increment_failed_auth_count(h_session, h_object): 73 | """ 74 | This function is called by HA group when auth failure happens on a key 75 | to sync up status. Here its defined mostly for testing purposes 76 | :param h_session: session handle 77 | :param object: key handle to update 78 | :return: Ret code 79 | """ 80 | h_object = CK_OBJECT_HANDLE(h_object) 81 | h_session = CK_SESSION_HANDLE(h_session) 82 | 83 | return CA_IncrementFailedAuthCount(h_session, h_object) 84 | 85 | 86 | ca_increment_failed_auth_count_ex = make_error_handle_function(ca_increment_failed_auth_count) 87 | 88 | 89 | def ca_authorize_key(h_session, h_object, auth_data): 90 | """ 91 | User authorizes key within session or access for use 92 | 93 | :param h_session: session handle 94 | :param object: key handle to authorize 95 | :param auth_data: authorization byte list, e.g. [11, 12, 13, ..] 96 | :return: Ret code 97 | """ 98 | if auth_data is not None: 99 | auth_data_ptr, auth_data_length = to_byte_array(auth_data) 100 | auth_data_ptr = cast(auth_data_ptr, POINTER(CK_UTF8CHAR)) 101 | else: 102 | auth_data_ptr = None 103 | auth_data_length = CK_ULONG(0) 104 | 105 | h_object = CK_OBJECT_HANDLE(h_object) 106 | h_session = CK_SESSION_HANDLE(h_session) 107 | 108 | return CA_AuthorizeKey(h_session, h_object, auth_data_ptr, auth_data_length) 109 | 110 | 111 | ca_authorize_key_ex = make_error_handle_function(ca_authorize_key) 112 | 113 | 114 | def ca_assign_key(h_session, h_object): 115 | """ 116 | Crypto Officer assigns a key 117 | 118 | :param h_session: session handle 119 | :param object: key handle to assign 120 | :return: Ret code 121 | """ 122 | 123 | h_object = CK_OBJECT_HANDLE(h_object) 124 | h_session = CK_SESSION_HANDLE(h_session) 125 | 126 | return CA_AssignKey(h_session, h_object) 127 | 128 | 129 | ca_assign_key_ex = make_error_handle_function(ca_assign_key) 130 | -------------------------------------------------------------------------------- /pycryptoki/ca_extensions/session.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to work with sessions, specifically dealing with ca_extension functions 3 | """ 4 | 5 | import logging 6 | from ctypes import byref, string_at, sizeof 7 | 8 | from pycryptoki.conversions import from_bytestring 9 | from pycryptoki.cryptoki import ( 10 | CK_FLAGS, 11 | CK_NOTIFY, 12 | CK_ULONG, 13 | CK_SESSION_HANDLE, 14 | CA_GetSessionInfo, 15 | CA_GetApplicationID, 16 | CK_APPLICATION_ID, 17 | CK_SLOT_ID, 18 | CA_OpenApplicationIDV2, 19 | CA_CloseApplicationIDV2, 20 | CA_SetApplicationIDV2, 21 | ) 22 | from pycryptoki.cryptoki.func_defs import ( 23 | CA_RandomizeApplicationID, 24 | CA_OpenSessionWithAppIDV2, 25 | CA_OpenApplicationIDForContainerV2, 26 | CA_CloseApplicationIDForContainerV2, 27 | CA_GetUserContainerNumber, 28 | CA_SessionCancel, 29 | ) 30 | from pycryptoki.defines import CKR_OK 31 | from pycryptoki.exceptions import make_error_handle_function 32 | 33 | LOG = logging.getLogger(__name__) 34 | 35 | 36 | def ca_get_session_info(session): 37 | """ 38 | ca extension function that returns session information 39 | 40 | :param session: session handle 41 | :return: tuple of return code and session info dict 42 | """ 43 | session_info = {} 44 | h_session = CK_SESSION_HANDLE(session) 45 | aid_hi = CK_ULONG() 46 | aid_lo = CK_ULONG() 47 | container = CK_ULONG() 48 | auth_level = CK_ULONG() 49 | ret = CA_GetSessionInfo( 50 | h_session, byref(aid_hi), byref(aid_lo), byref(container), byref(auth_level) 51 | ) 52 | if ret != CKR_OK: 53 | return ret, None 54 | 55 | session_info["aidHigh"] = aid_hi.value 56 | session_info["aidLow"] = aid_lo.value 57 | session_info["containerNumber"] = container.value 58 | session_info["authenticationLevel"] = auth_level.value 59 | 60 | return ret, session_info 61 | 62 | 63 | ca_get_session_info_ex = make_error_handle_function(ca_get_session_info) 64 | 65 | 66 | def ca_randomize_application_id(): 67 | """Randomize the application ID in use""" 68 | return CA_RandomizeApplicationID() 69 | 70 | 71 | ca_randomize_application_id_ex = make_error_handle_function(ca_randomize_application_id) 72 | 73 | 74 | def ca_get_application_id(): 75 | """ 76 | Get the current process's AccessID. 77 | 78 | :return: retcode, bytestring tuple. 79 | """ 80 | dest = CK_APPLICATION_ID() 81 | ret = CA_GetApplicationID(byref(dest)) 82 | if ret != CKR_OK: 83 | return ret, None 84 | return ret, string_at(dest.id, sizeof(dest.id)) 85 | 86 | 87 | ca_get_application_id_ex = make_error_handle_function(ca_get_application_id) 88 | 89 | 90 | def ca_open_application_id_v2(slot, appid): 91 | """ 92 | Open the given AccessID for the target slot. 93 | 94 | :param slot: Slot #. 95 | :param appid: bytestring of length 16. 96 | :return: Retcode. 97 | """ 98 | appid = from_bytestring(appid) 99 | access_id = CK_APPLICATION_ID(appid) 100 | return CA_OpenApplicationIDV2(CK_SLOT_ID(slot), byref(access_id)) 101 | 102 | 103 | ca_open_application_id_v2_ex = make_error_handle_function(ca_open_application_id_v2) 104 | 105 | 106 | def ca_close_application_id_v2(slot, appid): 107 | """ 108 | Close the AccessID associated with the given slot. 109 | 110 | :param slot: Slot #. 111 | :param appid: bytestring of length 16. 112 | :return: Retcode. 113 | """ 114 | appid = from_bytestring(appid) 115 | access_id = CK_APPLICATION_ID(appid) 116 | return CA_CloseApplicationIDV2(CK_SLOT_ID(slot), byref(access_id)) 117 | 118 | 119 | ca_close_application_id_v2_ex = make_error_handle_function(ca_close_application_id_v2) 120 | 121 | 122 | def ca_set_application_id_v2(appid): 123 | """ 124 | Set the Current process's AccessID. 125 | 126 | :param appid: bytestring of length 16 127 | :return: Retcode 128 | """ 129 | appid = from_bytestring(appid) 130 | access_id = CK_APPLICATION_ID(appid) 131 | return CA_SetApplicationIDV2(byref(access_id)) 132 | 133 | 134 | ca_set_application_id_v2_ex = make_error_handle_function(ca_set_application_id_v2) 135 | 136 | 137 | def ca_open_session_with_app_id_v2(slot, flags, appid, notify=None, application=None): 138 | """ 139 | Open a session with an app ID 140 | 141 | :param slot: slot number 142 | :param flags: session flags 143 | :param appid: bytestring of length 16 144 | :param notify: CK_NOTIFY callback (frequently unused in practice) 145 | :param application: also frequently unused in practice 146 | :return: 147 | """ 148 | appid = from_bytestring(appid) 149 | access_id = CK_APPLICATION_ID(appid) 150 | h_session = CK_SESSION_HANDLE() 151 | if notify is None: 152 | notify = CK_NOTIFY(0) 153 | ret = CA_OpenSessionWithAppIDV2( 154 | CK_SLOT_ID(slot), CK_FLAGS(flags), byref(access_id), application, notify, byref(h_session) 155 | ) 156 | 157 | if ret != CKR_OK: 158 | return ret, None 159 | 160 | return ret, h_session 161 | 162 | 163 | ca_open_session_with_app_id_v2_ex = make_error_handle_function(ca_open_session_with_app_id_v2) 164 | 165 | 166 | def ca_open_application_id_for_container_v2(slot, appid, container): 167 | """ 168 | Open an access ID for the container. This differs from CA_OpenApplicationID in that it 169 | facilitates the opening of an app ID for a container which was not directly accessible via slot, 170 | as was the case for pre-PPSO HSMs. Because slots map 1:1 to containers with PPSO FW, this is 171 | functionally the same as CA_OpenApplicationIDV2. 172 | 173 | :param slot: slot number 174 | :param appid: bytestring of length 16 175 | :param container: container number corresponding to slot. Can use CA_GetUserContainerNumber to 176 | get container number 177 | """ 178 | appid = from_bytestring(appid) 179 | access_id = CK_APPLICATION_ID(appid) 180 | return CA_OpenApplicationIDForContainerV2( 181 | CK_SLOT_ID(slot), byref(access_id), CK_ULONG(container) 182 | ) 183 | 184 | 185 | ca_open_application_id_for_container_v2_ex = make_error_handle_function( 186 | ca_open_application_id_for_container_v2 187 | ) 188 | 189 | 190 | def ca_close_application_id_for_container_v2(slot, appid, container): 191 | """Close an access ID for the container""" 192 | appid = from_bytestring(appid) 193 | access_id = CK_APPLICATION_ID(appid) 194 | return CA_CloseApplicationIDForContainerV2( 195 | CK_SLOT_ID(slot), byref(access_id), CK_ULONG(container) 196 | ) 197 | 198 | 199 | ca_close_application_id_for_container_v2_ex = make_error_handle_function( 200 | ca_close_application_id_for_container_v2 201 | ) 202 | 203 | 204 | def ca_session_cancel(h_session, flags): 205 | """ 206 | User cancels ongoing crypto operation 207 | 208 | :param h_session: session handle 209 | :param flags: session flags 210 | :return: Ret code 211 | """ 212 | return CA_SessionCancel(CK_SESSION_HANDLE(h_session), CK_FLAGS(flags)) 213 | 214 | 215 | ca_session_cancel_ex = make_error_handle_function(ca_session_cancel) 216 | -------------------------------------------------------------------------------- /pycryptoki/ca_extensions/utilization_metrics.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to work with utilization metrics 3 | """ 4 | import collections 5 | from ctypes import c_ulong 6 | from pycryptoki.cryptoki import ( 7 | CA_ReadUtilizationMetrics, 8 | CA_ReadAllUtilizationCounters, 9 | CA_ReadAndResetUtilizationMetrics, 10 | ) 11 | from pycryptoki.cryptoki import CK_UTILIZATION_COUNTER 12 | from pycryptoki.cryptoki import CK_SESSION_HANDLE 13 | from pycryptoki.exceptions import make_error_handle_function 14 | 15 | BIN_IDS = { 16 | 0: "SIGN", 17 | 1: "VERIFY", 18 | 2: "ENCRYPT", 19 | 3: "DECRYPT", 20 | 4: "KEY_GENERATION", 21 | 5: "KEY_DERIVATION", 22 | } 23 | 24 | 25 | def ca_read_utilization_metrics(session): 26 | """ 27 | HSM reads utilization data and saves as a snapshot 28 | 29 | :param session: session id that was opened to run the function 30 | :return: Ret code 31 | """ 32 | h_session = CK_SESSION_HANDLE(session) 33 | return CA_ReadUtilizationMetrics(h_session) 34 | 35 | 36 | ca_read_utilization_metrics_ex = make_error_handle_function(ca_read_utilization_metrics) 37 | 38 | 39 | def ca_read_and_reset_utilization_metrics(session): 40 | """ 41 | HSM reads current utilization data and saves as a snapshot; 42 | HSM resets metrics to zeroes 43 | 44 | :param session: session id that was opened to run the function 45 | :return: a dictionary with partition serial numbers as keys, 46 | value - dictionary of utilization metrics 47 | """ 48 | h_session = CK_SESSION_HANDLE(session) 49 | 50 | return CA_ReadAndResetUtilizationMetrics(h_session) 51 | 52 | 53 | ca_read_and_reset_utilization_metrics_ex = make_error_handle_function( 54 | ca_read_and_reset_utilization_metrics 55 | ) 56 | 57 | 58 | def ca_read_all_utilization_counters(h_session): 59 | """ 60 | Read Metrics from previously saved HSM snapshot 61 | Call either functions prior to create snapshot: 62 | ca_read_utilization_metrics 63 | ca_read_and_reset_utilization_metrics 64 | 65 | :return: a dictionary, where keys are serial numbers 66 | and values are dictionaries of bins and values, example: 'SIGN':0 67 | """ 68 | # Reading length of counters 69 | length = c_ulong() 70 | CA_ReadAllUtilizationCounters(h_session, None, length) 71 | 72 | arr = (CK_UTILIZATION_COUNTER * length.value)() 73 | # Reading actual Metrics 74 | ret = CA_ReadAllUtilizationCounters(h_session, arr, length) 75 | 76 | # HSM returns a list of dictionaries of all counters 77 | # Restructuting this list as a dictionary 78 | partitions = collections.defaultdict(dict) 79 | for counter in arr: 80 | partitions[str(counter.ullSerialNumber)][BIN_IDS[counter.ulBindId]] = counter.ullCount 81 | 82 | return ret, partitions 83 | 84 | 85 | ca_read_all_utilization_counters_ex = make_error_handle_function(ca_read_all_utilization_counters) 86 | -------------------------------------------------------------------------------- /pycryptoki/conversions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provide low-level conversions between common data types. 3 | 4 | The ``from_xyz`` functions should all return an iterator over a list of integers, 5 | representing the individual bytes in the passed-in value. 6 | 7 | The ``to_xyz`` functions take in an iterable of integers and convert it to the specified type. 8 | 9 | 10 | 11 | **Example 1** 12 | 13 | .. code-block:: python 14 | :caption: Convert a raw bytestring to hex 15 | 16 | raw_bytes = from_bytestring(b"Some test data") 17 | assert raw_bytes = [83, 111, 109, 101, 32, 116, 101, 115, 116, 32, 100, 97, 116, 97] 18 | 19 | hex_data = to_hex(from_bytestring(b"Some test data")) 20 | assert hex_data == b'536f6d6520746573742064617461' 21 | 22 | 23 | **Example 2** 24 | 25 | .. code-block:: python 26 | :caption: Convert hex data to a raw bytestring 27 | 28 | bytestring_data = to_bytestring(from_hex(b'536f6d6520746573742064617461')) 29 | assert bytestring_data == b"Some test data" 30 | 31 | raw_bytes = list(from_hex(b'536f6d6520746573742064617461')) 32 | assert raw_bytes == [83, 111, 109, 101, 32, 116, 101, 115, 116, 32, 100, 97, 116, 97] 33 | 34 | 35 | """ 36 | from six import b 37 | 38 | 39 | def _chunks(inval, chunk_size): 40 | """ 41 | Split an iterable into chunks of the given size. 42 | 43 | :param inval: Iterable to be chunked. 44 | :param chunk_size: Size of chunks. 45 | :return: Iterator 46 | """ 47 | for i in range(0, len(inval), chunk_size): 48 | yield inval[i : i + chunk_size] 49 | 50 | 51 | def from_bytestring(ascii_): 52 | """ 53 | Convert an iterable of strings into an iterable of integers. 54 | 55 | .. note:: For bytestrings on python3, this does effectively nothing, since 56 | iterating over a bytestring in python 3 will return integers. 57 | 58 | :param ascii_: String to convert 59 | :return: iterator 60 | """ 61 | for c in ascii_: 62 | try: 63 | yield ord(c) 64 | except TypeError: 65 | yield c 66 | 67 | 68 | def to_bytestring(ascii_): 69 | """ 70 | Convert an iterable of integers into a bytestring. 71 | 72 | :param iterable ascii_: Iterable of integers 73 | :return: bytestring 74 | """ 75 | return b("".join(chr(a) for a in ascii_)) 76 | 77 | 78 | def from_bin(bin_): 79 | """ 80 | Convert a string-representation of binary into a list 81 | of integers. 82 | 83 | :param str bin_: String representation of binary data (ex: "10110111") 84 | :return: iterator over integers 85 | """ 86 | for chunk in _chunks(bin_, 8): 87 | yield int(chunk, 2) 88 | 89 | 90 | def to_bin(ascii_): 91 | """ 92 | Convert an iterable of integers to a binary representation. 93 | 94 | :param iterable ascii_: iterable of integers 95 | :return: bytestring of the binary values 96 | """ 97 | return b"".join(b("{:08b}".format(a)) for a in ascii_) 98 | 99 | 100 | def from_hex(hex_): 101 | """ 102 | Convert a hexademical string to an iterable of integers. 103 | 104 | :param str hex_: Hex string 105 | :return: Iterator 106 | """ 107 | for chunk in _chunks(hex_, 2): 108 | yield int(chunk, 16) 109 | 110 | 111 | def to_hex(ints): 112 | """ 113 | Convert an iterable of integers to a hexadecimal string. 114 | 115 | :param iterable ints: Iterable of integers 116 | :return: bytestring representing the hex data. 117 | """ 118 | return b"".join(b("{:02x}".format(a)) for a in ints) 119 | -------------------------------------------------------------------------------- /pycryptoki/cryptoki/c_defs.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file should contain the 'redefinitions' of ctypes to common PKCS11 CK types. 3 | 4 | These types are the most basic types used in PKCS11. 5 | """ 6 | from ctypes import ( 7 | c_ulong, 8 | c_ubyte, 9 | POINTER, 10 | c_ulonglong, 11 | c_void_p, 12 | c_long, 13 | Structure, 14 | string_at, 15 | sizeof, 16 | ) 17 | 18 | CK_BYTE = c_ubyte 19 | CK_BYTE_PTR = POINTER(CK_BYTE) 20 | 21 | CK_LONG = c_long 22 | 23 | CK_ULONG = c_ulong 24 | CK_ULONG_PTR = POINTER(CK_ULONG) 25 | 26 | CK_UTF8CHAR = CK_BYTE 27 | CK_UTF8CHAR_PTR = POINTER(CK_UTF8CHAR) 28 | CK_RV = CK_ULONG 29 | 30 | CK_ULONGLONG = c_ulonglong 31 | 32 | CK_VOID_PTR = c_void_p 33 | CK_VOID_PTR_PTR = POINTER(CK_VOID_PTR) 34 | 35 | 36 | CK_FLAGS = CK_ULONG 37 | CK_SLOT_ID = CK_ULONG 38 | CK_SLOT_ID_PTR = POINTER(CK_SLOT_ID) 39 | 40 | CK_USHORT = c_ulong 41 | CK_USHORT_PTR = POINTER(CK_USHORT) 42 | 43 | 44 | CK_CHAR = CK_BYTE 45 | CK_CHAR_PTR = POINTER(CK_CHAR) 46 | 47 | CK_BBOOL = CK_BYTE 48 | -------------------------------------------------------------------------------- /pycryptoki/cryptoki_helpers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Deprecated in pycryptoki 2.4 3 | """ 4 | 5 | import warnings 6 | 7 | warnings.warn("Deprecated, use pycryptoki.cryptoki.helpers!", DeprecationWarning) 8 | 9 | # noinspection PyUnresolvedReferences 10 | from pycryptoki.cryptoki.helpers import * 11 | -------------------------------------------------------------------------------- /pycryptoki/daemon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThalesGroup/pycryptoki/ae98be3b9271e90fe9d38905884bfae79bd7dbe0/pycryptoki/daemon/__init__.py -------------------------------------------------------------------------------- /pycryptoki/defaults.py: -------------------------------------------------------------------------------- 1 | """ 2 | A file containing commonly used strings or other data similar to a config file 3 | """ 4 | 5 | # The location of the cryptoki file, if specified as None the environment variable 6 | # ChrystokiConfigurationPath will be used or it will revert to using /etc/Chrystoki.conf 7 | import os 8 | 9 | CHRYSTOKI_CONFIG_FILE = None 10 | 11 | # The location of the DLL file, if not specified it will try to look up the file in 12 | # the Chrystoki config file specified be the variable CHRYSTOKI_CONFIG_FILE 13 | CHRYSTOKI_DLL_FILE = None 14 | 15 | ADMIN_PARTITION_LABEL = "no label" 16 | AUDITOR_LABEL = "auditorlabel" 17 | 18 | ADMINISTRATOR_USERNAME = "Administrator" 19 | ADMINISTRATOR_PASSWORD = "admin_pwd" 20 | 21 | AUDITOR_USERNAME = "Auditor" 22 | AUDITOR_PASSWORD = "audit_pwd" 23 | 24 | CO_USERNAME = "Crypto Officer" 25 | CO_PASSWORD = "userpin" 26 | 27 | DEFAULT_USERNAME = "default_user" 28 | DEFAULT_LABEL = "default_label" 29 | DEFAULT_PASSWORD = "userpin" 30 | 31 | DEFAULT_UTILS_PATH = "/usr/safenet/lunaclient/sbin" 32 | FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 33 | 34 | user_credentials = { 35 | ADMINISTRATOR_USERNAME: ADMINISTRATOR_PASSWORD, 36 | AUDITOR_USERNAME: AUDITOR_PASSWORD, 37 | CO_USERNAME: CO_PASSWORD, 38 | DEFAULT_USERNAME: DEFAULT_PASSWORD, 39 | } 40 | 41 | DES3_KEY_SIZE = 120 42 | 43 | MANUFACTURER_ID = "SafeNet Inc." 44 | MODEL = "Luna K6" 45 | 46 | ADMIN_SLOT = int(os.environ.get("ADMIN_SLOT", 1)) 47 | -------------------------------------------------------------------------------- /pycryptoki/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exception-s and exception handling code. 3 | """ 4 | import inspect 5 | import logging 6 | from functools import wraps 7 | 8 | from six import integer_types 9 | 10 | from .defines import CKR_OK 11 | from .lookup_dicts import ret_vals_dictionary 12 | 13 | LOG = logging.getLogger(__name__) 14 | 15 | 16 | def make_error_handle_function(luna_function): 17 | """This function is a helper function that creates a new function which checks the 18 | result code returned from a function in luna. It is called by calling:: 19 | 20 | c_generate_key_pair_ex = make_error_handle_function(c_generate_key_pair) 21 | 22 | This code will create a c_generate_key_pair_ex which will call c_generate_key_pair and check the 23 | first argument. The first argument is the return code of c_generate_key_pair. If the return 24 | code != CKR_OK then c_generate_key_pair_ex will raise a LunaCallException. You can call 25 | c_generate_key_pair_ex as if it is c_generate_key_pair:: 26 | 27 | c_generate_key_pair_ex(h_session, CKM_RSA_PKCS_KEY_PAIR_GEN, 28 | CKM_RSA_PKCS_KEY_PAIR_GEN_PUBTEMP, 29 | CKM_RSA_PKCS_KEY_PAIR_GEN_PRIVTEMP) 30 | 31 | The return values of c_generate_pair are (ret, public_key_handle, private_key_handle) 32 | 33 | The return values of c_generate_pair_ex are (public_key_handle, private_key_handle) 34 | 35 | This lets you create two versions of a function. One version is for setup and 36 | the other version is for testing the result. 37 | 38 | Directly testing the result:: 39 | 40 | ret = c_initialize() 41 | assert ret == CKR_SOME_ERROR_CODE, "This test case will fail if this condition is not met" 42 | 43 | Expecting the call to go through without error. The test case should have an error (not a 44 | failure):: 45 | 46 | c_initialize_ex() 47 | 48 | This should therefore make for shorter test cases 49 | 50 | :param luna_function: Function object to wrap. 51 | """ 52 | 53 | @wraps(luna_function) 54 | def luna_function_exception_handle(*args, **kwargs): 55 | """ 56 | 57 | :param *args: 58 | :param **kwargs: 59 | 60 | """ 61 | return_tuple = luna_function(*args, **kwargs) 62 | if isinstance(return_tuple, tuple): 63 | if len(return_tuple) > 2: 64 | return_data = return_tuple[1:] 65 | ret = return_tuple[0] 66 | elif len(return_tuple) == 2: 67 | return_data = return_tuple[1] 68 | ret = return_tuple[0] 69 | else: 70 | return_data = return_tuple[0] 71 | ret = return_tuple[0] 72 | elif isinstance(return_tuple, integer_types): 73 | ret = return_tuple 74 | return_data = return_tuple 75 | else: 76 | raise Exception( 77 | "Functions wrapped by the exception handler should return a tuple or just the " 78 | "long representing Luna's return code." 79 | ) 80 | 81 | check_luna_exception(ret, luna_function, args, kwargs) 82 | return return_data 83 | 84 | luna_function_exception_handle.__doc__ = """Executes :py:func:`{}`, and checks the 85 | retcode; raising an exception if the return code is not CKR_OK. 86 | 87 | .. note:: By default, this will not return the return code if the function returns additional 88 | data. 89 | 90 | Example:: 91 | 92 | retcode, key_handle = c_generate_key(...) 93 | #vs 94 | key_handle = c_generate_key_ex(...) 95 | 96 | If the function *only* returns the retcode, then that will still be returned:: 97 | 98 | retcode = c_seed_random(...) 99 | retcode = c_seed_random_ex(...) 100 | 101 | 102 | """.format( 103 | luna_function.__name__ 104 | ) 105 | return luna_function_exception_handle 106 | 107 | 108 | def check_luna_exception(ret, luna_function, args, kwargs): 109 | """ 110 | Check the return code from cryptoki.dll, and if it's non-zero raise an 111 | exception with the error code looked up. 112 | 113 | :param ret: Return code from the C call 114 | :param luna_function: pycryptoki function that was called 115 | :param args: Arguments passed to the pycryptoki function. 116 | """ 117 | from pycryptoki.string_helpers import pformat_pyc_args 118 | 119 | all_args = inspect.getcallargs(luna_function, *args, **kwargs) 120 | formatted_args = pformat_pyc_args(all_args) 121 | 122 | # Tab it over one more for exception logging. 123 | arg_string = "\n".join("\t{}".format(x) for x in formatted_args) 124 | LOG.debug( 125 | "Call to %s returned %s (%s)", 126 | luna_function.__name__, 127 | ret_vals_dictionary.get(ret, "Unknown retcode"), 128 | str(hex(ret)), 129 | ) 130 | if ret != CKR_OK: 131 | raise LunaCallException(ret, luna_function.__name__, arg_string) 132 | 133 | 134 | class LunaException(Exception): 135 | """ 136 | Base exception class for every custom exception raised by pycryptoki. 137 | """ 138 | 139 | pass 140 | 141 | 142 | class LunaCallException(LunaException): 143 | """Exceptions raised from the result of a PKCS11 call that returned a non-zero 144 | return code. This will attempt to look up the error code defines for human-readable output. 145 | """ 146 | 147 | def __init__(self, error_code, function_name, arguments): 148 | """ 149 | :param error_code: The error code of the error 150 | :param function_name: The name of the function 151 | :param arguments: The arguments passed into the function 152 | """ 153 | self.error_code = error_code 154 | self.function_name = function_name 155 | self.arguments = arguments 156 | 157 | if self.error_code in ret_vals_dictionary: 158 | self.error_string = ret_vals_dictionary[self.error_code] 159 | else: 160 | self.error_string = "Unknown Code=" + str(hex(self.error_code)) 161 | 162 | def __str__(self): 163 | data = ( 164 | "\n\tFunction: {func_name}" 165 | "\n\tError: {err_string}" 166 | "\n\tError Code: {err_code}" 167 | "\n\tArguments:\n{args}" 168 | ).format( 169 | func_name=self.function_name, 170 | err_string=self.error_string, 171 | err_code=hex(self.error_code), 172 | args=self.arguments, 173 | ) 174 | 175 | return data 176 | -------------------------------------------------------------------------------- /pycryptoki/key_generator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods used to generate keys. 3 | """ 4 | from ctypes import byref 5 | 6 | from .attributes import Attributes 7 | from .cryptoki import C_DeriveKey 8 | from .cryptoki import ( 9 | C_DestroyObject, 10 | CK_OBJECT_HANDLE, 11 | CK_ULONG, 12 | C_GenerateKey, 13 | C_GenerateKeyPair, 14 | C_CopyObject, 15 | ) 16 | from .default_templates import CKM_DES_KEY_GEN_TEMP, get_default_key_pair_template 17 | from .defines import CKM_DES_KEY_GEN, CKM_RSA_PKCS_KEY_PAIR_GEN 18 | from .mechanism import parse_mechanism 19 | from .exceptions import make_error_handle_function 20 | 21 | 22 | def c_destroy_object(h_session, h_object_value): 23 | """Deletes the object corresponsing to the passed in object handle 24 | 25 | :param int h_session: Session handle 26 | :param int h_object_value: The handle of the object to delete 27 | :returns: Return code 28 | """ 29 | ret = C_DestroyObject(h_session, CK_OBJECT_HANDLE(h_object_value)) 30 | return ret 31 | 32 | 33 | c_destroy_object_ex = make_error_handle_function(c_destroy_object) 34 | 35 | 36 | def c_copy_object(h_session, h_object, template=None): 37 | """Method to call the C_CopyObject cryptoki command. 38 | 39 | :param int h_session: Session handle 40 | :param int h_object: Handle to the object to be cloned 41 | :param dict template: Template for the new object. Defaults to None 42 | :return: (retcode, Handle to the new cloned object) 43 | :rtype: tuple 44 | """ 45 | if template is None: 46 | template = {} 47 | attributes = Attributes(template) 48 | template_size = CK_ULONG(len(template)) 49 | 50 | h_new_object = CK_OBJECT_HANDLE() 51 | 52 | ret = C_CopyObject(h_session, h_object, attributes.get_c_struct(), template_size, h_new_object) 53 | 54 | return ret, h_new_object.value 55 | 56 | 57 | c_copy_object_ex = make_error_handle_function(c_copy_object) 58 | 59 | 60 | def c_generate_key(h_session, mechanism=None, template=None): 61 | """ 62 | Generates a symmetric key of a given flavor given the correct template. 63 | 64 | :param int h_session: Session handle 65 | :param dict template: The template to use to generate the key 66 | :param mechanism: See the :py:func:`~pycryptoki.mechanism.parse_mechanism` function 67 | for possible values. 68 | :return: (retcode, generated key handle) 69 | :rtype tuple: 70 | """ 71 | if mechanism is None: 72 | mechanism = {"mech_type": CKM_DES_KEY_GEN} 73 | 74 | mech = parse_mechanism(mechanism) 75 | 76 | if template is None: 77 | template = CKM_DES_KEY_GEN_TEMP 78 | 79 | key_attributes = Attributes(template) 80 | us_public_template_size = CK_ULONG(len(template)) 81 | 82 | # ACTUALLY GENERATE KEY 83 | h_key = CK_OBJECT_HANDLE() 84 | ret = C_GenerateKey( 85 | h_session, byref(mech), key_attributes.get_c_struct(), us_public_template_size, byref(h_key) 86 | ) 87 | 88 | return ret, h_key.value 89 | 90 | 91 | c_generate_key_ex = make_error_handle_function(c_generate_key) 92 | 93 | 94 | def c_generate_key_pair(h_session, mechanism=None, pbkey_template=None, prkey_template=None): 95 | """Generates a private and public key pair for a given flavor, and given public and private 96 | key templates. The return value will be the handle for the key. 97 | 98 | :param int h_session: Session handle 99 | :param dict pbkey_template: The public key template to use for key generation 100 | :param dict prkey_template: The private key template to use for key generation 101 | :param mechanism: See the :py:func:`~pycryptoki.mechanism.parse_mechanism` function 102 | for possible values. 103 | :returns: (retcode, public key handle, private key handle) 104 | :rtype: tuple 105 | """ 106 | if mechanism is None: 107 | mechanism = {"mech_type": CKM_RSA_PKCS_KEY_PAIR_GEN} 108 | 109 | if pbkey_template is None and prkey_template is None: 110 | pbkey_template, prkey_template = get_default_key_pair_template(CKM_RSA_PKCS_KEY_PAIR_GEN) 111 | 112 | mech = parse_mechanism(mechanism) 113 | 114 | pbkey_template_size = len(pbkey_template) 115 | pbkey_attributes = Attributes(pbkey_template) 116 | 117 | prkey_template_size = len(prkey_template) 118 | prkey_attributes = Attributes(prkey_template) 119 | 120 | h_pbkey = CK_OBJECT_HANDLE() 121 | h_prkey = CK_OBJECT_HANDLE() 122 | ret = C_GenerateKeyPair( 123 | h_session, 124 | byref(mech), 125 | pbkey_attributes.get_c_struct(), 126 | pbkey_template_size, 127 | prkey_attributes.get_c_struct(), 128 | prkey_template_size, 129 | byref(h_pbkey), 130 | byref(h_prkey), 131 | ) 132 | 133 | return ret, h_pbkey.value, h_prkey.value 134 | 135 | 136 | c_generate_key_pair_ex = make_error_handle_function(c_generate_key_pair) 137 | 138 | 139 | def c_derive_key(h_session, h_base_key, template, mechanism=None): 140 | """Derives a key from another key. 141 | 142 | :param int h_session: Session handle 143 | :param int h_base_key: The base key 144 | :param dict template: A python template of attributes to set on derived key 145 | :param mechanism: See the :py:func:`~pycryptoki.mechanism.parse_mechanism` function 146 | for possible values. 147 | :returns: The result code, The derived key's handle 148 | 149 | """ 150 | mech = parse_mechanism(mechanism) 151 | h_key = CK_OBJECT_HANDLE() 152 | c_template = Attributes(template).get_c_struct() 153 | ret = C_DeriveKey( 154 | h_session, 155 | mech, 156 | CK_OBJECT_HANDLE(h_base_key), 157 | c_template, 158 | CK_ULONG(len(template)), 159 | byref(h_key), 160 | ) 161 | return ret, h_key.value 162 | 163 | 164 | c_derive_key_ex = make_error_handle_function(c_derive_key) 165 | 166 | 167 | def clear_keys(h_session): 168 | """Quick hacked together function that can be used to clear the first 10 000 keys. 169 | 170 | :param int h_session: Session handle 171 | """ 172 | for i in range(1, 10000): 173 | c_destroy_object(h_session, i) 174 | -------------------------------------------------------------------------------- /pycryptoki/key_management.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods responsible for key management 3 | """ 4 | from .cryptoki import ( 5 | CA_GenerateMofN, 6 | CA_ModifyUsageCount, 7 | CK_VOID_PTR, 8 | CK_ULONG, 9 | CA_MOFN_GENERATION, 10 | CK_BYTE, 11 | CA_MOFN_GENERATION_PTR, 12 | ) 13 | from .exceptions import make_error_handle_function 14 | 15 | 16 | def ca_generatemofn(h_session, m_value, vector_value, vector_count, is_secure_port_used): 17 | """Generates MofN secret information on a token. 18 | 19 | :param int h_session: Session handle 20 | :param m_value: m 21 | :param vector_count: number of vectors 22 | :param is_secure_port_used: is secure port used 23 | :param vector_value: 24 | :returns: the result code 25 | 26 | """ 27 | reserved = CK_VOID_PTR(0) 28 | 29 | m_value = CK_ULONG(m_value) 30 | vector_count = CK_ULONG(vector_count) 31 | is_secure_port_used = CK_ULONG(is_secure_port_used) 32 | 33 | vector_value = (CK_BYTE * vector_value)() 34 | vector = (CA_MOFN_GENERATION * 2)() 35 | vector[0].ulWeight = CK_ULONG(1) 36 | vector[0].pVector = vector_value 37 | vector[0].ulVectorLen = CK_ULONG(16) 38 | vector[1].ulWeight = CK_ULONG(1) 39 | vector[1].pVector = (CK_BYTE * 16)() 40 | vector[1].ulVectorLen = CK_ULONG(16) 41 | vectors = CA_MOFN_GENERATION_PTR(vector) 42 | 43 | ret = CA_GenerateMofN(h_session, m_value, vectors, vector_count, is_secure_port_used, reserved) 44 | return ret 45 | 46 | 47 | ca_generatemofn_ex = make_error_handle_function(ca_generatemofn) 48 | 49 | 50 | def ca_modifyusagecount(h_session, h_object, command_type, value): 51 | """Modifies CKA_USAGE_COUNT attribute of the object. 52 | 53 | :param int h_session: Session handle 54 | :param h_object: object 55 | :param command_type: command type 56 | :param value: value 57 | :returns: the result code 58 | 59 | """ 60 | ret = CA_ModifyUsageCount(h_session, h_object, command_type, CK_ULONG(value)) 61 | return ret 62 | 63 | 64 | ca_modifyusagecount_ex = make_error_handle_function(ca_modifyusagecount) 65 | -------------------------------------------------------------------------------- /pycryptoki/key_usage.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods responsible for key usage 3 | """ 4 | from .cryptoki import CA_CloneMofN, CA_DuplicateMofN, CK_VOID_PTR, CK_SESSION_HANDLE 5 | from .exceptions import make_error_handle_function 6 | 7 | 8 | def ca_clonemofn(h_session): 9 | """Clones MofN secret from one token to another. 10 | 11 | :param int h_session: Session handle 12 | :returns: the result code 13 | 14 | """ 15 | h_primary_session = CK_SESSION_HANDLE(0) 16 | reserved = CK_VOID_PTR(0) 17 | 18 | ret = CA_CloneMofN(h_session, h_primary_session, reserved) 19 | return ret 20 | 21 | 22 | ca_clonemofn_ex = make_error_handle_function(ca_clonemofn) 23 | 24 | 25 | def ca_duplicatemofn(h_session): 26 | """Duplicates a set of M of N vectors. 27 | 28 | :param int h_session: Session handle 29 | :returns: the result code 30 | 31 | """ 32 | ret = CA_DuplicateMofN(h_session) 33 | return ret 34 | 35 | 36 | ca_duplicatemofn_ex = make_error_handle_function(ca_duplicatemofn) 37 | -------------------------------------------------------------------------------- /pycryptoki/luna_threading.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | import threading 4 | import time 5 | 6 | from .default_templates import ( 7 | CKM_DES_KEY_GEN_TEMP, 8 | CKM_RSA_PKCS_KEY_PAIR_GEN_PUBTEMP, 9 | CKM_RSA_PKCS_KEY_PAIR_GEN_PRIVTEMP, 10 | ) 11 | from .defaults import ADMIN_PARTITION_LABEL, MANUFACTURER_ID, MODEL 12 | from .defines import CKM_DES_KEY_GEN, CKM_RSA_PKCS_KEY_PAIR_GEN, CKR_OK 13 | from .key_generator import c_generate_key_ex, c_generate_key_pair_ex 14 | from .lookup_dicts import ret_vals_dictionary 15 | from .session_management import ( 16 | c_open_session_ex, 17 | c_get_token_info_ex, 18 | c_open_session, 19 | c_close_session, 20 | ) 21 | from .exceptions import verify_object_attributes 22 | from .token_management import ( 23 | get_token_by_label_ex, 24 | c_get_mechanism_list_ex, 25 | c_get_mechanism_info_ex, 26 | ) 27 | 28 | logger = logging.getLogger(__name__) 29 | 30 | CREATE_AND_REMOVE_KEYS = 2 31 | OPEN_AND_CLOSE_SESSIONS = 3 32 | GET_TOKEN_INFO = 4 33 | GET_MECHANISM_INFO = 5 34 | 35 | 36 | class TestThread(threading.Thread): 37 | """A member of the threading class which, when given the proper parameters, will 38 | perform some functions on the HSM in it's own thread. If one of the tests fails it will be 39 | reported when all the 40 | threads finish. 41 | 42 | 43 | """ 44 | 45 | def __init__(self, queue, thread_name, token_label, thread_type, max_time=60): # 60 seconds 46 | """ 47 | @param queue: The queue that the threads will be placed into, this is required to signal 48 | to the queue that the task is done 49 | @param thread_name: The name of the thread for debug printing purposes 50 | @param token_label: The token label to perform multithreaded operations on 51 | @param thread_type: The a numeric value specifyingoperation the thread will do, 52 | see the variables 53 | described above the TestThread class declaration ex. GET_TOKEN_INFO 54 | @param max_time: The amount of time to spend doing the test in seconds 55 | """ 56 | 57 | self.thread_name = thread_name 58 | self.thread_type = thread_type 59 | self.max_time = max_time 60 | self.queue = queue 61 | self.token_label = token_label 62 | threading.Thread.__init__(self) 63 | 64 | def run(self): 65 | """Called by the inheirited threading class to run the actual thread""" 66 | logger.debug("Starting thread " + self.thread_name + " type " + str(self.thread_type)) 67 | self._return = True 68 | 69 | try: 70 | # For a given amount of time run the operations in a separate thread 71 | start_time = time.time() 72 | while ((time.time() - start_time) < self.max_time) and ( 73 | (not self.starting_slot >= self.ending_slot) 74 | or (self.starting_slot == -1 and self.ending_slot == -1) 75 | ): 76 | if self.thread_type == CREATE_AND_REMOVE_KEYS: 77 | self.create_and_remove_keys() 78 | elif self.thread_type == OPEN_AND_CLOSE_SESSIONS: 79 | self.open_and_close_sessions() 80 | elif self.thread_type == GET_TOKEN_INFO: 81 | self.get_token_info() 82 | elif self.thread_type == GET_MECHANISM_INFO: 83 | self.get_mechanism_info() 84 | else: 85 | raise Exception("Unknown thread type " + str(self.thread_type)) 86 | 87 | logger.debug("Exiting thread " + self.thread_name + " type " + str(self.thread_type)) 88 | except Exception as e: 89 | self._return = e 90 | self.queue.task_done() 91 | print(sys.exc_info()[0]) 92 | raise 93 | 94 | if self._return == True: 95 | self._return = True 96 | self.queue.task_done() 97 | 98 | def get_token_info(self): 99 | """Test that will get the token info and verify that the fields have been 100 | set to something other than null 101 | 102 | 103 | """ 104 | slot = get_token_by_label_ex(self.token_label) 105 | token_info = c_get_token_info_ex(slot) 106 | 107 | assert token_info["label"] == ADMIN_PARTITION_LABEL 108 | assert token_info["manufacturerID"] == MANUFACTURER_ID 109 | assert token_info["model"] == MODEL 110 | assert token_info["serialNumber"] != 0 111 | assert token_info["flags"] != 0 112 | assert token_info["ulTotalPrivateMemory"] == 0 113 | assert token_info["ulSessionCount"] != 0 114 | assert token_info["ulRwSessionCount"] != 0 115 | assert token_info["ulMaxPinLen"] != 0 116 | assert token_info["ulMinPinLen"] != 0 117 | 118 | # token_info['hardwareVersion'] = c_token_info.hardwareVersion 119 | # token_info['firmwareVersion'] = c_token_info.firmwareVersion 120 | 121 | def create_and_remove_keys(self): 122 | """Test that will create a bunch of keys and verify the attributes on 123 | those keys 124 | 125 | 126 | """ 127 | slot = get_token_by_label_ex(self.token_label) 128 | h_session = c_open_session_ex(slot) 129 | 130 | logger.debug(self.thread_name + " Generating keys") 131 | key_handle = c_generate_key_ex(h_session, CKM_DES_KEY_GEN, CKM_DES_KEY_GEN_TEMP) 132 | key_handle_public, key_handle_private = c_generate_key_pair_ex( 133 | h_session, 134 | CKM_RSA_PKCS_KEY_PAIR_GEN, 135 | CKM_RSA_PKCS_KEY_PAIR_GEN_PUBTEMP, 136 | CKM_RSA_PKCS_KEY_PAIR_GEN_PRIVTEMP, 137 | ) 138 | 139 | logger.debug(self.thread_name + " Verifying keys") 140 | verify_object_attributes(h_session, key_handle, CKM_DES_KEY_GEN_TEMP) 141 | verify_object_attributes(h_session, key_handle_public, CKM_RSA_PKCS_KEY_PAIR_GEN_PUBTEMP) 142 | verify_object_attributes(h_session, key_handle_private, CKM_RSA_PKCS_KEY_PAIR_GEN_PRIVTEMP) 143 | 144 | def open_and_close_sessions(self): 145 | """Test that will open and close sessions repeatedly""" 146 | slot = get_token_by_label_ex(self.token_label) 147 | 148 | ret, h_session = c_open_session(slot) 149 | assert ret_vals_dictionary[ret] == ret_vals_dictionary[CKR_OK] 150 | 151 | ret = c_close_session(h_session) 152 | assert ret_vals_dictionary[ret] == ret_vals_dictionary[CKR_OK] 153 | 154 | def get_mechanism_info(self): 155 | """Test that will get the mechanism info repeatedly and verify it is non null""" 156 | slot = get_token_by_label_ex(self.token_label) 157 | mechanism_list = c_get_mechanism_list_ex(slot) 158 | 159 | assert len(mechanism_list) > 0, "The mechanism list should have a non zero length" 160 | for mechanism in mechanism_list: 161 | mech_info = c_get_mechanism_info_ex(slot, mechanism) 162 | assert ( 163 | mech_info.ulMinKeySize > 0 or mech_info.ulMaxKeySize > 0 or mech_info.flags > 0 164 | ) and mech_info.ulMinKeySize <= mech_info.ulMaxKeySize, ( 165 | "Verifing that all fields are not 0 should be good enough for now" 166 | ) 167 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/aes.py: -------------------------------------------------------------------------------- 1 | """ 2 | AES-specific mechanism implementations. 3 | """ 4 | import logging 5 | from ctypes import c_void_p, cast, pointer, sizeof 6 | 7 | from . import Mechanism 8 | from ..attributes import to_byte_array 9 | from ..cryptoki import ( 10 | CK_ULONG, 11 | CK_BYTE, 12 | CK_BYTE_PTR, 13 | CK_AES_XTS_PARAMS, 14 | CK_AES_GCM_PARAMS, 15 | CK_KEY_DERIVATION_STRING_DATA, 16 | CK_AES_CBC_ENCRYPT_DATA_PARAMS, 17 | CK_AES_CTR_PARAMS, 18 | c_ubyte, 19 | ) 20 | 21 | LOG = logging.getLogger(__name__) 22 | 23 | 24 | class IvMechanism(Mechanism): 25 | """ 26 | Mech class for flavors that require an IV set in the mechanism. 27 | Will default to `[0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38]` if no IV is passed in 28 | """ 29 | 30 | OPTIONAL_PARAMS = ["iv"] 31 | 32 | def to_c_mech(self): 33 | """ 34 | Convert extra parameters to ctypes, then build out the mechanism. 35 | 36 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 37 | """ 38 | super(IvMechanism, self).to_c_mech() 39 | if self.params is None or "iv" not in self.params: 40 | self.params["iv"] = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38] 41 | LOG.warning("Using static IVs can be insecure! ") 42 | if len(self.params["iv"]) == 0: 43 | LOG.debug("Setting IV to NULL (using internal)") 44 | iv_ba = None 45 | iv_len = 0 46 | else: 47 | iv_ba, iv_len = to_byte_array(self.params["iv"]) 48 | self.mech.pParameter = iv_ba 49 | self.mech.usParameterLen = iv_len 50 | return self.mech 51 | 52 | 53 | class Iv16Mechanism(Mechanism): 54 | """ 55 | Mech class for flavors that require an IV set in the mechanism. 56 | Will default to `[1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8]` if no IV is passed in 57 | """ 58 | 59 | OPTIONAL_PARAMS = ["iv"] 60 | 61 | def to_c_mech(self): 62 | """ 63 | Convert extra parameters to ctypes, then build out the mechanism. 64 | 65 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 66 | """ 67 | super(Iv16Mechanism, self).to_c_mech() 68 | if self.params is None or "iv" not in self.params: 69 | self.params["iv"] = [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8] 70 | LOG.warning("Using static IVs can be insecure! ") 71 | if len(self.params["iv"]) == 0: 72 | LOG.debug("Setting IV to NULL (using internal)") 73 | iv_ba = None 74 | iv_len = 0 75 | else: 76 | iv_ba, iv_len = to_byte_array(self.params["iv"]) 77 | self.mech.pParameter = iv_ba 78 | self.mech.usParameterLen = iv_len 79 | return self.mech 80 | 81 | 82 | class AESXTSMechanism(Mechanism): 83 | """ 84 | Creates the AES-XTS specific param structure & converts python types to C types. 85 | """ 86 | 87 | REQUIRED_PARAMS = ["cb", "hTweakKey"] 88 | 89 | def to_c_mech(self): 90 | """ 91 | Convert extra parameters to ctypes, then build out the mechanism. 92 | 93 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 94 | """ 95 | super(AESXTSMechanism, self).to_c_mech() 96 | xts_params = CK_AES_XTS_PARAMS() 97 | xts_params.cb = (CK_BYTE * 16)(*self.params["cb"]) 98 | xts_params.hTweakKey = CK_ULONG(self.params["hTweakKey"]) 99 | self.mech.pParameter = cast(pointer(xts_params), c_void_p) 100 | self.mech.usParameterLen = CK_ULONG(sizeof(xts_params)) 101 | return self.mech 102 | 103 | 104 | class AESGCMMechanism(Mechanism): 105 | """ 106 | Creates the AES-GCM specific param structure & converts python types to C types. 107 | """ 108 | 109 | REQUIRED_PARAMS = ["iv", "AAD", "ulTagBits"] 110 | 111 | def to_c_mech(self): 112 | """ 113 | Convert extra parameters to ctypes, then build out the mechanism. 114 | 115 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 116 | """ 117 | super(AESGCMMechanism, self).to_c_mech() 118 | gcm_params = CK_AES_GCM_PARAMS() 119 | if len(self.params["iv"]) == 0: 120 | LOG.debug("Setting IV to NULL (using internal)") 121 | iv_ba = None 122 | iv_len = 0 123 | else: 124 | iv_ba, iv_len = to_byte_array(self.params["iv"]) 125 | gcm_params.pIv = cast(iv_ba, CK_BYTE_PTR) 126 | gcm_params.ulIvLen = iv_len 127 | # Assuming 8 bits per entry in IV. 128 | gcm_params.ulIvBits = CK_ULONG(len(self.params["iv"]) * 8) 129 | aad, aadlen = to_byte_array(self.params["AAD"]) 130 | gcm_params.pAAD = cast(aad, CK_BYTE_PTR) 131 | gcm_params.ulAADLen = aadlen 132 | gcm_params.ulTagBits = CK_ULONG(self.params["ulTagBits"]) 133 | self.mech.pParameter = cast(pointer(gcm_params), c_void_p) 134 | self.mech.usParameterLen = CK_ULONG(sizeof(gcm_params)) 135 | return self.mech 136 | 137 | 138 | class AESECBEncryptDataMechanism(Mechanism): 139 | """ 140 | AES mechanism for deriving keys from encrypted data. 141 | """ 142 | 143 | REQUIRED_PARAMS = ["data"] 144 | 145 | def to_c_mech(self): 146 | """ 147 | Convert extra parameters to ctypes, then build out the mechanism. 148 | 149 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 150 | """ 151 | super(AESECBEncryptDataMechanism, self).to_c_mech() 152 | # from https://www.cryptsoft.com/pkcs11doc/v220 153 | # /group__SEC__12__14__2__MECHANISM__PARAMETERS.html 154 | # Note: data should be a multiple of 16 long. 155 | params = CK_KEY_DERIVATION_STRING_DATA() 156 | pdata, data_len = to_byte_array(self.params["data"]) 157 | params.pData = cast(pdata, CK_BYTE_PTR) 158 | params.ulLen = data_len 159 | self.mech.pParameter = cast(pointer(params), c_void_p) 160 | self.mech.usParameterLen = CK_ULONG(sizeof(params)) 161 | return self.mech 162 | 163 | 164 | class AESCBCEncryptDataMechanism(Mechanism): 165 | """ 166 | AES CBC mechanism for deriving keys from encrypted data. 167 | """ 168 | 169 | REQUIRED_PARAMS = ["iv", "data"] 170 | 171 | def to_c_mech(self): 172 | """ 173 | Convert extra parameters to ctypes, then build out the mechanism. 174 | 175 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 176 | """ 177 | super(AESCBCEncryptDataMechanism, self).to_c_mech() 178 | # https://www.cryptsoft.com/pkcs11doc/v220 179 | # /group__SEC__12__14__KEY__DERIVATION__BY__DATA__ENCRYPTION______DES______AES.html 180 | # #CKM_AES_CBC_ENCRYPT_DATA 181 | # Note: data should be a multiple of 16 long. 182 | params = CK_AES_CBC_ENCRYPT_DATA_PARAMS() 183 | pdata, data_len = to_byte_array(self.params["data"]) 184 | # Note: IV should always be a length of 8. 185 | params.pData = cast(pdata, CK_BYTE_PTR) 186 | params.length = data_len 187 | params.iv = (c_ubyte * 16)(*self.params["iv"]) 188 | self.mech.pParameter = cast(pointer(params), c_void_p) 189 | self.mech.usParameterLen = CK_ULONG(sizeof(params)) 190 | return self.mech 191 | 192 | 193 | class AESCTRMechanism(Mechanism): 194 | """ 195 | AES CTR Mechanism param conversion. 196 | """ 197 | 198 | REQUIRED_PARAMS = ["cb", "ulCounterBits"] 199 | 200 | def to_c_mech(self): 201 | """ 202 | Convert extra parameters to ctypes, then build out the mechanism. 203 | 204 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 205 | """ 206 | super(AESCTRMechanism, self).to_c_mech() 207 | ctr_params = CK_AES_CTR_PARAMS() 208 | ctr_params.cb = (CK_BYTE * 16)(*self.params["cb"]) 209 | ctr_params.ulCounterBits = CK_ULONG(self.params["ulCounterBits"]) 210 | self.mech.pParameter = cast(pointer(ctr_params), c_void_p) 211 | self.mech.usParameterLen = CK_ULONG(sizeof(ctr_params)) 212 | return self.mech 213 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/des.py: -------------------------------------------------------------------------------- 1 | """ 2 | DES3-specific mechanism implementations. 3 | """ 4 | import logging 5 | from ctypes import c_void_p, cast, pointer, sizeof 6 | 7 | from . import Mechanism 8 | 9 | 10 | from ..attributes import to_byte_array 11 | from ..conversions import from_bytestring 12 | from ..cryptoki import ( 13 | CK_ULONG, 14 | CK_BYTE, 15 | CK_BYTE_PTR, 16 | CK_DES_CTR_PARAMS, 17 | CK_KEY_DERIVATION_STRING_DATA, 18 | CK_DES_CBC_ENCRYPT_DATA_PARAMS, 19 | ) 20 | 21 | LOG = logging.getLogger(__name__) 22 | 23 | 24 | class DES3CTRMechanism(Mechanism): 25 | """ 26 | DES3 CTR Mechanism param conversion. 27 | """ 28 | 29 | REQUIRED_PARAMS = ["cb", "ulCounterBits"] 30 | 31 | def to_c_mech(self): 32 | """ 33 | Convert extra parameters to ctypes, then build out the mechanism. 34 | 35 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 36 | """ 37 | super(DES3CTRMechanism, self).to_c_mech() 38 | ctr_params = CK_DES_CTR_PARAMS() 39 | ctr_params.cb = (CK_BYTE * 8)(*self.params["cb"]) 40 | ctr_params.ulCounterBits = CK_ULONG(self.params["ulCounterBits"]) 41 | self.mech.pParameter = cast(pointer(ctr_params), c_void_p) 42 | self.mech.usParameterLen = CK_ULONG(sizeof(ctr_params)) 43 | return self.mech 44 | 45 | 46 | class DES3ECBEncryptDataMechanism(Mechanism): 47 | """ 48 | DES3 mechanism for deriving keys from encrypted data. 49 | """ 50 | 51 | REQUIRED_PARAMS = ["data"] 52 | 53 | def to_c_mech(self): 54 | """ 55 | Convert extra parameters to ctypes, then build out the mechanism. 56 | 57 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 58 | """ 59 | super(DES3ECBEncryptDataMechanism, self).to_c_mech() 60 | # from https://www.cryptsoft.com/pkcs11doc/v220 61 | # /group__SEC__12__14__2__MECHANISM__PARAMETERS.html 62 | # CKM_DES3_ECB_ENCRYPT_DATA 63 | # Note: data should same or > size of key in multiples of 8. 64 | params = CK_KEY_DERIVATION_STRING_DATA() 65 | pdata, data_len = to_byte_array(from_bytestring(self.params["data"])) 66 | pdata = cast(pdata, CK_BYTE_PTR) 67 | params.pData = pdata 68 | params.ulLen = CK_ULONG(data_len.value) 69 | self.mech.pParameter = cast(pointer(params), c_void_p) 70 | self.mech.usParameterLen = CK_ULONG(sizeof(params)) 71 | return self.mech 72 | 73 | 74 | class DES3CBCEncryptDataMechanism(Mechanism): 75 | """ 76 | DES3 CBC mechanism for deriving keys from encrypted data. 77 | """ 78 | 79 | REQUIRED_PARAMS = ["iv", "data"] 80 | 81 | def to_c_mech(self): 82 | """ 83 | Convert extra parameters to ctypes, then build out the mechanism. 84 | 85 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 86 | """ 87 | super(DES3CBCEncryptDataMechanism, self).to_c_mech() 88 | # from https://www.cryptsoft.com/pkcs11doc/v220 89 | # /group__SEC__12__14__2__MECHANISM__PARAMETERS.html 90 | # CKM_DES3_CBC_ENCRYPT_DATA 91 | # Note: data should same or > size of key in multiples of 8. 92 | params = CK_DES_CBC_ENCRYPT_DATA_PARAMS() 93 | pdata, data_len = to_byte_array(from_bytestring(self.params["data"])) 94 | pdata = cast(pdata, CK_BYTE_PTR) 95 | # Note: IV should always be a length of 8. 96 | params.iv = (CK_BYTE * 8)(*self.params["iv"]) 97 | params.pData = pdata 98 | params.length = CK_ULONG(data_len.value) 99 | self.mech.pParameter = cast(pointer(params), c_void_p) 100 | self.mech.usParameterLen = CK_ULONG(sizeof(params)) 101 | return self.mech 102 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/dh.py: -------------------------------------------------------------------------------- 1 | """ 2 | Diffie-Hellman mechanisms. 3 | """ 4 | from _ctypes import pointer, sizeof 5 | from ctypes import cast, c_void_p 6 | 7 | from ..attributes import to_byte_array 8 | from ..cryptoki import CK_ECDH1_DERIVE_PARAMS, CK_BYTE_PTR, CK_ULONG 9 | from .helpers import Mechanism 10 | 11 | 12 | class ECDH1DeriveMechanism(Mechanism): 13 | """ 14 | ECDH1-specific mechanism 15 | """ 16 | 17 | REQUIRED_PARAMS = ["kdf", "sharedData", "publicData"] 18 | 19 | def to_c_mech(self): 20 | """ 21 | Create the Param structure, then convert the data into byte arrays. 22 | 23 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 24 | """ 25 | super(ECDH1DeriveMechanism, self).to_c_mech() 26 | params = CK_ECDH1_DERIVE_PARAMS() 27 | params.kdf = self.params["kdf"] 28 | if self.params["sharedData"] is None: 29 | shared_data = None 30 | shared_data_len = 0 31 | else: 32 | shared_data, shared_data_len = to_byte_array(self.params["sharedData"]) 33 | params.pSharedData = cast(shared_data, CK_BYTE_PTR) 34 | params.ulSharedDataLen = shared_data_len 35 | public_data, public_data_len = to_byte_array(self.params["publicData"]) 36 | params.pPublicData = cast(public_data, CK_BYTE_PTR) 37 | params.ulPublicDataLen = public_data_len 38 | self.mech.pParameter = cast(pointer(params), c_void_p) 39 | self.mech.usParameterLen = CK_ULONG(sizeof(params)) 40 | return self.mech 41 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/eddsa.py: -------------------------------------------------------------------------------- 1 | """ 2 | Edwards EC-specific mechanism implementation. Params are optional. 3 | """ 4 | from ctypes import c_void_p, cast, pointer, sizeof 5 | 6 | 7 | from . import Mechanism, MechanismException 8 | from ..attributes import to_byte_array 9 | from ..cryptoki import CK_BBOOL, CK_ULONG, CK_BYTE_PTR, CK_EDDSA_PARAMS 10 | 11 | 12 | class EDDSAMechanism(Mechanism): 13 | """ 14 | Mech class for EDDSA. 15 | 16 | Luna Docs indicate that parameters are optional. The use of the pre-hashed 17 | ed25519ph curve variant is controlled by the phFlag value in the parameter 18 | struct. If parameters are not specified, it is the same as setting phFlag 19 | false; the regular ed25519 curve variant will be used. 20 | """ 21 | 22 | OPTIONAL_PARAMS = ["phFlag", "ContextData"] 23 | 24 | def to_c_mech(self): 25 | super(EDDSAMechanism, self).to_c_mech() 26 | 27 | if not self.params: 28 | self.mech.pParameter = c_void_p(0) 29 | self.mech.usParameterLen = CK_ULONG(0) 30 | return self.mech 31 | 32 | ph_flag = self.params.get("phFlag", None) 33 | if ph_flag is None: 34 | # if params were specified, phFlag is required 35 | raise MechanismException( 36 | "Cannot create {}, Missing required parameters:\n\t{}".format( 37 | self.__class__, "phFlag" 38 | ) 39 | ) 40 | 41 | params = CK_EDDSA_PARAMS() 42 | params.phFlag = CK_BBOOL(int(bool(ph_flag))) 43 | 44 | # Luna Docs state pContextData must be null; allow to be optional 45 | context_data = self.params.get("ContextData", None) 46 | if context_data is None: 47 | params.pContextData = None 48 | length = CK_ULONG(0) 49 | else: 50 | context_data, length = to_byte_array(self.params["ContextData"]) 51 | params.pContextData = cast(context_data, CK_BYTE_PTR) 52 | params.ulContextDataLen = length 53 | self.mech.pParameter = cast(pointer(params), c_void_p) 54 | self.mech.usParameterLen = CK_ULONG(sizeof(params)) 55 | return self.mech 56 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/generic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generic Mechanisms conversions. 3 | """ 4 | from ctypes import c_void_p, cast, pointer, POINTER, sizeof 5 | from . import Mechanism 6 | from ..attributes import to_byte_array 7 | from ..cryptoki import CK_ULONG, CK_KEY_DERIVATION_STRING_DATA, c_ubyte 8 | 9 | 10 | class ConcatenationDeriveMechanism(Mechanism): 11 | """ 12 | Mechanism class for key derivations. This will take in a second key handle in the parameters, 13 | and use it in the resulting Structure. 14 | 15 | .. warning :: This mechanism is disabled in later versions of PCKS11. 16 | 17 | """ 18 | 19 | REQUIRED_PARAMS = ["h_second_key"] 20 | 21 | def to_c_mech(self): 22 | """ 23 | Add in a pointer to the second key in the resulting mech structure. 24 | 25 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 26 | """ 27 | super(ConcatenationDeriveMechanism, self).to_c_mech() 28 | c_second_key = CK_ULONG(self.params["h_second_key"]) 29 | self.mech.pParameter = cast(pointer(c_second_key), c_void_p) 30 | self.mech.usParameterLen = sizeof(c_second_key) 31 | return self.mech 32 | 33 | 34 | class StringDataDerivationMechanism(Mechanism): 35 | """ 36 | Mechanism class for key derivation using passed in string data. 37 | """ 38 | 39 | REQUIRED_PARAMS = ["data"] 40 | 41 | def to_c_mech(self): 42 | """ 43 | Convert data to bytearray, then use in the resulting mech structure. 44 | 45 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 46 | """ 47 | super(StringDataDerivationMechanism, self).to_c_mech() 48 | parameters = CK_KEY_DERIVATION_STRING_DATA() 49 | data, length = to_byte_array(self.params["data"]) 50 | parameters.pData = cast(data, POINTER(c_ubyte)) 51 | parameters.ulLen = length 52 | self.mech.pParameter = cast(pointer(parameters), c_void_p) 53 | self.mech.usParameterLen = sizeof(parameters) 54 | return self.mech 55 | 56 | 57 | class NullMech(Mechanism): 58 | """ 59 | Class that creates a mechanism from a flavor with null parameters. 60 | Used mostly for signing mechanisms that really don't need anything else. 61 | """ 62 | 63 | def to_c_mech(self): 64 | """ 65 | Simply set the pParameter to null pointer. 66 | 67 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 68 | """ 69 | super(NullMech, self).to_c_mech() 70 | self.mech.pParameter = c_void_p(0) 71 | self.mech.usParameterLen = CK_ULONG(0) 72 | return self.mech 73 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/kdf.py: -------------------------------------------------------------------------------- 1 | """KDF-specific mechanism implementations.""" 2 | 3 | from _ctypes import pointer, sizeof 4 | from ctypes import cast, c_void_p 5 | 6 | from . import Mechanism 7 | from ..attributes import to_byte_array 8 | from ..cryptoki import CK_PRF_KDF_PARAMS, CK_BYTE_PTR, CK_ULONG 9 | 10 | 11 | class PRFKDFDeriveMechanism(Mechanism): 12 | """PRF KDF-specific mechanism.""" 13 | 14 | REQUIRED_PARAMS = ["prf_type", "label", "context", "counter", "encoding_scheme"] 15 | 16 | def to_c_mech(self): 17 | """ 18 | Create the Param structure, then convert the data into byte arrays. 19 | 20 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 21 | 22 | """ 23 | super(PRFKDFDeriveMechanism, self).to_c_mech() 24 | params = CK_PRF_KDF_PARAMS() 25 | params.prfType = self.params["prf_type"] 26 | if self.params["label"] is None: 27 | label = "" 28 | label_len = 0 29 | else: 30 | label, label_len = to_byte_array(self.params["label"]) 31 | if self.params["context"] is None: 32 | context = "" 33 | context_len = 0 34 | else: 35 | context, context_len = to_byte_array(self.params["context"]) 36 | if self.params["counter"] is None: 37 | counter = 1 38 | else: 39 | counter = self.params["counter"] 40 | ul_encoding_scheme = self.params["encoding_scheme"] 41 | 42 | params.pLabel = cast(label, CK_BYTE_PTR) 43 | params.ulLabelLen = label_len 44 | params.pContext = cast(context, CK_BYTE_PTR) 45 | params.ulContextLen = context_len 46 | params.ulCounter = counter 47 | params.ulEncodingScheme = ul_encoding_scheme 48 | self.mech.pParameter = cast(pointer(params), c_void_p) 49 | self.mech.usParameterLen = CK_ULONG(sizeof(params)) 50 | return self.mech 51 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/rc.py: -------------------------------------------------------------------------------- 1 | """ 2 | RC-related Mechanism implementations 3 | """ 4 | import logging 5 | import types 6 | from ctypes import c_void_p, cast, pointer, POINTER, sizeof, create_string_buffer, c_char 7 | 8 | from six import integer_types 9 | 10 | from .. import cryptoki 11 | from . import Mechanism 12 | from ..attributes import to_byte_array, to_char_array, CONVERSIONS 13 | from ..cryptoki import ( 14 | CK_AES_CBC_PAD_EXTRACT_PARAMS, 15 | CK_MECHANISM, 16 | CK_ULONG, 17 | CK_ULONG_PTR, 18 | CK_AES_CBC_PAD_INSERT_PARAMS, 19 | CK_BYTE, 20 | CK_BYTE_PTR, 21 | CK_RC2_CBC_PARAMS, 22 | CK_RC5_PARAMS, 23 | CK_RC5_CBC_PARAMS, 24 | CK_MECHANISM_TYPE, 25 | CK_AES_XTS_PARAMS, 26 | CK_RSA_PKCS_OAEP_PARAMS, 27 | CK_AES_GCM_PARAMS, 28 | CK_RSA_PKCS_PSS_PARAMS, 29 | CK_KEY_DERIVATION_STRING_DATA, 30 | c_ubyte, 31 | CK_AES_CBC_ENCRYPT_DATA_PARAMS, 32 | ) 33 | from ..defines import * 34 | from ..exceptions import LunaException 35 | 36 | 37 | class RC2Mechanism(Mechanism): 38 | """ 39 | Sets the mechanism parameter to the usEffectiveBits 40 | """ 41 | 42 | REQUIRED_PARAMS = ["usEffectiveBits"] 43 | 44 | def to_c_mech(self): 45 | """ 46 | Convert extra parameters to ctypes, then build out the mechanism. 47 | 48 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 49 | """ 50 | super(RC2Mechanism, self).to_c_mech() 51 | effective_bits = CK_ULONG(self.params["usEffectiveBits"]) 52 | self.mech.pParameter = cast(pointer(effective_bits), c_void_p) 53 | self.mech.usParameterLen = CK_ULONG(sizeof(effective_bits)) 54 | return self.mech 55 | 56 | 57 | class RC2CBCMechanism(Mechanism): 58 | """ 59 | Creates required RC2CBC Param structure & converts python data to C data. 60 | """ 61 | 62 | REQUIRED_PARAMS = ["usEffectiveBits", "iv"] 63 | 64 | def to_c_mech(self): 65 | """ 66 | Convert extra parameters to ctypes, then build out the mechanism. 67 | 68 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 69 | """ 70 | super(RC2CBCMechanism, self).to_c_mech() 71 | effective_bits = self.params["usEffectiveBits"] 72 | cbc_params = CK_RC2_CBC_PARAMS() 73 | cbc_params.usEffectiveBits = CK_ULONG(effective_bits) 74 | cbc_params.iv = (CK_BYTE * 8)(*self.params["iv"]) 75 | self.mech.pParameter = cast(pointer(cbc_params), c_void_p) 76 | self.mech.usParameterLen = CK_ULONG(sizeof(cbc_params)) 77 | return self.mech 78 | 79 | 80 | class RC5Mechanism(Mechanism): 81 | """ 82 | Creates required RC5 Param structure & converts python data to C data. 83 | """ 84 | 85 | REQUIRED_PARAMS = ["ulWordsize", "ulRounds"] 86 | 87 | def to_c_mech(self): 88 | """ 89 | Convert extra parameters to ctypes, then build out the mechanism. 90 | 91 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 92 | """ 93 | super(RC5Mechanism, self).to_c_mech() 94 | rc5_params = CK_RC5_PARAMS() 95 | rc5_params.ulWordsize = CK_ULONG(self.params["ulWordsize"]) 96 | rc5_params.ulRounds = CK_ULONG(self.params["ulRounds"]) 97 | self.mech.pParameter = cast(pointer(rc5_params), c_void_p) 98 | self.mech.usParameterLen = CK_ULONG(sizeof(rc5_params)) 99 | return self.mech 100 | 101 | 102 | class RC5CBCMechanism(Mechanism): 103 | """ 104 | Creates required RC5CBC Param structure & converts python data to C data. 105 | """ 106 | 107 | REQUIRED_PARAMS = ["ulWordsize", "ulRounds", "iv"] 108 | 109 | def to_c_mech(self): 110 | """ 111 | Convert extra parameters to ctypes, then build out the mechanism. 112 | 113 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 114 | """ 115 | super(RC5CBCMechanism, self).to_c_mech() 116 | rc5_params = CK_RC5_CBC_PARAMS() 117 | rc5_params.ulWordsize = CK_ULONG(self.params["ulWordsize"]) 118 | rc5_params.ulRounds = CK_ULONG(self.params["ulRounds"]) 119 | iv, ivlen = to_byte_array(self.params["iv"]) 120 | rc5_params.pIv = cast(iv, CK_BYTE_PTR) 121 | rc5_params.ulIvLen = ivlen 122 | self.mech.pParameter = cast(pointer(rc5_params), c_void_p) 123 | self.mech.usParameterLen = CK_ULONG(sizeof(rc5_params)) 124 | return self.mech 125 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/rsa.py: -------------------------------------------------------------------------------- 1 | """ 2 | RSA-related Mechanism implementations. 3 | """ 4 | from ctypes import c_void_p, cast, pointer, sizeof 5 | 6 | from .helpers import Mechanism 7 | from ..attributes import to_byte_array 8 | from ..cryptoki import CK_ULONG, CK_RSA_PKCS_OAEP_PARAMS, CK_RSA_PKCS_PSS_PARAMS 9 | from ..defines import * 10 | 11 | 12 | class RSAPKCSOAEPMechanism(Mechanism): 13 | """ 14 | Create the required RSA_PKCS_OAEP param structure & convert python data to 15 | C data. 16 | """ 17 | 18 | REQUIRED_PARAMS = ["hashAlg", "mgf"] 19 | OPTIONAL_PARAMS = ["sourceData"] 20 | 21 | def to_c_mech(self): 22 | """ 23 | Convert extra parameters to ctypes, then build out the mechanism. 24 | 25 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 26 | """ 27 | super(RSAPKCSOAEPMechanism, self).to_c_mech() 28 | oaep_params = CK_RSA_PKCS_OAEP_PARAMS() 29 | oaep_params.hashAlg = CK_ULONG(self.params["hashAlg"]) 30 | oaep_params.mgf = CK_ULONG(self.params["mgf"]) 31 | # Note: According to 32 | # https://www.cryptsoft.com/pkcs11doc/v220 33 | # /group__SEC__12__1__7__PKCS____1__RSA__OAEP__MECHANISM__PARAMETERS.html 34 | # there is only one encoding parameter source. 35 | oaep_params.source = CK_ULONG(CKZ_DATA_SPECIFIED) 36 | if "sourceData" in self.params: 37 | data, data_len = to_byte_array(self.params["sourceData"]) 38 | else: 39 | data = None 40 | data_len = 0 41 | oaep_params.pSourceData = data 42 | oaep_params.ulSourceDataLen = data_len 43 | 44 | self.mech.pParameter = cast(pointer(oaep_params), c_void_p) 45 | self.mech.usParameterLen = CK_ULONG(sizeof(oaep_params)) 46 | return self.mech 47 | 48 | 49 | class RSAPKCSPSSMechanism(Mechanism): 50 | """ 51 | Create the required RSA_PKCS_PSS param structure & convert python data to 52 | C data. 53 | """ 54 | 55 | REQUIRED_PARAMS = ["hashAlg", "mgf"] 56 | OPTIONAL_PARAMS = ["usSaltLen"] 57 | 58 | def to_c_mech(self): 59 | """ 60 | Uses default salt length of 8. 61 | Can be overridden w/ a parameter though. 62 | 63 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 64 | """ 65 | super(RSAPKCSPSSMechanism, self).to_c_mech() 66 | c_params = CK_RSA_PKCS_PSS_PARAMS() 67 | c_params.hashAlg = CK_ULONG(self.params["hashAlg"]) 68 | c_params.mgf = CK_ULONG(self.params["mgf"]) 69 | c_params.usSaltLen = CK_ULONG(self.params.get("usSaltLen", 8)) 70 | self.mech.pParameter = cast(pointer(c_params), c_void_p) 71 | self.mech.usParameterLen = CK_ULONG(sizeof(c_params)) 72 | return self.mech 73 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/sha.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sha Hmac General Mechanism implementations. 3 | """ 4 | from ctypes import c_void_p, cast, pointer, sizeof 5 | 6 | from .helpers import Mechanism 7 | from ..cryptoki import CK_ULONG, CK_SHA_HMAC_GENERAL_PARAMS 8 | from ..defines import * 9 | 10 | 11 | class ShaHmacGeneralMechanism(Mechanism): 12 | """ 13 | Create the required CK_SHA_HMAC_GENERAL_PARAMS param structure & convert python data to 14 | C data. 15 | """ 16 | 17 | REQUIRED_PARAMS = ["outputLen"] 18 | 19 | def to_c_mech(self): 20 | """ 21 | Convert extra parameters to ctypes, then build out the mechanism. 22 | 23 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 24 | """ 25 | super(ShaHmacGeneralMechanism, self).to_c_mech() 26 | sha_params = CK_SHA_HMAC_GENERAL_PARAMS() 27 | sha_params.ulOutputLen = CK_ULONG(self.params["outputLen"]) 28 | 29 | self.mech.pParameter = cast(pointer(sha_params), c_void_p) 30 | self.mech.usParameterLen = CK_ULONG(sizeof(sha_params)) 31 | return self.mech 32 | -------------------------------------------------------------------------------- /pycryptoki/mechanism/shake.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shake Mechanism implementations. 3 | """ 4 | from ctypes import c_void_p, cast, pointer, sizeof 5 | 6 | from .helpers import Mechanism 7 | from ..cryptoki import CK_ULONG, CK_SHAKE_PARAMS 8 | from ..defines import * 9 | 10 | 11 | class ShakeMechanism(Mechanism): 12 | """ 13 | Create the required CK_SHAKE_PARAMS param structure & convert python data to 14 | C data. 15 | """ 16 | 17 | REQUIRED_PARAMS = ["outputLen"] 18 | 19 | def to_c_mech(self): 20 | """ 21 | Convert extra parameters to ctypes, then build out the mechanism. 22 | 23 | :return: :class:`~pycryptoki.cryptoki.CK_MECHANISM` 24 | """ 25 | super(ShakeMechanism, self).to_c_mech() 26 | shake_params = CK_SHAKE_PARAMS() 27 | shake_params.ulOutputLen = CK_ULONG(self.params["outputLen"]) 28 | 29 | self.mech.pParameter = cast(pointer(shake_params), c_void_p) 30 | self.mech.usParameterLen = CK_ULONG(sizeof(shake_params)) 31 | return self.mech 32 | -------------------------------------------------------------------------------- /pycryptoki/misc.py: -------------------------------------------------------------------------------- 1 | """ 2 | PKCS11 Interface to the following functions: 3 | 4 | * c_generate_random 5 | * c_seed_random 6 | * c_digest 7 | * c_digestkey 8 | * c_create_object 9 | * c_set_ped_id (CA_ function) 10 | * c_get_ped_id (CA_ function) 11 | """ 12 | from _ctypes import POINTER 13 | from ctypes import create_string_buffer, cast, byref, string_at, c_ubyte 14 | 15 | from six import integer_types 16 | 17 | from pycryptoki.conversions import from_bytestring 18 | from .attributes import Attributes, to_byte_array 19 | from .common_utils import refresh_c_arrays, AutoCArray 20 | from .cryptoki import ( 21 | C_GenerateRandom, 22 | CK_BYTE_PTR, 23 | CK_ULONG, 24 | C_SeedRandom, 25 | C_DigestInit, 26 | C_DigestUpdate, 27 | C_DigestFinal, 28 | C_Digest, 29 | C_CreateObject, 30 | CA_SetPedId, 31 | CK_SLOT_ID, 32 | CA_GetPedId, 33 | C_DigestKey, 34 | ) 35 | from .defines import CKR_OK 36 | from .exceptions import make_error_handle_function 37 | from .mechanism import parse_mechanism 38 | from .sign_verify import do_multipart_sign_or_digest 39 | 40 | 41 | def c_generate_random(h_session, length): 42 | """Generates a sequence of random numbers 43 | 44 | :param int h_session: Session handle 45 | :param int length: The length in bytes of the random number sequence 46 | :returns: (retcode, A string of random data) 47 | :rtype: tuple 48 | """ 49 | 50 | random_data = create_string_buffer(b"", length) 51 | data_ptr = cast(random_data, CK_BYTE_PTR) 52 | ret = C_GenerateRandom(h_session, data_ptr, CK_ULONG(length)) 53 | 54 | data = string_at(data_ptr, length) 55 | return ret, data 56 | 57 | 58 | c_generate_random_ex = make_error_handle_function(c_generate_random) 59 | 60 | 61 | def c_seed_random(h_session, seed): 62 | """Seeds the random number generator 63 | 64 | :param int h_session: Session handle 65 | :param bytes seed: A python string of some seed 66 | :returns: retcode 67 | :rtype: int 68 | """ 69 | seed_bytes = cast(create_string_buffer(seed), CK_BYTE_PTR) 70 | if isinstance(seed, (integer_types, float)): 71 | seed_length = seed 72 | else: 73 | seed_length = CK_ULONG(len(seed)) 74 | ret = C_SeedRandom(h_session, seed_bytes, seed_length) 75 | return ret 76 | 77 | 78 | c_seed_random_ex = make_error_handle_function(c_seed_random) 79 | 80 | 81 | def c_digest(h_session, data_to_digest, digest_flavor, mechanism=None, output_buffer=None): 82 | """Digests some data 83 | 84 | :param int h_session: Session handle 85 | :param bytes data_to_digest: The data to digest, either a string or a list of strings. 86 | If this is a list a multipart operation will be used 87 | :param int digest_flavor: The flavour of the mechanism to digest (MD2, SHA-1, HAS-160, 88 | SHA224, SHA256, SHA384, SHA512) 89 | :param mechanism: See the :py:func:`~pycryptoki.mechanism.parse_mechanism` function 90 | for possible values. If None will use digest flavor. 91 | :param list|int output_buffer: Integer or list of integers that specify a size of output 92 | buffer to use for an operation. By default will query with NULL pointer buffer 93 | to get required size of buffer. 94 | :returns: (retcode, a python string of the digested data) 95 | :rtype: tuple 96 | """ 97 | if mechanism is None: 98 | mech = parse_mechanism(digest_flavor) 99 | else: 100 | mech = parse_mechanism(mechanism) 101 | 102 | # Initialize Digestion 103 | ret = C_DigestInit(h_session, mech) 104 | if ret != CKR_OK: 105 | return ret, None 106 | 107 | # if a list is passed out do an digest operation on each string in the list, otherwise just 108 | # do one digest operation 109 | is_multi_part_operation = isinstance(data_to_digest, (list, tuple)) 110 | 111 | if is_multi_part_operation: 112 | ret, digested_python_string = do_multipart_sign_or_digest( 113 | h_session, C_DigestUpdate, C_DigestFinal, data_to_digest, output_buffer=output_buffer 114 | ) 115 | else: 116 | # Get arguments 117 | c_data_to_digest, c_digest_data_len = to_byte_array(from_bytestring(data_to_digest)) 118 | c_data_to_digest = cast(c_data_to_digest, POINTER(c_ubyte)) 119 | 120 | if output_buffer is not None: 121 | size = CK_ULONG(output_buffer) 122 | digested_data = AutoCArray(ctype=c_ubyte, size=size) 123 | ret = C_Digest( 124 | h_session, 125 | c_data_to_digest, 126 | c_digest_data_len, 127 | digested_data.array, 128 | digested_data.size, 129 | ) 130 | else: 131 | digested_data = AutoCArray(ctype=c_ubyte) 132 | 133 | @refresh_c_arrays(1) 134 | def _digest(): 135 | """Perform the digest operations""" 136 | return C_Digest( 137 | h_session, 138 | c_data_to_digest, 139 | c_digest_data_len, 140 | digested_data.array, 141 | digested_data.size, 142 | ) 143 | 144 | ret = _digest() 145 | 146 | if ret != CKR_OK: 147 | return ret, None 148 | 149 | # Convert Digested data into a python string 150 | digested_python_string = string_at(digested_data.array, digested_data.size.contents.value) 151 | 152 | return ret, digested_python_string 153 | 154 | 155 | c_digest_ex = make_error_handle_function(c_digest) 156 | 157 | 158 | def c_digestkey(h_session, h_key, digest_flavor, mechanism=None): 159 | """Digest a key 160 | 161 | :param int h_session: Session handle 162 | :param int h_key: Key to digest 163 | :param int digest_flavor: Digest flavor 164 | :param mechanism: See the :py:func:`~pycryptoki.mechanism.parse_mechanism` function 165 | for possible values. If None will use digest flavor. 166 | """ 167 | if mechanism is None: 168 | mech = parse_mechanism(digest_flavor) 169 | else: 170 | mech = parse_mechanism(mechanism) 171 | 172 | # Initialize Digestion 173 | ret = C_DigestInit(h_session, mech) 174 | if ret != CKR_OK: 175 | return ret 176 | 177 | ret = C_DigestKey(h_session, h_key) 178 | 179 | return ret 180 | 181 | 182 | c_digestkey_ex = make_error_handle_function(c_digestkey) 183 | 184 | 185 | def c_create_object(h_session, template): 186 | """Creates an object based on a given python template 187 | 188 | :param int h_session: Session handle 189 | :param dict template: The python template which the object will be based on 190 | :returns: (retcode, the handle of the object) 191 | :rtype: tuple 192 | """ 193 | c_template = Attributes(template).get_c_struct() 194 | new_object_handle = CK_ULONG() 195 | ret = C_CreateObject(h_session, c_template, CK_ULONG(len(template)), byref(new_object_handle)) 196 | 197 | return ret, new_object_handle.value 198 | 199 | 200 | c_create_object_ex = make_error_handle_function(c_create_object) 201 | 202 | 203 | def c_set_ped_id(slot, id): 204 | """Set the PED ID for the given slot. 205 | 206 | :param slot: slot number 207 | :param id: PED ID to use 208 | :returns: The result code 209 | 210 | """ 211 | ret = CA_SetPedId(CK_SLOT_ID(slot), CK_ULONG(id)) 212 | return ret 213 | 214 | 215 | c_set_ped_id_ex = make_error_handle_function(c_set_ped_id) 216 | 217 | 218 | def c_get_ped_id(slot): 219 | """Get the PED ID for the given slot. 220 | 221 | :param slot: slot number 222 | :returns: The result code and ID 223 | 224 | """ 225 | pedId = CK_ULONG() 226 | ret = CA_GetPedId(CK_SLOT_ID(slot), byref(pedId)) 227 | return ret, pedId.value 228 | 229 | 230 | c_get_ped_id_ex = make_error_handle_function(c_get_ped_id) 231 | -------------------------------------------------------------------------------- /pycryptoki/object_attr_lookup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Functions for dealing with object attributes 3 | """ 4 | import logging 5 | from ctypes import byref, cast, c_void_p 6 | 7 | from .attributes import Attributes, c_struct_to_python, KEY_TRANSFORMS 8 | from .cryptoki import ( 9 | CK_OBJECT_HANDLE, 10 | C_FindObjectsInit, 11 | CK_ULONG, 12 | C_FindObjects, 13 | C_FindObjectsFinal, 14 | C_GetAttributeValue, 15 | C_SetAttributeValue, 16 | ) 17 | from .defines import CKR_OK 18 | from .exceptions import make_error_handle_function 19 | 20 | LOG = logging.getLogger(__name__) 21 | 22 | 23 | def c_find_objects(h_session, template, num_entries): 24 | """Calls c_find_objects and c_find_objects_init to get a python dictionary 25 | of the objects found. 26 | 27 | :param int h_session: Session handle 28 | :param template: A python dictionary of the object template to look for 29 | :param num_entries: The max number of entries to return 30 | :returns: Returns a list of handles of objects found 31 | 32 | """ 33 | struct = Attributes(template).get_c_struct() 34 | ret = C_FindObjectsInit(h_session, struct, CK_ULONG(len(template))) 35 | if ret != CKR_OK: 36 | return ret, None 37 | 38 | h_ary = (CK_OBJECT_HANDLE * num_entries)() 39 | us_total = CK_ULONG(num_entries) 40 | ret = C_FindObjects(h_session, h_ary, CK_ULONG(num_entries), byref(us_total)) 41 | if ret != CKR_OK: 42 | return ret, None 43 | 44 | ret = C_FindObjectsFinal(h_session) 45 | 46 | return ret, [h_ary[i] for i in range(us_total.value)] 47 | 48 | 49 | c_find_objects_ex = make_error_handle_function(c_find_objects) 50 | 51 | 52 | def c_get_attribute_value(h_session, h_object, template): 53 | """Calls C_GetAttrributeValue to get an attribute value based on a python template 54 | 55 | :param int h_session: Session handle 56 | :param h_object: The handle of the object to get attributes for 57 | :param template: A python dictionary representing the template of the attributes to be retrieved 58 | :returns: A python dictionary representing the attributes returned from the HSM/library 59 | 60 | """ 61 | c_struct = Attributes(template).get_c_struct() 62 | unknown_key_vals = [key for key, value in template.items() if value is None] 63 | if unknown_key_vals: 64 | LOG.debug("Retrieving Attribute Length for keys %s", unknown_key_vals) 65 | # We need to get the size of the target memory area first, then 66 | # we can allocate the mem size. 67 | ret = C_GetAttributeValue(h_session, h_object, c_struct, CK_ULONG(len(template))) 68 | if ret != CKR_OK: 69 | return ret, None 70 | 71 | for index in range(0, len(c_struct)): 72 | key_type = c_struct[index].type 73 | if any(key_type == unknown_key_type for unknown_key_type in unknown_key_vals): 74 | # Allocate memory for the type. 75 | c_obj_type = KEY_TRANSFORMS[key_type].ctype 76 | mem = (c_obj_type * c_struct[index].usValueLen)() 77 | c_struct[index].pValue = cast(mem, c_void_p) 78 | 79 | ret = C_GetAttributeValue(h_session, h_object, c_struct, CK_ULONG(len(template))) 80 | if ret != CKR_OK: 81 | return ret, None 82 | 83 | return ret, c_struct_to_python(c_struct) 84 | 85 | 86 | c_get_attribute_value_ex = make_error_handle_function(c_get_attribute_value) 87 | 88 | 89 | def c_set_attribute_value(h_session, h_object, template): 90 | """Calls C_SetAttributeValue to set an attribute value based on a python template 91 | 92 | :param int h_session: Session handle 93 | :param h_object: The handle of the object to get attributes for 94 | :param template: A python dictionary representing the template of the attributes to be written 95 | :returns: A python dictionary representing the attributes returned from the HSM/library 96 | 97 | """ 98 | c_struct = Attributes(template).get_c_struct() 99 | ret = C_SetAttributeValue(h_session, h_object, c_struct, CK_ULONG(len(template))) 100 | return ret 101 | 102 | 103 | c_set_attribute_value_ex = make_error_handle_function(c_set_attribute_value) 104 | -------------------------------------------------------------------------------- /pycryptoki/pycryptoki_warnings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Warning classes 3 | """ 4 | 5 | 6 | class CryptoWarning(Warning): 7 | """General (py)cryptoki warning""" 8 | 9 | pass 10 | -------------------------------------------------------------------------------- /pycryptoki/return_values.py: -------------------------------------------------------------------------------- 1 | """ 2 | Lookup dictionary for converting CK_ULONG return codes into 3 | their string equivalents -- backwards compatibility 4 | """ 5 | import warnings 6 | 7 | warnings.warn("Deprecated! Use 'pycryptoki.lookup_dicts' instead", DeprecationWarning) 8 | 9 | # Backwards compatibility for now... 10 | from .lookup_dicts import ret_vals_dictionary 11 | -------------------------------------------------------------------------------- /pycryptoki/string_helpers.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | 3 | from six import integer_types, binary_type, string_types 4 | 5 | from pycryptoki.lookup_dicts import ATTR_NAME_LOOKUP, MECH_NAME_LOOKUP 6 | from .conversions import to_hex, from_bytestring 7 | from .cryptoki import CK_MECHANISM 8 | 9 | PYC_MAX_ARG_LENGTH = 40 10 | 11 | 12 | def _decode(value): 13 | """ 14 | Attempt to convert a bytestring into something more readable. Assumes hex first, if that fails and non-standard 15 | characters are in the bytestring ( e.g. ``\x12``), convert to hex. 16 | 17 | Anything converted to hex will be returned as UTF-8; anything that is left 'as-is', will stay bytestring. 18 | """ 19 | try: 20 | binascii.unhexlify(value) 21 | # value is already valid hex, return it. 22 | return value.decode("utf-8", "ignore") 23 | except (binascii.Error, TypeError, AttributeError): 24 | if "\\x" in repr(value): 25 | return to_hex(from_bytestring(value)).decode("utf-8", "ignore") 26 | return value 27 | 28 | 29 | def _coerce_mech_to_str(mech): 30 | """ 31 | Similar to the ``parse_mechanism`` function, but instead of creating the C mechanism structures, 32 | we want to print out the mechanism in a nicer way. 33 | 34 | :param mech: Dict, Mechanism class, or integer 35 | :return: String display of a mechanism. 36 | """ 37 | from .mechanism import Mechanism, MechanismException 38 | 39 | try: 40 | if isinstance(mech, dict): 41 | mech = Mechanism(**mech) 42 | elif isinstance(mech, CK_MECHANISM): 43 | mech = mech 44 | elif isinstance(mech, integer_types): 45 | mech = Mechanism(mech_type=mech) 46 | elif isinstance(mech, Mechanism): 47 | pass 48 | except MechanismException: 49 | mech = MECH_NAME_LOOKUP.get(mech, mech) 50 | 51 | return str(mech) 52 | 53 | 54 | def _trunc(val, val_len=None): 55 | msg = str(val) 56 | if val_len is None: 57 | try: 58 | val_len = len(val) 59 | except TypeError: 60 | val_len = len(msg) 61 | 62 | if len(msg) > PYC_MAX_ARG_LENGTH: 63 | msg = "%s[...] (len: %s)" % (msg[:PYC_MAX_ARG_LENGTH], val_len,) 64 | return msg 65 | 66 | 67 | def pformat_pyc_args(func_args): 68 | """ 69 | Convert a dictionary of funcargs: funcvalues into a nicely formatted string. 70 | 71 | This will resolve template dictionary keys to CKA_ names, convert bytestring to 72 | hex, mask passwords, and truncate args over ``PYC_MAX_ARG_LENGTH`` to that size. 73 | 74 | :param func_args: dictionary 75 | :return: List of string lines 76 | """ 77 | log_list = [] 78 | for key, value in func_args.items(): 79 | if "template" in key and isinstance(value, dict): 80 | # Means it's a template, so let's perform a lookup on all of the objects within 81 | # this. 82 | log_list.append("%s: " % key) 83 | # Sorted so we get the same order every time for testing purposes. 84 | for template_key, template_value in sorted(value.items(), key=lambda x: x[0]): 85 | log_list.append( 86 | "\t%s: %s" 87 | % ( 88 | ATTR_NAME_LOOKUP.get(template_key, "0x%08x" % template_key), 89 | _trunc(_decode(template_value)), 90 | ) 91 | ) 92 | elif "password" in key: 93 | log_list.append("%s: *" % key) 94 | elif "mechanism" in key: 95 | log_list.append("%s: " % key) 96 | nice_mech = _coerce_mech_to_str(value).splitlines() 97 | log_list.extend(["\t%s" % x for x in nice_mech]) 98 | else: 99 | log_val = value 100 | val_len = None 101 | # Note: we get the length of value before we decode/truncate it. 102 | if isinstance(value, (binary_type, string_types)): 103 | if isinstance(value, binary_type): 104 | log_val = _decode(value) 105 | val_len = len(value) 106 | else: 107 | log_val = str(value) 108 | 109 | msg = "\t%s: %s" % (key, _trunc(log_val, val_len)) 110 | 111 | log_list.append(msg) 112 | 113 | return log_list 114 | -------------------------------------------------------------------------------- /pycryptoki/test_functions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Functions used for testing, or verifying return values. 3 | """ 4 | import sys 5 | import logging 6 | from ctypes import byref 7 | 8 | if sys.version_info < (3,): 9 | integer_types = (int, long) # noqa: F821 10 | else: 11 | integer_types = (int,) 12 | 13 | from .cryptoki import CK_OBJECT_HANDLE, CK_ULONG, C_GetObjectSize 14 | from .defines import CKR_OBJECT_HANDLE_INVALID 15 | from .defines import CKR_OK 16 | from .lookup_dicts import ret_vals_dictionary 17 | from .exceptions import ( 18 | LunaCallException, 19 | LunaException, # Backwards compatibility for external imports 20 | make_error_handle_function, 21 | ) 22 | 23 | _ = LunaException, make_error_handle_function 24 | LOG = logging.getLogger(__name__) 25 | 26 | 27 | def assert_test_return_value(value, expected_value, message, print_on_success=True): 28 | """Asserts a pass or fail based on whether the value parameter is equal to the expected_value 29 | parameter. 30 | Used to test the results of pkcs11 functions and looks up human readable strings for the 31 | various error codes. 32 | Prints out results in a consistent format. 33 | 34 | :param value: The return value of the pkcs11 function 35 | :param expected_value: The expected return value to be tested against 36 | :param message: Message to print on success/failure 37 | :param print_on_success: Whether or not to print if the test case passes (Default value = True) 38 | 39 | """ 40 | if value in ret_vals_dictionary: 41 | code = ret_vals_dictionary[value] 42 | else: 43 | code = "Unknown Code=" + str(hex(value)) 44 | 45 | if expected_value in ret_vals_dictionary: 46 | exp_code = ret_vals_dictionary[expected_value] 47 | else: 48 | exp_code = "Unknown Code=" + str(hex(value)) 49 | 50 | assert value == expected_value, ( 51 | "\nERROR: " + message + "\n\tExpected: " + exp_code + "\n\tFound: " + code 52 | ) 53 | if print_on_success: 54 | LOG.info("%s: %s", exp_code, message) 55 | 56 | 57 | class LunaReturn(object): 58 | """ """ 59 | 60 | def __init__(self, return_code, return_data): 61 | self.return_code = return_code 62 | self.return_data = return_data 63 | 64 | 65 | def verify_object_attributes(h_session, h_object, expected_template): 66 | """Verifies that an object generated has the correct attributes on the board. 67 | The expected attributes are passed in alongside the handle of the object. 68 | 69 | :param int h_session: Session handle 70 | :param h_object: Handle of the object to verify the attributes against 71 | :param expected_template: The expected template to compare against 72 | 73 | """ 74 | from .object_attr_lookup import c_get_attribute_value_ex 75 | 76 | # VERIFY OBJECT EXISTS 77 | h_object = CK_OBJECT_HANDLE(h_object) 78 | us_size = CK_ULONG() 79 | ret = C_GetObjectSize(h_session, h_object, byref(us_size)) 80 | assert ret == CKR_OK, "Object " + str(h_object) + " exists" 81 | assert us_size.value > 0, "Object " + str(h_object.value) + " size is greater than zero." 82 | 83 | # VERIFY ATTRIBUTES are the same as the ones passed in 84 | desired_attrs = {x: None for x in expected_template.keys()} 85 | attr = c_get_attribute_value_ex(h_session, h_object, template=desired_attrs) 86 | assert attr == expected_template 87 | 88 | 89 | def verify_object_exists(h_session, h_object, should_exist=True): 90 | """Queries the HSM to determine if an object exists. Asserts whether or not 91 | it exists. 92 | 93 | :param int h_session: Session handle 94 | :param h_object: The object to verify if it exists 95 | :param should_exist: Whether or not the parameter should exist (Default value = True) 96 | 97 | """ 98 | # VERIFY OBJECT EXISTS 99 | h_object = CK_OBJECT_HANDLE(h_object) 100 | us_size = CK_ULONG() 101 | 102 | if should_exist: 103 | expected_ret = CKR_OK 104 | out = "Verifying object " + str(h_object) + " exists." 105 | else: 106 | expected_ret = CKR_OBJECT_HANDLE_INVALID 107 | out = "Verifying object " + str(h_object) + " doesn't exist." 108 | 109 | try: 110 | ret = C_GetObjectSize(h_session, h_object, byref(us_size)) 111 | except LunaCallException as e: 112 | assert e.error_code == expected_ret, out 113 | else: 114 | assert ret == expected_ret, out 115 | 116 | if should_exist: 117 | assert_test_return_value( 118 | ret, CKR_OK, "Getting object " + str(h_object.value) + "'s size", True 119 | ) 120 | assert us_size.value > 0, "Object " + str(h_object.value) + " size is greater than zero." 121 | else: 122 | assert_test_return_value( 123 | ret, 124 | CKR_OBJECT_HANDLE_INVALID, 125 | "Getting object " + str(h_object.value) + "'s size", 126 | True, 127 | ) 128 | assert us_size.value <= 0, "Object " + str(h_object.value) + " size is greater than zero." 129 | -------------------------------------------------------------------------------- /pycryptoki/token_management.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on Aug 24, 2012 3 | 4 | @author: mhughes 5 | """ 6 | import logging 7 | from ctypes import byref 8 | 9 | # Cryptoki Constants 10 | from six import b 11 | 12 | from .cryptoki import CK_ULONG, CK_BBOOL, CK_MECHANISM_TYPE, CK_MECHANISM_INFO 13 | from .defaults import ADMIN_PARTITION_LABEL, ADMIN_SLOT 14 | from .defines import CKR_OK 15 | 16 | # Cryptoki functions. 17 | from .cryptoki import ( 18 | C_InitToken, 19 | C_GetSlotList, 20 | C_GetMechanismList, 21 | C_GetMechanismInfo, 22 | CA_GetTokenPolicies, 23 | ) 24 | from .session_management import c_get_token_info 25 | from .exceptions import make_error_handle_function 26 | from .common_utils import AutoCArray 27 | from .common_utils import refresh_c_arrays 28 | 29 | LOG = logging.getLogger(__name__) 30 | 31 | 32 | def c_init_token(slot_num, password, token_label="Main Token"): 33 | """Initializes at token at a given slot with the proper password and label 34 | 35 | :param slot_num: The index of the slot to c_initialize a token in 36 | :param password: The password to c_initialize the slot with 37 | :param token_label: The label to c_initialize the slot with (Default value = 'Main Token') 38 | :returns: The result code 39 | 40 | """ 41 | LOG.info( 42 | "C_InitToken: Initializing token (slot=%s, label='%s', password='%s')", 43 | slot_num, 44 | token_label, 45 | password, 46 | ) 47 | 48 | if password == b"": 49 | password = None 50 | password = AutoCArray(data=password) 51 | slot_id = CK_ULONG(slot_num) 52 | label = AutoCArray(data=token_label) 53 | 54 | return C_InitToken(slot_id, password.array, password.size.contents, label.array) 55 | 56 | 57 | c_init_token_ex = make_error_handle_function(c_init_token) 58 | 59 | 60 | def get_token_by_label(label): 61 | """Iterates through all the tokens and returns the first token that 62 | has a label that is identical to the one that is passed in 63 | 64 | :param label: The label of the token to search for 65 | :returns: The result code, The slot of the token 66 | 67 | """ 68 | 69 | if label == ADMIN_PARTITION_LABEL: 70 | # XXX the admin partition's label changes depending on 71 | # the boards state 72 | # ret, slot_info = get_slot_info("Viper") 73 | # return ret, slot_info.keys()[1] 74 | return CKR_OK, ADMIN_SLOT 75 | 76 | slot_list = AutoCArray() 77 | 78 | @refresh_c_arrays(1) 79 | def _get_slot_list(): 80 | """Closure""" 81 | return C_GetSlotList(CK_BBOOL(1), slot_list.array, slot_list.size) 82 | 83 | ret = _get_slot_list() 84 | if ret != CKR_OK: 85 | return ret, None 86 | 87 | for slot in slot_list: 88 | ret, token_info = c_get_token_info(slot) 89 | # Converting label parameter to bytes --- label is returned in bytes anyway. 90 | # on python2, you could do str(label) == info['label'], but this would fail on python3 w/ 91 | # the default str() -> unicode change. 92 | if token_info["label"] == b(label): 93 | return ret, slot 94 | 95 | raise Exception("Slot with label " + str(label) + " not found.") 96 | 97 | 98 | get_token_by_label_ex = make_error_handle_function(get_token_by_label) 99 | 100 | 101 | def c_get_mechanism_list(slot): 102 | """Gets the list of mechanisms from the HSM 103 | 104 | :param slot: The slot number to get the mechanism list on 105 | :returns: The result code, A python dictionary representing the mechanism list 106 | 107 | """ 108 | slot_id = CK_ULONG(slot) 109 | mech = AutoCArray(ctype=CK_MECHANISM_TYPE) 110 | 111 | @refresh_c_arrays(1) 112 | def _c_get_mech_list(): 113 | """Closure for retry to work w/ properties.""" 114 | return C_GetMechanismList(slot_id, mech.array, mech.size) 115 | 116 | ret = _c_get_mech_list() 117 | return ret, [x for x in mech] 118 | 119 | 120 | c_get_mechanism_list_ex = make_error_handle_function(c_get_mechanism_list) 121 | 122 | 123 | def c_get_mechanism_info(slot, mechanism_type): 124 | """Gets a mechanism's info 125 | 126 | :param slot: The slot to query 127 | :param mechanism_type: The type of the mechanism to get the information for 128 | :returns: The result code, The mechanism info 129 | 130 | """ 131 | mech_info = CK_MECHANISM_INFO() 132 | ret = C_GetMechanismInfo(CK_ULONG(slot), CK_MECHANISM_TYPE(mechanism_type), byref(mech_info)) 133 | return ret, mech_info 134 | 135 | 136 | c_get_mechanism_info_ex = make_error_handle_function(c_get_mechanism_info) 137 | 138 | 139 | def ca_get_token_policies(slot): 140 | """ 141 | Get the policies of the given slot. 142 | 143 | :param int slot: Target slot number 144 | :return: retcode, {id: val} dict of policies (None if command failed) 145 | """ 146 | slot_id = CK_ULONG(slot) 147 | pol_ids = AutoCArray() 148 | pol_vals = AutoCArray() 149 | 150 | @refresh_c_arrays(1) 151 | def _get_token_policies(): 152 | """Closure for retries to work w/ properties.""" 153 | return CA_GetTokenPolicies( 154 | slot_id, pol_ids.array, pol_ids.size, pol_vals.array, pol_vals.size 155 | ) 156 | 157 | ret = _get_token_policies() 158 | 159 | return ret, dict(list(zip(pol_ids, pol_vals))) 160 | 161 | 162 | ca_get_token_policies_ex = make_error_handle_function(ca_get_token_policies) 163 | -------------------------------------------------------------------------------- /pycryptoki/utilities.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for user convenience. Differs from: 3 | 4 | - common_utils.py -- used by pycryptoki modules to ease cryptoki operations 5 | - cryptoki_helpers.py -- used to bootstrap pycryptoki 6 | 7 | """ 8 | 9 | from pycryptoki.defines import CKF_RW_SESSION, CKF_SERIAL_SESSION 10 | from pycryptoki.exceptions import LunaException 11 | from pycryptoki.session_management import ( 12 | c_initialize_ex, 13 | c_open_session_ex, 14 | login_ex, 15 | c_logout_ex, 16 | c_close_session_ex, 17 | c_finalize_ex, 18 | c_get_session_info_ex, 19 | ) 20 | 21 | 22 | class CryptokiInitialized(object): 23 | """Initialized context""" 24 | 25 | def __enter__(self): 26 | """Initialize cryptoki""" 27 | c_initialize_ex() 28 | 29 | def __exit__(self, exc_type, exc_value, exc_traceback): 30 | """Finalize cryptoki""" 31 | c_finalize_ex() 32 | 33 | 34 | class Session(object): 35 | """Opened session""" 36 | 37 | def __init__(self, slot, flags=CKF_SERIAL_SESSION | CKF_RW_SESSION, initialized=False): 38 | """ 39 | Cryptoki can optionally already be initialized. 40 | 41 | :param slot: slot number 42 | :param flags: session flags 43 | :param initialized: whether cryptoki has already been initialized 44 | """ 45 | self.slot = slot 46 | self.flags = flags 47 | self.manage_init = not initialized 48 | self.session = None 49 | 50 | def __enter__(self): 51 | """Open the session.""" 52 | return self.open() 53 | 54 | def __exit__(self, exc_type, exc_value, exc_traceback): 55 | """Close the session.""" 56 | self.close() 57 | 58 | def open(self): 59 | """ 60 | Open the session, initializing cryptoki if necessary. 61 | 62 | :return: session handle 63 | """ 64 | if self.manage_init: 65 | c_initialize_ex() 66 | try: 67 | self.session = c_open_session_ex(self.slot, self.flags) 68 | return self.session 69 | except: 70 | if self.manage_init: 71 | c_finalize_ex() 72 | raise 73 | 74 | def close(self): 75 | """Close the session. Finalize if we initialized.""" 76 | c_close_session_ex(self.session) 77 | if self.manage_init: 78 | c_finalize_ex() 79 | 80 | 81 | class AuthenticatedSession(object): 82 | """Session which has a user authenticated; C_Login is run""" 83 | 84 | def __init__(self, password, user, session=None, slot=None): 85 | """ 86 | A session can optionally already be open. Otherwise open one on entry. 87 | It is an error to omit both session and slot. 88 | 89 | :param password: user password 90 | :param user: user 91 | :param session: session handle of an already open session 92 | :param slot: slot number 93 | """ 94 | if session is None and slot is None: 95 | raise LunaException("A slot ID or a session handle must be specified!") 96 | 97 | self.password = password 98 | self.user = user 99 | self.session = session # Session handle 100 | self.slot = slot 101 | self._session = None # Session object 102 | 103 | def __enter__(self): 104 | """ 105 | Log in to a session. Open a session if one was not given. 106 | 107 | :return: session handle 108 | """ 109 | if self.session is None: 110 | self._session = Session(self.slot) 111 | self.session = self._session.open() 112 | else: 113 | self.slot = c_get_session_info_ex(self.session)["slotID"] 114 | try: 115 | login_ex(self.session, self.slot, self.password, self.user) 116 | return self.session 117 | except: 118 | if self._session: 119 | self._session.close() 120 | raise 121 | 122 | def __exit__(self, exc_type, exc_value, exc_traceback): 123 | """ 124 | Log out of the session. Close the session if we opened it. 125 | """ 126 | c_logout_ex(self.session) 127 | if self._session: 128 | self._session.close() 129 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # note: rpyc == 3.4.4 for pythons < 3; rpyc == 4.0.2 for python >=3 2 | rpyc 3 | hypothesis 4 | pytest 5 | future 6 | mock 7 | six==1.16.0 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Script used by distutils to automatically generate a source code 3 | distribution of this python module (a .tar.gz file containing 4 | all of the source code). 5 | 6 | To generate this file run: 7 | python setup.py sdist 8 | """ 9 | from setuptools import setup 10 | 11 | setup( 12 | name="pycryptoki", 13 | description="A python wrapper around the C cryptoki library.", 14 | author="Ashley Straw", 15 | url="https://github.com/gemalto/pycryptoki", 16 | version="2.6.6", 17 | packages=[ 18 | "pycryptoki", 19 | "pycryptoki.cryptoki", 20 | "pycryptoki.daemon", 21 | "pycryptoki.mechanism", 22 | "pycryptoki.ca_extensions", 23 | ], 24 | scripts=["pycryptoki/daemon/rpyc_pycryptoki.py"], 25 | tests_require=["pytest", "hypothesis", "mock", "pytz"], 26 | install_requires=[ 27 | "future", 28 | "rpyc==3.4.4;python_version<='2.7'", 29 | "rpyc==6.0.0;python_version>'3'", 30 | "six==1.16.0", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /test_requirements.txt: -------------------------------------------------------------------------------- 1 | pytz 2 | mock 3 | pytest 4 | hypothesis 5 | pytest-coverage 6 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThalesGroup/pycryptoki/ae98be3b9271e90fe9d38905884bfae79bd7dbe0/tests/__init__.py -------------------------------------------------------------------------------- /tests/functional/__init__.py: -------------------------------------------------------------------------------- 1 | config = {} 2 | -------------------------------------------------------------------------------- /tests/functional/cpv4_util.py: -------------------------------------------------------------------------------- 1 | """Some utility functions for CPV4""" 2 | from ctypes import c_ubyte, c_void_p, c_ulong, cast, pointer, sizeof, POINTER 3 | 4 | from pycryptoki.conversions import from_bytestring 5 | from pycryptoki.attributes import to_byte_array 6 | 7 | from pycryptoki.defines import CKM_CPV4_EXTRACT, CKM_CPV4_INSERT, CKF_CPV4_CONTINUE_ON_ERR, CKR_OK 8 | from pycryptoki.cryptoki import ( 9 | CK_MECHANISM, 10 | CK_MECHANISM_TYPE, 11 | CK_CPV4_EXTRACT_PARAMS, 12 | CK_CPV4_INSERT_PARAMS, 13 | CK_BYTE_PTR, 14 | ) 15 | 16 | PCPROT_MAX_BUFFER_SIZE = 64000 17 | 18 | 19 | def initialize_cpv4_extract_params( 20 | obj_handles, obj_type, input_param, session_ouid, extraction_flags 21 | ): 22 | """ 23 | 24 | struct_def( 25 | CK_CPV4_EXTRACT_PARAMS, 26 | [ 27 | ("inputLength", CK_ULONG), 28 | ("input", CK_BYTE_PTR), 29 | ("sessionOuidLen", CK_ULONG), 30 | ("sessionOuid", CK_BYTE_PTR), 31 | ("extractionFlags", CK_ULONG), 32 | ("numberOfObjects", CK_ULONG), 33 | ("objectType", CK_ULONG_PTR), 34 | ("objectHandle", CK_ULONG_PTR), 35 | ("result", CK_ULONG_PTR), 36 | ("keyBlobLen", CK_ULONG_PTR), 37 | ("keyBlob", POINTER(CK_BYTE_PTR)), 38 | ], 39 | ) 40 | 41 | """ 42 | input_param, input_len = to_byte_array(from_bytestring(input_param)) 43 | input_param = cast(input_param, POINTER(c_ubyte)) 44 | 45 | session_ouid, session_ouid_len = to_byte_array(from_bytestring(session_ouid)) 46 | session_ouid = cast(session_ouid, POINTER(c_ubyte)) 47 | 48 | cpv4_extract_params = CK_CPV4_EXTRACT_PARAMS() 49 | 50 | cpv4_extract_params.input = input_param 51 | cpv4_extract_params.inputLength = input_len 52 | cpv4_extract_params.sessionOuidLen = session_ouid_len 53 | cpv4_extract_params.sessionOuid = session_ouid 54 | cpv4_extract_params.extractionFlags = extraction_flags 55 | cpv4_extract_params.objectType = (c_ulong * len(obj_type))(*obj_type) 56 | cpv4_extract_params.objectHandle = (c_ulong * len(obj_handles))(*obj_handles) 57 | cpv4_extract_params.numberOfObjects = c_ulong(len(obj_handles)) 58 | cpv4_extract_params.result = (c_ulong * len(obj_handles))() 59 | cpv4_extract_params.keyBlobLen = (c_ulong * len(obj_handles))() 60 | cpv4_extract_params.keyBlob = keyBlob = (len(obj_handles) * POINTER(c_ubyte))() 61 | 62 | for i in range(len(obj_handles)): 63 | cpv4_extract_params.keyBlob[i] = (c_ubyte * PCPROT_MAX_BUFFER_SIZE)() 64 | cpv4_extract_params.keyBlobLen[i] = PCPROT_MAX_BUFFER_SIZE 65 | 66 | return cpv4_extract_params 67 | 68 | 69 | def initialize_cpv4_insert_params( 70 | input_param, 71 | session_ouid, 72 | number_of_objects, 73 | obj_type, 74 | storage_type, 75 | key_blob_len, 76 | key_blob, 77 | insertion_flags, 78 | ): 79 | """ 80 | struct_def( 81 | CK_CPV4_INSERT_PARAMS, 82 | [ 83 | ("inputLength", CK_ULONG), 84 | ("input", CK_BYTE_PTR), 85 | ("sessionOuidLen", CK_ULONG), 86 | ("sessionOuid", CK_BYTE_PTR), 87 | ("insertionFlags", CK_ULONG), 88 | ("numberOfObjects", CK_ULONG), 89 | ("objectType", CK_ULONG_PTR), 90 | ("storageType", CK_ULONG_PTR), 91 | ("keyBlobLen", CK_ULONG_PTR), 92 | ("keyBlob", POINTER(CK_BYTE_PTR)), 93 | ("result", CK_ULONG_PTR), 94 | ("objectHandle", CK_ULONG_PTR), 95 | ], 96 | ) 97 | 98 | """ 99 | input_param, input_len = to_byte_array(from_bytestring(input_param)) 100 | input_param = cast(input_param, POINTER(c_ubyte)) 101 | 102 | session_ouid, session_ouid_len = to_byte_array(from_bytestring(session_ouid)) 103 | session_ouid = cast(session_ouid, POINTER(c_ubyte)) 104 | 105 | cpv4_insert_params = CK_CPV4_INSERT_PARAMS() 106 | 107 | cpv4_insert_params.inputLength = input_len 108 | cpv4_insert_params.input = input_param 109 | cpv4_insert_params.sessionOuidLen = session_ouid_len 110 | cpv4_insert_params.sessionOuid = session_ouid 111 | cpv4_insert_params.insertionFlags = insertion_flags 112 | cpv4_insert_params.numberOfObjects = number_of_objects 113 | cpv4_insert_params.objectType = (c_ulong * len(obj_type))(*obj_type) 114 | cpv4_insert_params.storageType = (c_ulong * len(storage_type))(*storage_type) 115 | cpv4_insert_params.keyBlobLen = key_blob_len 116 | cpv4_insert_params.keyBlob = key_blob 117 | cpv4_insert_params.objectHandle = (c_ulong * number_of_objects)() 118 | cpv4_insert_params.result = (c_ulong * number_of_objects)() 119 | 120 | return cpv4_insert_params 121 | 122 | 123 | def create_cpv4_extract_mech(cpv4_extract_params): 124 | mech = CK_MECHANISM() 125 | mech.mechanism = CK_MECHANISM_TYPE(CKM_CPV4_EXTRACT) 126 | mech.pParameter = cast(pointer(cpv4_extract_params), c_void_p) 127 | mech.usParameterLen = sizeof(cpv4_extract_params) 128 | return mech 129 | 130 | 131 | def create_cpv4_insert_mech(cpv4_insert_params): 132 | mech = CK_MECHANISM() 133 | mech.mechanism = CK_MECHANISM_TYPE(CKM_CPV4_INSERT) 134 | mech.pParameter = cast(pointer(cpv4_insert_params), c_void_p) 135 | mech.usParameterLen = sizeof(cpv4_insert_params) 136 | return mech 137 | 138 | 139 | def generate_cpv4_extract_mech_params( 140 | obj_handles, obj_type, input_param, session_ouid, extraction_flags=CKF_CPV4_CONTINUE_ON_ERR 141 | ): 142 | cpv4_extract_params = initialize_cpv4_extract_params( 143 | obj_handles, obj_type, input_param, session_ouid, extraction_flags 144 | ) 145 | 146 | return create_cpv4_extract_mech(cpv4_extract_params) 147 | 148 | 149 | def generate_cpv4_insert_mech( 150 | input_param, 151 | session_ouid, 152 | number_of_objects, 153 | obj_type, 154 | storage_type, 155 | key_blob_len, 156 | key_blob, 157 | insertion_flags=CKF_CPV4_CONTINUE_ON_ERR, 158 | ): 159 | cpv4_insert_params = initialize_cpv4_insert_params( 160 | input_param, 161 | session_ouid, 162 | number_of_objects, 163 | obj_type, 164 | storage_type, 165 | key_blob_len, 166 | key_blob, 167 | insertion_flags, 168 | ) 169 | 170 | # Create Put Mechanism object 171 | return create_cpv4_insert_mech(cpv4_insert_params) 172 | 173 | 174 | def verify_extracted_objects(cpv4_extracted_params, expected_retcode): 175 | """ 176 | verifies extracted objects 177 | 178 | CK_CPV4_EXTRACT_PARAMS, 179 | [ 180 | ("inputLength", CK_ULONG), 181 | ("input", CK_BYTE_PTR), 182 | ("sessionOuidLen", CK_ULONG), 183 | ("sessionOuid", CK_BYTE_PTR), 184 | ("extractionFlags", CK_ULONG), 185 | ("numberOfObjects", CK_ULONG), 186 | ("objectType", CK_ULONG_PTR), 187 | ("objectHandle", CK_ULONG_PTR), 188 | ("result", CK_ULONG_PTR), 189 | ("keyBlobLen", CK_ULONG_PTR), 190 | ("keyBlob", POINTER(CK_BYTE_PTR)), 191 | ], 192 | """ 193 | cpv4_params = cast(cpv4_extracted_params, POINTER(CK_CPV4_EXTRACT_PARAMS)).contents 194 | 195 | number_of_objects = cpv4_params.numberOfObjects 196 | results = cpv4_params.result 197 | key_blobs = cpv4_params.keyBlob 198 | key_blobs_len = cpv4_params.keyBlobLen 199 | 200 | # assert len(results) == numberOfObjs 201 | # assert len(keyBlobs) == numberOfObjs 202 | # assert len(keyBlobsLen) == numberOfObjs 203 | 204 | for i in range(number_of_objects): 205 | assert expected_retcode[i] == results[i] 206 | if results[i] != CKR_OK: 207 | del key_blobs[i] 208 | del key_blobs_len[i] 209 | 210 | return key_blobs, key_blobs_len 211 | -------------------------------------------------------------------------------- /tests/functional/test_audit_handling.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | from . import config as hsm_config 5 | from pycryptoki.defines import CKR_OK 6 | import pycryptoki.audit_handling as audit_handling 7 | 8 | from ctypes import c_ulong 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class TestAuditHandling(object): 14 | @pytest.fixture(autouse=True) 15 | def setup_teardown(self, auth_session): 16 | self.admin_slot = hsm_config["test_slot"] 17 | self.h_session = auth_session 18 | 19 | def test_ca_get_time(self): 20 | """ ca_get_time() """ 21 | ret, hsm_time = audit_handling.ca_get_time(self.h_session) 22 | assert ret == CKR_OK 23 | # Checks time formatting but not value of returned time 24 | assert isinstance(hsm_time, c_ulong) 25 | -------------------------------------------------------------------------------- /tests/functional/test_digest_data.py: -------------------------------------------------------------------------------- 1 | """ Functional tests for digest data """ 2 | import logging 3 | import pytest 4 | 5 | from pycryptoki.lookup_dicts import ret_vals_dictionary 6 | from pycryptoki.defines import ( 7 | CKR_OK, 8 | CKM_MD2, 9 | CKM_SHA_1, 10 | CKM_SHA224, 11 | CKM_SHA256, 12 | CKM_SHA384, 13 | CKM_SHA512, 14 | ) 15 | from pycryptoki.misc import c_digest 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | MECHS = { 20 | CKM_MD2: "MD2", 21 | CKM_SHA_1: "SHA1", 22 | CKM_SHA224: "SHA224", 23 | CKM_SHA256: "SHA256", 24 | CKM_SHA384: "SHA384", 25 | CKM_SHA512: "SHA512", 26 | } 27 | 28 | DATA = [b"Some arbitrary string", [b"Some arbitrary string", b"Some second arbitrary string"]] 29 | 30 | 31 | class TestDigestData(object): 32 | def verify_ret(self, ret, expected_ret): 33 | """ 34 | Assert that ret is as expected 35 | :param ret: the actual return value 36 | :param expected_ret: the expected return value 37 | """ 38 | assert ret == expected_ret, ( 39 | "Function should return: " 40 | + ret_vals_dictionary[expected_ret] 41 | + ".\nInstead returned: " 42 | + ret_vals_dictionary[ret] 43 | ) 44 | 45 | @pytest.fixture(autouse=True) 46 | def setup_teardown(self, auth_session): 47 | self.h_session = auth_session 48 | 49 | @pytest.mark.parametrize("data", DATA, ids=["String", "Blocks"]) 50 | @pytest.mark.parametrize("mech", list(MECHS.keys()), ids=list(MECHS.values())) 51 | def test_digest_data(self, mech, data): 52 | """ 53 | Tests digest data mechs 54 | :param mech: parametrized mech from 'MECHS' 55 | :param data: parametrized testing data from 'DATA' 56 | """ 57 | ret, digested_data = c_digest(self.h_session, data, mech) 58 | self.verify_ret(ret, CKR_OK) 59 | assert len(digested_data) > 0, "The digested data should have a length" 60 | assert data != digested_data, "Digested data should not be the same as the original string" 61 | -------------------------------------------------------------------------------- /tests/functional/test_get_token_info.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pytest 4 | 5 | from . import config as hsm_config 6 | from pycryptoki.defaults import ADMIN_PARTITION_LABEL, ADMINISTRATOR_PASSWORD 7 | from pycryptoki.defines import ( 8 | CKF_TOKEN_PRESENT, 9 | CKF_LOGIN_REQUIRED, 10 | CKF_RESTORE_KEY_NOT_NEEDED, 11 | CKF_TOKEN_INITIALIZED, 12 | CKF_SERIAL_SESSION, 13 | CKF_SO_SESSION, 14 | CKF_RW_SESSION, 15 | ) 16 | from pycryptoki.session_management import ( 17 | ca_factory_reset_ex, 18 | c_get_token_info_ex, 19 | c_close_all_sessions, 20 | c_close_all_sessions_ex, 21 | c_open_session_ex, 22 | ) 23 | from pycryptoki.token_management import c_init_token_ex 24 | 25 | logger = logging.getLogger(__name__) 26 | 27 | 28 | @pytest.fixture(scope="class", autouse=True) 29 | def reset_to_defaults(): 30 | yield 31 | # Factory Reset 32 | slot = hsm_config["test_slot"] 33 | 34 | c_close_all_sessions_ex(slot) 35 | ca_factory_reset_ex(slot) 36 | 37 | # Initialize the Admin Token 38 | session_flags = CKF_SERIAL_SESSION | CKF_RW_SESSION | CKF_SO_SESSION 39 | 40 | h_session = c_open_session_ex(slot, session_flags) 41 | c_init_token_ex(slot, hsm_config["admin_pwd"], ADMIN_PARTITION_LABEL) 42 | 43 | # TODO: change this for ppso hardware. 44 | # slot = get_token_by_label_ex(ADMIN_PARTITION_LABEL) 45 | # c_close_all_sessions_ex(slot) 46 | # h_session = c_open_session_ex(slot, session_flags) 47 | # login_ex(h_session, slot, ADMINISTRATOR_PASSWORD, 0) 48 | # c_init_pin_ex(h_session, CO_PASSWORD) 49 | # c_logout_ex(h_session) 50 | c_close_all_sessions_ex(slot) 51 | 52 | 53 | @pytest.mark.reset 54 | class TestGetTokenInfo(object): 55 | """ """ 56 | 57 | @pytest.fixture(autouse=True) 58 | def setup_teardown(self, auth_session): 59 | self.h_session = auth_session 60 | self.admin_slot = hsm_config["test_slot"] 61 | 62 | def test_initial_flags(self): 63 | """ """ 64 | admin_slot = self.admin_slot 65 | 66 | # Get to clean state 67 | c_close_all_sessions(admin_slot) 68 | ca_factory_reset_ex(admin_slot) 69 | 70 | # Look at flags before initialization 71 | flags = c_get_token_info_ex(admin_slot)["flags"] 72 | expected_flags = CKF_TOKEN_PRESENT | CKF_LOGIN_REQUIRED | CKF_RESTORE_KEY_NOT_NEEDED 73 | assert expected_flags & flags != 0, ( 74 | "After factory reset found flags " 75 | + str(hex(flags)) 76 | + " on admin partition should match expected flags" 77 | + str(hex(expected_flags)) 78 | ) 79 | 80 | c_init_token_ex(admin_slot, ADMINISTRATOR_PASSWORD, ADMIN_PARTITION_LABEL) 81 | 82 | # Test flags after initialization 83 | flags = c_get_token_info_ex(admin_slot)["flags"] 84 | expected_flags = expected_flags | CKF_TOKEN_INITIALIZED 85 | assert flags & expected_flags != 0, ( 86 | "After initialization found flags " 87 | + str(hex(flags)) 88 | + " on admin partition should match expected flags" 89 | + str(hex(expected_flags)) 90 | ) 91 | logger.info( 92 | "After initialization found flags " 93 | + str(hex(flags)) 94 | + " on admin partition should match expected flags" 95 | + str(hex(expected_flags)) 96 | ) 97 | -------------------------------------------------------------------------------- /tests/functional/test_hsm_management.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test methods for pycryptoki 'hsm management' set of commands. 3 | """ 4 | 5 | import pytest 6 | 7 | from pycryptoki.default_templates import ( 8 | CKM_RSA_PKCS_KEY_PAIR_GEN, 9 | CKM_RSA_PKCS_KEY_PAIR_GEN_PUBTEMP, 10 | CKM_RSA_PKCS_KEY_PAIR_GEN_PRIVTEMP, 11 | ) 12 | from pycryptoki.defines import ( 13 | CKR_OK, 14 | CKR_ATTRIBUTE_VALUE_INVALID, 15 | CKR_USER_NOT_AUTHORIZED, 16 | CKA_CLASS, 17 | CKO_SECRET_KEY, 18 | CKA_KEY_TYPE, 19 | CKK_AES, 20 | CKA_TOKEN, 21 | CKA_SENSITIVE, 22 | CKA_PRIVATE, 23 | CKA_ENCRYPT, 24 | CKA_DECRYPT, 25 | CKA_SIGN, 26 | CKA_VERIFY, 27 | CKA_WRAP, 28 | CKA_UNWRAP, 29 | CKA_DERIVE, 30 | CKA_VALUE_LEN, 31 | CKA_EXTRACTABLE, 32 | CKA_LABEL, 33 | LUNA_TTYPE_CRYPTO, 34 | LUNA_TTYPE_RNG, 35 | LUNA_DSS_SIGVERIFY_TEST, 36 | ) 37 | from pycryptoki.hsm_management import ( 38 | ca_settokencertificatesignature, 39 | ca_hainit, 40 | ca_initializeremotepedvector, 41 | ca_deleteremotepedvector, 42 | ca_mtkrestore, 43 | ca_mtkresplit, 44 | ca_mtkzeroize, 45 | c_performselftest, 46 | ) 47 | from pycryptoki.key_generator import c_generate_key_pair 48 | from pycryptoki.lookup_dicts import ret_vals_dictionary 49 | from . import config as hsm_config 50 | 51 | 52 | @pytest.mark.reset 53 | class TestHSMManagementFunctions(object): 54 | """Test HSM Management functions class""" 55 | 56 | @pytest.fixture(autouse=True) 57 | def setup_teardown(self, auth_session): 58 | self.h_session = auth_session 59 | self.admin_slot = hsm_config["test_slot"] 60 | 61 | @pytest.mark.parametrize( 62 | "test_type", [LUNA_TTYPE_CRYPTO, LUNA_TTYPE_RNG, LUNA_DSS_SIGVERIFY_TEST] 63 | ) 64 | def test_performselftest(self, test_type): 65 | """Tests performs self test 66 | 67 | :param test_type: test type 68 | 69 | """ 70 | input_data = list(range(1000)) 71 | input_length = 1000 72 | 73 | ret, data = c_performselftest(self.admin_slot, test_type, input_data, input_length) 74 | assert ret == CKR_OK, ( 75 | "Return code should be " 76 | + ret_vals_dictionary[CKR_OK] 77 | + " not " 78 | + ret_vals_dictionary[ret] 79 | ) 80 | 81 | def test_settokencertsignature(self): 82 | """Tests set token certificate signature 83 | To do: fix attribute value 84 | """ 85 | gen_temp = { 86 | CKA_CLASS: CKO_SECRET_KEY, 87 | CKA_KEY_TYPE: CKK_AES, 88 | CKA_TOKEN: True, 89 | CKA_SENSITIVE: True, 90 | CKA_PRIVATE: True, 91 | CKA_ENCRYPT: True, 92 | CKA_DECRYPT: True, 93 | CKA_SIGN: True, 94 | CKA_VERIFY: True, 95 | CKA_WRAP: True, 96 | CKA_UNWRAP: True, 97 | CKA_DERIVE: True, 98 | CKA_VALUE_LEN: 16, 99 | CKA_EXTRACTABLE: True, 100 | CKA_LABEL: b"AES Key", 101 | } 102 | 103 | access_level = 1 104 | customer_id = 1 105 | pub_template = gen_temp 106 | signature = list(range(4000)) 107 | signature_length = 4000 108 | 109 | ret = ca_settokencertificatesignature( 110 | self.h_session, access_level, customer_id, pub_template, signature, signature_length 111 | ) 112 | assert ret == CKR_ATTRIBUTE_VALUE_INVALID, ( 113 | "Return code should be " 114 | + ret_vals_dictionary[CKR_ATTRIBUTE_VALUE_INVALID] 115 | + " not " 116 | + ret_vals_dictionary[ret] 117 | ) 118 | 119 | @pytest.mark.xfail(hsm_config["user"] == "CO", reason="Unable to run on CO slot") 120 | def test_hainit(self): 121 | """Tests performs HA init""" 122 | ret, pubkey_h, prikey_h = c_generate_key_pair( 123 | self.h_session, 124 | CKM_RSA_PKCS_KEY_PAIR_GEN, 125 | CKM_RSA_PKCS_KEY_PAIR_GEN_PUBTEMP, 126 | CKM_RSA_PKCS_KEY_PAIR_GEN_PRIVTEMP, 127 | ) 128 | assert ret == CKR_OK, ( 129 | "Return code should be " 130 | + ret_vals_dictionary[CKR_OK] 131 | + " not " 132 | + ret_vals_dictionary[ret] 133 | ) 134 | assert pubkey_h > 0, "The public key handle returned should be non zero" 135 | assert prikey_h > 0, "The private key handle returned should be non zero" 136 | 137 | ret = ca_hainit(self.h_session, prikey_h) 138 | 139 | assert ret == CKR_OK, ( 140 | "Return code should be " 141 | + ret_vals_dictionary[CKR_OK] 142 | + " not " 143 | + ret_vals_dictionary[ret] 144 | ) 145 | 146 | @pytest.mark.skipif( 147 | condition=not hsm_config["is_ped"] or hsm_config["user"] == "CO", 148 | reason="Not valid on PWD auth", 149 | ) 150 | def test_initializeremotepedvector(self): 151 | """Tests to initialize remote ped vector""" 152 | ret = ca_initializeremotepedvector(self.h_session) 153 | # since not SO return value must be CKR_USER_NOT_AUTHORIZED 154 | assert ret == CKR_USER_NOT_AUTHORIZED, ( 155 | "Return code should be " 156 | + ret_vals_dictionary[CKR_OK] 157 | + " not " 158 | + ret_vals_dictionary[ret] 159 | ) 160 | 161 | @pytest.mark.skipif( 162 | condition=not hsm_config["is_ped"] or hsm_config["user"] == "CO", 163 | reason="Not valid on PWD auth", 164 | ) 165 | def test_deleteremotepedvector(self): 166 | """Tests to delete remote ped vector""" 167 | ret = ca_deleteremotepedvector(self.h_session) 168 | # since not SO return value must be CKR_USER_NOT_AUTHORIZED 169 | assert ret == CKR_USER_NOT_AUTHORIZED, ( 170 | "Return code should be " 171 | + ret_vals_dictionary[CKR_USER_NOT_AUTHORIZED] 172 | + " not " 173 | + ret_vals_dictionary[ret] 174 | ) 175 | 176 | def test_mtkrestore(self): 177 | """Tests MTK restore""" 178 | ret = ca_mtkrestore(self.admin_slot) 179 | assert ret == CKR_OK, ( 180 | "Return code should be " 181 | + ret_vals_dictionary[CKR_OK] 182 | + " not " 183 | + ret_vals_dictionary[ret] 184 | ) 185 | 186 | def test_mtkresplit(self): 187 | """Tests MTK resplit""" 188 | ret = ca_mtkresplit(self.admin_slot) 189 | assert ret == CKR_OK, ( 190 | "Return code should be " 191 | + ret_vals_dictionary[CKR_OK] 192 | + " not " 193 | + ret_vals_dictionary[ret] 194 | ) 195 | 196 | def test_mtkzeroize(self): 197 | """Tests MTK zeroize""" 198 | ret = ca_mtkzeroize(self.admin_slot) 199 | assert ret == CKR_OK, ( 200 | "Return code should be " 201 | + ret_vals_dictionary[CKR_OK] 202 | + " not " 203 | + ret_vals_dictionary[ret] 204 | ) 205 | -------------------------------------------------------------------------------- /tests/functional/test_key_management.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test methods for pycryptoki 'key management' set of commands. 3 | """ 4 | 5 | import pytest 6 | 7 | from pycryptoki.default_templates import CKM_DES_KEY_GEN, CKM_DES_KEY_GEN_TEMP 8 | from pycryptoki.defines import ( 9 | CKR_OK, 10 | CK_MODIFY_USAGE_COUNT_COMMAND_TYPE_INCREMENT, 11 | CK_MODIFY_USAGE_COUNT_COMMAND_TYPE_SET, 12 | ) 13 | from pycryptoki.key_generator import c_destroy_object, c_generate_key_ex 14 | from pycryptoki.key_management import ca_modifyusagecount 15 | from pycryptoki.lookup_dicts import ret_vals_dictionary 16 | from . import config as hsm_config 17 | from .util import get_session_template 18 | 19 | 20 | class TestKeyManagementFunctions(object): 21 | """Test algorithm class""" 22 | 23 | @pytest.fixture(autouse=True) 24 | def setup_teardown(self, auth_session): 25 | self.h_session = auth_session 26 | self.admin_slot = hsm_config["test_slot"] 27 | 28 | @pytest.mark.parametrize( 29 | "command_type", 30 | [CK_MODIFY_USAGE_COUNT_COMMAND_TYPE_INCREMENT, CK_MODIFY_USAGE_COUNT_COMMAND_TYPE_SET], 31 | ) 32 | def test_modifyusagecount(self, command_type): 33 | """Test modify usage count 34 | 35 | :param command_type: 36 | 37 | """ 38 | key_handle = c_generate_key_ex( 39 | self.h_session, CKM_DES_KEY_GEN, get_session_template(CKM_DES_KEY_GEN_TEMP) 40 | ) 41 | try: 42 | ret = ca_modifyusagecount(self.h_session, key_handle, command_type, 0) 43 | assert ret == CKR_OK, ( 44 | "Return code should be " 45 | + ret_vals_dictionary[CKR_OK] 46 | + " not " 47 | + ret_vals_dictionary[ret] 48 | ) 49 | finally: 50 | c_destroy_object(self.h_session, key_handle) 51 | -------------------------------------------------------------------------------- /tests/functional/test_key_usage.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test methods for .. 'hsm usage' set of commands. 3 | """ 4 | 5 | import pytest 6 | 7 | from . import config as hsm_config 8 | from pycryptoki.defines import CKR_SESSION_HANDLE_INVALID, CKR_USER_NOT_AUTHORIZED 9 | from pycryptoki.key_usage import ca_clonemofn, ca_duplicatemofn 10 | from pycryptoki.return_values import ret_vals_dictionary 11 | 12 | 13 | class TestAlgorithm(object): 14 | """Test algorithm class""" 15 | 16 | @pytest.fixture(autouse=True) 17 | def setup_teardown(self, auth_session): 18 | self.h_session = auth_session 19 | self.admin_slot = hsm_config["test_slot"] 20 | 21 | def test_clonemofn(self): 22 | """Test clone M of N""" 23 | ret = ca_clonemofn(self.h_session) 24 | assert ret == CKR_SESSION_HANDLE_INVALID, ( 25 | "Return code should be " 26 | + ret_vals_dictionary[CKR_SESSION_HANDLE_INVALID] 27 | + " not " 28 | + ret_vals_dictionary[ret] 29 | ) 30 | 31 | @pytest.mark.xfail(reason="Not valid on PWD auth") 32 | def test_duplicatemofn(self): 33 | """Test duplicate M of N""" 34 | ret = ca_duplicatemofn(self.h_session) 35 | assert ret == CKR_USER_NOT_AUTHORIZED, ( 36 | "Return code should be " 37 | + ret_vals_dictionary[CKR_USER_NOT_AUTHORIZED] 38 | + " not " 39 | + ret_vals_dictionary[ret] 40 | ) 41 | -------------------------------------------------------------------------------- /tests/functional/test_misc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test RNG functions 3 | """ 4 | import logging 5 | 6 | import pytest 7 | 8 | from . import config as hsm_config 9 | from pycryptoki.defines import CKR_OK 10 | from pycryptoki.misc import c_generate_random_ex, c_seed_random, c_generate_random 11 | from pycryptoki.lookup_dicts import ret_vals_dictionary 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class TestRNG(object): 17 | """Test RNG Functions""" 18 | 19 | @pytest.fixture(autouse=True) 20 | def setup_teardown(self, auth_session): 21 | self.h_session = auth_session 22 | self.admin_slot = hsm_config["test_slot"] 23 | 24 | def test_rng(self): 25 | """Tests generating a random number""" 26 | length = 15 27 | ret, random_string = c_generate_random(self.h_session, length) 28 | assert ret == CKR_OK, ( 29 | "C_GenerateRandom should return CKR_OK, instead it returned " + ret_vals_dictionary[ret] 30 | ) 31 | assert len(random_string) == length, ( 32 | "The length of the random string should be the same as the " 33 | "length of the requested data." 34 | ) 35 | 36 | def test_seeded_rng(self): 37 | """Tests that seeding the random number generator with the same data will 38 | generate the same random number 39 | 40 | 41 | """ 42 | seed = b"k" * 1024 43 | ret = c_seed_random(self.h_session, seed) 44 | assert ret == CKR_OK, ( 45 | "Seeding the random number generator shouldn't return an error, " 46 | "it returned " + ret_vals_dictionary[ret] 47 | ) 48 | 49 | random_string_one = c_generate_random_ex(self.h_session, 10) 50 | 51 | ret = c_seed_random(self.h_session, seed) 52 | assert ret == CKR_OK, ( 53 | "Seeding the random number generator a second time shouldn't return " 54 | "" 55 | "an error, it returned " + ret_vals_dictionary[ret] 56 | ) 57 | 58 | random_string_two = c_generate_random_ex(self.h_session, 10) 59 | -------------------------------------------------------------------------------- /tests/functional/test_object_create.py: -------------------------------------------------------------------------------- 1 | """ 2 | Testcases for object creation 3 | """ 4 | 5 | import logging 6 | 7 | import pytest 8 | 9 | from pycryptoki.ca_extensions.object_handler import ca_get_object_handle_ex 10 | from pycryptoki.default_templates import CERTIFICATE_TEMPLATE, DATA_TEMPLATE 11 | from pycryptoki.defines import CKA_VALUE, CKA_OUID 12 | from pycryptoki.key_generator import c_destroy_object 13 | from pycryptoki.misc import c_create_object_ex 14 | from pycryptoki.object_attr_lookup import c_get_attribute_value_ex 15 | from . import config as hsm_config 16 | from .util import get_session_template 17 | 18 | logger = logging.getLogger(__name__) 19 | 20 | 21 | class TestObjectCreation(object): 22 | """Tests certificate & data creation.""" 23 | 24 | @pytest.fixture(autouse=True) 25 | def setup_teardown(self, auth_session): 26 | self.h_session = auth_session 27 | self.admin_slot = hsm_config["test_slot"] 28 | 29 | def test_certificate_create(self): 30 | """Tests C_CreateObject with a certificate template and verifies the object's 31 | attributes 32 | 33 | 34 | """ 35 | template = get_session_template(CERTIFICATE_TEMPLATE) 36 | h_object = c_create_object_ex(self.h_session, template) 37 | try: 38 | desired_attrs = {x: None for x in template.keys()} 39 | attr = c_get_attribute_value_ex(self.h_session, h_object, template=desired_attrs) 40 | # CKA_VALUE in the template is a list of ints, but is returned as a single hex string. 41 | # Let's try to convert it back to the list of ints. 42 | value = attr[CKA_VALUE] 43 | attr[CKA_VALUE] = [int(value[x : x + 2], 16) for x in range(0, len(value), 2)] 44 | assert attr == template 45 | finally: 46 | c_destroy_object(self.h_session, h_object) 47 | 48 | def test_data_create(self): 49 | """Tests C_CreateObject with a data template and verifies the object's 50 | attributes 51 | 52 | 53 | """ 54 | template = get_session_template(DATA_TEMPLATE) 55 | h_object = c_create_object_ex(self.h_session, template) 56 | try: 57 | desired_attrs = {x: None for x in template.keys()} 58 | attr = c_get_attribute_value_ex(self.h_session, h_object, template=desired_attrs) 59 | # CKA_VALUE in the template is a list of ints, but is returned as a single hex string. 60 | # Let's try to convert it back to the list of ints. 61 | value = attr[CKA_VALUE] 62 | attr[CKA_VALUE] = [int(value[x : x + 2], 16) for x in range(0, len(value), 2)] 63 | assert attr == template 64 | finally: 65 | c_destroy_object(self.h_session, h_object) 66 | 67 | def test_ca_get_object_handle(self): 68 | """ 69 | Testing the function CA_GetObjectHandle 70 | :return: 71 | """ 72 | h_object = c_create_object_ex(self.h_session, DATA_TEMPLATE) 73 | try: 74 | object_uid = c_get_attribute_value_ex(self.h_session, h_object, {CKA_OUID: None})[ 75 | CKA_OUID 76 | ] 77 | object_handle = ca_get_object_handle_ex(self.admin_slot, self.h_session, object_uid) 78 | assert h_object == object_handle 79 | finally: 80 | c_destroy_object(self.h_session, h_object) 81 | -------------------------------------------------------------------------------- /tests/functional/test_session_management.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests session management functions 3 | """ 4 | import pytest 5 | import logging 6 | 7 | from six import integer_types 8 | 9 | from . import config as hsm_config 10 | from pycryptoki.defines import CKR_OK 11 | import pycryptoki.session_management as sess_mang 12 | from pycryptoki.ca_extensions.session import ca_get_session_info 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | class TestSessionManagement(object): 18 | """ 19 | Tests session management functions 20 | """ 21 | 22 | @pytest.fixture(autouse=True) 23 | def setup_teardown(self, auth_session): 24 | self.admin_slot = hsm_config["test_slot"] 25 | self.h_session = auth_session 26 | 27 | def test_c_get_session_info(self): 28 | """ c_get_session_info() """ 29 | ret, sess_info = sess_mang.c_get_session_info(self.h_session) 30 | assert ret == CKR_OK 31 | # Checks that session_info dictionary is the right format. Does not check the values 32 | assert isinstance(sess_info["state"], integer_types) 33 | assert isinstance(sess_info["flags"], integer_types) 34 | assert isinstance(sess_info["slotID"], integer_types) 35 | assert isinstance(sess_info["usDeviceError"], integer_types) 36 | 37 | def test_ca_get_session_info(self): 38 | """ ca_get_session_info() """ 39 | ret, sess_info = ca_get_session_info(self.h_session) 40 | assert ret == CKR_OK 41 | # Checks that session_info dictionary is the right format. Does not check the values 42 | assert isinstance(sess_info["aidHigh"], integer_types) 43 | assert isinstance(sess_info["aidLow"], integer_types) 44 | assert isinstance(sess_info["containerNumber"], integer_types) 45 | assert isinstance(sess_info["authenticationLevel"], integer_types) 46 | 47 | def test_get_slot_dict(self): 48 | """ get_slot_dict() """ 49 | ret, slot_dict = sess_mang.get_slot_dict() 50 | logger.debug("Slots: %s", slot_dict) 51 | assert ret == CKR_OK 52 | assert isinstance(slot_dict, dict) 53 | 54 | def test_get_slot_dict_token_present(self): 55 | """ 56 | Verify this also works with token_present = True 57 | """ 58 | slot_dict = sess_mang.get_slot_dict_ex(token_present=True) 59 | for slot in slot_dict.keys(): 60 | assert sess_mang.c_get_token_info(slot)[0] == CKR_OK 61 | 62 | def test_get_slot_list(self): 63 | """ 64 | Verify get slot list works as expected. 65 | """ 66 | slot_list = sess_mang.c_get_slot_list_ex(token_present=True) 67 | for slot in slot_list: 68 | assert isinstance(slot, integer_types) 69 | assert sess_mang.c_get_token_info(slot)[0] == CKR_OK 70 | -------------------------------------------------------------------------------- /tests/functional/test_sign_verify.py: -------------------------------------------------------------------------------- 1 | """ Functional tests for signature / verification""" 2 | import logging 3 | import pytest 4 | 5 | from pycryptoki.sign_verify import c_sign, c_verify 6 | from pycryptoki.key_generator import c_generate_key_pair, c_generate_key, c_destroy_object 7 | from pycryptoki.defines import ( 8 | CKM_AES_MAC, 9 | CKM_AES_CMAC, 10 | CKM_AES_KEY_GEN, 11 | CKM_DES_MAC, 12 | CKM_DES_KEY_GEN, 13 | CKM_DES3_MAC, 14 | CKM_DES3_CMAC, 15 | CKM_DES3_KEY_GEN, 16 | CKM_CAST3_MAC, 17 | CKM_CAST3_KEY_GEN, 18 | CKM_CAST5_MAC, 19 | CKM_CAST5_KEY_GEN, 20 | CKM_SEED_MAC, 21 | CKM_SEED_CMAC, 22 | CKM_SEED_KEY_GEN, 23 | CKM_DSA, 24 | CKM_DSA_KEY_PAIR_GEN, 25 | CKM_ECDSA, 26 | CKM_ECDSA_KEY_PAIR_GEN, 27 | CKR_OK, 28 | ) 29 | from pycryptoki.default_templates import ( 30 | CKM_DSA_KEY_PAIR_GEN_PRIVTEMP, 31 | CKM_DSA_KEY_PAIR_GEN_PUBTEMP_1024_160, 32 | CKM_DSA_KEY_PAIR_GEN_PUBTEMP_2048_224, 33 | CKM_DSA_KEY_PAIR_GEN_PUBTEMP_2048_256, 34 | CKM_DSA_KEY_PAIR_GEN_PUBTEMP_3072_256, 35 | CKM_ECDSA_KEY_PAIR_GEN_PRIVTEMP, 36 | CKM_ECDSA_KEY_PAIR_GEN_PUBTEMP, 37 | MECHANISM_LOOKUP_EXT, 38 | get_default_key_template, 39 | ) 40 | 41 | from pycryptoki.lookup_dicts import ret_vals_dictionary 42 | from .util import get_session_template 43 | 44 | logger = logging.getLogger(__name__) 45 | 46 | DATA = [b"This is some test string to sign.", [b"a" * 1024, b"b" * 1024]] 47 | 48 | SYM_PARAMS = [ 49 | (CKM_AES_KEY_GEN, CKM_AES_MAC), 50 | (CKM_AES_KEY_GEN, CKM_AES_CMAC), 51 | (CKM_DES_KEY_GEN, CKM_DES_MAC), 52 | (CKM_DES3_KEY_GEN, CKM_DES3_MAC), 53 | (CKM_DES3_KEY_GEN, CKM_DES3_CMAC), 54 | (CKM_CAST3_KEY_GEN, CKM_CAST3_MAC), 55 | (CKM_CAST5_KEY_GEN, CKM_CAST5_MAC), 56 | (CKM_SEED_KEY_GEN, CKM_SEED_MAC), 57 | (CKM_SEED_KEY_GEN, CKM_SEED_CMAC), 58 | ] 59 | SYM_KEYS = [key for key, _ in SYM_PARAMS] 60 | 61 | DSA_PUB_TEMPS = [ 62 | CKM_DSA_KEY_PAIR_GEN_PUBTEMP_1024_160, 63 | CKM_DSA_KEY_PAIR_GEN_PUBTEMP_2048_224, 64 | CKM_DSA_KEY_PAIR_GEN_PUBTEMP_2048_256, 65 | CKM_DSA_KEY_PAIR_GEN_PUBTEMP_3072_256, 66 | ] 67 | ASYM_PARAMS = [ 68 | ( 69 | CKM_ECDSA_KEY_PAIR_GEN, 70 | CKM_ECDSA_KEY_PAIR_GEN_PUBTEMP, 71 | CKM_ECDSA_KEY_PAIR_GEN_PRIVTEMP, 72 | CKM_ECDSA, 73 | ) 74 | ] + [(CKM_DSA_KEY_PAIR_GEN, x, CKM_DSA_KEY_PAIR_GEN_PRIVTEMP, CKM_DSA) for x in DSA_PUB_TEMPS] 75 | 76 | FORMAT_ASYM = [(key, sig) for (key, _, _, sig) in ASYM_PARAMS] 77 | 78 | 79 | def idfn(params): 80 | """ Generate test ids """ 81 | id_list = [] 82 | for s in params: 83 | id_list.append( 84 | MECHANISM_LOOKUP_EXT[s[0]][0] 85 | .replace("CKM_", "") 86 | .replace("_KEY_PAIR_GEN", "") 87 | .replace("_KEY_GEN", "") 88 | ) 89 | return id_list 90 | 91 | 92 | @pytest.fixture(scope="class") 93 | def sym_keys(auth_session): 94 | """ Fixture containing all sym. keys """ 95 | keys = {} 96 | try: 97 | for key_type in SYM_KEYS: 98 | template = get_session_template(get_default_key_template(key_type)) 99 | ret, key_handle = c_generate_key(auth_session, key_type, template) 100 | if ret == CKR_OK: 101 | keys[key_type] = key_handle 102 | else: 103 | logger.info("Failed to generate key: {}\nReturn code: {}".format(key_type, ret)) 104 | yield keys 105 | 106 | finally: 107 | for handle in keys.values(): 108 | c_destroy_object(auth_session, handle) 109 | 110 | 111 | @pytest.fixture(scope="class") 112 | def asym_keys(auth_session): 113 | """ Fixture containing all asym. keys """ 114 | keys = {} 115 | try: 116 | for params in ASYM_PARAMS: 117 | key_type, pub_temp, prv_temp, _ = params 118 | ret, pub_key, prv_key = c_generate_key_pair( 119 | auth_session, 120 | key_type, 121 | get_session_template(pub_temp), 122 | get_session_template(prv_temp), 123 | ) 124 | if ret == CKR_OK: 125 | keys[key_type] = (pub_key, prv_key) 126 | else: 127 | logger.info("Failed to generate key: {}\nReturn code: {}".format(key_type, ret)) 128 | yield keys 129 | 130 | finally: 131 | for pub_key, prv_key in keys.values(): 132 | c_destroy_object(auth_session, pub_key) 133 | c_destroy_object(auth_session, prv_key) 134 | 135 | 136 | class TestSignVerify(object): 137 | """ 138 | Creates key pairs, signs data, and verifies that data. 139 | """ 140 | 141 | def verify_ret(self, ret, expected_ret): 142 | """ 143 | Assert that ret is as expected 144 | :param ret: the actual return value 145 | :param expected_ret: the expected return value 146 | """ 147 | assert ret == expected_ret, ( 148 | "Function should return: " 149 | + ret_vals_dictionary[expected_ret] 150 | + ".\nInstead returned: " 151 | + ret_vals_dictionary[ret] 152 | ) 153 | 154 | @pytest.fixture(autouse=True) 155 | def setup_teardown(self, auth_session): 156 | self.h_session = auth_session 157 | 158 | @pytest.mark.parametrize("data", DATA, ids=["String", "Block"]) 159 | @pytest.mark.parametrize(("key_type", "sign_flavor"), SYM_PARAMS, ids=idfn(SYM_PARAMS)) 160 | def test_sym_sign_verify(self, key_type, sign_flavor, data, sym_keys): 161 | """ 162 | Test sym. sign / verify 163 | :param key_type: key_gen type 164 | :param sign_flavor: signature mech 165 | :param data: testing data 166 | :param sym_keys: key fixture 167 | """ 168 | # Auto-fail when key-generation fails 169 | if sym_keys.get(key_type) is None: 170 | pytest.skip("No valid key found for {}".format(MECHANISM_LOOKUP_EXT[key_type][0])) 171 | h_key = sym_keys[key_type] 172 | 173 | ret, signature = c_sign(self.h_session, h_key, data, mechanism=sign_flavor) 174 | self.verify_ret(ret, CKR_OK) 175 | 176 | ret = c_verify(self.h_session, h_key, data, signature, mechanism=sign_flavor) 177 | self.verify_ret(ret, CKR_OK) 178 | 179 | @pytest.mark.parametrize("data", DATA, ids=["String", "Block"]) 180 | @pytest.mark.parametrize(("k_type", "sig_mech"), FORMAT_ASYM, ids=idfn(ASYM_PARAMS)) 181 | def test_asym_sign_verify(self, k_type, sig_mech, data, asym_keys): 182 | """ 183 | Test asym. sign / verify 184 | :param k_type: key_gen type 185 | :param sig_mech: signature mech 186 | :param data: testing data 187 | :param asym_keys: key fixture 188 | """ 189 | # Auto-fail when key-generation fails 190 | if asym_keys.get(k_type) is None: 191 | pytest.skip("No valid key found for {}".format(MECHANISM_LOOKUP_EXT[k_type][0])) 192 | pub_key, prv_key = asym_keys[k_type] 193 | 194 | ret, signature = c_sign(self.h_session, prv_key, data, mechanism=sig_mech) 195 | self.verify_ret(ret, CKR_OK) 196 | 197 | ret = c_verify(self.h_session, pub_key, data, signature, mechanism=sig_mech) 198 | self.verify_ret(ret, CKR_OK) 199 | -------------------------------------------------------------------------------- /tests/functional/test_supporting_operations.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pytest 4 | 5 | from . import config as hsm_config 6 | from pycryptoki.defines import CKR_OK 7 | from pycryptoki.misc import c_generate_random_ex, c_seed_random, c_generate_random 8 | from pycryptoki.lookup_dicts import ret_vals_dictionary 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class TestSupportingOperations(object): 14 | """ """ 15 | 16 | @pytest.fixture(autouse=True) 17 | def setup_teardown(self, auth_session): 18 | self.h_session = auth_session 19 | self.admin_slot = hsm_config["test_slot"] 20 | 21 | def test_rng(self): 22 | """Tests generating a random number""" 23 | length = 15 24 | ret, random_string = c_generate_random(self.h_session, length) 25 | assert ret == CKR_OK, ( 26 | "C_GenerateRandom should return CKR_OK, instead it returned " + ret_vals_dictionary[ret] 27 | ) 28 | assert len(random_string) == length, ( 29 | "The length of the random string should be the same as the " 30 | "length of the requested data." 31 | ) 32 | 33 | def test_seeded_rng(self): 34 | """Tests that seeding the random number generator with the same data will 35 | generate the same random number 36 | 37 | 38 | """ 39 | seed = b"k" * 1024 40 | ret = c_seed_random(self.h_session, seed) 41 | assert ret == CKR_OK, ( 42 | "Seeding the random number generator shouldn't return an error, " 43 | "it returned " + ret_vals_dictionary[ret] 44 | ) 45 | 46 | random_string_one = c_generate_random_ex(self.h_session, 10) 47 | 48 | ret = c_seed_random(self.h_session, seed) 49 | assert ret == CKR_OK, ( 50 | "Seeding the random number generator a second time shouldn't return " 51 | "an error, it returned " + ret_vals_dictionary[ret] 52 | ) 53 | 54 | random_string_two = c_generate_random_ex(self.h_session, 10) 55 | -------------------------------------------------------------------------------- /tests/functional/testdata/sha1pkcs_plain.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThalesGroup/pycryptoki/ae98be3b9271e90fe9d38905884bfae79bd7dbe0/tests/functional/testdata/sha1pkcs_plain.der -------------------------------------------------------------------------------- /tests/functional/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility functions for testing 3 | """ 4 | from pycryptoki.defines import CKA_TOKEN 5 | 6 | 7 | def get_session_template(default_template): 8 | """ 9 | Set CKA_TOKEN to false on a template, so that it will be cleaned up on the 10 | session close. 11 | """ 12 | default_template.copy() 13 | default_template[CKA_TOKEN] = False 14 | return default_template 15 | -------------------------------------------------------------------------------- /tests/unittests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThalesGroup/pycryptoki/ae98be3b9271e90fe9d38905884bfae79bd7dbe0/tests/unittests/__init__.py -------------------------------------------------------------------------------- /tests/unittests/test_attributes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test creation of Attributes instance 3 | """ 4 | 5 | import pytest 6 | import mock 7 | 8 | from collections import defaultdict 9 | 10 | from pycryptoki.attributes import Attributes, KEY_TRANSFORMS, c_struct_to_python 11 | 12 | from hypothesis import given 13 | from hypothesis.strategies import dictionaries, integers, one_of, none, just 14 | 15 | from ctypes import c_ulong, sizeof 16 | 17 | MAX_INT = 2 ** (sizeof(c_ulong) * 8) - 1 18 | 19 | 20 | def new_xform(val, reverse=False): 21 | """ 22 | Mock transformation to replace existing xforms in KEY_TRANSFORMS 23 | :param val: Any value. 24 | :param reverse: 'reverse' place holder for conversion methods 25 | :return: (1, 1) 26 | """ 27 | if reverse: 28 | return 1 29 | return 1, 1 30 | 31 | 32 | # Create mock dict w/ all xforms = 'new_xform' 33 | mock_xform_dict = defaultdict(lambda: new_xform) 34 | mock_xform_dict.update({key: new_xform for key in KEY_TRANSFORMS}) 35 | 36 | 37 | @pytest.fixture() 38 | def setup_mock_dict(): 39 | """ Fixture for creating dictionary of mockxforms """ 40 | with mock.patch("pycryptoki.attributes.KEY_TRANSFORMS", new=mock_xform_dict): 41 | yield 42 | 43 | 44 | @pytest.mark.usefixtures("setup_mock_dict") 45 | class TestAttributes(object): 46 | def test_no_params(self): 47 | """ Create Attributes object without specifying any parameters """ 48 | attr = Attributes() 49 | c_struct = attr.get_c_struct() 50 | assert isinstance(attr, Attributes) 51 | assert len(attr) == sizeof(c_struct) == 0 52 | 53 | @given( 54 | dictionaries( 55 | keys=integers(min_value=1, max_value=MAX_INT), values=none(), dict_class=Attributes 56 | ) 57 | ) 58 | def test_null_dictionary(self, test_dic): 59 | """ 60 | Test creation of Attributes class. 61 | :param test_dic: Dictionary of random size, w/ all elements = None 62 | """ 63 | res = test_dic.get_c_struct() 64 | for attr in res: 65 | assert attr.pValue is None 66 | assert attr.usValueLen == 0 67 | 68 | # Back to python dictionary 69 | py_dic = c_struct_to_python(res) 70 | assert test_dic == py_dic 71 | 72 | @given( 73 | dictionaries( 74 | keys=integers(min_value=1, max_value=MAX_INT), values=just(1), dict_class=Attributes 75 | ) 76 | ) 77 | def test_full_dictionary(self, test_dic): 78 | """ 79 | Test creation of Attributes class. 80 | :param test_dic: Dicitonary of random size, w/ all elements = 1 81 | """ 82 | res = test_dic.get_c_struct() 83 | for attr in res: 84 | assert attr.pValue == 1 85 | assert attr.usValueLen == 1 86 | 87 | # Back to python dictionary 88 | py_dic = c_struct_to_python(res) 89 | assert test_dic == py_dic 90 | 91 | @given( 92 | dictionaries( 93 | keys=integers(min_value=1, max_value=MAX_INT), 94 | dict_class=Attributes, 95 | values=one_of(just(1), none()), 96 | ) 97 | ) 98 | def test_rand_dictionary(self, test_dic): 99 | """ 100 | Test creation of Attributes class. 101 | :param test_dic: Dictionary of random size, elements = 1 or None 102 | """ 103 | # Iterate through dictionary and store keys w/ value = 1 104 | l = [key for key in test_dic if test_dic[key] == 1] 105 | 106 | res = test_dic.get_c_struct() 107 | for attr in res: 108 | if attr.type in l: 109 | assert attr.pValue == 1 110 | assert attr.usValueLen == 1 111 | else: 112 | assert attr.pValue is None 113 | assert attr.usValueLen == 0 114 | 115 | # Back to python dictionary 116 | py_dic = c_struct_to_python(res) 117 | assert test_dic == py_dic 118 | -------------------------------------------------------------------------------- /tests/unittests/test_auto_c_array.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for AutoCArray in common_util.py 3 | """ 4 | from ctypes import * 5 | from string import ascii_letters 6 | 7 | import pytest 8 | import sys 9 | from hypothesis import given 10 | from hypothesis.strategies import text, lists, sampled_from, integers 11 | from six import b, binary_type 12 | 13 | from pycryptoki.common_utils import AutoCArray 14 | 15 | c_types = [ 16 | c_short, 17 | c_ushort, 18 | c_long, 19 | c_ulong, 20 | c_int, 21 | c_uint, 22 | c_float, 23 | c_double, 24 | c_longlong, 25 | c_ulonglong, 26 | c_byte, 27 | c_ubyte, 28 | c_char, 29 | c_char_p, 30 | c_void_p, 31 | c_bool, 32 | ] 33 | 34 | MAX_INT = 2 ** (sizeof(c_ulong) * 8) - 1 35 | 36 | 37 | class TestAutoCArray(object): 38 | @pytest.mark.xfail( 39 | hasattr(sys, "pypy_version_info"), reason="Fails on Pypy w/ AssertionError: unknown shape g" 40 | ) 41 | @given(sampled_from(c_types)) 42 | def test_auto_c_array_empty(self, typ_val): 43 | """ 44 | Initialize an empty array w/ elements of the given c_type. 45 | :param typ_val: randomly selected ctype 46 | """ 47 | c_array = AutoCArray(ctype=typ_val) 48 | 49 | assert c_array.array is None 50 | assert c_array.size.contents.value == len(c_array) == 0 51 | assert c_array.ctype == typ_val 52 | 53 | if typ_val == c_char: 54 | assert c_array.array.contents.value == typ_val(b"\x00").value 55 | else: 56 | assert c_array.array.contents.value == typ_val(0).value 57 | 58 | @given(text(alphabet=ascii_letters)) 59 | def test_auto_c_array_string(self, str_val): 60 | """ 61 | Initialize an array from string. 62 | :param str_val: randomly generated string 63 | """ 64 | c_array = AutoCArray(data=str_val) 65 | 66 | assert c_array.size.contents.value == len(c_array) == len(str_val) 67 | assert c_array.ctype == c_ubyte 68 | assert b"".join(c_array) == b(str_val) 69 | 70 | @given(lists(elements=integers(min_value=-128, max_value=127), min_size=1)) 71 | def test_auto_c_array_byte_list(self, list_val): 72 | """ 73 | Initalize an array from list of bytes. 74 | :param list_val: list of ints to be converted to c_byte's 75 | """ 76 | list_val = [c_byte(x) for x in list_val] 77 | c_array = AutoCArray(data=list_val, ctype=c_byte) 78 | 79 | assert c_array.size.contents.value == len(c_array) == len(list_val) 80 | assert c_array.ctype == c_byte 81 | assert b"".join([bytes(c_byte(x)) for x in c_array]) == b"".join( 82 | [bytes(x) for x in list_val] 83 | ) 84 | assert c_array.array[0] == cast(c_array.array, POINTER(c_byte)).contents.value 85 | 86 | @given(lists(elements=integers(min_value=0, max_value=256), min_size=1)) 87 | def test_auto_c_array_ubyte_list(self, list_val): 88 | """ 89 | Initalize an array from list of bytes. 90 | :param list_val: list of ints to be converted to c_ubyte's 91 | """ 92 | list_val = [c_ubyte(x) for x in list_val] 93 | c_array = AutoCArray(data=list_val, ctype=c_ubyte) 94 | 95 | assert c_array.size.contents.value == len(c_array) == len(list_val) 96 | assert c_array.ctype == c_ubyte 97 | assert b"".join([bytes(c_ubyte(x)) for x in c_array]) == b"".join( 98 | [bytes(x) for x in list_val] 99 | ) 100 | assert c_array.array[0] == cast(c_array.array, POINTER(c_ubyte)).contents.value 101 | 102 | @given( 103 | lists( 104 | elements=integers(min_value=int(-MAX_INT / 2), max_value=int(MAX_INT / 2)), min_size=1 105 | ) 106 | ) 107 | def test_auto_c_array_long_list(self, list_val): 108 | """ 109 | Initalize an array from list of long's 110 | :param list_val: list of ints to be converted to c_long's 111 | """ 112 | list_val = [c_long(x) for x in list_val] 113 | c_array = AutoCArray(data=list_val, ctype=c_long) 114 | 115 | assert c_array.size.contents.value == len(c_array) == len(list_val) 116 | assert c_array.ctype == c_long 117 | assert b"".join([bytes(c_long(x)) for x in c_array]) == b"".join( 118 | [bytes(x) for x in list_val] 119 | ) 120 | assert c_array.array[0] == cast(c_array.array, POINTER(c_long)).contents.value 121 | 122 | @given(lists(elements=integers(min_value=0, max_value=MAX_INT), min_size=1)) 123 | def test_auto_c_array_ulong_list(self, list_val): 124 | """ 125 | Initalize an array from list of ulong's 126 | :param list_val: list of ints to be converted to c_ulong's 127 | """ 128 | list_val = [c_ulong(x) for x in list_val] 129 | c_array = AutoCArray(data=list_val, ctype=c_ulong) 130 | 131 | assert c_array.size.contents.value == len(c_array) == len(list_val) 132 | assert c_array.ctype == c_ulong 133 | assert b"".join([bytes(c_ulong(x)) for x in c_array]) == b"".join( 134 | [bytes(x) for x in list_val] 135 | ) 136 | assert c_array.array[0] == cast(c_array.array, POINTER(c_ulong)).contents.value 137 | 138 | @given(lists(elements=text(alphabet=ascii_letters, min_size=1, max_size=1), min_size=1)) 139 | def test_auto_c_array_char_list(self, list_val): 140 | """ 141 | Initalize an array from list of c_chars 142 | :param list_val: list of char to be converted to c_char's 143 | """ 144 | list_val = [bytes(b(x)) for x in list_val] 145 | new_list_val = [c_char(x) for x in list_val] 146 | c_array = AutoCArray(data=new_list_val, ctype=c_char) 147 | 148 | assert c_array.size.contents.value == len(c_array) == len(list_val) 149 | assert c_array.ctype == c_char 150 | assert b"".join([x for x in c_array]) == b"".join(list_val) 151 | assert c_array.array[0] == cast(c_array.array, POINTER(c_char)).contents.value 152 | 153 | @given(list_val=lists(elements=integers(min_value=0, max_value=127), min_size=1)) 154 | @pytest.mark.parametrize("test_type", [c_byte, c_ubyte, c_long, c_char]) 155 | def test_auto_c_array_no_type_fail(self, list_val, test_type): 156 | """ 157 | Attempt to initialize an array of 'test_type' without specifying the type. Should error 158 | :param list_val: Generated list, convert to 'test_type' 159 | :param test_type: c_types to test with 160 | """ 161 | if test_type == c_char: 162 | new_list = [c_char(b(chr(x))) for x in list_val] 163 | else: 164 | new_list = [test_type(x) for x in list_val] 165 | 166 | with pytest.raises(TypeError): 167 | c_array = AutoCArray(data=new_list) 168 | 169 | def test_padded_data(self): 170 | data = "hello, world" 171 | padded_data = b"hello, world " 172 | 173 | arr = AutoCArray(data=data, size=20, pad=True, pad_char=" ") 174 | assert b"".join(arr) == padded_data 175 | 176 | def test_short_pad(self): 177 | data = "hello, world" 178 | 179 | with pytest.raises(ValueError): 180 | arr = AutoCArray(data=data, size=8, pad=True, pad_char=" ") 181 | 182 | def test_exact_size(self): 183 | data = "hello, world" 184 | 185 | arr = AutoCArray(data=data, size=12, pad=True, pad_char=" ") 186 | assert b"".join(arr) == b(data) 187 | -------------------------------------------------------------------------------- /tests/unittests/test_daemon.py: -------------------------------------------------------------------------------- 1 | """ 2 | Testcases for the RPYC Daemon & Client. 3 | """ 4 | import logging 5 | import os 6 | import random 7 | 8 | import pytest 9 | from pycryptoki.daemon.rpyc_pycryptoki import ( 10 | PycryptokiService, 11 | create_server_subprocess, 12 | server_launch, 13 | ) 14 | from pycryptoki.default_templates import get_default_key_template 15 | from pycryptoki.defines import CKM_AES_KEY_GEN 16 | from pycryptoki.pycryptoki_client import RemotePycryptokiClient 17 | 18 | 19 | @pytest.fixture() 20 | def local_rpyc(): 21 | """ 22 | Spin up a local-only RPYC Pycryptoki daemon in a subprocess. 23 | """ 24 | logger = logging.getLogger(__name__) 25 | server_config = { 26 | "allow_public_attrs": True, 27 | "allow_all_attrs": True, 28 | "allow_getattr": True, 29 | "allow_setattr": True, 30 | "allow_delattr": True, 31 | } 32 | port = random.randint(10000, 65535) 33 | server = create_server_subprocess( 34 | server_launch, args=(PycryptokiService, "127.0.0.1", port, server_config), logger=logger, 35 | ) 36 | assert server.exitcode is None 37 | assert server.is_alive() 38 | yield port 39 | server.terminate() 40 | 41 | 42 | class TestPycryptokiDaemon(object): 43 | def test_simple_connect(self, local_rpyc): 44 | client = RemotePycryptokiClient("127.0.0.1", local_rpyc) 45 | assert client.test_conn() 46 | 47 | def test_attribute_delivery(self, local_rpyc): 48 | client = RemotePycryptokiClient("127.0.0.1", local_rpyc) 49 | client.test_attrs(get_default_key_template(CKM_AES_KEY_GEN)) 50 | -------------------------------------------------------------------------------- /tests/unittests/test_dll_singleton.py: -------------------------------------------------------------------------------- 1 | """ 2 | Verify the ChrystokDLLSingleton behavior is correct when creating a from-chrystoki DLL link, 3 | or using the ``from_path`` variation. 4 | """ 5 | import mock 6 | 7 | from pycryptoki.cryptoki.helpers import CryptokiDLLSingleton 8 | 9 | 10 | class TestDllSingleton(object): 11 | def test_simple_singleton(self): 12 | with mock.patch("pycryptoki.cryptoki.helpers.CDLL"): 13 | with mock.patch("pycryptoki.cryptoki.helpers.parse_chrystoki_conf") as chrystoki_conf: 14 | chrystoki_conf.return_value = "conf_path" 15 | dll_class = CryptokiDLLSingleton() 16 | assert dll_class.dll_path == "conf_path" 17 | 18 | def test_from_path_singleton_simple(self): 19 | with mock.patch("pycryptoki.cryptoki.helpers.CDLL"): 20 | dll_class = CryptokiDLLSingleton.from_path("testpath") 21 | assert dll_class.dll_path == "testpath" 22 | 23 | def test_from_path_singleton_multiple_same(self): 24 | with mock.patch("pycryptoki.cryptoki.helpers.CDLL"): 25 | dll_class = CryptokiDLLSingleton.from_path("testpath") 26 | assert dll_class.dll_path == "testpath" 27 | dll_class2 = CryptokiDLLSingleton.from_path("testpath") 28 | dll_class3 = CryptokiDLLSingleton.from_path("testpath") 29 | 30 | assert dll_class == dll_class2 == dll_class3 31 | 32 | def test_from_path_singleton_multiple_diff(self): 33 | with mock.patch("pycryptoki.cryptoki.helpers.CDLL"): 34 | dll_class = CryptokiDLLSingleton.from_path("testpath") 35 | assert dll_class.dll_path == "testpath" 36 | dll_class2 = CryptokiDLLSingleton.from_path("testpath") 37 | assert dll_class2.dll_path == "testpath" 38 | 39 | dll_class3 = CryptokiDLLSingleton.from_path("testpath3") 40 | assert dll_class3.dll_path == "testpath3" 41 | 42 | with mock.patch("pycryptoki.cryptoki.helpers.parse_chrystoki_conf") as chrystoki_conf: 43 | chrystoki_conf.return_value = "conf_path" 44 | original_dll = CryptokiDLLSingleton() 45 | assert original_dll.dll_path == "conf_path" 46 | 47 | assert dll_class == dll_class2 48 | assert dll_class != original_dll 49 | assert dll_class != dll_class3 50 | -------------------------------------------------------------------------------- /tests/unittests/test_encryption.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for encryption.py 3 | """ 4 | import pytest 5 | from hypothesis import given 6 | from hypothesis.strategies import text, integers, data, lists 7 | from six import b 8 | 9 | import pycryptoki.encryption as encrypt 10 | 11 | from string import ascii_letters as ascii 12 | 13 | 14 | class TestEncryption(object): 15 | @given(data()) 16 | def test_split_string_into_list(self, data): 17 | """ 18 | _split_string_into_list() w/ random text and block size 19 | :param data: 20 | """ 21 | txt = data.draw(text(alphabet=ascii, min_size=1)) 22 | block = data.draw(integers(min_value=1, max_value=len(txt))) 23 | 24 | txt_list = [txt[i : i + block] for i in range(0, len(txt), block)] 25 | assert encrypt._split_string_into_list(txt, block) == txt_list 26 | 27 | @given(lists(elements=text(alphabet=ascii), min_size=1)) 28 | def test_get_string_from_list(self, list_val): 29 | """ 30 | _get_string_from_list w/ list of random text 31 | :param list_val: list of random text 32 | """ 33 | list_val = [b(x) for x in list_val] 34 | assert encrypt._get_string_from_list(list_val) == b"".join(list_val) 35 | -------------------------------------------------------------------------------- /tests/unittests/test_str_helpers.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Testcases for string helpers 4 | """ 5 | import datetime 6 | import sys 7 | 8 | import pytest 9 | from hypothesis import given, strategies, example 10 | 11 | from pycryptoki.default_templates import dh_prime 12 | from pycryptoki.mechanism import Mechanism, IvMechanism 13 | 14 | from pycryptoki.defines import ( 15 | CKM_DES_ECB, 16 | CKM_AES_CBC, 17 | CKA_DECRYPT, 18 | CKA_OUID, 19 | CKA_PRIME, 20 | CKA_KEY_TYPE, 21 | CKM_RSA_PKCS_PSS, 22 | CKA_START_DATE, 23 | ) 24 | from pycryptoki.string_helpers import _decode, _coerce_mech_to_str, pformat_pyc_args 25 | 26 | 27 | @pytest.mark.parametrize( 28 | "value,ret", 29 | [ 30 | (b"this is a test string", b"this is a test string"), 31 | (b"\x01\x23\x82\x20\x01\xb6\x09\xd2\xb6|gN\xcc", "0123822001b609d2b67c674ecc"), 32 | (None, None), 33 | (b"", ""), 34 | ], 35 | ) 36 | def test_decode(value, ret): 37 | assert _decode(value) == ret 38 | 39 | 40 | @given(strategies.binary()) 41 | # Explicit example from a failing test in live -- has a unicode character that will fail on a str() call in python2 42 | @example(u" SxҎTmi".encode("utf-8")) 43 | def test_fuzzed_decoding(bdata): 44 | # raises if we couldn't call str() on the results 45 | pformat_pyc_args({"data": bdata}) 46 | 47 | 48 | CBC_OUT = """Iv16Mechanism(mech_type: CKM_AES_CBC (0x00001082), 49 | iv: [0, 1, 2, 3, 4, 5, 6, 7])""" 50 | 51 | 52 | @pytest.mark.parametrize( 53 | "mech,output", 54 | [ 55 | ({"mech_type": CKM_DES_ECB}, "NullMech(mech_type: CKM_DES_ECB (0x00000121))"), 56 | (Mechanism(CKM_AES_CBC, params={"iv": list(range(8))}), CBC_OUT), 57 | ], 58 | ) 59 | def test_mech_printing(mech, output): 60 | assert _coerce_mech_to_str(mech) == output 61 | 62 | 63 | @pytest.mark.parametrize( 64 | "testargs,expected_result", 65 | [ 66 | ( 67 | {"mechanism": IvMechanism(mech_type=CKM_AES_CBC)}, 68 | "mechanism: \n\tIvMechanism(mech_type: CKM_AES_CBC (0x00001082))", 69 | ), 70 | ( 71 | { 72 | "wrapped_key": b"\n\xd7\x04R\xd5OufU\x15\x19\xf4\x93\x94\x05\xec\xf9b\x92\xb5,\xa75NM\x93\x14\xeb\xdd\x97\xe0\x8a\xe6\x15w\x86\xe9\x12mu\xb5l\x80QG\x852$X!\xf3H\x05+\xff\xc6j\xa7\x14\xf9\xdb\x1b\n\xd3", 73 | }, 74 | "wrapped_key: 0ad70452d54f7566551519f4939405ecf96292b5[...] (len: 64)", 75 | ), 76 | ( 77 | {"template": {CKA_DECRYPT: True, 0x80000111: True, CKA_KEY_TYPE: 0}}, 78 | "\n\tCKA_DECRYPT: True\n\t0x80000111: True", 79 | ), 80 | ({"password": "badpassword"}, "password: *"), 81 | ( 82 | {"template": {CKA_OUID: b"005211001100000128230900"}}, 83 | "template: \n\tCKA_OUID: 005211001100000128230900", 84 | ), 85 | ( 86 | {"public_template": {CKA_PRIME: dh_prime}}, 87 | "public_template: \n\tCKA_PRIME: [244, 136, 253, 88, 78, 73, 219, 205, 32[...] (len: 128)", 88 | ), 89 | ( 90 | {"find_template": {CKA_START_DATE: b"20010101"}}, 91 | "find_template: \n\tCKA_START_DATE: 20010101", 92 | ), 93 | pytest.param( 94 | {"find_template": {CKA_START_DATE: {"year": b"2001", "month": b"01", "day": b"01"}}}, 95 | "find_template: \n\tCKA_START_DATE: {'year': b'2001', 'month': b'01', 'day':[...] (len: 3)", 96 | marks=pytest.mark.xfail( 97 | sys.version_info < (3, 0), reason='Byte printing returns b"" in py3, "" in py2' 98 | ), 99 | ), 100 | ( 101 | {"find_template": {CKA_START_DATE: "20010101"}}, 102 | "find_template: \n\tCKA_START_DATE: 20010101", 103 | ), 104 | ( 105 | # TODO: Note the formatting difference between datetime.date() & other date formats 106 | # To me, this is not ideal. I'd prefer them to be identical. However, fixing that would require 107 | # significant changes to how we log/output template data. I do think that it would be a good change, 108 | # but it's far more effort than I want to put in right now, as this change is fixing a bug where we 109 | # would error on *logging* what we're trying to do. 110 | {"find_template": {CKA_START_DATE: datetime.date(2001, 1, 1)}}, 111 | "find_template: \n\tCKA_START_DATE: 2001-01-01", 112 | ), 113 | ], 114 | ids=[ 115 | "Mechanism", 116 | "Invalid UTF8 (binary data)", 117 | "Template", 118 | "password", 119 | "ouid template", 120 | "long template value", 121 | "date format, in bytes", 122 | "date format, in dict", 123 | "date format, in str", 124 | "date format, in date obj", 125 | ], 126 | ) 127 | def test_arg_formatting(testargs, expected_result): 128 | result = pformat_pyc_args(testargs) 129 | assert expected_result in "\n".join(result) 130 | 131 | 132 | def test_incomplete_mech(): 133 | kwargs = {"mechanism": CKM_RSA_PKCS_PSS} 134 | assert "mechanism: \n\tCKM_RSA_PKCS_PSS" in "\n".join(pformat_pyc_args(kwargs)) 135 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=clean,py27,py36,py37,py38,py39,py310,report 3 | [testenv] 4 | deps=pytest==3.10.1;python_version<'3' 5 | pytest;python_version>='3' 6 | six 7 | rpyc==3.4.4;python_version<='2.7' 8 | rpyc==6.0.0;python_version>'3' 9 | hypothesis 10 | mock 11 | pytz 12 | future 13 | pytest-cov 14 | commands=pytest \ 15 | tests/unittests \ 16 | --junitxml=junit-{envname}.xml \ 17 | --showlocals \ 18 | -ra \ 19 | --cov=pycryptoki --cov-append {posargs} 20 | 21 | 22 | [testenv:report] 23 | deps = coverage 24 | skip_install = true 25 | commands = 26 | coverage report 27 | coverage xml 28 | 29 | [testenv:clean] 30 | deps = coverage 31 | skip_install = true 32 | commands = coverage erase 33 | --------------------------------------------------------------------------------