Skip to content

Commit

Permalink
Merge pull request #98 from LedgerHQ/cev/fix_backup_restore
Browse files Browse the repository at this point in the history
Cev/fix backup restore
  • Loading branch information
cedelavergne-ledger authored Mar 1, 2024
2 parents aa8c720 + 18152ca commit d080e4f
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 39 deletions.
Binary file modified doc/user/app-openpgp.pdf
Binary file not shown.
75 changes: 42 additions & 33 deletions doc/user/app-openpgp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,9 @@ Seed mode
When generating new keys on the device, those keys can be generated randomly or in a deterministic way.
The deterministic way is specified in [GPGADD]_.
The current mode is displayed in the first sub menu.
To activate the seeded mode select *ON*, to deactivate the seeded mode select *OFF*.
To activate the seed mode select *ON*, to deactivate the seed mode select *OFF*.

When the application starts, the seeded mode is always set to *ON*
When the application starts, the seed mode is always set to *ON*

PIN mode
~~~~~~~~
Expand Down Expand Up @@ -1059,6 +1059,9 @@ The backup/restore tool is located in ``pytools`` directory.

See `Tools` later in this document for the tools details and usage.

Note: The keys backup will work *only* if the SEED Mode is enabled!


Restore without backup
~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -1375,41 +1378,12 @@ Tools

There are 2 tools provided:

- ``backup.py``: Backup and Restore of the configuration
- ``gpgcli.py``: General test tool
- ``backup.py``: Backup and Restore of the configuration

If you encounter an error when performing the backup/restore, reload your scdaemon with
``gpgconf --reload scdaemon``

Backup tool
-----------

The tool usage is the following:

| ``$ ./backup.py --help``
| ``usage: backup.py [-h] [--reader READER] [--slot {1,2,3}] [--pinpad] --adm-pin PIN --user-pin PIN [--restore] [--file FILE]``
|
| ``Backup/Restore OpenPGP App configuration``
|
| ``options:``
| ``-h, --help show this help message and exit``
| ``--reader READER PCSC reader name (default is 'Ledger')``
| ``--slot {1,2,3} Select slot (1 to 3)``
| ``--pinpad PIN validation will be delegated to pinpad``
| ``--adm-pin PIN Admin PIN (if pinpad not used)``
| ``--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')``
|
| ``Keys restore is only possible with SEED mode...``
To perform a backup, simply use the tool like this:

| ``$ ./backup.py --adm-pin 12345678 --user-pin 123456``
| ``Connect to card 'Ledger'...``
| ``Configuration saved in file 'gpg_backup'.``

Test command line tool
----------------------

Expand Down Expand Up @@ -1482,7 +1456,7 @@ Sample output to get Card information:
| ``- Manufacturer : 2C97``
| ``- Serial : E1A67CBF``
| ``=============== Historical Bytes ===============``
| ``- historical bytes : 0031c573c001800790000000000000``
| ``- historical bytes : 0031c573c001800000000000059000``
| ``=============== Max Extended Length ===============``
| ``- Command : 254``
| ``- Response : 254``
Expand Down Expand Up @@ -1568,6 +1542,41 @@ Sample output to get Card information:
| ``* Private key size: 1040``

Backup tool
-----------

The tool usage is the following:

| ``$ ./backup.py --help``
| ``usage: backup.py [-h] [--reader READER] [--slot {1,2,3}] [--pinpad] --adm-pin PIN --user-pin PIN [--restore] [--file FILE]``
|
| ``Backup/Restore OpenPGP App configuration``
|
| ``options:``
| ``-h, --help show this help message and exit``
| ``--reader READER PCSC reader name (default is 'Ledger')``
| ``--slot {1,2,3} Select slot (1 to 3)``
| ``--pinpad PIN validation will be delegated to pinpad``
| ``--adm-pin PIN Admin PIN (if pinpad not used)``
| ``--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')``
|
| ``Keys restore is only possible with SEED mode...``
To perform a backup, simply use the tool like this:

| ``$ ./backup.py --adm-pin 12345678 --user-pin 123456``
| ``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:

| ``./gpgcli.py --user-pin 123456 --adm-pin 12345678 --seed-key``
| ``Connect to card 'Ledger'...``
| ``Verify PINs...``
| ``Get card info...``
Annexes
=======

