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 committed Mar 20, 2024
1 parent 673e4fd commit 25f92f1
Show file tree
Hide file tree
Showing 7 changed files with 180 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__/
21 changes: 21 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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

If you run `make check` the tool has a test suite that will run
against NSS softoken in `../tests/tmp.softokn/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.
81 changes: 81 additions & 0 deletions tools/tests/test_softoken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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
73 changes: 73 additions & 0 deletions tools/uri2pem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
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')
parser.add_argument("--verify", action='store_true')
parser.add_argument("--out", action='store')
parser.add_argument("keyuri", action='store')

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:
ret = subprocess.run(["openssl", "pkey", "-in", str(out_file), "-pubout"])
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 25f92f1

Please sign in to comment.