Skip to content

Commit

Permalink
Migrate a few tests to pytest.
Browse files Browse the repository at this point in the history
  • Loading branch information
dainnilsson committed Jan 22, 2021
1 parent 5aed7e4 commit 9105104
Show file tree
Hide file tree
Showing 13 changed files with 833 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .bandit
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[bandit]
exclude: /test,/.tox
exclude: /tests
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ repos:
rev: 1.6.2
hooks:
- id: bandit
exclude: (^.tox/|^test/) # exclude .tox and test repo, keep in sync with .bandit file
exclude: ^test(s)?/ # keep in sync with .bandit file
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.782
hooks:
- id: mypy
exclude: ^tests/ # keep in sync with mypy.ini
5 changes: 5 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[mypy]
files = yubikit/,ykman/

[mypy-smartcard.*]
ignore_missing_imports = True
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pywin32 = {version = ">=223", platform = "win32"}
[tool.poetry.dev-dependencies]
pytest = "^6.0"
pyOpenSSL = "^17.0"
makefun = "^1.9.5"

[tool.poetry.scripts]
ykman = "ykman.cli.__main__:main"
Expand Down
Empty file added tests/__init__.py
Empty file.
71 changes: 71 additions & 0 deletions tests/on_yubikey/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from yubikit.core import TRANSPORT
from ykman.device import is_fips_version
from inspect import signature, Parameter, isgeneratorfunction
from makefun import wraps

import pytest


def condition(check, message="Condition not satisfied"):
def deco(func):
sig = signature(func)
if "info" not in sig.parameters:
params = [Parameter("info", kind=Parameter.POSITIONAL_OR_KEYWORD)]
params.extend(sig.parameters.values())
sig = sig.replace(parameters=params)

if isgeneratorfunction(func):

def wrapper(info, *args, **kwargs):
if not check(info):
pytest.skip(message)
yield from func(*args, **kwargs)

else:

def wrapper(info, *args, **kwargs):
if not check(info):
pytest.skip(message)
return func(*args, **kwargs)

return wraps(func, new_sig=sig)(wrapper)

return deco


def register_condition(cond):
setattr(condition, cond.__name__, cond)
return cond


@register_condition
def capability(capability):
return condition(
lambda info: capability in info.config.enabled_capabilities[TRANSPORT.USB],
"Requires %s" % capability,
)


@register_condition
def min_version(major, minor=0, micro=0):
if isinstance(major, tuple):
version = major
else:
version = (major, minor, micro)
return condition(lambda info: info.version >= version, "Version < %s" % (version,))


@register_condition
def max_version(major, minor=0, micro=0):
if isinstance(major, tuple):
version = major
else:
version = (major, minor, micro)
return condition(lambda info: info.version <= version, "Version > %s" % (version,))


@register_condition
def fips(status=True):
return condition(
lambda info: is_fips_version(info.version), "Requires FIPS = %s" % status
)
Empty file.
19 changes: 19 additions & 0 deletions tests/on_yubikey/cli/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from ykman.cli.__main__ import cli
from ykman.cli.aliases import apply_aliases
from click.testing import CliRunner
from functools import partial
import pytest


@pytest.fixture(scope="module")
def ykman_cli(info):
return partial(ykman_cli_for_serial, info.serial)


def ykman_cli_for_serial(serial, *argv, **kwargs):
argv = apply_aliases(["ykman"] + [str(a) for a in argv])
runner = CliRunner()
result = runner.invoke(cli, argv[1:], obj={}, **kwargs)
if result.exit_code != 0:
raise result.exception
return result
151 changes: 151 additions & 0 deletions tests/on_yubikey/cli/test_piv_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
from yubikit.core import Tlv
from yubikit.piv import SLOT, OBJECT_ID
from yubikit.management import CAPABILITY
from cryptography.hazmat.primitives import serialization
from ...util import generate_self_signed_certificate
from .. import condition

import pytest
import contextlib
import os
import io


DEFAULT_MANAGEMENT_KEY = "010203040506070801020304050607080102030405060708"


@pytest.fixture(autouse=True)
@condition.capability(CAPABILITY.PIV)
def ensure_piv(ykman_cli):
ykman_cli("piv", "reset", "-f")


def test_piv_info(ykman_cli):
ykman_cli("piv", "info")


def test_write_read_preserves_ansi_escapes(ykman_cli):
red = b"\x00\x1b[31m"
blue = b"\x00\x1b[34m"
reset = b"\x00\x1b[0m"
data = (
b"Hello, "
+ red
+ b"red"
+ reset
+ b" and "
+ blue
+ b"blue"
+ reset
+ b" world!"
)
ykman_cli(
"piv",
"objects",
"import",
"-m",
DEFAULT_MANAGEMENT_KEY,
"0x5f0001",
"-",
input=data,
)
output_data = ykman_cli("piv", "objects", "export", "0x5f0001", "-").stdout_bytes
assert data == output_data


def test_read_write_read_is_noop(ykman_cli):
data = os.urandom(32)

ykman_cli(
"piv",
"objects",
"import",
hex(OBJECT_ID.AUTHENTICATION),
"-",
"-m",
DEFAULT_MANAGEMENT_KEY,
input=data,
)