Expand Down
44 changes: 38 additions & 6 deletions pytools/gpgapp/gpgcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ def backup(self, file_name: str) -> None:
self.get_all()
with open(file_name, mode="w+b") as f:
pickle.dump(
(self.data.AID, self.data.PW_status, self.data.rsa_pub_exp,
(self.data.AID, self.data.PW_status, self.data.rsa_pub_exp, self.data.digital_counter,
self.data.private_01, self.data.private_02,
self.data.private_03, self.data.private_04,
self.data.name, self.data.login, self.data.salutation, self.data.url, self.data.lang,
Expand All @@ -351,7 +351,7 @@ def restore(self, file_name: str) -> None:
"""

with open(file_name, mode="r+b") as f:
(self.data.AID, self.data.PW_status, self.data.rsa_pub_exp,
(self.data.AID, self.data.PW_status, self.data.rsa_pub_exp, self.data.digital_counter,
self.data.private_01, self.data.private_02, self.data.private_03, self.data.private_04,
self.data.name, self.data.login, self.data.salutation, self.data.url, self.data.lang,
self.data.sig.key, self.data.sig.uif, self.data.sig.attribute, self.data.sig.date,
Expand All @@ -373,7 +373,11 @@ def restore(self, file_name: str) -> None:
self._put_data(DataObject.DO_LOGIN, self.data.login.encode("utf-8"))
self._put_data(DataObject.DO_CARD_LANG, self.data.lang.encode("utf-8"))
self._put_data(DataObject.DO_URL, self.data.url.encode("utf-8"))
self._put_data(DataObject.DO_CARD_SALUTATION, bytes.fromhex(USER_SALUTATION[self.data.salutation]))
if len(self.data.salutation) == 0:
self._put_data(DataObject.DO_CARD_SALUTATION, b'\x30')
else:
self._put_data(DataObject.DO_CARD_SALUTATION,
bytes.fromhex(USER_SALUTATION[self.data.salutation]))

self._put_data(DataObject.DO_SIG_ATTR, self.data.sig.attribute)
self._put_data(DataObject.DO_DEC_ATTR, self.data.dec.attribute)
Expand All @@ -384,7 +388,35 @@ def restore(self, file_name: str) -> None:
self._put_data(DataObject.DO_UIF_AUT, self.data.aut.uif.to_bytes(2, "little"))

self._put_data(DataObject.DO_SIG_COUNT, self.data.digital_counter.to_bytes(4, "big"))
self._put_data(DataObject.CMD_RSA_EXP, self.data.rsa_pub_exp.to_bytes(4, "big"))
self._put_data(DataObject.CMD_RSA_EXP, self.data.rsa_pub_exp.to_bytes(4, "little"))

self._put_data(DataObject.DO_CERT, self.data.aut.cert.encode("utf-8"))
self._put_data(DataObject.DO_CERT, self.data.dec.cert.encode("utf-8"))
self._put_data(DataObject.DO_CERT, self.data.sig.cert.encode("utf-8"))

self._put_data(DataObject.DO_CA_FINGERPRINT_WR_SIG, self.data.sig.ca_fingerprint)
self._put_data(DataObject.DO_FINGERPRINT_WR_SIG, self.data.sig.fingerprint)
date = str(self.data.sig.date)
dt = datetime.strptime(date, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
bdate = int(dt.timestamp()).to_bytes(4, "big")
self._put_data(DataObject.DO_DATES_WR_SIG, bdate)
self._put_data(DataObject.DO_SIG_KEY, self.data.sig.key)

self._put_data(DataObject.DO_CA_FINGERPRINT_WR_DEC, self.data.dec.ca_fingerprint)
self._put_data(DataObject.DO_FINGERPRINT_WR_DEC, self.data.dec.fingerprint)
date = str(self.data.dec.date)
dt = datetime.strptime(date, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
bdate = int(dt.timestamp()).to_bytes(4, "big")
self._put_data(DataObject.DO_DATES_WR_DEC, bdate)
self._put_data(DataObject.DO_DEC_KEY, self.data.dec.key)

self._put_data(DataObject.DO_CA_FINGERPRINT_WR_AUT, self.data.aut.ca_fingerprint)
self._put_data(DataObject.DO_FINGERPRINT_WR_AUT, self.data.aut.fingerprint)
date = str(self.data.aut.date)
dt = datetime.strptime(date, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
bdate = int(dt.timestamp()).to_bytes(4, "big")
self._put_data(DataObject.DO_DATES_WR_AUT, bdate)
self._put_data(DataObject.DO_AUT_KEY, self.data.aut.key)


def export_pub_key(self, pubkey: dict, file_name: str) -> None:
Expand Down Expand Up @@ -810,13 +842,13 @@ def get_rsa_pub_exp(self) -> int:
return self.data.rsa_pub_exp

def get_key_cert(self, key: str) -> str:
"""Get key fingerprint
"""Get key Certificate
Args:
key (str): Key type (SIG, DC, AUT)
Return:
Key Fingerprint
Key Certificate
"""

return self._get_key_object(key).cert
Expand Down

0 comments on commit d080e4f

Please sign in to comment.