openpgp-card/tools/prepare-card.py
Lars Wirzenius 3a241a107f add an example Python script to prepare a card for some organization
This is a fairly simplistic example, but shows how to use opgpcard via
its JSON API to set up a card with a specific configuration, for a
specific user. It's meant to show how to get started, and hopefully
can be a base for a custom tool for an organization with specific
needs.

It won't modify a card that already has an OpenPGP key on it, unless
--force is used.

Sponsored-by: author
2022-11-07 21:11:15 +02:00

149 lines
3.8 KiB
Python
Executable file

#!/usr/bin/python3
#
# WARNING: This will wipe any information on a card. Do not use it unless
# you're very sure you don't mind.
#
# Prepare an OpenPGP card for use within a hypothetical organization:
#
# - factory reset the card
# - set card holder name, if desired
# - generate elliptic curve 25519 keys
# - write to stdout a JSON object with the card id, card holder, and
# key fingerprints
#
# Usage: run with --help.
#
# SPDX-FileCopyrightText: 2022 Lars Wirzenius <liw@liw.fi>
# SPDX-License-Identifier: MIT OR Apache-2.0
import argparse
import json
import sys
from subprocess import run
tracing = False
def trace(msg):
if tracing:
sys.stderr.write(f"DEBUG: {msg}\n")
sys.stderr.flush()
def opgpcard_raw(args):
argv = ["opgpcard"] + args
trace(f"running {argv}")
p = run(argv, capture_output=True)
if p.returncode != 0:
raise Exception(f"opgpcard failed:\n{p.stderr}")
o = p.stdout
trace(f"opgpcard raw output: {o!r}")
return o
def opgpcard_json(args):
o = json.loads(opgpcard_raw(["--output-format=json"] + args))
trace(f"opgpcard JSON output: {o}")
return o
def list_cards():
return opgpcard_json(["list"])["idents"]
def pick_card(card):
cards = list_cards()
if card is None:
if not cards:
raise Exception("No cards found")
if len(cards) > 1:
raise Exception(f"Can't pick card automatically: found {len(cards)} cards")
return cards[0]
elif card in cards:
return card
else:
raise Exception(f"Can't find specified card {card}")
def factory_reset(card):
opgpcard_raw(["factory-reset", "--card", card])
def set_card_holder(card, admin_pin, name):
trace(f"set card holder to {name!r}")
opgpcard_raw(["admin", "--card", card, "--admin-pin", admin_pin, "name", name])
def generate_key(card, admin_pin, user_pin):
opgpcard_raw(
[
"admin",
f"--card={card}",
f"--admin-pin={admin_pin}",
"generate",
f"--user-pin={user_pin}",
"--output=/dev/null",
"cv25519",
]
)
def status(card):
o = opgpcard_json(["status", f"--card={card}"])
return {
"card_ident": o["ident"],
"cardholder_name": o["cardholder_name"],
"signature_key": o["signature_key"]["fingerprint"],
"decryption_key": o["signature_key"]["fingerprint"],
"authentication_key": o["signature_key"]["fingerprint"],
}
def card_is_empty(card):
o = status(card)
del o["card_ident"]
for key in o:
if o[key]:
return False
return True
def main():
p = argparse.ArgumentParser()
p.add_argument("--force", action="store_true", help="prepare a card that has data")
p.add_argument(
"--verbose", action="store_true", help="produce debugging output to stderr"
)
p.add_argument("--card", help="card identifier, default is to pick the only one")
p.add_argument("--card-holder", help="name of card holder", required=True)
p.add_argument(
"--admin-pin", action="store", help="set file with admin PIN", required=True
)
p.add_argument(
"--user-pin", action="store", help="set file with user PIN", required=True
)
args = p.parse_args()
if args.verbose:
global tracing
tracing = True
trace(f"args: {args}")
card = pick_card(args.card)
if not args.force and not card_is_empty(card):
raise Exception(f"card {card} has existing keys, not touching it")
factory_reset(card)
set_card_holder(card, args.admin_pin, args.card_holder)
key = generate_key(card, args.admin_pin, args.user_pin)
o = status(card)
print(json.dumps(o, indent=4))
if __name__ == "__main__":
try:
main()
except Exception as e:
sys.stderr.write(f"ERROR: {e}\n")
sys.exit(1)