Skip to content

Commit

Permalink
Add uri2pem.py tool to create pkcs11-provider PEM key files
Browse files Browse the repository at this point in the history
Signed-off-by: S-P Chan <[email protected]>
  • Loading branch information
space88man authored and simo5 committed Apr 4, 2024
1 parent 8658522 commit b8624f0
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ cscope.files
cscope.out
cscope.in.out
cscope.po.out
__pycache__/
1 change: 1 addition & 0 deletions .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Files: **/Makefile.am
packaging/pkcs11-provider.spec
docs/*
tests/lsan.supp
tools/openssl*.cnf
Copyright: (C) 2022 Simo Sorce <[email protected]>
License: Apache-2.0

Expand Down
24 changes: 24 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# CLI tool uri2pem.py

Simple tool to create PEM files for PKCS#11 URI
Usage:

python uri2pem.py --help
python uri2pem.py 'pkcs11:token=MyToken;object=MyObject;type=private'
python uri2pem.py --bypass 'someBogusURI'
# output
python uri2pem.py --out mykey.pem 'pkcs11:token=MyToken;object=MyObject;type=private'
# verification, if token is available, requires --out <filename>
python uri2pem.py --verify --out mykey.pem 'pkcs11:token=MyToken;object=MyObject;type=private'

The tool doesn't validate the argument for a valid PKCS#11 URI

## Tests

Requires: pytest

To run the tests for `uri2pem.py`,
first run `make check` to create the test NSS softoken.
Then in `tools/`, run `pytest tests`.

The test suite enables `pkcs11-module-encode-provider-uri-to-pem = true`.
Empty file added tools/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions tools/openssl-tools.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.include ../tests/tmp.softokn/openssl.cnf

[pkcs11_sect]
pkcs11-module-encode-provider-uri-to-pem = true
Empty file added tools/tests/__init__.py
Empty file.
86 changes: 86 additions & 0 deletions tools/tests/test_softoken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""
Copyright (C) 2024 S-P Chan <[email protected]>
SPDX-License-Identifier: Apache-2.0
"""

import os
import pathlib
import subprocess
import sys
import random
import string
import re

from asn1crypto import pem
from .. import uri2pem

tokens = pathlib.Path("../tests/tmp.softokn/tokens/key4.db")


if not tokens.exists():
print("Run 'make check' first to create a NSS softoken in tests/tmp.softokn/tokens")
raise SystemExit(1)


P11_TOKEN = "".join(random.choices(string.ascii_letters, k=12))
P11_OBJECT = "".join(random.choices(string.ascii_letters, k=12))
KEY_URI = f"pkcs11:token={P11_TOKEN};object={P11_OBJECT};type=private"
KEY_DESC = "PKCS#11 Provider URI v1.0"


def test_roundtrip(tmp_path):

pem_bytes = uri2pem.uri2pem(KEY_URI)
# asn1crypto does not like '#' in PEM labels
pem_replace = pem_bytes.decode("ascii").replace("#", "0")

# read back the object
der_bytes = pem.unarmor(pem_replace.encode("ascii"), multiple=False)
key = uri2pem.Pkcs11PrivateKey.load(der_bytes[2])

assert key["desc"].native == KEY_DESC
assert key["uri"].native == KEY_URI


def test_asn1parse(tmp_path):
pem_bytes = uri2pem.uri2pem(KEY_URI)
pem_file = pathlib.Path(tmp_path / "test_asn1parse.pem")
pathlib.Path(tmp_path / "test_asn1parse.pem").write_bytes(pem_bytes)
ret = subprocess.run(
["openssl", "asn1parse", "-in", str(pem_file)], capture_output=True, text=True
)

assert ret.returncode == 0
assert "SEQUENCE" in ret.stdout and KEY_DESC in ret.stdout and KEY_URI in ret.stdout


def test_storeutl(tmp_path):
ret = subprocess.run(
["openssl", "storeutl", "-text", "pkcs11:"],
capture_output=True,
text=True,
env={"OPENSSL_CONF": "./openssl-tools.cnf"}
)

assert ret.returncode == 0

private_key = None
for line in ret.stdout.splitlines():
if m := re.match("URI (pkcs11.*type=private)$", line):
private_key = m.group(1)
break

assert private_key

data = uri2pem.uri2pem(private_key)
private_key_pem = pathlib.Path(tmp_path / "private_key.pem")
private_key_pem.write_bytes(data)

ret = subprocess.run(
["openssl", "pkey", "-in", str(private_key_pem)],
capture_output=True,
text=True,
env={"OPENSSL_CONF": "./openssl-tools.cnf"}
)

assert ret.returncode == 0
75 changes: 75 additions & 0 deletions tools/uri2pem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
Copyright (C) 2024 S-P Chan <[email protected]>
SPDX-License-Identifier: Apache-2.0
"""

"""
CLI tool to create pkcs11-provider pem files from a key uri
Requirements: asn1crypto
Installation:
pip install asn1crypto
dnf install python3-asn1crypto
Usage:
python uri2pem.py 'pkcs11:URI-goes-here'
"""

import sys
from asn1crypto.core import Sequence, VisibleString, UTF8String
from asn1crypto import pem


class Pkcs11PrivateKey(Sequence):
_fields = [("desc", VisibleString), ("uri", UTF8String)]


def uri2pem(uri: str, bypass: bool = False) -> bytes:
if not bypass:
if not (uri.startswith("pkcs11:") and "type=private" in uri):
print(f"Error: uri({uri}) not a valid PKCS#11 URI")
sys.exit(1)
if not ("object=" in uri or "id=" in uri):
print(f"Error: uri({uri}) does not specify an object by label or id")
sys.exit(1)

data = Pkcs11PrivateKey(
{
"desc": VisibleString("PKCS#11 Provider URI v1.0"),
"uri": UTF8String(uri),
}
)
return pem.armor("PKCS#11 PROVIDER URI", data.dump())


if __name__ == "__main__":
import argparse
import pathlib
import subprocess

parser = argparse.ArgumentParser()
parser.add_argument("--bypass", action='store_true', help='skip basic URI checks')
parser.add_argument("--verify", action='store_true', help='verify PEM file with OpenSSL; requires --out to be specified')
parser.add_argument("--out", action='store', help='output to PEM file, otherwise to stdout', metavar='OUTPUT_FILE')
parser.add_argument("keyuri", action='store', help='the PKCS#11 key URI to encode')

opts = parser.parse_args()
if opts.verify and not opts.out:
print(f"{sys.argv[0]}: --verify option requires --out <filename> to be specified")
sys.exit(1)

data = uri2pem(opts.keyuri, bypass=opts.bypass)
if opts.out:
out_file = pathlib.Path(opts.out)
out_file.write_bytes(data)
else:
print(data.decode("ascii"), end="")

if opts.verify:
print("===== OpenSSL pkey output =====")
ret = subprocess.run(["openssl", "pkey", "-in", str(out_file), "-pubout"])
print("===== END =====")
if ret.returncode != 0:
print(f"{sys.argv[0]}: verification of private key PEM({str(out_file)}) failed")
else:
print(f"{sys.argv[0]}: verification of private key PEM({str(out_file)}) OK")

0 comments on commit b8624f0

Please sign in to comment.