├── .clang-format ├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build_and_functional_tests.yml │ ├── codeql_checks.yml │ ├── coding_style_checks.yml │ ├── guidelines_enforcer.yml │ ├── misspellings_checks.yml │ ├── python_client_checks.yml │ ├── python_tool_checks.yml │ └── unit_tests.yml ├── .gitignore ├── .mdl.rb ├── .mdlrc ├── .pre-commit-config.yaml ├── LICENSE ├── Makefile ├── README.md ├── doc ├── LogoLedger.png ├── developer │ ├── gpgcard-addon.pdf │ ├── gpgcard-addon.rst │ ├── quick-test.md │ ├── slots.png │ └── template.latex ├── generate.sh ├── specification │ └── OpenPGP-smart-card-application.pdf └── user │ ├── 0001-plist.patch │ ├── app-openpgp.pdf │ ├── app-openpgp.rst │ ├── pin_abort.png │ ├── pin_cancel.png │ ├── pin_confirm.png │ ├── pin_entry.png │ ├── pin_validate.png │ └── template.latex ├── glyphs ├── gpg_16px.gif └── gpg_64px.gif ├── icons ├── gpg_14px.gif ├── gpg_16px.gif ├── gpg_32px.gif └── gpg_40px.gif ├── ledger_app.toml ├── manual-tests └── manual.sh ├── pytools ├── backup.py ├── gpgapp │ ├── __init__.py │ ├── gpgcard.py │ └── gpgcmd.py ├── gpgcli.py ├── requirements.txt └── setup.cfg ├── src ├── gpg_api.h ├── gpg_challenge.c ├── gpg_data.c ├── gpg_dispatch.c ├── gpg_gen.c ├── gpg_init.c ├── gpg_io.c ├── gpg_main.c ├── gpg_mse.c ├── gpg_pin.c ├── gpg_pso.c ├── gpg_select.c ├── gpg_types.h ├── gpg_ux.c ├── gpg_ux.h ├── gpg_ux_msg.c ├── gpg_ux_msg.h ├── gpg_ux_nanos.c ├── gpg_ux_nanox.c ├── gpg_ux_nbgl.c ├── gpg_vars.c └── gpg_vars.h ├── tests ├── application_client │ ├── __init__.py │ ├── app_def.py │ ├── command_sender.py │ └── response_unpacker.py ├── conftest.py ├── requirements.txt ├── setup.cfg ├── snapshots │ ├── flex │ │ ├── test_menu_settings │ │ │ ├── 00000.png │ │ │ ├── 00001.png │ │ │ ├── 00002.png │ │ │ ├── 00003.png │ │ │ ├── 00004.png │ │ │ ├── 00005.png │ │ │ ├── 00006.png │ │ │ ├── 00007.png │ │ │ ├── 00008.png │ │ │ ├── 00009.png │ │ │ ├── 00010.png │ │ │ ├── 00011.png │ │ │ ├── 00012.png │ │ │ ├── 00013.png │ │ │ ├── 00014.png │ │ │ └── 00015.png │ │ ├── test_menu_slot │ │ │ ├── 00000.png │ │ │ └── 00001.png │ │ ├── test_verify_confirm_accepted │ │ │ └── 00000.png │ │ └── test_verify_confirm_refused │ │ │ └── 00000.png │ ├── nanos │ │ ├── test_menu_settings │ │ │ ├── 00000.png │ │ │ ├── 00001.png │ │ │ ├── 00002.png │ │ │ ├── 00003.png │ │ │ ├── 00004.png │ │ │ ├── 00005.png │ │ │ ├── 00006.png │ │ │ ├── 00007.png │ │ │ ├── 00008.png │ │ │ ├── 00009.png │ │ │ ├── 00010.png │ │ │ ├── 00011.png │ │ │ ├── 00012.png │ │ │ ├── 00013.png │ │ │ ├── 00014.png │ │ │ ├── 00015.png │ │ │ ├── 00016.png │ │ │ ├── 00017.png │ │ │ ├── 00018.png │ │ │ ├── 00019.png │ │ │ ├── 00020.png │ │ │ ├── 00021.png │ │ │ ├── 00022.png │ │ │ ├── 00023.png │ │ │ ├── 00024.png │ │ │ ├── 00025.png │ │ │ ├── 00026.png │ │ │ ├── 00027.png │ │ │ ├── 00028.png │ │ │ ├── 00029.png │ │ │ ├── 00030.png │ │ │ ├── 00031.png │ │ │ ├── 00032.png │ │ │ ├── 00033.png │ │ │ ├── 00034.png │ │ │ ├── 00035.png │ │ │ ├── 00036.png │ │ │ ├── 00037.png │ │ │ ├── 00038.png │ │ │ ├── 00039.png │ │ │ ├── 00040.png │ │ │ ├── 00041.png │ │ │ ├── 00042.png │ │ │ ├── 00043.png │ │ │ ├── 00044.png │ │ │ ├── 00045.png │ │ │ ├── 00046.png │ │ │ ├── 00047.png │ │ │ ├── 00048.png │ │ │ ├── 00049.png │ │ │ ├── 00050.png │ │ │ ├── 00051.png │ │ │ ├── 00052.png │ │ │ └── 00053.png │ │ ├── test_verify_confirm_accepted │ │ │ └── 00000.png │ │ └── test_verify_confirm_refused │ │ │ └── 00000.png │ ├── nanosp │ │ ├── test_menu_settings │ │ │ ├── 00000.png │ │ │ ├── 00001.png │ │ │ ├── 00002.png │ │ │ ├── 00003.png │ │ │ ├── 00004.png │ │ │ ├── 00005.png │ │ │ ├── 00006.png │ │ │ ├── 00007.png │ │ │ ├── 00008.png │ │ │ ├── 00009.png │ │ │ ├── 00010.png │ │ │ ├── 00011.png │ │ │ ├── 00012.png │ │ │ ├── 00013.png │ │ │ ├── 00014.png │ │ │ ├── 00015.png │ │ │ ├── 00016.png │ │ │ ├── 00017.png │ │ │ ├── 00018.png │ │ │ ├── 00019.png │ │ │ ├── 00020.png │ │ │ ├── 00021.png │ │ │ ├── 00022.png │ │ │ ├── 00023.png │ │ │ ├── 00024.png │ │ │ ├── 00025.png │ │ │ ├── 00026.png │ │ │ ├── 00027.png │ │ │ ├── 00028.png │ │ │ ├── 00029.png │ │ │ ├── 00030.png │ │ │ ├── 00031.png │ │ │ ├── 00032.png │ │ │ ├── 00033.png │ │ │ ├── 00034.png │ │ │ ├── 00035.png │ │ │ ├── 00036.png │ │ │ ├── 00037.png │ │ │ ├── 00038.png │ │ │ ├── 00039.png │ │ │ ├── 00040.png │ │ │ ├── 00041.png │ │ │ ├── 00042.png │ │ │ └── 00043.png │ │ ├── test_menu_slot │ │ │ ├── 00000.png │ │ │ ├── 00001.png │ │ │ ├── 00002.png │ │ │ ├── 00003.png │ │ │ ├── 00004.png │ │ │ ├── 00005.png │ │ │ ├── 00006.png │ │ │ ├── 00007.png │ │ │ └── 00008.png │ │ ├── test_verify_confirm_accepted │ │ │ ├── 00000.png │ │ │ ├── 00001.png │ │ │ └── 00002.png │ │ └── test_verify_confirm_refused │ │ │ ├── 00000.png │ │ │ └── 00001.png │ ├── nanox │ │ ├── test_menu_settings │ │ │ ├── 00000.png │ │ │ ├── 00001.png │ │ │ ├── 00002.png │ │ │ ├── 00003.png │ │ │ ├── 00004.png │ │ │ ├── 00005.png │ │ │ ├── 00006.png │ │ │ ├── 00007.png │ │ │ ├── 00008.png │ │ │ ├── 00009.png │ │ │ ├── 00010.png │ │ │ ├── 00011.png │ │ │ ├── 00012.png │ │ │ ├── 00013.png │ │ │ ├── 00014.png │ │ │ ├── 00015.png │ │ │ ├── 00016.png │ │ │ ├── 00017.png │ │ │ ├── 00018.png │ │ │ ├── 00019.png │ │ │ ├── 00020.png │ │ │ ├── 00021.png │ │ │ ├── 00022.png │ │ │ ├── 00023.png │ │ │ ├── 00024.png │ │ │ ├── 00025.png │ │ │ ├── 00026.png │ │ │ ├── 00027.png │ │ │ ├── 00028.png │ │ │ ├── 00029.png │ │ │ ├── 00030.png │ │ │ ├── 00031.png │ │ │ ├── 00032.png │ │ │ ├── 00033.png │ │ │ ├── 00034.png │ │ │ ├── 00035.png │ │ │ ├── 00036.png │ │ │ ├── 00037.png │ │ │ ├── 00038.png │ │ │ ├── 00039.png │ │ │ ├── 00040.png │ │ │ ├── 00041.png │ │ │ ├── 00042.png │ │ │ └── 00043.png │ │ ├── test_menu_slot │ │ │ ├── 00000.png │ │ │ ├── 00001.png │ │ │ ├── 00002.png │ │ │ ├── 00003.png │ │ │ ├── 00004.png │ │ │ ├── 00005.png │ │ │ ├── 00006.png │ │ │ ├── 00007.png │ │ │ └── 00008.png │ │ ├── test_verify_confirm_accepted │ │ │ ├── 00000.png │ │ │ ├── 00001.png │ │ │ └── 00002.png │ │ └── test_verify_confirm_refused │ │ │ ├── 00000.png │ │ │ └── 00001.png │ └── stax │ │ ├── test_menu_settings │ │ ├── 00000.png │ │ ├── 00001.png │ │ ├── 00002.png │ │ ├── 00003.png │ │ ├── 00004.png │ │ ├── 00005.png │ │ ├── 00006.png │ │ ├── 00007.png │ │ ├── 00008.png │ │ ├── 00009.png │ │ ├── 00010.png │ │ ├── 00011.png │ │ ├── 00012.png │ │ ├── 00013.png │ │ └── 00014.png │ │ ├── test_menu_slot │ │ ├── 00000.png │ │ ├── 00001.png │ │ └── 00002.png │ │ ├── test_verify_confirm_accepted │ │ └── 00000.png │ │ └── test_verify_confirm_refused │ │ └── 00000.png ├── test_cipher.py ├── test_menus.py ├── test_password.py ├── test_seed.py ├── test_sign.py ├── test_slot.py ├── test_template.py ├── test_version.py ├── usage.md └── utils.py └── unit-tests ├── CMakeLists.txt ├── README.md ├── gen_coverage.sh ├── mocks ├── bolos_target.h ├── cx.h ├── lcx_sha3.h ├── ledger_assert.h ├── mocks.c ├── offsets.h ├── os.h ├── os_utils.h ├── usbd_ccid_if.h └── ux.h └── src └── test_io.c /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | Language: Cpp 5 | ColumnLimit: 100 6 | PointerAlignment: Right 7 | AlignAfterOpenBracket: Align 8 | AlignConsecutiveMacros: true 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | SortIncludes: false 11 | SpaceAfterCStyleCast: true 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowAllArgumentsOnNextLine: false 14 | AllowShortBlocksOnASingleLine: Never 15 | AllowShortFunctionsOnASingleLine: None 16 | BinPackArguments: false 17 | BinPackParameters: false 18 | --- 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pdf binary 2 | *.png binary 3 | *.gif binary 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | # Checklist 4 | 5 | - [ ] App update process has been followed 6 | - [ ] Target branch is `develop` 7 | - [ ] Application version has been bumped 8 | 9 | 11 | -------------------------------------------------------------------------------- /.github/workflows/build_and_functional_tests.yml: -------------------------------------------------------------------------------- 1 | name: Build and run functional tests using ragger through reusable workflow 2 | 3 | # This workflow will build the app. 4 | # It calls a reusable workflow developed by Ledger's internal developer team to build the application and upload the 5 | # resulting binaries. 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | golden_run: 10 | type: choice 11 | required: true 12 | default: 'Raise an error (default)' 13 | description: CI behavior if the test snapshots are different than expected. 14 | options: 15 | - 'Raise an error (default)' 16 | - 'Open a PR' 17 | push: 18 | branches: 19 | - develop 20 | - master 21 | pull_request: 22 | 23 | jobs: 24 | build_application: 25 | name: Build application using the reusable workflow 26 | uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1 27 | with: 28 | upload_app_binaries_artifact: "compiled_app_binaries" 29 | 30 | ragger_tests: 31 | name: Run ragger tests using the reusable workflow 32 | needs: build_application 33 | uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1 34 | with: 35 | download_app_binaries_artifact: "compiled_app_binaries" 36 | regenerate_snapshots: ${{ inputs.golden_run == 'Open a PR' }} 37 | -------------------------------------------------------------------------------- /.github/workflows/codeql_checks.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - develop 8 | - master 9 | pull_request: 10 | # Excluded path: add the paths you want to ignore instead of deleting the workflow 11 | paths-ignore: 12 | - '.github/workflows/*.yml' 13 | - 'tests/*' 14 | 15 | jobs: 16 | analyse: 17 | name: Analyse 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | sdk: ["$NANOS_SDK", "$NANOX_SDK", "$NANOSP_SDK", "$STAX_SDK", "$FLEX_SDK"] 22 | # 'cpp' covers C and C++ 23 | language: ['cpp'] 24 | runs-on: ubuntu-latest 25 | container: 26 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest 27 | 28 | steps: 29 | - name: Clone 30 | uses: actions/checkout@v4 31 | 32 | - name: Initialize CodeQL 33 | uses: github/codeql-action/init@v3 34 | with: 35 | languages: ${{ matrix.language }} 36 | queries: security-and-quality 37 | 38 | # CodeQL will create the database during the compilation 39 | - name: Build 40 | run: | 41 | make BOLOS_SDK=${{ matrix.sdk }} 42 | 43 | - name: Perform CodeQL Analysis 44 | uses: github/codeql-action/analyze@v3 45 | -------------------------------------------------------------------------------- /.github/workflows/coding_style_checks.yml: -------------------------------------------------------------------------------- 1 | name: Run coding style check through reusable workflow 2 | 3 | # This workflow will run linting checks to ensure a level of uniformization among all Ledger applications. 4 | # 5 | # The presence of this workflow is mandatory as a minimal level of linting is required. 6 | # You are however free to modify the content of the .clang-format file and thus the coding style of your application. 7 | # We simply ask you to not diverge too much from the linting of the Boilerplate application. 8 | 9 | on: 10 | workflow_dispatch: 11 | push: 12 | branches: 13 | - develop 14 | - master 15 | pull_request: 16 | 17 | jobs: 18 | check_linting: 19 | name: Check linting using the reusable workflow 20 | uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_lint.yml@v1 21 | with: 22 | source: './src' 23 | extensions: 'h,c' 24 | version: 12 25 | -------------------------------------------------------------------------------- /.github/workflows/guidelines_enforcer.yml: -------------------------------------------------------------------------------- 1 | name: Ensure compliance with Ledger guidelines 2 | 3 | # This workflow is mandatory in all applications 4 | # It calls a reusable workflow guidelines_enforcer developed by Ledger's internal developer team. 5 | # The successful completion of the reusable workflow is a mandatory step for an app to be available on the Ledger 6 | # application store. 7 | # 8 | # More information on the guidelines can be found in the repository: 9 | # LedgerHQ/ledger-app-workflows/ 10 | 11 | on: 12 | workflow_dispatch: 13 | push: 14 | branches: 15 | - develop 16 | - master 17 | pull_request: 18 | 19 | jobs: 20 | guidelines_enforcer: 21 | name: Call Ledger guidelines_enforcer 22 | uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1 23 | -------------------------------------------------------------------------------- /.github/workflows/misspellings_checks.yml: -------------------------------------------------------------------------------- 1 | name: Misspellings checks 2 | 3 | # This workflow performs some misspelling checks on the repository 4 | # It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked 5 | # applications. 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - develop 12 | - master 13 | pull_request: 14 | 15 | jobs: 16 | misspell: 17 | name: Check misspellings 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Clone 21 | uses: actions/checkout@v4 22 | 23 | - name: Check misspellings 24 | uses: codespell-project/actions-codespell@v2 25 | with: 26 | builtin: clear,rare 27 | check_filenames: true 28 | ignore_words_list: ontop 29 | -------------------------------------------------------------------------------- /.github/workflows/python_client_checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks on the Python client 2 | 3 | # This workflow performs some checks on the Python client used by the Application tests 4 | # It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked 5 | # applications. 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - develop 12 | - master 13 | pull_request: 14 | 15 | jobs: 16 | lint: 17 | name: Client linting 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Clone 21 | uses: actions/checkout@v4 22 | - name: Installing PIP dependencies 23 | run: | 24 | pip install pylint 25 | pip install -r tests/requirements.txt 26 | - name: Lint Python code 27 | run: pylint --rc tests/setup.cfg tests/application_client/ 28 | 29 | mypy: 30 | name: Type checking 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Clone 34 | uses: actions/checkout@v4 35 | - name: Installing PIP dependencies 36 | run: | 37 | pip install mypy 38 | pip install -r tests/requirements.txt 39 | - name: Mypy type checking 40 | run: mypy tests/application_client/ 41 | -------------------------------------------------------------------------------- /.github/workflows/python_tool_checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks on the Tools client 2 | 3 | # This workflow performs some checks on the Python client used by the cli tool 4 | # It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked 5 | # applications. 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - develop 12 | - master 13 | pull_request: 14 | 15 | jobs: 16 | lint: 17 | name: Client linting 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Clone 21 | uses: actions/checkout@v4 22 | - name: Installing PIP dependencies 23 | run: | 24 | sudo apt-get update && sudo apt-get install -y libpcsclite-dev 25 | pip install pylint 26 | pip install -r pytools/requirements.txt 27 | - name: Lint Python code 28 | run: pylint --rc pytools/setup.cfg pytools/ 29 | 30 | mypy: 31 | name: Type checking 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Clone 35 | uses: actions/checkout@v4 36 | - name: Installing PIP dependencies 37 | run: | 38 | sudo apt-get update && sudo apt-get install -y libpcsclite-dev 39 | pip install mypy 40 | pip install -r pytools/requirements.txt 41 | - name: Mypy type checking 42 | run: mypy pytools/ 43 | -------------------------------------------------------------------------------- /.github/workflows/unit_tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit testing with Codecov coverage checking 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - develop 8 | - master 9 | pull_request: 10 | 11 | jobs: 12 | job_unit_test: 13 | name: Unit test 14 | runs-on: ubuntu-latest 15 | container: 16 | image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest 17 | 18 | steps: 19 | - name: Clone 20 | uses: actions/checkout@v4 21 | 22 | - name: Build unit tests 23 | run: | 24 | cd unit-tests/ 25 | cmake -Bbuild -H. && make -C build && make -C build test 26 | 27 | - name: Generate code coverage 28 | run: | 29 | cd unit-tests/ 30 | lcov --directory . -b "$(realpath build/)" --capture --initial -o coverage.base && \ 31 | lcov --rc lcov_branch_coverage=1 --directory . -b "$(realpath build/)" --capture -o coverage.capture && \ 32 | lcov --directory . -b "$(realpath build/)" --add-tracefile coverage.base --add-tracefile coverage.capture -o coverage.info && \ 33 | lcov --directory . -b "$(realpath build/)" --remove coverage.info '*/unit-tests/*' -o coverage.info && \ 34 | genhtml coverage.info -o coverage 35 | 36 | - uses: actions/upload-artifact@v4 37 | with: 38 | name: code-coverage 39 | path: unit-tests/coverage 40 | 41 | - name: Install codecov dependencies 42 | run: apt install --no-install-recommends -y curl gpg 43 | 44 | - name: Upload to codecov.io 45 | uses: codecov/codecov-action@v5 46 | env: 47 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 48 | with: 49 | files: ./unit-tests/coverage.info 50 | flags: unittests 51 | name: codecov-app-openpgp 52 | fail_ci_if_error: true 53 | verbose: true 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compilation files of the application 2 | build/ 3 | 4 | # Legacy compilation output 5 | bin/ 6 | debug/ 7 | 8 | # Temporary directory with snapshots taken during test runs 9 | tests/snapshots-tmp/ 10 | 11 | # manual-tests 12 | manual-tests/foo* 13 | manual-tests/gnupg 14 | 15 | # Unit tests and code coverage 16 | unit-tests/build/ 17 | unit-tests/coverage/ 18 | unit-tests/coverage.info 19 | 20 | # Fuzzing 21 | fuzzing/build/ 22 | 23 | # Python 24 | *.pyc[cod] 25 | *.egg 26 | __pycache__/ 27 | *.egg-info/ 28 | .eggs/ 29 | .python-version 30 | venv/ 31 | gpg_backup 32 | 33 | # Doxygen 34 | doc/html 35 | doc/latex 36 | 37 | # Virtual env for sideload (macOS and Windows) 38 | ledger/ 39 | -------------------------------------------------------------------------------- /.mdl.rb: -------------------------------------------------------------------------------- 1 | # Style file for mdl 2 | # https://github.com/markdownlint/markdownlint/blob/main/docs/creating_styles.md 3 | 4 | # Include all rules 5 | all 6 | 7 | # Disable specific rules 8 | #exclude_rule 'MD012' 9 | 10 | # Update rules configuration 11 | rule 'MD013', :line_length => 120 12 | -------------------------------------------------------------------------------- /.mdlrc: -------------------------------------------------------------------------------- 1 | # markdownlint config file 2 | 3 | # Use custom style file 4 | style "#{File.dirname(__FILE__)}/.mdl.rb" 5 | 6 | # MD005 - Inconsistent indentation for list items at the same level 7 | # MD007 - Unordered list indentation 8 | # MD014 - Dollar signs used before commands without showing output 9 | # MD024 - Multiple headers with the same content 10 | # MD041 - First line in file should be a top level header 11 | rules "~MD005,~MD007,~MD014,~MD024,~MD041" 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # To install hooks, run: 2 | # pre-commit install --hook-type pre-commit 3 | # pre-commit install --hook-type commit-msg 4 | 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v5.0.0 8 | hooks: 9 | - id: trailing-whitespace 10 | - id: end-of-file-fixer 11 | - id: mixed-line-ending 12 | - id: check-added-large-files 13 | - id: check-merge-conflict 14 | - id: check-case-conflict 15 | 16 | - repo: https://github.com/codespell-project/codespell 17 | rev: v2.3.0 18 | hooks: 19 | - id: codespell 20 | args: ['--ignore-words-list', 'ontop'] 21 | 22 | - repo: https://github.com/pre-commit/mirrors-clang-format 23 | rev: v12.0.1 24 | hooks: 25 | - id: clang-format 26 | types_or: [c] 27 | 28 | - repo: https://github.com/Mateusz-Grzelinski/actionlint-py 29 | rev: v1.7.6.22 30 | hooks: 31 | - id: actionlint 32 | types_or: [yaml] 33 | args: [-shellcheck='' -pyflakes=''] 34 | 35 | - repo: https://github.com/markdownlint/markdownlint 36 | rev: v0.12.0 37 | hooks: 38 | - id: markdownlint 39 | types_or: [markdown] 40 | 41 | - repo: https://github.com/PyCQA/pylint 42 | rev: v3.3.3 43 | hooks: 44 | - id: pylint 45 | name: Check python Client 46 | language: system 47 | types: [python] 48 | args: ['--jobs=0', '--rcfile=tests/setup.cfg'] 49 | files: '^tests\/.*$' 50 | 51 | - id: pylint 52 | name: Check python Tool 53 | language: system 54 | types: [python] 55 | args: ['--jobs=0', '--rcfile=pytools/setup.cfg'] 56 | files: '^pytools\/.*$' 57 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # **************************************************************************** 2 | # Ledger App OpenPGP 3 | # (c) 2024 Ledger SAS. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # **************************************************************************** 17 | 18 | ifeq ($(BOLOS_SDK),) 19 | $(error Environment variable BOLOS_SDK is not set) 20 | endif 21 | 22 | include $(BOLOS_SDK)/Makefile.defines 23 | 24 | ######################################## 25 | # Mandatory configuration # 26 | ######################################## 27 | # Application name 28 | APPNAME = OpenPGP 29 | 30 | # Application version 31 | APPVERSION_M = 2 32 | APPVERSION_N = 4 33 | APPVERSION_P = 0 34 | APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" 35 | 36 | SPECVERSION:="3.3.1" 37 | DEFINES += SPEC_VERSION=$(SPECVERSION) 38 | 39 | # Application source files 40 | APP_SOURCE_PATH += src 41 | APP_SOURCE_FILES += $(BOLOS_SDK)/lib_cxng/src/cx_rsa.c 42 | APP_SOURCE_FILES += $(BOLOS_SDK)/lib_cxng/src/cx_pkcs1.c 43 | APP_SOURCE_FILES += ${BOLOS_SDK}/lib_cxng/src/cx_ram.c 44 | 45 | INCLUDES_PATH += $(BOLOS_SDK)/lib_cxng/src 46 | 47 | # Application icons following guidelines: 48 | # https://developers.ledger.com/docs/embedded-app/design-requirements/#device-icon 49 | ICON_NANOS = icons/gpg_16px.gif 50 | ICON_NANOX = icons/gpg_14px.gif 51 | ICON_NANOSP = icons/gpg_14px.gif 52 | ICON_STAX = icons/gpg_32px.gif 53 | ICON_FLEX = icons/gpg_40px.gif 54 | 55 | # Application allowed derivation curves. 56 | # Possibles curves are: secp256k1, secp256r1, ed25519 and bls12381g1 57 | # If your app needs it, you can specify multiple curves by using: 58 | # `CURVE_APP_LOAD_PARAMS = ` 59 | CURVE_APP_LOAD_PARAMS = secp256k1 60 | 61 | # Application allowed derivation paths. 62 | # You should request a specific path for your app. 63 | # This serve as an isolation mechanism. 64 | # Most application will have to request a path according to the BIP-0044 65 | # and SLIP-0044 standards. 66 | # If your app needs it, you can specify multiple path by using: 67 | # `PATH_APP_LOAD_PARAMS = "44'/1'" "45'/1'"` 68 | PATH_APP_LOAD_PARAMS = "2152157255'" 69 | 70 | # Setting to allow building variant applications 71 | # - is the name of the parameter which should be set 72 | # to specify the variant that should be build. 73 | # - a list of variant that can be build using this app code. 74 | # * It must at least contains one value. 75 | # * Values can be the app ticker or anything else but should be unique. 76 | VARIANT_PARAM = APPNAME 77 | VARIANT_VALUES = OpenPGP 78 | 79 | # Enabling DEBUG flag will enable PRINTF and disable optimizations 80 | #DEBUG = 1 81 | 82 | ######################################## 83 | # Application custom permissions # 84 | ######################################## 85 | # See SDK `include/appflags.h` for the purpose of each permission 86 | #HAVE_APPLICATION_FLAG_DERIVE_MASTER = 1 87 | #HAVE_APPLICATION_FLAG_GLOBAL_PIN = 1 88 | #HAVE_APPLICATION_FLAG_BOLOS_SETTINGS = 1 89 | #HAVE_APPLICATION_FLAG_LIBRARY = 1 90 | 91 | ######################################## 92 | # Application communication interfaces # 93 | ######################################## 94 | #ENABLE_BLUETOOTH = 1 95 | #ENABLE_NFC = 1 96 | 97 | ######################################## 98 | # NBGL custom features # 99 | ######################################## 100 | #ENABLE_NBGL_QRCODE = 1 101 | #ENABLE_NBGL_KEYBOARD = 1 102 | ENABLE_NBGL_KEYPAD = 1 103 | 104 | ######################################## 105 | # Features disablers # 106 | ######################################## 107 | # These advanced settings allow to disable some feature that are by 108 | # default enabled in the SDK `Makefile.standard_app`. 109 | #DISABLE_STANDARD_APP_FILES = 1 110 | #DISABLE_DEFAULT_IO_SEPROXY_BUFFER_SIZE = 1 # To allow custom size declaration 111 | #DISABLE_STANDARD_APP_DEFINES = 1 # Will set all the following disablers 112 | #DISABLE_STANDARD_SNPRINTF = 1 113 | #DISABLE_STANDARD_USB = 1 114 | DISABLE_STANDARD_WEBUSB = 1 115 | DISABLE_STANDARD_U2F = 1 116 | #DISABLE_STANDARD_BAGL_UX_FLOW = 1 117 | #DISABLE_DEBUG_LEDGER_ASSERT = 1 118 | #DISABLE_DEBUG_THROW = 1 119 | ENABLE_USB_CCID = 1 120 | DISABLE_OS_IO_STACK_USE = 1 121 | 122 | ######################################## 123 | # Main app configuration # 124 | ######################################## 125 | 126 | DEFINES += CUSTOM_IO_APDU_BUFFER_SIZE=\(255+5+64\) 127 | DEFINES += HAVE_USB_CLASS_CCID 128 | DEFINES += HAVE_RSA 129 | # Limitation (maybe due to openpgp itself): no support of DEC operation with cv25519 130 | DEFINES += NO_DECRYPT_cv25519 131 | 132 | ifeq ($(TARGET_NAME),TARGET_NANOS) 133 | DEFINES += HAVE_UX_LEGACY 134 | endif 135 | 136 | ######################### 137 | 138 | # Import generic rules from the SDK 139 | include $(BOLOS_SDK)/Makefile.standard_app 140 | -------------------------------------------------------------------------------- /doc/LogoLedger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/LogoLedger.png -------------------------------------------------------------------------------- /doc/developer/gpgcard-addon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/developer/gpgcard-addon.pdf -------------------------------------------------------------------------------- /doc/developer/gpgcard-addon.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Ledger App OpenPGP. 3 | (c) 2024 Ledger SAS. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | .. 18 | ------------------------------------------------------------------------ 19 | LaTex substitution Definition 20 | ------------------------------------------------------------------------ 21 | 22 | 23 | 24 | License 25 | ======= 26 | 27 | | Ledger App OpenPGP. 28 | | (c) 2024 Ledger SAS. 29 | | 30 | | Licensed under the Apache License, Version 2.0 (the "License"); 31 | | you may not use this file except in compliance with the License. 32 | | You may obtain a copy of the License at 33 | | 34 | | http://www.apache.org/licenses/LICENSE-2.0 35 | | 36 | | Unless required by applicable law or agreed to in writing, software 37 | | distributed under the License is distributed on an "AS IS" BASIS, 38 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 39 | | See the License for the specific language governing permissions and 40 | | limitations under the License. 41 | 42 | 43 | Introduction 44 | ============ 45 | 46 | OpenPGP Card Application add-ons summary 47 | ---------------------------------------- 48 | 49 | Key management: 50 | ~~~~~~~~~~~~~~~ 51 | 52 | OpenPGP Application manage 4 keys to Perform Security Operation (PSO) plus 2 for secure channel. 53 | 54 | The 4 keys are defined as follow: 55 | 56 | - One asymmetric signature private key (RSA or EC), named 'sig' 57 | - One asymmetric decryption private key (RSA or EC), named 'dec' 58 | - One asymmetric authentication private key (RSA or EC), named 'aut' 59 | - One symmetric decryption private key (AES), named 'sym0' 60 | 61 | The 3 first asymmetric keys can be either randomly generated on-card or 62 | explicitly imported from outside. 63 | 64 | The 4th is imported from outside. 65 | 66 | It's never possible to retrieve private key from the card. 67 | 68 | This add-on specification propose a solution to derive those keys from the 69 | master seed managed by the Ledger Token. 70 | This allows owner to restore a broken token without the needs to keep track of keys 71 | outside the card. 72 | 73 | Moreover this add-on specification propose to manage multiple set of the 4 previously described keys. 74 | 75 | Keys Slots 76 | ~~~~~~~~~~ 77 | 78 | To modify the keys slot, just select the corresponding menu from the screen. 79 | 80 | .. image:: slots.png 81 | 82 | Random number generation 83 | ~~~~~~~~~~~~~~~~~~~~~~~~ 84 | 85 | OpenPGP Application provides, as optional feature, to generate random bytes. 86 | 87 | This add-on specification propose new type of random generation: 88 | 89 | - random prime number generation 90 | - seeded random number 91 | - seeded prime number generation 92 | 93 | Key Backup 94 | ~~~~~~~~~~ 95 | 96 | A full key backup mechanism is provided. 97 | 98 | 99 | Ledger OpenPGP Application 100 | ========================== 101 | 102 | How 103 | --- 104 | 105 | Deterministic key derivation 106 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 107 | 108 | The deterministic key derivation process relies on the **BIP32** scheme. 109 | The master install path of the App is set to ``/0x80'GPG'``, aka ``/80475047``. 110 | 111 | Deterministic key derivation maybe activated in: 112 | 113 | | ``Settings -> Seed Mode -> Set on`` 114 | 115 | This activation remains effective until *set off* is selected. 116 | 117 | The key management remains the same if seed mode is *on* or *off*. So there is no performance impact when using seeded keys. 118 | 119 | Seeded keys are generated as follow: 120 | 121 | **Step 1**: 122 | 123 | For a given keys slot n, starting from 1, a seed is first derived with the following path 124 | 125 | | ``Sn = BIP32_derive (/0x80475047/n)`` 126 | 127 | **Step 2**: 128 | 129 | Then specific seeds are derived with the *SHA3-XOF* function for each of the 4 key: 130 | 131 | | ``Sk[i] = SHA3-XOF(SHA256(Sn \| \| int16(i)), length)`` 132 | 133 | Where: 134 | 135 | - Sn is the dedicated slot seed from step 1. 136 | - key_name is one of 'sig ','dec ', 'aut ', 'sym0', each 4 characters. 137 | - i is the index, starting from 1, of the desired seed (see below) 138 | 139 | **Step 3**: 140 | 141 | *RSA key generation* 142 | 143 | Generate two seed Sp, Sq in step2 with: 144 | 145 | - i € {1,2} 146 | - length equals to half key size 147 | 148 | Generate two prime numbers p, q: 149 | 150 | - p = next_prime(Sp) 151 | - q = next_prime(Sq) 152 | 153 | Generate RSA key pair as usual: 154 | 155 | - choose e 156 | - n = p*q 157 | - d = inv(e) mod (p-1)(q-1) 158 | 159 | *ECC key generation* 160 | 161 | Generate one seed Sd in step2 with: 162 | 163 | - i = 1 164 | - length equals to curve size 165 | 166 | Generate ECC key pair: 167 | 168 | - d = Sd 169 | - W = d.G 170 | 171 | *AES key generation* 172 | 173 | Generate one seed Sd in step2 with: 174 | 175 | - i = 1 176 | - length equals to 16 177 | 178 | Generate AES key: 179 | 180 | - k = Sk 181 | 182 | Deterministic random number 183 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 184 | 185 | The deterministic random number generation relies on the **BIP32** scheme. 186 | The master install path of the App is set to ``/0x80'GPG'``, aka ``/80475047``. 187 | 188 | **Random prime number generation**: 189 | 190 | For a given length *L*: 191 | 192 | - generate random number r of *L* bytes. 193 | - generate rp = next_prime(r) 194 | - return rp 195 | 196 | **Seeded random number**: 197 | 198 | For a given length *L* and seed *S*: 199 | 200 | - generate Sr = BIP32_derive(/0x80475047/0x0F0F0F0F) 201 | - generate r = SHA3-XOF(SHA256(Sr \| 'rnd' \| S), L) 202 | - return r 203 | 204 | **Seeded prime number generation**: 205 | 206 | For a given length *L* and seed *S*: 207 | 208 | - generate r as for "Seeded random number" 209 | - generate rp = next_prime(r) 210 | - return rp 211 | 212 | Key Backup & Restore 213 | ~~~~~~~~~~~~~~~~~~~~ 214 | 215 | In order to backup/restore private key the commands `put_data` and `get_data` accept the tags: 216 | 217 | - `B6` (signature key) 218 | - `B8` (encryption key) 219 | - `A4` (authentication). 220 | 221 | `put_data` command accept the exact output of `get_data`. The `get_data` command 222 | return both the public and private key. 223 | 224 | For security and confidentiality, private key is returned encrypted in AES. 225 | The key used is derived according to previously described AES key derivation 226 | with name 'key'. 227 | 228 | 229 | The data payload is formatted as follow: 230 | 231 | +-------+--------------------------+ 232 | | size | Description | 233 | +=======+==========================+ 234 | | 4 | OS Target ID | 235 | +-------+--------------------------+ 236 | | 4 | API Level | 237 | +-------+--------------------------+ 238 | | 4 | compliance Level | 239 | +-------+--------------------------+ 240 | | 4 | public key size | 241 | +-------+--------------------------+ 242 | | var | public key | 243 | +-------+--------------------------+ 244 | | 4 | private key size | 245 | +-------+--------------------------+ 246 | | var | encrypted private key | 247 | +-------+--------------------------+ 248 | 249 | APDU Modification 250 | ----------------- 251 | 252 | Key Slot management 253 | ~~~~~~~~~~~~~~~~~~~~ 254 | 255 | Key slots are managed by data object *01F1* and *01F2* witch are 256 | manageable by PUT/GET DATA command as for others DO and organized as follow. 257 | 258 | On application reset, the *01F2* content is set to *Default Slot* value 259 | of *01F1*. 260 | 261 | *01F1:* 262 | 263 | +-------+----------------------------------+-------+ 264 | | bytes | Description | R/W | 265 | +=======+==================================+=======+ 266 | | 1 | Number of slot | R | 267 | +-------+----------------------------------+-------+ 268 | | 2 | Default slot | R/W | 269 | +-------+----------------------------------+-------+ 270 | | 3 | Allowed slot selection method | R/W | 271 | +-------+----------------------------------+-------+ 272 | 273 | Byte 3 is endoced as follow: 274 | 275 | +----+----+----+----+----+----+----+----+-------------------------+ 276 | | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Meaning | 277 | +----+----+----+----+----+----+----+----+-------------------------+ 278 | | \- | \- | \- | \- | \- | \- | \- | x | selection by APDU | 279 | +----+----+----+----+----+----+----+----+-------------------------+ 280 | | \- | \- | \- | \- | \- | \- | x | \- | selection by screen | 281 | +----+----+----+----+----+----+----+----+-------------------------+ 282 | 283 | *01F2:* 284 | 285 | +--------+-----------------+-------+ 286 | | bytes | Description | R/W | 287 | +========+=================+=======+ 288 | | 1 | Current slot | R/W | 289 | +--------+-----------------+-------+ 290 | 291 | *01F0:* 292 | 293 | +--------+-----------------+-------+ 294 | | bytes | Description | R/W | 295 | +========+=================+=======+ 296 | | 1-3 | 01F1 content | R | 297 | +--------+-----------------+-------+ 298 | | 4 | 01F2 content | R | 299 | +--------+-----------------+-------+ 300 | 301 | *Access Conditions:* 302 | 303 | +------+-----------+------------+ 304 | | DO | Read | Write | 305 | +======+===========+============+ 306 | | 01F0 | Always | Never | 307 | +------+-----------+------------+ 308 | | 01F1 | Always | Verify PW3 | 309 | +------+-----------+------------+ 310 | | 01F2 | Always | Verify PW2 | 311 | +------+-----------+------------+ 312 | 313 | Deterministic key derivation 314 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 315 | 316 | P2 parameter of GENERATE ASYMMETRIC KEY PAIR is set to (hex value): 317 | 318 | - 00 for true random key generation 319 | - 01 for seeded random key 320 | 321 | Deterministic random number 322 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 323 | 324 | P1 parameter of GET CHALLENGE is a bit-field encoded as follow: 325 | 326 | +----+----+----+----+----+----+----+----+-------------------------+ 327 | | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Meaning | 328 | +----+----+----+----+----+----+----+----+-------------------------+ 329 | | \- | \- | \- | \- | \- | \- | \- | x | seeded random | 330 | +----+----+----+----+----+----+----+----+-------------------------+ 331 | | \- | \- | \- | \- | \- | \- | x | \- | prime random | 332 | +----+----+----+----+----+----+----+----+-------------------------+ 333 | 334 | When *seeded mode* is set, data field contains the seed and P2 contains 335 | the length of random bytes to generate. 336 | 337 | 338 | Other minor add-on 339 | ------------------ 340 | 341 | GnuPG use both fingerprints and serial number to identify key on card. 342 | So, the `put_data` command is able to modify the AID file with '4F' tag. 343 | In that case the data field shall be 4 bytes length and shall contain 344 | the new serial number. '4F' is protected by PW3 (admin) PIN. 345 | -------------------------------------------------------------------------------- /doc/developer/slots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/developer/slots.png -------------------------------------------------------------------------------- /doc/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NAMES=() 4 | NAMES+=(user/app-openpgp) 5 | NAMES+=(developer/gpgcard-addon) 6 | 7 | OPTIONS=() 8 | OPTIONS+=("--standalone") 9 | OPTIONS+=("--from=rst") 10 | OPTIONS+=("--to=latex") 11 | OPTIONS+=("--variable=papersize:A4") 12 | OPTIONS+=("--variable=geometry:margin=1in") 13 | OPTIONS+=("--variable=fontsize:10pt") 14 | OPTIONS+=("--toc") 15 | OPTIONS+=("--number-sections") 16 | OPTIONS+=("--template=template.latex") 17 | 18 | for name in "${NAMES[@]}"; do 19 | rm -f "${name}.pdf" 20 | dir=$(dirname "${name}") 21 | file=$(basename "${name}") 22 | 23 | (cd "${dir}"; pandoc ${OPTIONS[@]} --output="${file}.pdf" "${file}.rst") 24 | done 25 | -------------------------------------------------------------------------------- /doc/specification/OpenPGP-smart-card-application.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/specification/OpenPGP-smart-card-application.pdf -------------------------------------------------------------------------------- /doc/user/0001-plist.patch: -------------------------------------------------------------------------------- 1 | --- libccid_Info.plist_org 2023-10-17 10:50:56.030148081 +0200 2 | +++ libccid_Info.plist_test 2024-01-16 15:11:54.798891142 +0100 3 | @@ -462,6 +462,9 @@ 4 | 0x2D25 5 | 0x2C97 6 | 0x2C97 7 | + 0x2C97 8 | + 0x2C97 9 | + 0x2C97 10 | 0x17EF 11 | 0x17EF 12 | 0x17EF 13 | @@ -1005,8 +1007,11 @@ 14 | 0x43A9 15 | 0x0000 16 | 0x0001 17 | - 0x0001 18 | - 0x0004 19 | + 0x1000 20 | + 0x4000 21 | + 0x5000 22 | + 0x6000 23 | + 0x7000 24 | 0x6007 25 | 0x6055 26 | 0x6111 27 | @@ -1552,6 +1556,9 @@ 28 | KRONEGGER Micro Core Platform 29 | Ledger Nano S 30 | Ledger Nano X 31 | + Ledger Nano S Plus 32 | + Ledger Stax 33 | + Ledger Flex 34 | Lenovo Lenovo USB Smartcard Keyboard 35 | Lenovo Lenovo USB Smartcard Keyboard 36 | Lenovo Lenovo Smartcard Wired Keyboard II 37 | -------------------------------------------------------------------------------- /doc/user/app-openpgp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/user/app-openpgp.pdf -------------------------------------------------------------------------------- /doc/user/pin_abort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/user/pin_abort.png -------------------------------------------------------------------------------- /doc/user/pin_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/user/pin_cancel.png -------------------------------------------------------------------------------- /doc/user/pin_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/user/pin_confirm.png -------------------------------------------------------------------------------- /doc/user/pin_entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/user/pin_entry.png -------------------------------------------------------------------------------- /doc/user/pin_validate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/doc/user/pin_validate.png -------------------------------------------------------------------------------- /glyphs/gpg_16px.gif: -------------------------------------------------------------------------------- 1 | ../icons/gpg_16px.gif -------------------------------------------------------------------------------- /glyphs/gpg_64px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/glyphs/gpg_64px.gif -------------------------------------------------------------------------------- /icons/gpg_14px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/icons/gpg_14px.gif -------------------------------------------------------------------------------- /icons/gpg_16px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/icons/gpg_16px.gif -------------------------------------------------------------------------------- /icons/gpg_32px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/icons/gpg_32px.gif -------------------------------------------------------------------------------- /icons/gpg_40px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/icons/gpg_40px.gif -------------------------------------------------------------------------------- /ledger_app.toml: -------------------------------------------------------------------------------- 1 | [app] 2 | build_directory = "./" 3 | sdk = "C" 4 | devices = ["nanos", "nanox", "nanos+", "stax", "flex"] 5 | 6 | [tests] 7 | unit_directory = "./unit-tests/" 8 | pytest_directory = "./tests/" 9 | -------------------------------------------------------------------------------- /manual-tests/manual.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # script to check OpenPGP Application features 4 | # 5 | 6 | exeName=$(readlink "$0") 7 | [[ -z ${exeName} ]] && exeName=$0 8 | dirName=$(dirname "${exeName}") 9 | 10 | gnupg_home_dir="$(realpath "${dirName}/gnupg")" 11 | 12 | VERBOSE=false 13 | EXPERT=false 14 | 15 | #=============================================================================== 16 | # 17 | # help - Prints script help and usage 18 | # 19 | #=============================================================================== 20 | # shellcheck disable=SC2154 # var is referenced but not assigned 21 | help() { 22 | echo 23 | echo "Usage: ${exeName} " 24 | echo 25 | echo "Options:" 26 | echo 27 | echo " -c : Requested command" 28 | echo " -e : Expert mode" 29 | echo " -v : Verbose mode" 30 | echo " -h : Displays this help" 31 | echo 32 | exit 1 33 | } 34 | 35 | #=============================================================================== 36 | # 37 | # reset - Kill running process, ensure clear next operation 38 | # 39 | #=============================================================================== 40 | reset() { 41 | # Kill running process 42 | killall scdaemon gpg-agent 2>/dev/null 43 | } 44 | 45 | #=============================================================================== 46 | # 47 | # default - Set default key in conf file 48 | # 49 | #=============================================================================== 50 | default() { 51 | dir=$(basename "${gnupg_home_dir}") 52 | if [[ ! -d "${dir}" ]]; then 53 | mkdir "${dir}" 54 | chmod 700 "${dir}" 55 | fi 56 | 57 | recipient=$(gpg --homedir "${gnupg_home_dir}" --card-status | grep "General key info" | awk '{print $NF}') 58 | 59 | if [[ ${recipient} =~ "none" ]]; then 60 | read -r -p "Enter default key name: " recipient 61 | fi 62 | 63 | { 64 | echo "default-key ${recipient}" 65 | echo "default-recipient ${recipient}" 66 | } > "${dir}/gpg.conf" 67 | } 68 | 69 | #=============================================================================== 70 | # 71 | # init - Init the gnupg config, start from an empty keyring 72 | # 73 | #=============================================================================== 74 | init() { 75 | reset 76 | 77 | # Cleanup old gnupg home directory 78 | dir=$(basename "${gnupg_home_dir}") 79 | rm -fr "${dir}" foo.txt* 80 | mkdir "${dir}" 81 | chmod 700 "${dir}" 82 | 83 | { 84 | echo reader-port \"Ledger token\" 85 | echo allow-admin 86 | echo enable-pinpad-varlen 87 | echo card-timeout 1 88 | echo disable-ccid 89 | echo pcsc-shared 90 | } > "${dir}/scdaemon.conf" 91 | 92 | if [[ ${EXPERT} == true ]]; then 93 | { 94 | echo log-file /tmp/scd.log 95 | echo debug-level guru 96 | echo debug-all 97 | } >> "${dir}/scdaemon.conf" 98 | fi 99 | 100 | gpgconf --reload scdaemon 101 | } 102 | 103 | #=============================================================================== 104 | # 105 | # card - Edit the card status and configuration 106 | # 107 | #=============================================================================== 108 | card() { 109 | local expert_mode="" 110 | 111 | [[ ${EXPERT} == true ]] && expert_mode="--expert" 112 | 113 | gpg --homedir "${gnupg_home_dir}" ${expert_mode} --card-edit 114 | } 115 | 116 | #=============================================================================== 117 | # 118 | # card - Show the card status and configuration 119 | # 120 | #=============================================================================== 121 | status() { 122 | gpg --homedir "${gnupg_home_dir}" --card-status 123 | } 124 | 125 | #=============================================================================== 126 | # 127 | # encrypt - Encrypt a clear file 128 | # 129 | #=============================================================================== 130 | encrypt() { 131 | local verbose_mode="" 132 | 133 | reset 134 | rm -fr foo* 135 | echo CLEAR > foo.txt 136 | 137 | [[ ${VERBOSE} == true ]] && verbose_mode="--verbose" 138 | 139 | gpg --homedir "${gnupg_home_dir}" ${verbose_mode} --encrypt foo.txt 140 | } 141 | 142 | #=============================================================================== 143 | # 144 | # decrypt - Decrypt a file and compare with original clear content 145 | # 146 | #=============================================================================== 147 | decrypt() { 148 | local verbose_mode="" 149 | 150 | reset 151 | 152 | [[ ${VERBOSE} == true ]] && verbose_mode="--verbose" 153 | 154 | gpg --homedir "${gnupg_home_dir}" ${verbose_mode} --decrypt foo.txt.gpg > foo_dec.txt 155 | 156 | # Check with original clear file 157 | if diff foo.txt foo_dec.txt >/dev/null; then 158 | echo "Success !" 159 | else 160 | echo "Decryption error!" 161 | fi 162 | rm -fr foo* 163 | } 164 | 165 | #=============================================================================== 166 | # 167 | # sign - Sign a file 168 | # 169 | #=============================================================================== 170 | sign() { 171 | local verbose_mode="" 172 | 173 | reset 174 | rm -fr foo* 175 | echo CLEAR > foo.txt 176 | 177 | [[ ${VERBOSE} == true ]] && verbose_mode="--verbose" 178 | 179 | gpg --homedir "${gnupg_home_dir}" ${verbose_mode} --sign foo.txt 180 | } 181 | 182 | #=============================================================================== 183 | # 184 | # verify - Verify a file signature 185 | # 186 | #=============================================================================== 187 | verify() { 188 | local verbose_mode="" 189 | 190 | reset 191 | 192 | [[ ${VERBOSE} == true ]] && verbose_mode="--verbose" 193 | 194 | gpg --homedir "${gnupg_home_dir}" ${verbose_mode} --verify foo.txt.gpg 195 | rm -fr foo* 196 | } 197 | 198 | #=============================================================================== 199 | # 200 | # Parsing parameters 201 | # 202 | #=============================================================================== 203 | 204 | if (($# < 1)); then 205 | help 206 | fi 207 | 208 | while getopts ":c:evh" opt; do 209 | case $opt in 210 | 211 | c) 212 | case ${OPTARG} in 213 | init|reset|card|status|encrypt|decrypt|sign|verify|default) 214 | CMD=${OPTARG} 215 | ;; 216 | *) 217 | echo "Wrong parameter '${OPTARG}'!" 218 | exit 1 219 | ;; 220 | esac 221 | ;; 222 | 223 | e) EXPERT=true ;; 224 | v) VERBOSE=true ;; 225 | h) help ;; 226 | 227 | \?) echo "Unknown option: -${OPTARG}" >&2; exit 1;; 228 | : ) echo "Missing option argument for -${OPTARG}" >&2; exit 1;; 229 | * ) echo "Unimplemented option: -${OPTARG}" >&2; exit 1;; 230 | esac 231 | done 232 | 233 | #=============================================================================== 234 | # 235 | # Main 236 | # 237 | #=============================================================================== 238 | 239 | # execute the command 240 | ${CMD} 241 | -------------------------------------------------------------------------------- /pytools/backup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | #***************************************************************************** 4 | # Ledger App OpenPGP. 5 | # (c) 2024 Ledger SAS. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | #***************************************************************************** 19 | 20 | import sys 21 | from pathlib import Path 22 | from argparse import ArgumentParser, RawTextHelpFormatter, Namespace 23 | from gpgapp.gpgcard import GPGCard, PassWord, GPGCardExcpetion 24 | 25 | # =============================================================================== 26 | # Parse command line options 27 | # =============================================================================== 28 | def get_argparser() -> Namespace: 29 | """Parse the commandline options""" 30 | 31 | parser = ArgumentParser( 32 | description="Backup/Restore OpenPGP App configuration", 33 | epilog="Keys restore is only possible with SEED mode...", 34 | formatter_class=RawTextHelpFormatter 35 | ) 36 | parser.add_argument("--reader", type=str, default="Ledger", 37 | help="PCSC reader name (default is '%(default)s') or 'speculos'") 38 | 39 | parser.add_argument("--apdu", action="store_true", help="Log APDU exchange") 40 | parser.add_argument("--slot", type=int, choices=range(1, 4), help="Select slot (1 to 3)") 41 | 42 | parser.add_argument("--pinpad", action="store_true", help="PIN validation delegated to pinpad") 43 | parser.add_argument("--adm-pin", metavar="PIN", help="Admin PIN (if pinpad not used)") 44 | parser.add_argument("--user-pin", metavar="PIN", help="User PIN (if pinpad not used)") 45 | 46 | parser.add_argument("--restore", action="store_true", 47 | help="Perform a Restore instead of Backup") 48 | 49 | parser.add_argument("--file", type=str, default="gpg_backup", 50 | help="Backup/Restore file (default is '%(default)s')") 51 | 52 | parser.add_argument("--seed-key", action="store_true", 53 | help="After Restore, regenerate all keys, based on seed mode") 54 | 55 | return parser.parse_args() 56 | 57 | 58 | # =============================================================================== 59 | # MAIN 60 | # =============================================================================== 61 | def entrypoint() -> None: 62 | """Main function""" 63 | 64 | # Arguments parsing 65 | # ----------------- 66 | args = get_argparser() 67 | 68 | # Arguments checking 69 | # ------------------ 70 | if not args.pinpad: 71 | if not args.user_pin: 72 | args.user_pin = "123456" 73 | print(f"Using default 'userpin': {args.user_pin}") 74 | if not args.adm_pin: 75 | args.adm_pin = "12345678" 76 | print(f"Using default 'admpin': {args.adm_pin}") 77 | 78 | if args.restore is False: 79 | if Path(args.file).is_file(): 80 | print(f"Provided backup file '{args.file}' already exist. Aborting!") 81 | sys.exit() 82 | 83 | # Processing 84 | # ---------- 85 | try: 86 | print(f"Connect to card '{args.reader}'...") 87 | gpgcard: GPGCard = GPGCard() 88 | gpgcard.log_apdu(args.apdu) 89 | gpgcard.connect(args.reader) 90 | 91 | if not gpgcard.verify_pin(PassWord.PW1, args.user_pin, args.pinpad) or \ 92 | not gpgcard.verify_pin(PassWord.PW3, args.adm_pin, args.pinpad): 93 | raise GPGCardExcpetion(0, "PIN not verified") 94 | 95 | if args.slot: 96 | gpgcard.select_slot(args.slot - 1) 97 | 98 | gpgcard.get_all() 99 | 100 | if args.restore: 101 | gpgcard.restore(args.file) 102 | print(f"Configuration restored from file '{args.file}'.") 103 | 104 | if args.seed_key: 105 | gpgcard.seed_key() 106 | 107 | else: 108 | gpgcard.backup(args.file) 109 | print(f"Configuration saved in file '{args.file}'.") 110 | 111 | gpgcard.disconnect() 112 | 113 | except GPGCardExcpetion as err: 114 | print(f"\n### Error {err.code}: {err.message}!\n") 115 | 116 | 117 | if __name__ == "__main__": 118 | 119 | entrypoint() 120 | -------------------------------------------------------------------------------- /pytools/gpgapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/pytools/gpgapp/__init__.py -------------------------------------------------------------------------------- /pytools/gpgapp/gpgcmd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #***************************************************************************** 3 | # Ledger App OpenPGP. 4 | # (c) 2024 Ledger SAS. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | #***************************************************************************** 18 | 19 | from enum import Enum, IntEnum 20 | 21 | 22 | KEY_TEMPLATES = { 23 | "rsa2048" : "010800002001", 24 | "rsa3072" : "010C00002001", 25 | "rsa4096" : "011000002001", 26 | "nistp256": "132A8648CE3D030107", 27 | "ed25519" : "162B06010401DA470F01", 28 | "cv25519" : "122B060104019755010501" 29 | } 30 | 31 | 32 | KEY_OPERATIONS = { 33 | "Export": 0x00, # Read and export a Public Key 34 | "Generate": 0x80, # Generate a new Asymmetric key pair 35 | "Read": 0x81, # Read Public Key 36 | } 37 | 38 | 39 | USER_SALUTATION = { 40 | "Male": "1", 41 | "Female": "2", 42 | } 43 | 44 | 45 | class KeyTypes(str, Enum): 46 | """Key types definition 47 | OpenPGP Application manage four keys for cryptographic operation (PSO) plus two 48 | for secure channel. 49 | The first four keys are defined as follow: 50 | - One asymmetric signature private key (RSA or EC), named 'sig' 51 | - One asymmetric decryption private key (RSA or EC), named 'dec' 52 | - One asymmetric authentication private key (RSA or EC), named 'aut' 53 | - One symmetric decryption private key (AES), named 'sym0' 54 | 55 | The 3 first asymmetric keys can be either randomly generated on-card or 56 | explicitly put from outside. 57 | The fourth is put from outside. 58 | """ 59 | 60 | # Asymmetric Signature Private Key (RSA or EC) 61 | KEY_SIG = "SIG" 62 | # Asymmetric Decryption Private Key (RSA or EC) 63 | KEY_DEC = "DEC" 64 | # Asymmetric Authentication Private Key (RSA or EC) 65 | KEY_AUT = "AUT" 66 | # Symmetric Decryption Key (AES) 67 | 68 | 69 | class PubkeyAlgo(IntEnum): 70 | """ Public-Key Algorithm IDs definition """ 71 | # https://www.rfc-editor.org/rfc/rfc4880#section-9.1 72 | 73 | # RSA (Encrypt or Sign) 74 | RSA = 1 75 | # Elliptic Curve Diffie-Hellman 76 | ECDH = 18 77 | # Elliptic Curve Digital Signature Algorithm 78 | ECDSA = 19 79 | # Edwards-curve Digital Signature Algorithm 80 | EDDSA = 22 81 | 82 | 83 | class PassWord(IntEnum): 84 | """ Password type definition """ 85 | 86 | # USER_PIN for only one PSO:CDS command 87 | PW1 = 0x81 88 | # USER_PIN for several attempts 89 | PW2 = 0x82 90 | # Admin PIN 91 | PW3 = 0x83 92 | 93 | 94 | class ErrorCodes: 95 | """ Error codes definition """ 96 | 97 | err_list = { 98 | 0x6285: "Selected file in termination state", 99 | 0x6581: "Memory failure", 100 | 0x6600: "Security-related issues (reserved for UIF in this application)", 101 | 0x6700: "Wrong length (Lc and/or Le)", 102 | 0x6881: "Logical channel not supported", 103 | 0x6882: "Secure messaging not supported", 104 | 0x6883: "Last command of the chain expected", 105 | 0x6884: "Command chaining not supported", 106 | 0x6982: "Security status not satisfied", 107 | 0x6983: "Authentication method blocked", 108 | 0x6984: "Data Invalid", 109 | 0x6985: "Condition of use not satisfied", 110 | 0x6986: "Command not allowed", 111 | 0x6987: "Expected SM data objects missing", 112 | 0x6988: "SM data objects incorrect", 113 | 0x6A80: "Incorrect parameters in the data field", 114 | 0x6A82: "File or application not found", 115 | 0x6A86: "Incorrect P1-P2", 116 | 0x6A88: "Referenced data not found", 117 | 0x6B00: "Wrong parameters P1-P2", 118 | 0x6D00: "Instruction (INS) not supported", 119 | 0x6E00: "Class (CLA) not supported", 120 | 0x6F00: "Unknown Error", 121 | 0x9000: "Success", 122 | } 123 | ERR_SUCCESS = 0x9000 124 | ERR_SW1_VALID = 0x61 125 | ERR_INTERNAL = 0 126 | 127 | 128 | class DataObject(IntEnum): 129 | """ Data Objects definition """ 130 | 131 | # [Read/Write] Slot config 132 | CMD_SLOT_CFG = 0x01F1 133 | # [Read/Write] Slot selection 134 | CMD_SLOT_CUR = 0x01F2 135 | # [Read/Write] RSA Exponent 136 | CMD_RSA_EXP = 0x01F8 137 | 138 | # [Read] Full Application identifier (AID), ISO 7816-4 139 | DO_AID = 0x4F 140 | # [Read/Write] Login data 141 | DO_LOGIN = 0x5E 142 | # [Read/Write] Uniform resource locator (URL, as defined in RFC 1738) 143 | DO_URL = 0x5F50 144 | # [Read] Historical bytes, Card service data and Card capabilities 145 | DO_HIST = 0x5F52 146 | 147 | # [Read/Write] Optional DO for private use 148 | DO_PRIVATE_01 = 0x0101 149 | DO_PRIVATE_02 = 0x0102 150 | DO_PRIVATE_03 = 0x0103 151 | DO_PRIVATE_04 = 0x0104 152 | 153 | # [Read] Cardholder Related Data 154 | DO_CARDHOLDER_DATA = 0x65 155 | # [Read/Write] Name according to ISO/IEC 7501-1) 156 | DO_CARD_NAME = 0x5B 157 | # [Read/Write] Language preferences (according to ISO 639) 158 | DO_CARD_LANG = 0x5F2D 159 | # [Read/Write] Salutation (according to ISO 5218) 160 | DO_CARD_SALUTATION = 0x5F35 161 | 162 | # [Read/Write] Digital signature 163 | DO_SIG_KEY = 0xB6 164 | # [Read/Write] Confidentiality 165 | DO_DEC_KEY = 0xB8 166 | # [Read/Write] Authentication 167 | DO_AUT_KEY = 0xA4 168 | 169 | # [Read] Application Related Data 170 | DO_APP_DATA = 0x6E 171 | # [Read] Extended length information (ISO 7816-4) 172 | DO_EXT_LEN = 0x7F66 173 | # [Read] Discretionary data objects 174 | DO_DISCRET_DATA = 0x73 175 | 176 | # [Read] Extended capabilities Flag list 177 | DO_EXT_CAP = 0xC0 178 | # [Read/Write] Algorithm attributes SIGnature 179 | DO_SIG_ATTR = 0xC1 180 | # [Read/Write] Algorithm attributes DECryption 181 | DO_DEC_ATTR = 0xC2 182 | # [Read/Write] Algorithm attributes AUThentication 183 | DO_AUT_ATTR = 0xC3 184 | # [Read/Write] PW status Bytes 185 | DO_PW_STATUS = 0xC4 186 | # [Read] Fingerprints (binary, 20 bytes (dec.) each for SIG, DEC, AUT) 187 | DO_FINGERPRINTS = 0xC5 188 | # [Read] List of CA-Fingerprints (binary, 20 bytes (dec.) each for SIG, DEC, AUT) 189 | DO_CA_FINGERPRINTS = 0xC6 190 | # [Write] Fingerprint for SIGnature key, format according to RFC 4880 191 | DO_FINGERPRINT_WR_SIG = 0xC7 192 | # [Write] Fingerprint for DECryption key, format according to RFC 4880 193 | DO_FINGERPRINT_WR_DEC = 0xC8 194 | # [Write] Fingerprint for AUThentication key, format according to RFC 4880 195 | DO_FINGERPRINT_WR_AUT = 0xC9 196 | # [Write] CA-Fingerprint for SIGnature key 197 | DO_CA_FINGERPRINT_WR_SIG = 0xCA 198 | # [Write] CA-Fingerprint for DECryption key 199 | DO_CA_FINGERPRINT_WR_DEC = 0xCB 200 | # [Write] CA-Fingerprint for AUThentication key 201 | DO_CA_FINGERPRINT_WR_AUT = 0xCC 202 | # [Read] List of generation dates. 4 bytes, Big Endian each for SIG, DEC, AUT 203 | DO_KEY_DATES = 0xCD 204 | 205 | # [Write] Generation date/time of SIGnature key (Big Endian, according to RFC 4880) 206 | DO_DATES_WR_SIG = 0xCE 207 | # [Write] Generation date/time of DECryption key (Big Endian, according to RFC 4880) 208 | DO_DATES_WR_DEC = 0xCF 209 | # [Write] Generation date/time of AUThentication key (Big Endian, according to RFC 4880) 210 | DO_DATES_WR_AUT = 0xD0 211 | 212 | # [Read] Security support template 213 | DO_SEC_TEMPL = 0x7A 214 | # [Read] Digital signature counter 215 | DO_SIG_COUNT = 0x93 216 | 217 | # [Write] Resetting Code 218 | DO_RESET_CODE = 0xD3 219 | # [Read/Write] User Interaction Flag (UIF) for PSO:CDS 220 | DO_UIF_SIG = 0xD6 221 | # [Read/Write] User Interaction Flag (UIF) for PSO:DEC 222 | DO_UIF_DEC = 0xD7 223 | # [Read/Write] User Interaction Flag (UIF) for PSO:AUT 224 | DO_UIF_AUT = 0xD8 225 | # [Read/Write] Cardholder certificate (each for AUT, DEC and SIG) 226 | DO_CERT = 0x7F21 227 | 228 | # [Read/Write] Asymmetric Key Pair 229 | DO_PUB_KEY = 0x7F49 230 | 231 | # [Read] General Feature management 232 | DO_GEN_FEATURES = 0x7F74 233 | -------------------------------------------------------------------------------- /pytools/requirements.txt: -------------------------------------------------------------------------------- 1 | pycryptodome 2 | ledgercomm 3 | -------------------------------------------------------------------------------- /pytools/setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | addopts = --strict-markers 3 | 4 | [pylint] 5 | disable = C0114, # missing-module-docstring 6 | C0115, # missing-class-docstring 7 | C0116, # missing-function-docstring 8 | C0103, # invalid-name 9 | C0302, # too-many-lines 10 | R0801, # duplicate-code 11 | R0902, # too-many-instances 12 | R0903, # too-few-public-methods 13 | R0904, # too-many-public-methods 14 | R0912, # too-many-branches 15 | R0913, # too-many-arguments 16 | R0914, # too-many-statements 17 | R0915 # too-many-local-variables 18 | max-line-length=110 19 | extension-pkg-whitelist=hid 20 | 21 | [pycodestyle] 22 | max-line-length = 100 23 | 24 | [mypy-hid.*] 25 | ignore_missing_imports = True 26 | 27 | [mypy-pytest.*] 28 | ignore_missing_imports = True 29 | -------------------------------------------------------------------------------- /src/gpg_api.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #ifndef GPG_API_H 19 | #define GPG_API_H 20 | 21 | /* ----------------------------------------------------------------------- */ 22 | /* --- INIT ---- */ 23 | /* ----------------------------------------------------------------------- */ 24 | 25 | void gpg_activate_pinpad(uint8_t enabled); 26 | unsigned int gpg_oid2curve(unsigned char *oid, unsigned int len); 27 | unsigned char *gpg_curve2oid(unsigned int cv, unsigned int *len); 28 | unsigned int gpg_curve2domainlen(unsigned int cv); 29 | 30 | void gpg_init(void); 31 | void gpg_install(unsigned char app_state); 32 | 33 | /* ----------------------------------------------------------------------- */ 34 | /* --- DISPATCH ---- */ 35 | /* ----------------------------------------------------------------------- */ 36 | 37 | int gpg_dispatch(void); 38 | 39 | /* ----------------------------------------------------------------------- */ 40 | /* --- DATA ---- */ 41 | /* ----------------------------------------------------------------------- */ 42 | 43 | void gpg_apdu_select_data(unsigned int ref, int record); 44 | int gpg_apdu_get_data(unsigned int ref); 45 | int gpg_apdu_get_next_data(unsigned int ref); 46 | int gpg_apdu_put_data(unsigned int ref); 47 | int gpg_apdu_get_key_data(unsigned int ref); 48 | int gpg_apdu_put_key_data(unsigned int ref); 49 | 50 | /* ----------------------------------------------------------------------- */ 51 | /* --- PSO ---- */ 52 | /* ----------------------------------------------------------------------- */ 53 | 54 | int gpg_pso_derive_slot_seed(int slot, unsigned char *seed); 55 | int gpg_pso_derive_key_seed(unsigned char *Sn, 56 | unsigned char *key_name, 57 | unsigned int idx, 58 | unsigned char *Ski, 59 | unsigned int Ski_len); 60 | int gpg_apdu_pso(void); 61 | int gpg_apdu_internal_authenticate(void); 62 | 63 | /* ----------------------------------------------------------------------- */ 64 | /* --- GEN ---- */ 65 | /* ----------------------------------------------------------------------- */ 66 | 67 | int gpg_apdu_gen(void); 68 | 69 | /* ----------------------------------------------------------------------- */ 70 | /* --- CHALLENGE ---- */ 71 | /* ----------------------------------------------------------------------- */ 72 | 73 | int gpg_apdu_get_challenge(void); 74 | 75 | /* ----------------------------------------------------------------------- */ 76 | /* --- SELECT ---- */ 77 | /* ----------------------------------------------------------------------- */ 78 | 79 | int gpg_apdu_select(void); 80 | 81 | /* ----------------------------------------------------------------------- */ 82 | /* --- PIN ---- */ 83 | /* ----------------------------------------------------------------------- */ 84 | 85 | int gpg_apdu_verify(void); 86 | int gpg_apdu_change_ref_data(void); 87 | int gpg_apdu_reset_retry_counter(void); 88 | 89 | gpg_pin_t *gpg_pin_get_pin(int id); 90 | int gpg_pin_is_verified(int pinID); 91 | void gpg_pin_set_verified(int pinID, int verified); 92 | int gpg_pin_check(gpg_pin_t *pin, int pinID, const unsigned char *pin_val, unsigned int pin_len); 93 | int gpg_pin_set(gpg_pin_t *pin, unsigned char *pin_val, unsigned int pin_len); 94 | 95 | /* ----------------------------------------------------------------------- */ 96 | /* --- MSE ---- */ 97 | /* ----------------------------------------------------------------------- */ 98 | 99 | void gpg_mse_reset(); 100 | int gpg_apdu_mse(); 101 | 102 | /* ----------------------------------------------------------------------- */ 103 | /* --- IO ---- */ 104 | /* ----------------------------------------------------------------------- */ 105 | void gpg_io_discard(int clear); 106 | void gpg_io_clear(void); 107 | void gpg_io_set_offset(unsigned int offset); 108 | void gpg_io_mark(void); 109 | void gpg_io_rewind(void); 110 | void gpg_io_inserted(unsigned int len); 111 | void gpg_io_insert(unsigned char const *buffer, unsigned int len); 112 | void gpg_io_insert_u32(unsigned int v32); 113 | void gpg_io_insert_u24(unsigned int v24); 114 | void gpg_io_insert_u16(unsigned int v16); 115 | void gpg_io_insert_u8(unsigned int v8); 116 | void gpg_io_insert_t(unsigned int T); 117 | void gpg_io_insert_tl(unsigned int T, unsigned int L); 118 | void gpg_io_insert_tlv(unsigned int T, unsigned int L, unsigned char const *V); 119 | 120 | void gpg_io_fetch_buffer(unsigned char *buffer, unsigned int len); 121 | unsigned int gpg_io_fetch_u32(void); 122 | unsigned int gpg_io_fetch_u24(void); 123 | unsigned int gpg_io_fetch_u16(void); 124 | unsigned int gpg_io_fetch_u8(void); 125 | void gpg_io_fetch_t(unsigned int *T); 126 | void gpg_io_fetch_l(unsigned int *L); 127 | void gpg_io_fetch_tl(unsigned int *T, unsigned int *L); 128 | void gpg_io_fetch_nv(unsigned char *buffer, int len); 129 | int gpg_io_fetch(unsigned char *buffer, int len); 130 | 131 | void gpg_io_do(unsigned int io_flags); 132 | 133 | #endif 134 | -------------------------------------------------------------------------------- /src/gpg_challenge.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #include "gpg_vars.h" 19 | #include "cx_errors.h" 20 | 21 | /** 22 | * Generate a Random Number 23 | * 24 | * @return Status Word 25 | * 26 | */ 27 | int gpg_apdu_get_challenge() { 28 | unsigned int olen; 29 | cx_err_t error = CX_INTERNAL_ERROR; 30 | unsigned char Sr[64]; 31 | 32 | switch (G_gpg_vstate.io_p1) { 33 | case CHALLENGE_NOMINAL: 34 | case PRIME_MODE: 35 | olen = G_gpg_vstate.io_le; 36 | break; 37 | case SEEDED_MODE: 38 | olen = G_gpg_vstate.io_p2; 39 | break; 40 | default: 41 | return SW_WRONG_P1P2; 42 | } 43 | if (olen == 0 || olen > GPG_EXT_CHALLENGE_LENTH) { 44 | return SW_WRONG_LENGTH; 45 | } 46 | 47 | if (G_gpg_vstate.io_p1 == SEEDED_MODE) { 48 | // Ledger Add-on: Seeded random 49 | unsigned int path[2]; 50 | unsigned char chain[32] = {0}; 51 | 52 | explicit_bzero(chain, 32); 53 | path[0] = 0x80475047; 54 | path[1] = 0x0F0F0F0F; 55 | CX_CHECK(os_derive_bip32_no_throw(CX_CURVE_SECP256K1, path, 2, Sr, chain)); 56 | chain[0] = 'r'; 57 | chain[1] = 'n'; 58 | chain[2] = 'd'; 59 | 60 | cx_sha256_init(&G_gpg_vstate.work.md.sha256); 61 | CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, 0, Sr, 32, NULL, 0)); 62 | CX_CHECK( 63 | cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, 0, chain, 3, NULL, 0)); 64 | CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, 65 | CX_LAST, 66 | G_gpg_vstate.work.io_buffer, 67 | G_gpg_vstate.io_length, 68 | G_gpg_vstate.work.io_buffer, 69 | 32)); 70 | CX_CHECK(cx_sha3_xof_init_no_throw(&G_gpg_vstate.work.md.sha3, 256, olen)); 71 | CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha3, 72 | CX_LAST, 73 | G_gpg_vstate.work.io_buffer, 74 | 32, 75 | G_gpg_vstate.work.io_buffer, 76 | olen)); 77 | } else { 78 | cx_rng(G_gpg_vstate.work.io_buffer, olen); 79 | error = CX_OK; 80 | } 81 | 82 | if (G_gpg_vstate.io_p1 == PRIME_MODE) { 83 | // Ledger Add-on: Prime random 84 | CX_CHECK(cx_math_next_prime_no_throw(G_gpg_vstate.work.io_buffer, olen)); 85 | } 86 | 87 | end: 88 | explicit_bzero(&Sr, sizeof(Sr)); 89 | if (error != CX_OK) { 90 | return error; 91 | } 92 | gpg_io_discard(0); 93 | gpg_io_inserted(olen); 94 | return SW_OK; 95 | } 96 | -------------------------------------------------------------------------------- /src/gpg_main.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #include "gpg_vars.h" 19 | #include "gpg_ux.h" 20 | #include "io.h" 21 | #include "usbd_ccid_if.h" 22 | 23 | /* ----------------------------------------------------------------------- */ 24 | /* --- Application Entry --- */ 25 | /* ----------------------------------------------------------------------- */ 26 | 27 | void app_main(void) { 28 | unsigned int io_flags = 0; 29 | io_flags = 0; 30 | volatile unsigned short sw = SW_UNKNOWN; 31 | 32 | // start communication with MCU 33 | ui_CCID_reset(); 34 | 35 | // set up 36 | io_init(); 37 | 38 | gpg_init(); 39 | 40 | // set up initial screen 41 | ui_init(); 42 | 43 | // start the application 44 | // the first exchange will: 45 | // - display the initial screen 46 | // - send the ATR 47 | // - receive the first command 48 | for (;;) { 49 | gpg_io_do(io_flags); 50 | sw = gpg_dispatch(); 51 | if (sw) { 52 | PRINTF("[MAIN] - FINALLY INSERT sw=0x%x\n", sw); 53 | if ((sw != SW_OK) && ((sw & 0xFF00) != SW_CORRECT_BYTES_AVAILABLE)) { 54 | gpg_io_discard(1); 55 | } 56 | gpg_io_insert_u16(sw); 57 | io_flags = 0; 58 | } else { 59 | io_flags = IO_ASYNCH_REPLY; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/gpg_mse.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #include "gpg_vars.h" 19 | 20 | /** 21 | * Set a new MSE configuration 22 | * 23 | * @param[in] crt selected key 24 | * @param[in] ref new operation type 25 | * 26 | */ 27 | static void gpg_mse_set(int crt, int ref) { 28 | if (crt == KEY_AUT) { 29 | if (ref == 0x02) { 30 | G_gpg_vstate.mse_aut = &G_gpg_vstate.kslot->dec; 31 | } 32 | if (ref == 0x03) { 33 | G_gpg_vstate.mse_aut = &G_gpg_vstate.kslot->aut; 34 | } 35 | } 36 | 37 | if (crt == KEY_DEC) { 38 | if (ref == 0x02) { 39 | G_gpg_vstate.mse_dec = &G_gpg_vstate.kslot->dec; 40 | } 41 | if (ref == 0x03) { 42 | G_gpg_vstate.mse_dec = &G_gpg_vstate.kslot->aut; 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * Reset MSE config 49 | * 50 | */ 51 | void gpg_mse_reset() { 52 | gpg_mse_set(KEY_AUT, 0x03); 53 | gpg_mse_set(KEY_DEC, 0x02); 54 | } 55 | 56 | /** 57 | * APDU handler to Manage Security Environment 58 | * 59 | * @return Status Word 60 | * 61 | */ 62 | int gpg_apdu_mse() { 63 | int crt, ref; 64 | 65 | if ((G_gpg_vstate.io_p1 != MSE_SET) || 66 | ((G_gpg_vstate.io_p2 != KEY_AUT) && (G_gpg_vstate.io_p2 != KEY_DEC))) { 67 | return SW_WRONG_P1P2; 68 | } 69 | 70 | crt = gpg_io_fetch_u16(); 71 | if (crt != 0x8301) { 72 | return SW_WRONG_DATA; 73 | } 74 | 75 | ref = gpg_io_fetch_u8(); 76 | if ((ref != 0x02) && (ref != 0x03)) { 77 | return SW_WRONG_DATA; 78 | } 79 | 80 | gpg_mse_set(G_gpg_vstate.io_p2, ref); 81 | gpg_io_discard(1); 82 | return SW_OK; 83 | } 84 | -------------------------------------------------------------------------------- /src/gpg_pin.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #include "gpg_vars.h" 19 | #include "gpg_ux.h" 20 | 21 | /** 22 | * Get Pin structure from reference ID value 23 | * 24 | * @param[in] pinref PinCode reference ID 25 | * 26 | * @return Pin structure, or NULL if invalid 27 | * 28 | */ 29 | gpg_pin_t *gpg_pin_get_pin(int pinref) { 30 | switch (pinref) { 31 | case PIN_ID_PW1: 32 | case PIN_ID_PW2: 33 | return (gpg_pin_t *) &N_gpg_pstate->PW1; 34 | case PIN_ID_PW3: 35 | return (gpg_pin_t *) &N_gpg_pstate->PW3; 36 | case PIN_ID_RC: 37 | return (gpg_pin_t *) &N_gpg_pstate->RC; 38 | } 39 | return NULL; 40 | } 41 | 42 | /** 43 | * Get Pin index from reference ID value 44 | * 45 | * @param[in] pinref PinCode reference ID 46 | * 47 | * @return Pin index 48 | * 49 | */ 50 | static int gpg_pin_get_state_index(unsigned int pinref) { 51 | switch (pinref) { 52 | case PIN_ID_PW1: 53 | return 1; 54 | case PIN_ID_PW2: 55 | return 2; 56 | case PIN_ID_PW3: 57 | return 3; 58 | case PIN_ID_RC: 59 | return 4; 60 | } 61 | return -1; 62 | } 63 | 64 | /** 65 | * Compare the PinCode hash and handle the associated counter 66 | * 67 | * @param[in] pin PinCode reference to check 68 | * @param[in] pin_val PinCode value 69 | * @param[in] pin_len PinCode length 70 | * 71 | * @return Status Word 72 | * 73 | */ 74 | static int gpg_pin_check_internal(gpg_pin_t *pin, const unsigned char *pin_val, int pin_len) { 75 | unsigned int counter; 76 | cx_err_t error = CX_INTERNAL_ERROR; 77 | 78 | if (pin->counter == 0) { 79 | return SW_PIN_BLOCKED; 80 | } 81 | 82 | counter = pin->counter - 1; 83 | cx_sha256_init(&G_gpg_vstate.work.md.sha256); 84 | CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, 85 | CX_LAST, 86 | pin_val, 87 | pin_len, 88 | G_gpg_vstate.work.md.H, 89 | sizeof(G_gpg_vstate.work.md.H))); 90 | if (memcmp(G_gpg_vstate.work.md.H, pin->value, 32)) { 91 | error = (counter == 0) ? SW_PIN_BLOCKED : SW_SECURITY_STATUS_NOT_SATISFIED; 92 | } else { 93 | counter = 3; 94 | error = SW_OK; 95 | } 96 | 97 | end: 98 | if (counter != pin->counter) { 99 | nvm_write(&(pin->counter), &counter, sizeof(int)); 100 | } 101 | return error; 102 | } 103 | 104 | /** 105 | * Check the PinCode value and set verification status 106 | * 107 | * @param[in] pin PinCode reference to check 108 | * @param[in] pin_val PinCode value 109 | * @param[in] pin_len PinCode length 110 | * 111 | * @return Status Word 112 | * 113 | */ 114 | int gpg_pin_check(gpg_pin_t *pin, int pinID, const unsigned char *pin_val, unsigned int pin_len) { 115 | int sw = SW_UNKNOWN; 116 | gpg_pin_set_verified(pinID, 0); 117 | sw = gpg_pin_check_internal(pin, pin_val, pin_len); 118 | if (sw == SW_OK) { 119 | gpg_pin_set_verified(pinID, 1); 120 | } 121 | return sw; 122 | } 123 | 124 | /** 125 | * Set the PinCode value in NVRam 126 | * 127 | * @param[in] pin PinCode reference to set 128 | * @param[in] pin_val PinCode value 129 | * @param[in] pin_len PinCode length 130 | * 131 | * @return Status Word 132 | * 133 | */ 134 | int gpg_pin_set(gpg_pin_t *pin, unsigned char *pin_val, unsigned int pin_len) { 135 | cx_sha256_t sha256; 136 | cx_err_t error = CX_INTERNAL_ERROR; 137 | gpg_pin_t newpin; 138 | 139 | cx_sha256_init(&sha256); 140 | CX_CHECK(cx_hash_no_throw((cx_hash_t *) &sha256, CX_LAST, pin_val, pin_len, newpin.value, 32)); 141 | newpin.length = pin_len; 142 | newpin.counter = 3; 143 | 144 | nvm_write(pin, &newpin, sizeof(gpg_pin_t)); 145 | end: 146 | if (error != CX_OK) { 147 | return error; 148 | } 149 | return SW_OK; 150 | } 151 | 152 | /** 153 | * Change the Pin verification status 154 | * 155 | * @param[in] pinID PinCode ID to change 156 | * @param[in] verified new status 157 | * 158 | */ 159 | void gpg_pin_set_verified(int pinID, int verified) { 160 | int idx; 161 | idx = gpg_pin_get_state_index(pinID); 162 | if (idx >= 0) { 163 | G_gpg_vstate.verified_pin[idx] = verified; 164 | } 165 | } 166 | 167 | /** 168 | * Check if the selected Pin is verified 169 | * 170 | * @param[in] pinID PinCode ID to check 171 | * 172 | * @return 0 or 1 (false or true) 173 | * 174 | */ 175 | int gpg_pin_is_verified(int pinID) { 176 | int idx; 177 | idx = gpg_pin_get_state_index(pinID); 178 | if (idx >= 0) { 179 | return G_gpg_vstate.verified_pin[idx]; 180 | } 181 | return 0; 182 | } 183 | 184 | /** 185 | * Check if the selected Pin is blocked 186 | * 187 | * @param[in] pin PinCode reference to check 188 | * 189 | * @return 0 or 1 (false or true) 190 | * 191 | */ 192 | int gpg_pin_is_blocked(gpg_pin_t *pin) { 193 | return pin->counter == 0; 194 | } 195 | 196 | /** 197 | * APDU handler to Verify PinCode 198 | * 199 | * @return Status Word 200 | * 201 | */ 202 | int gpg_apdu_verify() { 203 | int sw = SW_UNKNOWN; 204 | gpg_pin_t *pin; 205 | 206 | pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); 207 | if (pin == NULL) { 208 | return SW_WRONG_DATA; 209 | } 210 | 211 | // PINPAD 212 | if (G_gpg_vstate.io_cla == CLA_APP_APDU_PIN) { 213 | if (gpg_pin_is_blocked(pin)) { 214 | return SW_PIN_BLOCKED; 215 | } 216 | 217 | switch (G_gpg_vstate.pinmode) { 218 | case PIN_MODE_SCREEN: 219 | // Delegate pin check to ui 220 | gpg_io_discard(1); 221 | ui_menu_pinentry_display(0); 222 | sw = 0; 223 | break; 224 | case PIN_MODE_CONFIRM: 225 | // Delegate pin check to ui 226 | gpg_io_discard(1); 227 | ui_menu_pinconfirm_display(G_gpg_vstate.io_p2); 228 | sw = 0; 229 | break; 230 | case PIN_MODE_TRUST: 231 | gpg_pin_set_verified(G_gpg_vstate.io_p2, 1); 232 | gpg_io_discard(1); 233 | sw = 0; 234 | break; 235 | default: 236 | sw = SW_WRONG_DATA; 237 | break; 238 | } 239 | return sw; 240 | } 241 | 242 | // NORMAL CHECK 243 | if ((G_gpg_vstate.io_p1 == PIN_VERIFY) && G_gpg_vstate.io_length) { 244 | if (gpg_pin_is_blocked(pin)) { 245 | return SW_PIN_BLOCKED; 246 | } 247 | sw = gpg_pin_check(pin, 248 | G_gpg_vstate.io_p2, 249 | G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, 250 | G_gpg_vstate.io_length); 251 | gpg_io_discard(1); 252 | return sw; 253 | } 254 | 255 | gpg_io_discard(1); 256 | 257 | // STATUS REQUEST 258 | if ((G_gpg_vstate.io_p1 == PIN_VERIFY) && G_gpg_vstate.io_length == 0) { 259 | if (gpg_pin_is_verified(G_gpg_vstate.io_p2)) { 260 | return SW_OK; 261 | } 262 | return SW_PWD_NOT_CHECKED | pin->counter; 263 | } 264 | 265 | // RESET REQUEST 266 | if ((G_gpg_vstate.io_p1 == PIN_NOT_VERIFIED) && G_gpg_vstate.io_length == 0) { 267 | gpg_pin_set_verified(G_gpg_vstate.io_p2, 0); 268 | return SW_OK; 269 | } 270 | 271 | return SW_WRONG_DATA; 272 | } 273 | 274 | /** 275 | * APDU handler to Change PinCode 276 | * 277 | * @return Status Word 278 | * 279 | */ 280 | int gpg_apdu_change_ref_data() { 281 | int sw = SW_UNKNOWN; 282 | gpg_pin_t *pin; 283 | int len, newlen; 284 | 285 | pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); 286 | if (pin == NULL) { 287 | return SW_WRONG_DATA; 288 | } 289 | 290 | gpg_pin_set_verified(pin->ref, 0); 291 | 292 | // --- PW1/PW3 pin --- 293 | if (gpg_pin_is_blocked(pin)) { 294 | return SW_PIN_BLOCKED; 295 | } 296 | // avoid any-overflow without giving info 297 | if (G_gpg_vstate.io_length == 0) { 298 | // Delegate pin change to ui 299 | gpg_io_discard(1); 300 | ui_menu_pinentry_display(0); 301 | return 0; 302 | } 303 | 304 | len = MIN(G_gpg_vstate.io_length, pin->length); 305 | sw = gpg_pin_check(pin, pin->ref, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, len); 306 | if (sw != SW_OK) { 307 | return sw; 308 | } 309 | 310 | newlen = G_gpg_vstate.io_length - len; 311 | if ((newlen > GPG_MAX_PW_LENGTH) || 312 | ((pin->ref == PIN_ID_PW1) && (newlen < GPG_MIN_PW1_LENGTH)) || 313 | ((pin->ref == PIN_ID_PW3) && (newlen < GPG_MIN_PW3_LENGTH))) { 314 | return SW_WRONG_DATA; 315 | } 316 | sw = gpg_pin_set(pin, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset + len, newlen); 317 | gpg_io_discard(1); 318 | return sw; 319 | } 320 | 321 | /** 322 | * APDU handler to Reset PinCode or Counter 323 | * 324 | * @return Status Word 325 | * 326 | */ 327 | int gpg_apdu_reset_retry_counter() { 328 | int sw = SW_UNKNOWN; 329 | gpg_pin_t *pin_pw1; 330 | gpg_pin_t *pin_rc; 331 | int rc_len, pw1_len; 332 | 333 | pin_pw1 = gpg_pin_get_pin(PIN_ID_PW1); 334 | pin_rc = gpg_pin_get_pin(PIN_ID_RC); 335 | 336 | if (G_gpg_vstate.io_p1 == RESET_RETRY_WITH_PW3) { 337 | // PW3 must be verified, and the data contain the new PW1 338 | if (!gpg_pin_is_verified(PIN_ID_PW3)) { 339 | return SW_SECURITY_STATUS_NOT_SATISFIED; 340 | } 341 | rc_len = 0; 342 | pw1_len = G_gpg_vstate.io_length; 343 | } else { 344 | // The data contain the Resetting Code + the new PW1 345 | // avoid any-overflow without giving info 346 | rc_len = MIN(G_gpg_vstate.io_length, pin_rc->length); 347 | pw1_len = G_gpg_vstate.io_length - rc_len; 348 | sw = gpg_pin_check(pin_rc, 349 | pin_rc->ref, 350 | G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, 351 | rc_len); 352 | if (sw != SW_OK) { 353 | return sw; 354 | } 355 | } 356 | 357 | if ((pw1_len > GPG_MAX_PW_LENGTH) || (pw1_len < GPG_MIN_PW1_LENGTH)) { 358 | return SW_WRONG_DATA; 359 | } 360 | sw = gpg_pin_set(pin_pw1, 361 | G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset + rc_len, 362 | pw1_len); 363 | gpg_io_discard(1); 364 | return sw; 365 | } 366 | -------------------------------------------------------------------------------- /src/gpg_select.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #include "gpg_vars.h" 19 | 20 | const unsigned char C_MF[] = {0x3F, 0x00}; 21 | const unsigned char C_ATR[] = {0x2F, 0x02}; 22 | 23 | /** 24 | * APDU handler to Select the card 25 | * 26 | * @return Status Word 27 | * 28 | */ 29 | int gpg_apdu_select() { 30 | int sw = SW_UNKNOWN; 31 | 32 | // MF 33 | if ((G_gpg_vstate.io_length == sizeof(C_MF)) && 34 | (memcmp(G_gpg_vstate.work.io_buffer, C_MF, G_gpg_vstate.io_length) == 0)) { 35 | gpg_io_discard(0); 36 | sw = SW_OK; 37 | } 38 | // EF.ATR 39 | else if ((G_gpg_vstate.io_length == sizeof(C_ATR)) && 40 | (memcmp(G_gpg_vstate.work.io_buffer, C_ATR, G_gpg_vstate.io_length) == 0)) { 41 | gpg_io_discard(0); 42 | sw = SW_OK; 43 | } 44 | // AID APP 45 | else if ((G_gpg_vstate.io_length == 6) && (memcmp(G_gpg_vstate.work.io_buffer, 46 | (const void *) N_gpg_pstate->AID, 47 | G_gpg_vstate.io_length) == 0)) { 48 | G_gpg_vstate.DO_current = 0; 49 | G_gpg_vstate.DO_reccord = 0; 50 | G_gpg_vstate.DO_offset = 0; 51 | if (G_gpg_vstate.selected == 0) { 52 | G_gpg_vstate.verified_pin[0] = 0; 53 | G_gpg_vstate.verified_pin[1] = 0; 54 | G_gpg_vstate.verified_pin[2] = 0; 55 | G_gpg_vstate.verified_pin[3] = 0; 56 | G_gpg_vstate.verified_pin[4] = 0; 57 | } 58 | 59 | gpg_io_discard(0); 60 | if (N_gpg_pstate->histo[HISTO_OFFSET_STATE] != STATE_ACTIVATE) { 61 | sw = SW_STATE_TERMINATED; 62 | } else { 63 | sw = SW_OK; 64 | } 65 | } 66 | // NOT FOUND 67 | else { 68 | sw = SW_FILE_NOT_FOUND; 69 | } 70 | return sw; 71 | } 72 | -------------------------------------------------------------------------------- /src/gpg_ux.c: -------------------------------------------------------------------------------- 1 | 2 | /***************************************************************************** 3 | * Ledger App OpenPGP. 4 | * (c) 2024 Ledger SAS. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | *****************************************************************************/ 18 | 19 | #include "gpg_vars.h" 20 | #include "gpg_ux.h" 21 | #include "usbd_ccid_if.h" 22 | 23 | /** 24 | * Reset CCID 25 | * 26 | */ 27 | void ui_CCID_reset(void) { 28 | io_usb_ccid_set_card_inserted(0); 29 | io_usb_ccid_set_card_inserted(1); 30 | } 31 | 32 | /** 33 | * Exit app 34 | * 35 | */ 36 | void app_quit(void) { 37 | // exit app here 38 | os_sched_exit(0); 39 | } 40 | 41 | /** 42 | * Reset app 43 | * 44 | */ 45 | void app_reset(void) { 46 | unsigned char magic[MAGIC_LENGTH]; 47 | 48 | explicit_bzero(magic, MAGIC_LENGTH); 49 | nvm_write((void*) (N_gpg_pstate->magic), magic, MAGIC_LENGTH); 50 | gpg_init(); 51 | ui_CCID_reset(); 52 | } 53 | -------------------------------------------------------------------------------- /src/gpg_ux.h: -------------------------------------------------------------------------------- 1 | 2 | /***************************************************************************** 3 | * Ledger App OpenPGP. 4 | * (c) 2024 Ledger SAS. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | *****************************************************************************/ 18 | 19 | #ifndef GPG_UX_H 20 | #define GPG_UX_H 21 | 22 | #define STR(x) #x 23 | #define XSTR(x) STR(x) 24 | 25 | #define LABEL_SIG "Signature" 26 | #define LABEL_AUT "Authentication" 27 | #define LABEL_DEC "Decryption" 28 | 29 | #define LABEL_RSA2048 "RSA 2048" 30 | #define LABEL_RSA3072 "RSA 3072" 31 | #define LABEL_RSA4096 "RSA 4096" 32 | #define LABEL_SECP256K1 "SECP 256K1" 33 | #define LABEL_SECP256R1 "SECP 256R1" 34 | #define LABEL_Ed25519 "Ed25519" 35 | 36 | void ui_CCID_reset(void); 37 | void app_quit(void); 38 | void app_reset(void); 39 | void ui_init(void); 40 | void ui_menu_pinconfirm_display(unsigned int value); 41 | void ui_menu_pinentry_display(unsigned int value); 42 | void ui_menu_uifconfirm_display(unsigned int value); 43 | 44 | extern void app_exit(void); 45 | 46 | #endif // GPG_UX_H 47 | -------------------------------------------------------------------------------- /src/gpg_ux_msg.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | const char *const C_TEMPLATE_TYPE = "Key type"; 19 | const char *const C_TEMPLATE_KEY = "Key"; 20 | const char *const C_INVALID_SELECTION = "Invalid selection"; 21 | 22 | const char *const C_OK = "OK"; 23 | const char *const C_NOK = "NOK"; 24 | 25 | const char *const C_WRONG_PIN = "PIN Incorrect"; 26 | const char *const C_RIGHT_PIN = "PIN Correct"; 27 | const char *const C_PIN_CHANGED = "PIN changed"; 28 | const char *const C_PIN_LOCKED = "PIN locked"; 29 | const char *const C_PIN_DIFFERS = "2 PINs differs"; 30 | const char *const C_PIN_USER = "User PIN"; 31 | const char *const C_PIN_ADMIN = "Admin PIN"; 32 | 33 | const char *const C_VERIFIED = "Verified"; 34 | const char *const C_NOT_VERIFIED = "Not Verified"; 35 | const char *const C_ALLOWED = "Allowed"; 36 | const char *const C_NOT_ALLOWED = "Not Allowed "; 37 | 38 | const char *const C_DEFAULT_MODE = "Default mode"; 39 | 40 | const char *const C_UIF_LOCKED = "UIF locked"; 41 | 42 | const char *const C_EMPTY = ""; 43 | -------------------------------------------------------------------------------- /src/gpg_ux_msg.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #ifndef GPG_UX_MSG_H 19 | #define GPG_UX_MSG_H 20 | 21 | extern const char *const C_TEMPLATE_TYPE; 22 | extern const char *const C_TEMPLATE_KEY; 23 | extern const char *const C_INVALID_SELECTION; 24 | 25 | extern const char *const C_OK; 26 | extern const char *const C_NOK; 27 | 28 | extern const char *const C_WRONG_PIN; 29 | extern const char *const C_RIGHT_PIN; 30 | extern const char *const C_PIN_CHANGED; 31 | extern const char *const C_PIN_LOCKED; 32 | extern const char *const C_PIN_DIFFERS; 33 | extern const char *const C_PIN_USER; 34 | extern const char *const C_PIN_ADMIN; 35 | 36 | extern const char *const C_VERIFIED; 37 | extern const char *const C_NOT_VERIFIED; 38 | extern const char *const C_NOT_ALLOWED; 39 | 40 | extern const char *const C_DEFAULT_MODE; 41 | 42 | extern const char *const C_UIF_LOCKED; 43 | extern const char *const C_UIF_INVALID; 44 | 45 | extern const char *const C_EMPTY; 46 | 47 | #define PICSTR(x) ((char *) PIC(x)) 48 | 49 | #define TEMPLATE_TYPE PICSTR(C_TEMPLATE_TYPE) 50 | #define TEMPLATE_KEY PICSTR(C_TEMPLATE_KEY) 51 | #define INVALID_SELECTION PICSTR(C_INVALID_SELECTION) 52 | #define OK PICSTR(C_OK) 53 | #define NOK PICSTR(C_NOK) 54 | #define WRONG_PIN PICSTR(C_WRONG_PIN) 55 | #define RIGHT_PIN PICSTR(C_RIGHT_PIN) 56 | #define PIN_CHANGED PICSTR(C_PIN_CHANGED) 57 | #define PIN_LOCKED PICSTR(C_PIN_LOCKED) 58 | #define PIN_DIFFERS PICSTR(C_PIN_DIFFERS) 59 | #define PIN_USER PICSTR(C_PIN_USER) 60 | #define PIN_ADMIN PICSTR(C_PIN_ADMIN) 61 | #define VERIFIED PICSTR(C_VERIFIED) 62 | #define NOT_VERIFIED PICSTR(C_NOT_VERIFIED) 63 | #define ALLOWED PICSTR(C_ALLOWED) 64 | #define NOT_ALLOWED PICSTR(C_NOT_ALLOWED) 65 | #define DEFAULT_MODE PICSTR(C_DEFAULT_MODE) 66 | #define UIF_LOCKED PICSTR(C_UIF_LOCKED) 67 | #define UIF_INVALID PICSTR(C_UIF_INVALID) 68 | #define EMPTY PICSTR(C_EMPTY) 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /src/gpg_vars.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #include "gpg_vars.h" 19 | 20 | gpg_v_state_t G_gpg_vstate; 21 | 22 | const gpg_nv_state_t N_state_pic; 23 | -------------------------------------------------------------------------------- /src/gpg_vars.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * Ledger App OpenPGP. 3 | * (c) 2024 Ledger SAS. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | *****************************************************************************/ 17 | 18 | #ifndef GPG_VARS_H 19 | #define GPG_VARS_H 20 | 21 | #include "os.h" 22 | #include "cx.h" 23 | #include "ux.h" 24 | #include "gpg_types.h" 25 | #include "gpg_api.h" 26 | 27 | extern const unsigned char C_ext_capabilities[10]; 28 | extern const unsigned char C_ext_length[8]; 29 | extern const unsigned char C_OID_SECP256K1[5]; 30 | extern const unsigned char C_OID_SECP256R1[8]; 31 | extern const unsigned char C_OID_BRAINPOOL256R1[9]; 32 | extern const unsigned char C_OID_BRAINPOOL256T1[9]; 33 | extern const unsigned char C_OID_Ed25519[9]; 34 | extern const unsigned char C_OID_cv25519[10]; 35 | extern const unsigned char C_gen_feature; 36 | 37 | extern gpg_v_state_t G_gpg_vstate; 38 | 39 | extern const gpg_nv_state_t N_state_pic; 40 | #define N_gpg_pstate ((volatile gpg_nv_state_t *) PIC(&N_state_pic)) 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /tests/application_client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/application_client/__init__.py -------------------------------------------------------------------------------- /tests/application_client/app_def.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests Client application. 6 | It contains the applicatuion definitions. 7 | """ 8 | from enum import IntEnum 9 | 10 | 11 | class ClaType(IntEnum): 12 | """Application ID definitions""" 13 | # Application CLA 14 | CLA_APP = 0x00 15 | CLA_APP_CHAIN = 0x10 16 | # Special CLA for Verify with pinpad 17 | CLA_APP_VERIFY = 0xEF 18 | 19 | 20 | class InsType(IntEnum): 21 | """Application Command ID definitions""" 22 | INS_ACTIVATE_FILE = 0x44 23 | INS_SELECT = 0xA4 24 | INS_TERMINATE_DF = 0xE6 25 | INS_VERIFY = 0x20 26 | INS_CHANGE_REF_DATA = 0x24 27 | INS_RESET_RC = 0x2C 28 | INS_GET_DATA = 0xCA 29 | INS_PUT_DATA = 0xDA 30 | INS_GEN_ASYM_KEYPAIR = 0x47 31 | INS_GET_RESPONSE = 0xC0 32 | INS_PSO = 0x2A 33 | INS_INT_AUTHENTICATE = 0x88 34 | INS_GET_CHALLENGE = 0x84 35 | INS_MSE = 0x22 36 | 37 | 38 | class PubkeyAlgo(IntEnum): 39 | """Public-Key Algorithm IDs definition""" 40 | # https://www.rfc-editor.org/rfc/rfc4880#section-9.1 41 | 42 | INVALID = 0 43 | 44 | # RSA (Encrypt or Sign) 45 | RSA = 1 46 | # Elliptic Curve Diffie-Hellman 47 | ECDH = 18 48 | # Elliptic Curve Digital Signature Algorithm 49 | ECDSA = 19 50 | # Edwards-curve Digital Signature Algorithm 51 | EDDSA = 22 52 | 53 | 54 | class PassWord(IntEnum): 55 | """Password type definition""" 56 | # USER_PIN for only one PSO:CDS command 57 | PW1 = 0x81 58 | # USER_PIN for several attempts 59 | PW2 = 0x82 60 | # Admin PIN 61 | PW3 = 0x83 62 | 63 | 64 | class Errors(IntEnum): 65 | """Application Errors definitions""" 66 | SW_STATE_TERMINATED = 0x6285 67 | SW_MEMORY = 0x6581 68 | SW_SECURITY = 0x6600 69 | SW_WRONG_LENGTH = 0x6700 70 | SW_LOGICAL_CHANNEL_NOT_SUPPORTED = 0x6881 71 | SW_SECURE_MESSAGING_NOT_SUPPORTED = 0x6882 72 | SW_LAST_COMMAND_EXPECTED = 0x6883 73 | SW_COMMAND_CHAINING_NOT_SUPPORTED = 0x6884 74 | SW_SECURITY_STATUS_NOT_SATISFIED = 0x6982 75 | SW_AUTH_METHOD_BLOCKED = 0x6983 76 | SW_DATA_INVALID = 0x6984 77 | SW_CONDITIONS_NOT_SATISFIED = 0x6985 78 | SW_COMMAND_NOT_ALLOWED = 0x6986 79 | SW_EXPECTED_SM_MISSING = 0x6987 80 | SW_SM_DATA_INCORRECT = 0x6988 81 | SW_WRONG_DATA = 0x6a80 82 | SW_FILE_NOT_FOUND = 0x6a82 83 | SW_INCORRECT_P1P2 = 0x6a86 84 | SW_REFERENCED_DATA_NOT_FOUND = 0x6a88 85 | SW_WRONG_P1P2 = 0x6b00 86 | SW_INS_NOT_SUPPORTED = 0x6d00 87 | SW_CLA_NOT_SUPPORTED = 0x6e00 88 | SW_UNKNOWN = 0x6f00 89 | SW_OK = 0x9000 90 | SW_CORRECT_LONG_RESPONSE = 0x6100 91 | 92 | 93 | class DataObject(IntEnum): 94 | """Data Objects definition""" 95 | 96 | # [Read] Full Application identifier (AID), ISO 7816-4 97 | DO_AID = 0x4F 98 | 99 | # [Read/Write] Name according to ISO/IEC 7501-1) 100 | DO_CARD_NAME = 0x5B 101 | # [Read/Write] Login data 102 | DO_LOGIN = 0x5E 103 | # [Read] Cardholder Related Data 104 | DO_CARDHOLDER_DATA = 0x65 105 | # [Read] Application Related Data 106 | DO_APP_DATA = 0x6E 107 | # [Read] Discretionary data objects 108 | DO_DISCRET_DATA = 0x73 109 | 110 | # [Read/Write] Digital signature 111 | DO_SIG_KEY = 0xB6 112 | # [Read/Write] Confidentiality 113 | DO_DEC_KEY = 0xB8 114 | # [Read/Write] Authentication 115 | DO_AUT_KEY = 0xA4 116 | 117 | # [Read/Write] Algorithm attributes SIGnature 118 | DO_SIG_ATTR = 0xC1 119 | # [Read/Write] Algorithm attributes DECryption 120 | DO_DEC_ATTR = 0xC2 121 | # [Read/Write] Algorithm attributes AUThentication 122 | DO_AUT_ATTR = 0xC3 123 | # [Write] AES symmetric key 124 | DO_KEY_AES = 0xD5 125 | 126 | # [Read/Write] User Interaction Flag (UIF) for PSO:CDS 127 | DO_UIF_SIG = 0xD6 128 | # [Read/Write] User Interaction Flag (UIF) for PSO:DEC 129 | DO_UIF_DEC = 0xD7 130 | # [Read/Write] User Interaction Flag (UIF) for PSO:AUT 131 | DO_UIF_AUT = 0xD8 132 | 133 | # [Read/Write] Asymmetric Key Pair 134 | DO_PUB_KEY = 0x7F49 135 | 136 | # [Read/Write] Slot config 137 | CMD_SLOT_CFG = 0x01F1 138 | # [Read/Write] Slot selection 139 | CMD_SLOT_CUR = 0x01F2 140 | 141 | # [Read/Write] Language preferences (according to ISO 639) 142 | DO_CARD_LANG = 0x5F2D 143 | # [Read/Write] Salutation (according to ISO 5218) 144 | DO_CARD_SALUTATION = 0x5F35 145 | # [Read/Write] Uniform resource locator (URL, as defined in RFC 1738) 146 | DO_URL = 0x5F50 147 | -------------------------------------------------------------------------------- /tests/application_client/response_unpacker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests Client application. 6 | It contains the response parsing part. 7 | """ 8 | from typing import Tuple 9 | 10 | 11 | def _pop_sized_buf_from_buffer(buffer:bytes, size:int) -> Tuple[bytes, bytes]: 12 | """Parse buffer and returns: remainder, data[size]""" 13 | 14 | return buffer[size:], buffer[0:size] 15 | 16 | 17 | def unpack_info_response(response: bytes) -> Tuple[str, str]: 18 | """Unpack response for AID: 19 | RID (5) 20 | Application (1) 21 | Version (2) 22 | Manufacturer (2) 23 | Serial (4) 24 | RFU (2) 25 | """ 26 | 27 | assert len(response) == 16 28 | response, rid = _pop_sized_buf_from_buffer(response, 5) 29 | response, app = _pop_sized_buf_from_buffer(response, 1) 30 | response, version = _pop_sized_buf_from_buffer(response, 2) 31 | response, manuf = _pop_sized_buf_from_buffer(response, 2) 32 | response, serial = _pop_sized_buf_from_buffer(response, 4) 33 | assert rid.hex() == "d276000124" 34 | assert app.hex() == "01" 35 | assert manuf.hex() == "2c97" 36 | 37 | return (version.hex(), serial.hex()) 38 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # from ragger.conftest import configuration 2 | 3 | ########################### 4 | ### CONFIGURATION START ### 5 | ########################### 6 | 7 | # You can configure optional parameters by overriding the value of 8 | # ragger.configuration.OPTIONAL_CONFIGURATION 9 | # Please refer to ragger/conftest/configuration.py for their descriptions and accepted values 10 | 11 | ######################### 12 | ### CONFIGURATION END ### 13 | ######################### 14 | 15 | # Pull all features from the base ragger conftest using the overridden configuration 16 | pytest_plugins = ("ragger.conftest.base_conftest", ) 17 | 18 | 19 | ########################## 20 | # CONFIGURATION OVERRIDE # 21 | ########################## 22 | 23 | BACKENDS = ["speculos"] 24 | 25 | def pytest_addoption(parser): 26 | parser.addoption("--full", action="store_true", help="Run full tests") 27 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | ragger[speculos,ledgerwallet] 3 | ledgered 4 | -------------------------------------------------------------------------------- /tests/setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | addopts = --strict-markers --strict-config 3 | console_output_style = count 4 | # log_cli = True 5 | 6 | [pylint] 7 | disable = C0114, # missing-module-docstring 8 | C0115, # missing-class-docstring 9 | C0116, # missing-function-docstring 10 | C0103, # invalid-name 11 | R0801, # duplicate-code 12 | R0904, # too-many-public-methods 13 | R0913 # too-many-arguments 14 | max-line-length=120 15 | extension-pkg-whitelist=hid 16 | 17 | [pycodestyle] 18 | max-line-length = 100 19 | 20 | [mypy-hid.*] 21 | ignore_missing_imports = True 22 | 23 | [mypy-pytest.*] 24 | ignore_missing_imports = True 25 | -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00000.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00001.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00002.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00003.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00004.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00005.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00006.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00007.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00008.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00009.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00010.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00011.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00012.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00013.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00014.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_settings/00015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_settings/00015.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_slot/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_slot/00000.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_menu_slot/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_menu_slot/00001.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_verify_confirm_accepted/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_verify_confirm_accepted/00000.png -------------------------------------------------------------------------------- /tests/snapshots/flex/test_verify_confirm_refused/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/flex/test_verify_confirm_refused/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00001.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00002.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00003.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00004.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00005.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00006.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00007.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00008.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00009.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00010.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00011.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00012.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00013.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00014.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00015.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00016.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00017.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00018.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00019.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00020.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00021.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00022.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00023.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00024.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00025.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00026.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00027.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00027.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00028.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00028.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00029.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00029.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00030.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00031.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00032.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00033.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00034.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00034.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00035.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00036.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00037.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00037.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00038.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00038.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00039.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00039.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00040.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00041.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00042.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00042.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00043.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00044.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00044.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00045.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00045.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00046.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00046.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00047.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00047.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00048.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00049.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00049.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00050.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00050.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00051.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00051.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00052.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00052.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_menu_settings/00053.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_menu_settings/00053.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_verify_confirm_accepted/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_verify_confirm_accepted/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanos/test_verify_confirm_refused/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanos/test_verify_confirm_refused/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00001.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00002.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00003.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00004.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00005.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00006.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00007.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00008.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00009.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00010.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00011.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00012.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00013.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00014.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00015.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00016.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00017.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00018.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00019.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00020.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00021.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00022.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00023.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00024.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00025.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00026.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00027.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00027.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00028.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00028.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00029.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00029.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00030.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00031.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00032.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00033.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00034.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00034.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00035.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00036.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00037.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00037.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00038.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00038.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00039.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00039.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00040.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00041.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00042.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00042.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_settings/00043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_settings/00043.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_slot/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_slot/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_slot/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_slot/00001.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_slot/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_slot/00002.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_slot/00003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_slot/00003.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_slot/00004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_slot/00004.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_slot/00005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_slot/00005.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_slot/00006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_slot/00006.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_slot/00007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_slot/00007.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_menu_slot/00008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_menu_slot/00008.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_verify_confirm_accepted/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_verify_confirm_accepted/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_verify_confirm_accepted/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_verify_confirm_accepted/00001.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_verify_confirm_accepted/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_verify_confirm_accepted/00002.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_verify_confirm_refused/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_verify_confirm_refused/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanosp/test_verify_confirm_refused/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanosp/test_verify_confirm_refused/00001.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00001.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00002.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00003.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00004.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00005.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00006.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00007.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00008.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00009.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00010.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00011.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00012.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00013.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00014.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00015.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00016.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00016.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00017.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00017.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00018.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00019.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00020.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00021.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00022.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00023.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00023.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00024.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00025.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00026.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00026.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00027.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00027.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00028.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00028.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00029.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00029.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00030.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00030.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00031.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00032.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00032.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00033.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00034.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00034.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00035.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00036.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00036.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00037.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00037.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00038.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00038.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00039.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00039.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00040.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00041.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00042.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00042.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_settings/00043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_settings/00043.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_slot/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_slot/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_slot/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_slot/00001.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_slot/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_slot/00002.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_slot/00003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_slot/00003.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_slot/00004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_slot/00004.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_slot/00005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_slot/00005.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_slot/00006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_slot/00006.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_slot/00007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_slot/00007.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_menu_slot/00008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_menu_slot/00008.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_verify_confirm_accepted/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_verify_confirm_accepted/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_verify_confirm_accepted/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_verify_confirm_accepted/00001.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_verify_confirm_accepted/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_verify_confirm_accepted/00002.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_verify_confirm_refused/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_verify_confirm_refused/00000.png -------------------------------------------------------------------------------- /tests/snapshots/nanox/test_verify_confirm_refused/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/nanox/test_verify_confirm_refused/00001.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00000.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00001.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00002.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00003.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00004.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00005.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00006.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00007.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00008.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00009.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00010.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00011.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00012.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00013.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_settings/00014.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_settings/00014.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_slot/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_slot/00000.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_slot/00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_slot/00001.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_menu_slot/00002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_menu_slot/00002.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_verify_confirm_accepted/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_verify_confirm_accepted/00000.png -------------------------------------------------------------------------------- /tests/snapshots/stax/test_verify_confirm_refused/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/tests/snapshots/stax/test_verify_confirm_refused/00000.png -------------------------------------------------------------------------------- /tests/test_cipher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests for Cipher feature 6 | """ 7 | from Crypto.Random import get_random_bytes 8 | from Crypto.Cipher import PKCS1_v1_5 9 | 10 | from ragger.backend import BackendInterface 11 | 12 | from application_client.command_sender import CommandSender 13 | from application_client.app_def import Errors, DataObject, PassWord 14 | 15 | from utils import check_pincode, generate_key, get_RSA_pub_key 16 | 17 | 18 | # In this test we check the symmetric key encryption 19 | def test_AES(backend: BackendInterface) -> None: 20 | # Use the app interface instead of raw interface 21 | client = CommandSender(backend) 22 | 23 | # Verify PW3 (Admin) 24 | check_pincode(client, PassWord.PW3) 25 | 26 | key = get_random_bytes(32) 27 | # Store the AES Key 28 | rapdu = client.put_data(DataObject.DO_KEY_AES, key) 29 | assert rapdu.status == Errors.SW_OK 30 | 31 | # Verify PW2 (User) 32 | check_pincode(client, PassWord.PW2) 33 | 34 | # Encrypt the data 35 | plain = get_random_bytes(16) 36 | rapdu = client.encrypt(plain) 37 | assert rapdu.status == Errors.SW_OK 38 | 39 | # Decrypt the data 40 | rapdu = client.decrypt(rapdu.data) 41 | assert rapdu.status == Errors.SW_OK 42 | 43 | assert rapdu.data == plain 44 | 45 | 46 | # In this test we check the symmetric key encryption 47 | def test_Asym(backend: BackendInterface) -> None: 48 | # Use the app interface instead of raw interface 49 | client = CommandSender(backend) 50 | 51 | # Generate the DEC Key Pair 52 | generate_key(client, DataObject.DO_DEC_KEY) 53 | 54 | # Verify PW2 (User) 55 | check_pincode(client, PassWord.PW2) 56 | 57 | # Read the DEC pub Key 58 | pubkey = get_RSA_pub_key(client, DataObject.DO_DEC_KEY) 59 | 60 | # Encrypt random bytes with Pub Key 61 | plain = get_random_bytes(32) 62 | cipher = PKCS1_v1_5.new(pubkey) 63 | ciphertext = cipher.encrypt(plain) 64 | 65 | # Decrypt the data with the Private key 66 | rapdu = client.decrypt_asym(ciphertext) 67 | assert rapdu.status == Errors.SW_OK 68 | 69 | assert rapdu.data == plain 70 | 71 | 72 | # In this test we check the symmetric key encryption with MSE 73 | def test_MSE(backend: BackendInterface) -> None: 74 | # Use the app interface instead of raw interface 75 | client = CommandSender(backend) 76 | 77 | # Generate the AUT Key Pair 78 | generate_key(client, DataObject.DO_AUT_KEY) 79 | 80 | # Verify PW2 (User) 81 | check_pincode(client, PassWord.PW2) 82 | 83 | # Read the AUT pub Key 84 | pubkey = get_RSA_pub_key(client, DataObject.DO_AUT_KEY) 85 | 86 | # Encrypt random bytes with Pub Key 87 | plain = get_random_bytes(32) 88 | cipher = PKCS1_v1_5.new(pubkey) 89 | ciphertext = cipher.encrypt(plain) 90 | 91 | # Change default DEC key by AUT 92 | rapdu = client.manage_security_env(DataObject.DO_DEC_KEY, 3) 93 | assert rapdu.status == Errors.SW_OK 94 | 95 | # Decrypt the data with the Private key 96 | rapdu = client.decrypt_asym(ciphertext) 97 | assert rapdu.status == Errors.SW_OK 98 | 99 | assert rapdu.data == plain 100 | -------------------------------------------------------------------------------- /tests/test_password.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests for Password feature 6 | """ 7 | 8 | from pathlib import Path 9 | import pytest 10 | from ledgered.devices import Device 11 | from ragger.error import ExceptionRAPDU 12 | from ragger.backend import BackendInterface 13 | from ragger.navigator import Navigator 14 | 15 | from application_client.command_sender import CommandSender 16 | from application_client.app_def import Errors, PassWord 17 | 18 | 19 | from utils import util_navigate 20 | 21 | 22 | # In this test we check the card Password verification 23 | @pytest.mark.parametrize( 24 | "pwd, value", 25 | [ 26 | (PassWord.PW1, "123456"), 27 | (PassWord.PW2, "123456"), 28 | (PassWord.PW3, "12345678"), 29 | ], 30 | ) 31 | def test_verify(backend: BackendInterface, pwd: PassWord, value: str) -> None: 32 | # Use the app interface instead of raw interface 33 | client = CommandSender(backend) 34 | 35 | # Verify PW status - Not yet verified 36 | with pytest.raises(ExceptionRAPDU) as err: 37 | client.send_verify_pw(pwd) 38 | assert err.value.status & 0xFFF0 == 0x63c0 39 | 40 | # Verify PW with its value 41 | rapdu = client.send_verify_pw(pwd, value) 42 | assert rapdu.status == Errors.SW_OK 43 | 44 | # Verify PW status 45 | rapdu = client.send_verify_pw(pwd) 46 | assert rapdu.status == Errors.SW_OK 47 | 48 | # Verify PW Reset Status 49 | rapdu = client.send_verify_pw(pwd, reset=True) 50 | assert rapdu.status == Errors.SW_OK 51 | 52 | # Verify PW status - Not yet verified 53 | with pytest.raises(ExceptionRAPDU) as err: 54 | client.send_verify_pw(pwd) 55 | assert err.value.status & 0xFFF0 == 0x63c0 56 | 57 | 58 | def test_verify_wrong(backend: BackendInterface) -> None: 59 | # Use the app interface instead of raw interface 60 | client = CommandSender(backend) 61 | 62 | # Verify PW status - Wrong Password 63 | with pytest.raises(ExceptionRAPDU) as err: 64 | client.send_verify_pw(PassWord.PW1, "999999") 65 | assert err.value.status == Errors.SW_SECURITY_STATUS_NOT_SATISFIED 66 | 67 | 68 | # In this test we check the card Password verification with Pinpad 69 | def test_verify_confirm_accepted(device: Device, 70 | backend: BackendInterface, 71 | navigator: Navigator, 72 | test_name: Path) -> None: 73 | # Use the app interface instead of raw interface 74 | client = CommandSender(backend) 75 | 76 | # Send the APDU (Asynchronous) 77 | with client.send_verify_pw_with_confirmation(PassWord.PW1): 78 | util_navigate(device, navigator, test_name, "Confirm_Yes") 79 | 80 | # Check the status (Asynchronous) 81 | response = client.get_async_response() 82 | assert response and response.status == Errors.SW_OK 83 | 84 | 85 | # In this test we check the Rejected card Password verification with Pinpad 86 | def test_verify_confirm_refused(device: Device, 87 | backend: BackendInterface, 88 | navigator: Navigator, 89 | test_name: Path) -> None: 90 | # Use the app interface instead of raw interface 91 | client = CommandSender(backend) 92 | 93 | # Send the APDU (Asynchronous) 94 | with pytest.raises(ExceptionRAPDU) as err: 95 | with client.send_verify_pw_with_confirmation(PassWord.PW1): 96 | util_navigate(device, navigator, test_name, "Confirm_No") 97 | 98 | # Assert we have received a refusal 99 | assert err.value.status == Errors.SW_CONDITIONS_NOT_SATISFIED 100 | assert len(err.value.data) == 0 101 | 102 | 103 | # In this test we check the Password Update 104 | @pytest.mark.parametrize( 105 | "pwd, actual, new", 106 | [ 107 | (PassWord.PW1, "123456", "654321"), 108 | (PassWord.PW3, "12345678", "87654321"), 109 | ], 110 | ) 111 | def test_change(backend: BackendInterface, pwd: PassWord, actual: str, new: str) -> None: 112 | # Use the app interface instead of raw interface 113 | client = CommandSender(backend) 114 | 115 | # Verify PW with its value 116 | rapdu = client.send_verify_pw(pwd, actual) 117 | assert rapdu.status == Errors.SW_OK 118 | 119 | # Change PW value 120 | rapdu = client.send_change_pw(pwd, actual, new) 121 | assert rapdu.status == Errors.SW_OK 122 | 123 | # Verify PW status 124 | rapdu = client.send_verify_pw(pwd, new) 125 | assert rapdu.status == Errors.SW_OK 126 | 127 | 128 | # In this test we check the Password Reset 129 | def test_reset(backend: BackendInterface) -> None: 130 | # Use the app interface instead of raw interface 131 | client = CommandSender(backend) 132 | 133 | # Verify PW1 134 | rapdu = client.send_verify_pw(PassWord.PW1, "123456") 135 | assert rapdu.status == Errors.SW_OK 136 | 137 | # Verify PW3 (Admin) 138 | rapdu = client.send_verify_pw(PassWord.PW3, "12345678") 139 | assert rapdu.status == Errors.SW_OK 140 | 141 | # Reset PW1 with a new value 142 | rapdu = client.send_reset_pw("654321") 143 | assert rapdu.status == Errors.SW_OK 144 | 145 | # Verify PW status 146 | rapdu = client.send_verify_pw(PassWord.PW1, "654321") 147 | assert rapdu.status == Errors.SW_OK 148 | 149 | 150 | # In this test we check the Get Challenge 151 | def test_challenge(backend: BackendInterface) -> None: 152 | # Use the app interface instead of raw interface 153 | client = CommandSender(backend) 154 | 155 | # Get Random number 156 | rapdu = client.get_challenge(32) 157 | assert rapdu.status == Errors.SW_OK 158 | print(f"Random: {rapdu.data.hex()}") 159 | -------------------------------------------------------------------------------- /tests/test_seed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests for Signing feature with SEED mode 6 | """ 7 | import sys 8 | from typing import Union 9 | import pytest 10 | 11 | from Crypto.PublicKey.RSA import RsaKey 12 | from Crypto.PublicKey.ECC import EccKey 13 | 14 | from ragger.backend import BackendInterface 15 | 16 | from application_client.command_sender import CommandSender 17 | from application_client.app_def import Errors, DataObject, PassWord, PubkeyAlgo 18 | 19 | from utils import get_RSA_pub_key, get_ECDSA_pub_key, get_EDDSA_pub_key 20 | from utils import check_pincode, generate_key, get_key_attributes, KEY_TEMPLATES 21 | 22 | 23 | def _gen_key(client: CommandSender, template: str) -> Union[RsaKey,EccKey]: 24 | 25 | # Verify PW3 (Admin) 26 | check_pincode(client, PassWord.PW3) 27 | 28 | # Set SIG key template 29 | rapdu = client.set_template(DataObject.DO_SIG_ATTR, KEY_TEMPLATES[template]) 30 | assert rapdu.status == Errors.SW_OK 31 | 32 | # Generate the SIG Key Pair in SEED mode 33 | generate_key(client, DataObject.DO_SIG_KEY, True) 34 | 35 | key_algo, _ = get_key_attributes(client, DataObject.DO_SIG_ATTR) 36 | 37 | # Read the SIG pub Key 38 | if key_algo == PubkeyAlgo.RSA: 39 | return get_RSA_pub_key(client, DataObject.DO_SIG_KEY) 40 | if key_algo == PubkeyAlgo.ECDSA: 41 | return get_ECDSA_pub_key(client, DataObject.DO_SIG_KEY) 42 | if key_algo == PubkeyAlgo.EDDSA: 43 | return get_EDDSA_pub_key(client, DataObject.DO_SIG_KEY) 44 | 45 | raise ValueError 46 | 47 | 48 | @pytest.mark.parametrize( 49 | "template", 50 | [ 51 | "rsa2048", 52 | pytest.param("rsa3072", marks=pytest.mark.skipif("--full" not in sys.argv, reason="skipping long test")), 53 | # pytest.param("rsa4096", marks=pytest.mark.skipif("--full" not in sys.argv, reason="skipping long test")), 54 | "nistp256", # ECDSA 55 | "ed25519", # EdDSA 56 | # "cv25519", # ECDH, SDK returns CX_EC_INVALID_CURVE 57 | ], 58 | ) 59 | def test_seed_key(backend: BackendInterface, template: str) -> None: 60 | # Use the app interface instead of raw interface 61 | client = CommandSender(backend) 62 | 63 | # Generate the key 64 | pubkey1 = _gen_key(client, template) 65 | 66 | # Reset the App (delete the key) 67 | client.send_terminate() 68 | client.send_activate() 69 | 70 | # Ensure the SIG Key is no more available 71 | rapdu = client.read_key(DataObject.DO_SIG_KEY) 72 | assert rapdu.status == Errors.SW_REFERENCED_DATA_NOT_FOUND 73 | 74 | # Generate the key again 75 | pubkey2 = _gen_key(client, template) 76 | 77 | # Check generated keys 78 | assert pubkey1 == pubkey2 79 | -------------------------------------------------------------------------------- /tests/test_sign.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests for Signing feature 6 | """ 7 | from Crypto.Hash import SHA256 8 | from Crypto.PublicKey.RSA import RsaKey 9 | from Crypto.Signature import pkcs1_15 10 | from Crypto.Random import get_random_bytes 11 | 12 | from ragger.backend import BackendInterface 13 | 14 | from application_client.command_sender import CommandSender 15 | from application_client.app_def import Errors, DataObject, PassWord, PubkeyAlgo 16 | 17 | from utils import check_pincode, get_key_attributes, get_RSA_pub_key, generate_key, SHA256_DIGEST_INFO 18 | 19 | 20 | # In this test we check the key pair generation 21 | def test_sign(backend: BackendInterface) -> None: 22 | # Use the app interface instead of raw interface 23 | client = CommandSender(backend) 24 | 25 | # Generate the SIG Key Pair 26 | generate_key(client, DataObject.DO_SIG_KEY) 27 | 28 | # SIG Key attributes 29 | key_algo, key_size = get_key_attributes(client, DataObject.DO_SIG_ATTR) 30 | # Check default config values 31 | assert key_algo == PubkeyAlgo.RSA 32 | assert key_size == 2048 33 | 34 | # Verify PW1 (User) 35 | check_pincode(client, PassWord.PW1) 36 | 37 | # Hash data buffer 38 | hash_obj = SHA256.new(get_random_bytes(16)) 39 | 40 | rapdu = client.sign(SHA256_DIGEST_INFO + hash_obj.digest()) 41 | assert rapdu.status == Errors.SW_OK 42 | 43 | # Verify the signature 44 | _verify_signature(client, hash_obj, DataObject.DO_SIG_KEY, rapdu.data) 45 | 46 | 47 | # In this test we check the key pair generation 48 | def test_auth(backend: BackendInterface) -> None: 49 | # Use the app interface instead of raw interface 50 | client = CommandSender(backend) 51 | 52 | # Generate the AUT Key Pair 53 | generate_key(client, DataObject.DO_AUT_KEY) 54 | 55 | # Verify PW2 (User) 56 | check_pincode(client, PassWord.PW2) 57 | 58 | # Hash data buffer 59 | hash_obj = SHA256.new(get_random_bytes(16)) 60 | 61 | rapdu = client.authenticate(SHA256_DIGEST_INFO + hash_obj.digest()) 62 | assert rapdu.status == Errors.SW_OK 63 | 64 | # Verify the signature 65 | _verify_signature(client, hash_obj, DataObject.DO_AUT_KEY, rapdu.data) 66 | 67 | 68 | def _verify_signature(client: CommandSender, 69 | hash_obj: SHA256.SHA256Hash, 70 | key_tag: DataObject, 71 | signature: bytes) -> None: 72 | # Read the SIG pub Key 73 | pubkey: RsaKey = get_RSA_pub_key(client, key_tag) 74 | # Verify the signature 75 | verifier = pkcs1_15.new(pubkey) 76 | verifier.verify(hash_obj, signature) 77 | -------------------------------------------------------------------------------- /tests/test_slot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests for Slots feature 6 | """ 7 | import pytest 8 | 9 | from ragger.backend import BackendInterface 10 | 11 | from application_client.command_sender import CommandSender 12 | from application_client.app_def import Errors, DataObject, PassWord 13 | 14 | from utils import check_pincode, generate_key 15 | 16 | def test_slot(backend: BackendInterface) -> None: 17 | # Use the app interface instead of raw interface 18 | client = CommandSender(backend) 19 | 20 | # Check slots availability 21 | nb_slots, def_slot = client.get_slot_config() 22 | print("Slots configuration:") 23 | print(f" Nb: {nb_slots}") 24 | print(f" default: {def_slot}") 25 | if nb_slots == 1: 26 | pytest.skip("single slot configuration") 27 | 28 | # Generate the SIG Key Pair 29 | generate_key(client, DataObject.DO_SIG_KEY) 30 | 31 | # Read slot 32 | slot = client.get_slot() 33 | assert slot == 0 34 | 35 | # Read the SIG pub Key 36 | rapdu = client.read_key(DataObject.DO_SIG_KEY) 37 | assert rapdu.status == Errors.SW_OK 38 | 39 | # Verify PW2 40 | check_pincode(client, PassWord.PW2) 41 | 42 | # Change slot 43 | rapdu = client.set_slot(2) 44 | assert rapdu.status == Errors.SW_OK 45 | 46 | # Read slot 47 | slot = client.get_slot() 48 | assert slot == 2 49 | 50 | # Read an empty pub key 51 | rapdu = client.read_key(DataObject.DO_SIG_KEY) 52 | assert rapdu.status == Errors.SW_REFERENCED_DATA_NOT_FOUND 53 | -------------------------------------------------------------------------------- /tests/test_template.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests for Key Templates feature 6 | """ 7 | import sys 8 | import pytest 9 | from Crypto.Hash import SHA256 10 | from Crypto.Signature import pkcs1_15, eddsa 11 | from Crypto.Random import get_random_bytes 12 | from Crypto.Signature import DSS 13 | 14 | from ragger.backend import BackendInterface 15 | 16 | from application_client.command_sender import CommandSender 17 | from application_client.app_def import Errors, DataObject, PassWord, PubkeyAlgo 18 | 19 | from utils import get_RSA_pub_key, get_ECDSA_pub_key, get_EDDSA_pub_key 20 | from utils import check_pincode, generate_key, get_key_attributes 21 | from utils import KEY_TEMPLATES, SHA256_DIGEST_INFO 22 | 23 | 24 | @pytest.mark.parametrize( 25 | "template", 26 | [ 27 | "rsa2048", 28 | pytest.param("rsa3072", marks=pytest.mark.skipif("--full" not in sys.argv, reason="skipping long test")), 29 | # "rsa4096", # Invalid signature? 30 | # "nistp256", # ECDSA, Pb with Pubkey generation? 31 | "ed25519", # EdDSA 32 | ], 33 | ) 34 | def test_sign(backend: BackendInterface, template: str) -> None: 35 | # Use the app interface instead of raw interface 36 | client = CommandSender(backend) 37 | 38 | # Verify PW3 (Admin) 39 | check_pincode(client, PassWord.PW3) 40 | 41 | # Set SIG key template 42 | rapdu = client.set_template(DataObject.DO_SIG_ATTR, KEY_TEMPLATES[template]) 43 | assert rapdu.status == Errors.SW_OK 44 | 45 | # Generate the SIG Key Pair 46 | generate_key(client, DataObject.DO_SIG_KEY) 47 | 48 | key_algo, _ = get_key_attributes(client, DataObject.DO_SIG_ATTR) 49 | 50 | # Hash data buffer 51 | plain = get_random_bytes(16) 52 | hash_obj = SHA256.new(plain) 53 | 54 | # Sign data buffer 55 | if key_algo == PubkeyAlgo.RSA: 56 | digest_info = SHA256_DIGEST_INFO 57 | data = digest_info + hash_obj.digest() 58 | elif key_algo == PubkeyAlgo.ECDSA: 59 | data = hash_obj.digest() 60 | else: 61 | data = plain 62 | 63 | # Verify PW1 (User) 64 | check_pincode(client, PassWord.PW1) 65 | 66 | rapdu = client.sign(data) 67 | assert rapdu.status == Errors.SW_OK 68 | 69 | # Read the SIG pub Key and Verify the signature 70 | if key_algo == PubkeyAlgo.RSA: 71 | pubRsakey = get_RSA_pub_key(client, DataObject.DO_SIG_KEY) 72 | rsaVerifier = pkcs1_15.new(pubRsakey) 73 | rsaVerifier.verify(hash_obj, rapdu.data) 74 | elif key_algo == PubkeyAlgo.ECDSA: 75 | pubEcckey = get_ECDSA_pub_key(client, DataObject.DO_SIG_KEY) 76 | verifier = DSS.new(pubEcckey, 'fips-186-3') 77 | verifier.verify(hash_obj, rapdu.data[2:]) 78 | elif key_algo == PubkeyAlgo.EDDSA: 79 | pubEcckey = get_EDDSA_pub_key(client, DataObject.DO_SIG_KEY) 80 | eddsaVerifier = eddsa.new(pubEcckey, 'rfc8032') 81 | eddsaVerifier.verify(plain, rapdu.data) 82 | else: 83 | raise ValueError 84 | -------------------------------------------------------------------------------- /tests/test_version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests for Version check 6 | """ 7 | from ragger.utils.misc import get_current_app_name_and_version 8 | from ragger.backend import BackendInterface 9 | 10 | from application_client.command_sender import CommandSender 11 | from application_client.app_def import Errors, DataObject, PassWord 12 | from application_client.response_unpacker import unpack_info_response 13 | 14 | from utils import verify_name, verify_version, decode_tlv, check_pincode 15 | 16 | 17 | # In this test we check the App name and version 18 | def test_check_version(backend: BackendInterface) -> None: 19 | """Check version and name""" 20 | 21 | # Send the APDU 22 | app_name, version = get_current_app_name_and_version(backend) 23 | print(f" Name: {app_name}") 24 | print(f" Version: {version}") 25 | verify_name(app_name) 26 | verify_version(version) 27 | 28 | 29 | # In this test we check the Card activation 30 | def test_activate(backend: BackendInterface) -> None: 31 | # Use the app interface instead of raw interface 32 | client = CommandSender(backend) 33 | 34 | # Activate the Card 35 | rapdu = client.send_activate() 36 | assert rapdu.status == Errors.SW_OK 37 | 38 | 39 | # In this test we get the Card Application ID value 40 | def test_info(backend: BackendInterface) -> None: 41 | # Use the app interface instead of raw interface 42 | client = CommandSender(backend) 43 | 44 | # Get info from the Card 45 | rapdu = client.get_data(DataObject.DO_AID) 46 | assert rapdu.status == Errors.SW_OK 47 | aid = rapdu.data.hex() 48 | print(f" AID: {aid}") 49 | assert aid[:12] == "d27600012401" 50 | 51 | # Parse the response 52 | version, serial = unpack_info_response(rapdu.data) 53 | print(f" Version: {int(version[0:2]):d}.{int(version[2:4]):d}") 54 | print(f" Serial: {serial}") 55 | 56 | # Check expected value 57 | assert version == "0303" 58 | 59 | 60 | # In this test we test the User Data information 61 | def test_user(backend: BackendInterface) -> None: 62 | # Use the app interface instead of raw interface 63 | client = CommandSender(backend) 64 | 65 | # Verify PW3 (Admin) 66 | check_pincode(client, PassWord.PW3) 67 | 68 | # Write and Read the 'Login' 69 | _check_user_value(client, DataObject.DO_LOGIN, "John.Doe") 70 | 71 | # Write and Read the 'URL' 72 | _check_user_value(client, DataObject.DO_URL, "This is John Doe URL") 73 | 74 | # Write and Read the 'name' 75 | _check_card_value(client, DataObject.DO_CARD_NAME, "John Doe") 76 | 77 | # Write and Read the 'Lang' 78 | _check_card_value(client, DataObject.DO_CARD_LANG, "fr") 79 | 80 | # Write and Read the 'Salutation' 81 | _check_card_value(client, DataObject.DO_CARD_SALUTATION, "1") 82 | 83 | # Write and Read the 'Serial number' 84 | serial = "12345678" 85 | rapdu = client.put_data(DataObject.DO_AID, bytes.fromhex(serial)) 86 | assert rapdu.status == Errors.SW_OK 87 | 88 | rapdu = client.get_data(DataObject.DO_AID) 89 | assert rapdu.status == Errors.SW_OK 90 | assert rapdu.data.hex()[20:28] == serial 91 | 92 | 93 | def _check_card_value(client: CommandSender, tag: DataObject, value: str) -> None: 94 | 95 | rapdu = client.put_data(tag, value.encode("utf-8")) 96 | assert rapdu.status == Errors.SW_OK 97 | 98 | rapdu = client.get_data(DataObject.DO_CARDHOLDER_DATA) 99 | assert rapdu.status == Errors.SW_OK 100 | tags = decode_tlv(rapdu.data) 101 | rvalue = tags.get(tag, b"").decode("utf-8") 102 | assert rvalue == value 103 | 104 | 105 | def _check_user_value(client: CommandSender, tag: DataObject, value: str) -> None: 106 | 107 | rapdu = client.put_data(tag, value.encode("utf-8")) 108 | assert rapdu.status == Errors.SW_OK 109 | 110 | rapdu = client.get_data(tag) 111 | assert rapdu.status == Errors.SW_OK 112 | assert rapdu.data.decode("utf-8") == value 113 | -------------------------------------------------------------------------------- /tests/usage.md: -------------------------------------------------------------------------------- 1 | # How to use the Ragger test framework 2 | 3 | This framework allows testing the application on the Speculos emulator or on a real device using LedgerComm or LedgerWallet 4 | 5 | ## Quickly get started with Ragger and Speculos 6 | 7 | ### Install ragger and dependencies 8 | 9 | ```shell 10 | pip install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt 11 | sudo apt-get update && sudo apt-get install qemu-user-static 12 | ``` 13 | 14 | ### Compile the application 15 | 16 | The application to test must be compiled for all required devices. 17 | You can use for this the container `ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite`: 18 | 19 | ```shell 20 | docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest 21 | cd 22 | 23 | docker run --user "$(id -u)":"$(id -g)" --rm -ti -v "$(realpath .):/app" --privileged -v "/dev/bus/usb:/dev/bus/usb" ledger-app-builder-lite:latest 24 | make clean && make BOLOS_SDK=$_SDK # replace with one of [NANOS, NANOX, NANOSP, STAX, FLEX] 25 | exit 26 | ``` 27 | 28 | ### Run a simple test using the Speculos emulator 29 | 30 | You can use the following command to get your first experience with Ragger and Speculos 31 | 32 | ```shell 33 | pytest -v --tb=short --device nanox --display 34 | ``` 35 | 36 | Or you can refer to the section `Available pytest options` to configure the options you want to use 37 | 38 | ### Run a simple test using a real device 39 | 40 | The application to test must be loaded and started on a Ledger device plugged in USB. 41 | You can use for this the container `ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite`: 42 | 43 | ```shell 44 | docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest 45 | cd app-/ 46 | 47 | docker run --user "$(id -u)":"$(id -g)" --rm -ti -v "$(realpath .):/app" --privileged -v "/dev/bus/usb:/dev/bus/usb" ledger-app-builder-lite:latest 48 | make clean && make BOLOS_SDK=$_SDK load # replace with one of [NANOS, NANOX, NANOSP, STAX, FLEX] 49 | exit 50 | ``` 51 | 52 | You can use the following command to get your first experience with Ragger and Ledgerwallet on a NANOX. 53 | Make sure that the device is plugged, unlocked, and that the tested application is open. 54 | 55 | ```shell 56 | pytest -v --tb=short --device nanox --backend ledgerwallet 57 | ``` 58 | 59 | Or you can refer to the section `Available pytest options` to configure the options you want to use 60 | 61 | ## Available pytest options 62 | 63 | Standard useful pytest options 64 | 65 | ```shell 66 | -v formats the test summary in a readable way 67 | -s enable logs for successful tests, on Speculos it will enable app logs if compiled with DEBUG=1 68 | -k only run the tests that contain in their names 69 | --tb=short in case of errors, formats the test traceback in a readable way 70 | ``` 71 | 72 | Custom pytest options 73 | 74 | ```shell 75 | --full Run full tests 76 | --device Run the test on the specified device [nanos,nanox,nanosp,stax,flex,all]. This parameter is mandatory 77 | --backend Run the tests against the backend [speculos, ledgercomm, ledgerwallet]. Speculos is the default 78 | --display On Speculos, enables the display of the app screen using QT 79 | --golden_run Pn Speculos, screen comparison functions will save the current screen instead of comparing 80 | --log_apdu_file Log all apdu exchanges to the file in parameter. The previous file content is erased 81 | --seed=SEED Set a custom seed 82 | ``` 83 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # SPDX-FileCopyrightText: 2023 Ledger SAS 3 | # SPDX-License-Identifier: LicenseRef-LEDGER 4 | """ 5 | This module provides Ragger tests utility functions 6 | """ 7 | from pathlib import Path 8 | from typing import List, Tuple 9 | import re 10 | from Crypto.PublicKey import RSA, ECC 11 | from Crypto.Util.number import bytes_to_long 12 | from Crypto.Signature import eddsa 13 | from ledgered.devices import Device, DeviceType 14 | 15 | from ragger.navigator import NavInsID, NavIns, Navigator 16 | 17 | from application_client.command_sender import CommandSender 18 | from application_client.app_def import Errors, PassWord, DataObject, PubkeyAlgo 19 | 20 | ROOT_SCREENSHOT_PATH = Path(__file__).parent.resolve() 21 | 22 | KEY_TEMPLATES = { 23 | "rsa2048" : "010800002001", 24 | "rsa3072" : "010C00002001", 25 | "rsa4096" : "011000002001", 26 | "nistp256": "132A8648CE3D030107", 27 | "ed25519" : "162B06010401DA470F01", 28 | "cv25519" : "122B060104019755010501" 29 | } 30 | 31 | # digestInfo header: https://www.rfc-editor.org/rfc/rfc8017#section-9.2 32 | SHA256_DIGEST_INFO = b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20" 33 | 34 | 35 | def util_navigate( 36 | device: Device, 37 | navigator: Navigator, 38 | test_name: Path, 39 | text: str = "", 40 | ) -> None: 41 | """Navigate in the menus with conditions """ 42 | 43 | assert text 44 | valid_instr: list[NavIns | NavInsID] = [] 45 | 46 | if device.type == DeviceType.NANOS: 47 | text, txt_cfg = text.split("_") 48 | nav_inst = NavInsID.RIGHT_CLICK 49 | if txt_cfg == "Yes": 50 | valid_instr.append(NavInsID.RIGHT_CLICK) 51 | elif txt_cfg == "No": 52 | valid_instr.append(NavInsID.LEFT_CLICK) 53 | else: 54 | raise ValueError(f'Wrong text "{text}"') 55 | 56 | elif device.is_nano: 57 | text = text.split("_")[1] 58 | nav_inst = NavInsID.RIGHT_CLICK 59 | valid_instr.append(NavInsID.BOTH_CLICK) 60 | 61 | else: 62 | text, txt_cfg = text.split("_") 63 | if txt_cfg == "Yes": 64 | nav_inst = NavInsID.USE_CASE_CHOICE_CONFIRM 65 | valid_instr.append(NavInsID.USE_CASE_CHOICE_CONFIRM) 66 | elif txt_cfg == "No": 67 | nav_inst = NavInsID.USE_CASE_REVIEW_REJECT 68 | valid_instr.append(NavInsID.USE_CASE_CHOICE_REJECT) 69 | else: 70 | raise ValueError(f'Wrong text "{text}"') 71 | 72 | # Do not wait last screen change because home screen contain a "random" ID 73 | navigator.navigate_until_text_and_compare(nav_inst, 74 | valid_instr, 75 | text, 76 | ROOT_SCREENSHOT_PATH, 77 | test_name, 78 | screen_change_after_last_instruction=False) 79 | 80 | 81 | def generate_key(client: CommandSender, key_tag: DataObject, seed: bool = False) -> None: 82 | """Generate a Asymmetric key 83 | 84 | Args: 85 | client (CommandSender): Application object 86 | key_tag (DataObject): Tag identifying the key 87 | seed (bool): Generate a key in SEED mode 88 | """ 89 | 90 | # Verify PW3 (Admin) 91 | check_pincode(client, PassWord.PW3) 92 | 93 | # Generate the SIG Key Pair 94 | rapdu = client.generate_key(key_tag, seed) 95 | assert rapdu.status == Errors.SW_OK 96 | 97 | 98 | def get_key_attributes(client: CommandSender, key_attr: int) -> Tuple[PubkeyAlgo, int]: 99 | """Send and check the pincode 100 | 101 | Args: 102 | client (CommandSender): Application object 103 | key_attr (int): Key related attribute to be parsed 104 | 105 | Returns: 106 | Public-Key Algorithm ID and Key size 107 | """ 108 | 109 | rapdu = client.get_data(DataObject.DO_APP_DATA) 110 | assert rapdu.status == Errors.SW_OK 111 | 112 | tags = decode_tlv(rapdu.data) 113 | data1 = tags.get(DataObject.DO_DISCRET_DATA, b"") 114 | if not data1: 115 | return PubkeyAlgo.INVALID, 0 116 | data2 = decode_tlv(data1) 117 | attr = data2.get(key_attr, b"") 118 | if not attr: 119 | return PubkeyAlgo.INVALID, 0 120 | key_algo = attr[0] 121 | print(f" Key ID: {key_algo}") 122 | if key_algo == PubkeyAlgo.RSA: 123 | key_size = (attr[1] << 8) | attr[2] 124 | print(f" Key size: {key_size}") 125 | else: 126 | key_size = 0 127 | return key_algo, key_size 128 | 129 | 130 | def get_EDDSA_pub_key(client: CommandSender, key_tag: DataObject) -> ECC.EccKey: 131 | """Read the Public Key and generate a EccKey object 132 | 133 | Args: 134 | client (CommandSender): Application object 135 | key_tag (DataObject): Tag identifying the key to read 136 | 137 | Returns: 138 | EccKey 139 | """ 140 | 141 | # Extract Pub key parameters 142 | data = _get_pub_key(client, key_tag) 143 | 144 | oid = data[0x86] 145 | print(f" OID[{len(oid)}]: {oid.hex()}") 146 | assert len(oid) == 32 147 | return eddsa.import_public_key(oid) 148 | 149 | 150 | def get_ECDSA_pub_key(client: CommandSender, key_tag: DataObject) -> ECC.EccKey: 151 | """Read the Public Key and generate a EccKey object 152 | 153 | Args: 154 | client (CommandSender): Application object 155 | key_tag (DataObject): Tag identifying the key to read 156 | 157 | Returns: 158 | EccKey 159 | """ 160 | 161 | # Extract Pub key parameters 162 | data = _get_pub_key(client, key_tag) 163 | 164 | oid = data[0x86][1:] 165 | pt_len = int(len(oid) / 2) 166 | x = oid[:pt_len] 167 | y = oid[pt_len:] 168 | print(f" X[{len(x)}]: {x.hex()}") 169 | print(f" Y[{len(y)}]: {y.hex()}") 170 | assert len(x) == len(y) 171 | return ECC.construct(curve="P-256", point_x=bytes_to_long(x), point_y=bytes_to_long(y)) 172 | 173 | 174 | def get_RSA_pub_key(client: CommandSender, key_tag: DataObject) -> RSA.RsaKey: 175 | """Read the Public Key and generate a RsaKey object 176 | 177 | Args: 178 | client (CommandSender): Application object 179 | key_tag (DataObject): Tag identifying the key to read 180 | 181 | Returns: 182 | RsaKey 183 | """ 184 | 185 | # Extract Pub key parameters 186 | data = _get_pub_key(client, key_tag) 187 | 188 | exponent = data[0x82] 189 | modulus = data[0x81] 190 | print(f" Key Exponent: 0x{exponent.hex()}") 191 | print(f" Key Modulus[{len(modulus)}]: {modulus.hex()}") 192 | 193 | return RSA.construct((int.from_bytes(modulus, 'big'), int.from_bytes(exponent, 'big'))) 194 | 195 | 196 | def check_pincode(client: CommandSender, pwd: PassWord) -> None: 197 | """Send and check the pincode 198 | 199 | Args: 200 | client (CommandSender): Application object 201 | pwd (PassWord): Password to be verified 202 | """ 203 | 204 | if pwd in (PassWord.PW1, PassWord.PW2): 205 | pincode = "123456" 206 | elif pwd == PassWord.PW3: 207 | pincode = "12345678" 208 | else: 209 | raise ValueError(f"Invalid pwd: {pwd}") 210 | 211 | # Verify PW 212 | rapdu = client.send_verify_pw(pwd, pincode) 213 | assert rapdu.status == Errors.SW_OK 214 | 215 | 216 | def verify_version(version: str) -> None: 217 | """Verify the app version, based on defines in Makefile 218 | 219 | Args: 220 | Version (str): Version to be checked 221 | """ 222 | 223 | vers_dict = {} 224 | vers_str = "" 225 | lines = _read_makefile() 226 | version_re = re.compile(r"^APPVERSION_(?P\w)\s?=\s?(?P\d)", re.I) 227 | for line in lines: 228 | info = version_re.match(line) 229 | if info: 230 | dinfo = info.groupdict() 231 | vers_dict[dinfo["part"]] = dinfo["val"] 232 | try: 233 | vers_str = f"{vers_dict['M']}.{vers_dict['N']}.{vers_dict['P']}" 234 | except KeyError: 235 | pass 236 | assert version == vers_str 237 | 238 | 239 | def verify_name(name: str) -> None: 240 | """Verify the app name, based on defines in Makefile 241 | 242 | Args: 243 | name (str): Name to be checked 244 | """ 245 | 246 | name_str = "" 247 | lines = _read_makefile() 248 | name_re = re.compile(r"^APPNAME\s?=\s?(?P\w+)", re.I) 249 | for line in lines: 250 | info = name_re.match(line) 251 | if info: 252 | dinfo = info.groupdict() 253 | name_str = dinfo["val"] 254 | assert name == name_str 255 | 256 | 257 | def decode_tlv(tlv: bytes) -> dict: 258 | """Decode TLV fields 259 | 260 | Args: 261 | tlv (bytes): Input data bytes to parse 262 | 263 | Returns: 264 | dict {t: v, t:v, ...} 265 | """ 266 | 267 | tags = {} 268 | while len(tlv): 269 | o = 0 270 | l = 0 271 | if (tlv[0] & 0x1F) == 0x1F: 272 | t = (tlv[0] << 8) | tlv[1] 273 | o = 2 274 | else: 275 | t = tlv[0] 276 | o = 1 277 | l = tlv[o] 278 | if l & 0x80 : 279 | if (l & 0x7f) == 1: 280 | l = tlv[o + 1] 281 | o += 2 282 | if (l & 0x7f) == 2: 283 | l = (tlv[o + 1] << 8) | tlv[o + 2] 284 | o += 3 285 | else: 286 | o += 1 287 | v = tlv[o:o + l] 288 | tags[t] = v 289 | tlv = tlv[o + l:] 290 | return tags 291 | 292 | 293 | def _read_makefile() -> List[str]: 294 | """Read lines from the parent Makefile """ 295 | 296 | parent = Path(ROOT_SCREENSHOT_PATH).parent.resolve() 297 | makefile = f"{parent}/Makefile" 298 | print(f"Analyzing {makefile}...") 299 | with open(makefile, "r", encoding="utf-8") as f_p: 300 | lines = f_p.readlines() 301 | 302 | return lines 303 | 304 | 305 | def _get_pub_key(client: CommandSender, key_tag: DataObject) -> dict: 306 | """Read the Public Key parameters 307 | 308 | Args: 309 | client (CommandSender): Application object 310 | key_tag (DataObject): Tag identifying the key to read 311 | 312 | Returns: 313 | Dictionary with key parameters 314 | """ 315 | 316 | rapdu = client.read_key(key_tag) 317 | assert rapdu.status == Errors.SW_OK 318 | 319 | # Extract Pub key parameters 320 | tags = decode_tlv(rapdu.data) 321 | return decode_tlv(tags[DataObject.DO_PUB_KEY]) 322 | -------------------------------------------------------------------------------- /unit-tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | if(${CMAKE_VERSION} VERSION_LESS 3.10) 4 | cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) 5 | endif() 6 | 7 | # project information 8 | project(unit_tests 9 | VERSION 0.1 10 | DESCRIPTION "Unit tests for Ledger OpenPGP application" 11 | LANGUAGES C) 12 | 13 | 14 | # guard against bad build-type strings 15 | if (NOT CMAKE_BUILD_TYPE) 16 | set(CMAKE_BUILD_TYPE "Release") 17 | endif() 18 | 19 | # guard against in-source builds 20 | if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) 21 | message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt. ") 22 | endif() 23 | 24 | include(CTest) 25 | 26 | # specify C standard 27 | set(CMAKE_C_STANDARD 11) 28 | set(CMAKE_C_STANDARD_REQUIRED True) 29 | 30 | add_compile_options(-Wall -Wextra -g -pedantic --coverage) 31 | # Flag depending on the Build Type 32 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0") 33 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}-O3") 34 | 35 | set(GCC_COVERAGE_LINK_FLAGS "--coverage -lgcov") 36 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") 37 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") 38 | 39 | set(APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../src") 40 | set(SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") 41 | set(MOCK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/mocks") 42 | 43 | add_compile_definitions(TEST) 44 | 45 | include_directories( 46 | ${MOCK_DIR} 47 | ${APP_DIR} 48 | ) 49 | # include_directories($ENV{BOLOS_SDK}/lib_standard_app) 50 | 51 | add_executable(test_io 52 | ${SRC_DIR}/test_io.c 53 | ${MOCK_DIR}/mocks.c 54 | ${APP_DIR}/gpg_io.c 55 | ${APP_DIR}/gpg_vars.c 56 | ) 57 | 58 | target_link_libraries(test_io PUBLIC 59 | cmocka 60 | gcov) 61 | 62 | add_test(test_io test_io) 63 | -------------------------------------------------------------------------------- /unit-tests/README.md: -------------------------------------------------------------------------------- 1 | # Unit tests 2 | 3 | ## Prerequisite 4 | 5 | Be sure to have installed: 6 | 7 | - CMake >= 3.10 8 | - CMocka >= 1.1.5 9 | 10 | and for code coverage generation: 11 | 12 | - lcov >= 1.14 13 | 14 | ## Overview 15 | 16 | In `unit-tests` folder, compile with: 17 | 18 | ```shell 19 | cmake -Bbuild -H. && make -C build 20 | ``` 21 | 22 | and run tests with: 23 | 24 | ```shell 25 | CTEST_OUTPUT_ON_FAILURE=1 make -C build test 26 | ``` 27 | 28 | To get more verbose output, use: 29 | 30 | ```shell 31 | CTEST_OUTPUT_ON_FAILURE=1 make -C build test ARGS="-V" 32 | ``` 33 | 34 | Or also directly with: 35 | 36 | ```shell 37 | CTEST_OUTPUT_ON_FAILURE=1 build/test_io 38 | ``` 39 | 40 | ## Generate code coverage 41 | 42 | Just execute in `unit-tests` folder: 43 | 44 | ```shell 45 | ./gen_coverage.sh 46 | ``` 47 | 48 | it will output `coverage.total` and `coverage/` folder with HTML details (in `coverage/index.html`). 49 | -------------------------------------------------------------------------------- /unit-tests/gen_coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | set -e 5 | 6 | BUILD_DIRECTORY=$(realpath build/) 7 | 8 | lcov --directory . -b "${BUILD_DIRECTORY}" --capture --initial -o coverage.base && 9 | lcov --rc lcov_branch_coverage=1 --directory . -b "${BUILD_DIRECTORY}" --capture -o coverage.capture && 10 | lcov --directory . -b "${BUILD_DIRECTORY}" --add-tracefile coverage.base --add-tracefile coverage.capture -o coverage.info && 11 | lcov --directory . -b "${BUILD_DIRECTORY}" --remove coverage.info '*/unit-tests/*' -o coverage.info && 12 | echo "Generated 'coverage.info'." && 13 | genhtml coverage.info -o coverage 14 | 15 | rm -f coverage.base coverage.capture 16 | -------------------------------------------------------------------------------- /unit-tests/mocks/bolos_target.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/unit-tests/mocks/bolos_target.h -------------------------------------------------------------------------------- /unit-tests/mocks/cx.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #define cx_rsa_public_key_t char 4 | #define cx_rsa_1024_public_key_t char 5 | #define cx_rsa_2048_public_key_t char 6 | #define cx_rsa_3072_public_key_t char 7 | #define cx_rsa_4096_public_key_t char 8 | 9 | #define cx_rsa_private_key_t char 10 | #define cx_rsa_1024_private_key_t char 11 | #define cx_rsa_2048_private_key_t char 12 | #define cx_rsa_3072_private_key_t char 13 | #define cx_rsa_4096_private_key_t char 14 | 15 | #define cx_ecfp_public_key_t char 16 | #define cx_ecfp_256_public_key_t char 17 | #define cx_ecfp_384_public_key_t char 18 | #define cx_ecfp_512_public_key_t char 19 | #define cx_ecfp_640_public_key_t char 20 | 21 | #define cx_ecfp_private_key_t char 22 | #define cx_ecfp_256_private_key_t char 23 | #define cx_ecfp_384_private_key_t char 24 | #define cx_ecfp_512_private_key_t char 25 | #define cx_ecfp_640_private_key_t char 26 | 27 | #define cx_sha3_t char 28 | #define cx_sha256_t char 29 | 30 | #define cx_aes_key_t char 31 | -------------------------------------------------------------------------------- /unit-tests/mocks/lcx_sha3.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LedgerHQ/app-openpgp/7bf7f61753bec93f0d0422daadf4b5e21f5e5bb3/unit-tests/mocks/lcx_sha3.h -------------------------------------------------------------------------------- /unit-tests/mocks/ledger_assert.h: -------------------------------------------------------------------------------- 1 | #define LEDGER_ASSERT(test, message) \ 2 | do { \ 3 | if (!(test)) { \ 4 | return; \ 5 | } \ 6 | } while (0) 7 | -------------------------------------------------------------------------------- /unit-tests/mocks/mocks.c: -------------------------------------------------------------------------------- 1 | 2 | #include "os.h" 3 | 4 | unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; 5 | 6 | unsigned short io_exchange(unsigned char channel_and_flags, unsigned short tx_len) { 7 | (void) channel_and_flags; 8 | (void) tx_len; 9 | return 0; 10 | } 11 | 12 | void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len) { 13 | (void) dst_adr; 14 | (void) src_adr; 15 | (void) src_len; 16 | return; 17 | } 18 | -------------------------------------------------------------------------------- /unit-tests/mocks/offsets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Offset of instruction class. 5 | */ 6 | #define OFFSET_CLA 0 7 | /** 8 | * Offset of instruction code. 9 | */ 10 | #define OFFSET_INS 1 11 | /** 12 | * Offset of instruction parameter 1. 13 | */ 14 | #define OFFSET_P1 2 15 | /** 16 | * Offset of instruction parameter 2. 17 | */ 18 | #define OFFSET_P2 3 19 | /** 20 | * Offset of command data length. 21 | */ 22 | #define OFFSET_LC 4 23 | /** 24 | * Offset of command data. 25 | */ 26 | #define OFFSET_CDATA 5 27 | -------------------------------------------------------------------------------- /unit-tests/mocks/os.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #undef MAX 6 | #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 7 | 8 | #define PRINTF(...) 9 | #define THROW(x) 10 | 11 | // send tx_len bytes (atr or rapdu) and retrieve the length of the next command apdu (over the 12 | // requested channel) 13 | #define CHANNEL_APDU 0 14 | #define IO_RETURN_AFTER_TX 0x20 15 | #define IO_ASYNCH_REPLY 0x10 // avoid apdu state reset if tx_len == 0 when we're expected to reply 16 | 17 | #define IO_APDU_BUFFER_SIZE (255 + 5 + 64) 18 | 19 | extern unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; 20 | 21 | extern unsigned short io_exchange(unsigned char channel_and_flags, unsigned short tx_len); 22 | extern void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len); 23 | -------------------------------------------------------------------------------- /unit-tests/mocks/os_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define U2(hi, lo) ((((hi) &0xFFu) << 8) | ((lo) &0xFFu)) 4 | #define U4(hi3, hi2, lo1, lo0) \ 5 | ((((hi3) &0xFFu) << 24) | (((hi2) &0xFFu) << 16) | (((lo1) &0xFFu) << 8) | ((lo0) &0xFFu)) 6 | static inline uint16_t U2BE(const uint8_t *buf, size_t off) { 7 | return (buf[off] << 8) | buf[off + 1]; 8 | } 9 | static inline uint32_t U4BE(const uint8_t *buf, size_t off) { 10 | return (((uint32_t) buf[off]) << 24) | (buf[off + 1] << 16) | (buf[off + 2] << 8) | 11 | buf[off + 3]; 12 | } 13 | 14 | static inline void U2BE_ENCODE(uint8_t *buf, size_t off, uint32_t value) { 15 | buf[off + 0] = (value >> 8) & 0xFF; 16 | buf[off + 1] = value & 0xFF; 17 | } 18 | static inline void U4BE_ENCODE(uint8_t *buf, size_t off, uint32_t value) { 19 | buf[off + 0] = (value >> 24) & 0xFF; 20 | buf[off + 1] = (value >> 16) & 0xFF; 21 | buf[off + 2] = (value >> 8) & 0xFF; 22 | buf[off + 3] = value & 0xFF; 23 | } 24 | -------------------------------------------------------------------------------- /unit-tests/mocks/usbd_ccid_if.h: -------------------------------------------------------------------------------- 1 | #define PIN_OPR_APDU_CLA 0xEF 2 | -------------------------------------------------------------------------------- /unit-tests/mocks/ux.h: -------------------------------------------------------------------------------- 1 | #define ux_state_t char 2 | -------------------------------------------------------------------------------- /unit-tests/src/test_io.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "gpg_vars.h" 11 | 12 | static int setup(void **state) { 13 | (void) state; 14 | 15 | // Init tests 16 | gpg_io_discard(1); 17 | return 0; 18 | } 19 | 20 | static void test_io(void **state) { 21 | (void) state; 22 | 23 | unsigned int v32 = 0x789ABCDE; 24 | unsigned int v16 = 0x3456; 25 | unsigned int v8 = 0x12; 26 | 27 | gpg_io_insert_u8(v8); 28 | 29 | gpg_io_insert_u16(v16); 30 | 31 | // Mark the current offset 32 | gpg_io_mark(); 33 | 34 | gpg_io_insert_u32(v32); 35 | 36 | // rewind offset to the beginning to the buffer 37 | gpg_io_set_offset(0); 38 | 39 | assert_int_equal(gpg_io_fetch_u8(), v8); 40 | assert_int_equal(gpg_io_fetch_u16(), v16); 41 | assert_int_equal(gpg_io_fetch_u32(), v32); 42 | 43 | // rewind offset to the mark 44 | gpg_io_set_offset(IO_OFFSET_MARK); 45 | 46 | assert_int_equal(gpg_io_fetch_u32(), v32); 47 | } 48 | 49 | int main() { 50 | const struct CMUnitTest tests[] = {cmocka_unit_test_setup(test_io, setup)}; 51 | 52 | return cmocka_run_group_tests(tests, NULL, NULL); 53 | } 54 | --------------------------------------------------------------------------------