diff --git a/doc/user/app-openpgp.pdf b/doc/user/app-openpgp.pdf index ccab45b..5e2ea57 100644 Binary files a/doc/user/app-openpgp.pdf and b/doc/user/app-openpgp.pdf differ diff --git a/doc/user/app-openpgp.rst b/doc/user/app-openpgp.rst index d665220..48ac27d 100644 --- a/doc/user/app-openpgp.rst +++ b/doc/user/app-openpgp.rst @@ -1562,6 +1562,7 @@ The tool usage is the following: | ``--user-pin PIN User PIN (if pinpad not used)`` | ``--restore Perform a Restore instead of Backup`` | ``--file FILE Backup/Restore file (default is 'gpg_backup')`` + | ``--seed-key After Restore, regenerate all keys, based on seed mode`` | | ``Keys restore is only possible with SEED mode...`` @@ -1571,12 +1572,11 @@ To perform a backup, simply use the tool like this: | ``Connect to card 'Ledger'...`` | ``Configuration saved in file 'gpg_backup'.`` -Once the configuration is restored, just use the previous tool to re-generate the seeded keys: +To *restore* a backup, simply use the tool like this: - | ``./gpgcli.py --user-pin 123456 --adm-pin 12345678 --seed-key`` + | ``$ ./backup.py --restore --adm-pin 12345678 --user-pin 123456 --seed-key`` | ``Connect to card 'Ledger'...`` - | ``Verify PINs...`` - | ``Get card info...`` + | ``Configuration saved in file 'gpg_backup'.`` Annexes ======= diff --git a/manual-tests/manual.sh b/manual-tests/manual.sh index d882da9..082cd9b 100755 --- a/manual-tests/manual.sh +++ b/manual-tests/manual.sh @@ -62,6 +62,16 @@ init() { echo enable-pinpad-varlen echo card-timeout 1 } > "${dir}/scdaemon.conf" + + if [[ ${EXPERT} == true ]]; then + { + echo log-file /tmp/scd.log + echo debug-level guru + echo debug-all + } >> "${dir}/scdaemon.conf" + fi + + gpgconf --reload scdaemon } #=============================================================================== diff --git a/pytools/backup.py b/pytools/backup.py index bdf7268..9a4e66e 100755 --- a/pytools/backup.py +++ b/pytools/backup.py @@ -34,7 +34,7 @@ def get_argparser() -> Namespace: formatter_class=RawTextHelpFormatter ) parser.add_argument("--reader", type=str, default="Ledger", - help="PCSC reader name (default is '%(default)s')") + help="PCSC reader name (default is '%(default)s') or 'speculos'") parser.add_argument("--slot", type=int, choices=range(1, 4), help="Select slot (1 to 3)") @@ -51,6 +51,9 @@ def get_argparser() -> Namespace: parser.add_argument("--file", type=str, default="gpg_backup", help="Backup/Restore file (default is '%(default)s')") + parser.add_argument("--seed-key", action="store_true", + help="After Restore, regenerate all keys, based on seed mode") + return parser.parse_args() @@ -95,6 +98,10 @@ def entrypoint() -> None: if args.restore: gpgcard.restore(args.file) print(f"Configuration restored from file '{args.file}'.") + + if args.seed_key: + gpgcard.seed_key() + else: gpgcard.backup(args.file) print(f"Configuration saved in file '{args.file}'.") diff --git a/pytools/gpgapp/gpgcard.py b/pytools/gpgapp/gpgcard.py index 665a353..9e08884 100644 --- a/pytools/gpgapp/gpgcard.py +++ b/pytools/gpgapp/gpgcard.py @@ -24,14 +24,12 @@ from dataclasses import dataclass from Crypto.PublicKey.RSA import construct -# pylint: disable=import-error -from smartcard.System import readers # type: ignore -from smartcard.pcsc import PCSCReader # type: ignore -from smartcard import CardConnectionDecorator # type: ignore -# pylint: enable=import-error from gpgapp.gpgcmd import DataObject, ErrorCodes, KeyTypes, PassWord, PubkeyAlgo # type: ignore from gpgapp.gpgcmd import KEY_OPERATIONS, KEY_TEMPLATES, USER_SALUTATION # type: ignore +# pylint: disable=import-error +from ledgercomm import Transport # type: ignore +# pylint: enable=import-error APDU_MAX_SIZE: int = 0xFE APDU_CHAINING_MODE: int = 0x10 @@ -143,7 +141,7 @@ def reset(self): class GPGCard() : def __init__(self) -> None: self.log: bool = False - self.connection: CardConnectionDecorator = None + self.transport: Transport = None self.slot_current: bytes = b"\x00" self.slot_config: bytes = bytes(3) self.data: CardInfo = CardInfo() @@ -156,21 +154,17 @@ def connect(self, device: str) -> None: device (str): Reader device name """ - allreaders: list = readers() - for elt in allreaders: - if str(elt).startswith(device): - reader: PCSCReader.PCSCReader = elt - self.connection = reader.createConnection() - self.connection.connect() - return + if device == "speculos": + self.transport = Transport("tcp", server="127.0.0.1", port=9999, debug=False) + else: + self.transport = Transport("hid") print("") - raise GPGCardExcpetion(ErrorCodes.ERR_INTERNAL, "No Reader detected!") def disconnect(self): """Connect from the selected Reader""" - return self.connection.disconnect() + self.transport.close() ############### LOG interface ############### @@ -1236,8 +1230,9 @@ def _transmit(self, data: bytes, long_resp: bool = False) -> Tuple[bytes, int, i """ self.add_log("send", data) - resp, sw1, sw2 = self.connection.transmit(list(data)) - sw = (sw1 << 8) | sw2 + sw, resp = self.transport.exchange_raw(data) + sw1 = (sw >> 8) & 0xFF + sw2 = sw & 0xFF self.add_log("recv", resp, sw) if sw != ErrorCodes.ERR_SUCCESS and not long_resp: raise GPGCardExcpetion(sw, "") diff --git a/pytools/gpgcli.py b/pytools/gpgcli.py index 797fb12..4d85839 100755 --- a/pytools/gpgcli.py +++ b/pytools/gpgcli.py @@ -38,7 +38,7 @@ def get_argparser() -> Namespace: parser.add_argument("--info", action="store_true", help="Get and display card information") parser.add_argument("--reader", type=str, default="Ledger", - help="PCSC reader name (default is '%(default)s')") + help="PCSC reader name (default is '%(default)s') or 'speculos'") parser.add_argument("--apdu", action="store_true", help="Log APDU exchange") parser.add_argument("--slot", type=int, choices=range(1, 4), help="Select slot (1 to 3)") diff --git a/pytools/requirements.txt b/pytools/requirements.txt index 2baafa8..0c51a3d 100644 --- a/pytools/requirements.txt +++ b/pytools/requirements.txt @@ -1,2 +1,2 @@ -pyscard pycryptodome +ledgercomm