output1 = ykman_cli(
"piv", "objects", "export", hex(OBJECT_ID.AUTHENTICATION), "-"
).stdout_bytes
assert output1 == data

ykman_cli(
"piv",
"objects",
"import",
hex(OBJECT_ID.AUTHENTICATION),
"-",
"-m",
DEFAULT_MANAGEMENT_KEY,
input=output1,
)

output2 = ykman_cli(
"piv", "objects", "export", hex(OBJECT_ID.AUTHENTICATION), "-"
).stdout_bytes
assert output2 == data


def test_read_write_aliases(ykman_cli):
data = os.urandom(32)

with io.StringIO() as buf:
with contextlib.redirect_stderr(buf):
ykman_cli(
"piv",
"write-object",
hex(OBJECT_ID.AUTHENTICATION),
"-",
"-m",
DEFAULT_MANAGEMENT_KEY,
input=data,
)

output1 = ykman_cli(
"piv", "read-object", hex(OBJECT_ID.AUTHENTICATION), "-"
).stdout_bytes
err = buf.getvalue()
assert output1 == data
assert "piv objects import" in err
assert "piv objects export" in err


def test_read_write_certificate_as_object(ykman_cli):
with pytest.raises(SystemExit):
ykman_cli("piv", "objects", "export", hex(OBJECT_ID.AUTHENTICATION), "-")

cert = generate_self_signed_certificate()
cert_bytes_der = cert.public_bytes(encoding=serialization.Encoding.DER)

input_tlv = Tlv(0x70, cert_bytes_der) + Tlv(0x71, b"\0") + Tlv(0xFE, b"")

ykman_cli(
"piv",
"objects",
"import",
hex(OBJECT_ID.AUTHENTICATION),
"-",
"-m",
DEFAULT_MANAGEMENT_KEY,
input=input_tlv,
)

output1 = ykman_cli(
"piv", "objects", "export", hex(OBJECT_ID.AUTHENTICATION), "-"
).stdout_bytes
output_cert_bytes = Tlv.parse_dict(output1)[0x70]
assert output_cert_bytes == cert_bytes_der

output2 = ykman_cli(
"piv",
"certificates",
"export",
hex(SLOT.AUTHENTICATION),
"-",
"--format",
"DER",
).stdout_bytes
assert output2 == cert_bytes_der
52 changes: 52 additions & 0 deletions tests/on_yubikey/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from ykman.device import connect_to_device, list_all_devices
from yubikit.core.otp import OtpConnection
from yubikit.core.fido import FidoConnection
from yubikit.core.smartcard import SmartCardConnection

import pytest
import os


@pytest.fixture(scope="session")
def _pid_info():
devices = list_all_devices()
assert len(devices) == 1
dev, info = devices[0]
assert info.serial == int(os.environ.get("DESTRUCTIVE_TEST_YUBIKEY_SERIALS"))
return dev.pid, info


@pytest.fixture(scope="session")
def pid(_pid_info):
return _pid_info[0]


@pytest.fixture(scope="session")
def info(_pid_info):
return _pid_info[1]


@pytest.fixture(scope="session")
def key_type(pid):
return pid.get_type()


connection_scope = os.environ.get("CONNECTION_SCOPE", "module")


@pytest.fixture(scope=connection_scope)
def otp_connection(info):
with connect_to_device(info.serial, [OtpConnection])[0] as c:
yield c


@pytest.fixture(scope=connection_scope)
def fido_connection(info):
with connect_to_device(info.serial, [FidoConnection])[0] as c:
yield c


@pytest.fixture(scope=connection_scope)
def ccid_connection(info):
with connect_to_device(info.serial, [SmartCardConnection])[0] as c:
yield c
8 changes: 8 additions & 0 deletions tests/on_yubikey/test_ccid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from yubikit.core.smartcard import SmartCardProtocol, ApplicationNotAvailableError
import pytest


def test_select_wrong_app(ccid_connection):
p = SmartCardProtocol(ccid_connection)
with pytest.raises(ApplicationNotAvailableError):
p.select(b"not_a_real_aid")
28 changes: 28 additions & 0 deletions tests/on_yubikey/test_interfaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from ykman.device import connect_to_device
from yubikit.core.otp import OtpConnection
from yubikit.core.fido import FidoConnection
from yubikit.core.smartcard import SmartCardConnection
from yubikit.management import USB_INTERFACE


def try_connection(conn_type):
with connect_to_device(None, [conn_type])[0]:
return True


def test_switch_interfaces(pid):
interfaces = pid.get_interfaces()
if USB_INTERFACE.FIDO in interfaces:
assert try_connection(FidoConnection)
if USB_INTERFACE.OTP in interfaces:
assert try_connection(OtpConnection)
if USB_INTERFACE.FIDO in interfaces:
assert try_connection(FidoConnection)
if USB_INTERFACE.CCID in interfaces:
assert try_connection(SmartCardConnection)
if USB_INTERFACE.OTP in interfaces:
assert try_connection(OtpConnection)
if USB_INTERFACE.CCID in interfaces:
assert try_connection(SmartCardConnection)
if USB_INTERFACE.FIDO in interfaces:
assert try_connection(FidoConnection)
Loading

0 comments on commit 9105104

Please sign in to comment.