feat: Bolt Card writer CLI for PC/SC NFC readers

Provision, inspect and wipe NTAG 424 DNA Bolt Cards from a Linux PC
using an LNbits /boltcards/api/v1/auth link, replacing the phone app
for desks with a USB reader (ACR1252U).

- pcsc.py: ctypes client for libpcsclite (no compiled deps)
- ntag424.py: EV2 secure messaging (AuthenticateEV2First, ChangeKey,
  ChangeFileSettings, GetCardUID, WriteData) per NT4H2421Gx / AN12196
- boltcard.py: the same write/wipe sequence as bolt-card-programmer
  (NDEF URL, SDM 40 00E0 C1 FF12, keys 1-4 then 0, key version 1)
  plus local p/c verification identical to the extension's nxp424.py
- cli.py: readers / read / write / wipe, keys backed up before any
  card mutation, uid cross-check on wipe

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-09-20 21:41:24 +02:00
commit 7d35d3e2c7
10 changed files with 1772 additions and 0 deletions

40
pyproject.toml Normal file
View file

@ -0,0 +1,40 @@
[project]
name = "boltcard-writer"
version = "0.1.0"
description = "Write, verify and wipe Bolt Cards (NTAG 424 DNA) from a Linux PC with a USB NFC reader, straight from an LNbits auth link"
readme = "README.md"
license = "MIT"
requires-python = ">=3.11"
dependencies = [
"click>=8.1",
"cryptography>=42",
]
[project.scripts]
boltcard-writer = "boltcard_writer.cli:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/boltcard_writer"]
[dependency-groups]
dev = [
"pytest>=8",
"ruff>=0.5",
]
[tool.ruff]
line-length = 140
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP"]
[tool.pytest.ini_options]
testpaths = ["tests"]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["E501"]