diff --git a/codecov.yml b/codecov.yml index f21826b3..fe696ee5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,6 +1,5 @@ ignore: - "pyhanko_tests" - "docs" - - "pyhanko/sign/beid.py" - "pyhanko/__main__.py" - "pyhanko/pdf_utils/_saslprep.py" # this is mostly vendored code diff --git a/docs/api-docs/pyhanko.sign.rst b/docs/api-docs/pyhanko.sign.rst index e23329f3..51d3bad2 100644 --- a/docs/api-docs/pyhanko.sign.rst +++ b/docs/api-docs/pyhanko.sign.rst @@ -24,14 +24,6 @@ pyhanko.sign.attributes module :undoc-members: :show-inheritance: -pyhanko.sign.beid module ------------------------- - -.. automodule:: pyhanko.sign.beid - :members: - :undoc-members: - :show-inheritance: - pyhanko.sign.fields module -------------------------- diff --git a/docs/changelog.rst b/docs/changelog.rst index 1f8f6fc7..efab552d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,6 +3,25 @@ Release history *************** +.. _release-0.23.0: + +*Release date:* 2024-03-10 + + +Breaking changes +---------------- + + * The BeID signer implementation and CLI command was moved into a separate + package; see + `pyhanko-beid-plugin `_. + While this integration was so far preserved in the core tree for + historical reasons, pyHanko has matured beyond this kind of vendor/country-specific + code. Note that CLI invocations will continue to work unchanged as long as + ``pyhanko-beid-plugin`` is installed alongside pyHanko, thanks to Python's + package entry point mechanism. + + + .. _release-0.22.0: *Release date:* 2024-03-07 @@ -334,7 +353,7 @@ Other than these, there have been some miscellaneous changes. * Move ``add_content_to_page`` to :meth:`~pyhanko.pdf_utils.content.PdfContent.add_to_page` to deal with a (conceptual) circular dependency between modules. * :class:`~pyhanko_certvalidator.registry.CertificateStore` is no longer reexported by :mod:`pyhanko.sign.general`. - * The :class:`~pyhanko.sign.beid.BEIDSigner` no longer allows convenient access to the authentication certificate. + * The ``BEIDSigner`` no longer allows convenient access to the authentication certificate. * Packaging-wise, underscores have been replaced with hyphens in optional dependency groups. * In ``pyhanko_certvalidator``, :class:`~pyhanko_certvalidator.errors.InvalidCertificateError` is no longer a subclass of :class:`~pyhanko_certvalidator.errors.PathValidationError`. diff --git a/docs/cli-guide/signing.rst b/docs/cli-guide/signing.rst index 1d9c3a4c..4bfca361 100644 --- a/docs/cli-guide/signing.rst +++ b/docs/cli-guide/signing.rst @@ -254,56 +254,6 @@ With this information, producing a basic signature isn't very hard: Have a look at ``pyhanko sign addsig pkcs11 --help`` for a full list of options. -Signing a PDF file using a Belgian eID card -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To sign a PDF file using your eID card, use the ``beid`` subcommand to -``addsig``, with the ``--lib`` parameter to tell pyHanko where to look for the -eID PKCS#11 library. - -.. note:: - Of course, you can also use the ``pkcs11`` subcommand, but ``beid`` provides an extra layer - of convenience. - -On Linux, it is named ``libbeidpkcs11.so`` and can usually be found under -``/usr/lib`` or ``/usr/local/lib``. -On macOS, it is named ``libbeidpkcs11.dylib``, and can similarly be found under -``/usr/local/lib``. -The Windows version is typically installed to ``C:\Windows\System32`` and is -called ``beidpkcs11.dll``. - - -On Linux, this boils down to the following: - -.. code-block:: bash - - pyhanko sign addsig --field Sig1 beid \ - --lib /path/to/libbeidpkcs11.so input.pdf output.pdf - -On all platforms, the eID middleware will prompt you to enter your PIN to create -the signature. - - -.. warning:: - This command will produce a non-repudiable signature using the 'Signature' - certificate on your eID card (as opposed to the 'Authentication' - certificate). These signatures are legally equivalent to - a normal "wet" signature wherever they are allowed, so use them with care. - - In particular, you should only allow software you trust\ [#disclaimer]_ - to use the 'Signature' certificate! - - -.. warning:: - You should also be aware that your national registry number - (rijksregisternummer, no. de registre national) is embedded into the - metadata of the signature certificate on your eID card\ [#nnserial]_. - As such, it can also be **read off from any digital signature you create**. - While national registry numbers aren't secret per se, they are nevertheless - often considered sensitive personal information, so you may want to be - careful where you send documents containing your eID signature or that - of someone else. - .. _ltv-signing: @@ -507,10 +457,3 @@ see :doc:`stamping` and :ref:`style-definitions` for details. .. [#validationscope] The author has it on good authority that a rigorous incremental update validation specification is beyond the scope of the PDF standard itself. -.. [#disclaimer] - This obviously also applies to pyHanko itself; be aware that pyHanko's - :doc:`license ` doesn't make any fitness-for-purpose guarantees, - so making sure you know what you're running is 100% your own responsibility. -.. [#nnserial] - The certificate's serial number is in fact equal to the holder's - national registry number. \ No newline at end of file diff --git a/pyhanko/cli/_root.py b/pyhanko/cli/_root.py index eb43f169..72aaf180 100644 --- a/pyhanko/cli/_root.py +++ b/pyhanko/cli/_root.py @@ -117,7 +117,6 @@ def _load_plugins(root_config: Optional[CLIRootConfig], plugins_enabled: bool): # we always load the default ones to_load = [ 'pyhanko.cli.commands.signing.pkcs11_cli:PKCS11Plugin', - 'pyhanko.cli.commands.signing.pkcs11_cli:BEIDPlugin', 'pyhanko.cli.commands.signing.simple:PKCS12Plugin', 'pyhanko.cli.commands.signing.simple:PemderPlugin', ] diff --git a/pyhanko/cli/commands/signing/pkcs11_cli.py b/pyhanko/cli/commands/signing/pkcs11_cli.py index 6eb0a946..89cca508 100644 --- a/pyhanko/cli/commands/signing/pkcs11_cli.py +++ b/pyhanko/cli/commands/signing/pkcs11_cli.py @@ -1,4 +1,3 @@ -import contextlib import getpass import os from typing import ContextManager, List, Optional @@ -17,7 +16,7 @@ ) from pyhanko.sign import Signer -__all__ = ['PKCS11Plugin', 'BEIDPlugin'] +__all__ = ['PKCS11Plugin'] try: @@ -174,41 +173,9 @@ def _pkcs11_signer_context( return pkcs11.PKCS11SigningContext(pkcs11_config, user_pin=pin) -class BEIDPlugin(SigningCommandPlugin): - subcommand_name = 'beid' - help_summary = 'use Belgian eID to sign' - unavailable_message = UNAVAIL_MSG - - def is_available(self) -> bool: - return pkcs11_available - - def click_options(self) -> List[click.Option]: - return [ - click.Option( - ('--lib',), - help='path to libbeidpkcs11 library file', - type=readable_file, - required=False, - ), - click.Option( - ('--slot-no',), - help='specify PKCS#11 slot to use', - required=False, - type=int, - default=None, - ), - ] - - def create_signer( - self, context: CLIContext, **kwargs - ) -> ContextManager[Signer]: - return _beid_signer_context(context, **kwargs) - - class ModuleConfigWrapper: def __init__(self, config: CLIConfig): config_dict = config.raw_config - self.beid_module_path = config_dict.get('beid-module-path', None) self.pkcs11_setups = config_dict.get('pkcs11-setups', {}) def get_pkcs11_config(self, name): @@ -217,39 +184,3 @@ def get_pkcs11_config(self, name): except KeyError: raise ConfigurationError(f"There's no PKCS#11 setup named '{name}'") return PKCS11SignatureConfig.from_config(setup) - - -def _beid_signer_context(ctx: CLIContext, lib, slot_no): - import pkcs11 - - from pyhanko.sign import beid - - module_path: str - if not lib: - cli_config: Optional[CLIConfig] = ctx.config - beid_module_path = None - if cli_config is not None: - beid_module_path = ModuleConfigWrapper(cli_config).beid_module_path - if beid_module_path is None: - raise click.ClickException( - "The --lib option is mandatory unless beid-module-path is " - "provided in the configuration file." - ) - module_path = beid_module_path - else: - module_path = lib - - @contextlib.contextmanager - def manager(): - try: - session = beid.open_beid_session(module_path, slot_no=slot_no) - except pkcs11.PKCS11Error as e: - logger.error("PKCS#11 error", exc_info=e) - raise click.ClickException( - f"PKCS#11 error: [{type(e).__name__}] {e}" - ) - - with session: - yield beid.BEIDSigner(session) - - return manager() diff --git a/pyhanko/sign/beid.py b/pyhanko/sign/beid.py deleted file mode 100644 index 2d60af48..00000000 --- a/pyhanko/sign/beid.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Sign PDF files using a Belgian eID card. - -This module defines a very thin convenience wrapper around -:mod:`.pyhanko.sign.pkcs11` to set up a PKCS#11 session with an eID card and -read the appropriate certificates on the device. -""" - -from pkcs11 import Session - -from . import pkcs11 as sign_pkcs11 - -__all__ = ['open_beid_session', 'BEIDSigner'] - - -def open_beid_session(lib_location, slot_no=None) -> Session: - """ - Open a PKCS#11 session - - :param lib_location: - Path to the shared library file containing the eID PKCS#11 module. - Usually, the file is named ``libbeidpkcs11.so``, - ``libbeidpkcs11.dylib`` or ``beidpkcs11.dll``, depending on your - operating system. - :param slot_no: - Slot number to use. If not specified, the first slot containing a token - labelled ``BELPIC`` will be used. - :return: - An open PKCS#11 session object. - """ - # the middleware will prompt for the user's PIN when we attempt - # to sign later, so there's no need to specify it here - return sign_pkcs11.open_pkcs11_session( - lib_location, slot_no=slot_no, token_label='BELPIC' - ) - - -class BEIDSigner(sign_pkcs11.PKCS11Signer): - """ - Belgian eID-specific signer implementation that automatically populates - the (trustless) certificate list with the relevant certificates stored - on the card. - This includes the government's (self-signed) root certificate and the - certificate of the appropriate intermediate CA. - """ - - def __init__( - self, - pkcs11_session: Session, - use_auth_cert: bool = False, - bulk_fetch: bool = False, - embed_roots=True, - ): - super().__init__( - pkcs11_session=pkcs11_session, - cert_label='Authentication' if use_auth_cert else 'Signature', - other_certs_to_pull=('Root', 'CA'), - bulk_fetch=bulk_fetch, - embed_roots=embed_roots, - ) diff --git a/pyhanko/sign/signers/pdf_cms.py b/pyhanko/sign/signers/pdf_cms.py index 04ce09cb..047d404e 100644 --- a/pyhanko/sign/signers/pdf_cms.py +++ b/pyhanko/sign/signers/pdf_cms.py @@ -250,8 +250,7 @@ class Signer: * :class:`.SimpleSigner` implements the easy case where all the key material can be loaded into memory. * :class:`~pyhanko.sign.pkcs11.PKCS11Signer` implements a signer that is - capable of interfacing with a PKCS#11 device - (see also :class:`~pyhanko.sign.beid.BEIDSigner`). + capable of interfacing with a PKCS#11 device. :param prefer_pss: When signing using an RSA key, prefer PSS padding to legacy PKCS#1 v1.5 diff --git a/pyhanko/version.py b/pyhanko/version.py index a65d3b59..ff04b025 100644 --- a/pyhanko/version.py +++ b/pyhanko/version.py @@ -1,2 +1,2 @@ -__version__ = '0.22.1-dev1' -__version_info__ = (0, 22, 1, 'dev1') +__version__ = '0.23.0' +__version_info__ = (0, 23, 0) diff --git a/pyhanko_tests/cli_tests/conftest.py b/pyhanko_tests/cli_tests/conftest.py index fdc4236e..40d78891 100644 --- a/pyhanko_tests/cli_tests/conftest.py +++ b/pyhanko_tests/cli_tests/conftest.py @@ -128,14 +128,6 @@ def _write_config(config: dict, fname: str = 'pyhanko.yml'): yaml.dump(config, outf) -class _DummyManager: - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - return - - logger = logging.getLogger(__name__) diff --git a/pyhanko_tests/cli_tests/test_cli_signing_pkcs11.py b/pyhanko_tests/cli_tests/test_cli_signing_pkcs11.py index 366fd7a9..4810f806 100644 --- a/pyhanko_tests/cli_tests/test_cli_signing_pkcs11.py +++ b/pyhanko_tests/cli_tests/test_cli_signing_pkcs11.py @@ -1,23 +1,14 @@ import getpass -import requests_mock -from certomancer.integrations.illusionist import Illusionist -from freezegun import freeze_time - from pyhanko.cli import cli_root from pyhanko.cli.commands.signing.pkcs11_cli import P11_PIN_ENV_VAR from pyhanko_tests.cli_tests.conftest import ( - FREEZE_DT, INPUT_PATH, SIGNED_OUTPUT_PATH, _const, - _DummyManager, - _validate_last_sig_in, _write_config, ) -from pyhanko_tests.samples import TESTING_CA from pyhanko_tests.signing_commons import ( - FROM_CA, SOFTHSM, pkcs11_only, pkcs11_test_module, @@ -207,119 +198,3 @@ def test_cli_addsig_pkcs11_with_setup_and_pin_prompt( ], ) assert not result.exception, result.output - - -@pkcs11_only -@freeze_time(FREEZE_DT) -def test_cli_addsig_beid(cli_runner, monkeypatch): - from pyhanko.sign import beid - - monkeypatch.setattr( - beid, 'open_beid_session', value=_const(_DummyManager()) - ) - monkeypatch.setattr(beid, 'BEIDSigner', value=_const(FROM_CA)) - with open('libbeidpkcs11-mock', 'wb') as mocklib: - mocklib.write(b"\x00") - result = cli_runner.invoke( - cli_root, - [ - 'sign', - 'addsig', - '--field', - 'Sig1', - 'beid', - '--lib', - 'libbeidpkcs11-mock', - INPUT_PATH, - SIGNED_OUTPUT_PATH, - ], - ) - assert not result.exception, result.output - - with requests_mock.Mocker() as m: - Illusionist(TESTING_CA).register(m) - _validate_last_sig_in(TESTING_CA, SIGNED_OUTPUT_PATH) - - -@pkcs11_only -@freeze_time(FREEZE_DT) -def test_cli_addsig_beid_with_setup(cli_runner, monkeypatch): - from pyhanko.sign import beid - - monkeypatch.setattr( - beid, 'open_beid_session', value=_const(_DummyManager()) - ) - monkeypatch.setattr(beid, 'BEIDSigner', value=_const(FROM_CA)) - - _write_config({'beid-module-path': 'libbeidpkcs11-mock'}) - result = cli_runner.invoke( - cli_root, - [ - 'sign', - 'addsig', - '--field', - 'Sig1', - 'beid', - INPUT_PATH, - SIGNED_OUTPUT_PATH, - ], - ) - assert not result.exception, result.output - - with requests_mock.Mocker() as m: - Illusionist(TESTING_CA).register(m) - _validate_last_sig_in(TESTING_CA, SIGNED_OUTPUT_PATH) - - -@pkcs11_only -def test_cli_beid_lib_mandatory(cli_runner, monkeypatch): - from pyhanko.sign import beid - - monkeypatch.setattr( - beid, 'open_beid_session', value=_const(_DummyManager()) - ) - monkeypatch.setattr(beid, 'BEIDSigner', value=_const(FROM_CA)) - - result = cli_runner.invoke( - cli_root, - [ - 'sign', - 'addsig', - '--field', - 'Sig1', - 'beid', - INPUT_PATH, - SIGNED_OUTPUT_PATH, - ], - ) - assert result.exit_code == 1 - assert '--lib option is mandatory' in result.output - - -@pkcs11_only -def test_cli_beid_pkcs11_error(cli_runner, monkeypatch): - from pkcs11 import PKCS11Error - - from pyhanko.sign import beid - - def _throw(*_args, **_kwargs): - raise PKCS11Error - - monkeypatch.setattr(beid, 'open_beid_session', value=_throw) - - _write_config({'beid-module-path': 'blah'}) - - result = cli_runner.invoke( - cli_root, - [ - 'sign', - 'addsig', - '--field', - 'Sig1', - 'beid', - INPUT_PATH, - SIGNED_OUTPUT_PATH, - ], - ) - assert result.exit_code == 1 - assert 'PKCS#11 error' in result.output