From 3a241a107f29dd8f542a713f630a76fc1dcff6dc Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Thu, 27 Oct 2022 14:57:49 +0300 Subject: [PATCH] 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 --- tools/prepare-card.py | 149 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100755 tools/prepare-card.py diff --git a/tools/prepare-card.py b/tools/prepare-card.py new file mode 100755 index 0000000..ee153e9 --- /dev/null +++ b/tools/prepare-card.py @@ -0,0 +1,149 @@ +#!/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 +# 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)