diff --git a/.clang-format b/.clang-format index 95b750c..d65f1ce 100644 --- a/.clang-format +++ b/.clang-format @@ -1,119 +1,18 @@ --- -Language: Cpp -# BasedOnStyle: LLVM -AccessModifierOffset: -2 +BasedOnStyle: Google +IndentWidth: 4 +Language: Cpp +ColumnLimit: 100 +PointerAlignment: Right AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: true -AlignConsecutiveDeclarations: true -#AlignConsecutiveMacros: true -AlignEscapedNewlines: Right -AlignOperands: true -AlignTrailingComments: true -#AllowAllArgumentsOnNextLine: false -#AllowAllConstructorInitializersOnNextLine: false +AlignConsecutiveMacros: true AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: false +SortIncludes: false +SpaceAfterCStyleCast: true AllowShortCaseLabelsOnASingleLine: false +AllowAllArgumentsOnNextLine: false +AllowShortBlocksOnASingleLine: Never AllowShortFunctionsOnASingleLine: None -AllowShortIfStatementsOnASingleLine: false -#AllowShortLambdasOnASingleLine: None -AllowShortLoopsOnASingleLine: false -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: false -BinPackArguments: true +BinPackArguments: false BinPackParameters: false -BraceWrapping: - AfterClass: false - AfterControlStatement: false - AfterEnum: false - AfterFunction: false - AfterNamespace: false - AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false - AfterExternBlock: false - BeforeCatch: false - BeforeElse: false - IndentBraces: false - SplitEmptyFunction: true - SplitEmptyRecord: true - SplitEmptyNamespace: true -BreakBeforeBinaryOperators: None -BreakBeforeBraces: Custom -BreakBeforeInheritanceComma: false -BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false -BreakConstructorInitializers: BeforeColon -BreakAfterJavaFieldAnnotations: false -BreakStringLiterals: true -ColumnLimit: 120 -CommentPragmas: '^ IWYU pragma:' -CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: false -ConstructorInitializerIndentWidth: 4 -ContinuationIndentWidth: 4 -Cpp11BracedListStyle: true -DerivePointerAlignment: false -DisableFormat: false -ExperimentalAutoDetectBinPacking: false -FixNamespaceComments: true -ForEachMacros: - - foreach - - Q_FOREACH - - BOOST_FOREACH -IncludeBlocks: Preserve -IncludeCategories: - - Regex: '^"(llvm|llvm-c|clang|clang-c)/' - Priority: 2 - - Regex: '^(<|"(gtest|gmock|isl|json)/)' - Priority: 3 - - Regex: '.*' - Priority: 1 -IncludeIsMainRegex: '(Test)?$' -IndentCaseLabels: false -#IndentPPDirectives: BeforeHash -IndentWidth: 2 -IndentWrappedFunctionNames: true -JavaScriptQuotes: Leave -JavaScriptWrapImports: true -KeepEmptyLinesAtTheStartOfBlocks: false -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: All -ObjCBlockIndentWidth: 2 -ObjCSpaceAfterProperty: false -ObjCSpaceBeforeProtocolList: true -PenaltyBreakAssignment: 2 -PenaltyBreakBeforeFirstCallParameter: 19 -PenaltyBreakComment: 300 -PenaltyBreakFirstLessLess: 120 -PenaltyBreakString: 1000 -PenaltyExcessCharacter: 1000000 -PenaltyReturnTypeOnItsOwnLine: 60 -PointerAlignment: Right -RawStringFormats: - - Delimiter: pb - Language: TextProto - BasedOnStyle: google -ReflowComments: true -SortIncludes: false -SortUsingDeclarations: false -SpaceAfterCStyleCast: false -SpaceAfterTemplateKeyword: true -SpaceBeforeAssignmentOperators: true -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 1 -SpacesInAngles: false -SpacesInContainerLiterals: false -SpacesInCStyleCastParentheses: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: Cpp11 -TabWidth: 8 -UseTab: Never -... - +--- diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f2fb620 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +# Checklist + +- [ ] App update process has been followed +- [ ] Target branch is `develop` +- [ ] Application version has been bumped + + diff --git a/.github/workflows/build_and_functional_tests.yml b/.github/workflows/build_and_functional_tests.yml index 3feee3b..0efd14d 100644 --- a/.github/workflows/build_and_functional_tests.yml +++ b/.github/workflows/build_and_functional_tests.yml @@ -18,4 +18,10 @@ jobs: uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1 with: upload_app_binaries_artifact: "compiled_app_binaries" - run_for_devices: '["nanos", "nanox", "nanosp"]' + + ragger_tests: + name: Run ragger tests using the reusable workflow + needs: build_application + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_ragger_tests.yml@v1 + with: + download_app_binaries_artifact: "compiled_app_binaries" diff --git a/.github/workflows/codeql_checks.yml b/.github/workflows/codeql_checks.yml new file mode 100644 index 0000000..fb95064 --- /dev/null +++ b/.github/workflows/codeql_checks.yml @@ -0,0 +1,44 @@ +name: "CodeQL" + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + # Excluded path: add the paths you want to ignore instead of deleting the workflow + paths-ignore: + - '.github/workflows/*.yml' + - 'tests/*' + +jobs: + analyse: + name: Analyse + strategy: + matrix: + sdk: [ "$NANOS_SDK", "$NANOX_SDK", "$NANOSP_SDK", "$STAX_SDK" ] + #'cpp' covers C and C++ + language: [ 'cpp' ] + runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-legacy:latest + + steps: + - name: Clone + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: security-and-quality + + # CodeQL will create the database during the compilation + - name: Build + run: | + make BOLOS_SDK=${{ matrix.sdk }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/coding_style_checks.yml b/.github/workflows/coding_style_checks.yml new file mode 100644 index 0000000..6be2786 --- /dev/null +++ b/.github/workflows/coding_style_checks.yml @@ -0,0 +1,25 @@ +name: Run coding style check through reusable workflow + +# This workflow will run linting checks to ensure a level of uniformization among all Ledger applications. +# +# The presence of this workflow is mandatory as a minimal level of linting is required. +# You are however free to modify the content of the .clang-format file and thus the coding style of your application. +# We simply ask you to not diverge too much from the linting of the Boilerplate application. + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + check_linting: + name: Check linting using the reusable workflow + uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_lint.yml@v1 + with: + source: './src' + extensions: 'h,c' + version: 11 diff --git a/.github/workflows/guidelines_enforcer.yml b/.github/workflows/guidelines_enforcer.yml index 894eed0..fdaf9f2 100644 --- a/.github/workflows/guidelines_enforcer.yml +++ b/.github/workflows/guidelines_enforcer.yml @@ -21,5 +21,3 @@ jobs: guidelines_enforcer: name: Call Ledger guidelines_enforcer uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_guidelines_enforcer.yml@v1 - with: - run_for_devices: '["nanos", "nanox", "nanosp"]' diff --git a/.github/workflows/misspellings_checks.yml b/.github/workflows/misspellings_checks.yml new file mode 100644 index 0000000..d808379 --- /dev/null +++ b/.github/workflows/misspellings_checks.yml @@ -0,0 +1,29 @@ +name: Misspellings checks + +# This workflow performs some misspelling checks on the repository +# It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked +# applications. + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + misspell: + name: Check misspellings + runs-on: ubuntu-latest + steps: + - name: Clone + uses: actions/checkout@v4 + + - name: Check misspellings + uses: codespell-project/actions-codespell@v2 + with: + builtin: clear,rare + check_filenames: true + ignore_words_list: ontop diff --git a/.github/workflows/python_client_checks.yml b/.github/workflows/python_client_checks.yml new file mode 100644 index 0000000..91329bb --- /dev/null +++ b/.github/workflows/python_client_checks.yml @@ -0,0 +1,43 @@ +name: Checks on the Python client + +# This workflow performs some checks on the Python client used by the Application tests +# It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked +# applications. + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + lint: + name: Client linting + runs-on: ubuntu-latest + steps: + - name: Clone + uses: actions/checkout@v4 + - name: Installing PIP dependencies + run: | + pip install pylint + pip install -r tests/requirements.txt + - name: Lint Python code + run: | + pylint --rc tests/setup.cfg tests/application_client/ + + mypy: + name: Type checking + runs-on: ubuntu-latest + steps: + - name: Clone + uses: actions/checkout@v4 + - name: Installing PIP dependencies + run: | + pip install mypy + pip install -r tests/requirements.txt + - name: Mypy type checking + run: | + mypy tests/application_client/ diff --git a/.github/workflows/python_tool_checks.yml b/.github/workflows/python_tool_checks.yml new file mode 100644 index 0000000..ade35b6 --- /dev/null +++ b/.github/workflows/python_tool_checks.yml @@ -0,0 +1,45 @@ +name: Checks on the Tools client + +# This workflow performs some checks on the Python client used by the cli tool +# It is there to help us maintain a level of quality in our codebase and does not have to be kept on forked +# applications. + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + lint: + name: Client linting + runs-on: ubuntu-latest + steps: + - name: Clone + uses: actions/checkout@v4 + - name: Installing PIP dependencies + run: | + sudo apt-get update && sudo apt-get install -y libpcsclite-dev + pip install pylint + pip install -r pytools/requirements.txt + - name: Lint Python code + run: | + pylint --rc pytools/setup.cfg pytools/ + + mypy: + name: Type checking + runs-on: ubuntu-latest + steps: + - name: Clone + uses: actions/checkout@v4 + - name: Installing PIP dependencies + run: | + sudo apt-get update && sudo apt-get install -y libpcsclite-dev + pip install mypy + pip install -r pytools/requirements.txt + - name: Mypy type checking + run: | + mypy pytools/ diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000..90d6f7d --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,50 @@ +name: Unit testing with Codecov coverage checking + +on: + workflow_dispatch: + push: + branches: + - master + - main + - develop + pull_request: + +jobs: + job_unit_test: + name: Unit test + runs-on: ubuntu-latest + container: + image: ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest + + steps: + - name: Clone + uses: actions/checkout@v4 + + - name: Build unit tests + run: | + cd unit-tests/ + cmake -Bbuild -H. && make -C build && make -C build test + + - name: Generate code coverage + run: | + cd unit-tests/ + lcov --directory . -b "$(realpath build/)" --capture --initial -o coverage.base && \ + lcov --rc lcov_branch_coverage=1 --directory . -b "$(realpath build/)" --capture -o coverage.capture && \ + lcov --directory . -b "$(realpath build/)" --add-tracefile coverage.base --add-tracefile coverage.capture -o coverage.info && \ + lcov --directory . -b "$(realpath build/)" --remove coverage.info '*/unit-tests/*' -o coverage.info && \ + genhtml coverage.info -o coverage + + - uses: actions/upload-artifact@v3 + with: + name: code-coverage + path: unit-tests/coverage + + - name: Upload to codecov.io + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./unit-tests/coverage.info + flags: unittests + name: codecov-app-openpgp + fail_ci_if_error: true + verbose: true diff --git a/.gitignore b/.gitignore index 1b26681..86c174c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,38 @@ -# Glyphs auto-generated -src/glyphs.* - -# Build files -dep -obj -release -bin -debug -build +# Compilation files of the application +build/ + +# Legacy compilation output +bin/ +debug/ + +# Temporary directory with snapshots taken during test runs +tests/snapshots-tmp/ + +# manual-tests +manual-tests/foo* +manual-tests/gnupg + +# Unit tests and code coverage +unit-tests/build/ +unit-tests/coverage/ +unit-tests/coverage.info + +# Fuzzing +fuzzing/build/ + +# Python +*.pyc[cod] +*.egg +__pycache__/ +*.egg-info/ +.eggs/ +.python-version +venv/ +gpg_backup + +# Doxygen +doc/html +doc/latex + +# Virtual env for sideload (macOS and Windows) +ledger/ diff --git a/.mdl.rb b/.mdl.rb new file mode 100644 index 0000000..0b971f6 --- /dev/null +++ b/.mdl.rb @@ -0,0 +1,11 @@ +# Style file for mdl +# https://github.com/markdownlint/markdownlint/blob/main/docs/creating_styles.md + +# Include all rules +all + +# Disable specific rules +#exclude_rule 'MD012' + +# Update rules configuration +rule 'MD013', :line_length => 120 diff --git a/.mdlrc b/.mdlrc new file mode 100644 index 0000000..4d04c4d --- /dev/null +++ b/.mdlrc @@ -0,0 +1,11 @@ +# markdownlint config file + +# Use custom style file +style "#{File.dirname(__FILE__)}/.mdl.rb" + +# MD005 - Inconsistent indentation for list items at the same level +# MD007 - Unordered list indentation +# MD014 - Dollar signs used before commands without showing output +# MD024 - Multiple headers with the same content +# MD041 - First line in file should be a top level header +rules "~MD005,~MD007,~MD014,~MD024,~MD041" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f79567a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,58 @@ +# To install hooks, run: +# pre-commit install --hook-type pre-commit +# pre-commit install --hook-type commit-msg + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: mixed-line-ending + - id: check-added-large-files + - id: check-merge-conflict + - id: check-case-conflict + + - repo: https://github.com/codespell-project/codespell + rev: v2.2.5 + hooks: + - id: codespell + args: ['--ignore-words-list', 'ontop'] + + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v11.0.1 + hooks: + - id: clang-format + types_or: [c] + + - repo: https://github.com/Mateusz-Grzelinski/actionlint-py + rev: v1.6.26.11 + hooks: + - id: actionlint + types_or: [yaml] + + - repo: https://github.com/markdownlint/markdownlint + rev: v0.13.0 + hooks: + - id: markdownlint + types_or: [markdown] + + - repo: local + hooks: + + # Python scripts + - id: pylint + name: Check python Client + # Only display messages, no score, disable few errors + entry: pylint -j 0 --rc tests/setup.cfg + language: system + types: [python] + files: '^tests\/.*$' + + - id: pylint + name: Check python Tool + # Only display messages, no score, disable few errors + entry: pylint -j 0 --rc pytools/setup.cfg + language: system + types: [python] + files: '^pytools\/.*$' diff --git a/Makefile b/Makefile index af240bb..90076c4 100644 --- a/Makefile +++ b/Makefile @@ -1,232 +1,135 @@ -#******************************************************************************* -# Ledger App -# (c) 2016-2019 Ledger +# **************************************************************************** +# Ledger App OpenPGP +# (c) 2024 Ledger SAS. # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#******************************************************************************* - --include Makefile.env +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# **************************************************************************** + ifeq ($(BOLOS_SDK),) $(error Environment variable BOLOS_SDK is not set) endif -include $(BOLOS_SDK)/Makefile.defines -APP_LOAD_PARAMS=--appFlags 0x240 --path "2152157255'" --curve secp256k1 $(COMMON_LOAD_PARAMS) +include $(BOLOS_SDK)/Makefile.defines -ifeq ($(APPNAME),) +######################################## +# Mandatory configuration # +######################################## +# Application name APPNAME = OpenPGP -endif -ifeq ($(APPNAME),OpenPGP) -GPG_MULTISLOT:=0 -else ifeq ($(APPNAME),OpenPGP.XL) -GPG_MULTISLOT:=1 -APPNAME:=OpenPGP.XL -else -$(error APPNAME ($(APPNAME)) is not set or unknown) -endif -ifeq ($(TARGET_NAME),TARGET_BLUE) -ICONNAME = images/icon_monero_blue.gif -else ifeq ($(TARGET_NAME),TARGET_NANOS) -ICONNAME = images/icon_pgp.gif -else -ICONNAME = images/icon_pgp_nanox.gif -endif +# Application version +APPVERSION_M = 2 +APPVERSION_N = 2 +APPVERSION_P = 2 +APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" -APPVERSION_M:=1 -APPVERSION_N:=4 -APPVERSION_P:=4 -APPVERSION:=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) SPECVERSION:="3.3.1" - -DEFINES += $(OPENPGP_CONFIG) -DEFINES += OPENPGP_VERSION_MAJOR=$(APPVERSION_M) OPENPGP_VERSION_MINOR=$(APPVERSION_N) OPENPGP_VERSION_MICRO=$(APPVERSION_P) -DEFINES += OPENPGP_VERSION=$(APPVERSION) -DEFINES += OPENPGP_NAME=$(APPNAME) DEFINES += SPEC_VERSION=$(SPECVERSION) -DEFINES += GPG_MULTISLOT=$(GPG_MULTISLOT) -#DEFINES += GPG_LOG - -ifeq ($(TARGET_NAME),TARGET_BLUE) -DEFINES += UI_BLUE -else ifeq ($(TARGET_NAME),TARGET_NANOS) -DEFINES += UI_NANO_S -else -DEFINES += UI_NANO_X -DEFINES += GPG_SHAKE256 -endif - - - -################ -# Default rule # -################ - -.PHONY: allvariants listvariants -all: default - mkdir -p release - cp -a bin/app.elf release/$(APPNAME).elf - cp -a bin/app.hex release/$(APPNAME).hex - cp -a debug/app.asm release/$(APPNAME).asm - cp -a debug/app.map release/$(APPNAME).map +# Application source files +APP_SOURCE_PATH += src +APP_SOURCE_FILES += $(BOLOS_SDK)/lib_cxng/src/cx_rsa.c +APP_SOURCE_FILES += $(BOLOS_SDK)/lib_cxng/src/cx_pkcs1.c +APP_SOURCE_FILES += ${BOLOS_SDK}/lib_cxng/src/cx_ram.c +INCLUDES_PATH += $(BOLOS_SDK)/lib_cxng/src -listvariants: - @echo VARIANTS APPNAME OpenPGP OpenPGP.XL - -allvariants: - make MULTISLOT=0 clean all - make MULTISLOT=1 clean all - -############ -# Platform # -############ -#SCRIPT_LD := script.ld - -ifneq ($(NO_CONSENT),) -DEFINES += NO_CONSENT -endif +# Application icons following guidelines: +# https://developers.ledger.com/docs/embedded-app/design-requirements/#device-icon +ICON_NANOS = icons/gpg_16px.gif +ICON_NANOX = icons/gpg_14px.gif +ICON_NANOSP = icons/gpg_14px.gif +ICON_STAX = icons/gpg_32px.gif + +# Application allowed derivation curves. +# Possibles curves are: secp256k1, secp256r1, ed25519 and bls12381g1 +# If your app needs it, you can specify multiple curves by using: +# `CURVE_APP_LOAD_PARAMS = ` +CURVE_APP_LOAD_PARAMS = secp256k1 + +# Application allowed derivation paths. +# You should request a specific path for your app. +# This serve as an isolation mechanism. +# Most application will have to request a path according to the BIP-0044 +# and SLIP-0044 standards. +# If your app needs it, you can specify multiple path by using: +# `PATH_APP_LOAD_PARAMS = "44'/1'" "45'/1'"` +PATH_APP_LOAD_PARAMS = "2152157255'" + +# Setting to allow building variant applications +# - is the name of the parameter which should be set +# to specify the variant that should be build. +# - a list of variant that can be build using this app code. +# * It must at least contains one value. +# * Values can be the app ticker or anything else but should be unique. +VARIANT_PARAM = APPNAME +VARIANT_VALUES = OpenPGP + +# Enabling DEBUG flag will enable PRINTF and disable optimizations +#DEBUG = 1 + +######################################## +# Application custom permissions # +######################################## +# See SDK `include/appflags.h` for the purpose of each permission +#HAVE_APPLICATION_FLAG_DERIVE_MASTER = 1 +#HAVE_APPLICATION_FLAG_GLOBAL_PIN = 1 +#HAVE_APPLICATION_FLAG_BOLOS_SETTINGS = 1 +#HAVE_APPLICATION_FLAG_LIBRARY = 1 + +######################################## +# Application communication interfaces # +######################################## +#ENABLE_BLUETOOTH = 1 +#ENABLE_NFC = 1 + +######################################## +# NBGL custom features # +######################################## +#ENABLE_NBGL_QRCODE = 1 +#ENABLE_NBGL_KEYBOARD = 1 +ENABLE_NBGL_KEYPAD = 1 + +######################################## +# Features disablers # +######################################## +# These advanced settings allow to disable some feature that are by +# default enabled in the SDK `Makefile.standard_app`. +#DISABLE_STANDARD_APP_FILES = 1 +#DISABLE_DEFAULT_IO_SEPROXY_BUFFER_SIZE = 1 # To allow custom size declaration +#DISABLE_STANDARD_APP_DEFINES = 1 # Will set all the following disablers +#DISABLE_STANDARD_SNPRINTF = 1 +#DISABLE_STANDARD_USB = 1 +DISABLE_STANDARD_WEBUSB = 1 +#DISABLE_STANDARD_BAGL_UX_FLOW = 1 +#DISABLE_DEBUG_LEDGER_ASSERT = 1 +#DISABLE_DEBUG_THROW = 1 + +######################################## +# Main app configuration # +######################################## -DEFINES += OS_IO_SEPROXYHAL -DEFINES += HAVE_BAGL HAVE_SPRINTF -DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=4 IO_HID_EP_LENGTH=64 HAVE_USB_APDU DEFINES += CUSTOM_IO_APDU_BUFFER_SIZE=\(255+5+64\) -DEFINES += HAVE_LEGACY_PID - -DEFINES += USB_SEGMENT_SIZE=64 -DEFINES += U2F_PROXY_MAGIC=\"MOON\" -#DEFINES += HAVE_IO_U2F HAVE_U2F - -DEFINES += UNUSED\(x\)=\(void\)x -DEFINES += APPVERSION=\"$(APPVERSION)\" - DEFINES += HAVE_USB_CLASS_CCID - -ifeq ($(NO_CXNG),) -INCLUDES_PATH += $(BOLOS_SDK)/lib_cxng/include -endif - -# RSA addition. -DEFINES += HAVE_RSA -INCLUDES_PATH += $(BOLOS_SDK)/lib_cxng/src -SOURCE_PATH += $(BOLOS_SDK)/lib_cxng/src/cx_rsa.c -SOURCE_PATH += $(BOLOS_SDK)/lib_cxng/src/cx_pkcs1.c -SOURCE_PATH += $(BOLOS_SDK)/lib_cxng/src/cx_utils.c -# RSA - End - -ifeq ($(TARGET_NAME),TARGET_NANOX) -DEFINES += HAVE_BLE BLE_COMMAND_TIMEOUT_MS=2000 -DEFINES += HAVE_BLE_APDU # basic ledger apdu transport over BLE -endif +DEFINES += HAVE_RSA +# Watchdog issue causing the device reset with long prime number computation +# DEFINES += WITH_SUPPORT_RSA4096 ifeq ($(TARGET_NAME),TARGET_NANOS) -DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=128 -else -DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 -DEFINES += HAVE_GLO096 -DEFINES += HAVE_BAGL BAGL_WIDTH=128 BAGL_HEIGHT=64 -DEFINES += HAVE_BAGL_ELLIPSIS # long label truncation feature -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_REGULAR_11PX -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_EXTRABOLD_11PX -DEFINES += HAVE_BAGL_FONT_OPEN_SANS_LIGHT_16PX -DEFINES += HAVE_UX_FLOW -endif - -# Enabling debug PRINTF -DEBUG = 0 -ifneq ($(DEBUG),0) - - ifeq ($(TARGET_NAME),TARGET_NANOS) - DEFINES += HAVE_PRINTF PRINTF=screen_printf - else - DEFINES += HAVE_PRINTF PRINTF=mcu_usb_printf - endif - DEFINES += PLINE="PRINTF(\"FILE:%s..LINE:%d\n\",__FILE__,__LINE__)" -else - DEFINES += PRINTF\(...\)= - DEFINES += PLINE\(...\)= -endif - - -############## -# Compiler # -############## -ifneq ($(BOLOS_ENV),) -$(info BOLOS_ENV=$(BOLOS_ENV)) -CLANGPATH := $(BOLOS_ENV)/clang-arm-fropi/bin/ -GCCPATH := $(BOLOS_ENV)/gcc-arm-none-eabi-5_3-2016q1/bin/ -else -$(info BOLOS_ENV is not set: falling back to CLANGPATH and GCCPATH) -endif -ifeq ($(CLANGPATH),) -$(info CLANGPATH is not set: clang will be used from PATH) -endif -ifeq ($(GCCPATH),) -$(info GCCPATH is not set: arm-none-eabi-* will be used from PATH) -endif -CC := $(CLANGPATH)clang - -#CFLAGS += -O0 -gdwarf-2 -gstrict-dwarf -CFLAGS += -O3 -Os -#CFLAGS += -fno-jump-tables -fno-lookup-tables -fsave-optimization-record -#$(info $(CFLAGS)) - -AS := $(GCCPATH)arm-none-eabi-gcc - -LD := $(GCCPATH)arm-none-eabi-gcc -#LDFLAGS += -O0 -gdwarf-2 -gstrict-dwarf -LDFLAGS += -O3 -Os -LDLIBS += -lm -lgcc -lc - -# import rules to compile glyphs(/pone) -include $(BOLOS_SDK)/Makefile.glyphs - -### variables processed by the common makefile.rules of the SDK to grab source files and include dirs -APP_SOURCE_PATH += src -SDK_SOURCE_PATH += lib_stusb lib_stusb_impl -SDK_SOURCE_PATH += lib_ux -ifeq ($(TARGET_NAME),TARGET_NANOX) -SDK_SOURCE_PATH += lib_blewbxx lib_blewbxx_impl +DEFINES += HAVE_UX_LEGACY endif +######################### -cformat: - clang-format -i src/*.c src/*.h - -load: all - cp -a release/$(APPNAME).elf bin/app.elf - cp -a release/$(APPNAME).hex bin/app.hex - cp -a release/$(APPNAME).asm debug/app.asm - cp -a release/$(APPNAME).map debug/app.map - python -m ledgerblue.loadApp $(APP_LOAD_PARAMS) - -run: - python -m ledgerblue.runApp --appName $(APPNAME) - -exit: - echo -e "0020008206313233343536\n0002000000" |scriptor -r "Ledger Nano S [Nano S] (0001) 01 00" - -delete: - python -m ledgerblue.deleteApp $(COMMON_DELETE_PARAMS) - - - -# import generic rules from the user and SDK --include Makefile.rules -#include $(BOLOS_SDK)/Makefile.rules - -#add dependency on custom makefile filename -dep/%.d: %.c Makefile - - +# Import generic rules from the SDK +include $(BOLOS_SDK)/Makefile.standard_app diff --git a/Makefile.rules b/Makefile.rules deleted file mode 100644 index c524513..0000000 --- a/Makefile.rules +++ /dev/null @@ -1,39 +0,0 @@ -#******************************************************************************* -# Ledger SDK -# (c) 2017 Ledger -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#******************************************************************************* - -# temporary redef, to ensure wider compliance of the SDK with pre-1.6 apps -ifeq ($(TARGET_NAME),$(filter $(TARGET_NAME),TARGET_NANOX TARGET_NANOS2)) -SDK_SOURCE_PATH += lib_bagl -else -SDK_SOURCE_PATH += lib_bagl lib_ux -endif - -# adding the correct target header to sources -SDK_SOURCE_PATH += target/$(TARGET)/include - -# Expose all SDK header files with their full relative path to the SDK root folder -INCLUDES_PATH += ${BOLOS_SDK} - -SOURCE_PATH += $(dir $(foreach libdir, $(APP_SOURCE_PATH), $(dir $(shell find $(libdir) -name '*.[csS]')))) $(BOLOS_SDK)/src $(foreach libdir, $(SDK_SOURCE_PATH), $(dir $(shell find $(BOLOS_SDK)/$(libdir) -name '*.[csS]'))) -SOURCE_FILES := $(foreach path, $(SOURCE_PATH),$(shell find $(path) -name '*.[csS]') ) $(GLYPH_DESTC) -INCLUDES_PATH += $(dir $(foreach libdir, $(SDK_SOURCE_PATH), $(dir $(shell find $(BOLOS_SDK)/$(libdir) -name '*.h')))) include $(BOLOS_SDK)/include $(BOLOS_SDK)/include/arm $(dir $(shell find $(APP_SOURCE_PATH) -name '*.h')) $(GLYPH_SRC_DIR) - -VPATH += $(dir $(SOURCE_FILES)) -OBJECT_FILES += $(sort $(addprefix $(OBJ_DIR)/, $(addsuffix .o, $(basename $(notdir $(SOURCE_FILES)))))) -DEPEND_FILES += $(sort $(addprefix $(DEP_DIR)/, $(addsuffix .d, $(basename $(notdir $(SOURCE_FILES)))))) - -include $(BOLOS_SDK)/Makefile.rules_generic diff --git a/README.md b/README.md index 782962c..5c5a959 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,331 @@ -## GnuPG application: blue-app-gnupg +# GnuPG application -GnuPG application for Nano S and Nano X +[![Ensure compliance with Ledger guidelines](https://github.com/LedgerHQ/app-openpgp/actions/workflows/guidelines_enforcer.yml/badge.svg)](https://github.com/LedgerHQ/app-openpgp/actions/workflows/guidelines_enforcer.yml) -This application implements "The OpenPGP card" specification revision 3.0. This specification is available in *doc* directory and at https://g10code.com/p-card.html . - -The application supports: - - RSA with key up to 4096 bits - - ECDSA with secp256k1 - - EDDSA with Ed25519 curve - - ECDH with secp256k1 and curve25519 curves +[![Build and run functional tests using ragger through reusable workflow](https://github.com/LedgerHQ/app-openpgp/actions/workflows/build_and_functional_tests.yml/badge.svg?branch=master)](https://github.com/LedgerHQ/app-openpgp/actions/workflows/build_and_functional_tests.yml) +GnuPG application for Ledger devices -This release has known missing parts (see also Add-on) : +This application implements "The OpenPGP card" specification revision 3.3. +This specification is available in *doc* directory and at . - * Ledger Blue support - * Seed mode ON/OFF via apdu +The application supports: +- RSA with key up to 3072 bits +- ECDSA with secp256R1 and secp256K1 +- EDDSA with Ed25519 curve +- ECDH with secp256R1, secp256K1 and curve25519 curves ## Installation and Usage -See the full doc at https://github.com/LedgerHQ/blue-app-openpgp-card/blob/master/doc/user/blue-app-openpgp-card.pdf - +See the full doc in [rst](doc/user/app-openpgp.rst), or in [pdf]() ## Add-on The GnuPG application implements the following addon: - - serial modification - - on screen reset - - 3 independent key slots - - seeded key generation -Technical specification is available at https://github.com/LedgerHQ/blue-app-openpgp-card/blob/master/doc/developper/gpgcard3.0-addon.rst +- Serial modification +- On screen reset +- 3 independent key slots (except for Nanos, where we have only a single slot) +- Seeded key generation + +Technical specification is available in [rst](doc/developer/gpgcard-addon.rst), or in [pdf]() - ### Key slot -"The OpenPGP card" specification specifies: - - 3 asymmetric keys : Signature, Decryption, Authentication - - 1 symmetric key +The OpenPGP card specification indicates: + +- 3 asymmetric keys: + - Signature, + - Decryption + - Authentication +- 1 symmetric key -The blue application allow you to store 3 different key sets, named slot. Each slot contains the above 4 keys. +The application allows you to store 3 different key sets, named slot. Each slot contains the above 4 keys. You can choose the active slot on the main screen. When installed the default slot is "1". You can change it in settings. - -### seeded key generation +### Seeded key generation A seeded mode is implemented in order to restore private keys on a new token. -In this mode key material is generated from the global token seeded. +In this mode, key material is generated from the global token seed. -Please consider SEED mode as experimental. +Also, a backup/restore mechanism is provided. Please report to the [Documentation](#documentation). -More details to come... +> Warning: Without such configuration, an OS or App update will cause your private key to be lost!" + +The following is a repeatable process that will generate the same keys and fingerprints +(even with different card serial numbers). + +1. Reset the OpenPGP App +1. Set Key Templates from the OpenPGP App menu, if needed +1. On computer, use `gpg` to edit the key with a fixed timestamp: + +```shell +gpg --faked-system-time 19990101T000000! --card-edit # note the exclamation mark to keep the time fixed +gpg> admin +gpg> generate +... # complete the wizard +``` + +While doing this, ensure that you use the same `--faked-system-time` and "Real Name" and "Email" +during the generation wizard to reproduce the same keys each time. ### On screen reset The application can be reset as if it was fresh installed. In settings, choose reset and confirm. +## Quick start guide + +### With VSCode + +You can quickly setup a convenient environment to build and test your application by using +[Ledger's VSCode developer tools extension](https://marketplace.visualstudio.com/items?itemName=LedgerHQ.ledger-dev-tools) +which leverages the [ledger-app-dev-tools](https://github.com/LedgerHQ/ledger-app-builder/pkgs/container/ledger-app-builder%2Fledger-app-dev-tools) +docker image. + +It will allow you, whether you are developing on macOS, Windows or Linux, +to quickly **build** your apps, **test** them on **Speculos** and **load** them on any supported device. + +- Install and run [Docker](https://www.docker.com/products/docker-desktop/). +- Make sure you have an X11 server running: + - On Ubuntu Linux, it should be running by default. + - On macOS, install and launch [XQuartz](https://www.xquartz.org/) + (make sure to go to XQuartz > Preferences > Security and check "Allow client connections"). + - On Windows, install and launch [VcXsrv](https://sourceforge.net/projects/vcxsrv/) + (make sure to configure it to disable access control). +- Install [VScode](https://code.visualstudio.com/download) and add [Ledger's extension](https://marketplace.visualstudio.com/items?itemName=LedgerHQ.ledger-dev-tools). +- Open a terminal and clone `app-openpgp` with `git clone git@github.com:LedgerHQ/app-openpgp.git`. +- Open the `app-openpgp` folder with VSCode. +- Use Ledger extension's sidebar menu or open the tasks menu with `ctrl + shift + b` + (`command + shift + b` on a Mac) to conveniently execute actions: + - Build the app for the device model of your choice with `Build`. + - Test your binary on [Speculos](https://github.com/LedgerHQ/speculos) with `Run with Speculos`. + - You can also run functional tests, load the app on a physical device, and more. + +> The terminal tab of VSCode will show you what commands the extension runs behind the scene. + +### With a terminal + +The [ledger-app-dev-tools](https://github.com/LedgerHQ/ledger-app-builder/pkgs/container/ledger-app-builder%2Fledger-app-dev-tools) +docker image contains all the required tools and libraries to **build**, **test** and **load** an application. + +You can download it from the ghcr.io docker repository: + +```shell +sudo docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest +``` + +You can then enter this development environment by executing the following command +from the directory of the application `git` repository: + +#### Linux (Ubuntu) + +```shell +sudo docker run --rm -ti --user "$(id -u):$(id -g)" --privileged -v "/dev/bus/usb:/dev/bus/usb" -v "$(realpath .):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest +``` + +#### macOS + +```shell +sudo docker run --rm -ti --user "$(id -u):$(id -g)" --privileged -v "$(pwd -P):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest +``` + +#### Windows (with PowerShell) + +```shell +docker run --rm -ti --privileged -v "$(Get-Location):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest +``` + +The application's code will be available from inside the docker container, +you can proceed to the following compilation steps to build your app. + +## Compilation and load + +To easily setup a development environment for compilation and loading on a physical device, you can use the [VSCode integration](#with-vscode) +whether you are on Linux, macOS or Windows. + +If you prefer using a terminal to perform the steps manually, you can use the guide below. + +### Compilation + +Setup a compilation environment by following the [shell with docker approach](#with-a-terminal). + +From inside the container, use the following command to build the app: + +```shell +make DEBUG=1 # compile optionally with PRINTF +``` + +You can choose which device to compile and load for by setting the `BOLOS_SDK` environment variable to the following values: + +- `BOLOS_SDK=$NANOS_SDK` +- `BOLOS_SDK=$NANOX_SDK` +- `BOLOS_SDK=$NANOSP_SDK` +- `BOLOS_SDK=$STAX_SDK` + +### Loading on a physical device + +This step will vary slightly depending on your platform. + +> Your physical device must be connected, unlocked and the screen showing the dashboard (not inside an application). + +#### Linux (Ubuntu) + +First make sure you have the proper udev rules added on your host. +See [udev-rules](https://github.com/LedgerHQ/udev-rules) + +Then once you have [opened a terminal](#with-a-terminal) in the `app-builder` image and [built the app](#compilation-and-load) +for the device you want, run the following command: + +```shell +# Run this command from the app-builder container terminal. +make load # load the app on a Nano S by default +``` + +[Setting the BOLOS_SDK environment variable](#compilation-and-load) will allow you to load +on whichever supported device you want. + +#### macOS / Windows (with PowerShell) + +> It is assumed you have [Python](https://www.python.org/downloads/) installed on your computer. + +Run these commands on your host from the app's source folder once you have [built the app](#compilation-and-load) +for the device you want: + +```shell +# Install Python virtualenv +python3 -m pip install virtualenv +# Create the 'ledger' virtualenv +python3 -m virtualenv ledger +``` + +Enter the Python virtual environment + +- macOS: `source ledger/bin/activate` +- Windows: `.\ledger\Scripts\Activate.ps1` + +```shell +# Install Ledgerblue (tool to load the app) +python3 -m pip install ledgerblue +# Load the app. +python3 -m ledgerblue.runScript --scp --fileName bin/app.apdu --elfFile bin/app.elf +``` + +## Tests + +The OpenPGP app comes with different tests: + +- Functional Tests implemented with Ledger's [Ragger](https://github.com/LedgerHQ/ragger) test framework. +- Unit Tests, allowing to test basic simple functions +- Manual Tests, using some script to perform real tests on the device: + - Key generation + - Encryption/Decryption + - Sign/Verify + +### Functional Tests (Ragger based) + +#### Linux (Ubuntu) + +On Linux, you can use [Ledger's VS Code extension](#with-vscode) to run the tests. +If you prefer not to, open a terminal and follow the steps below. + +Install the tests requirements: + +```shell +pip install -r tests/requirements.txt +``` + +Then you can: + +Run the functional tests (here for nanos but available for any device once you have built the binaries): + +```shell +pytest tests/ --tb=short -v --device nanos +``` + +Or run your app directly with Speculos + +```shell +speculos --model nanos build/nanos/bin/app.elf +``` + +#### macOS / Windows + +To test your app on macOS or Windows, it is recommended to use [Ledger's VS Code extension](#with-vscode) +to quickly setup a working test environment. + +You can use the following sequence of tasks and commands (all accessible in the **extension sidebar menu**): + +- `Select build target` +- `Build app` + +Then you can choose to execute the functional tests: + +- Use `Run tests`. + +Or simply run the app on the Speculos emulator: + +- `Run with Speculos`. + +### Unit Tests + +Those tests are available in the directory `unit-tests`. Please see the corresponding [README](unit-tests/README.md) +to compile and run them. + +### Manual Tests + +Those tests are available in the directory `manual-tests`. This consists in a helper script (`manual.sh`) +corresponding to different cases described in the document [Quick tests](doc/developer/quick-test.md). + +## Documentation + +Several documents are available. + +Functional Specification of the OpenPGP Application available [here](), +but also at . + +User documentation for the Ledger Application available here: [rst](doc/user/app-openpgp.rst), +or [pdf]() + +Developer documentation related to the Ledger Add-ons available here: [rst](doc/developer/gpgcard-addon.rst), +or [pdf]() + +A Quick Test document to perform `Manual Tests` available [here](doc/developer/quick-test.md) + +The pdf documentation for **User** and **Developer** are available, and can be generated from +the corresponding `.rst` files (in the same respective directories) using this command: + +```shell +cd doc/ +./generate.sh +``` + +## Continuous Integration + +The flow processed in [GitHub Actions](https://github.com/features/actions) is the following: + +- Ledger guidelines enforcer which verifies that an app is compliant with Ledger guidelines. + The successful completion of this reusable workflow is a mandatory step for an app to be available + on the Ledger application store. More information on the guidelines can be found + in the repository [ledger-app-workflow](https://github.com/LedgerHQ/ledger-app-workflows) +- Code formatting with [clang-format](http://clang.llvm.org/docs/ClangFormat.html) +- Compilation of the application for all Ledger hardware in [ledger-app-builder](https://github.com/LedgerHQ/ledger-app-builder) +- Unit tests of C functions with [cmocka](https://cmocka.org/) (see [unit-tests/](unit-tests/)) +- End-to-end tests with [Speculos](https://github.com/LedgerHQ/speculos) emulator + and [ragger](https://github.com/LedgerHQ/ragger) (see [tests/](tests/)) +- Code coverage with [gcov](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html)/[lcov](http://ltp.sourceforge.net/coverage/lcov.php) + and upload to [codecov.io](https://about.codecov.io) + +It outputs 3 artifacts: + +- `compiled_app_binaries` within binary files of the build process for each device +- `code-coverage` within HTML details of code coverage + +## Known limitations + +Today, the current App has some known limitations. + +- RSA4096 is disabled, because of an issue with the watchdog, resetting the device + during long prime number operation. +- Using Ed25519 template, the decrypt doesn't output a correct result. diff --git a/doc/LogoLedger.png b/doc/LogoLedger.png new file mode 100644 index 0000000..7c92121 Binary files /dev/null and b/doc/LogoLedger.png differ diff --git a/doc/common/LogoLedgerV.png b/doc/common/LogoLedgerV.png deleted file mode 100644 index 369b0ba..0000000 Binary files a/doc/common/LogoLedgerV.png and /dev/null differ diff --git a/doc/developer/gpgcard-addon.pdf b/doc/developer/gpgcard-addon.pdf new file mode 100644 index 0000000..df36d69 Binary files /dev/null and b/doc/developer/gpgcard-addon.pdf differ diff --git a/doc/developer/gpgcard-addon.rst b/doc/developer/gpgcard-addon.rst new file mode 100644 index 0000000..9d27241 --- /dev/null +++ b/doc/developer/gpgcard-addon.rst @@ -0,0 +1,344 @@ +.. + Ledger App OpenPGP. + (c) 2024 Ledger SAS. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +.. + ------------------------------------------------------------------------ + LaTex substitution Definition + ------------------------------------------------------------------------ + + + +License +======= + + | Ledger App OpenPGP. + | (c) 2024 Ledger SAS. + | + | Licensed under the Apache License, Version 2.0 (the "License"); + | you may not use this file except in compliance with the License. + | You may obtain a copy of the License at + | + | http://www.apache.org/licenses/LICENSE-2.0 + | + | Unless required by applicable law or agreed to in writing, software + | distributed under the License is distributed on an "AS IS" BASIS, + | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + | See the License for the specific language governing permissions and + | limitations under the License. + + +Introduction +============ + +OpenPGP Card Application add-ons summary +---------------------------------------- + +Key management: +~~~~~~~~~~~~~~~ + +OpenPGP Application manage 4 keys to Perform Security Operation (PSO) plus 2 for secure channel. + +The 4 keys are defined as follow: + +- One asymmetric signature private key (RSA or EC), named 'sig' +- One asymmetric decryption private key (RSA or EC), named 'dec' +- One asymmetric authentication private key (RSA or EC), named 'aut' +- One symmetric decryption private key (AES), named 'sym0' + +The 3 first asymmetric keys can be either randomly generated on-card or +explicitly imported from outside. + +The 4th is imported from outside. + +It's never possible to retrieve private key from the card. + +This add-on specification propose a solution to derive those keys from the +master seed managed by the Ledger Token. +This allows owner to restore a broken token without the needs to keep track of keys +outside the card. + +Moreover this add-on specification propose to manage multiple set of the 4 previously described keys. + +Keys Slots +~~~~~~~~~~ + +To modify the keys slot, just select the corresponding menu from the screen. + +.. image:: slots.png + +Random number generation +~~~~~~~~~~~~~~~~~~~~~~~~ + +OpenPGP Application provides, as optional feature, to generate random bytes. + +This add-on specification propose new type of random generation: + +- random prime number generation +- seeded random number +- seeded prime number generation + +Key Backup +~~~~~~~~~~ + +A full key backup mechanism is provided. + + +Ledger OpenPGP Application +========================== + +How +--- + +Deterministic key derivation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The deterministic key derivation process relies on the **BIP32** scheme. +The master install path of the App is set to ``/0x80'GPG'``, aka ``/80475047``. + +Deterministic key derivation maybe activated in: + + | ``Settings -> Seed Mode -> Set on`` + +This activation remains effective until *set off* is selected. + +The key management remains the same if seed mode is *on* or *off*. So there is no performance impact when using seeded keys. + +Seeded keys are generated as follow: + +**Step 1**: + +For a given keys slot n, starting from 1, a seed is first derived with the following path + + | ``Sn = BIP32_derive (/0x80475047/n)`` + +**Step 2**: + +Then specific seeds are derived with the *SHA3-XOF* function for each of the 4 key: + + | ``Sk[i] = SHA3-XOF(SHA256(Sn \| \| int16(i)), length)`` + +Where: + +- Sn is the dedicated slot seed from step 1. +- key_name is one of 'sig ','dec ', 'aut ', 'sym0', each 4 characters. +- i is the index, starting from 1, of the desired seed (see below) + +**Step 3**: + +*RSA key generation* + +Generate two seed Sp, Sq in step2 with: + +- i € {1,2} +- length equals to half key size + +Generate two prime numbers p, q: + +- p = next_prime(Sp) +- q = next_prime(Sq) + +Generate RSA key pair as usual: + +- choose e +- n = p*q +- d = inv(e) mod (p-1)(q-1) + +*ECC key generation* + +Generate one seed Sd in step2 with: + +- i = 1 +- length equals to curve size + +Generate ECC key pair: + +- d = Sd +- W = d.G + +*AES key generation* + +Generate one seed Sd in step2 with: + +- i = 1 +- length equals to 16 + +Generate AES key: + +- k = Sk + +Deterministic random number +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The deterministic random number generation relies on the **BIP32** scheme. +The master install path of the App is set to ``/0x80'GPG'``, aka ``/80475047``. + +**Random prime number generation**: + +For a given length *L*: + +- generate random number r of *L* bytes. +- generate rp = next_prime(r) +- return rp + +**Seeded random number**: + +For a given length *L* and seed *S*: + +- generate Sr = BIP32_derive(/0x80475047/0x0F0F0F0F) +- generate r = SHA3-XOF(SHA256(Sr \| 'rnd' \| S), L) +- return r + +**Seeded prime number generation**: + +For a given length *L* and seed *S*: + +- generate r as for "Seeded random number" +- generate rp = next_prime(r) +- return rp + +Key Backup & Restore +~~~~~~~~~~~~~~~~~~~~ + +In order to backup/restore private key the commands `put_data` and `get_data` accept the tags: + +- `B6` (signature key) +- `B8` (encryption key) +- `A4` (authentication). + +`put_data` command accept the exact output of `get_data`. The `get_data` command +return both the public and private key. + +For security and confidentiality, private key is returned encrypted in AES. +The key used is derived according to previously described AES key derivation +with name 'key'. + + +The data payload is formatted as follow: + + +-------+--------------------------+ + | size | Description | + +=======+==========================+ + | 4 | OS Target ID | + +-------+--------------------------+ + | 4 | API Level | + +-------+--------------------------+ + | 4 | compliance Level | + +-------+--------------------------+ + | 4 | public key size | + +-------+--------------------------+ + | var | public key | + +-------+--------------------------+ + | 4 | private key size | + +-------+--------------------------+ + | var | encrypted private key | + +-------+--------------------------+ + +APDU Modification +----------------- + +Key Slot management +~~~~~~~~~~~~~~~~~~~~ + +Key slots are managed by data object *01F1* and *01F2* witch are +manageable by PUT/GET DATA command as for others DO and organized as follow. + +On application reset, the *01F2* content is set to *Default Slot* value +of *01F1*. + +*01F1:* + + +-------+----------------------------------+-------+ + | bytes | Description | R/W | + +=======+==================================+=======+ + | 1 | Number of slot | R | + +-------+----------------------------------+-------+ + | 2 | Default slot | R/W | + +-------+----------------------------------+-------+ + | 3 | Allowed slot selection method | R/W | + +-------+----------------------------------+-------+ + +Byte 3 is endoced as follow: + + +----+----+----+----+----+----+----+----+-------------------------+ + | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Meaning | + +----+----+----+----+----+----+----+----+-------------------------+ + | \- | \- | \- | \- | \- | \- | \- | x | selection by APDU | + +----+----+----+----+----+----+----+----+-------------------------+ + | \- | \- | \- | \- | \- | \- | x | \- | selection by screen | + +----+----+----+----+----+----+----+----+-------------------------+ + +*01F2:* + + +--------+-----------------+-------+ + | bytes | Description | R/W | + +========+=================+=======+ + | 1 | Current slot | R/W | + +--------+-----------------+-------+ + +*01F0:* + + +--------+-----------------+-------+ + | bytes | Description | R/W | + +========+=================+=======+ + | 1-3 | 01F1 content | R | + +--------+-----------------+-------+ + | 4 | 01F2 content | R | + +--------+-----------------+-------+ + +*Access Conditions:* + + +------+-----------+------------+ + | DO | Read | Write | + +======+===========+============+ + | 01F0 | Always | Never | + +------+-----------+------------+ + | 01F1 | Always | Verify PW3 | + +------+-----------+------------+ + | 01F2 | Always | Verify PW2 | + +------+-----------+------------+ + +Deterministic key derivation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +P2 parameter of GENERATE ASYMMETRIC KEY PAIR is set to (hex value): + +- 00 for true random key generation +- 01 for seeded random key + +Deterministic random number +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +P1 parameter of GET CHALLENGE is a bit-field encoded as follow: + + +----+----+----+----+----+----+----+----+-------------------------+ + | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Meaning | + +----+----+----+----+----+----+----+----+-------------------------+ + | \- | \- | \- | \- | \- | \- | \- | x | seeded random | + +----+----+----+----+----+----+----+----+-------------------------+ + | \- | \- | \- | \- | \- | \- | x | \- | prime random | + +----+----+----+----+----+----+----+----+-------------------------+ + +When *seeded mode* is set, data field contains the seed and P2 contains +the length of random bytes to generate. + + +Other minor add-on +------------------ + +GnuPG use both fingerprints and serial number to identify key on card. +So, the `put_data` command is able to modify the AID file with '4F' tag. +In that case the data field shall be 4 bytes length and shall contain +the new serial number. '4F' is protected by PW3 (admin) PIN. diff --git a/doc/developer/quick-test.md b/doc/developer/quick-test.md new file mode 100644 index 0000000..1e3d892 --- /dev/null +++ b/doc/developer/quick-test.md @@ -0,0 +1,361 @@ +# Quick Test guide + +This page helps the developer to quickly test basic features of the OpenPGP Application. + +Please note this guideline is targeting Linux (Ubuntu) only machine, but once installed, +all commands are valid on any other OS. + +## Step 1: Install your device + +Do a fresh installation of OpenPGP Application on the device. + +## Step 2: Setup conf + +### Tools and scripts + +Ensure `pgp` is available on your host machine. + +Install the needed mandatory tools: + +```shell +sudo apt install libpcsclite-dev scdaemon pcscd -y +``` + +Optionally, the following diagnostic tool can also be installed: + +```shell +sudo apt install pcsc-tools -y +``` + +You can check the tools are operational with the commands (see *help* for other options): + +```shell +$ pcsc_scan -c + +Thu Jan 18 09:45:19 2024 + Reader 0: Ledger Nano S Plus [Nano S Plus] (0001) 00 00 + Event number: 0 + Card state: Card inserted, + ATR: 3B 00 + Reader 1: Alcor Micro AU9540 01 00 + Event number: 0 + Card state: Card removed, + +$ p11-kit list-modules +p11-kit-trust: p11-kit-trust.so + library-description: PKCS#11 Kit Trust Module + library-manufacturer: PKCS#11 Kit + library-version: 0.24 + token: System Trust + manufacturer: PKCS#11 Kit + model: p11-kit-trust + serial-number: 1 + hardware-version: 0.24 + flags: + write-protected + token-initialized +opensc-pkcs11: opensc-pkcs11.so + library-description: OpenSC smartcard framework + library-manufacturer: OpenSC Project + library-version: 0.22 + token: OpenPGP card (User PIN) + manufacturer: OpenPGP project + model: PKCS#15 emulated + serial-number: 2c97c3e750db + hardware-version: 3.3 + firmware-version: 3.3 + flags: + rng + login-required + user-pin-initialized + protected-authentication-path + token-initialized + token: OpenPGP card (User PIN (sig)) + manufacturer: OpenPGP project + model: PKCS#15 emulated + serial-number: 2c97c3e750db + hardware-version: 3.3 + firmware-version: 3.3 + flags: + rng + login-required + user-pin-initialized + protected-authentication-path + token-initialized + +Check the installation of CCID driver and more particularly its device config: + +Edit the file `/etc/libccid_Info.plist`, and check if the Ledger devices are correctly defined. + +Please take care, the different lists are ordered in the same way. Do not insert elements anywhere! + +> Note: To add a new Ledger device, check [doc/user/app-openpgp.rst](../user/app-openpgp.rst) + +### Manual Tests + +Jump into the directory `manual-tests`. There is a helper script to perform some of the following described operations. + +```shell +cd manual-tests/ +$ ./manual.sh -h + +Usage: ./manual.sh + +Options: + + -c : Requested command + -v : Verbose mode + -h : Displays this help +``` + +The `init` command allows to prepare a local `gnupg` home directory, with the default minimal config file for *scdaemon*. +For further investigations, you can also add in the file `manual-tests/gnupg/scdaemon.conf` the following lines: + +```shell +debug-level expert +debug 11 +log-file /tmp/scdaemon.log +``` + +## Step 3: Verify the card status and the pin code + +Launch the Application on the device. + +Now, on the PC, inside a terminal: + +```shell +$ killall scdaemon gpg-agent +$ gpg --homedir $(pwd)/gnupg --card-edit +gpg: keybox 'xxxx/manual-tests/gnupg/pubring.kbx' created + +Reader ...........: Ledger Nano S Plus [Nano S Plus] (0001) 01 00 +Application ID ...: D2760001240103032C97B7DA92860000 +Version ..........: 3.3 +Manufacturer .....: unknown +Serial number ....: B7DA9286 +Name of cardholder: [not set] +Language prefs ...: [not set] +Salutation .......: +URL of public key : [not set] +Login data .......: [not set] +Signature PIN ....: not forced +Key attributes ...: rsa2048 rsa2048 rsa2048 +Max. PIN lengths .: 12 12 12 +PIN retry counter : 3 0 3 +Signature counter : 0 +Signature key ....: [none] +Encryption key....: [none] +Authentication key: [none] +General key info..: [none] +``` + +This gives the status of the current card. We can see on the 1st line, the detected Card Reader (i.e. the Ledger device). +In the Application ID information, we can see, concatenated: + +- `D27600012401`: The *Registered application provider identifier* (ISO 7816-5) +- `01`: The *Proprietary application identifier extension* (OpenPGP) +- `0303`: The current specification version *3.3* +- `2C97`: The Ledger Manufacturer Id +- `B7DA9286`: the Card Serial + +And currently, no keys are present on the Card. +Now, we can verify the Card pin code using the `verify` command. +You will be prompt to validate with the buttons, or enter the pin code, depending on the Application settings. + +```shell +gpg/card> verify + +Reader ...........: Ledger Nano S Plus [Nano S Plus] (0001) 01 00 +Application ID ...: D2760001240103032C97B7DA92860000 +Version ..........: 3.3 +Manufacturer .....: unknown +Serial number ....: B7DA9286 +Name of cardholder: [not set] +Language prefs ...: [not set] +Salutation .......: +URL of public key : [not set] +Login data .......: [not set] +Signature PIN ....: not forced +Key attributes ...: rsa2048 rsa2048 rsa2048 +Max. PIN lengths .: 12 12 12 +PIN retry counter : 3 0 3 +Signature counter : 0 +Signature key ....: [none] +Encryption key....: [none] +Authentication key: [none] +General key info..: [none] +``` + +> Note: you can exit by using the command `quit` or using `CTRL-D`. + +## Step 4: Change to screen pin style + +Then on the device, go to: + +```text + settings -> PIN mode, and select ‘On Screen’ + settings -> PIN mode, and select ‘Set as default’ +``` + +unplug and replug the nanos, relaunch the Application, and check: + +```text + settings -> PIN mode, you should have ‘On Screen # +’ (DASH and PLUS) +``` + +## Step 5: Create RSA keys + +Back in the terminal window, in `manual-tests` directory. + +> Note: During this phase PIN has to be validate on the device. + +```shell +$ killall scdaemon gpg-agent +$ gpg --homedir $(pwd)/gnupg --card-edit +gpg: keybox 'xxxx/manual-tests/gnupg/pubring.kbx' created + +Reader ...........: Ledger Nano S Plus [Nano S Plus] (0001) 01 00 +Application ID ...: D2760001240103032C97B7DA92860000 +Version ..........: 3.3 +Manufacturer .....: unknown +Serial number ....: B7DA9286 +Name of cardholder: [not set] +Language prefs ...: [not set] +Salutation .......: +URL of public key : [not set] +Login data .......: [not set] +Signature PIN ....: not forced +Key attributes ...: rsa2048 rsa2048 rsa2048 +Max. PIN lengths .: 12 12 12 +PIN retry counter : 3 0 3 +Signature counter : 0 +Signature key ....: [none] +Encryption key....: [none] +Authentication key: [none] +General key info..: [none] +``` + +Now, switch to **Admin** mode, and generate the keys (this is an interactive operation). + +```shell +gpg/card> admin +Admin commands are allowed + +gpg/card> generate +Make off-card backup of encryption key? (Y/n) n + +Please note that the factory settings of the PINs are + PIN = '123456' Admin PIN = '12345678' +You should change them using the command --change-pin + +What keysize do you want for the Signature key? (2048) 2048 +What keysize do you want for the Encryption key? (2048) 2048 +What keysize do you want for the Authentication key? (2048) 2048 +Please specify how long the key should be valid. + 0 = key does not expire + = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years +Key is valid for? (0) 0 +Key does not expire at all +Is this correct? (y/N) y + +GnuPG needs to construct a user ID to identify your key. + +Real name: testkey +Email address: +Comment: +You selected this USER-ID: + "testkey" + +Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O +gpg: xxxx/manual-tests/gnupg/trustdb.gpg: trustdb created +gpg: key 5ED17DF289C757A2 marked as ultimately trusted +gpg: directory 'xxxx/manual-tests/gnupg/openpgp-revocs.d' created +gpg: revocation certificate stored as 'xxxx/manual-tests/gnupg/openpgp-revocs.d/7FDC3D2FCD3558CB06631EAB5ED17DF289C757A2.rev' +public and secret key created and signed. + +gpg/card> quit +pub rsa2048 2017-10-03 [SC] + 7FDC3D2FCD3558CB06631EAB5ED17DF289C757A2 +uid testkey +sub rsa2048 2017-10-03 [A] +sub rsa2047 2017-10-03 [E] +``` + +## Step 6: Encrypt/Decrypt + +Simple *Encrypt* and *Decrypt* test to use and check the generated keys. + +Start to create a dummy file to be encrypted and checked. + +```shell +$ killall scdaemon gpg-agent +$ echo CLEAR > foo.txt +``` + +### Encrypt + +Use this command to encrypt. Please note we specify the key to be used. + +```shell +$ gpg --homedir $(pwd)/gnupg --encrypt --recipient testkey foo.txt +gpg: checking the trustdb +gpg: marginals needed: 3 completes needed: 1 trust model: pgp +gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u +``` + +Just kill the processes to force pin to be asked... + +```shell +$ killall gpg-agent scdaemon +``` + +### Decrypt + +Use this command to encrypt. Here, no need to specify the key... + +```shell +$ gpg --homedir $(pwd)/gnupg --decrypt foo.txt.gpg > foo_dec.txt +gpg: encrypted with 2047-bit RSA key, ID 602FE5EB7BFA4B00, created 2017-10-03 +"testkey" +$ cat foo_dec.txt +CLEAR +``` + +## Step 7: Sign/Verify + +Simple *Sign* and *Verify* test to use and check signature with the generated keys. + +Start to create a dummy file to be signed and verified. + +```shell +$ killall scdaemon gpg-agent +$ echo CLEAR > foo.txt +``` + +### Sign + +Use this command to sign. The generated file is the encrypted signature. + +```shell +$ gpg --homedir $(pwd)/gnupg --sign foo.txt +``` + +Just kill the processes to force pin to be asked... + +```shell +$ killall gpg-agent scdaemon +``` + +### Verify + +Use this command to verify the signature + +```shell +$ gpg --homedir $(pwd)/gnupg --verify foo.txt.gpg +gpg: Signature made jeu. 18 janv. 2024 09:59:33 CET +gpg: using RSA key FD2D7E0C99825F8515EDB544156DEEF5959D4DC6 +gpg: Good signature from "testkey" [ultimate] +``` diff --git a/doc/developer/slots.png b/doc/developer/slots.png new file mode 100644 index 0000000..8c2a9a0 Binary files /dev/null and b/doc/developer/slots.png differ diff --git a/doc/developer/template.latex b/doc/developer/template.latex new file mode 100644 index 0000000..5749c63 --- /dev/null +++ b/doc/developer/template.latex @@ -0,0 +1,516 @@ +% Options for packages loaded elsewhere +\PassOptionsToPackage{unicode$for(hyperrefoptions)$,$hyperrefoptions$$endfor$}{hyperref} +\PassOptionsToPackage{hyphens}{url} +$if(colorlinks)$ +\PassOptionsToPackage{dvipsnames,svgnames*,x11names*}{xcolor} +$endif$ +$if(dir)$ +$if(latex-dir-rtl)$ +\PassOptionsToPackage{RTLdocument}{bidi} +$endif$ +$endif$ +$if(CJKmainfont)$ +\PassOptionsToPackage{space}{xeCJK} +$endif$ +% +\documentclass[ +$if(fontsize)$ + $fontsize$, +$endif$ +$if(lang)$ + $babel-lang$, +$endif$ +$if(papersize)$ + $papersize$paper, +$endif$ +$if(beamer)$ + ignorenonframetext, +$if(handout)$ + handout, +$endif$ +$if(aspectratio)$ + aspectratio=$aspectratio$, +$endif$ +$endif$ +$for(classoption)$ + $classoption$$sep$, +$endfor$ +]{$documentclass$} +$if(beamer)$ +$if(background-image)$ +\usebackgroundtemplate{% + \includegraphics[width=\paperwidth]{$background-image$}% +} +$endif$ +\usepackage{pgfpages} +\setbeamertemplate{caption}[numbered] +\setbeamertemplate{caption label separator}{: } +\setbeamercolor{caption name}{fg=normal text.fg} +\beamertemplatenavigationsymbols$if(navigation)$$navigation$$else$empty$endif$ +$for(beameroption)$ +\setbeameroption{$beameroption$} +$endfor$ +% Prevent slide breaks in the middle of a paragraph +\widowpenalties 1 10000 +\raggedbottom +$if(section-titles)$ +\setbeamertemplate{part page}{ + \centering + \begin{beamercolorbox}[sep=16pt,center]{part title} + \usebeamerfont{part title}\insertpart\par + \end{beamercolorbox} +} +\setbeamertemplate{section page}{ + \centering + \begin{beamercolorbox}[sep=12pt,center]{part title} + \usebeamerfont{section title}\insertsection\par + \end{beamercolorbox} +} +\setbeamertemplate{subsection page}{ + \centering + \begin{beamercolorbox}[sep=8pt,center]{part title} + \usebeamerfont{subsection title}\insertsubsection\par + \end{beamercolorbox} +} +\AtBeginPart{ + \frame{\partpage} +} +\AtBeginSection{ + \ifbibliography + \else + \frame{\sectionpage} + \fi +} +\AtBeginSubsection{ + \frame{\subsectionpage} +} +$endif$ +$endif$ +$if(beamerarticle)$ +\usepackage{beamerarticle} % needs to be loaded first +$endif$ +$if(fontfamily)$ +\usepackage[$for(fontfamilyoptions)$$fontfamilyoptions$$sep$,$endfor$]{$fontfamily$} +$else$ +\usepackage{lmodern} +$endif$ +$if(linestretch)$ +\usepackage{setspace} +$endif$ +\usepackage{amssymb,amsmath} +\usepackage{ifxetex,ifluatex} +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + \usepackage[$if(fontenc)$$fontenc$$else$T1$endif$]{fontenc} + \usepackage[utf8]{inputenc} + \usepackage{textcomp} % provide euro and other symbols +\else % if luatex or xetex +$if(mathspec)$ + \ifxetex + \usepackage{mathspec} + \else + \usepackage{unicode-math} + \fi +$else$ + \usepackage{unicode-math} +$endif$ + \defaultfontfeatures{Scale=MatchLowercase} + \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1} +$if(mainfont)$ + \setmainfont[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} +$endif$ +$if(sansfont)$ + \setsansfont[$for(sansfontoptions)$$sansfontoptions$$sep$,$endfor$]{$sansfont$} +$endif$ +$if(monofont)$ + \setmonofont[$for(monofontoptions)$$monofontoptions$$sep$,$endfor$]{$monofont$} +$endif$ +$for(fontfamilies)$ + \newfontfamily{$fontfamilies.name$}[$for(fontfamilies.options)$$fontfamilies.options$$sep$,$endfor$]{$fontfamilies.font$} +$endfor$ +$if(mathfont)$ +$if(mathspec)$ + \ifxetex + \setmathfont(Digits,Latin,Greek)[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \else + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \fi +$else$ + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} +$endif$ +$endif$ +$if(CJKmainfont)$ + \ifxetex + \usepackage{xeCJK} + \setCJKmainfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + \fi +$endif$ +$if(luatexjapresetoptions)$ + \ifluatex + \usepackage[$for(luatexjapresetoptions)$$luatexjapresetoptions$$sep$,$endfor$]{luatexja-preset} + \fi +$endif$ +$if(CJKmainfont)$ + \ifluatex + \usepackage[$for(luatexjafontspecoptions)$$luatexjafontspecoptions$$sep$,$endfor$]{luatexja-fontspec} + \setmainjfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + \fi +$endif$ +\fi +$if(beamer)$ +$if(theme)$ +\usetheme[$for(themeoptions)$$themeoptions$$sep$,$endfor$]{$theme$} +$endif$ +$if(colortheme)$ +\usecolortheme{$colortheme$} +$endif$ +$if(fonttheme)$ +\usefonttheme{$fonttheme$} +$endif$ +$if(mainfont)$ +\usefonttheme{serif} % use mainfont rather than sansfont for slide text +$endif$ +$if(innertheme)$ +\useinnertheme{$innertheme$} +$endif$ +$if(outertheme)$ +\useoutertheme{$outertheme$} +$endif$ +$endif$ +% Use upquote if available, for straight quotes in verbatim environments +\IfFileExists{upquote.sty}{\usepackage{upquote}}{} +\IfFileExists{microtype.sty}{% use microtype if available + \usepackage[$for(microtypeoptions)$$microtypeoptions$$sep$,$endfor$]{microtype} + \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts +}{} +$if(indent)$ +$else$ +\makeatletter +\@ifundefined{KOMAClassName}{% if non-KOMA class + \IfFileExists{parskip.sty}{% + \usepackage{parskip} + }{% else + \setlength{\parindent}{0pt} + \setlength{\parskip}{6pt plus 2pt minus 1pt}} +}{% if KOMA class + \KOMAoptions{parskip=half}} +\makeatother +$endif$ +$if(verbatim-in-note)$ +\usepackage{fancyvrb} +$endif$ +\usepackage{xcolor} +\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available +\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}} +\hypersetup{ +$if(title-meta)$ + pdftitle={$title-meta$}, +$endif$ +$if(author-meta)$ + pdfauthor={$author-meta$}, +$endif$ +$if(lang)$ + pdflang={$lang$}, +$endif$ +$if(subject)$ + pdfsubject={$subject$}, +$endif$ +$if(keywords)$ + pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$}, +$endif$ +$if(colorlinks)$ + colorlinks=true, + linkcolor=$if(linkcolor)$$linkcolor$$else$Maroon$endif$, + filecolor=$if(filecolor)$$filecolor$$else$Maroon$endif$, + citecolor=$if(citecolor)$$citecolor$$else$Blue$endif$, + urlcolor=$if(urlcolor)$$urlcolor$$else$Blue$endif$, +$else$ + hidelinks, +$endif$ + pdfcreator={LaTeX via pandoc}} +\urlstyle{same} % disable monospaced font for URLs +$if(verbatim-in-note)$ +\VerbatimFootnotes % allow verbatim text in footnotes +$endif$ +$if(geometry)$ +$if(beamer)$ +\geometry{$for(geometry)$$geometry$$sep$,$endfor$} +$else$ +\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} +$endif$ +$endif$ +$if(beamer)$ +\newif\ifbibliography +$endif$ +$if(listings)$ +\usepackage{listings} +\newcommand{\passthrough}[1]{#1} +\lstset{defaultdialect=[5.3]Lua} +\lstset{defaultdialect=[x86masm]Assembler} +$endif$ +$if(lhs)$ +\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} +$endif$ +$if(highlighting-macros)$ +$highlighting-macros$ +$endif$ +$if(tables)$ +\usepackage{longtable,booktabs} +$if(beamer)$ +\usepackage{caption} +% Make caption package work with longtable +\makeatletter +\def\fnum@table{\tablename~\thetable} +\makeatother +$else$ +% Correct order of tables after \paragraph or \subparagraph +\usepackage{etoolbox} +\makeatletter +\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{} +\makeatother +% Allow footnotes in longtable head/foot +\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}} +\makesavenoteenv{longtable} +$endif$ +$endif$ +$if(graphics)$ +\usepackage{graphicx} +\makeatletter +\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} +\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} +\makeatother +% Scale images if necessary, so that they will not overflow the page +% margins by default, and it is still possible to overwrite the defaults +% using explicit options in \includegraphics[width, height, ...]{} +\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} +% Set default figure placement to htbp +\makeatletter +\def\fps@figure{htbp} +\makeatother +$endif$ +$if(links-as-notes)$ +% Make links footnotes instead of hotlinks: +\DeclareRobustCommand{\href}[2]{#2\footnote{\url{#1}}} +$endif$ +$if(strikeout)$ +\usepackage[normalem]{ulem} +% Avoid problems with \sout in headers with hyperref +\pdfstringdefDisableCommands{\renewcommand{\sout}{}} +$endif$ +\setlength{\emergencystretch}{3em} % prevent overfull lines +\providecommand{\tightlist}{% + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +$if(numbersections)$ +\setcounter{secnumdepth}{$if(secnumdepth)$$secnumdepth$$else$5$endif$} +$else$ +\setcounter{secnumdepth}{-\maxdimen} % remove section numbering +$endif$ +$if(beamer)$ +$else$ +$if(block-headings)$ +% Make \paragraph and \subparagraph free-standing +\ifx\paragraph\undefined\else + \let\oldparagraph\paragraph + \renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}} +\fi +\ifx\subparagraph\undefined\else + \let\oldsubparagraph\subparagraph + \renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}} +\fi +$endif$ +$endif$ +$if(pagestyle)$ +\pagestyle{$pagestyle$} +$endif$ +$for(header-includes)$ +$header-includes$ +$endfor$ +$if(lang)$ +\ifxetex + % Load polyglossia as late as possible: uses bidi with RTL languages (e.g. Hebrew, Arabic) + \usepackage{polyglossia} + \setmainlanguage[$polyglossia-lang.options$]{$polyglossia-lang.name$} +$for(polyglossia-otherlangs)$ + \setotherlanguage[$polyglossia-otherlangs.options$]{$polyglossia-otherlangs.name$} +$endfor$ +\else + \usepackage[shorthands=off,$for(babel-otherlangs)$$babel-otherlangs$,$endfor$main=$babel-lang$]{babel} +$if(babel-newcommands)$ + $babel-newcommands$ +$endif$ +\fi +$endif$ +$if(dir)$ +\ifxetex + % Load bidi as late as possible as it modifies e.g. graphicx + \usepackage{bidi} +\fi +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + \TeXXeTstate=1 + \newcommand{\RL}[1]{\beginR #1\endR} + \newcommand{\LR}[1]{\beginL #1\endL} + \newenvironment{RTL}{\beginR}{\endR} + \newenvironment{LTR}{\beginL}{\endL} +\fi +$endif$ +$if(natbib)$ +\usepackage[$natbiboptions$]{natbib} +\bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$} +$endif$ +$if(biblatex)$ +\usepackage[$if(biblio-style)$style=$biblio-style$,$endif$$for(biblatexoptions)$$biblatexoptions$$sep$,$endfor$]{biblatex} +$for(bibliography)$ +\addbibresource{$bibliography$} +$endfor$ +$endif$ +$if(csl-refs)$ +\newlength{\cslhangindent} +\setlength{\cslhangindent}{1.5em} +\newenvironment{cslreferences}% + {$if(csl-hanging-indent)$\setlength{\parindent}{0pt}% + \everypar{\setlength{\hangindent}{\cslhangindent}}\ignorespaces$endif$}% + {\par} +$endif$ + +$if(title)$ +\title{$title$$if(thanks)$\thanks{$thanks$}$endif$} +$endif$ +$if(subtitle)$ +$if(beamer)$ +$else$ +\usepackage{etoolbox} +\makeatletter +\providecommand{\subtitle}[1]{% add subtitle to \maketitle + \apptocmd{\@title}{\par {\large #1 \par}}{}{} +} +\makeatother +$endif$ +\subtitle{$subtitle$} +$endif$ +\author{$for(author)$$author$$sep$ \and $endfor$} +\date{$date$} +$if(beamer)$ +$if(institute)$ +\institute{$for(institute)$$institute$$sep$ \and $endfor$} +$endif$ +$if(titlegraphic)$ +\titlegraphic{\includegraphics{$titlegraphic$}} +$endif$ +$if(logo)$ +\logo{\includegraphics{$logo$}} +$endif$ +$endif$ + +\begin{document} + +\begin{titlepage} + \centering + {\scshape\Huge OpenPGP Card Application \par} + {\scshape \huge Add-on \par} + \vspace{1cm} + + {\scshape\LARGE Ledger SAS \par} + \vspace{2cm} + + \includegraphics{../LogoLedger.png} + + \vspace{1cm} + {\Large\itshape\url {https://github.com/LedgerHQ/app-openpgp}\par} + + \vfill + {\large \today\par} +\end{titlepage} + +$if(has-frontmatter)$ +\frontmatter +$endif$ +$if(title)$ +$if(beamer)$ +\frame{\titlepage} +$else$ +\maketitle +$endif$ +$if(abstract)$ +\begin{abstract} +$abstract$ +\end{abstract} +$endif$ +$endif$ + +$for(include-before)$ +$include-before$ + +$endfor$ +$if(toc)$ +$if(toc-title)$ +\renewcommand*\contentsname{$toc-title$} +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks] +$if(toc-title)$ + \frametitle{$toc-title$} +$endif$ + \tableofcontents[hideallsubsections] +\end{frame} +$else$ +{ +$if(colorlinks)$ +\hypersetup{linkcolor=$if(toccolor)$$toccolor$$else$$endif$} +$endif$ +\setcounter{tocdepth}{$toc-depth$} +\tableofcontents +} +$endif$ +$endif$ +$if(lot)$ +\listoftables +$endif$ +$if(lof)$ +\listoffigures +$endif$ +$if(linestretch)$ +\setstretch{$linestretch$} +$endif$ +$if(has-frontmatter)$ +\mainmatter +$endif$ +$body$ + +$if(has-frontmatter)$ +\backmatter +$endif$ +$if(natbib)$ +$if(bibliography)$ +$if(biblio-title)$ +$if(has-chapters)$ +\renewcommand\bibname{$biblio-title$} +$else$ +\renewcommand\refname{$biblio-title$} +$endif$ +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue +$endif$ + \bibliography{$for(bibliography)$$bibliography$$sep$,$endfor$} +$if(beamer)$ +\end{frame} +$endif$ + +$endif$ +$endif$ +$if(biblatex)$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue + \printbibliography[heading=none] +\end{frame} +$else$ +\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ +$endif$ + +$endif$ +$for(include-after)$ +$include-after$ + +$endfor$ +\end{document} diff --git a/doc/developper/gpgcard3.0-addon.rst b/doc/developper/gpgcard3.0-addon.rst deleted file mode 100644 index 9ffa82c..0000000 --- a/doc/developper/gpgcard3.0-addon.rst +++ /dev/null @@ -1,326 +0,0 @@ -License -======= - -Author: Cedric Mesnil - -License: - - - | Copyright 2017 Cedric Mesnil , Ledger SAS - | - | Licensed under the Apache License, Version 2.0 (the "License"); - | you may not use this file except in compliance with the License. - | You may obtain a copy of the License at - | - | http://www.apache.org/licenses/LICENSE-2.0 - | - | Unless required by applicable law or agreed to in writing, software - | distributed under the License is distributed on an "AS IS" BASIS, - | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - | See the License for the specific language governing permissions and - | limitations under the License. - - - -Introduction -============ - -OpenPGP Card Application v3.0 add-ons summary ---------------------------------------------- - -Key management: -~~~~~~~~~~~~~~~ - -OpenPGP Application manage four keys for cryptographic operation (PSO) plus two -for secure channel. - -The first four keys are defined as follow: - - One asymmetric signature private key (RSA or EC), named 'sig'; - - One asymmetric decryption private key (RSA or EC), named 'dec' - - One asymmetric authentication private key (RSA or EC), named 'aut' - - One symmetric decryption private key (AES), named 'sym0' - -The 3 first asymmetric keys can be either randomly generated on-card or -explicitly put from outside. - -The fourth is put from outside. - -It's never possible to retrieve private key from the card. - -This add-on specification propose a solution to derive those keys from the -master seed managed by the Ledger Token. -This allow owner to restore a broken token without the needs to keep track of keys -outside the card. - -Moreover this add-on specification propose to manage multiple set of the -four previously described keys. - -Random number generation -~~~~~~~~~~~~~~~~~~~~~~~~ - -OpenPGP Application provides, as optional feature, to generate random bytes. - -This add-on specification propose new type of random generation: -- random prime number generation -- seeded random number -- seeded prime number generation - - -Key Backup -~~~~~~~~~~ - -A full keybackup mecanism is provided. - - -GPG-ledger -========== - -Definitions ------------ - - - The application is named GPG-ledger - - A keys set is named 'keys slot' - -How ---- - -Deterministic key derivation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The deterministic key derivation process relies on the BIP32 scheme. -The master install path of GPG-ledger is set to /0x80'GPG', aka /80475047 -Deterministic key derivation maybe activated in: - Settings->Seed Mode->Set on - -This activation remains effective until *set off* is selected or the application -ends. - -The key management remains the same if seed mode is on or off, i.e. key are stored in memory key containers. So their is no perfomance inpact when using seeded keys. - -Seeded keys are generated as follow: - -**Step1**: - -For a given keys slot n, starting from 1, a seed is first derived with the following path - -Sn = BIP32_derive (/0x80475047/n) - -**Step2**: - -Then specific seeds are derived with the SHA3-XOF function for each of the four key : - - Sk[i] = SHA3-XOF(SHA256(Sn \| \| int16(i)), length) - -Sn is the dedicated slot seed from step 1. -key_name is one of 'sig ','dec ', 'aut ', 'sym0', each four characters. -i is the index, starting from 1, of the desired seed (see below) - - -**Step 3**: - -*RSA key are generated as follow* : - -Generate two seed Sp, Sq in step2 with : - - i € {1,2} - - length equals to half key size - -Generate two prime numbers p, q : - - p = next_prime(Sp) - - q = next_prime(Sq) - -Generate RSA key pair as usual. - - choose e - - n = p*q - - d = inv(e) mod (p-1)(q-1) - -*ECC key genration* : - -Generate one seed Sd in step2 with : - - i = 1 - - length equals to curve size - -Generate ECC key pair : - - d = Sd - - W = d.G - - -*AES key generation* : - -Generate one seed Sd in step2 with : - - i = 1 - - length equals to 16 - -Generate AES key : - - k = Sk - -Deterministic random number -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The deterministic random number generation relies on the BIP32 scheme. -The master install path of GPG-ledger is set to /0x80'GPG', aka /80475047 - -**Random prime number generation** : - - For a given length *L*: - - - generate random number r of *L* bytes. - - generate rp = next_prime(r) - - return rp - -**Seeded random number** : - -For a given length *L* and seed *S*: - - - generate Sr = BIP32_derive(/0x80475047/0x0F0F0F0F) - - generate r = SHA3-XOF(SHA256(Sr \| 'rnd' \| S), L) - - return r - -**Seeded prime number generation** : - -For a given length *L* and seed *S*: - - - generate r as for "Seeded random number" - - generate rp = next_prime(r) - - return rp - - - -Key Backup & Restore -~~~~~~~~~~~~~~~~~~~~ - -In order to backup/restore private key the commands `put_data` and -`get_data` accept the tag `B6` (signature key), `B8`(encryption key), -`A4` (authentication). - -put_data command accept the exact output of get_data. The get_data command -return both the public and private key. - -For security and confidentiality private key is returned encryped in AES. -The key used is derived according to previously described AES key derivation -with name 'key '. - - -The data payload is formatted as follow: - - +-------+--------------------------------------------------+ - | size | Description | - +=======+==================================================+ - | 4 | OS Target ID | - +-------+--------------------------------------------------+ - | 4 | API Level | - +-------+--------------------------------------------------+ - | 4 | compliance Level | - +-------+--------------------------------------------------+ - | 4 | public key size | - +-------+--------------------------------------------------+ - | var | public key | - +-------+--------------------------------------------------+ - | 4 | private key size | - +-------+--------------------------------------------------+ - | var | encrypted private key | - +-------+--------------------------------------------------+ - - -APDU Modification ------------------ - -Key Slot management -~~~~~~~~~~~~~~~~~~~~ - -Key slots are managed by data object 01F1 and 01F2 witch are -manageable by PUT/GET DATA command as for others DO and organized as follow. - -On application reset, the *01F2* content is set to *Default Slot* value -of *01F1*. - -*01F1:* - - +------+--------------------------------------------------+--------+ - |bytes | description | R/W | - +======+==================================================+========+ - | 1 | Number of slot | R | - +------+--------------------------------------------------+--------+ - | 2 | Default slot | R/W | - +------+--------------------------------------------------+--------+ - | 3 | Allowed slot selection method | R/W | - +------+--------------------------------------------------+--------+ - -Byte 3 is endoced as follow: - - +----+----+----+----+----+----+----+----+-------------------------+ - | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Meaning | - +----+----+----+----+----+----+----+----+-------------------------+ - | \- | \- | \- | \- | \- | \- | \- | x | selection by APDU | - +----+----+----+----+----+----+----+----+-------------------------+ - | \- | \- | \- | \- | \- | \- | x | \- | selection by screen | - +----+----+----+----+----+----+----+----+-------------------------+ - - - - -*01F2:* - - +------+--------------------------------------------------+--------+ - |bytes | Description | R/W | - +======+==================================================+========+ - | 1 | Current slot | R/W | - +------+--------------------------------------------------+--------+ - -*01F0:* - - +------+--------------------------------------------------+--------+ - |bytes | Description | R/W | - +======+==================================================+========+ - | 1-3 | 01F1 content | R | - +------+--------------------------------------------------+--------+ - | 4 | 01F2 content | R | - +------+--------------------------------------------------+--------+ - - -*Access Conditions:* - - +-------+------------+-------------+ - | DO | Read | Write | - +=======+============+=============+ - | 01F0 | Always | Never | - +-------+------------+-------------+ - | 01F1 | Always | Verify PW3 | - +-------+------------+-------------+ - | 01F2 | Always | Verify PW2 | - +-------+------------+-------------+ - - - -Deterministic key derivation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -P2 parameter of GENERATE ASYMMETRIC KEY PAIR is set to (hex value): - - 00 for true random key generation - - 01 for seeded random key - - -Deterministic random number -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -P1 parameter of GET CHALLENGE is a bits field encoded as follow: - - +----+-----+----+----+----+----+----+----+-------------------------+ - | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | Meaning | - +----+-----+----+----+----+----+----+----+-------------------------+ - | \- | \- | \- | \- | \- | \- | \- | x | prime random | - +----+-----+----+----+----+----+----+----+-------------------------+ - | \- | \- | \- | \- | \- | \- | x | \- | seeded random | - +----+-----+----+----+----+----+----+----+-------------------------+ - - -When bit b2 is set, data field contains the seed and P2 contains -the length of random bytes to generate. - - -Other minor add-on ------------------- - -GnuPG use both fingerprints and serial number to identfy key on card. -So, the put data command is able to modify the AID file with '4F' tag. -In that case the data field shall be four bytes length and shall contain -the new serial number. '4F' is protected by PW3 (admin) PIN. diff --git a/doc/developper/quick-test.txt b/doc/developper/quick-test.txt deleted file mode 100644 index 5558ce7..0000000 --- a/doc/developper/quick-test.txt +++ /dev/null @@ -1,196 +0,0 @@ -Step1: ... ------ -Jump into any temp dir - - -Step2: install nanos ------ -Do a fresh install of gpg application 1.1.0 from google app manager - - -Step3: setup conf ------ -Create a 'manual-test' directory - $ mkdir manual-test - -Create a 'manual-test/gnupg' - $ mkdir manual-test/gnupg - -Create a 'manual-test/gnupg/scdaemon.conf' file with content: - reader-port "Ledger Token [Nano S] (0001) 01 00" - allow-admin - card-timeout 1 - debug-level expert - debug 11 - log-file /tmp/scdaemon.log - -Jump into manual-test dir - -Step4: change to host pin style ------ -Launch gpg NanoS application and: - $ killall scdaemon gpg-agent - $ gpg2 --homedir `pwd`/gnupg --card-edit - gpg: WARNING: unsafe permissions on homedir '/home/cme/Projects/Git/ledgerblue/blue-app-openpgp-card/manual-test/gnupg' - gpg: keybox '/home/cme/Projects/Git/ledgerblue/blue-app-openpgp-card/manual-test/gnupg/pubring.kbx' created - - Reader ...........: Ledger Token [Nano S] (0001) 01 00 - Application ID ...: D2760001240103002C97DDD38BA90000 - Version ..........: 3.0 - Manufacturer .....: unknown - Serial number ....: DDD38BA9 - Name of cardholder: [not set] - Language prefs ...: [not set] - Sex ..............: unspecified - URL of public key : [not set] - Login data .......: [not set] - Signature PIN ....: not forced - Key attributes ...: rsa2048 rsa2048 rsa2048 - Max. PIN lengths .: 12 12 12 - PIN retry counter : 3 0 3 - Signature counter : 0 - Signature key ....: [none] - Encryption key....: [none] - Authentication key: [none] - General key info..: [none] - - gpg/card> verify - - Reader ...........: Ledger Token [Nano S] (0001) 01 00 - Application ID ...: D2760001240103002C97DDD38BA90000 - Version ..........: 3.0 - Manufacturer .....: unknown - Serial number ....: DDD38BA9 - Name of cardholder: [not set] - Language prefs ...: [not set] - Sex ..............: unspecified - URL of public key : [not set] - Login data .......: [not set] - Signature PIN ....: not forced - Key attributes ...: rsa2048 rsa2048 rsa2048 - Max. PIN lengths .: 12 12 12 - PIN retry counter : 3 0 3 - Signature counter : 0 - Signature key ....: [none] - Encryption key....: [none] - Authentication key: [none] - General key info..: [none] - - gpg/card> - -Then on nanos, goto settings->PIN mode, and select 'Host' -Then on nanos, goto settings->PIN mode, and select 'Set as default' - -unplug and replug the nanos - -relaunch the openpgp application - -Goto settings->PIN mode, and check you have "Host # +" (DASH and PLUS) - - -Step5: create 2048bits RSA keys ------ - - -In 'manual-test' directory, ask key generation. Nota that during this phase PIN has to be validate on Nanos - - $ killall scdaemon gpg-agent - $ gpg2 --homedir `pwd`/gnupg --card-edit - gpg: WARNING: unsafe permissions on homedir '/home/cme/Projects/Git/ledgerblue/blue-app-openpgp-card/manual-test/gnupg' - - Reader ...........: Ledger Token [Nano S] (0001) 01 00 - Application ID ...: D2760001240103002C97DDD38BA90000 - Version ..........: 3.0 - Manufacturer .....: unknown - Serial number ....: DDD38BA9 - Name of cardholder: [not set] - Language prefs ...: [not set] - Sex ..............: unspecified - URL of public key : [not set] - Login data .......: [not set] - Signature PIN ....: not forced - Key attributes ...: rsa2048 rsa2048 rsa2048 - Max. PIN lengths .: 12 12 12 - PIN retry counter : 3 0 3 - Signature counter : 0 - Signature key ....: [none] - Encryption key....: [none] - Authentication key: [none] - General key info..: [none] - - gpg/card> admin - Admin commands are allowed - - gpg/card> generate - Make off-card backup of encryption key? (Y/n) n - - Please note that the factory settings of the PINs are - PIN = '123456' Admin PIN = '12345678' - You should change them using the command --change-pin - - What keysize do you want for the Signature key? (2048) 2048 - What keysize do you want for the Encryption key? (2048) 2048 - What keysize do you want for the Authentication key? (2048) 2048 - Please specify how long the key should be valid. - 0 = key does not expire - = key expires in n days - w = key expires in n weeks - m = key expires in n months - y = key expires in n years - Key is valid for? (0) 0 - Key does not expire at all - Is this correct? (y/N) y - - GnuPG needs to construct a user ID to identify your key. - - Real name: testkey - Email address: - Comment: - You selected this USER-ID: - "testkey" - - Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O - gpg: /home/cme/Projects/Git/ledgerblue/blue-app-openpgp-card/manual-test/gnupg/trustdb.gpg: trustdb created - gpg: key 5ED17DF289C757A2 marked as ultimately trusted - gpg: directory '/home/cme/Projects/Git/ledgerblue/blue-app-openpgp-card/manual-test/gnupg/openpgp-revocs.d' created - gpg: revocation certificate stored as '/home/cme/Projects/Git/ledgerblue/blue-app-openpgp-card/manual-test/gnupg/openpgp-revocs.d/7FDC3D2FCD3558CB06631EAB5ED17DF289C757A2.rev' - public and secret key created and signed. - - - gpg/card> quit - pub rsa2048 2017-10-03 [SC] - 7FDC3D2FCD3558CB06631EAB5ED17DF289C757A2 - uid testkey - sub rsa2048 2017-10-03 [A] - sub rsa2047 2017-10-03 [E] - - - -Step6: encrypt/decrypt ------ -encrypt - - $ killall scdaemon gpg-agent - $ echo CLEAR > foo.txt - $ gpg2 --homedir `pwd`/gnupg -e -r testkey foo.txt - gpg: WARNING: unsafe permissions on homedir '/home/cme/Projects/Git/ledgerblue/blue-app-openpgp-card/manual-test/gnupg' - gpg: checking the trustdb - gpg: marginals needed: 3 completes needed: 1 trust model: pgp - gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u - -Force pin to asked - - $ killall gpg-agent scdaemon - -decrypt - - $ gpg2 --homedir `pwd`/gnupg foo.txt.gpg - gpg: WARNING: unsafe permissions on homedir '/home/cme/Projects/Git/ledgerblue/blue-app-openpgp-card/manual-test/gnupg' - gpg: encrypted with 2047-bit RSA key, ID 602FE5EB7BFA4B00, created 2017-10-03 - "testkey" - File 'foo.txt' exists. Overwrite? (y/N) y - -Step7: pin on screen ------- - -Restart from Step1, but skip step4 diff --git a/doc/generate.sh b/doc/generate.sh new file mode 100755 index 0000000..7f8716d --- /dev/null +++ b/doc/generate.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +NAMES=() +NAMES+=(user/app-openpgp) +NAMES+=(developer/gpgcard-addon) + +OPTIONS=() +OPTIONS+=("--standalone") +OPTIONS+=("--from=rst") +OPTIONS+=("--to=latex") +OPTIONS+=("--variable=papersize:A4") +OPTIONS+=("--variable=geometry:margin=1in") +OPTIONS+=("--variable=fontsize:10pt") +OPTIONS+=("--toc") +OPTIONS+=("--number-sections") +OPTIONS+=("--template=template.latex") + +for name in "${NAMES[@]}"; do + rm -f "${name}.pdf" + dir=$(dirname "${name}") + file=$(basename "${name}") + + (cd "${dir}"; pandoc ${OPTIONS[@]} --output="${file}.pdf" "${file}.rst") +done diff --git a/doc/specification/OpenPGP-smart-card-application.pdf b/doc/specification/OpenPGP-smart-card-application.pdf new file mode 100644 index 0000000..841c172 Binary files /dev/null and b/doc/specification/OpenPGP-smart-card-application.pdf differ diff --git a/doc/specification/openpgp-card-3.0.pdf b/doc/specification/openpgp-card-3.0.pdf deleted file mode 100644 index 3064f70..0000000 Binary files a/doc/specification/openpgp-card-3.0.pdf and /dev/null differ diff --git a/doc/user/0001-plist.patch b/doc/user/0001-plist.patch new file mode 100644 index 0000000..73ca1ad --- /dev/null +++ b/doc/user/0001-plist.patch @@ -0,0 +1,33 @@ +--- libccid_Info.plist_org 2023-10-17 10:50:56.030148081 +0200 ++++ libccid_Info.plist_test 2024-01-16 15:11:54.798891142 +0100 +@@ -462,6 +462,8 @@ + 0x2D25 + 0x2C97 + 0x2C97 ++ 0x2C97 ++ 0x2C97 + 0x17EF + 0x17EF + 0x17EF +@@ -1005,8 +1007,10 @@ + 0x43A9 + 0x0000 + 0x0001 +- 0x0001 +- 0x0004 ++ 0x1009 ++ 0x4009 ++ 0x5009 ++ 0x6009 + 0x6007 + 0x6055 + 0x6111 +@@ -1552,6 +1556,8 @@ + KRONEGGER Micro Core Platform + Ledger Nano S + Ledger Nano X ++ Ledger Nano S Plus ++ Ledger Stax + Lenovo Lenovo USB Smartcard Keyboard + Lenovo Lenovo USB Smartcard Keyboard + Lenovo Lenovo Smartcard Wired Keyboard II diff --git a/doc/user/blue-app-openpgp-card.pdf b/doc/user/app-openpgp.pdf similarity index 77% rename from doc/user/blue-app-openpgp-card.pdf rename to doc/user/app-openpgp.pdf index 614ae08..5e2ea57 100644 Binary files a/doc/user/blue-app-openpgp-card.pdf and b/doc/user/app-openpgp.pdf differ diff --git a/doc/user/app-openpgp.rst b/doc/user/app-openpgp.rst new file mode 100644 index 0000000..48ac27d --- /dev/null +++ b/doc/user/app-openpgp.rst @@ -0,0 +1,1695 @@ +.. + Ledger App OpenPGP. + (c) 2024 Ledger SAS. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +.. + ------------------------------------------------------------------------ + LaTex substitution Definition + ------------------------------------------------------------------------ + + + +License +======= + + | Ledger App OpenPGP. + | (c) 2024 Ledger SAS. + | + | Licensed under the Apache License, Version 2.0 (the "License"); + | you may not use this file except in compliance with the License. + | You may obtain a copy of the License at + | + | http://www.apache.org/licenses/LICENSE-2.0 + | + | Unless required by applicable law or agreed to in writing, software + | distributed under the License is distributed on an "AS IS" BASIS, + | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + | See the License for the specific language governing permissions and + | limitations under the License. + + +Introduction +============ + +GnuPG application for Ledger devices. + +This application implements "The OpenPGP card" specification revision 3.3. +This specification is available in doc directory at [G10CODE]_. + +The application supports: + +- RSA with key up to 3072 bits +- ECDSA with secp256R1 and secp256K1 +- EDDSA with Ed25519 curve +- ECDH with secp256R1, secp256K1 and curve25519 curves + +This release has known missing parts (see also [GPGADD]_): + +- Seed mode ON/OFF via apdu + + + +How to install GPG Application +============================== + +System Configuration +-------------------- + +You need to install CCID. +For Linux and MAC, the Ledger CCID interface is not supported by default by ``pcscd`` and must be manually added. + +Linux +~~~~~ + +You have to add your devices to ``/etc/libccid_Info.plist`` + +MAC +~~~ + +1. First it is necessary to disable SIP, that forbid editing files in ``/usr/``. +2. You have to add your devices to ``/usr/libexec/SmartCardServices/drivers/ifd-ccid.bundle/Contents/Info.plist`` +3. Enable SIP + +Note: See https://developer.apple.com/library/content/documentation/Security/Conceptual/System_Integrity_Protection_Guide/ConfiguringSystemIntegrityProtection/ConfiguringSystemIntegrityProtection.html + +Windows +~~~~~~~ + +TODO... + +Manual update of CCID +~~~~~~~~~~~~~~~~~~~~~ + +In case the devices ids are not set or not correct, please update the ``Info.plist`` file manually. +Remember there are 3 important nodes in this xml file, and the lines must be coherent between those nodes: + +- ifdVendorID +- ifdProductID +- ifdFriendlyName + +Thus, you must ensure (or add): + +- For Nanos: + + - ifdVendorID: 0x2C97 + - ifdProductID: 0x1009 + - ifdFriendlyName: Ledger Nano S + +- For Nanox: + + - ifdVendorID: 0x2C97 + - ifdProductID: 0x4009 + - ifdFriendlyName: Ledger Nano X + +- For Nanos+: + + - ifdVendorID: 0x2C97 + - ifdProductID: 0x5009 + - ifdFriendlyName: Ledger Nano S Plus + +- for Stax: + + - ifdVendorID: 0x2C97 + - ifdProductID: 0x6009 + - ifdFriendlyName: Ledger Stax + +Notes: + +- The 3 entry nodes must be added for each device. It can be easier to add new ones at the end of each list. +- A file `0001-plist.patch` is provided in this directory. + + +OpenPGP Card application explained +================================== + +Menu Overview +------------- + +The full menu layout is: + + | Select Slot + | \ *Choose:* + | Slot 1 #+ + | Slot 2 + | Slot 3 + | Set Default slot + | Settings + | Key Template + | Choose Key... + | Signature + | Decryption + | Authentication + | Choose Type... + | RSA 2048 + | RSA 3072 + | SECP 256R1 + | ED25519 + | Set Template + | Seed mode *ON/OFF* + | PIN mode + | \ *Choose:* + | On Screen + | Confirm only #+ + | Trust + | Set as Default + | UIF mode + | UIF for Signature *ON/OFF* + | UIF for Decryption *ON/OFF* + | UIF for Authentication *ON/OFF* + | Reset + | About + | \ *OpenPGP Card* + | \ *(c) Ledger SAS* + | \ *Spec 3.3.1* + | \ *App 1.5.4* + +| Emphasis entries are not selectable and just provide information. +| A "**#**" after the entry label means default value on reset. +| A "**+**" after the entry label means current value. + + +Device Info +------------- + +The *Device Info* provides current user and slot information. The format is: + + | ```` + +with: + +- **name** is the one provided to ``gpg --card-edit``. See [GPGSC]_. +- **s** is the 32 bits card serial number. Note that the last three bits always encode the current slot value. +- **n** is the current slot, see below. + + +Select Slot +------------- + +For Nanos, this menu is only available on *XL* version. It is available on all other devices. + +A Slot is a set of 3 key pairs *Signature, Decryption, Authentication* as defined by gnupg specification. + +Usually a GPG card application only manages a single set. Ledger version enhances this and allows you to manage 3 key sets. + +The *Select Slot* menu allows you to select the slot you want to play with, and +to set the default slot when the application start. + +To change the current slot, display the slot you want and select it + +To change the default slot, first select it, and then select the *Set Default* entry. + + +Settings +-------- + +Key Template +~~~~~~~~~~~~ + +A key template is defined by the OpenGPG card application specification. +It describes the key to be generated with the ``generate`` command in ``gpg --card-edit`` + +To set up a new ECC template you have three choices: + +- The ``gpg --edit-card`` interactive setup (recommended) +- The ``gpg-connect-agent`` tool +- The device menu. + +**gpg --card-edit** + +This method suppose you have a recent GnuPG tool and that you correctly configured it. +See the dedicated section for that. + +In a terminal launch: + + | ``$ gpg --card-edit`` + | ``gpg/card>`` *admin* + | ``Admin commands are allowed`` + | + | ``gpg/card>`` *key-attr* + | ``Changing card key attribute for: Signature key`` + | ``Please select what kind of key you want:`` + | ``(1) RSA`` + | ``(2) ECC`` + | ``Your selection?`` *2* + | ``Please select which elliptic curve you want:`` + | ``(1) Curve 25519`` + | ``(4) NIST P-384`` + | ``Your selection?`` *1* + | ``The card will now be re-configured to generate a key of type: ed25519`` + | ``Note: There is no guarantee that the card supports the requested size.`` + | ``If the key generation does not succeed, please check the`` + | ``documentation of your card to see what sizes are allowed.`` + | ``Changing card key attribute for: Encryption key`` + | ``Please select what kind of key you want:`` + | ``(1) RSA`` + | ``(2) ECC`` + | ``Your selection?`` *2* + | ``Please select which elliptic curve you want:`` + | ``(1) Curve 25519`` + | ``(4) NIST P-384`` + | ``Your selection?`` *1* + | ``The card will now be re-configured to generate a key of type: cv25519`` + | ``Changing card key attribute for: Authentication key`` + | ``Please select what kind of key you want:`` + | ``(1) RSA`` + | ``(2) ECC`` + | ``Your selection?`` *2* + | ``Please select which elliptic curve you want:`` + | ``(1) Curve 25519`` + | ``(4) NIST P-384`` + | ``Your selection?`` *1* + | ``The card will now be re-configured to generate a key of type: ed25519`` + +To show the current template use the ``gpg --card-status`` command. + +**gpg-connect-agent** + +This method suppose you have correctly configured your GnuPG tool. +See the dedicated section for that. + +In a terminal launch: + + | ``gpg-connect-agent "SCD SETATTR KEY-ATTR --force 1 " /bye`` + | ``gpg-connect-agent "SCD SETATTR KEY-ATTR --force 2 18 " /bye`` + | ``gpg-connect-agent "SCD SETATTR KEY-ATTR --force 3 " /bye`` + +This 3 commands fix, in that order, the template for Signature, Decryption, Authentication keys. + +Supported curve name are: + +- secp256k1 with tag 19 +- secp256r1 with tag 19 +- nistp256 with tag 19 +- cv25519 (only for key 2) +- ed25519 with tag 22 (only for key 1 and 3) + +To show the current template use the ``gpg --card-status`` command. + +**Device menu** + +First under *Choose Key* menu, select the one of 3 keys for which you want to modify the template. +Then under "Choose Type", select the desired key template. +Finally select "Set Template" entry to set it. + +To show the current template use the ``gpg --card-status`` command. + +Seed mode +~~~~~~~~~ + +When generating new keys on the device, those keys can be generated randomly or in a deterministic way. +The deterministic way is specified in [GPGADD]_. +The current mode is displayed in the first sub menu. +To activate the seed mode select *ON*, to deactivate the seed mode select *OFF*. + +When the application starts, the seed mode is always set to *ON* + +PIN mode +~~~~~~~~ + +Some operations require the user to enter his PIN code. +The default PIN values are: + +- user: ``123456`` +- admin: ``12345678`` + +The PIN entry can be done using 3 methods, named +*On Screen*, *Confirm only*, *Trust*. + +After each mode a *+* or *#* symbol may appear to tell which mode is the current one +and which one is the default when the application starts. +The default mode can be changed by first selecting the desired mode and then +selecting the *Set default* menu. + +Note: *Trust* can not be set as default mode. + +Before you can change the PIN mode, you need to verify the PIN on the client. To do this, +run ``gpg --card-edit``, then ``admin`` and finally ``verify`` on you PC. You will then +be asked to enter the current PIN. After doing so, you can change the PIN mode on your +device. + + +**On Screen** + +The PIN is entered on the device screen. For entering the PIN choose the +next digit by using the left or right button. When the digit you expect is displayed +select it by pressing both buttons at the same time. + +.. image:: pin_entry.png + :align: middle + +Once all digits are selected, validate the PIN by selecting the **'V'** (Validate) +letter + +.. image:: pin_validate.png + :align: middle + +If you want to change the previous digit select the **'C'** (Cancel) letter. + +.. image:: pin_cancel.png + :align: middle + +Finally if you want to abort the PIN entry, select the **'A'** (Abort) letter. + +.. image:: pin_abort.png + :align: middle + +**Confirm only** + +The user is requested, on the device screen, to confirm the PIN validation. +The PIN value is not required, the user just has to push the *REJECT* or *OK* button on the device. + +This is the default mode after application installation. + +.. image:: pin_confirm.png + :align: middle + +**Trust** + +Act as if the PIN is always validated. This is a dangerous mode which should only be +used in a highly secure environment. + +UIF mode +~~~~~~~~ + +By activating UIF mode for either signature, decryption or authentication, a user validation +will be ask by the device each time the related operation is performed. + +To activate or deactivate the UIF, select the operation to protect and press both button. +When activated, a '+' symbol appears after the operation name. + +Reset +~~~~~ + +Selecting the menu will erase all OpenPGP Card Application data and will +reset the application in its '*just installed*' state. + + + +OpenPGP Card application usage +============================== + +GPG +--- + +The OpenGPG Card application need at least version 2.1.19 for full support. +A version prior to 2.1.19 will fail when using ECC. + +You should test with a test key and make a backup of your +keyring before starting, except if your are sure about what you do. + +Configuration +~~~~~~~~~~~~~ + +In order to use a Ledger device with gpg it is needed to explicitly setup +the reader and the delegated PIN support. +Edit the file ``~/.gnupg/scdaemon.conf`` and add the following lines: + + | ``reader-port "Ledger Token"`` + | ``allow-admin`` + | ``enable-pinpad-varlen`` + +Note: ``enable-pinpad-varlen`` option is mandatory, else ``gpg`` could request +the PIN on the *host*, which is not supported by Ledger App. + +You can check ``the reader-port`` value by running the command line ``pcsc_scan``: + + | ``$ pcsc_scan`` + | ``Using reader plug'n play mechanism`` + | ``Scanning present readers...`` + | ``0: Ledger Nano S Plus [Nano S Plus] (0001) 00 00`` + | ``1: Alcor Micro AU9540 01 00`` + | + | ``Thu Jan 11 10:58:25 2024`` + | ``Reader 0: Ledger Nano S Plus [Nano S Plus] (0001) 00 00`` + | ``Event number: 0`` + | ``Card state: Card inserted, Exclusive Mode,`` + | ``ATR: 3B 00`` + | + | ``ATR: 3B 00`` + | ``+ TS = 3B --> Direct Convention`` + | ``+ T0 = 00, Y(1): 0000, K: 0 (historical bytes)`` + | ``Reader 1: Alcor Micro AU9540 01 00`` + | ``Event number: 0`` + | ``Card state: Card removed,`` + +Get/Set basic information +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``gpg --card-status`` command provides default card information. Just after +installation it should look like this: + + | ``$ gpg --card-status`` + | ``Reader ...........: Ledger Nano S Plus [Nano S Plus] (0001) 01 00`` + | ``Application ID ...: D2760001240103002C97AFB114290000`` + | ``Version ..........: 3.3`` + | ``Manufacturer .....: unknown`` + | ``Serial number ....: AFB11429`` + | ``Name of cardholder: [not set]`` + | ``Language prefs ...: [not set]`` + | ``Salutation .......:`` + | ``URL of public key: [not set]`` + | ``Login data .......: [not set]`` + | ``Signature PIN ....: not forced`` + | ``Key attributes ...: rsa2048 rsa2048 rsa2048`` + | ``Max. PIN lengths .: 12 12 12`` + | ``PIN retry counter: 3 0 3`` + | ``Signature counter: 0`` + | ``Signature key ....: [none]`` + | ``Encryption key....: [none]`` + | ``Authentication key: [none]`` + | ``General key info..: [none]`` + +You can set the user information with the ``gpg --card-edit`` subcommands. +For examples: + + | ``$ gpg --card-edit`` + | ``gpg/card>`` *admin* + | ``Admin commands are allowed`` + | + | ``gpg/card>`` *name* + | ``Cardholder's surname:`` *Doe* + | ``Cardholder's given name:`` *John* + | + | ``gpg/card>`` salutation + | ``salutation ((M)ale, (F)emale or space):`` *M* + | + | ``gpg/card>`` *list* + | + | ``Reader ...........: Ledger Nano S Plus [Nano S Plus] (0001) 01 00`` + | ``Application ID ...: D2760001240103002C97AFB114290000`` + | ``Version ..........: 3.3`` + | ``Manufacturer .....: unknown`` + | ``Serial number ....: AFB11429`` + | ``Name of cardholder: John Doe`` + | ``Language prefs ...: [not set]`` + | ``Salutation .......: Mr.`` + | ``URL of public key: [not set]`` + | ``Login data .......: [not set]`` + | ``Signature PIN ....: not forced`` + | ``Key attributes ...: rsa2048 rsa2048 rsa2048`` + | ``Max. PIN lengths .: 12 12 12`` + | ``PIN retry counter: 3 0 3`` + | ``Signature counter: 0`` + | ``Signature key ....: [none]`` + | ``Encryption key....: [none]`` + | ``Authentication key: [none]`` + | ``General key info..: [none]`` + +Notes: + +- Modifying the user information will prompt you to enter ``User PIN``. +- Setting user information is not required for using gpg client. + +Generate new key pair +~~~~~~~~~~~~~~~~~~~~~ + +For generating a new key pair follow those steps: + +- Select the desired slot +- Setup the desired key template for this slot +- Generate the new key set + +**Step 1** + +Starting from main menu: + +- Select *Select slot* menu +- Scroll to desired slot +- Select it +- Optionally set it as default by selecting *Set Default* menu +- Select *Back* to return to main menu. + +**Step 2** + +The default template for each three keys (*signature*, *decryption*, +*authentication*) is ``RSA 2048``. If you want another kind of key +you have to set the template before generating keys. + +WARNING: Changing the current template of a key automatically erases the associated one. + +Starting from main menu: + +- Select *Settings* +- Select *Key template* +- Select *Choose Key...* (a) +- Scroll and select which key you want to set the new template for +- Select *Choose type...* +- Scroll and select among the supported key types and sizes +- Select *Set template* +- Repeat this process from (a) if you want to modify another key template +- Select *Back* to return to main. + +**Step 3** + +Once the template has been set, it's possible to generate new key pairs with ``gpg``. + +WARNING: gpg will generate the 3 key pairs and +will overwrite any key already present in the selected slot. + +Here after is a detailed log of key generation of ECC keys, assuming +the key templates are ``NIST P256``. + +**Edit Card** + + | ``$ gpg --edit-card`` + | ``Reader ...........: Ledger Nano S Plus [Nano S Plus] (0001) 01 00`` + | ``Application ID ...: D2760001240103002C97AFB1142B0000`` + | ``Version ..........: 3.3`` + | ``Manufacturer .....: unknown`` + | ``Serial number ....: AFB1142B`` + | ``Name of cardholder: John Doe`` + | ``Language prefs ...: [not set]`` + | ``Salutation .......: Mr.`` + | ``URL of public key: [not set]`` + | ``Login data .......: [not set]`` + | ``Signature PIN ....: not forced`` + | ``Key attributes ...: nistp256 nistp256 nistp256`` + | ``Max. PIN lengths .: 12 12 12`` + | ``PIN retry counter: 3 0 3`` + | ``Signature counter: 0`` + | ``Signature key ....: [none]`` + | ``Encryption key....: [none]`` + | ``Authentication key: [none]`` + | ``General key info..: [none]`` + +**Switch to admin mode:** + + | ``gpg/card>`` *admin* + | ``Admin commands are allowed`` + +**Request new key generation without backup** + + | ``gpg/card>`` *generate* + | ``Make off-card backup of encryption key? (Y/n)`` *n* + +**Unlock user level ``81``** + + | ``Please unlock the card`` + | + | ``Number: 2C97 AFB1142B`` + | ``Holder: John Doe`` + | + | ``Use the reader's pinpad for input.`` + | ``OK`` + | ``Press any key to continue.`` + +**Set key validity** + + | ``Please specify how long the key should be valid.`` + | ``0 = key does not expire`` + | `` = key expires in n days`` + | ``w = key expires in n weeks`` + | ``m = key expires in n months`` + | ``y = key expires in n years`` + | ``Key is valid for? (0)`` *0* + | ``Key does not expire at all`` + | ``Is this correct? (y/N)`` *y* + +**Set user ID** + + | ``GnuPG needs to construct a user ID to identify your key.`` + | + | ``Real name:`` *John Doe* + | ``Email address:`` *john.doe@foo.com* + | ``Comment:`` + | ``You selected this USER-ID:`` + | ``"John Doe "`` + | + | ``Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?`` *O* + +**Unlock admin level ``83``** + + | ``Please enter the Admin PIN`` + | + | ``Number: 2C97 AFB1142B`` + | ``Holder: John Doe`` + | + | ``Use the reader's pinpad for input.`` + | ``OK`` + | ``Press any key to continue.`` + +**Unlock user level ``81``** + + | ``Please unlock the card`` + | + | ``Number: 2C97 AFB1142B`` + | ``Holder: John Doe`` + | ``Counter: 0`` + | + | ``Use the reader's pinpad for input.`` + | ``OK`` + | ``Press any key to continue.`` + +**Final confirmation** + + | ``Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?`` *O* + | ``gpg: key DF3FA4A33EF00E47 marked as ultimately trusted`` + | ``gpg: revocation certificate stored as 'xxxx/openpgp-revocs.d/89F772243C9A3E583CB59AB5DF3FA4A33EF00E47.rev'`` + | ``public and secret key created and signed.`` + +**Get information after key generation** + + | ``gpg/card>`` *list* + | + | ``Reader ...........: Ledger Nano S Plus [Nano S Plus] (0001) 01 00`` + | ``Application ID ...: D2760001240103002C97AFB1142B0000`` + | ``Version ..........: 3.3`` + | ``Manufacturer .....: unknown`` + | ``Serial number ....: AFB1142B`` + | ``Name of cardholder: John Doe`` + | ``Language prefs ...: [not set]`` + | ``Salutation .......: Mr.`` + | ``URL of public key: [not set]`` + | ``Login data .......: [not set]`` + | ``Signature PIN ....: not forced`` + | ``Key attributes ...: nistp256 nistp256 nistp256`` + | ``Max. PIN lengths .: 12 12 12`` + | ``PIN retry counter: 3 0 3`` + | ``Signature counter: 12`` + | ``Signature key ....: F844 38BB CA87 F9A7 6830 F002 F8A4 A353 3CBF CAA5`` + | ``created ....: 2017-08-22 15:59:36`` + | ``Encryption key....: B1D3 C9F2 C3C5 87CA 36A7 F02E E137 28E9 13B8 77E1`` + | ``created ....: 2017-08-22 15:59:36`` + | ``Authentication key: F87D EF02 9C38 C43D 41F0 6872 2345 A677 CE9D 8223`` + | ``created ....: 2017-08-22 15:59:36`` + | ``General key info..: pub nistp256/F8A4A3533CBFCAA5 2017-08-22 John Doe `` + | ``sec> nistp256/F8A4A3533CBFCAA5 created: 2017-08-22 expires: never`` + | ``card-no: 2C97 AFB1142B`` + | ``ssb> nistp256/2345A677CE9D8223 created: 2017-08-22 expires: never`` + | ``card-no: 2C97 AFB1142B`` + | ``ssb> nistp256/E13728E913B877E1 created: 2017-08-22 expires: never`` + | ``card-no: 2C97 AFB1142B`` + +At this point it's possible to check that the key has been generated on card with the following command: + + | ``$ gpg --list-secret-keys john.doe@foo.com`` + | ``gpg: checking the trustdb`` + | ``gpg: marginals needed: 3 completes needed: 1 trust model: pgp`` + | ``gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u`` + | + | ``sec> nistp256 2017-08-22 [SC]`` + | ``F84438BBCA87F9A76830F002F8A4A3533CBFCAA5`` + | ``Card serial no. = 2C97 AFB1142B`` + | ``uid [ultimate] John Doe `` + | ``ssb> nistp256 2017-08-22 [A]`` + | ``ssb> nistp256 2017-08-22 [E]`` + +Moving an existing key pair +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This section shows how to move an existing key onto the Ledger device. + +The key to transfer here is a RSA 4096 bits key: + + | ``$ gpg --list-secret-keys "RSA 4096"`` + | ``sec rsa4096 2017-04-26 [SC]`` + | ``FB6C6C75FB016635872ED3E49B93CB47F954FB53`` + | ``uid [ultimate] RSA 4096`` + | ``ssb rsa4096 2017-04-26 [E]`` + +In case of transfer it is not necessary to previously set the template. +It will be automatically changed. +When generating a new key, the 3 keys (*signature*, *decryption*, *authentication*) are automatically generated. +When transferring existing ones, it is possible to choose which one will be moved. + +**Edit Key** + + | ``$ gpg --edit-key "RSA 4096"`` + | ``gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.`` + | ``This is free software: you are free to change and redistribute it.`` + | ``There is NO WARRANTY, to the extent permitted by law.`` + | + | ``Secret key is available.`` + | + | ``sec rsa4096/9B93CB47F954FB53`` + | ``created: 2017-04-26 expires: never usage: SC`` + | ``trust: ultimate validity: ultimate`` + | ``ssb rsa4096/49EE12B0F5CBDF26`` + | ``created: 2017-04-26 expires: never usage: E`` + | ``[ultimate] (1). RSA 4096`` + +**Select the key to move, here the encryption one.** + + | ``gpg>`` *key 1* + | + | ``sec rsa4096/9B93CB47F954FB53`` + | ``created: 2017-04-26 expires: never usage: SC`` + | ``trust: ultimate validity: ultimate`` + | ``ssb* rsa4096/49EE12B0F5CBDF26`` + | ``created: 2017-04-26 expires: never usage: E`` + | ``[ultimate] (1). RSA 4096`` + +**Move** + + | ``gpg>`` *keytocard* + | ``Please select where to store the key:`` + | ``(2) Encryption key`` + | ``Your selection?`` *2* + +**Unlock admin level ``83``** + + | ``Please enter the Admin PIN`` + | + | ``Number: 2C97 1D49B409`` + | ``Holder:`` + | + | ``Use the reader's pinpad for input.`` + | ``OK`` + | ``Press any key to continue.`` + +**Unlock admin level ``83`` (maybe twice....)** + + | ``Please enter the Admin PIN`` + | + | ``Number: 2C97 1D49B409`` + | ``Holder:`` + | + | ``Use the reader's pinpad for input.`` + | ``OK`` + | ``Press any key to continue.`` + | + | ``sec rsa4096/9B93CB47F954FB53`` + | ``created: 2017-04-26 expires: never usage: SC`` + | ``trust: ultimate validity: ultimate`` + | ``ssb* rsa4096/49EE12B0F5CBDF26`` + | ``created: 2017-04-26 expires: never usage: E`` + | ``[ultimate] (1). RSA 4096`` + | + | ``gpg>`` *save* + | ``gpg>`` *quit* + +**check** + + | ``$ gpg --edit-key "RSA 4096"`` + | ``gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.`` + | ``This is free software: you are free to change and redistribute it.`` + | ``There is NO WARRANTY, to the extent permitted by law.`` + | + | ``Secret key is available.`` + | + | ``sec rsa4096/9B93CB47F954FB53`` + | ``created: 2017-04-26 expires: never usage: SC`` + | ``trust: ultimate validity: ultimate`` + | ``ssb rsa4096/49EE12B0F5CBDF26`` + | ``created: 2017-04-26 expires: never usage: E`` + | ``card-no: 2C97 7BB895B9`` + | ``[ultimate] (1). RSA 4096`` + +The encryption key is now associated with a card. + +Decrypting and Signing +~~~~~~~~~~~~~~~~~~~~~~ + +Decrypting and Signing will act exactly the same way as if keys were not on the card. +The only difference is ``gpg`` will request the PIN code instead of the passphrase. + + +SSH +--- + +Overview +~~~~~~~~ + +In order to use gpg for SSH authentication, an "authentication" is needed. +There are two solutions for that, either generate one on the device +or add an authentication sub-key to your existing master gpg key. + +Once done, it is necessary to configure ssh to point to the right key and +delegate the authentication to *gpg-ssh-agent* instead of *ssh-agent*. + +Generate new key on device +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The important thing to keep in mind here is there is no way to tell gpg to +only generate the authentication key. So generating this key will also +generate the two other under a new identity and will erase existing keys +on the current slot on the device. + +Nevertheless, if you want to use a different identity for ssh login, you can use +another slot on the device. See `OpenPGP Card application explained` +and `Generate new key pair`. + +Add sub-key +~~~~~~~~~~~ + +**Edit pgp key set** + + | ``$ gpg --expert --edit-key "john.doe@foo.com"`` + | ``gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.`` + | ``This is free software: you are free to change and redistribute it.`` + | ``There is NO WARRANTY, to the extent permitted by law.`` + | + | ``Secret key is available.`` + | + | ``sec rsa2048/831415DA94A9A15C`` + | ``created: 2017-08-25 expires: never usage: SC`` + | ``trust: ultimate validity: ultimate`` + | ``ssb rsa2048/8E95F2999EEC38C4`` + | ``created: 2017-08-25 expires: never usage: E`` + | ``[ultimate] (1). John Doe `` + +**Add sub key** + + | ``gpg>`` *addkey* + | + | ``Please select what kind of key you want:`` + | ``(3) DSA (sign only)`` + | ``(4) RSA (sign only)`` + | ``(5) Elgamal (encrypt only)`` + | ``(6) RSA (encrypt only)`` + | ``(7) DSA (set your own capabilities)`` + | ``(8) RSA (set your own capabilities)`` + | ``(10) ECC (sign only)`` + | ``(11) ECC (set your own capabilities)`` + | ``(12) ECC (encrypt only)`` + | ``(13) Existing key`` + | ``(14) Existing key from card`` + | ``Your selection?`` *8* + +**Toggle sign/encrypt OFF, Toggle authentication ON** + + | ``Possible actions for a RSA key: Sign Encrypt Authenticate`` + | ``Current allowed actions: Sign Encrypt`` + | + | ``(S) Toggle the sign capability`` + | ``(E) Toggle the encrypt capability`` + | ``(A) Toggle the authenticate capability`` + | ``(Q) Finished`` + | + | ``Your selection?`` *S* + | + | ``Possible actions for a RSA key: Sign Encrypt Authenticate`` + | ``Current allowed actions: Encrypt`` + | + | ``(S) Toggle the sign capability`` + | ``(E) Toggle the encrypt capability`` + | ``(A) Toggle the authenticate capability`` + | ``(Q) Finished`` + | + | ``Your selection?`` *E* + | + | ``Possible actions for a RSA key: Sign Encrypt Authenticate`` + | ``Current allowed actions:`` + | + | ``(S) Toggle the sign capability`` + | ``(E) Toggle the encrypt capability`` + | ``(A) Toggle the authenticate capability`` + | ``(Q) Finished`` + | + | ``Your selection?`` *A* + | + | ``Possible actions for a RSA key: Sign Encrypt Authenticate`` + | ``Current allowed actions: Authenticate`` + | + | ``(S) Toggle the sign capability`` + | ``(E) Toggle the encrypt capability`` + | ``(A) Toggle the authenticate capability`` + | ``(Q) Finished`` + | + | ``Your selection?`` *Q* + +**Set key options** + + | ``RSA keys may be between 1024 and 4096 bits long.`` + | ``What keysize do you want? (2048)`` *2048* + | ``Requested keysize is 2048 bits`` + | ``Please specify how long the key should be valid.`` + | ``0 = key does not expire`` + | `` = key expires in n days`` + | ``w = key expires in n weeks`` + | ``m = key expires in n months`` + | ``y = key expires in n years`` + | ``Key is valid for? (0)`` *0* + | ``Key does not expire at all`` + | ``Is this correct? (y/N)`` *y* + | ``Really create? (y/N)`` *y* + | ``We need to generate a lot of random bytes. It is a good idea to perform`` + | ``some other action (type on the keyboard, move the mouse, utilize the`` + | ``disks) during the prime generation; this gives the random number`` + | ``generator a better chance to gain enough entropy.`` + | + | ``sec rsa2048/831415DA94A9A15C`` + | ``created: 2017-08-25 expires: never usage: SC`` + | ``trust: ultimate validity: ultimate`` + | ``ssb rsa2048/8E95F2999EEC38C4`` + | ``created: 2017-08-25 expires: never usage: E`` + | ``ssb rsa2048/C20B90E12F68F035`` + | ``created: 2017-08-28 expires: never usage: A`` + | ``[ultimate] (1). John Doe `` + +**Select the key and move it** + + | ``gpg> key`` *2* + | + | ``sec rsa2048/831415DA94A9A15C`` + | ``created: 2017-08-25 expires: never usage: SC`` + | ``trust: ultimate validity: ultimate`` + | ``ssb rsa2048/8E95F2999EEC38C4`` + | ``created: 2017-08-25 expires: never usage: E`` + | ``ssb* rsa2048/C20B90E12F68F035`` + | ``created: 2017-08-28 expires: never usage: A`` + | ``[ultimate] (1). John Doe `` + | + | ``gpg>`` *keytocard* + | ``Please select where to store the key:`` + | ``(3) Authentication key`` + | ``Your selection?`` *3* + | + | ``sec rsa2048/831415DA94A9A15C`` + | ``created: 2017-08-25 expires: never usage: SC`` + | ``trust: ultimate validity: ultimate`` + | ``ssb rsa2048/8E95F2999EEC38C4`` + | ``created: 2017-08-25 expires: never usage: E`` + | ``ssb* rsa2048/C20B90E12F68F035`` + | ``created: 2017-08-28 expires: never usage: A`` + | ``[ultimate] (1). John Doe `` + | + | ``gpg>`` *save* + +Configure SSH and GPG +~~~~~~~~~~~~~~~~~~~~~ + +First, tell ``gpg-agent`` to enable ``ssh-auth`` feature by adding the following line to your ``.gpg-agent.conf``: + + ``enable-ssh-support`` + +Starting with ``gpg`` is necessary to add some configuration options to make the *pinentry* +work properly. Add the following line to ``~/.bashrc`` file: + + | ``export SSH_AUTH_SOCK=`gpgconf --list-dirs agent-ssh-socket``` + | ``export GPG_TTY=`tty``` + | ``gpgconf --launch gpg-agent`` + +It may be also necessary to setup the loopback pinentry options. + +Add the following line to your ``~/.gnupg/gpg-agent.conf``: + + ``allow-loopback-pinentry`` + +And add the following line to your ``~/.gnupg/gpg.conf``: + + ``pinentry-mode loopback`` + +Then export your authentication public key. First execute the command: + | ``gpg -k --with-subkey-fingerprint --with-keygrip john.doe@foo.com``. + + | ``pub rsa2048 2017-08-25 [SC]`` + | ``7886147C4C2E5CE2A4B1546C831415DA94A9A15C`` + | ``Keygrip = DE2B63C13AB92EBD2D05C1021A9DAA2D40ECB564`` + | ``uid [ultimate] John Doe `` + | ``sub rsa2048 2017-08-25 [E]`` + | ``789E56872A0D9A5AC8AF9C2F8E95F2999EEC38C4`` + | ``Keygrip = 9D7C2EF8D84E3B31371A09DFD9A4B3EF72AB4ACE`` + | ``sub rsa2048 2017-08-28 [A]`` + | ``2D0E4FFFAA448AA2770C7F02C20B90E12F68F035`` + | ``Keygrip = 6D60CB58D9D66EE09804E7FE460E865A91F5E41A`` + +Add the ``keygrip`` of the authentication key, the one identified by ``[A]``, to ``.gnupg/sshcontrol`` file: + + | ``$ echo 6D60CB58D9D66EE09804E7FE460E865A91F5E41A > .gnupg/sshcontrol`` + +Export your authentication key, identifier by its fingerprint, in a SSH compliant format. + + | ``$ gpg --export-ssh-key 2D0E4FFFAA448AA2770C7F02C20B90E12F68F035`` + | ``ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCIARKh0IZTHld+I6oA8nwrgnCUQE8f`` + | ``7X3pmI4ZwryT52fKhpcsQJsd3krodXrM//LiK8+m2ZRMneJ9iGlqqE7SCyZkNBj1GUm9s`` + | ``rK3Q5eoR6nU0s+sq17b/FAtQWHBJTqqaOtyA33hFj5twUtWZ6rokX9cNZrD1ne8kRVHDe`` + | ``3uEBsaY5PR1Tuko/GwywLyZu0SwfEobl/RPjL7P8rUSc7DTHpQMw8fjJFb4BNvIHAlaVC`` + | ``5FwZwkuogygaJdN/44MayHFmOZmzx9CAgYgLpTzen35+CcyhlqCqi+HjNlnHL2DDWd4iR`` + | ``d3Y6pY8LjS3xQkECc3Bhedptp17D+H9AVJt openpgp:0x2F68F035`` + +Finally copy the above export (``ssh-rsa AAAAB...Jt openpgp:0x2F68F035``) into the +``~/.ssh/authorized_keys`` file on your remote server. + +Now, if everything is correctly setup and running, an ``ssh-add -l`` should show your key: + + | ``$ ssh-add -l`` + | ``2048 SHA256:sLCzsoi5GAG2kJkG6hSp8gTLPxSvo/zNtsks2kQ7vTU cardno:2C979421A9E1 (RSA)`` + | ``2048 SHA256:sLCzsoi5GAG2kJkG6hSp8gTLPxSvo/zNtsks2kQ7vTU (none) (RSA)`` + +And you should be able to ssh to your remote server with your gpg key! + + +Backup and Restore +------------------ + +Introduction +~~~~~~~~~~~~ + +The OpenPGP card specification does not provide any mechanism for backuping you key. +Thus if you generate your keys on device and loose it, you definitively loose you private key. + +In order to avoid such extreme panic situation, a backup/restore mechanism is provided. +At any time you can backup a snapshot of your device data, including your private keys. +All public data are retrieve in clear form. The private key are stored +encrypted with a key derived from your seed, i.e. from your 24 BIP words. + +The backup/restore tool is located in ``pytools`` directory. + +See `Tools` later in this document for the tools details and usage. + +Note: The keys backup will work *only* if the SEED Mode is enabled! + + +Restore without backup +~~~~~~~~~~~~~~~~~~~~~~ + +If you have seeded key but do not have done a backup and still have your keyring, there is a +solution to restore at least the key and their related information: serial and fingerprints. +All other information such as name, url, ... shall be set manually with ``gpg --card-edit``. + +**Step 1: Retrieve information** + +Run the command ``gpg --edit-key john.doe@foo.com``. + + | ``$ gpg --edit-key john.doe@foo.com`` + | ``gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.`` + | ``This is free software: you are free to change and redistribute it.`` + | ``There is NO WARRANTY, to the extent permitted by law.`` + | + | ``Secret key is available.`` + | + | ``sec ed25519/8451AAF7D43D1095`` + | ``created: 2018-10-10 expires: never usage: SC`` + | ``card-no: 2C97 FD6C11BE`` + | ``trust: ultimate validity: ultimate`` + | ``ssb ed25519/C5A8FB078520ABBB`` + | ``created: 2018-10-10 expires: never usage: A`` + | ``card-no: 2C97 FD6C11BE`` + | ``ssb cv25519/0953D871FC4B9EA4`` + | ``created: 2018-10-10 expires: never usage: E`` + | ``card-no: 2C97 FD6C11BE`` + | ``[ultimate] (1). John Doe `` + +The *usage* field tells you each key purpose: + +- **S** for signature, +- **C** for Certification (subkey signature), +- **A** for authentication, +- **E** for encryption. + +The *card-no* field provides you with the serial number of the card on which the key are stored. +You should have 3 or less keys with the same serial. These are the keys we want to restore. + +For each key you also have the key template (*rsa2048*, *rsa3072*, *ed2559*, *cv25519*) followed by the +short fingerprint, e.g. ``ed25519/8451AAF7D43D1095`` + +Please note the serial and the 3 key template names: ``FD6C11BE`` , ``ed25519:cv25519:ed25519``. +Take care of the order: ``SC:E:A``. + +To get the full fingerprint of each key, run (yes twice ``--fingerprint``): + + | ``$ gpg --fingerprint --fingerprint John`` + | ``pub ed25519 2018-10-10 [SC]`` + | ``2C68 8345 BDDA 0EDF B24D B4FB 8451 AAF7 D43D 1095`` + | ``uid [ultimate] John Doe `` + | ``sub ed25519 2018-10-10 [A]`` + | ``CEC5 9AE6 A766 14BC 3C6D 37D9 C5A8 FB07 8520 ABBB`` + | ``sub cv25519 2018-10-10 [E]`` + | ``DF15 7BD4 AC3B D1EE 9910 99C8 0953 D871 FC4B 9EA4`` + +Assemble the 3 full fingerprint, corresponding to the one identified previously, +in the the following order ``SC:E:A``: + +``2C688345BDDA0EDFB24DB4FB8451AAF7D43D1095:DF157BD4AC3BD1EE991099C80953D871FC4B9EA4: +CEC59AE6A76614BC3C6D37D9C5A8FB078520ABBB``. + +Note: If you only have one single key to restore you can omit the others. +For example, to only restore the authentication key: ``::CEC59AE6A76614BC3C6D37D9C5A8FB078520ABBB`` + +**Step 2: Restore** + +Plug your device and run the OpenPGP application. + +Finally run the following command: + + | ``python3 -m gpgcard.gpgcli --pinpad --set-template ed255519:cv25519:ed255519 --set-fingerprints`` + | ``'2C688345BDDA0EDFB24DB4FB8451AAF7D43D1095:DF157BD4AC3BD1EE991099C80953D871FC4B9EA4:CEC59AE6A76614BC3C6D37D9C5A8FB078520ABBB'`` + | ``--set-serial 'FD6C11BE' --seed-key`` + +Restore lost Keyring +~~~~~~~~~~~~~~~~~~~~ + +In case the local keyring files are lost, follow the recovery process hereafter. +Usually under ``~/.gnupg/``, the keyring files contain the Public keys and associated metadata. + +**Step 1: Retrieve key metadata** + +Check that your device is connected and recognised, and print out the *keygrips* and *creation timestamps* of your keys: + + | ``$ gpg --card-status --with-keygrip`` + | + | ``Reader ...........: Ledger Nano S Plus [Nano S Plus] (0001) 00 00`` + | ``Application ID ...: D2760001240103032C97E1A67CBF0000`` + | ``Application type .: OpenPGP`` + | ``Version ..........: 3.3`` + | ``Manufacturer .....: unknown`` + | ``Serial number ....: E1A67CBF`` + | ``Name of cardholder: [not set]`` + | ``Language prefs ...: [not set]`` + | ``Salutation .......: `` + | ``URL of public key : [not set]`` + | ``Login data .......: [not set]`` + | ``Signature PIN ....: not forced`` + | ``Key attributes ...: rsa2048 rsa2048 rsa2048`` + | ``Max. PIN lengths .: 12 12 12`` + | ``PIN retry counter : 3 0 3`` + | ``Signature counter : 4`` + | ``Signature key ....: FE93 6FEC 13BE BDAA A0C6 3E72 05DC 472D A6F6 A13B`` + | ``created ....: 2024-01-18 10:08:41`` + | ``keygrip ....: 348411953EBC6DE6416D40A7048F5C5795A956A2`` + | ``Encryption key....: CD29 B086 FE23 3DAD 3D51 B713 7E6F 425E 7A90 EE9E`` + | ``created ....: 2024-01-18 10:08:41`` + | ``keygrip ....: 1066E2EC6FB7F21738C010D62676CA64FDD5001F`` + | ``Authentication key: 218F 67FB 8577 1DF1 60C1 CFE0 4A6F EB8C 0F76 76FD`` + | ``created ....: 2024-01-18 10:08:41`` + | ``keygrip ....: 73921B6FC73851E61AE9A0196003BE9516B916A0`` + | ``General key info..: `` + +**Step 2: Import the Master key** + +First, import your master **Signature key** from the device. + +Because GPG key IDs are based in part on their creation time, +we need to set a fake system time to match the *created* time for the Signature key shown above. + +Convert the creation date format like so by removing punctuation, adding a “T” between the date and time, +and adding an exclamation mark to the end: + + | ``2024-01-18 10:08:41`` becomes ``20240118T100841!`` + +Add that to your GPG arguments like so to start importing the key. +When the menu pops up, pick the *Existing key from card* option. +Then, pick the key which has the **cert,sign** right enabled **(1)**, +and follow through the prompts to create your user ID. + + | ``$ gpg --faked-system-time "20240118T100841!" --full-generate-key`` + | ``gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.`` + | ``This is free software: you are free to change and redistribute it.`` + | ``There is NO WARRANTY, to the extent permitted by law.`` + | + | ``gpg: WARNING: running with faked system time: 2024-01-18 10:08:41`` + | ``Please select what kind of key you want:`` + | ``(1) RSA and RSA (default)`` + | ``(2) DSA and Elgamal`` + | ``(3) DSA (sign only)`` + | ``(4) RSA (sign only)`` + | ``(14) Existing key from card`` + | ``Your selection?`` *14* + | ``Serial number of the card: D2760001240103032C97E1A67CBF0000`` + | ``Available keys:`` + | ``(1) 348411953EBC6DE6416D40A7048F5C5795A956A2 OPENPGP.1 rsa2048 (cert,sign)`` + | ``(2) 1066E2EC6FB7F21738C010D62676CA64FDD5001F OPENPGP.2 rsa2048 (encr)`` + | ``(3) 73921B6FC73851E61AE9A0196003BE9516B916A0 OPENPGP.3 rsa2048 (sign,auth)`` + | ``Your selection?`` *1* + | ``Please specify how long the key should be valid.`` + | ``0 = key does not expire`` + | `` = key expires in n days`` + | ``w = key expires in n weeks`` + | ``m = key expires in n months`` + | ``y = key expires in n years`` + | ``Key is valid for? (0)`` + | ``Key does not expire at all`` + | ``Is this correct? (y/N)`` *y* + | + | ``GnuPG needs to construct a user ID to identify your key.`` + | + | ``Real name:`` *testkey* + | ``Email address:`` + | ``Comment:`` + | ``You selected this USER-ID:`` + | ``"testkey"`` + | + | ``Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?`` O + | ``gpg: xxxx/manual-tests/gnupg/trustdb.gpg: trustdb created`` + | ``gpg: key 05DC472DA6F6A13B marked as ultimately trusted`` + | ``gpg: directory 'xxxx/manual-tests/gnupg/openpgp-revocs.d' created`` + | ``gpg: revocation certificate stored as 'xxxx/manual-tests/gnupg/openpgp-revocs.d/FE936FEC13BEBDAAA0C63E7205DC472DA6F6A13B.rev'`` + | ``public and secret key created and signed.`` + | + | ``Note that this key cannot be used for encryption. You may want to use`` + | ``the command "--edit-key" to generate a subkey for this purpose.`` + | ``pub rsa2048 2024-01-18 [SC]`` + | ``FE936FEC13BEBDAAA0C63E7205DC472DA6F6A13B`` + | ``uid testkey`` + + +**Step 3: Import the Encryption subkey** + +Next, add the **encr** key as subkey of this master key. + +Use the ID of the master key that was printed in that final **pub rsa2048** block to start editing it, +along with the creation dates from ``card-status``. + +Note: Please ensure the creation date is the same, or update the command line accordingly! + + | ``$ gpg --faked-system-time "20240118T100841!" --edit-key FE936FEC13BEBDAAA0C63E7205DC472DA6F6A13B`` + | ``gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.`` + | ``This is free software: you are free to change and redistribute it.`` + | ``There is NO WARRANTY, to the extent permitted by law.`` + | + | ``gpg: WARNING: running with faked system time: 2024-01-18 10:08:41`` + | ``Secret key is available.`` + | + | ``gpg: checking the trustdb`` + | ``gpg: marginals needed: 3 completes needed: 1 trust model: pgp`` + | ``gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u`` + | ``sec rsa2048/05DC472DA6F6A13B`` + | ``created: 2024-01-18 expires: never usage: SC`` + | ``card-no: 2C97 E1A67CBF`` + | ``trust: ultimate validity: ultimate`` + | ``[ultimate] (1). testkey`` + | + | ``gpg>`` *addkey* + | ``Secret parts of primary key are stored on-card.`` + | ``Please select what kind of key you want:`` + | ``(3) DSA (sign only)`` + | ``(4) RSA (sign only)`` + | ``(5) Elgamal (encrypt only)`` + | ``(6) RSA (encrypt only)`` + | ``(14) Existing key from card`` + | ``Your selection?`` *14* + | ``Serial number of the card: D2760001240103032C97E1A67CBF0000`` + | ``Available keys:`` + | ``(1) 348411953EBC6DE6416D40A7048F5C5795A956A2 OPENPGP.1 rsa2048 (cert,sign)`` + | ``(2) 1066E2EC6FB7F21738C010D62676CA64FDD5001F OPENPGP.2 rsa2048 (encr)`` + | ``(3) 73921B6FC73851E61AE9A0196003BE9516B916A0 OPENPGP.3 rsa2048 (sign,auth)`` + | ``Your selection?`` *2* + | ``Please specify how long the key should be valid.`` + | ``0 = key does not expire`` + | `` = key expires in n days`` + | ``w = key expires in n weeks`` + | ``m = key expires in n months`` + | ``y = key expires in n years`` + | ``Key is valid for? (0)`` + | ``Key does not expire at all`` + | ``Is this correct? (y/N)`` *y* + | ``Really create? (y/N)`` *y* + | + | ``sec rsa2048/05DC472DA6F6A13B`` + | ``created: 2024-01-18 expires: never usage: SC`` + | ``card-no: 2C97 E1A67CBF`` + | ``trust: ultimate validity: ultimate`` + | ``ssb rsa2048/7E6F425E7A90EE9E`` + | ``created: 2024-01-18 expires: never usage: E`` + | ``card-no: 2C97 E1A67CBF`` + | ``[ultimate] (1). testkey`` + +**Step 4: Import the Authentication subkey** + +Finally, add the **sign,auth** key as subkey of this master key. + +Use the ID of the master key that was printed in that final **pub rsa2048** block to start editing it, +along with the creation dates from ``card-status``. + +Note: Please ensure the creation date is the same, or update the command line accordingly! + + | ``gpg>`` *addkey* + | ``Secret parts of primary key are stored on-card.`` + | ``Please select what kind of key you want:`` + | ``(3) DSA (sign only)`` + | ``(4) RSA (sign only)`` + | ``(5) Elgamal (encrypt only)`` + | ``(6) RSA (encrypt only)`` + | ``(14) Existing key from card`` + | ``Your selection?`` *14* + | ``Serial number of the card: D2760001240103032C97E1A67CBF0000`` + | ``Available keys:`` + | ``(1) 348411953EBC6DE6416D40A7048F5C5795A956A2 OPENPGP.1 rsa2048 (cert,sign)`` + | ``(2) 1066E2EC6FB7F21738C010D62676CA64FDD5001F OPENPGP.2 rsa2048 (encr)`` + | ``(3) 73921B6FC73851E61AE9A0196003BE9516B916A0 OPENPGP.3 rsa2048 (sign,auth)`` + | ``Your selection?`` *3* + | ``Please specify how long the key should be valid.`` + | ``0 = key does not expire`` + | `` = key expires in n days`` + | ``w = key expires in n weeks`` + | ``m = key expires in n months`` + | ``y = key expires in n years`` + | ``Key is valid for? (0)`` + | ``Key does not expire at all`` + | ``Is this correct? (y/N)`` *y* + | ``Really create? (y/N)`` *y* + | + | ``sec rsa2048/05DC472DA6F6A13B`` + | ``created: 2024-01-18 expires: never usage: SC`` + | ``card-no: 2C97 E1A67CBF`` + | ``trust: ultimate validity: ultimate`` + | ``ssb rsa2048/7E6F425E7A90EE9E`` + | ``created: 2024-01-18 expires: never usage: E`` + | ``card-no: 2C97 E1A67CBF`` + | ``ssb rsa2048/4A6FEB8C0F7676FD`` + | ``created: 2024-01-18 expires: never usage: SA`` + | ``card-no: 2C97 E1A67CBF`` + | ``[ultimate] (1). testkey`` + | + | ``gpg>`` *save* + | ``gpg>`` *quit* + +Now you should be able to see your imported key by running this command: + + | ``$ gpg --list-secret-keys --with-keygrip`` + | ``xxxx/manual-tests/gnupg/pubring.kbx`` + | ``-------------------------------------------------------------------`` + | ``sec> rsa2048 2024-01-18 [SC]`` + | ``FE936FEC13BEBDAAA0C63E7205DC472DA6F6A13B`` + | ``Keygrip = 348411953EBC6DE6416D40A7048F5C5795A956A2`` + | ``Card serial no. = 2C97 E1A67CBF`` + | ``uid [ultimate] testkey`` + | ``ssb> rsa2048 2024-01-18 [E]`` + | ``Keygrip = 1066E2EC6FB7F21738C010D62676CA64FDD5001F`` + | ``ssb> rsa2048 2024-01-18 [SA]`` + | ``Keygrip = 73921B6FC73851E61AE9A0196003BE9516B916A0`` + + + +Tools +===== + +There are 2 tools provided: + +- ``gpgcli.py``: General test tool +- ``backup.py``: Backup and Restore of the configuration + +If you encounter an error when performing the backup/restore, reload your scdaemon with +``gpgconf --reload scdaemon`` + +Test command line tool +---------------------- + +A test tool is provided under the directory ``pytools``. +There may be some dependencies package to install. Thus, don't forget to use the command: + + | ``pip install -r requirements.txt`` + +This tool allows to execute lots of test with the device, like key generation, metadata modification, +or simply get the information on the configuration and keys. + +Its usage is: + + | ``$ ./gpgcli.py --help`` + | ``usage: gpgcli.py [-h] [--info] [--reader READER] [--apdu] [--slot {1,2,3}] [--reset] [--pinpad] --adm-pin PIN --user-pin PIN [--new-user-pin PIN] [--new-adm-pin PIN] [--reset-code RESET_CODE | --reset-pw1 RESET_PW1] [--serial SERIAL]`` + | ``[--salutation {Male,Female}] [--name NAME] [--url URL] [--login LOGIN] [--lang LANG] [--key-type {SIG,DEC,AUT}] [--key-action {Export,Generate,Read}] [--set-fingerprints SIG:DEC:AUT] [--set-templates SIG:DEC:AUT] [--seed-key]`` + | ``[--file FILE]`` + | + | ``Manage OpenPGP App on Ledger device`` + | + | ``options:`` + | ``-h, --help show this help message and exit`` + | ``--info Get and display card information`` + | ``--reader READER PCSC reader name (default is 'Ledger')`` + | ``--apdu Log APDU exchange`` + | ``--slot {1,2,3} Select slot (1 to 3)`` + | ``--reset Reset the application (all data will be erased)`` + | ``--pinpad PIN validation will be delegated to pinpad`` + | ``--adm-pin PIN Admin PIN (if pinpad not used)`` + | ``--user-pin PIN User PIN (if pinpad not used)`` + | ``--new-user-pin PIN Change User PIN`` + | ``--new-adm-pin PIN Change Admin PIN`` + | ``--reset-code RESET_CODE`` + | ``Update 'PW1 Resetting Code'`` + | ``--reset-pw1 RESET_PW1`` + | ``Reset the User PIN`` + | ``--serial SERIAL Update the 'serial' data (4 bytes)`` + | ``--salutation {Male,Female}`` + | ``Update 'salutation' data`` + | ``--name NAME Update 'name' data`` + | ``--url URL Update 'url' data`` + | ``--login LOGIN Update 'login' data`` + | ``--lang LANG Update 'lang' data`` + | ``--key-type {SIG,DEC,AUT}`` + | ``Select key type SIG:DEC:AUT (default is all)`` + | ``--key-action {Export,Generate,Read}`` + | ``Generate key pair or Read public key`` + | ``--set-fingerprints SIG:DEC:AUT`` + | ``Set fingerprints for selected 'key-type'`` + | ``If 'key-type' is not specified, set for all keys (SIG:DEC:AUT)`` + | ``Each fingerprint is 20 hex bytes long`` + | ``--set-templates SIG:DEC:AUT`` + | ``Set template identifier for selected 'key-type'`` + | ``If 'key-type' is not specified, set for all keys (SIG:DEC:AUT)`` + | ``Valid values are rsa2048, rsa3072, nistp256, ed25519, cv25519`` + | ``--seed-key Regenerate all keys, based on seed mode`` + | ``--file FILE Public Key export file (default is 'pubkey')`` + +Sample output to get Card information: + + | ``$ ./gpgcli.py --adm-pin 12345678 --user-pin 123456 --info`` + | ``Connect to card 'Ledger'...`` + | ``Verify PINs...`` + | ``Get card info...`` + | ``=============== Application Identifier ===============`` + | ``# AID : D2760001240103032C97E1A67CBF0000`` + | ``- RID : D276000124`` + | ``- Application : 01`` + | ``- Version : 3.3`` + | ``- Manufacturer : 2C97`` + | ``- Serial : E1A67CBF`` + | ``=============== Historical Bytes ===============`` + | ``- historical bytes : 0031c573c001800000000000059000`` + | ``=============== Max Extended Length ===============`` + | ``- Command : 254`` + | ``- Response : 254`` + | ``=============== PIN Info ===============`` + | ``- PW1 : UTF-8 (12 bytes), Error Counter=3, Validity=Several PSO:CDS`` + | ``- Reset Counter : UTF-8 (12 bytes), Error Counter=0`` + | ``- PW3 : UTF-8 (12 bytes), Error Counter=3`` + | ``=============== Extended Capabilities ===============`` + | ``- Secure Messaging : ✗`` + | ``- Get Challenge : ✓ (Max length: 254)`` + | ``- Key import : ✓`` + | ``- PW status : Changeable`` + | ``- Private DOs : ✓`` + | ``- Algo attributes : Changeable`` + | ``- PSO:DEC AES : ✓`` + | ``- Key Derived Format : ✗`` + | ``- Max Cert len : 2560`` + | ``- Max Special DO : 512`` + | ``- PIN 2 format : ✗`` + | ``- MSE : ✓`` + | ``=============== Hardware Features ===============`` + | ``- Display : ✗`` + | ``- Biometric sensor : ✗`` + | ``- Button/Keypad : ✓`` + | ``- LED : ✗`` + | ``- Loudspeaker : ✗`` + | ``- Microphone : ✗`` + | ``- Touchscreen : ✗`` + | ``- Battery : ✗`` + | ``=============== User Info ===============`` + | ``- Name : `` + | ``- Login : `` + | ``- URL : `` + | ``- Salutation : `` + | ``- Lang : `` + | ``=============== Slots Info ===============`` + | ``- Number of Slots : 3`` + | ``- Default Slot : 1`` + | ``- Selection by APDU : ✓`` + | ``- Selection by screen : ✓`` + | ``- Current : 1`` + | ``=============== Keys Info ===============`` + | ``- CDS counter : 8`` + | ``- RSA Pub Exponent : 0x010001`` + | ``SIG:`` + | ``- UIF : ✗`` + | ``- Fingerprint : fe936fec13bebdaaa0c63e7205dc472da6f6a13b`` + | ``- CA fingerprint : N/A`` + | ``- Creation date : 2024-01-18 10:08:41`` + | ``- Attribute : RSA-2048, Format: standard with modulus (n), Exponent size: 32`` + | ``- Certificate : `` + | ``- Key:`` + | ``* OS Target ID : 0x33100004`` + | ``* API Level : 12`` + | ``* Public exp size : 4`` + | ``* Public exp : 0x010001`` + | ``* Private key size: 1040`` + | ``DEC:`` + | ``- UIF : ✗`` + | ``- Fingerprint : cd29b086fe233dad3d51b7137e6f425e7a90ee9e`` + | ``- CA fingerprint : N/A`` + | ``- Creation date : 2024-01-18 10:08:41`` + | ``- Attribute : RSA-2048, Format: standard with modulus (n), Exponent size: 32`` + | ``- Certificate : `` + | ``- Key:`` + | ``* OS Target ID : 0x33100004`` + | ``* API Level : 12`` + | ``* Public exp size : 4`` + | ``* Public exp : 0x010001`` + | ``* Private key size: 1040`` + | ``AUT:`` + | ``- UIF : ✗`` + | ``- Fingerprint : 218f67fb85771df160c1cfe04a6feb8c0f7676fd`` + | ``- CA fingerprint : N/A`` + | ``- Creation date : 2024-01-18 10:08:41`` + | ``- Attribute : RSA-2048, Format: standard with modulus (n), Exponent size: 32`` + | ``- Certificate : `` + | ``- Key:`` + | ``* OS Target ID : 0x33100004`` + | ``* API Level : 12`` + | ``* Public exp size : 4`` + | ``* Public exp : 0x010001`` + | ``* Private key size: 1040`` + + +Backup tool +----------- + +The tool usage is the following: + + | ``$ ./backup.py --help`` + | ``usage: backup.py [-h] [--reader READER] [--slot {1,2,3}] [--pinpad] --adm-pin PIN --user-pin PIN [--restore] [--file FILE]`` + | + | ``Backup/Restore OpenPGP App configuration`` + | + | ``options:`` + | ``-h, --help show this help message and exit`` + | ``--reader READER PCSC reader name (default is 'Ledger')`` + | ``--slot {1,2,3} Select slot (1 to 3)`` + | ``--pinpad PIN validation will be delegated to pinpad`` + | ``--adm-pin PIN Admin PIN (if pinpad not used)`` + | ``--user-pin PIN User PIN (if pinpad not used)`` + | ``--restore Perform a Restore instead of Backup`` + | ``--file FILE Backup/Restore file (default is 'gpg_backup')`` + | ``--seed-key After Restore, regenerate all keys, based on seed mode`` + | + | ``Keys restore is only possible with SEED mode...`` + +To perform a backup, simply use the tool like this: + + | ``$ ./backup.py --adm-pin 12345678 --user-pin 123456`` + | ``Connect to card 'Ledger'...`` + | ``Configuration saved in file 'gpg_backup'.`` + +To *restore* a backup, simply use the tool like this: + + | ``$ ./backup.py --restore --adm-pin 12345678 --user-pin 123456 --seed-key`` + | ``Connect to card 'Ledger'...`` + | ``Configuration saved in file 'gpg_backup'.`` + +Annexes +======= + +Trouble/FAQ +----------- + +**Q:** It may happens the reader is no more visible with ``gpg`` tool, whereas it can be seen by ``pytools``. + +**R:** In such case (which seems to be linked to the PC configuration), one solution is to re-install the tool packages and libraries: + + | ``sudo apt remove --purge libpcsclite-dev scdaemon pcscd opensc pcsc-tool`` + | ``sudo apt autoremove`` + | ``sudo apt install libpcsclite-dev scdaemon pcscd opensc pcsc-tool`` + +**Q:** gpg-connection agent failed + +**R:** Check that you don't have multiple running agents. After setting-up all SSH stuff, try to fully logout/login + + | + +**Q:** It does not work at all, HELP ME!!! + +**R** Please keep calm and do not cry. +Add the following option to ``~/.gnupg/gpg-agent.conf`` + + | ``debug-level guru`` + | ``log-file /tmp/gpgagent.log`` + +Add the following option to ``~/.gnupg/scdaemon.conf`` + + | ``log-file /tmp/scd.log`` + | ``debug-level guru`` + | ``debug-all`` + +Make a nice issue report under github providing log and and command line you run. + +**WARNING**: This may reveal confidential information such as key values. Do your log with a test key. + + | + +**Q:** I'm having issue when using SSH, there is no pinpad prompt. +(``sign_and_send_pubkey: signing failed: agent refused operation``) + +**R:** You might need to add this command to your ``.bashrc`` or ``.zshrc``: + + | ``gpg-connect-agent updatestartuptty /bye >/dev/null`` + + + | + +**Q:** My mac is not able to see my ``Ledger Token`` + +**R:** For some reason, SC communication on Mac takes some times or mess it up sometimes. + +To troubleshot those issues, you can try to reload the ``scdaemon`` using this command: + + | ``gpgconf --reload scdaemon`` + | ``gpgconf --reload gpg-agent`` + +If not successful, you can try to trigger daemons to restart by sending a **SIGTERM** like so: + + | ``kill -TERM $(pgrep gpg-agent) $(pgrep scdaemon)``. + +Changing USB port might also help sometimes. Do not hesitate. + + | + +**Q:** My mac is **STILL** not able to see my ``Ledger Token`` + +**R:** This might be related to your CCID drivers. +You can manually install a more recent version from this +https://ccid.apdu.fr/files/ and install it this way: + + | ``CCID_VERSION=1.5.4`` + | ``wget https://ccid.apdu.fr/files/ccid-${CCID_VERSION}.tar.bz2`` + | ``tar xzvf ccid-${CCID_VERSION}.tar.bz2`` + | ``cd ccid-${CCID_VERSION}`` + | ``./MacOSX/configure`` + | ``make`` + | ``make install`` + +Installing the driver depends on ``libusb`` which can be installed using the following +``brew install libusb``. It also requires static linking against it, if you use +dynamic linking you will have the following output when using the ``./MacOSX/configure`` step: + + | ``/usr/local/Cellar/libusb/1.0.23/lib/libusb-1.0.0.dylib`` + | ``/usr/local/Cellar/libusb/1.0.23/lib/libusb-1.0.dylib`` + | ``*****************************`` + | ``Dynamic library libusb found in /usr/local/Cellar/libusb/1.0.23/lib`` + | ``*****************************`` + | ``Rename it to force a static link`` + +You can use the following: + + | ``LIBUSB_VERSION=1.0.23`` + | + | ``for f in /usr/local/Cellar/libusb/${LIBUSB_VERSION}/lib/*.dylib; do`` + | ``mv $f $f.fake`` + | ``done`` + | + | ``./MacOSX/configure`` + | + | ``for f in /usr/local/Cellar/libusb/${LIBUSB_VERSION}/lib/*.dylib.fake; do`` + | ``ORIG="$( echo $f | sed 's#.fake##g' )"`` + | ``mv $f ${ORIG}`` + | ``done`` + + +References +---------- + +.. [GPG] *The GNU Privacy Guard*, https://gnupg.org/ +.. [GPGSC] *The GnuPG Smartcard HOWTO*, https://gnupg.org/howtos/card-howto/en/smartcard-howto.html +.. [G10CODE] *The OpenPGP card application*, https://g10code.com/p-card.html +.. [GPGADD] *The OpenPGP card application add-on*, https://github.com/LedgerHQ/app-openpgp/blob/master/doc/developer/gpgcard-addon.rst diff --git a/doc/user/blue-app-openpgp-card.rst b/doc/user/blue-app-openpgp-card.rst deleted file mode 100644 index 7e350c9..0000000 --- a/doc/user/blue-app-openpgp-card.rst +++ /dev/null @@ -1,1447 +0,0 @@ -.. - Copyright 2018 Cedric Mesnil , Ledger SAS - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -.. - ------------------------------------------------------------------------ - LaTex substitution Definition - ------------------------------------------------------------------------ - - - -License -======= - -Author: Cedric Mesnil - -License: - - | Copyright 2017 Cedric Mesnil , Ledger SAS - | - | Licensed under the Apache License, Version 2.0 (the "License"); - | you may not use this file except in compliance with the License. - | You may obtain a copy of the License at - | - | http://www.apache.org/licenses/LICENSE-2.0 - | - | Unless required by applicable law or agreed to in writing, software - | distributed under the License is distributed on an "AS IS" BASIS, - | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - | See the License for the specific language governing permissions and - | limitations under the License. - - - - -Introduction -============ - -GnuPG application for Ledger Blue and Nano S - -This application implements "The OpenPGP card" specification revision 3.1. This specification is available in doc directory and at https://g10code.com/p-card.html . - -The application supports: - - - RSA with key up to 4096 bits - - ECDSA with secp256k1, secp256r1, brainpool 256r1 and brainpool 256t1 curves - - EDDSA with Ed25519 curve - - ECDH with secp256k1, secp256r1, brainpool 256r1, brainpool 256t1 and - curve25519 curves - -This release has known missing parts (see also [GPGADD]) : - - - Ledger Blue support - - Seed mode ON/OFF via apdu - - - -How to install GPG Application -============================== - -Nano S / Blue -------------- - -For both, source and binary installation, use the most recent tag. - -From Binary -~~~~~~~~~~~~~ - -Use the "Ledger Manager" Chrome App. See https://www.ledgerwallet.com/apps/manager for details. - -As the "OpenPGP card" application is not fully compliant with UI and documentation guidelines, the application is in developer section: click on "Show developers items" on the bottom right corner to see it. - - - Launch the Ledger Manager. See `Ledger Manager `_ - and ` `_ - for details about installing and using the manager; - - Connect your Nano S or your Blue, enter your PIN, and stay on the - dashboard; - - Click on *show developer items* on the bottom right corner; - - Click on the green bottom arrow icon near the Ledger *Open PGP* logo; - - Confirm the installation when required on your device by pressing the - right button above the check mark; - - Quit the Ledger Manager - - -The application is ready to use! - - -From source -~~~~~~~~~~~~~ - -Building from sources requires the the Nano S SDK 1.4.2.1 on firmware 1.4.2. See https://github.com/LedgerHQ/nanos-secure-sdk - -Refer to the SDK documentation for the compiling/loading... - -System Configuration --------------------- - -You need to install CCID. -For Linux and MAC, the Ledger NanoS CCID interface is not supported -by default by pcscd and must be manually added for CCID versions earlier than 1.4.28. -The support of the NanoX is expected to be released as part of the upcoming version 1.4.32. - -For Windows.... - -Linux -~~~~~ - -If you are using an old version of CCID, you have to have to add the NanoS to /etc/libccid_Info.plist - - - In ifdVendorID add the entry 0x2C97 - - In ifdProductID add the entry 0x0001 - - In ifdFriendlyName add the entry Ledger Token - -These 3 entries must be added at the end of each list. - -For the NanoX : - - - In ifdVendorID add the entry 0x2C97 - - In ifdProductID add the entry 0x0004 - - In ifdFriendlyName add the entry Ledger Token - -MAC -~~~ - -1. First it is necessary to [disable SIP](https://developer.apple.com/library/mac/documentation/Security/Conceptual/System_Integrity_Protection_Guide/ConfiguringSystemIntegrityProtection/ConfiguringSystemIntegrityProtection.html) That doesn't allow the editing of files in /usr/. - -2. You have to add the Nano S to /usr/libexec/SmartCardServices/drivers/ifd-ccid.bundle/Contents/Info.plist - - - - In ifdVendorID add the entry 0x2C97 - - In ifdProductID add the entry 0x0001 - - In ifdFriendlyName add the entry Ledger Token - -This 3 entries must be added at the end of each list. - -3. [Enable SIP](https://developer.apple.com/library/content/documentation/Security/Conceptual/System_Integrity_Protection_Guide/ConfiguringSystemIntegrityProtection/ConfiguringSystemIntegrityProtection.html) - -Windows -~~~~~~~ - -TODO - - - -Nano S OpenPGP Card application explained -========================================= - -Menu Overview -------------- - -The full menu layout is : - - -| \ *Device Info* -| Select Slot -| \ *Choose:* -| Slot 1 #+ -| Slot 2 -| Slot 3 -| Set Default -| Settings -| Key Template -| Choose Key... -| Signature -| Decryption -| Authentication -| Choose Type... -| RSA 2048 -| RSA 3072 -| RSA 4096 -| NIST P256 -| ED25519 -| Set Template -| Seed mode -| \ ** -| Set on -| Set off -| PIN mode -| UIF mode -| \ *Choose:* -| Host -| On Screen -| Confirm only #+ -| Trust -| Reset -| About -| \ *OpenPGP Card* -| \ *(c) Ledger SAS* -| \ *Spec 3.0* -| \ *App 1.0.1* - -| Emphasis entries are not selectable and just provide information. -| A "**#**" after the entry label means default value on reset. -| A "**+**" after the entry label means current value. - - - -Device Info -------------- - -The *Device Info* provides current user and slot information. The format is: - -| - -with: - - * **name** is the one provided to ``gpg --card-edit``. See [GPGSC]. - * **n** is the current slot, see below. - * **s** is the 32 bits card serial number. Note that the last three bits always -encode the current slot value. - - -Select Slot -------------- - -This menu is only available on ``XL`` version - -A Slot is a set of -three key pairs *Signature, Decryption, Authentication* as defined by gnupg -specification. - -Usually a GPG card application only manages a single set. Ledger version enhances -this and allows you to manage three key sets. - -The *Select Slot* menu allows you to select the slot you want to play with, and -to set the default slot when the application start. - -To change the current slot, display the slot you want and select it - -To change the default slot, first select it, and then select the *Set Default* -entry. - - -Settings --------- - -Key Template -~~~~~~~~~~~~ - -A key template is defined by the OpenGPG card application specification. It -describes the key to be generated with the ``generate`` command in -``gpg --card-edit`` - -To set up a new ECC template you have three choices: the ``gpg --edit-card`` -interactive setup, the ``gpg-connect-agent`` tool and the NanoS menu. - - -**gpg --card-edit** (recommended) - -This method suppose you have a recent GnuPG tool and that you correctly configured it. -See the dedicated section for that. - -In a terminal launch : - - | ``$ gpg --card-edit`` - | ``gpg/card> admin`` - | ``Admin commands are allowed`` - | ```` - | ``gpg/card> key-attr`` - | ``Changing card key attribute for: Signature key`` - | ``Please select what kind of key you want:`` - | ``(1) RSA`` - | ``(2) ECC`` - | ``Your selection? 2`` - | ``Please select which elliptic curve you want:`` - | ``(1) Curve 25519`` - | ``(4) NIST P-384`` - | ``Your selection? 1`` - | ``The card will now be re-configured to generate a key of type: ed25519`` - | ``Note: There is no guarantee that the card supports the requested size.`` - | ``If the key generation does not succeed, please check the`` - | ``documentation of your card to see what sizes are allowed.`` - | ``Changing card key attribute for: Encryption key`` - | ``Please select what kind of key you want:`` - | ``(1) RSA`` - | ``(2) ECC`` - | ``Your selection? 2`` - | ``Please select which elliptic curve you want:`` - | ``(1) Curve 25519`` - | ``(4) NIST P-384`` - | ``Your selection? 1`` - | ``The card will now be re-configured to generate a key of type: cv25519`` - | ``Changing card key attribute for: Authentication key`` - | ``Please select what kind of key you want:`` - | ``(1) RSA`` - | ``(2) ECC`` - | ``Your selection? 2`` - | ``Please select which elliptic curve you want:`` - | ``(1) Curve 25519`` - | ``(4) NIST P-384`` - | ``Your selection? 1`` - | ``The card will now be re-configured to generate a key of type: ed25519`` - -To show the current template use the ``gpg --card-status`` command. - -**gpg-connect-agent** - -This method suppose you have correctly configured your GnuPG tool. -See the dedicated section for that. - -In a terminal launch : - - | ``gpg-connect-agent "SCD SETATTR KEY-ATTR --force 1 " /bye`` - | ``gpg-connect-agent "SCD SETATTR KEY-ATTR --force 2 18 " /bye`` - | ``gpg-connect-agent "SCD SETATTR KEY-ATTR --force 3 " /bye`` - -This 3 commands fix, in that order, the template for Signature, Decryption, Authentication keys. - -Supported curve name are: - -- secp256k1 with tag 19 -- nistp256 with tag 19 -- cv25519 (only for key 2) -- ed25519 with tag 22 (only for key 1 and 3) - - -To show the current template use the ``gpg --card-status`` command. - -**NanoS menu** - -First under *Choose Key* menu, select the one of three keys for which you want to modify -the template. Then under "Choose Type", select the desired key template. -Finally select "Set Template" entry to set it. - -To show the current template use the ``gpg --card-status`` command. - -Seed mode -~~~~~~~~~ - -**WARNING** : SEED MODE IS EXPERIMENTAL - -When generating new keys on NanoS, those keys can be generated randomly -or in a deterministic way. The deterministic way is specified in [GPGADD]. -The current mode is displayed in the first sub menu. To activate the seeded - mode select *ON*, to deactivate the seeded mode select *OFF*. - -When the application starts, the seeded mode is always set to *OFF* - -**WARNING** : SEED MODE IS EXPERIMENTAL - -PIN mode -~~~~~~~~ - -Some operations require the user to enter his PIN code. -The default PIN values are: - - - user: ``123456`` - - admin: ``12345678`` - -The PIN entry can be done using four methods, named -"*Host*", "*On Screen*", "*Confirm only*", "*Trust*". - -After each mode a *+* or *#* symbol may appear to tell which mode is the current one -and which one is the default when the application starts. -The default mode can be changed by first selecting the desired mode and then -selecting the *Set default" menu. Note that *Trust* can not be set as default mode. - -Before you can change the PIN mode, you need to verify the PIN on the client. To do this, -run ``gpg --card-edit``, then ``admin`` and finally ``verify`` on you PC. You will then -be asked to enter the current PIN. After doing so, you can change the PIN mode on your -device. - - -Note that *On Screen*", "*Confirm only*" and "*Trust*" may not work if the -client application does not support it. In that case the "*Host*" should be -automatically used by the client in a transparent way. - -**Host** - -The PIN is entered on the external computer. - - -**On Screen** - -The PIN is entered on the Nano S or Blue screen. For entering the PIN choose the -next digit by using the left or right button. When the digit you expect is displayed -select it by pressing both buttons at the same time - -.. image:: pin_entry.png - :align: middle - - -Once all digits are selected, validate the PIN by selecting the **'V'** (Validate) -letter - -.. image:: pin_validate.png - :align: middle - - -If you want to change the previous digit select the **'C'** (Cancel) letter. - -.. image:: pin_cancel.png - :align: middle - - -Finally if you want to abort the PIN entry, select the **'A'** (Abort) letter. - -.. image:: pin_abort.png - :align: middle - - -**Confirm only** - -The user is requested, on the NanoS or Blue screen, to confirm -the PIN validation. The PIN value is not required, the user just has -to push the *REJECT* or *OK* button on the device. - -This is the default mode after application installation. - -.. image:: pin_confirm.png - :align: middle - - -**Trust** - -Act as if the PIN is always validated. This is a dangerous mode which should only be -used in a highly secure environment. - -UIF mode -~~~~~~~~ - - -By activating UIF mode for either signature, decryption or authentication, a user validation -will be ask by the device each time the related operation is performed. - -To activate or deactivate the UIF, select the operation to protect and press both button. -When activated, a '+' symbol appears after the operation name. - - -Reset -~~~~~ - -Selecting the menu will erase all OpenPGP Card Application data and will -reset the application in its '*just installed*' state. - - - -Nano S OpenPGP Card application usage -===================================== - - -GPG ---- - -The OpenGPG Card application need at least version 2.1.19 for full support. -A version prior to 2.1.19 will fail when using ECC. - -You should test with a test key and make a backup of your -keyring before starting, except if your are sure about what you do. - - -Configuration -~~~~~~~~~~~~~ - -In order to use a Ledger device with gpg it is needed to explicitly setup -the reader and the delegated PIN support. -Edit the file ~/.gnupg/scdaemon.conf and add the following lines: - - | ``reader-port "Ledger Token [Nano S] (0001) 01 00"`` - | ``allow-admin`` - | ``enable-pinpad-varlen`` - - -If you do not set the ``enable-pinpad-varlen`` option, even if Nano S is -configured in *On Screen* mode, gpg will keep requesting the PIN on the host. - -You can check ``the reader-port`` value by running the command line ``pcsc_scan``: - - | ``$ pcsc_scan `` - | ``PC/SC device scanner`` - | ``V 1.4.27 (c) 2001-2011, Ludovic Rousseau `` - | ``Compiled with PC/SC lite version: 1.8.14`` - | ``Using reader plug'n play mechanism`` - | ``Scanning present readers...`` - | ``0: Alcor Micro AU9560 00 00`` - | ``1: Ledger Token [Nano S] (0001) 01 00`` - | ``Reader 0: Alcor Micro AU9560 00 00`` - | ``Card state: Card removed, `` - | ``Reader 1: Ledger Token [Nano S] (0001) 01 00`` - | ``Card state: Card inserted, `` - | ``ATR: 3B 00`` - | ``+ TS = 3B --> Direct Convention`` - | ``+ T0 = 00, Y(1): 0000, K: 0 (historical bytes)`` - - - -Get/Set basic information -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``gpg --card-status`` command provides default card information. Just after -installation it should look like this: - - | ``$ gpg --card-status`` - | ``Reader ...........: Ledger Token [Nano S] (0001) 01 00`` - | ``Application ID ...: D2760001240103002C97AFB114290000`` - | ``Version ..........: 3.0`` - | ``Manufacturer .....: unknown`` - | ``Serial number ....: AFB11429`` - | ``Name of cardholder: [not set]`` - | ``Language prefs ...: [not set]`` - | ``Sex ..............: unspecified`` - | ``URL of public key : [not set]`` - | ``Login data .......: [not set]`` - | ``Signature PIN ....: not forced`` - | ``Key attributes ...: rsa2048 rsa2048 rsa2048`` - | ``Max. PIN lengths .: 12 12 12`` - | ``PIN retry counter : 3 0 3`` - | ``Signature counter : 0`` - | ``Signature key ....: [none]`` - | ``Encryption key....: [none]`` - | ``Authentication key: [none]`` - | ``General key info..: [none]`` - -You can set the user information with the ``gpg --card-edit`` command and -``name``, ``url``, ``login``, ``lang``, ``sex`` subcommands. For example if you -want to set up your name: - - | ``$ gpg --card-edit`` - | ``gpg/card> admin`` - | ``Admin commands are allowed`` - | ```` - | ``gpg/card> name`` - | ``Cardholder's surname: Mesnil`` - | ``Cardholder's given name: Cedric`` - | ```` - | ``gpg/card> sex`` - | ``Sex ((M)ale, (F)emale or space): M`` - | ```` - | ``gpg/card> list`` - | ```` - | ``Reader ...........: Ledger Token [Nano S] (0001) 01 00`` - | ``Application ID ...: D2760001240103002C97AFB114290000`` - | ``Version ..........: 3.0`` - | ``Manufacturer .....: unknown`` - | ``Serial number ....: AFB11429`` - | ``Name of cardholder: Cedric Mesnil`` - | ``Language prefs ...: [not set]`` - | ``Sex ..............: unspecified`` - | ``URL of public key : [not set]`` - | ``Login data .......: [not set]`` - | ``Signature PIN ....: not forced`` - | ``Key attributes ...: rsa2048 rsa2048 rsa2048`` - | ``Max. PIN lengths .: 12 12 12`` - | ``PIN retry counter : 3 0 3`` - | ``Signature counter : 0`` - | ``Signature key ....: [none]`` - | ``Encryption key....: [none]`` - | ``Authentication key: [none]`` - | ``General key info..: [none]`` - - -Notes: - - * Modifying the user information will prompt you to enter ``User PIN``. - * Setting user information is not required for using gpg client. - - -Generate new key pair -~~~~~~~~~~~~~~~~~~~~~ - -For generating a new key pair follow those steps: - - - Select the desired NanoS OpenPGP Card application slot - - Setup the desired key template for this slot - - Generate the new key set - - -**Step 1** - -Starting from main menu: - - - Select *Select slot* menu - - Scroll to desired slot - - Select it - - Optionally set it as default by selecting *Set Default* menu - - Select *Back* to return to main menu. - - -**Step 2** - -The default template for each three keys (*signature*, *decryption*, -*authentication*) is ``RSA 2048``. If you want another kind of key -you have to set the template before generating keys. - -!WARNING!: changing the current template of a key automatically erases -the associated key. - -Starting from main menu: - - - Select *Settings* menu - - Select *Key template* menu - - Select *Choose Key...* menu (a) - - Scroll and select which key you want to set the new template for - - Select *Choose type...* menu - - Scroll and select among the supported key types and sizes - - Select *Set template* - - Repeat this process from (a) if you want to modify another key - template - - Select *Back* to return to main menu. - - -**Step 3** - -Once the template has been set, it's possible to generate new key pairs -with ``gpg``. - -!WARNING!: gpg will generate the three key pairs and -will overwrite any key already present in the selected slot. - -Here after is a detailed log of key generation of ECC keys, assuming -the three key templates are ``NIST P256``. - -**Edit Card** - - - | ``$ gpg2 --edit-card`` - | ``Reader ...........: Ledger Token [Nano S] (0001) 01 00`` - | ``Application ID ...: D2760001240103002C97AFB1142B0000`` - | ``Version ..........: 3.0`` - | ``Manufacturer .....: unknown`` - | ``Serial number ....: AFB1142B`` - | ``Name of cardholder: Cedric Mesnil`` - | ``Language prefs ...: [not set]`` - | ``Sex ..............: male`` - | ``URL of public key : [not set]`` - | ``Login data .......: [not set]`` - | ``Signature PIN ....: not forced`` - | ``Key attributes ...: nistp256 nistp256 nistp256`` - | ``Max. PIN lengths .: 12 12 12`` - | ``PIN retry counter : 3 0 3`` - | ``Signature counter : 0`` - | ``Signature key ....: [none]`` - | ``Encryption key....: [none]`` - | ``Authentication key: [none]`` - | ``General key info..: [none]`` - -**Switch to admin mode:** - - | ``gpg/card>`` *admin* - | ``Admin commands are allowed`` - -**Request new key generation without backup** - - | ``gpg/card>`` *generate* - | ``Make off-card backup of encryption key? (Y/n)`` **n** - -**Unlock user level ``81``** - - | ``Please unlock the card`` - | ```` - | ``Number: 2C97 AFB1142B`` - | ``Holder: Cedric Mesnil`` - | ```` - | ``Use the reader's pinpad for input.`` - | `` OK`` - | ``Press any key to continue. `` - -**Set key validity** - - | ``Please specify how long the key should be valid.`` - | `` 0 = key does not expire`` - | `` = key expires in n days`` - | `` w = key expires in n weeks`` - | `` m = key expires in n months`` - | `` y = key expires in n years`` - | ``Key is valid for? (0)`` *0* - | ``Key does not expire at all`` - | ``Is this correct? (y/N)``*y* - -**Set user ID** - - | ``GnuPG needs to construct a user ID to identify your key.`` - | ```` - | ``Real name: Cedric Mesnil`` - | ``Email address: cedric@ledger.fr`` - | ``Comment: `` - | ``You selected this USER-ID:`` - | `` "Cedric Mesnil "`` - | ```` - | ``Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?`` *O* - | ```` - | ``You selected this USER-ID:`` - | `` "Cedric Mesnil "`` - -**Unlock admin level ``83``** - - | ``Please enter the Admin PIN `` - | ```` - | ``Number: 2C97 AFB1142B`` - | ``Holder: Cedric Mesnil`` - | ```` - | ``Use the reader's pinpad for input.`` - | `` OK`` - | ``Press any key to continue.`` - -**Unlock user level ``82``** - - | ``Please unlock the card`` - | ```` - | ``Number: 2C97 AFB1142B`` - | ``Holder: Cedric Mesnil`` - | ``Counter: 8`` - | ```` - | ``Use the reader's pinpad for input.`` - | `` OK`` - | ``Press any key to continue.`` - -**Final confirmation** - - | ``Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?`` *O* - | ``gpg: key DF3FA4A33EF00E47 marked as ultimately trusted`` - | ``gpg: revocation certificate stored as '/home/gnuk/.gnupg/openpgp-revocs.d/89F772243C9A3E583CB59AB5DF3FA4A33EF00E47.rev'`` - | ``public and secret key created and signed.`` - -**Get information after key generation** - - | ``gpg/card> list`` - | ```` - | ``Reader ...........: Ledger Token [Nano S] (0001) 01 00`` - | ``Application ID ...: D2760001240103002C97AFB1142B0000`` - | ``Version ..........: 3.0`` - | ``Manufacturer .....: unknown`` - | ``Serial number ....: AFB1142B`` - | ``Name of cardholder: Cedric Mesnil`` - | ``Language prefs ...: [not set]`` - | ``Sex ..............: male`` - | ``URL of public key : [not set]`` - | ``Login data .......: [not set]`` - | ``Signature PIN ....: not forced`` - | ``Key attributes ...: nistp256 nistp256 nistp256`` - | ``Max. PIN lengths .: 12 12 12`` - | ``PIN retry counter : 3 0 3`` - | ``Signature counter : 12`` - | ``Signature key ....: F844 38BB CA87 F9A7 6830 F002 F8A4 A353 3CBF CAA5`` - | `` created ....: 2017-08-22 15:59:36`` - | ``Encryption key....: B1D3 C9F2 C3C5 87CA 36A7 F02E E137 28E9 13B8 77E1`` - | `` created ....: 2017-08-22 15:59:36`` - | ``Authentication key: F87D EF02 9C38 C43D 41F0 6872 2345 A677 CE9D 8223`` - | `` created ....: 2017-08-22 15:59:36`` - | ``General key info..: pub nistp256/F8A4A3533CBFCAA5 2017-08-22 cedric mesnilCedric Mesnil `` - | ``sec> nistp256/F8A4A3533CBFCAA5 created: 2017-08-22 expires: never `` - | `` card-no: 2C97 AFB1142B`` - | ``ssb> nistp256/2345A677CE9D8223 created: 2017-08-22 expires: never `` - | `` card-no: 2C97 AFB1142B`` - | ``ssb> nistp256/E13728E913B877E1 created: 2017-08-22 expires: never `` - | `` card-no: 2C97 AFB1142B`` - -**Say goodbye - - | ``gpg/card> quit``** - - - -At this point it's possible to check that the key has been generated on card with the following command: - - | ``$ gpg2 --list-secret-keys cedric@ledger`` - | ``gpg: checking the trustdb`` - | ```` - | ``sec> nistp256 2017-08-22 [SC]`` - | `` F84438BBCA87F9A76830F002F8A4A3533CBFCAA5`` - | `` Card serial no. = 2C97 AFB1142B`` - | ``uid [ultimate] cedric mesnilCedric Mesnil `` - | ``ssb> nistp256 2017-08-22 [A]`` - | ``ssb> nistp256 2017-08-22 [E]`` - - - -Moving existing key pair -~~~~~~~~~~~~~~~~~~~~~~~~ - -This section shows how to move an existing key onto the Ledger device. - -The key to transfer here is a RSA 4096 bits key: - - | ``$ gpg2 --list-secret-keys "RSA 4096"`` - | ``sec rsa4096 2017-04-26 [SC]`` - | `` FB6C6C75FB016635872ED3E49B93CB47F954FB53`` - | ``uid [ultimate] RSA 4096`` - | ``ssb rsa4096 2017-04-26 [E]`` - - -In case of transfer it is not necessary to previously set the template. -It will be automatically changed. -When generating a new key, the three keys (*signature*, *decryption*, -*authentication*)) are automatically generated. -When transferring existing ones, it is possible to choose which one will be -moved. - -**Edit Key** - - | ``$ gpg2 --edit-key "RSA 4096"`` - | ``gpg (GnuPG) 2.1.19; Copyright (C) 2017 Free Software Foundation, Inc.`` - | ``This is free software: you are free to change and redistribute it.`` - | ``There is NO WARRANTY, to the extent permitted by law.`` - | ```` - | ``Secret key is available.`` - | ```` - | ``sec rsa4096/9B93CB47F954FB53`` - | `` created: 2017-04-26 expires: never usage: SC `` - | `` trust: ultimate validity: ultimate`` - | ``ssb rsa4096/49EE12B0F5CBDF26`` - | `` created: 2017-04-26 expires: never usage: E `` - | ``[ultimate] (1). RSA 4096`` - -**Select the key to move, here the *encryption* one.** - - | ``gpg> `` *key 1* - | `` `` - | ``sec rsa4096/9B93CB47F954FB53`` - | `` created: 2017-04-26 expires: never usage: SC `` - | `` trust: ultimate validity: ultimate`` - | ``ssb* rsa4096/49EE12B0F5CBDF26`` - | `` created: 2017-04-26 expires: never usage: E `` - | ``[ultimate] (1). RSA 4096`` - -**Move** - - | ``gpg> `` **keytocard** - | ``Please select where to store the key:`` - | `` (2) Encryption key`` - | ``Your selection?`` *2* - -**Unlock admin level ``83``** - - | ``Please enter the Admin PIN`` - | ```` - | ``Number: 2C97 1D49B409`` - | ``Holder: `` - | ```` - | ``Use the reader's pinpad for input.`` - | `` OK`` - | ``Press any key to continue.`` - -**Unlock admin level ``83`` (maybe twice....)** - - | ``Please enter the Admin PIN`` - | ```` - | ``Number: 2C97 1D49B409`` - | ``Holder: `` - | ```` - | ``Use the reader's pinpad for input.`` - | `` OK`` - | ``Press any key to continue.`` - - | ``sec rsa4096/9B93CB47F954FB53`` - | `` created: 2017-04-26 expires: never usage: SC `` - | `` trust: ultimate validity: ultimate`` - | ``ssb* rsa4096/49EE12B0F5CBDF26`` - | `` created: 2017-04-26 expires: never usage: E `` - | ``[ultimate] (1). RSA 4096`` - -**Say goodbye with saving!** - - | ``gpg> `` *save* - -**check** - - | ``$ gpg2 --edit-keys cedric`` - | ``gpg: error reading key: No public key`` - | ``gnuk@Lulu:~$ /opt/gnupg2.1.19/bin/gpg2 --edit-key "RSA 4096"`` - | ``gpg (GnuPG) 2.1.19; Copyright (C) 2017 Free Software Foundation, Inc.`` - | ``This is free software: you are free to change and redistribute it.`` - | ``There is NO WARRANTY, to the extent permitted by law.`` - | ```` - | ``Secret key is available.`` - | ```` - | ``sec rsa4096/9B93CB47F954FB53`` - | `` created: 2017-04-26 expires: never usage: SC `` - | `` trust: ultimate validity: ultimate`` - | ``ssb rsa4096/49EE12B0F5CBDF26`` - | `` created: 2017-04-26 expires: never usage: E `` - | `` card-no: 2C97 7BB895B9`` - | ``[ultimate] (1). RSA 4096`` - | ```` - | ``gpg> `` *quit* - - -The encryption key is now associated with a card. - -Decrypting and Signing -~~~~~~~~~~~~~~~~~~~~~~ - - -Decrypting and Signing will act exactly the same way as if keys were not on -the card. The only difference is ``gpg`` will request the PIN code instead -of the passphrase. - - -SSH ---- - - -Overview -~~~~~~~~ - -In order to use gpg for SSH authentication, an "authentication" is needed. -There are two solutions for that, either generate one on the device -or add an authentication sub-key to your existing master gpg key. - -Once done, it is necessary to configure ssh to point to the right key and -delegate the authentication to *gpg-ssh-agent* instead of *ssh-agent*. - - -Generate new key on device -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The important thing to keep in mind here is there is no way to tell gpg to -only generate the authentication key. So generating this key will also -generate the two other under a new identity and will erase existing keys -on the current slot on the device. - -Nevertheless, if you want to use a different identity for ssh login, you can use -another slot on the device. See `Nano S OpenPGP Card application explained` -and `Generate new key pair`. - - -Add sub-key -~~~~~~~~~~~ - -**Edit pgp key set** - - | ``$ gpg --expert --edit-key cedric`` - | ``gpg (GnuPG) 2.1.15; Copyright (C) 2016 Free Software Foundation, Inc.`` - | ``This is free software: you are free to change and redistribute it.`` - | ``There is NO WARRANTY, to the extent permitted by law.`` - - | ``Secret key is available.`` - - | ``sec rsa2048/831415DA94A9A15C`` - | `` created: 2017-08-25 expires: never usage: SC `` - | `` trust: ultimate validity: ultimate`` - | ``ssb rsa2048/8E95F2999EEC38C4`` - | `` created: 2017-08-25 expires: never usage: E `` - | ``[ultimate] (1). cedric`` - -**Add sub key** - - | ``gpg> *addkey*`` - - | ``Please select what kind of key you want:`` - | `` (3) DSA (sign only)`` - | `` (4) RSA (sign only)`` - | `` (5) Elgamal (encrypt only)`` - | `` (6) RSA (encrypt only)`` - | `` (7) DSA (set your own capabilities)`` - | `` (8) RSA (set your own capabilities)`` - | `` (10) ECC (sign only)`` - | `` (11) ECC (set your own capabilities)`` - | `` (12) ECC (encrypt only)`` - | `` (13) Existing key`` - | ``Your selection? 8`` - -**Toggle sign/encrypt OFF, Toggle authentication ON** - - | ``Possible actions for a RSA key: Sign Encrypt Authenticate `` - | ``Current allowed actions: Sign Encrypt `` - - | `` (S) Toggle the sign capability`` - | `` (E) Toggle the encrypt capability`` - | `` (A) Toggle the authenticate capability`` - | `` (Q) Finished`` - - | ``Your selection?`` *S* - - | ``Possible actions for a RSA key: Sign Encrypt Authenticate `` - | ``Current allowed actions: Encrypt `` - - | `` (S) Toggle the sign capability`` - | `` (E) Toggle the encrypt capability`` - | `` (A) Toggle the authenticate capability`` - | `` (Q) Finished`` - - | ``Your selection?`` *E* - - | ``Possible actions for a RSA key: Sign Encrypt Authenticate `` - | ``Current allowed actions: `` - - | `` (S) Toggle the sign capability`` - | `` (E) Toggle the encrypt capability`` - | `` (A) Toggle the authenticate capability`` - | `` (Q) Finished`` - - | ``Your selection?`` *A* - - | ``Possible actions for a RSA key: Sign Encrypt Authenticate `` - | ``Current allowed actions: Authenticate `` - - | `` (S) Toggle the sign capability`` - | `` (E) Toggle the encrypt capability`` - | `` (A) Toggle the authenticate capability`` - | `` (Q) Finished`` - - | ``Your selection? Q`` - -**Set key options** - - | ``RSA keys may be between 1024 and 4096 bits long.`` - | ``What keysize do you want? (2048)`` *2048* - | ``Requested keysize is 2048 bits`` - | ``Please specify how long the key should be valid.`` - | `` 0 = key does not expire`` - | `` = key expires in n days`` - | `` w = key expires in n weeks`` - | `` m = key expires in n months`` - | `` y = key expires in n years`` - | ``Key is valid for? (0)`` *0* - | ``Key does not expire at all`` - | ``Is this correct? (y/N)`` *y* - | ``Really create? (y/N)`` *y* - | ``We need to generate a lot of random bytes. It is a good idea to perform`` - | ``some other action (type on the keyboard, move the mouse, utilize the`` - | ``disks) during the prime generation; this gives the random number`` - | ``generator a better chance to gain enough entropy.`` - - | ``sec rsa2048/831415DA94A9A15C`` - | `` created: 2017-08-25 expires: never usage: SC `` - | `` trust: ultimate validity: ultimate`` - | ``ssb rsa2048/8E95F2999EEC38C4`` - | `` created: 2017-08-25 expires: never usage: E `` - | ``ssb rsa2048/C20B90E12F68F035`` - | `` created: 2017-08-28 expires: never usage: A `` - | ``[ultimate] (1). cedric`` - -**Select the key and move it** - - | ``gpg> key`` *2* - | ```` - | ``sec rsa2048/831415DA94A9A15C`` - | `` created: 2017-08-25 expires: never usage: SC `` - | `` trust: ultimate validity: ultimate`` - | ``ssb rsa2048/8E95F2999EEC38C4`` - | `` created: 2017-08-25 expires: never usage: E `` - | ``ssb* rsa2048/C20B90E12F68F035`` - | `` created: 2017-08-28 expires: never usage: A `` - | ``[ultimate] (1). cedric`` - | ```` - | ``gpg>`` *keytocard* - | ``Please select where to store the key:`` - | `` (3) Authentication key`` - | ``Your selection?`` *3* - | `` `` - | ``sec rsa2048/831415DA94A9A15C`` - | `` created: 2017-08-25 expires: never usage: SC `` - | `` trust: ultimate validity: ultimate`` - | ``ssb rsa2048/8E95F2999EEC38C4`` - | `` created: 2017-08-25 expires: never usage: E `` - | ``ssb* rsa2048/C20B90E12F68F035`` - | `` created: 2017-08-28 expires: never usage: A `` - | ``[ultimate] (1). cedric`` - - -**Save and Quit** - - | ``gpg>`` save - | ``$ `` - - - - -Configure SSH and GPG -~~~~~~~~~~~~~~~~~~~~~ - - -First, tell gpg-agent to enable ssh-auth feature by adding the following line -to your .gpg-agent.conf: - - ``enable-ssh-support`` - -Starting with gpg2 it necessary to add some configuration options to make the *pinentry* -work properly. Add the following line to ~/.bashrc file: - - | ``export SSH_AUTH_SOCK=`gpgconf --list-dirs agent-ssh-socket` `` - | ``export GPG_TTY=`tty` `` - | ``gpgconf --launch gpg-agent `` - - -It may be also necessary to setup the loopback pinentry options. - -Add the following line to your ~/.gnupg/gpg-agent.conf: - - ``allow-loopback-pinentry`` - -And add the following line to your ~/.gnupg/gpg.conf: - - ``pinentry-mode loopback`` - - -Then export your authentication public key. First execute the -``gpg -k --with-subkey-fingerprint --with-keygrip cedric`` command. - - - | ``pub rsa2048 2017-08-25 [SC]`` - | `` 7886147C4C2E5CE2A4B1546C831415DA94A9A15C`` - | `` Keygrip = DE2B63C13AB92EBD2D05C1021A9DAA2D40ECB564`` - | ``uid [ultimate] cedric`` - | ``sub rsa2048 2017-08-25 [E]`` - | `` 789E56872A0D9A5AC8AF9C2F8E95F2999EEC38C4`` - | `` Keygrip = 9D7C2EF8D84E3B31371A09DFD9A4B3EF72AB4ACE`` - | ``sub rsa2048 2017-08-28 [A]`` - | `` 2D0E4FFFAA448AA2770C7F02C20B90E12F68F035`` - | `` Keygrip = 6D60CB58D9D66EE09804E7FE460E865A91F5E41A`` - -Add the keygrip of the authentication key, the one identified by ``[A]``, -to .gnupg/sshcontrol file: - - | ``$ echo 6D60CB58D9D66EE09804E7FE460E865A91F5E41A > .gnupg/sshcontrol`` - -Export your authentication key, identifier by its fingerprint, in a SSH compliant format. - - | ``$ gpg --export-ssh-key 2D0E4FFFAA448AA2770C7F02C20B90E12F68F035`` - | ``ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCIARKh0IZTHld+I6oA8nwrgnCUQE8f`` - | ``7X3pmI4ZwryT52fKhpcsQJsd3krodXrM//LiK8+m2ZRMneJ9iGlqqE7SCyZkNBj1GUm9s`` - | ``rK3Q5eoR6nU0s+sq17b/FAtQWHBJTqqaOtyA33hFj5twUtWZ6rokX9cNZrD1ne8kRVHDe`` - | ``3uEBsaY5PR1Tuko/GwywLyZu0SwfEobl/RPjL7P8rUSc7DTHpQMw8fjJFb4BNvIHAlaVC`` - | ``5FwZwkuogygaJdN/44MayHFmOZmzx9CAgYgLpTzen35+CcyhlqCqi+HjNlnHL2DDWd4iR`` - | ``d3Y6pY8LjS3xQkECc3Bhedptp17D+H9AVJt openpgp:0x2F68F035`` - -Finally copy the above export (``ssh-rsa AAAAB...Jt openpgp:0x2F68F035``) into the -~/.ssh/authorized_keys file on your remote server. - - -Now, if everything is correctly setup and running, an ``ssh-add -l`` should show your key: - - | ``$ ssh-add -l`` - | ``2048 SHA256:sLCzsoi5GAG2kJkG6hSp8gTLPxSvo/zNtsks2kQ7vTU cardno:2C979421A9E1 (RSA)`` - | ``2048 SHA256:sLCzsoi5GAG2kJkG6hSp8gTLPxSvo/zNtsks2kQ7vTU (none) (RSA)`` - -And you should be able to ssh to your remote server with your gpg key! - - - -Backup and Restore ------------------- - -Introduction -~~~~~~~~~~~~ - -"The OpenPGP card" specification does not provide any mechanism for backuping you key. -Thus if you generate your keys on device and loose it, you definitively loose you private key. - -In order to avoid such extreme panic situation, a backup/restore mechanism is provided. -At any time you can backup a snapshot of your device data, including your private keys. -All public data are retrieve in clear form. The private key are stored -encrypted with a key derived from your seed, i.e. from your 24 BIP words. - -The backup/restore tool is located in ``pytools`` directory: - - | ``usage: gpgcli.py [-h] [--adm-pin PIN] [--backup] [--backup-keys] [--file FILE]`` - | `` [--pinpad] [--reader READER] [--reset] [--restore]`` - | `` [--set-serial SERIAL] [--set-fp SIG:DEC:AUT] [--seed-key]`` - | `` [--user-pin PIN]`` - | - | ``optional arguments:`` - | `` -h, --help show this help message and exit`` - | `` --adm-pin PIN Administrative PIN, if pinpad not used`` - | `` --backup Perfom a full backup except the key`` - | `` --backup-keys Perfom keys encrypted backup`` - | `` --file FILE basckup/restore file`` - | `` --pinpad PIN validation will be deledated to pinpad`` - | `` --reader READER PCSC reader`` - | `` --reset Reset the application. All data are erased`` - | `` --restore Perfom a full restore except the key`` - | `` --set-serial SERIAL set the four serial bytes`` - | `` --set-fp SIG:DEC:AUT sig:dec:aut fingerprints, 20 bytes each in hexa`` - | `` --seed-key Regenerate all keys, based on seed mode`` - | `` --slot SLOT slot to backup`` - | `` --user-pin PIN User PIN, if pinpad not used`` - - -First you must either provide your pin codes or use the pinpad (onscreen pin). This is -done by giving either ``--adm-pin`` AND ``--user-pin`` or ``--pinpad``. Note that -using ``--xx-pin`` may compromise your pin codes. - -Then you must precise if you want a backup or a restore with ``--backup`` or ``--restore`` - -By default backup is performed without saving keys, assuming you use the seed mode. -If you also want to backup keys you have to pass the ``--backup-keys`` option. -In a general manner it is better to also save your keys with ``--backup-keys`` option. - -Note that backup and restore works on current slot, so you have to perform a backup per slot -even if some data are shared. You can precise the slot/backup to restore with ``--slot`` - -If you encounter an error when performing the backup/restore, reload your scdaemon with -``gpgconf --reload scdaemon`` - - -Backup and Restore example -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -First you must have the path of the `ledger-app-openpgp-card/pytools` in your PYTHONPATH. - -full backup command: - - | ``python3 -m gpgcard.gpgcli --backup --pinpad --backup-keys --file my_bck_file_name.pickle`` - -backup command without private keys: - - | ``python3 -m gpgcard.gpgcli --backup --pinpad --file my_bck_file_name.pickle`` - - -full restore command: - - | ``python3 -m gpgcard.gpgcli --restore --pinpad --file my_bck_file_name.pickle`` - - -full restore command with seed key generation: - - | ``python3 -m gpgcard.gpgcli --restore --pinpad --seed-key --file my_bck_file_name.pickle`` - - -Restore without backup -~~~~~~~~~~~~~~~~~~~~~~ - -If you have seeded key but do not have done a backup and still have your keyring, there is a -solution to restore at least the key and their related information: serial and fingerprints. -All other information such as name, url, ... shall be set manually with ``gpg --card-edit``. - - - -**Step 1: retrieve information** - -Run the command ``gpg --edit-key John``, replace John by your own key id. - - | ``$ gpg --edit-key John`` - | ``gpg: WARNING: unsafe permissions on homedir './test/ring'`` - | ``gpg (GnuPG) 2.2.4; Copyright (C) 2017 Free Software Foundation, Inc.`` - | ``This is free software: you are free to change and redistribute it.`` - | ``There is NO WARRANTY, to the extent permitted by law.`` - | - | ``Secret key is available.`` - | - | ``sec ed25519/8451AAF7D43D1095`` - | `` created: 2018-10-10 expires: never usage: SC `` - | `` card-no: 2C97 FD6C11BE`` - | `` trust: ultimate validity: ultimate`` - | ``ssb ed25519/C5A8FB078520ABBB`` - | `` created: 2018-10-10 expires: never usage: A `` - | `` card-no: 2C97 FD6C11BE`` - | ``ssb cv25519/0953D871FC4B9EA4`` - | `` created: 2018-10-10 expires: never usage: E `` - | `` card-no: 2C97 FD6C11BE`` - | ``[ultimate] (1). John Doe`` - | - | ``gpg> `` - | - - -The ``usage`` field tells you each key purpose: ``SC`` or ``S`` for signature, ``A`` for authentication, ``E`` for encryption. - -The ``card-no``field provides you with the serial number of the card on which the key are stored. -You should have three or less keys with the same serial. These are the keys we want to restore. - -For each key you also have the key template (rsa2048, rsa3072, rsa4096, ed2559, cv25519) followed by the -short fingerprint, e.g. ``ed25519/8451AAF7D43D1095`` - -Note the serial and the three key template names: ``FD6C11BE`` , ``ed25519:cv25519:ed25519``. -Take care of the order: ``SC:E:A``. - -Now type the ``quit`` command. - -To get the full fingerprint of each key, run (yes twice ``--fingerprint``): - -``gpg --fingerprint --fingerprint John``, - - - | ``$ gpg --fingerprint --fingerprint John`` - | ``gpg: WARNING: unsafe permissions on homedir './test/ring'`` - | ``pub ed25519 2018-10-10 [SC]`` - | `` 2C68 8345 BDDA 0EDF B24D B4FB 8451 AAF7 D43D 1095`` - | ``uid [ultimate] John Doe`` - | ``sub ed25519 2018-10-10 [A]`` - | `` CEC5 9AE6 A766 14BC 3C6D 37D9 C5A8 FB07 8520 ABBB`` - | ``sub cv25519 2018-10-10 [E]`` - | `` DF15 7BD4 AC3B D1EE 9910 99C8 0953 D871 FC4B 9EA4`` - -Assemble the three full fingerprint, corresponding to the one identified previously, -in the the following order ``SC:E:A`` : - -``2C688345BDDA0EDFB24DB4FB8451AAF7D43D1095:DF157BD4AC3BD1EE991099C80953D871FC4B9EA4: -CEC59AE6A76614BC3C6D37D9C5A8FB078520ABBB``. - -If you only have one key to restore you can omit the others, for example to only restore the authentication key : - -``::CEC59AE6A76614BC3C6D37D9C5A8FB078520ABBB`` - - -**Step 1: restore** - -Plug you Nano S and run the OpenPGP application. - -Finally run the following command : - - | ``python3 -m gpgcard.gpgcli --pinpad --set-template ed255519:cv25519:ed255519 --set-fingerprints `` - | `` '2C688345BDDA0EDFB24DB4FB8451AAF7D43D1095:DF157BD4AC3BD1EE991099C80953D871FC4B9EA4:CEC59AE6A76614BC3C6D37D9C5A8FB078520ABBB'`` - | `` --set-serial 'FD6C11BE' --seed-key `` - - - -Trouble/FAQ ------------ - -**Q:** pinentry failed with a strange canceled message: - -**R:** there is some problem with gpg2 and pinentry-gnome3. You may update your system -to use pinentry-gtk-2. Under Ubuntu-like OS, use ``update-alternatives --config pinentry`` - - | ```` - -**Q:** gpg-connection agent failed - -**R:** check that you don't have multiple running agents. After setting-up all SSH stuff, try to fully -logout/login - - | ```` - -**Q:** It does not work at all, HELP ME!!! - -**R** Please keep calm and do not cry. -Add the following option to ~/.gnupg/gpg-agent.conf - - | ``debug-level guru`` - | ``log-file /tmp/gpgagent.log`` - -Add the following option to ~/.gnupg/scdaemon.conf - - | ``log-file /tmp/scd.log`` - | ``debug-level guru`` - | ``debug-all`` - -Make a nice issue report under github providing log and and command line you run. - -**!*WARNING*!** : this may reveal confidential information such as key values. Do your log with a test key. - - | ```` - -**Q:** I'm having issue when using SSH, there is no pinpad prompt either on my host nor my Nano -(``sign_and_send_pubkey: signing failed: agent refused operation``) - -**R:** You might need to add this command to your .bashrc/.zshrc : - - | ``gpg-connect-agent updatestartuptty /bye >/dev/null`` - -Be aware that when using **Host** PIN mode, you will have to enter your PIN directly on your -computer and if you use a ncurses-like PIN entry program. In some cases, you will be prompted -to the first shell that uses the above command (at least on Mac). - - | ```` - -**Q:** My mac is not able to see my ``Ledger Token`` - -**R:** For some reason, SC communication on Mac takes some times or mess it up sometimes. - -To troubleshot those issues, you can try to reload the ``scdaemon`` using this command : - - | ``gpgconf --reload scdaemon`` - | ``gpgconf --reload gpg-agent`` - -If not successful, you can try to trigger daemons to restart by sending a **SIGTERM** like so : - - | ``kill -TERM $(pgrep gpg-agent) $(pgrep scdaemon)``. - -Changing USB port might also help sometimes. Do not hesitate. - - | ```` - -**Q:** My mac is **STILL* not able to see my ``Ledger Token`` - -**R:** This might be related to your CCID drivers. Mojave comes with the version ``1.4.27`` -pre-installed. You can manually install a more recent version from this -`website` and install it this way : - - | ``CCID_VERSION=1.4.30`` - | ``wget https://ccid.apdu.fr/files/ccid-${CCID_VERSION}.tar.bz2`` - | ``tar xzvf ccid-${CCID_VERSION}.tar.bz2`` - | ``cd ccid-${CCID_VERSION}`` - | ``./MacOSX/configure`` - | ``make`` - | ``make install`` - -Installing the driver depends on ``libusb`` which can be installed using the following -``brew install libusb``. It also requires static linking against it, if you use -dynamic linking you will have the following output when using the ``./MacOSX/configure`` step : - - | ``/usr/local/Cellar/libusb/1.0.23/lib/libusb-1.0.0.dylib`` - | ``/usr/local/Cellar/libusb/1.0.23/lib/libusb-1.0.dylib`` - | ``*****************************`` - | ``Dynamic library libusb found in /usr/local/Cellar/libusb/1.0.23/lib`` - | ``*****************************`` - | ``Rename it to force a static link`` - -You can use the following : - - | ``LIBUSB_VERSION=1.0.23`` - | ```` - | ``for f in /usr/local/Cellar/libusb/${LIBUSB_VERSION}/lib/*.dylib; do`` - | ``mv $f $f.fake`` - | ``done`` - | ```` - | ``./MacOSX/configure`` - | ```` - | ``for f in /usr/local/Cellar/libusb/${LIBUSB_VERSION}/lib/*.dylib.fake; do`` - | ``ORIG="$( echo $f | sed 's#.fake##g' )"`` - | ``mv $f ${ORIG}`` - | ``done`` - -Once installed, you should see the new driver installed using this command ```` : - - | ``SmartCards:`` - | ```` - | ``Readers:`` - | ```` - | ``Reader Drivers:`` - | ```` - | ``#01: org.debian.alioth.pcsclite.smartcardccid:1.4.27`` - | ``(/usr/libexec/SmartCardServices/drivers/ifd-ccid.bundle)`` - | ``#02: org.debian.alioth.pcsclite.smartcardccid:1.4.30`` - | ``(/usr/local/libexec/SmartCardServices/drivers/ifd-ccid.bundle)`` - | ```` - | ``Tokend Drivers:`` - | ```` - | ``SmartCard Drivers:`` - | ```` - | ``#01: com.apple.CryptoTokenKit.pivtoken:1.0`` - | ``(/System/Library/Frameworks/CryptoTokenKit.framework/PlugIns/pivtoken.appex)`` - | ```` - | ``Available SmartCards (keychain):`` - | ```` - | ``Available SmartCards (token):`` - -Annexes -======= - -References ----------- - -* [GPG] *The GNU Privacy Guard*, https://gnupg.org/ -* [GPGSC] *The GnuPG Smartcard HOWTO*, https://gnupg.org/howtos/card-howto/en/smartcard-howto.html -* [G10CODE] *The OpenPGP card application*, https://g10code.com/p-card.html -* [GPGADD] *The OpenPGP card application add-on*, https://github.com/LedgerHQ/blue-app-openpgp-card/blob/master/doc/gpgcard3.0-addon.rst diff --git a/doc/user/blue-app-openpgp-card.template b/doc/user/blue-app-openpgp-card.template deleted file mode 100644 index 6e2e0f8..0000000 --- a/doc/user/blue-app-openpgp-card.template +++ /dev/null @@ -1,275 +0,0 @@ -\documentclass[$if(fontsize)$$fontsize$,$endif$$if(lang)$$babel-lang$,$endif$$if(papersize)$$papersize$paper,$endif$$for(classoption)$$classoption$$sep$,$endfor$,towside]{report} -$if(fontfamily)$ -\usepackage[$for(fontfamilyoptions)$$fontfamilyoptions$$sep$,$endfor$]{$fontfamily$} -$else$ -\usepackage{lmodern} -$endif$ -$if(linestretch)$ -\usepackage{setspace} -\setstretch{$linestretch$} -$endif$ - -\usepackage{amssymb,amsmath} -\usepackage{ifxetex,ifluatex} -\usepackage{fixltx2e} % provides \textsubscript -\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex - \usepackage[$if(fontenc)$$fontenc$$else$T1$endif$]{fontenc} - \usepackage[utf8]{inputenc} -$if(euro)$ - \usepackage{eurosym} -$endif$ -\else % if luatex or xelatex - \ifxetex - \usepackage{mathspec} - \else - \usepackage{fontspec} - \fi - \defaultfontfeatures{Ligatures=TeX,Scale=MatchLowercase} -$for(fontfamilies)$ - \newfontfamily{$fontfamilies.name$}[$fontfamilies.options$]{$fontfamilies.font$} -$endfor$ -$if(euro)$ - \newcommand{\euro}{€} -$endif$ -$if(mainfont)$ - \setmainfont[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} -$endif$ -$if(sansfont)$ - \setsansfont[$for(sansfontoptions)$$sansfontoptions$$sep$,$endfor$]{$sansfont$} -$endif$ -$if(monofont)$ - \setmonofont[Mapping=tex-ansi$if(monofontoptions)$,$for(monofontoptions)$$monofontoptions$$sep$,$endfor$$endif$]{$monofont$} -$endif$ -$if(mathfont)$ - \setmathfont(Digits,Latin,Greek)[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} -$endif$ -$if(CJKmainfont)$ - \usepackage{xeCJK} - \setCJKmainfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} -$endif$ -\fi -% use upquote if available, for straight quotes in verbatim environments -\IfFileExists{upquote.sty}{\usepackage{upquote}}{} -% use microtype if available -\IfFileExists{microtype.sty}{% -\usepackage{microtype} -\UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts -}{} - -$if(geometry)$ -\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} -$endif$ - -\usepackage[unicode=true]{hyperref} -$if(colorlinks)$ -\PassOptionsToPackage{usenames,dvipsnames}{color} % color is loaded by hyperref -$endif$ -\hypersetup{ -$if(title-meta)$ - pdftitle={$title-meta$}, -$endif$ -$if(author-meta)$ - pdfauthor={$author-meta$}, -$endif$ -$if(keywords)$ - pdfkeywords={$for(keywords)$$keywords$$sep$; $endfor$}, -$endif$ -$if(colorlinks)$ - colorlinks=true, - linkcolor=$if(linkcolor)$$linkcolor$$else$Maroon$endif$, - citecolor=$if(citecolor)$$citecolor$$else$Blue$endif$, - urlcolor=$if(urlcolor)$$urlcolor$$else$Blue$endif$, -$else$ - pdfborder={0 0 0}, -$endif$ - breaklinks=true} -\urlstyle{same} % don't use monospace font for urls -$if(lang)$ -\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex - \usepackage[shorthands=off,$for(babel-otherlangs)$$babel-otherlangs$,$endfor$main=$babel-lang$]{babel} -$if(babel-newcommands)$ - $babel-newcommands$ -$endif$ -\else - \usepackage{polyglossia} - \setmainlanguage[$polyglossia-lang.options$]{$polyglossia-lang.name$} -$for(polyglossia-otherlangs)$ - \setotherlanguage[$polyglossia-otherlangs.options$]{$polyglossia-otherlangs.name$} -$endfor$ -\fi -$endif$ -$if(natbib)$ -\usepackage{natbib} -\bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$} -$endif$ -$if(biblatex)$ -\usepackage[$if(biblio-style)$style=$biblio-style$,$endif$$for(biblatexoptions)$$biblatexoptions$$sep$,$endfor$]{biblatex} -$for(bibliography)$ -\addbibresource{$bibliography$} -$endfor$ -$endif$ -$if(listings)$ -\usepackage{listings} -$endif$ -$if(lhs)$ -\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} -$endif$ -$if(highlighting-macros)$ -$highlighting-macros$ -$endif$ -$if(verbatim-in-note)$ -\usepackage{fancyvrb} -\VerbatimFootnotes % allows verbatim text in footnotes -$endif$ -$if(tables)$ -\usepackage{longtable,booktabs} -$endif$ -$if(graphics)$ -\usepackage{graphicx,grffile,float} -\makeatletter -\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} -\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} -\makeatother -% Scale images if necessary, so that they will not overflow the page -% margins by default, and it is still possible to overwrite the defaults -% using explicit options in \includegraphics[width, height, ...]{} -\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} -$endif$ -$if(links-as-notes)$ -% Make links footnotes instead of hotlinks: -\renewcommand{\href}[2]{#2\footnote{\url{#1}}} -$endif$ -$if(strikeout)$ -\usepackage[normalem]{ulem} -% avoid problems with \sout in headers with hyperref: -\pdfstringdefDisableCommands{\renewcommand{\sout}{}} -$endif$ -$if(indent)$ -$else$ -\IfFileExists{parskip.sty}{% -\usepackage{parskip} -}{% else -\setlength{\parindent}{0pt} -\setlength{\parskip}{6pt plus 2pt minus 1pt} -} -$endif$ -\setlength{\emergencystretch}{3em} % prevent overfull lines -\providecommand{\tightlist}{% - \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} -$if(numbersections)$ -\setcounter{secnumdepth}{$if(secnumdepth)$$secnumdepth$$else$5$endif$} -$else$ -\setcounter{secnumdepth}{0} -$endif$ -$if(subparagraph)$ -$else$ -% Redefines (sub)paragraphs to behave more like sections -\ifx\paragraph\undefined\else -\let\oldparagraph\paragraph -\renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}} -\fi -\ifx\subparagraph\undefined\else -\let\oldsubparagraph\subparagraph -\renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}} -\fi -$endif$ -$if(dir)$ -\ifxetex - % load bidi as late as possible as it modifies e.g. graphicx - $if(latex-dir-rtl)$ - \usepackage[RTLdocument]{bidi} - $else$ - \usepackage{bidi} - $endif$ -\fi -\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex - \TeXXeTstate=1 - \newcommand{\RL}[1]{\beginR #1\endR} - \newcommand{\LR}[1]{\beginL #1\endL} - \newenvironment{RTL}{\beginR}{\endR} - \newenvironment{LTR}{\beginL}{\endL} -\fi -$endif$ -$for(header-includes)$ -$header-includes$ -$endfor$ - - -\title{OpenPGP Card Application} -\author{Cedric Mesnil cedric@ledger.fr} -\date{$date$} - - -\begin{document} - -\begin{titlepage} - \centering -% \includegraphics[width=0.15\textwidth]{example-image-1x1}\par\vspace{1cm} - {\scshape\LARGE OpenPGP Card Application \par} - {\scshape \LARGE User Guide \par} - \vspace{1cm} - -% {\scshape\Large Ledger SAS \par} - \vspace{1cm} - \begin{figure}[h] - \includegraphics{../common/LogoLedgerV.png} - \centering - \end{figure} - {\Large\itshape Cédric Mesnil (cedric@ledger.fr)\par} - \vfill - -% Bottom of the page - {\large \today\par} -\end{titlepage} - - - -$if(abstract)$ -\begin{abstract} -$abstract$ -\end{abstract} -$endif$ - -$for(include-before)$ -$include-before$ - -$endfor$ -$if(toc)$ -{ -$if(colorlinks)$ -\hypersetup{linkcolor=$if(toccolor)$$toccolor$$else$black$endif$} -$endif$ -\setcounter{tocdepth}{$toc-depth$} -\tableofcontents -} -$endif$ -$if(lot)$ -\listoftables -$endif$ -$if(lof)$ -\listoffigures -$endif$ -$body$ - -$if(natbib)$ -$if(bibliography)$ -$if(biblio-title)$ -$if(book-class)$ -\renewcommand\bibname{$biblio-title$} -$else$ -\renewcommand\refname{$biblio-title$} -$endif$ -$endif$ -\bibliography{$for(bibliography)$$bibliography$$sep$,$endfor$} - -$endif$ -$endif$ -$if(biblatex)$ -\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ - -$endif$ -$for(include-after)$ -$include-after$ - -$endfor$ -\end{document} diff --git a/doc/user/generate.sh b/doc/user/generate.sh deleted file mode 100755 index a511592..0000000 --- a/doc/user/generate.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -NAME=blue-app-openpgp-card - -rm -f ${NAME}.pdf - -OUTPUT_FORMAT=rst+smart - -pandoc -s --template=${NAME}.template -f ${OUTPUT_FORMAT} -V geometry:a4paper -V geometry:margin=1in -V fontsize=10pt -t latex --toc -N -o ${NAME}.pdf ${NAME}.rst diff --git a/doc/user/template.latex b/doc/user/template.latex new file mode 100644 index 0000000..c072c7a --- /dev/null +++ b/doc/user/template.latex @@ -0,0 +1,520 @@ +% Options for packages loaded elsewhere +\PassOptionsToPackage{unicode$for(hyperrefoptions)$,$hyperrefoptions$$endfor$}{hyperref} +\PassOptionsToPackage{hyphens}{url} +$if(colorlinks)$ +\PassOptionsToPackage{dvipsnames,svgnames*,x11names*}{xcolor} +$endif$ +$if(dir)$ +$if(latex-dir-rtl)$ +\PassOptionsToPackage{RTLdocument}{bidi} +$endif$ +$endif$ +$if(CJKmainfont)$ +\PassOptionsToPackage{space}{xeCJK} +$endif$ +% +\documentclass[ +$if(fontsize)$ + $fontsize$, +$endif$ +$if(lang)$ + $babel-lang$, +$endif$ +$if(papersize)$ + $papersize$paper, +$endif$ +$if(beamer)$ + ignorenonframetext, +$if(handout)$ + handout, +$endif$ +$if(aspectratio)$ + aspectratio=$aspectratio$, +$endif$ +$endif$ +$for(classoption)$ + $classoption$$sep$, +$endfor$ +]{$documentclass$} +$if(beamer)$ +$if(background-image)$ +\usebackgroundtemplate{% + \includegraphics[width=\paperwidth]{$background-image$}% +} +$endif$ +\usepackage{pgfpages} +\setbeamertemplate{caption}[numbered] +\setbeamertemplate{caption label separator}{: } +\setbeamercolor{caption name}{fg=normal text.fg} +\beamertemplatenavigationsymbols$if(navigation)$$navigation$$else$empty$endif$ +$for(beameroption)$ +\setbeameroption{$beameroption$} +$endfor$ +% Prevent slide breaks in the middle of a paragraph +\widowpenalties 1 10000 +\raggedbottom +$if(section-titles)$ +\setbeamertemplate{part page}{ + \centering + \begin{beamercolorbox}[sep=16pt,center]{part title} + \usebeamerfont{part title}\insertpart\par + \end{beamercolorbox} +} +\setbeamertemplate{section page}{ + \centering + \begin{beamercolorbox}[sep=12pt,center]{part title} + \usebeamerfont{section title}\insertsection\par + \end{beamercolorbox} +} +\setbeamertemplate{subsection page}{ + \centering + \begin{beamercolorbox}[sep=8pt,center]{part title} + \usebeamerfont{subsection title}\insertsubsection\par + \end{beamercolorbox} +} +\AtBeginPart{ + \frame{\partpage} +} +\AtBeginSection{ + \ifbibliography + \else + \frame{\sectionpage} + \fi +} +\AtBeginSubsection{ + \frame{\subsectionpage} +} +$endif$ +$endif$ +$if(beamerarticle)$ +\usepackage{beamerarticle} % needs to be loaded first +$endif$ +$if(fontfamily)$ +\usepackage[$for(fontfamilyoptions)$$fontfamilyoptions$$sep$,$endfor$]{$fontfamily$} +$else$ +\usepackage{lmodern} +$endif$ +$if(linestretch)$ +\usepackage{setspace} +$endif$ +\usepackage{amssymb,amsmath} +\usepackage{ifxetex,ifluatex} +\usepackage{pifont} +\usepackage{newunicodechar} +\newunicodechar{✓}{\ding{51}} +\newunicodechar{✗}{\ding{55}} +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + \usepackage[$if(fontenc)$$fontenc$$else$T1$endif$]{fontenc} + \usepackage[utf8]{inputenc} + \usepackage{textcomp} % provide euro and other symbols +\else % if luatex or xetex +$if(mathspec)$ + \ifxetex + \usepackage{mathspec} + \else + \usepackage{unicode-math} + \fi +$else$ + \usepackage{unicode-math} +$endif$ + \defaultfontfeatures{Scale=MatchLowercase} + \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1} +$if(mainfont)$ + \setmainfont[$for(mainfontoptions)$$mainfontoptions$$sep$,$endfor$]{$mainfont$} +$endif$ +$if(sansfont)$ + \setsansfont[$for(sansfontoptions)$$sansfontoptions$$sep$,$endfor$]{$sansfont$} +$endif$ +$if(monofont)$ + \setmonofont[$for(monofontoptions)$$monofontoptions$$sep$,$endfor$]{$monofont$} +$endif$ +$for(fontfamilies)$ + \newfontfamily{$fontfamilies.name$}[$for(fontfamilies.options)$$fontfamilies.options$$sep$,$endfor$]{$fontfamilies.font$} +$endfor$ +$if(mathfont)$ +$if(mathspec)$ + \ifxetex + \setmathfont(Digits,Latin,Greek)[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \else + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} + \fi +$else$ + \setmathfont[$for(mathfontoptions)$$mathfontoptions$$sep$,$endfor$]{$mathfont$} +$endif$ +$endif$ +$if(CJKmainfont)$ + \ifxetex + \usepackage{xeCJK} + \setCJKmainfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + \fi +$endif$ +$if(luatexjapresetoptions)$ + \ifluatex + \usepackage[$for(luatexjapresetoptions)$$luatexjapresetoptions$$sep$,$endfor$]{luatexja-preset} + \fi +$endif$ +$if(CJKmainfont)$ + \ifluatex + \usepackage[$for(luatexjafontspecoptions)$$luatexjafontspecoptions$$sep$,$endfor$]{luatexja-fontspec} + \setmainjfont[$for(CJKoptions)$$CJKoptions$$sep$,$endfor$]{$CJKmainfont$} + \fi +$endif$ +\fi +$if(beamer)$ +$if(theme)$ +\usetheme[$for(themeoptions)$$themeoptions$$sep$,$endfor$]{$theme$} +$endif$ +$if(colortheme)$ +\usecolortheme{$colortheme$} +$endif$ +$if(fonttheme)$ +\usefonttheme{$fonttheme$} +$endif$ +$if(mainfont)$ +\usefonttheme{serif} % use mainfont rather than sansfont for slide text +$endif$ +$if(innertheme)$ +\useinnertheme{$innertheme$} +$endif$ +$if(outertheme)$ +\useoutertheme{$outertheme$} +$endif$ +$endif$ +% Use upquote if available, for straight quotes in verbatim environments +\IfFileExists{upquote.sty}{\usepackage{upquote}}{} +\IfFileExists{microtype.sty}{% use microtype if available + \usepackage[$for(microtypeoptions)$$microtypeoptions$$sep$,$endfor$]{microtype} + \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts +}{} +$if(indent)$ +$else$ +\makeatletter +\@ifundefined{KOMAClassName}{% if non-KOMA class + \IfFileExists{parskip.sty}{% + \usepackage{parskip} + }{% else + \setlength{\parindent}{0pt} + \setlength{\parskip}{6pt plus 2pt minus 1pt}} +}{% if KOMA class + \KOMAoptions{parskip=half}} +\makeatother +$endif$ +$if(verbatim-in-note)$ +\usepackage{fancyvrb} +$endif$ +\usepackage{xcolor} +\IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available +\IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}} +\hypersetup{ +$if(title-meta)$ + pdftitle={$title-meta$}, +$endif$ +$if(author-meta)$ + pdfauthor={$author-meta$}, +$endif$ +$if(lang)$ + pdflang={$lang$}, +$endif$ +$if(subject)$ + pdfsubject={$subject$}, +$endif$ +$if(keywords)$ + pdfkeywords={$for(keywords)$$keywords$$sep$, $endfor$}, +$endif$ +$if(colorlinks)$ + colorlinks=true, + linkcolor=$if(linkcolor)$$linkcolor$$else$Maroon$endif$, + filecolor=$if(filecolor)$$filecolor$$else$Maroon$endif$, + citecolor=$if(citecolor)$$citecolor$$else$Blue$endif$, + urlcolor=$if(urlcolor)$$urlcolor$$else$Blue$endif$, +$else$ + hidelinks, +$endif$ + pdfcreator={LaTeX via pandoc}} +\urlstyle{same} % disable monospaced font for URLs +$if(verbatim-in-note)$ +\VerbatimFootnotes % allow verbatim text in footnotes +$endif$ +$if(geometry)$ +$if(beamer)$ +\geometry{$for(geometry)$$geometry$$sep$,$endfor$} +$else$ +\usepackage[$for(geometry)$$geometry$$sep$,$endfor$]{geometry} +$endif$ +$endif$ +$if(beamer)$ +\newif\ifbibliography +$endif$ +$if(listings)$ +\usepackage{listings} +\newcommand{\passthrough}[1]{#1} +\lstset{defaultdialect=[5.3]Lua} +\lstset{defaultdialect=[x86masm]Assembler} +$endif$ +$if(lhs)$ +\lstnewenvironment{code}{\lstset{language=Haskell,basicstyle=\small\ttfamily}}{} +$endif$ +$if(highlighting-macros)$ +$highlighting-macros$ +$endif$ +$if(tables)$ +\usepackage{longtable,booktabs} +$if(beamer)$ +\usepackage{caption} +% Make caption package work with longtable +\makeatletter +\def\fnum@table{\tablename~\thetable} +\makeatother +$else$ +% Correct order of tables after \paragraph or \subparagraph +\usepackage{etoolbox} +\makeatletter +\patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{} +\makeatother +% Allow footnotes in longtable head/foot +\IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}} +\makesavenoteenv{longtable} +$endif$ +$endif$ +$if(graphics)$ +\usepackage{graphicx} +\makeatletter +\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} +\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} +\makeatother +% Scale images if necessary, so that they will not overflow the page +% margins by default, and it is still possible to overwrite the defaults +% using explicit options in \includegraphics[width, height, ...]{} +\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} +% Set default figure placement to htbp +\makeatletter +\def\fps@figure{htbp} +\makeatother +$endif$ +$if(links-as-notes)$ +% Make links footnotes instead of hotlinks: +\DeclareRobustCommand{\href}[2]{#2\footnote{\url{#1}}} +$endif$ +$if(strikeout)$ +\usepackage[normalem]{ulem} +% Avoid problems with \sout in headers with hyperref +\pdfstringdefDisableCommands{\renewcommand{\sout}{}} +$endif$ +\setlength{\emergencystretch}{3em} % prevent overfull lines +\providecommand{\tightlist}{% + \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} +$if(numbersections)$ +\setcounter{secnumdepth}{$if(secnumdepth)$$secnumdepth$$else$5$endif$} +$else$ +\setcounter{secnumdepth}{-\maxdimen} % remove section numbering +$endif$ +$if(beamer)$ +$else$ +$if(block-headings)$ +% Make \paragraph and \subparagraph free-standing +\ifx\paragraph\undefined\else + \let\oldparagraph\paragraph + \renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}} +\fi +\ifx\subparagraph\undefined\else + \let\oldsubparagraph\subparagraph + \renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}} +\fi +$endif$ +$endif$ +$if(pagestyle)$ +\pagestyle{$pagestyle$} +$endif$ +$for(header-includes)$ +$header-includes$ +$endfor$ +$if(lang)$ +\ifxetex + % Load polyglossia as late as possible: uses bidi with RTL languages (e.g. Hebrew, Arabic) + \usepackage{polyglossia} + \setmainlanguage[$polyglossia-lang.options$]{$polyglossia-lang.name$} +$for(polyglossia-otherlangs)$ + \setotherlanguage[$polyglossia-otherlangs.options$]{$polyglossia-otherlangs.name$} +$endfor$ +\else + \usepackage[shorthands=off,$for(babel-otherlangs)$$babel-otherlangs$,$endfor$main=$babel-lang$]{babel} +$if(babel-newcommands)$ + $babel-newcommands$ +$endif$ +\fi +$endif$ +$if(dir)$ +\ifxetex + % Load bidi as late as possible as it modifies e.g. graphicx + \usepackage{bidi} +\fi +\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex + \TeXXeTstate=1 + \newcommand{\RL}[1]{\beginR #1\endR} + \newcommand{\LR}[1]{\beginL #1\endL} + \newenvironment{RTL}{\beginR}{\endR} + \newenvironment{LTR}{\beginL}{\endL} +\fi +$endif$ +$if(natbib)$ +\usepackage[$natbiboptions$]{natbib} +\bibliographystyle{$if(biblio-style)$$biblio-style$$else$plainnat$endif$} +$endif$ +$if(biblatex)$ +\usepackage[$if(biblio-style)$style=$biblio-style$,$endif$$for(biblatexoptions)$$biblatexoptions$$sep$,$endfor$]{biblatex} +$for(bibliography)$ +\addbibresource{$bibliography$} +$endfor$ +$endif$ +$if(csl-refs)$ +\newlength{\cslhangindent} +\setlength{\cslhangindent}{1.5em} +\newenvironment{cslreferences}% + {$if(csl-hanging-indent)$\setlength{\parindent}{0pt}% + \everypar{\setlength{\hangindent}{\cslhangindent}}\ignorespaces$endif$}% + {\par} +$endif$ + +$if(title)$ +\title{$title$$if(thanks)$\thanks{$thanks$}$endif$} +$endif$ +$if(subtitle)$ +$if(beamer)$ +$else$ +\usepackage{etoolbox} +\makeatletter +\providecommand{\subtitle}[1]{% add subtitle to \maketitle + \apptocmd{\@title}{\par {\large #1 \par}}{}{} +} +\makeatother +$endif$ +\subtitle{$subtitle$} +$endif$ +\author{$for(author)$$author$$sep$ \and $endfor$} +\date{$date$} +$if(beamer)$ +$if(institute)$ +\institute{$for(institute)$$institute$$sep$ \and $endfor$} +$endif$ +$if(titlegraphic)$ +\titlegraphic{\includegraphics{$titlegraphic$}} +$endif$ +$if(logo)$ +\logo{\includegraphics{$logo$}} +$endif$ +$endif$ + +\begin{document} + +\begin{titlepage} + \centering + {\scshape\Huge OpenPGP Card Application \par} + {\scshape \huge User Guide \par} + \vspace{1cm} + + {\scshape\LARGE Ledger SAS \par} + \vspace{2cm} + + \includegraphics{../LogoLedger.png} + + \vspace{1cm} + {\Large\itshape\url {https://github.com/LedgerHQ/app-openpgp}\par} + + \vfill + {\large \today\par} +\end{titlepage} + +$if(has-frontmatter)$ +\frontmatter +$endif$ +$if(title)$ +$if(beamer)$ +\frame{\titlepage} +$else$ +\maketitle +$endif$ +$if(abstract)$ +\begin{abstract} +$abstract$ +\end{abstract} +$endif$ +$endif$ + +$for(include-before)$ +$include-before$ + +$endfor$ +$if(toc)$ +$if(toc-title)$ +\renewcommand*\contentsname{$toc-title$} +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks] +$if(toc-title)$ + \frametitle{$toc-title$} +$endif$ + \tableofcontents[hideallsubsections] +\end{frame} +$else$ +{ +$if(colorlinks)$ +\hypersetup{linkcolor=$if(toccolor)$$toccolor$$else$$endif$} +$endif$ +\setcounter{tocdepth}{$toc-depth$} +\tableofcontents +} +$endif$ +$endif$ +$if(lot)$ +\listoftables +$endif$ +$if(lof)$ +\listoffigures +$endif$ +$if(linestretch)$ +\setstretch{$linestretch$} +$endif$ +$if(has-frontmatter)$ +\mainmatter +$endif$ +$body$ + +$if(has-frontmatter)$ +\backmatter +$endif$ +$if(natbib)$ +$if(bibliography)$ +$if(biblio-title)$ +$if(has-chapters)$ +\renewcommand\bibname{$biblio-title$} +$else$ +\renewcommand\refname{$biblio-title$} +$endif$ +$endif$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue +$endif$ + \bibliography{$for(bibliography)$$bibliography$$sep$,$endfor$} +$if(beamer)$ +\end{frame} +$endif$ + +$endif$ +$endif$ +$if(biblatex)$ +$if(beamer)$ +\begin{frame}[allowframebreaks]{$biblio-title$} + \bibliographytrue + \printbibliography[heading=none] +\end{frame} +$else$ +\printbibliography$if(biblio-title)$[title=$biblio-title$]$endif$ +$endif$ + +$endif$ +$for(include-after)$ +$include-after$ + +$endfor$ +\end{document} diff --git a/glyphs/badge_back.gif b/glyphs/badge_back.gif deleted file mode 100644 index a2a7e6d..0000000 Binary files a/glyphs/badge_back.gif and /dev/null differ diff --git a/glyphs/gpg_16px.gif b/glyphs/gpg_16px.gif new file mode 120000 index 0000000..b1c7844 --- /dev/null +++ b/glyphs/gpg_16px.gif @@ -0,0 +1 @@ +../icons/gpg_16px.gif \ No newline at end of file diff --git a/glyphs/gpg_64px.gif b/glyphs/gpg_64px.gif new file mode 100644 index 0000000..c7710ef Binary files /dev/null and b/glyphs/gpg_64px.gif differ diff --git a/glyphs/icon_back.gif b/glyphs/icon_back.gif deleted file mode 100644 index ff04361..0000000 Binary files a/glyphs/icon_back.gif and /dev/null differ diff --git a/glyphs/icon_certificate.gif b/glyphs/icon_certificate.gif deleted file mode 100644 index 89b529f..0000000 Binary files a/glyphs/icon_certificate.gif and /dev/null differ diff --git a/glyphs/icon_coggle.gif b/glyphs/icon_coggle.gif deleted file mode 100644 index 01c43b2..0000000 Binary files a/glyphs/icon_coggle.gif and /dev/null differ diff --git a/glyphs/icon_crossmark.gif b/glyphs/icon_crossmark.gif deleted file mode 100644 index 2dcf9d9..0000000 Binary files a/glyphs/icon_crossmark.gif and /dev/null differ diff --git a/glyphs/icon_dashboard.gif b/glyphs/icon_dashboard.gif deleted file mode 100644 index 5c30551..0000000 Binary files a/glyphs/icon_dashboard.gif and /dev/null differ diff --git a/glyphs/icon_dashboard_x.gif b/glyphs/icon_dashboard_x.gif deleted file mode 100644 index 33d9b0a..0000000 Binary files a/glyphs/icon_dashboard_x.gif and /dev/null differ diff --git a/glyphs/icon_down.gif b/glyphs/icon_down.gif deleted file mode 100644 index 4f4e39e..0000000 Binary files a/glyphs/icon_down.gif and /dev/null differ diff --git a/glyphs/icon_left.gif b/glyphs/icon_left.gif deleted file mode 100644 index 524226b..0000000 Binary files a/glyphs/icon_left.gif and /dev/null differ diff --git a/glyphs/icon_pgp.gif b/glyphs/icon_pgp.gif deleted file mode 100644 index 732270e..0000000 Binary files a/glyphs/icon_pgp.gif and /dev/null differ diff --git a/glyphs/icon_right.gif b/glyphs/icon_right.gif deleted file mode 100644 index 15ff3cf..0000000 Binary files a/glyphs/icon_right.gif and /dev/null differ diff --git a/glyphs/icon_up.gif b/glyphs/icon_up.gif deleted file mode 100644 index 4e13c06..0000000 Binary files a/glyphs/icon_up.gif and /dev/null differ diff --git a/glyphs/icon_validate_14.gif b/glyphs/icon_validate_14.gif deleted file mode 100644 index ccb5cab..0000000 Binary files a/glyphs/icon_validate_14.gif and /dev/null differ diff --git a/glyphs/icon_warning.gif b/glyphs/icon_warning.gif deleted file mode 100644 index 08bd4a7..0000000 Binary files a/glyphs/icon_warning.gif and /dev/null differ diff --git a/icons/gpg_14px.gif b/icons/gpg_14px.gif new file mode 100644 index 0000000..14a054f Binary files /dev/null and b/icons/gpg_14px.gif differ diff --git a/icons/gpg_16px.gif b/icons/gpg_16px.gif new file mode 100644 index 0000000..d81eef7 Binary files /dev/null and b/icons/gpg_16px.gif differ diff --git a/icons/gpg_32px.gif b/icons/gpg_32px.gif new file mode 100644 index 0000000..32664b0 Binary files /dev/null and b/icons/gpg_32px.gif differ diff --git a/images/LICENSE b/images/LICENSE deleted file mode 100644 index ef84e83..0000000 --- a/images/LICENSE +++ /dev/null @@ -1,11 +0,0 @@ - LICENSE - - - -Files "manager_gnupg.xcf" and "manager_gnupg.png" are covered by Creative Commons Attribution-ShareAlike 3.0 Unported License. -See https://creativecommons.org/licenses/by-sa/3.0/legalcode . -Thanks to gnupg.org for the original images. - - -Others under this directory are covered by Apache License Version 2.0, - diff --git a/images/icon_pgp.gif b/images/icon_pgp.gif deleted file mode 100644 index 857cfbf..0000000 Binary files a/images/icon_pgp.gif and /dev/null differ diff --git a/images/icon_pgp_nanox.gif b/images/icon_pgp_nanox.gif deleted file mode 100644 index 732270e..0000000 Binary files a/images/icon_pgp_nanox.gif and /dev/null differ diff --git a/images/manager_gnupg.png b/images/manager_gnupg.png deleted file mode 100644 index 0291cb9..0000000 Binary files a/images/manager_gnupg.png and /dev/null differ diff --git a/ledger_app.toml b/ledger_app.toml new file mode 100644 index 0000000..e557cc8 --- /dev/null +++ b/ledger_app.toml @@ -0,0 +1,8 @@ +[app] +build_directory = "./" +sdk = "C" +devices = ["nanos", "nanox", "nanos+", "stax"] + +[tests] +unit_directory = "./unit-tests/" +pytest_directory = "./tests/" diff --git a/manual-tests/manual.sh b/manual-tests/manual.sh new file mode 100755 index 0000000..082cd9b --- /dev/null +++ b/manual-tests/manual.sh @@ -0,0 +1,210 @@ +#!/bin/bash +# +# script to check OpenPGP Application features +# + +exeName=$(readlink "$0") +[[ -z ${exeName} ]] && exeName=$0 +dirName=$(dirname "${exeName}") + +gnupg_home_dir="$(realpath "${dirName}/gnupg")" + +VERBOSE=false +EXPERT=false + +#=============================================================================== +# +# help - Prints script help and usage +# +#=============================================================================== +# shellcheck disable=SC2154 # var is referenced but not assigned +help() { + echo + echo "Usage: ${exeName} " + echo + echo "Options:" + echo + echo " -c : Requested command" + echo " -e : Expert mode mode" + echo " -v : Verbose mode" + echo " -h : Displays this help" + echo + exit 1 +} + +#=============================================================================== +# +# reset - Kill running process, ensure clear next operation +# +#=============================================================================== +reset() { + # Kill running process + killall scdaemon gpg-agent 2>/dev/null +} + +#=============================================================================== +# +# init - Init the gnupg config, start from an empty keyring +# +#=============================================================================== +init() { + reset + + # Cleanup old gnupg home directory + dir=$(basename "${gnupg_home_dir}") + rm -fr "${dir}" foo.txt* + mkdir "${dir}" + chmod 700 "${dir}" + + { + echo reader-port \"Ledger token\" + echo allow-admin + echo enable-pinpad-varlen + echo card-timeout 1 + } > "${dir}/scdaemon.conf" + + if [[ ${EXPERT} == true ]]; then + { + echo log-file /tmp/scd.log + echo debug-level guru + echo debug-all + } >> "${dir}/scdaemon.conf" + fi + + gpgconf --reload scdaemon +} + +#=============================================================================== +# +# card - Show/edit the card status and configuration +# +#=============================================================================== +card() { + local expert_mode="" + + [[ ${EXPERT} == true ]] && expert_mode="--expert" + + gpg --homedir "${gnupg_home_dir}" ${expert_mode} --card-edit +} + +#=============================================================================== +# +# encrypt - Encrypt a clear file +# +#=============================================================================== +encrypt() { + local recipient="" + local verbose_mode="" + reset + rm -fr foo* + echo CLEAR > foo.txt + + [[ ${VERBOSE} == true ]] && verbose_mode="--verbose" + + recipient=$(gpg --homedir "${gnupg_home_dir}" --card-status | grep "General key info" | awk '{print $NF}') + + echo "Encrypt with recipient '${recipient}'" + + gpg --homedir "${gnupg_home_dir}" ${verbose_mode} --encrypt --recipient "${recipient}" foo.txt +} + +#=============================================================================== +# +# decrypt - Decrypt a file and compare with original clear content +# +#=============================================================================== +decrypt() { + local verbose_mode="" + + reset + + [[ ${VERBOSE} == true ]] && verbose_mode="--verbose" + + gpg --homedir "${gnupg_home_dir}" ${verbose_mode} --decrypt foo.txt.gpg > foo_dec.txt + + # Check with original clear file + diff foo.txt foo_dec.txt >/dev/null + if [[ $? -eq 0 ]]; then + echo "Success !" + else + echo "Decryption error!" + fi + rm -fr foo* +} + +#=============================================================================== +# +# sign - Sign a file +# +#=============================================================================== +sign() { + local verbose_mode="" + + reset + rm -fr foo* + echo CLEAR > foo.txt + + [[ ${VERBOSE} == true ]] && verbose_mode="--verbose" + + gpg --homedir "${gnupg_home_dir}" ${verbose_mode} --sign foo.txt +} + +#=============================================================================== +# +# verify - Verify a file signature +# +#=============================================================================== +verify() { + local verbose_mode="" + + reset + + [[ ${VERBOSE} == true ]] && verbose_mode="--verbose" + + gpg --homedir "${gnupg_home_dir}" ${verbose_mode} --verify foo.txt.gpg + rm -fr foo* +} + +#=============================================================================== +# +# Parsing parameters +# +#=============================================================================== + +if (($# < 1)); then + help +fi + +while getopts ":c:evh" opt; do + case $opt in + + c) + case ${OPTARG} in + init|reset|card|encrypt|decrypt|sign|verify) + CMD=${OPTARG} + ;; + *) + echo "Wrong parameter '${OPTARG}'!" + exit 1 + ;; + esac + ;; + + e) EXPERT=true ;; + v) VERBOSE=true ;; + h) help ;; + + \?) echo "Unknown option: -${OPTARG}" >&2; exit 1;; + : ) echo "Missing option argument for -${OPTARG}" >&2; exit 1;; + * ) echo "Unimplemented option: -${OPTARG}" >&2; exit 1;; + esac +done + +#=============================================================================== +# +# Main +# +#=============================================================================== + +# execute the command +${CMD} diff --git a/pytools/backup.py b/pytools/backup.py new file mode 100755 index 0000000..9a4e66e --- /dev/null +++ b/pytools/backup.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +#***************************************************************************** +# Ledger App OpenPGP. +# (c) 2024 Ledger SAS. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#***************************************************************************** + +import sys +from pathlib import Path +from argparse import ArgumentParser, RawTextHelpFormatter, Namespace +from gpgapp.gpgcard import GPGCard, PassWord, GPGCardExcpetion + +# =============================================================================== +# Parse command line options +# =============================================================================== +def get_argparser() -> Namespace: + """Parse the commandline options""" + + parser = ArgumentParser( + description="Backup/Restore OpenPGP App configuration", + epilog="Keys restore is only possible with SEED mode...", + formatter_class=RawTextHelpFormatter + ) + parser.add_argument("--reader", type=str, default="Ledger", + help="PCSC reader name (default is '%(default)s') or 'speculos'") + + parser.add_argument("--slot", type=int, choices=range(1, 4), help="Select slot (1 to 3)") + + parser.add_argument("--pinpad", action="store_true", + help="PIN validation will be delegated to pinpad") + parser.add_argument("--adm-pin", metavar="PIN", + help="Admin PIN (if pinpad not used)", required="--pinpad" not in sys.argv) + parser.add_argument("--user-pin", metavar="PIN", + help="User PIN (if pinpad not used)", required="--pinpad" not in sys.argv) + + parser.add_argument("--restore", action="store_true", + help="Perform a Restore instead of Backup") + + parser.add_argument("--file", type=str, default="gpg_backup", + help="Backup/Restore file (default is '%(default)s')") + + parser.add_argument("--seed-key", action="store_true", + help="After Restore, regenerate all keys, based on seed mode") + + return parser.parse_args() + + +# =============================================================================== +# MAIN +# =============================================================================== +def entrypoint() -> None: + """Main function""" + + # Arguments parsing + # ----------------- + args = get_argparser() + + # Arguments checking + # ------------------ + if not args.pinpad: + if not args.adm_pin or not args.user_pin: + print("If 'pinpad' is not use, 'userpin' and 'admpin' must be provided.") + sys.exit() + + if args.restore is False: + if Path(args.file).is_file(): + print(f"Provided backup file '{args.file}' already exist. Aborting!") + sys.exit() + + # Processing + # ---------- + try: + print(f"Connect to card '{args.reader}'...") + gpgcard: GPGCard = GPGCard() + gpgcard.connect(args.reader) + + if not gpgcard.verify_pin(PassWord.PW1, args.user_pin, args.pinpad) or \ + not gpgcard.verify_pin(PassWord.PW3, args.adm_pin, args.pinpad): + raise GPGCardExcpetion(0, "PIN not verified") + + if args.slot: + gpgcard.select_slot(args.slot - 1) + + gpgcard.get_all() + + if args.restore: + gpgcard.restore(args.file) + print(f"Configuration restored from file '{args.file}'.") + + if args.seed_key: + gpgcard.seed_key() + + else: + gpgcard.backup(args.file) + print(f"Configuration saved in file '{args.file}'.") + + gpgcard.disconnect() + + except GPGCardExcpetion as err: + print(f"\n### Error {err.code}: {err.message}!\n") + + +if __name__ == "__main__": + + entrypoint() diff --git a/pytools/gpgapp/__init__.py b/pytools/gpgapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pytools/gpgapp/gpgcard.py b/pytools/gpgapp/gpgcard.py new file mode 100644 index 0000000..897f5b4 --- /dev/null +++ b/pytools/gpgapp/gpgcard.py @@ -0,0 +1,1292 @@ +# -*- coding: utf-8 -*- +#***************************************************************************** +# Ledger App OpenPGP. +# (c) 2024 Ledger SAS. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#***************************************************************************** + +import binascii +from datetime import datetime, timezone +import pickle +from hashlib import sha1 +from typing import Optional, Tuple +from dataclasses import dataclass +from Crypto.PublicKey.RSA import construct + +from gpgapp.gpgcmd import DataObject, ErrorCodes, KeyTypes, PassWord, PubkeyAlgo # type: ignore +from gpgapp.gpgcmd import KEY_OPERATIONS, KEY_TEMPLATES, USER_SALUTATION # type: ignore + +# pylint: disable=import-error +from ledgercomm import Transport # type: ignore +# pylint: enable=import-error + +APDU_MAX_SIZE: int = 0xFE +APDU_CHAINING_MODE: int = 0x10 + + +class GPGCardExcpetion(Exception): + """Exception handler. + + Attributes: + code (int): Error code + message (str): Error message + """ + + def __init__(self, code, message): + self.code = code + self.message = message + super().__init__(self.message) + + +@dataclass +class KeyInfo: + """Key description information""" + + attribute: bytes = b"" + fingerprint: bytes = b"" + ca_fingerprint: bytes = b"" + cert: str = "" + date: datetime = datetime.min + uif: int = 0 + key: bytes = b"" + + def reset(self): + """Reset the data to the initial value""" + + self.attribute = b"" + self.fingerprint = b"" + self.ca_fingerprint = b"" + self.cert = "" + self.date = datetime.min + self.uif = 0 + self.key = b"" + + +@dataclass +class CardInfo: + """Card description information""" + + #token info + AID: str = "" + ext_length: bytes = b"" + ext_capabilities: bytes = b"" + histo_bytes: bytes = b"" + PW_status: bytes = b"" + hw_features: int = 0 + + #user info + name: str = "" + login: str = "" + url: str = "" + lang: str = "" + salutation: str = "" + + #keys info + rsa_pub_exp: int = 0 + digital_counter: int = 0 + + sig: KeyInfo = KeyInfo() + dec: KeyInfo = KeyInfo() + aut: KeyInfo = KeyInfo() + + #private info + private_01: bytes = b"" + private_02: bytes = b"" + private_03: bytes = b"" + private_04: bytes = b"" + + + def reset(self): + """Reset the data to the initial value""" + + #token info + self.AID = "" + self.ext_length = b"" + self.ext_capabilities = b"" + self.histo_bytes = b"" + self.PW_status = b"" + + #user info + self.name = "" + self.login = "" + self.url = "" + self.lang = "" + self.salutation = "" + + #keys info + self.rsa_pub_exp = 0 + self.digital_counter = 0 + + self.sig.reset() + self.dec.reset() + self.aut.reset() + + #private info + self.private_01 = b"" + self.private_02 = b"" + self.private_03 = b"" + + +class GPGCard() : + def __init__(self) -> None: + self.log: bool = False + self.transport: Transport = None + self.slot_current: bytes = b"\x00" + self.slot_config: bytes = bytes(3) + self.data: CardInfo = CardInfo() + self.data.reset() + + def connect(self, device: str) -> None: + """Connect to the selected Reader + + Args: + device (str): Reader device name + """ + + if device == "speculos": + self.transport = Transport("tcp", server="127.0.0.1", port=9999, debug=False) + else: + self.transport = Transport("hid") + print("") + + + def disconnect(self): + """Connect from the selected Reader""" + + self.transport.close() + + + ############### LOG interface ############### + def log_apdu(self, log: bool) -> None: + """Control APDU debugging display + + Args: + log (bool): Activate or not the debug print + """ + + self.log = log + + def add_log(self, mode: str, data: bytes, sw: int = 0) -> None: + """Print APDU content + + Args: + mode (str): Indicate the Send or Recv information + data (bytes): APDU content + sw (int): Returned Status + """ + + if self.log: + sw_code = f" ({sw:04x})" if mode == "recv" else "" + print(f"{mode}:{sw_code} {''.join([f'{b:02x}' for b in data])}") + + + ############### CARD interface ############### + def select(self): + """Send SELECT APDU command""" + + apdu = binascii.unhexlify(b"00A4040006D27600012401") + return self._exchange(apdu) + + + def activate(self): + """Send ACTIVATE APDU command""" + + apdu = binascii.unhexlify(b"00440000") + return self._exchange(apdu) + + + def terminate(self): + """Send TERMINATE APDU command""" + + apdu = binascii.unhexlify(b"00E60000") + return self._exchange(apdu) + + + def get_log(self): + """Send GET_LOG APDU command""" + + apdu = binascii.unhexlify(b"00040000") + return self._exchange(apdu) + + + ############### API interfaces ############### + def get_all(self) -> None: + """Retrieve all Data Object values from the Card""" + + self.data.reset() + data: Optional[bytes] = b"" + b_data: bytes = b"" + s_data: str = "" + + self.slot_current = self._get_data(DataObject.CMD_SLOT_CUR) + self.slot_config = self._get_data(DataObject.CMD_SLOT_CFG) + + self.data.AID = self._get_data(DataObject.DO_AID).hex().upper() + self.data.login = self._get_data(DataObject.DO_LOGIN).decode("utf-8") + self.data.url = self._get_data(DataObject.DO_URL).decode("utf-8") + self.data.histo_bytes = self._get_data(DataObject.DO_HIST) + data = self._get_data(DataObject.DO_GEN_FEATURES) + if data: + self.data.hw_features = data[0] + + data = self._get_data(DataObject.DO_CARDHOLDER_DATA) + tags = self._decode_tlv(data) + if DataObject.DO_CARD_NAME in tags: + self.data.name = tags[DataObject.DO_CARD_NAME].decode("utf-8") + if DataObject.DO_CARD_SALUTATION in tags: + s_data = tags[DataObject.DO_CARD_SALUTATION].decode("utf-8") + for k,v in USER_SALUTATION.items(): + if v == s_data: + self.data.salutation = k + break + + if DataObject.DO_CARD_LANG in tags: + self.data.lang = tags[DataObject.DO_CARD_LANG].decode("utf-8") + + data = self._get_data(DataObject.DO_APP_DATA) + tags = self._decode_tlv(data) + if DataObject.DO_EXT_LEN in tags: + self.data.ext_length = tags[DataObject.DO_EXT_LEN] + if DataObject.DO_DISCRET_DATA in tags: + b_data = tags[DataObject.DO_DISCRET_DATA] + tags = self._decode_tlv(b_data) + if DataObject.DO_EXT_CAP in tags: + self.data.ext_capabilities = tags[DataObject.DO_EXT_CAP] + if DataObject.DO_SIG_ATTR in tags: + self.data.sig.attribute = tags[DataObject.DO_SIG_ATTR] + if DataObject.DO_DEC_ATTR in tags: + self.data.dec.attribute = tags[DataObject.DO_DEC_ATTR] + if DataObject.DO_AUT_ATTR in tags: + self.data.aut.attribute = tags[DataObject.DO_AUT_ATTR] + if DataObject.DO_PW_STATUS in tags: + self.data.PW_status = tags[DataObject.DO_PW_STATUS] + + data = tags.get(DataObject.DO_FINGERPRINTS) + if data: + self.data.sig.fingerprint = data[0:20] + self.data.dec.fingerprint = data[20:40] + self.data.aut.fingerprint = data[40:60] + data = tags.get(DataObject.DO_CA_FINGERPRINTS) + if data: + self.data.sig.ca_fingerprint = data[0:20] + self.data.dec.ca_fingerprint = data[20:40] + self.data.aut.ca_fingerprint = data[40:60] + data = tags.get(DataObject.DO_KEY_DATES) + if data: + dates = tags[DataObject.DO_KEY_DATES] + self._conv_date_from_bytes(KeyTypes.KEY_SIG, dates[0:4]) + self._conv_date_from_bytes(KeyTypes.KEY_DEC, dates[4:8]) + self._conv_date_from_bytes(KeyTypes.KEY_AUT, dates[8:12]) + + data = self._get_data(DataObject.CMD_RSA_EXP) + self.data.rsa_pub_exp = self._get_int(data, 4) + self.data.aut.cert = self._get_data(DataObject.DO_CERT).decode("utf-8") + self.data.dec.cert = self._get_data(DataObject.DO_CERT, True).decode("utf-8") + self.data.sig.cert = self._get_data(DataObject.DO_CERT, True).decode("utf-8") + + self.data.sig.uif = int(self._get_data(DataObject.DO_UIF_SIG)[0]) + self.data.dec.uif = int(self._get_data(DataObject.DO_UIF_DEC)[0]) + self.data.aut.uif = int(self._get_data(DataObject.DO_UIF_AUT)[0]) + + data = self._get_data(DataObject.DO_SEC_TEMPL) + tags = self._decode_tlv(data) + if DataObject.DO_SIG_COUNT in tags: + b_data = tags[DataObject.DO_SIG_COUNT] + self.data.digital_counter = self._get_int(b_data, 3) + + if self.data.ext_capabilities[0] & 0x08: + self.data.private_01 = self._get_data(DataObject.DO_PRIVATE_01) + self.data.private_02 = self._get_data(DataObject.DO_PRIVATE_02) + self.data.private_03 = self._get_data(DataObject.DO_PRIVATE_03) + self.data.private_04 = self._get_data(DataObject.DO_PRIVATE_04) + + self.data.sig.key = self._get_data(DataObject.DO_SIG_KEY) + self.data.dec.key = self._get_data(DataObject.DO_DEC_KEY) + self.data.aut.key = self._get_data(DataObject.DO_AUT_KEY) + + + def backup(self, file_name: str) -> None: + """Backup data to backup file + + Args: + file_name (str): Backup filename + """ + + self.get_all() + with open(file_name, mode="w+b") as f: + pickle.dump( + (self.data.AID, self.data.PW_status, self.data.rsa_pub_exp, self.data.digital_counter, + self.data.private_01, self.data.private_02, + self.data.private_03, self.data.private_04, + self.data.name, self.data.login, self.data.salutation, self.data.url, self.data.lang, + self.data.sig.key, self.data.sig.uif, self.data.sig.attribute, self.data.sig.date, + self.data.sig.fingerprint, self.data.sig.ca_fingerprint, self.data.sig.cert, + self.data.dec.key, self.data.dec.uif, self.data.dec.attribute, self.data.dec.date, + self.data.dec.fingerprint, self.data.dec.ca_fingerprint, self.data.dec.cert, + self.data.aut.key, self.data.aut.uif, self.data.aut.attribute, self.data.aut.date, + self.data.aut.fingerprint, self.data.aut.ca_fingerprint, self.data.aut.cert), + f, 2) + + + def restore(self, file_name: str) -> None: + """Restore data from backup file + + Args: + file_name (str): Backup filename + """ + + with open(file_name, mode="r+b") as f: + (self.data.AID, self.data.PW_status, self.data.rsa_pub_exp, self.data.digital_counter, + self.data.private_01, self.data.private_02, self.data.private_03, self.data.private_04, + self.data.name, self.data.login, self.data.salutation, self.data.url, self.data.lang, + self.data.sig.key, self.data.sig.uif, self.data.sig.attribute, self.data.sig.date, + self.data.sig.fingerprint, self.data.sig.ca_fingerprint, self.data.sig.cert, + self.data.dec.key, self.data.dec.uif, self.data.dec.attribute, self.data.dec.date, + self.data.dec.fingerprint, self.data.dec.ca_fingerprint, self.data.dec.cert, + self.data.aut.key, self.data.aut.uif, self.data.aut.attribute, self.data.aut.date, + self.data.aut.fingerprint, self.data.aut.ca_fingerprint, self.data.aut.cert) = pickle.load(f) + + self._put_data(DataObject.DO_AID, bytes.fromhex(self.data.AID[20:28])) + self._put_data(DataObject.DO_PW_STATUS, self.data.PW_status) + + self._put_data(DataObject.DO_PRIVATE_01, self.data.private_01) + self._put_data(DataObject.DO_PRIVATE_02, self.data.private_02) + self._put_data(DataObject.DO_PRIVATE_03, self.data.private_03) + self._put_data(DataObject.DO_PRIVATE_04, self.data.private_04) + + self._put_data(DataObject.DO_CARD_NAME, self.data.name.encode("utf-8")) + self._put_data(DataObject.DO_LOGIN, self.data.login.encode("utf-8")) + self._put_data(DataObject.DO_CARD_LANG, self.data.lang.encode("utf-8")) + self._put_data(DataObject.DO_URL, self.data.url.encode("utf-8")) + if len(self.data.salutation) == 0: + self._put_data(DataObject.DO_CARD_SALUTATION, b'\x30') + else: + self._put_data(DataObject.DO_CARD_SALUTATION, + bytes.fromhex(USER_SALUTATION[self.data.salutation])) + + self._put_data(DataObject.DO_SIG_ATTR, self.data.sig.attribute) + self._put_data(DataObject.DO_DEC_ATTR, self.data.dec.attribute) + self._put_data(DataObject.DO_AUT_ATTR, self.data.aut.attribute) + + self._put_data(DataObject.DO_UIF_SIG, self.data.sig.uif.to_bytes(2, "little")) + self._put_data(DataObject.DO_UIF_DEC, self.data.dec.uif.to_bytes(2, "little")) + self._put_data(DataObject.DO_UIF_AUT, self.data.aut.uif.to_bytes(2, "little")) + + self._put_data(DataObject.DO_SIG_COUNT, self.data.digital_counter.to_bytes(4, "big")) + self._put_data(DataObject.CMD_RSA_EXP, self.data.rsa_pub_exp.to_bytes(4, "little")) + + self._put_data(DataObject.DO_CERT, self.data.aut.cert.encode("utf-8")) + self._put_data(DataObject.DO_CERT, self.data.dec.cert.encode("utf-8")) + self._put_data(DataObject.DO_CERT, self.data.sig.cert.encode("utf-8")) + + self._put_data(DataObject.DO_CA_FINGERPRINT_WR_SIG, self.data.sig.ca_fingerprint) + self._put_data(DataObject.DO_FINGERPRINT_WR_SIG, self.data.sig.fingerprint) + date = str(self.data.sig.date) + dt = datetime.strptime(date, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) + bdate = int(dt.timestamp()).to_bytes(4, "big") + self._put_data(DataObject.DO_DATES_WR_SIG, bdate) + self._put_data(DataObject.DO_SIG_KEY, self.data.sig.key) + + self._put_data(DataObject.DO_CA_FINGERPRINT_WR_DEC, self.data.dec.ca_fingerprint) + self._put_data(DataObject.DO_FINGERPRINT_WR_DEC, self.data.dec.fingerprint) + date = str(self.data.dec.date) + dt = datetime.strptime(date, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) + bdate = int(dt.timestamp()).to_bytes(4, "big") + self._put_data(DataObject.DO_DATES_WR_DEC, bdate) + self._put_data(DataObject.DO_DEC_KEY, self.data.dec.key) + + self._put_data(DataObject.DO_CA_FINGERPRINT_WR_AUT, self.data.aut.ca_fingerprint) + self._put_data(DataObject.DO_FINGERPRINT_WR_AUT, self.data.aut.fingerprint) + date = str(self.data.aut.date) + dt = datetime.strptime(date, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) + bdate = int(dt.timestamp()).to_bytes(4, "big") + self._put_data(DataObject.DO_DATES_WR_AUT, bdate) + self._put_data(DataObject.DO_AUT_KEY, self.data.aut.key) + + + def export_pub_key(self, pubkey: dict, file_name: str) -> None: + """Export a Public to file + + Args: + pubkey (dict): Public key parameters + file_name (str): Backup filename + """ + + modulus = bytearray.fromhex(pubkey["Modulus"]) + exponent = bytearray.fromhex(pubkey["Pub Exp"][2:]) + key = construct((int.from_bytes(modulus, 'big'), int.from_bytes(exponent, 'big'))) + public_key = key.publickey().export_key() + with open(file_name, mode="wb") as f: + f.write(public_key) + + + def seed_key(self) -> None: + """Regenerate keys, based on seed mode""" + + apdu = binascii.unhexlify(b"0047800102B600") + self._exchange(apdu) + apdu = binascii.unhexlify(b"0047800102B800") + self._exchange(apdu) + apdu = binascii.unhexlify(b"0047800102A400") + self._exchange(apdu) + + + ############### Information decoding ############### + def decode_AID(self) -> dict: + """Decode Application IDentity information""" + + return { + "AID": f"{self.data.AID}", + "RID": f"{self.data.AID[0:10]}", + "Application": f"{self.data.AID[10:12]}", + "Version": f"{int(self.data.AID[12:14]):d}.{int(self.data.AID[14:16]):d}", + "Manufacturer": f"{self.data.AID[16:20]}", + "Serial": f"{self.data.AID[20:28]}" + } + + def decode_histo(self) -> dict: + """Decode Historical Bytes information""" + + return { + "historical bytes": self.data.histo_bytes.hex() + } + + def decode_extlength(self) -> dict: + """Decode Extended Length information""" + + d = { + "Command": "N/A", + "Response": "N/A", + } + + if self.data.ext_length: + d["Command"] = f"{self._get_int(self.data.ext_length, offset=2):d}" + d["Response"] = f"{self._get_int(self.data.ext_length, offset=6):d}" + return d + + def decode_ext_capabilities(self) -> dict: + """Decode Extended Capabilities information""" + + d = {} + b1 = self.data.ext_capabilities[0] + if b1 & 0x80: + if self.data.ext_capabilities[1] == 1: + d["Secure Messaging"] = "✓: AES 128 bits" + elif self.data.ext_capabilities[1] == 2: + d["Secure Messaging"] = "✓: AES 256 bits" + else: + d["Secure Messaging"] = "✓: ?? bits" + else: + d["Secure Messaging"] = "✗" + + if b1 & 0x40: + max_val = self._get_int(self.data.ext_capabilities, offset=2) + d["Get Challenge"] = f"✓ (Max length: {max_val:d})" + else: + d["Get Challenge"] = "✗" + + if b1 & 0x20: + d["Key import"] = "✓" + else: + d["Key import"] = "✗" + + if b1 & 0x10: + d["PW status"] = "Changeable" + else: + d["PW status"] = "Fixed" + + if b1 & 0x08: + d["Private DOs"] = "✓" + else: + d["Private DOs"] = "✗" + + if b1 & 0x04: + d["Algo attributes"] = "Changeable" + else: + d["Algo attributes"] = "Fixed" + + if b1 & 0x02: + d["PSO:DEC AES"] = "✓" + else: + d["PSO:DEC AES"] = "✗" + + if b1 & 0x01: + d["Key Derived Format"] = "✓" + else: + d["Key Derived Format"] = "✗" + + max_val = self._get_int(self.data.ext_capabilities, offset=4) + d["Max Cert len"] = f"{max_val:d}" + max_val = self._get_int(self.data.ext_capabilities, offset=6) + d["Max Special DO"] = f"{max_val:d}" + + if self.data.ext_capabilities[8]: + d["PIN 2 format"] = "✓" + else: + d["PIN 2 format"] = "✗" + if self.data.ext_capabilities[9]: + d["MSE"] = "✓" + else: + d["MSE"] = "✗" + return d + + def decode_pws(self) -> dict: + """Decode Password information""" + + if self.data.PW_status[0] == 0: + validity = "Only 1 PSO:CDS" + elif self.data.PW_status[0] == 1: + validity = "Several PSO:CDS" + else: + validity = f"unknown ({self.data.PW_status[0]:d})" + + cfg = { + "PW1": {"format": 1, "counter": 4}, + "Reset Counter": {"format": 2, "counter": 5}, + "PW3": {"format": 3, "counter": 6}, + } + + d = {} + for name, pw in cfg.items(): + if self.data.PW_status[pw["format"]] & 0x80: + fmt = "Format-2" + else: + fmt = "UTF-8" + pwlen = self.data.PW_status[pw["format"]] & 0x7f + counter = self.data.PW_status[pw['counter']] + d[name] = f"{fmt} ({pwlen:d} bytes), Error Counter={counter:d}" + if name == "PW1": + d[name] += f", Validity={validity}" + return d + + def decode_hardware(self) -> dict: + """Decode Hardware features information""" + + d = {} + d["Display"] = "✓" if self.data.hw_features & 0x80 else "✗" + d["Biometric sensor"] = "✓" if self.data.hw_features & 0x40 else "✗" + d["Button/Keypad"] = "✓" if self.data.hw_features & 0x20 else "✗" + d["LED"] = "✓" if self.data.hw_features & 0x10 else "✗" + d["Loudspeaker"] = "✓" if self.data.hw_features & 0x08 else "✗" + d["Microphone"] = "✓" if self.data.hw_features & 0x04 else "✗" + d["Touchscreen"] = "✓" if self.data.hw_features & 0x02 else "✗" + d["Battery"] = "✓" if self.data.hw_features & 0x01 else "✗" + return d + + + ############### SLOT interface ############### + def select_slot(self, slot: int) -> None: + """Select the key slot + + Args: + slot (int): slot id to select (0 to 3) + """ + + self.slot_current = slot.to_bytes(1, "big") + self._put_data(DataObject.CMD_SLOT_CUR, self.slot_current) + + def decode_slot(self) -> dict: + """Decode Slots information + + Returns: + Slots configuration dictionary + """ + + d = {} + d["Number of Slots"] = str(self.slot_config[0]) + d["Default Slot"] = str(self.slot_config[1] + 1) + d["Selection by APDU"] = "✓" if self.slot_config[2] & 0x01 else "✗" + d["Selection by screen"] = "✓" if self.slot_config[2] & 0x02 else "✗" + d["Current"] = str(int.from_bytes(self.slot_current, "big") + 1) + return d + + + ############### USER interface ############### + def set_serial(self, serial: str) -> None: + """Set the Card serial number + + Args: + serial (str): New serial number + """ + + if not self.data.AID: + raise GPGCardExcpetion(ErrorCodes.ERR_INTERNAL, "Invalid AID!") + + self.data.AID = self.data.AID[0:20] + serial + self._put_data(DataObject.DO_AID, bytes.fromhex(serial)) + + + def set_name(self, name: str) -> None: + """Set the Card User name + + Args: + name (str): New name + """ + + self.data.name = name + self._put_data(DataObject.DO_CARD_NAME, name.encode("utf-8")) + + def get_name(self) -> str: + """Get the Card User name""" + + return self.data.name + + + def set_login(self, login: str) -> None: + """Set the Card User login + + Args: + login (str): New login + """ + + self.data.login = login + self._put_data(DataObject.DO_LOGIN, login.encode("utf-8")) + + def get_login(self) -> str: + """Get the Card User login""" + + return self.data.login + + + def set_url(self, url: str) -> None: + """Set the Card User URL + + Args: + url (str): New URL + """ + + self.data.url = url + self._put_data(DataObject.DO_URL, url.encode("utf-8")) + + def get_url(self) -> str: + """Get the Card User URL""" + + return self.data.url + + + def set_lang(self, lang: str) -> None: + """Set the Card User language + + Args: + lang (str): New language + """ + + self.data.lang = lang + self._put_data(DataObject.DO_CARD_LANG, lang.encode("utf-8")) + + def get_lang(self) -> str: + """Get the Card User language""" + + return self.data.lang + + + def set_salutation(self, salutation: str) -> None: + """Set the Card User salutation + + Args: + salutation (str): New salutation + """ + + try: + salutation_str = USER_SALUTATION[salutation].encode("utf-8") + except KeyError as err: + raise GPGCardExcpetion(ErrorCodes.ERR_INTERNAL, + f"Invalid salutation value ({salutation})!") from err + + self.data.salutation = salutation + self._put_data(DataObject.DO_CARD_SALUTATION, salutation_str) + + def get_salutation(self) -> str: + """Get the Card User salutation""" + + return self.data.salutation + + + ############### PASSWORD interface ############### + def verify_pin(self, pw: PassWord, value: str, pinpad: bool = False) -> bool: + """Verify the password + + Args: + pw (PassWord): Password type, corresponding to User, Admin + value (str) : Password value + pinpad (bool): Indicate to use pinpad + + Return: + Success / KO boolean + """ + + value = value if value else "" + if pinpad: + apdu = bytes.fromhex(f"EF2000{pw:02x}00") + else: + apdu = bytes.fromhex(f"002000{pw:02x}{len(value):02x}") + value.encode("utf-8") + _, sw = self._exchange(apdu) + return sw == ErrorCodes.ERR_SUCCESS + + + def change_pin(self, pw: PassWord, cur_value: str, new_value: str) -> bool: + """Update the password + + Args: + pw (PassWord): Password type, corresponding to User, Admin + cur_value (str): Current password value + new_value (str): New password value + + Return: + Success / KO boolean + """ + + lc = len(cur_value) + len(new_value) + apdu = bytes.fromhex(f"002400{pw:02x}{lc:02x}") + \ + cur_value.encode("utf-8") + \ + new_value.encode("utf-8") + _, sw = self._exchange(apdu) + return sw == ErrorCodes.ERR_SUCCESS + + + def set_RC(self, value: str) -> bool: + """Set the User Password Resetting Code + + Args: + value (str): Resetting Code value + + Return: + Success / KO boolean + """ + + b_value = value.encode("utf-8") + return self._put_data(DataObject.DO_RESET_CODE, b_value) == ErrorCodes.ERR_SUCCESS + + + def reset_PW1(self, RC: str, value: str) -> bool: + """Reset the User Password with Resetting Code + + Args: + RC (str): Resetting Code value + value (str): User Password value + + Return: + Success / KO boolean + """ + + p1 = 2 if len(RC) == 0 else 0 + lc = len(RC) + len(value) + apdu = bytes.fromhex(f"002C{p1:02x}81{lc:02x}") + RC.encode("utf-8") + value.encode("utf-8") + _, sw = self._exchange(apdu) + return sw == ErrorCodes.ERR_SUCCESS + + + ############### KEYS interface ############### + def decode_key_uif(self, key: str) -> str: + """Decode the selected key User Interaction Flag + + Args: + key (str): Key type (SIG, DC, AUT) + + Return: + UIF status for the selected key + """ + + uif = self._get_key_object(key).uif + if uif == 0: + return "✗" + if uif == 1: + return "✓" + if uif == 2: + return "✓ (Permanent)" + return "" + + def get_key_date(self, key: str) -> str: + """Get key Creation Date + + Args: + key (str): Key type (SIG, DC, AUT) + + Return: + Key Creation Date + """ + + return str(self._get_key_object(key).date) + + def get_sig_count(self) -> int: + """Get Digital Signatures Count + + Return: + Number of Digital Signatures Count + """ + + return self.data.digital_counter + + def get_rsa_pub_exp(self) -> int: + """Get RSA Public Exponent + + Return: + RSA Public Exponent + """ + + return self.data.rsa_pub_exp + + def get_key_cert(self, key: str) -> str: + """Get key Certificate + + Args: + key (str): Key type (SIG, DC, AUT) + + Return: + Key Certificate + """ + + return self._get_key_object(key).cert + + def set_template(self, key: str, template: str) -> None: + """Set key Template + + Args: + key (str): Key type (SIG, DC, AUT) + template (str): Key template + """ + + if template not in KEY_TEMPLATES: + raise GPGCardExcpetion(ErrorCodes.ERR_INTERNAL, f"Invalid template: {template}") + + data = binascii.unhexlify(KEY_TEMPLATES[template]) + if key == KeyTypes.KEY_SIG: + self._put_data(DataObject.DO_SIG_ATTR, data) + elif key == KeyTypes.KEY_DEC: + self._put_data(DataObject.DO_DEC_ATTR, data) + elif key == KeyTypes.KEY_AUT: + self._put_data(DataObject.DO_AUT_ATTR, data) + + def set_key_fingerprint(self, key: str, data: bytes) -> None: + """Set key fingerprint + + Args: + key (str): Key type (SIG, DC, AUT) + data (bytes): Fingerprint + """ + + if key == KeyTypes.KEY_SIG: + self.data.sig.fingerprint = data + self._put_data(DataObject.DO_FINGERPRINT_WR_SIG, data) + elif key == KeyTypes.KEY_AUT: + self.data.aut.fingerprint = data + self._put_data(DataObject.DO_FINGERPRINT_WR_AUT, data) + elif key == KeyTypes.KEY_DEC: + self.data.dec.fingerprint = data + self._put_data(DataObject.DO_FINGERPRINT_WR_DEC, data) + + def get_key_fingerprint(self, key: str) -> str: + """Get key fingerprint + + Args: + key (str): Key type (SIG, DC, AUT) + + Return: + Key Fingerprint + """ + + fingerprint = self._get_key_object(key).fingerprint + sdata = binascii.hexlify(fingerprint).decode("ascii") + return sdata if sdata != "0"*40 else "N/A" + + def get_key_CA_fingerprint(self, key: str) -> str: + """Get key CA fingerprint + + Args: + ey (str): Key type (SIG, DC, AUT) + + Return: + Key CA Fingerprint + """ + + fingerprint = self._get_key_object(key).ca_fingerprint + sdata = binascii.hexlify(fingerprint).decode("ascii") + return sdata if sdata != "0"*40 else "N/A" + + def decode_attributes(self, key: str) -> str: + """Decode key attribute + + Args: + key (str): Key type (SIG, DC, AUT) + + Return: + String with attributes and size + """ + + attributes = self._get_key_object(key).attribute + if not attributes or len(attributes) == 0: + return "" + + if attributes[0] == PubkeyAlgo.RSA: + if attributes[5] == 0: + fmt = "standard (e, p, q)" + elif attributes[5] == 1: + fmt = "standard with modulus (n)" + elif attributes[5] == 2: + fmt = "crt (Chinese Remainder Theorem)" + elif attributes[5] == 3: + fmt = "crt (Chinese Remainder Theorem) with modulus (n)" + ret = f"RSA-{self._get_int(attributes, offset=1)}" + ret += f", Format: {fmt}" + ret += f", Exponent size: {self._get_int(attributes, offset=3)}" + return ret + + if attributes[0] == PubkeyAlgo.ECDSA: + return "ECDSA" + if attributes[0] == PubkeyAlgo.ECDH: + return "ECDH" + if attributes[0] == PubkeyAlgo.EDDSA: + return "EDDSA" + return "" + + + def decode_key(self, key: str) -> dict: + """Get key parameters + + Args: + key (str): Key type (SIG, DC, AUT) + + Return: + Key information dictionary + """ + + d = {} + offset: int = 0 + key_data = self._get_key_object(key).key + + d["OS Target ID"] = f"0x{int.from_bytes(key_data[offset:offset + 4], 'big'):04x}" + offset += 4 + d["API Level"] = str(int.from_bytes(key_data[offset:offset + 4], 'big')) + offset += 4 + size = int.from_bytes(key_data[offset:offset + 4], 'big') + # Should be Public key here from doc, but only Public Exp from the code + d["Public exp size"] = str(size) + offset += 4 + d["Public exp"] = f"0x{int.from_bytes(key_data[offset:offset + 4], 'big'):06x}" + offset += size + size = int.from_bytes(key_data[offset:offset + 4], 'big') + d["Private key size"] = str(size) + # offset += 4 + # d["Private key encrypted"] = key_data[offset:offset + size].hex() + return d + + def asymmetric_key(self, key: str, action: str) -> dict: + """Asymmetric key operation + + Args: + key (str): Key type (SIG, DC, AUT) + action (str): Generate or Read + + Return: + Public key information: + RSA (Encrypt or Sign): { + "id": (int) 1, + "Modulus": (int) + "Pub Exp" (int) + } + ECDSA for PSO:CDS and INT-AUT: { + "id": (int) 19, + "OID": (bytes) + } + ECDH for PSO:DEC { + "id": (int) 18, + "OID": (bytes) + } + """ + + if action not in KEY_OPERATIONS: + raise GPGCardExcpetion(ErrorCodes.ERR_INTERNAL, f"Invalid Key operation: {action}") + + op = KEY_OPERATIONS[action] + attributes = None + if key == KeyTypes.KEY_SIG: + attributes = self.data.sig.attribute + b_key = DataObject.DO_SIG_KEY + elif key == KeyTypes.KEY_DEC: + attributes = self.data.dec.attribute + b_key = DataObject.DO_DEC_KEY + elif key == KeyTypes.KEY_AUT: + attributes = self.data.aut.attribute + b_key = DataObject.DO_AUT_KEY + if not attributes or len(attributes) == 0: + raise GPGCardExcpetion(ErrorCodes.ERR_INTERNAL, "Invalid key attribute!") + + if attributes[0] not in set(iter(PubkeyAlgo)): + raise GPGCardExcpetion(ErrorCodes.ERR_INTERNAL, "Invalid key ID in attribute!") + + d = {} + tags = self._asym_key_pair(op, b_key) + + if attributes[0] == PubkeyAlgo.RSA: + d["ID"] = f"RSA-{self._get_int(attributes, offset=1)}" + d["Modulus"] = tags[0x81].hex() + d["Pub Exp"] = f"0x{tags[0x82].hex()}" + else: + if attributes[0] == PubkeyAlgo.ECDSA: + d["ID"] = "ECDSA" + if attributes[0] == PubkeyAlgo.ECDH: + d["ID"] = "ECDH" + if attributes[0] == PubkeyAlgo.EDDSA: + d["ID"] = "EDDSA" + d["oid"] = tags[0x86].hex() + + if attributes[0] == PubkeyAlgo.RSA: + if action == "Generate": + # Set the generation date + self._set_key_date_now(key) + + # Get the generation date + date = self.get_key_date(key) + dt = datetime.strptime(date, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) + kdate = int(dt.timestamp()) + d["Creation date"] = date + + # Compute the fingerprint https://www.rfc-editor.org/rfc/rfc4880#section-12.2 + modulus: bytes = bytes.fromhex(d["Modulus"]) + ksize: int = self._get_int(attributes, offset=1) + size: int = int(ksize / 8) + 0x0D # len(header + tag + pub exp) + + header: bytes = bytes.fromhex(f"99{size:04x}04{kdate:08x}01{ksize:04x}") + footer: bytes = bytes.fromhex("0011010001") + data: bytes = header + modulus + footer + + _hash = sha1() + _hash.update(data) + result: bytes = _hash.digest() + d["Fingerprint"] = result.hex() + if action == "Generate": + self.set_key_fingerprint(key, result) + + return d + + + # =============================================================== + # Internal functions + # =============================================================== + + def _get_key_object(self, key: str) -> KeyInfo: + """Get KeyInfo data class object + + Args: + key (str): Key type (SIG, DC, AUT) + + Return: + KeyInfo Object + """ + + if key == KeyTypes.KEY_SIG: + return self.data.sig + if key == KeyTypes.KEY_AUT: + return self.data.aut + if key == KeyTypes.KEY_DEC: + return self.data.dec + + raise GPGCardExcpetion(ErrorCodes.ERR_INTERNAL, f"Invalid key type {key}!") + + + def _get_data(self, tag: int, bnext: bool = False) -> bytes: + """Send APDU command to GET a specific Data Object + + Args: + tag (int): Data Object tag + + Return: + Data Object bytes + """ + + ins = 0xCC if bnext else 0xCA + apdu = bytes.fromhex(f"00{ins:02x}{tag:04x}00") + resp, _ = self._exchange(apdu) + return resp + + + def _put_data(self, tag: int, data: bytes) -> int: + """Send APDU command to PUT a Data Object value + + Args: + tag (int): Data Object tag + data (bytes): Data Object bytes + + Return: + Status Word + """ + + apdu = bytes.fromhex(f"00DA{tag:04x}") + _, sw = self._exchange(apdu, data) + return sw + + + def _asym_key_pair(self, op: int, key: int) -> dict: + """Asymmetric key pair operation + + Args: + op (int): Operation to execute + key (int): Key type + + Return: + Public key decoded data bytes + """ + + apdu = bytes.fromhex(f"0047{op:02x}0002{key:02x}00") + resp, sw = self._exchange(apdu) + if sw != ErrorCodes.ERR_SUCCESS: + raise GPGCardExcpetion(ErrorCodes.ERR_INTERNAL, "Key operation error!") + tags = self._decode_tlv(resp) + return self._decode_tlv(tags[DataObject.DO_PUB_KEY]) + + + def _conv_date_from_bytes(self, key: str, data: bytes) -> None: + """Convert date from bytes to datetime local dataclass + + Args: + key (str): Key type (SIG, DC, AUT) + data (bytes): Date value + """ + + idate = int.from_bytes(data, "big") + self._get_key_object(key).date = datetime.utcfromtimestamp(idate) + + + def _set_key_date_now(self, key: str) -> None: + """Set Key creation date + + Args: + key (str): Key type (SIG, DC, AUT) + """ + + dt = datetime.utcnow().replace(microsecond=0) + bdate = int(dt.timestamp()).to_bytes(4, "big") + if key == KeyTypes.KEY_SIG: + self.data.sig.date = dt + tag = DataObject.DO_DATES_WR_SIG + elif key == KeyTypes.KEY_AUT: + self.data.aut.date = dt + tag = DataObject.DO_DATES_WR_AUT + elif key == KeyTypes.KEY_DEC: + self.data.dec.date = dt + tag = DataObject.DO_DATES_WR_DEC + self._put_data(tag, bdate) + + + def _decode_tlv(self, tlv: bytes) -> dict: + """Decode TLV fields + + Args: + tlv (bytes): Input data bytes to parse + + Returns: + dict {t: v, t:v, ...} + """ + + tags = {} + while len(tlv): + o = 0 + l = 0 + if (tlv[0] & 0x1F) == 0x1F: + t = self._get_int(tlv) + o = 2 + else: + t = tlv[0] + o = 1 + l = tlv[o] + if l & 0x80 : + if (l & 0x7f) == 1: + l = tlv[o + 1] + o += 2 + if (l & 0x7f) == 2: + l = self._get_int(tlv, offset=o + 1) + o += 3 + else: + o += 1 + v = tlv[o:o + l] + tags[t] = v + tlv = tlv[o + l:] + return tags + + + def _transmit(self, data: bytes, long_resp: bool = False) -> Tuple[bytes, int, int]: + """Transmit data, and get the response + + Args: + data (bytes): APDU to transmit + long_resp (bool): Indicate if long response is expected + + Return: + Response data bytes and the Status Word + """ + + self.add_log("send", data) + sw, resp = self.transport.exchange_raw(data) + sw1 = (sw >> 8) & 0xFF + sw2 = sw & 0xFF + self.add_log("recv", resp, sw) + if sw != ErrorCodes.ERR_SUCCESS and not long_resp: + raise GPGCardExcpetion(sw, "") + return resp, sw1, sw2 + + + def _exchange(self, apdu: bytes, data: bytes = b"") -> Tuple[bytes, int]: + """Exchange APDU, and get the response + + Args: + apdu (bytes): APDU content + data (bytes): Data to transmit + + Return: + Response data bytes and the Status Word + """ + + #send + apdux: bytes = b"" + resp: bytes = b"" + if len(data) > 0: + if len(data) > APDU_MAX_SIZE: + cla: bytes = (apdu[0] | APDU_CHAINING_MODE).to_bytes(1, "big") + m_apdu: bytes = cla + apdu[1:5] + APDU_MAX_SIZE.to_bytes(1, "big") + while len(data) > APDU_MAX_SIZE: + apdux = m_apdu + data[0:APDU_MAX_SIZE] + self._transmit(apdux) + data = data[APDU_MAX_SIZE:] + apdu += len(data).to_bytes(1, "big") + data + + resp, sw1, sw2 = self._transmit(apdu, True) + + #receive + while sw1 == ErrorCodes.ERR_SW1_VALID: + apdux = bytes.fromhex(f"00c00000{sw2:02x}") + resp2, sw1, sw2 = self._transmit(apdux, True) + resp += resp2 + sw = (sw1 << 8) | sw2 + return bytes(resp), sw + + + def _get_int(self, buffer: bytes, size: int = 2, offset: int = 0) -> int: + """Exchange APDU, and get the response + + Args: + buffer (bytes): data content + offset (int): Offset of MSB + + Return: + Converted int + """ + + if size == 2: + return (buffer[offset] << 8) | buffer[offset + 1] + if size == 3: + return (buffer[offset] << 16) | (buffer[offset + 1] << 8) | buffer[offset + 2] + if size == 4: + return (buffer[offset] << 24) | (buffer[offset + 1] << 16) | \ + (buffer[offset + 2] << 8) | buffer[offset + 3] + return 0 diff --git a/pytools/gpgapp/gpgcmd.py b/pytools/gpgapp/gpgcmd.py new file mode 100644 index 0000000..e378106 --- /dev/null +++ b/pytools/gpgapp/gpgcmd.py @@ -0,0 +1,232 @@ +# -*- coding: utf-8 -*- +#***************************************************************************** +# Ledger App OpenPGP. +# (c) 2024 Ledger SAS. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#***************************************************************************** + +from enum import Enum, IntEnum + + +KEY_TEMPLATES = { + "rsa2048" : "010800002001", + "rsa3072" : "010C00002001", + # "rsa4096" : "011000002001", not supported yet + "nistp256": "132A8648CE3D030107", + "ed25519" : "162B06010401DA470F01", + "cv25519" : "122B060104019755010501" +} + + +KEY_OPERATIONS = { + "Export": 0x00, # Read and export a Public Key + "Generate": 0x80, # Generate a new Asymmetric key pair + "Read": 0x81, # Read Public Key +} + + +USER_SALUTATION = { + "Male": "1", + "Female": "2", +} + + +class KeyTypes(str, Enum): + """Key types definition + OpenPGP Application manage four keys for cryptographic operation (PSO) plus two + for secure channel. + The first four keys are defined as follow: + - One asymmetric signature private key (RSA or EC), named 'sig' + - One asymmetric decryption private key (RSA or EC), named 'dec' + - One asymmetric authentication private key (RSA or EC), named 'aut' + - One symmetric decryption private key (AES), named 'sym0' + + The 3 first asymmetric keys can be either randomly generated on-card or + explicitly put from outside. + The fourth is put from outside. + """ + + # Asymmetric Signature Private Key (RSA or EC) + KEY_SIG = "SIG" + # Asymmetric Decryption Private Key (RSA or EC) + KEY_DEC = "DEC" + # Asymmetric Authentication Private Key (RSA or EC) + KEY_AUT = "AUT" + # Symmetric Decryption Key (AES) + + +class PubkeyAlgo(IntEnum): + """ Public-Key Algorithm IDs definition """ + # https://www.rfc-editor.org/rfc/rfc4880#section-9.1 + + # RSA (Encrypt or Sign) + RSA = 1 + # Elliptic Curve Diffie-Hellman + ECDH = 18 + # Elliptic Curve Digital Signature Algorithm + ECDSA = 19 + # Edwards-curve Digital Signature Algorithm + EDDSA = 22 + + +class PassWord(IntEnum): + """ Password type definition """ + + # USER_PIN for only one PSO:CDS command + PW1 = 0x81 + # USER_PIN for several attempts + PW2 = 0x82 + # Admin PIN + PW3 = 0x83 + + +class ErrorCodes: + """ Error codes definition """ + + err_list = { + 0x6285: "Selected file in termination state", + 0x6581: "Memory failure", + 0x6600: "Security-related issues (reserved for UIF in this application)", + 0x6700: "Wrong length (Lc and/or Le)", + 0x6881: "Logical channel not supported", + 0x6882: "Secure messaging not supported", + 0x6883: "Last command of the chain expected", + 0x6884: "Command chaining not supported", + 0x6982: "Security status not satisfied", + 0x6983: "Authentication method blocked", + 0x6984: "Data Invalid", + 0x6985: "Condition of use not satisfied", + 0x6986: "Command not allowed", + 0x6987: "Expected SM data objects missing", + 0x6988: "SM data objects incorrect", + 0x6A80: "Incorrect parameters in the data field", + 0x6A82: "File or application not found", + 0x6A86: "Incorrect P1-P2", + 0x6A88: "Referenced data not found", + 0x6B00: "Wrong parameters P1-P2", + 0x6D00: "Instruction (INS) not supported", + 0x6E00: "Class (CLA) not supported", + 0x6F00: "Unknown Error", + 0x9000: "Success", + } + ERR_SUCCESS = 0x9000 + ERR_SW1_VALID = 0x61 + ERR_INTERNAL = 0 + + +class DataObject(IntEnum): + """ Data Objects definition """ + + # [Read/Write] Slot config + CMD_SLOT_CFG = 0x01F1 + # [Read/Write] Slot selection + CMD_SLOT_CUR = 0x01F2 + # [Read/Write] RSA Exponent + CMD_RSA_EXP = 0x01F8 + + # [Read] Full Application identifier (AID), ISO 7816-4 + DO_AID = 0x4F + # [Read/Write] Login data + DO_LOGIN = 0x5E + # [Read/Write] Uniform resource locator (URL, as defined in RFC 1738) + DO_URL = 0x5F50 + # [Read] Historical bytes, Card service data and Card capabilities + DO_HIST = 0x5F52 + + # [Read/Write] Optional DO for private use + DO_PRIVATE_01 = 0x0101 + DO_PRIVATE_02 = 0x0102 + DO_PRIVATE_03 = 0x0103 + DO_PRIVATE_04 = 0x0104 + + # [Read] Cardholder Related Data + DO_CARDHOLDER_DATA = 0x65 + # [Read/Write] Name according to ISO/IEC 7501-1) + DO_CARD_NAME = 0x5B + # [Read/Write] Language preferences (according to ISO 639) + DO_CARD_LANG = 0x5F2D + # [Read/Write] Salutation (according to ISO 5218) + DO_CARD_SALUTATION = 0x5F35 + + # [Read/Write] Digital signature + DO_SIG_KEY = 0xB6 + # [Read/Write] Confidentiality + DO_DEC_KEY = 0xB8 + # [Read/Write] Authentication + DO_AUT_KEY = 0xA4 + + # [Read] Application Related Data + DO_APP_DATA = 0x6E + # [Read] Extended length information (ISO 7816-4) + DO_EXT_LEN = 0x7F66 + # [Read] Discretionary data objects + DO_DISCRET_DATA = 0x73 + + # [Read] Extended capabilities Flag list + DO_EXT_CAP = 0xC0 + # [Read/Write] Algorithm attributes SIGnature + DO_SIG_ATTR = 0xC1 + # [Read/Write] Algorithm attributes DECryption + DO_DEC_ATTR = 0xC2 + # [Read/Write] Algorithm attributes AUThentication + DO_AUT_ATTR = 0xC3 + # [Read/Write] PW status Bytes + DO_PW_STATUS = 0xC4 + # [Read] Fingerprints (binary, 20 bytes (dec.) each for SIG, DEC, AUT) + DO_FINGERPRINTS = 0xC5 + # [Read] List of CA-Fingerprints (binary, 20 bytes (dec.) each for SIG, DEC, AUT) + DO_CA_FINGERPRINTS = 0xC6 + # [Write] Fingerprint for SIGnature key, format according to RFC 4880 + DO_FINGERPRINT_WR_SIG = 0xC7 + # [Write] Fingerprint for DECryption key, format according to RFC 4880 + DO_FINGERPRINT_WR_DEC = 0xC8 + # [Write] Fingerprint for AUThentication key, format according to RFC 4880 + DO_FINGERPRINT_WR_AUT = 0xC9 + # [Write] CA-Fingerprint for SIGnature key + DO_CA_FINGERPRINT_WR_SIG = 0xCA + # [Write] CA-Fingerprint for DECryption key + DO_CA_FINGERPRINT_WR_DEC = 0xCB + # [Write] CA-Fingerprint for AUThentication key + DO_CA_FINGERPRINT_WR_AUT = 0xCC + # [Read] List of generation dates. 4 bytes, Big Endian each for SIG, DEC, AUT + DO_KEY_DATES = 0xCD + + # [Write] Generation date/time of SIGnature key (Big Endian, according to RFC 4880) + DO_DATES_WR_SIG = 0xCE + # [Write] Generation date/time of DECryption key (Big Endian, according to RFC 4880) + DO_DATES_WR_DEC = 0xCF + # [Write] Generation date/time of AUThentication key (Big Endian, according to RFC 4880) + DO_DATES_WR_AUT = 0xD0 + + # [Read] Security support template + DO_SEC_TEMPL = 0x7A + # [Read] Digital signature counter + DO_SIG_COUNT = 0x93 + + # [Write] Resetting Code + DO_RESET_CODE = 0xD3 + # [Read/Write] User Interaction Flag (UIF) for PSO:CDS + DO_UIF_SIG = 0xD6 + # [Read/Write] User Interaction Flag (UIF) for PSO:DEC + DO_UIF_DEC = 0xD7 + # [Read/Write] User Interaction Flag (UIF) for PSO:AUT + DO_UIF_AUT = 0xD8 + # [Read/Write] Cardholder certificate (each for AUT, DEC and SIG) + DO_CERT = 0x7F21 + + # [Read/Write] Asymmetric Key Pair + DO_PUB_KEY = 0x7F49 + + # [Read] General Feature management + DO_GEN_FEATURES = 0x7F74 diff --git a/pytools/gpgcard/__init__.py b/pytools/gpgcard/__init__.py deleted file mode 100644 index 48b1f69..0000000 --- a/pytools/gpgcard/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2017 Cedric Mesnil , Ledger SAS -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - diff --git a/pytools/gpgcard/backup.py b/pytools/gpgcard/backup.py deleted file mode 100644 index 4abcf4e..0000000 --- a/pytools/gpgcard/backup.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2017 Cedric Mesnil , Ledger SAS -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from gpgcard import GPGCard - -gpgcard = GPGCard() -gpgcard.connect("pcsc:Ledger") -gpgcard.get_all() - - -gpgcard.verify_pin(0x81, "123456") -gpgcard.verify_pin(0x83, "12345678") -gpgcard.backup("backup_card.pickle") diff --git a/pytools/gpgcard/gpgcard.py b/pytools/gpgcard/gpgcard.py deleted file mode 100644 index 59dd999..0000000 --- a/pytools/gpgcard/gpgcard.py +++ /dev/null @@ -1,850 +0,0 @@ -# Copyright 2017 Cedric Mesnil , Ledger SAS -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - - -try: - from ledgerblue.comm import getDongle - from ledgerblue.commException import CommException -except : - pass -import binascii - -from smartcard.System import readers -import sys -import datetime -import pickle - -# decode level 0 of tlv|tlv|tlv -# return dico {t: v, t:v, ...} -#tlv: hexstring - -def decode_tlv(tlv) : - tags = {} - while len(tlv) : - o = 0 - l = 0 - if (tlv[0] & 0x1F) == 0x1F: - t = (tlv[0]<<8)|tlv[1] - o = 2 - else: - t = tlv[0] - o = 1 - l = tlv[o] - if l & 0x80 : - if (l&0x7f) == 1: - l = tlv[o+1] - o += 2 - if (l&0x7f) == 2: - l = (tlv[o+1]<<8)|tlv[o+2] - o += 3 - else: - o += 1 - v = tlv[o:o+l] - tags[t] = v - tlv = tlv[o+l:] - return tags - -class GPGCardExcpetion(Exception): - pass - -class GPGCard() : - def __init__(self): - self.reset() - self.log = False - - def reset(self): - #token info - self.AID = b'' - self.aid = b'' - self.ext_length = b'' - self.ext_capabilities = b'' - self.histo_bytes = b'' - self.PW_status = b'' - - #user info - self.login = b'' - self.url = b'' - self.name = b'' - self.sex = b'' - self.lang = b'' - - #keys info - self.cardholder_cert = b'' - self.sig_attribute = b'' - self.dec_attribute = b'' - self.aut_attribute = b'' - self.sig_fingerprints = b'' - self.dec_fingerprints = b'' - self.aut_fingerprints = b'' - self.sig_CA_fingerprints = b'' - self.dec_CA_fingerprints = b'' - self.aut_CA_fingerprints = b'' - self.sig_date = b'' - self.dec_date = b'' - self.aut_date = b'' - self.cert_aut = b'' - self.cert_dec = b'' - self.cert_sig = b'' - self.UIF_SIG = b'' - self.UIF_DEC = b'' - self.UIF_AUT = b'' - self.digital_counter = b'' - - #private info - self.private_01 = b'' - self.private_02 = b'' - self.private_03 = b'' - - #keys - self.sig_key = b'' - self.dec_key = b'' - self.aut_key = b'' - - - def connect(self, device): - self.token = None - if device.startswith("ledger:"): - self.token = getDongle(True) - self.exchange = self._exchange_ledger - self.disconnect = self._disconnect_ledger - elif device.startswith("pcsc:"): - allreaders = readers() - for r in allreaders: - rname = str(r) - #print('try: %s : %s'%(rname,device[5:])) - if rname.startswith(device[5:]): - r.createConnection() - self.token = r - self.connection = r.createConnection() - self.connection.connect() - self.exchange = self._exchange_pcsc - self.disconnect = self._disconnect_pcsc - else: - #print("No") - pass - if not self.token: - print("No token") - - - - - ### APDU interface ### - def _exchange_ledger(self,cmd,data=None, sw=0x9000): - resp = b'' - cond = True - while cond: - try: - resp = resp + self.token.exchange(cmd,300) - sw = 0x9000 - cond = False - except CommException as e: - if (e.data) : - resp = resp + e.data - sw = e.sw - if (sw&0xFF00) == 0x6100 : - cmd = binascii.unhexlify("00C00000%.02x"%(sw&0xFF)) - else: - cond = False - return resp,sw - - def log_apdu(self,l): - self.log = l - - def alog(self, m,dt,sw=0): - if self.log: - print("%s %.04x %s"%(m,sw,''.join(["%.02x"%b for b in dt]))) - - def _exchange_pcsc(self,apdu, data=None, sw_expected=0x9000, sw_mask=0xFFFF): - if data: - data = [x for x in data] - apdu = [x for x in apdu] - #send - if data: - while len(data) > 0xFE: - apdux = apdu[0:5]+[0xfe]+data[0:0xFE] - apdux[0] |= 0x10 - self.alog('send', apdux) - resp, sw1, sw2 = self.connection.transmit(apdux) - sw = (sw1<<8)|sw2 - self.alog('recv',resp,sw) - if sw != 0x9000: - return resp,sw - data = data[0xFE:] - apdu = apdu+[len(data)]+data - self.alog('send', apdu) - resp, sw1, sw2 = self.connection.transmit(apdu) - sw = (sw1<<8)|sw2 - self.alog('recv', resp, sw) - - #receive - while sw1==0x61: - apdu = binascii.unhexlify(b"00c00000%.02x"%sw2) - apdu = [x for x in apdu] - self.alog('send', apdu) - resp2, sw1, sw2 = self.connection.transmit(apdu) - sw = (sw1<<8)|sw2 - self.alog('recv', resp2, sw) - resp = resp + resp2 - resp = bytes(resp) - sw = (sw1<<8)|sw2 - if sw&sw_mask == sw_expected: - return resp,sw - raise GPGCardExcpetion(binascii.hexlify(resp), "%.04x"%sw) - - def _disconnect_ledger(self): - return self.token.close() - - def _disconnect_pcsc(self): - r = self.connection.disconnect() - #self.connection.releaseContext() - return r - - def select(self): - apdu = binascii.unhexlify(b"00A4040006D27600012401") - return self.exchange(apdu) - - def activate(self): - apdu = binascii.unhexlify(b"00440000") - return self.exchange(apdu) - - def terminate(self): - apdu = binascii.unhexlify(b"00E60000") - return self.exchange(apdu) - - def get_log(self): - apdu = binascii.unhexlify(b"00040000") - return self.exchange(apdu) - - def get_data(self,tag): - apdu = binascii.unhexlify(b"00CA%.04x00"%tag) - return self.exchange(apdu) - - def put_data(self,tag,value): - return self.exchange(binascii.unhexlify(b"00DA%.04x"%tag), value) - - def verify(self,id,value, pinpad=False): - if pinpad: - apdu = binascii.unhexlify(b"EF2000%.02x00"%id) - else: - apdu = binascii.unhexlify(b"002000%.02x%.02x"%(id,len(value)))+value - return self.exchange(apdu) - - def change_reference_data(self,id,value,new_value): - lc = len(value)+len(new_value) - apdu = binascii.unhexlify(b"002400%.02x%.02x"%(id,lc))+value+new_value - return self.exchange(apdu) - - def reset_retry_counter(self,RC,new_value): - if len(RC)==0: - p1 = 2 - else: - p1 = 0 - lc = len(RC)+len(new_value) - apdu = binascii.unhexlify(b"002C%02x81%.02x"%(p1,lc))+RC+new_value - return self.exchange(apdu) - - def generate_asym_key_pair(self, mode, key): - apdu = binascii.unhexlify(b"0047%02x0002%.04x"%(mode,key)) - return self.exchange(apdu) - - ### API interfaces ### - def get_all(self, with_key=False): - self.reset() - - self.slot,sw = self.get_data(0x01F2) - self.AID,sw = self.get_data(0x4f) - self.login ,sw = self.get_data(0x5e) - self.url,sw = self.get_data(0x5f50) - self.histo_bytes,sw = self.get_data(0x5f52) - - cardholder,sw = self.get_data(0x65) - tags = decode_tlv(cardholder) - if 0x5b in tags: - self.name = tags[0x5b] - if 0x5f35 in tags: - self.sex = tags[0x5f35] - if 0x5f35 in tags: - self.lang = tags[0x5f2d] - - application_data,sw = self.get_data(0x6E) - tags = decode_tlv(application_data) - if 0x7f66 in tags: - self.ext_length = tags[0x7f66] - if 0x73 in tags: - dicretionary_data = tags[0x73] - tags = decode_tlv(dicretionary_data) - if 0xc0 in tags: - self.ext_capabilities = tags[0xC0] - - if 0xc4 in tags: - self.PW_status = tags[0xC4] - - if 0xC1 in tags: - self.sig_attribute = tags[0xC1] - if 0xC2 in tags: - self.dec_attribute = tags[0xC2] - if 0xC3 in tags: - self.aut_attribute = tags[0xC3] - if 0xC5 in tags: - fingerprints = tags[0xC5] - self.sig_fingerprints = fingerprints[0:20] - self.dec_fingerprints = fingerprints[20:40] - self.aut_fingerprints = fingerprints[40:60] - if 0xC6 in tags: - fingerprints = tags[0xC6] - self.sig_CA_fingerprints = fingerprints[0:20] - self.dec_CA_fingerprints = fingerprints[20:40] - self.aut_CA_fingerprints = fingerprints[40:60] - if 0xcd in tags: - dates = tags[0xCD] - self.sig_date = dates[0:4] - self.dec_date = dates[4:8] - self.aut_date = dates[8:12] - - self.cardholder_cert = self.get_data(0x7f21) - - self.UIF_SIG,sw = self.get_data(0xD6) - self.UIF_DEC,sw = self.get_data(0xD7) - self.UIF_AUT,sw = self.get_data(0xD8) - - sec_template,sw = self.get_data(0x7A) - tags = decode_tlv(sec_template) - if 0x93 in tags: - self.digital_counter = tags[0x93] - - self.private_01,sw = self.get_data(0x0101) - self.private_02,sw = self.get_data(0x0102) - self.private_03,sw = self.get_data(0x0103) - self.private_04,sw = self.get_data(0x0104) - - if with_key: - self.sig_key,sw = self.get_data(0x00B6) - self.dec_key,sw = self.get_data(0x00B8) - self.aut_key,sw = self.get_data(0x00A4) - - - return True - - def set_all(self): - - self.put_data(0x4f, self.AID[10:14]) - self.put_data(0x0101, self.private_01) - self.put_data(0x0102, self.private_02) - self.put_data(0x0103, self.private_03) - self.put_data(0x0104, self.private_04) - - self.put_data(0x5b, self.name) - self.put_data(0x5e, self.login) - self.put_data(0x5f2d, self.lang) - self.put_data(0x5f35, self.sex) - self.put_data(0x5f50, self.url) - - self.put_data(0xc1, self.sig_attribute) - self.put_data(0xc2, self.dec_attribute) - self.put_data(0xc3, self.aut_attribute) - - self.put_data(0xc4, self.PW_status) - - self.put_data(0xc7, self.sig_fingerprints) - self.put_data(0xc8, self.dec_fingerprints) - self.put_data(0xc9, self.aut_fingerprints) - self.put_data(0xca, self.sig_CA_fingerprints) - self.put_data(0xcb, self.dec_CA_fingerprints) - self.put_data(0xcc, self.aut_CA_fingerprints) - self.put_data(0xce, self.sig_date) - self.put_data(0xcf, self.dec_date) - self.put_data(0xd0, self.aut_date) - #self.put_data(0x7f21, self.cardholder_cert) - - self.put_data(0xd6, self.UIF_SIG) - self.put_data(0xd7, self.UIF_DEC) - self.put_data(0xd8, self.UIF_AUT) - - if len(self.sig_key): - self.put_data(0x00B6, self.sig_key) - if len(self.dec_key): - self.put_data(0x00B8, self.dec_key) - if len(self.aut_key): - self.put_data(0x00A4, self.aut_key) - return True - - - def _backup_file_name(self,file_name): - return file_name #file_name+"_slot%d"%(self.slot[0]+1)+".pickle" - - def backup(self, file_name, with_key=False): - self.get_all(with_key) - file_name = self._backup_file_name(file_name) - f = open(file_name,mode='w+b') - pickle.dump( - (self.AID, - self.private_01, self.private_02, self.private_03, self.private_04, - self.name, self.login, self.sex, self.url, - self.sig_attribute, self.dec_attribute, self.aut_attribute, - self.PW_status, - self.sig_fingerprints, self.dec_fingerprints, self.aut_fingerprints, - self.sig_CA_fingerprints, self.dec_CA_fingerprints, self.aut_CA_fingerprints, - self.sig_date, self.dec_date, self.aut_date, - self.cardholder_cert, - self.UIF_SIG, self.UIF_DEC, self.UIF_AUT, - self.sig_key, self.dec_key, self.aut_key), - f, 2) - return True - - - def restore(self, file_name): - file_name = self._backup_file_name(file_name) - f = open(file_name,mode='r+b') - (self.AID, - self.private_01, self.private_02, self.private_03, self.private_04, - self.name, self.login, self.sex, self.url, - self.sig_attribute, self.dec_attribute, self.aut_attribute, - self.PW_status, - self.sig_fingerprints, self.dec_fingerprints, self.aut_fingerprints, - self.sig_CA_fingerprints, self.dec_CA_fingerprints, self.aut_CA_fingerprints, - self.sig_date, self.dec_date, self.aut_date, - self.cardholder_cert, - self.UIF_SIG, self.UIF_DEC, self.UIF_AUT, - self.sig_key, self.dec_key, self.aut_key) = pickle.load(f) - self.set_all() - return True - - def seed_key(self): - apdu = binascii.unhexlify(b"0047800102B600") - self.exchange(apdu) - apdu = binascii.unhexlify(b"0047800102B800") - self.exchange(apdu) - apdu = binascii.unhexlify(b"0047800102A400") - self.exchange(apdu) - - - def decode_AID(self): - return { - 'AID': ('AID' , "%x"%int.from_bytes(self.AID,'big')), - 'RID': ('RID' , "%x"%int.from_bytes(self.AID[0:5],'big')), - 'APP': ('application' , "%.02x"%self.AID[5]), - 'VER': ('version' , "%.02x.%.02x"%(self.AID[6], self.AID[7])), - 'MAN': ('manufacturer' , "%x"%int.from_bytes(self.AID[8:10],'big')), - 'SER': ('serial' , "%x"%int.from_bytes(self.AID[10:14],'big')) - } - - def decode_histo(self): - return { - 'HIST': ('historical bytes', binascii.hexlify(self.histo_bytes)) - } - - def decode_extlength(self): - if self.ext_length: - return { - 'CMD': ('Max command length' , "%d" %((self.ext_length[2]<<8)|self.ext_length[3])), - 'RESP':( 'Max response length' , "%d" %((self.ext_length[6]<<8)|self.ext_length[7])) - } - else: - return { - 'CMD': ('Max command length' , "unspecified"), - 'RESP':( 'Max response length' ,"unspecified"), - } - - def decode_capabilities(self): - d = {} - b1 = self.ext_capabilities[0] - if b1&0x80 : - if self.ext_capabilities[1] == 1: - d['SM'] = ('Secure Messaging', "yes: 128 bits") - elif self.ext_capabilities[1] == 2: - d['SM'] = ('Secure Messaging', "yes: 256 bits") - else: - d['SM'] = ('Secure Messaging', "yes: ?? bits") - else: - d['SM'] = ('Secure Messaging', "no") - - if b1&0x40 : - d['CHAL'] = ('Get Challenge', "yes") - else: - d['CHAL'] = ('Get Challenge', "no") - - if b1&0x20 : - d['KEY'] = ('Key import', "yes") - else: - d['KEY'] = ('Key import', "no") - - if b1&0x10 : - d['PWS'] = ('PW status changeable', "yes") - else: - d['PWS'] = ('PW status changeable', "no") - - if b1&0x08 : - d['PDO'] = ('Private DOs', "yes") - else: - d['PDO'] = ('Private DOs', "no") - - if b1&0x04 : - d['ATTR'] = ('Algo attributes changeable', "yes") - else: - dd['ATTR'] = ('Algo attributes changeable', "no") - - if b1&0x02 : - d['PSO'] = ('PSO:DEC support AES', "yes") - else: - d['PSO'] = ('PSO:DEC support AES', "no") - - - d['CHAL_MAX'] = ('Max GET_CHALLENGE length', - "%d"% ((self.ext_capabilities[2]<<8)|self.ext_capabilities[3])) - d['CERT_MAX'] = ('Max Cert length', - "%d"% ((self.ext_capabilities[4]<<8)|self.ext_capabilities[5])) - d['PDO_MAX'] = ('Max special DO length', - "%d"% ((self.ext_capabilities[6]<<8)|self.ext_capabilities[7])) - if self.ext_capabilities[8] : - d['PIN2'] = ('PIN 2 format supported', "yes") - else: - d['PIN2'] = ('PIN 2 format supported',"no") - - return d - - def decode_pws(self): - d = {} - if self.PW_status[0]==0: - d['ONCE'] = ('PW1 valid for several CDS', 'yes') - elif self.PW_status[0]==1: - d['ONCE'] = ('PW1 valid for several CDS', 'no') - else: - d['ONCE'] = ('PW1 valid for several CDS', 'unknown (%d)'%self.PW_status[0]) - - if self.PW_status[1] & 0x80: - fmt = "Format-2" - else: - fmt = "UTF-8" - pwlen = self.PW_status[1] & 0x7f - d['PW1'] = ("PW1 format", "%s : %d bytes"%(fmt,pwlen)) - - if self.PW_status[2] & 0x80: - fmt = "Format-2" - else: - fmt = "UTF-8" - pwlen = self.PW_status[2] & 0x7f - d['RC'] = ("RC format", "%s : %d bytes"%(fmt,pwlen)) - - if self.PW_status[3] & 0x80: - fmt = "Format-2" - else: - fmt = "UTF-8" - pwlen = self.PW_status[3] & 0x7f - d['PW3'] = ("PW3 format", "%s : %d bytes"%(fmt,pwlen)) - - d['CNT1'] = ('PW1 counter', "%x"%self.PW_status[4]) - d['CNTRC'] =('RC counter', "%x"%self.PW_status[5]) - d['CNT3'] = ('PW3 counter', "%x"%self.PW_status[6]) - - return d - - #slot - def select_slot(self, slot): - """ Args: - slot (int) : slot id (1 to MAX) to select - """ - self.put_data( 0x01F2, (slot-1).to_bytes(1,'big')) - - #USER Info - def set_serial(self, ser): - ser=binascii.unhexlify(ser) - - self.AID = self.AID[0:10]+ser - self.put_data(0x4f, self.AID[10:14]) - - # internals are always store as byres, get/set automatically convert from/to - def set_name(self,name): - """ Args: - name (str) : utf8 string - """ - self.name = name.encode('utf-8') - self.put_data( 0x5b, self.name) - - def get_name(self): - return self.name.decode('utf-8') - - def set_login(self,login): - """ Args: - login (str) : utf8 string - """ - self.login = login.encode('utf-8') - self.put_data( 0x5e, self.login) - - def get_login(self): - return self.login.decode('utf-8') - - def set_url(self,url): - """ Args: - url (str) : utf8 string - """ - self.url = url.encode('utf-8') - self.put_data(0x5f50, self.url) - - def get_url(self): - return self.url.decode('utf-8') - - def set_sex(self,sex): - """ Args: - sex (str) : ascii string ('9', '1', '2') - """ - self.sex = sex.encode('utf-8') - self.put_data(0x5f35, self.sex) - - def get_sex(self): - return self.sex.decode('utf-8') - - def set_lang(self,lang): - """ Args: - lang (str) : utf8 string - """ - self.lang = lang.encode('utf-8') - self.put_data(0x5f2d, self.lang) - - def get_lang(self): - return self.lang.decode('utf-8') - - - #PINs - def verify_pin(self,id,value, pinpad=False): - """ Args: - id (int) : 0x81, 0x82, ox83 - value (str) : ascii string - """ - value = value.encode('ascii') - resp,sw = self.verify(id,value, pinpad) - return sw == 0x9000 - - def change_pin(self, id, value,new_value): - """ Args: - id (int) : 0x81, ox83 - value (str) : ascii string - """ - value = value.encode('ascii') - new_value = new_value.encode('ascii') - resp,sw = self.change_reference_data(id,value,new_value) - return sw == 0x9000 - - def change_RC(self,new_value): - """ Args: - id (int) : 0x81, ox83 - value (str) : ascii string - """ - new_value = new_value.encode('ascii') - resp,sw = self.put_data(0xd3,new_value) - return sw == 0x9000 - - def reset_PW1(self,RC,new_value): - """ Args: - id (int) : 0x81, ox83 - value (str) : ascii string - """ - new_value = new_value.encode('ascii') - RC = RC.encode('ascii') - resp,sw = self.reset_retry_counter(RC,new_value) - return sw == 0x9000 - - #keys - def get_key_uif(self,key): - """ - Returns: (int) 0,1,2,256(not supported) - """ - uif = None - if key=='sig': - uif = self.UIF_SIG - if key=='aut': - uif = self.UIF_DEC - if key=='dec': - uif = self.UIF_AUT - - if uif: - uif = int.from_bytes(uif,'big') - else: - uif = 256 - return uif - - def get_key_fingerprints(self, key): - """ - Returns: (str) fingerprints hex string - """ - fprints = None - if key=='sig': - fprints = self.sig_fingerprints - if key=='aut': - fprints = self.aut_fingerprints - if key=='dec': - fprints = self.dec_fingerprints - if fprints: - fprints = binascii.hexlify(fprints) - else: - fprint = '-' - return fprints.decode('ascii') - - def set_key_fingerprints(self, key, fprints): - - fprints = binascii.unhexlify(fprints) - if key=='sig': - self.sig_fingerprints = fprints - self.put_data(0xc7, fprints) - if key=='aut': - self.aut_fingerprints = fprints - self.put_data(0xc9, fprints) - if key=='dec': - self.dec_fingerprints = fprints - self.put_data(0xc8, fprints) - - - def get_key_CA_fingerprints(self, key): - """ - Returns: (str) CA fingerprints hex string - """ - fprints = None - if key=='sig': - fprints = self.sig_CA_fingerprints - if key=='aut': - fprints = self.aut_CA_fingerprints - if key=='dec': - fprints = self.dec_CA_fingerprints - if fprints: - fprints = binascii.hexlify(fprints) - else: - fprint = b'-' - return fprints.decode('ascii') - - def get_key_date(self, key): - """ - Returns: (str) date - """ - fdate = None - if key=='sig': - fdate = self.sig_date - if key=='aut': - fdate = self.aut_date - if key=='dec': - fdate = self.dec_date - if fdate: - fdate = datetime.datetime.fromtimestamp(int.from_bytes(fdate,'big')).isoformat(' ') - else: - fprint = b'-'.decode('ascii') - return fdate - - - def get_key_attribute(self, key): - """ - for RSA: - {'id': (int) 0x01, - 'nsize': (int) - 'esize' (int) - 'format': (int) - } - - for ECC: - - {'id': (int) 0x18|0x19, - 'OID': (bytes) - } - - Args: - key: (str) 'sig' | 'aut', 'dec' - """ - - attributes = None - if key=='sig': - attributes = self.sig_attribute - if key=='dec': - attributes = self.dec_attribute - if key=='aut': - attributes = self.aut_attribute - if not attributes: - return None - if len(attributes) == 0: - return None - - if attributes[0] == 0x01: - return { - 'id': 1, - 'nsize': (attributes[1]<<8) | attributes[2], - 'esize': (attributes[3]<<8) | attributes[4], - 'format': attributes[5] - } - if attributes[0] == 18 or attributes[0] == 19 : - return { - 'id': attributes[0] , - 'oid': attributes[1:] - } - print ("NONE: %s"%binascii.hexlify(attributes)) - return None - - def set_template(self, sig, dec, aut): - """ - See get_template - """ - if (sig): - self.put_data(0x00C1, binascii.unhexlify(sig)) - if dec: - self.put_data(0x00C2, binascii.unhexlify(dec)) - if aut: - self.put_data(0x00C3, binascii.unhexlify(aut)) - pass - - - def asymmetric_key(self, op, key) : - """ - Args: - op: (int) 0x80 generate, 0x81 read pub, 0x82 read pub&priv - key: (str) 'sig' | 'aut', 'dec' - - Returns: - for RSA: - {'id': (int) 0x01, - 'n': (bytes) - 'e' (bytes) - 'd': (bytes) - } - - for ECC: - - {'id': (int) 0x18|0x19, - 'OID': (bytes) - } - - """ - attributes = None - if key=='sig': - attributes = self.sig_attribute - key = 0xb600 - if key=='dec': - attributes = self.dec_attribute - key = 0xb800 - if key=='aut': - attributes = self.aut_attribute - key = 0xa400 - if not attributes: - return None - if len(attributes) == 0: - return None - resp,sw = self.generate_asym_key_pair(op,key) - if sw != 0x9000: - return None - resp,sw = self.generate_asym_key_pair(0x82,key) - tags = decode_tlv(resp) - tags = decode_tlv(tags[0x7f49]) - if attributes[0] == 0x01: - return { - 'id': 1, - 'n': tags[0x81], - 'e': tags[0x82], - 'd': tags[0x98], - } - diff --git a/pytools/gpgcard/gpgcli.py b/pytools/gpgcard/gpgcli.py deleted file mode 100644 index 29465bb..0000000 --- a/pytools/gpgcard/gpgcli.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright 2018 Cedric Mesnil , Ledger SAS -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import sys -import argparse -import binascii - -from .gpgcard import GPGCard - -def get_argparser(): - parser = argparse.ArgumentParser(epilog=""" -reset, backup, restore are always executed in THIS order. - -Template identifiers are ed2559, cv25519, rsa2048, rsa3072, rsa4096. - """ -) - parser.add_argument('--adm-pin', metavar='PIN', help='Administrative PIN, if pinpad not used') - parser.add_argument('--backup', help='Perfom a full backup except the key', action='store_true') - parser.add_argument('--backup-keys', help='Perfom keys encrypted backup', action='store_true') - parser.add_argument('--file', help='basckup/restore file', type=str, default='gpg_backup') - parser.add_argument('--pinpad', help='PIN validation will be deledated to pinpad', action='store_true') - parser.add_argument('--reader', help='PCSC reader', type=str, default='pcsc:Ledger') - parser.add_argument('--reset', help='Reset the application. All data are erased', action='store_true') - parser.add_argument('--restore', help='Perfom a full restore except the key', action='store_true') - parser.add_argument('--set-serial', metavar='SERIAL', help='set the four serial bytes') - parser.add_argument('--set-templates', metavar='SIG:DEC:AUT', help='sig:dec:aut templates identifier') - parser.add_argument('--set-fingerprints', metavar='SIG:DEC:AUT', help='sig:dec:aut fingerprints, 20 bytes each in hexa') - parser.add_argument('--seed-key', help='Regenerate all keys, based on seed mode', action='store_true') - parser.add_argument('--slot', metavar='SLOT', help='slot to backup', type=int, default=1) - parser.add_argument('--user-pin', metavar='PIN', help='User PIN, if pinpad not used'), - parser.add_argument('--apdu', help='Log APDU exchange', action='store_true') - return parser - -def banner(): - print( -""" -GPG Ledger Admin Tool v0.1. -Copyright 2018 Cedric Mesnil , Ledger SAS - -""" - ) - - -def error(msg) : - print("Error: ") - print(" "+msg) - sys.exit() - -banner() - -args = get_argparser().parse_args() - -if args.backup and args.restore: - error('Only one backup or restore must be specified') - - -if not args.pinpad: - if not args.adm_pin or not args.user_pin: - error('If pinpad is not use, userpin and admpin must be provided') - - -try: - - print("Connect to card %s..."%args.reader, end='', flush=True) - gpgcard = GPGCard() - if args.apdu: - gpgcard.log_apdu(args.apdu) - gpgcard.connect(args.reader) - print("OK") - - print("Verfify PINs...", end='', flush=True) - if args.pinpad: - if not gpgcard.verify_pin(0x82, "", True) or not gpgcard.verify_pin(0x83, "", True): - error("PIN not verified") - else: - if not gpgcard.verify_pin(0x82, args.user_pin) or not gpgcard.verify_pin(0x83, args.adm_pin): - error("PIN not verified") - print("OK") - - print("Select slot %d..."%args.slot, end='', flush=True) - gpgcard.select_slot(args.slot) - print("OK") - - if args.reset: - print("Reset application...", end='', flush=True) - gpgcard.terminate() - gpgcard.activate() - print("OK") - - print("Get card info...", end='', flush=True) - gpgcard.get_all() - print("OK", flush=True) - - if args.backup: - print("Backup application...", end='', flush=True) - if not gpgcard.backup(args.file, args.backup_keys): - error("NOK") - print("OK") - - if args.restore: - print("Restore application...", end='', flush=True) - if not gpgcard.restore(args.file): - error("NOK") - print("OK", flush=True) - - if args.set_templates: - print("Set template...", end='', flush=True) - templates= { - 'rsa2048' : "010800002001", - 'rsa3072' : "010C00002001", - 'rsa4096' : "011000002001", - 'nistp256' : "132A8648CE3D030107", - 'ed255519' : "162B06010401DA470F01", - 'cv25519' : "122B060104019755010501" - } - sig,dec,aut = args.set_templates.split(":") - gpgcard.set_template(templates[sig],templates[dec],templates[aut]) - print("OK", flush=True) - - if args.seed_key: - print("Seed Key...", end='', flush=True) - gpgcard.seed_key(); - print("OK", flush=True) - - if args.set_fingerprints: - print("Set fingerprints...", end='', flush=True) - sig,dec,aut = args.set_fingerprints.split(":") - if sig: - gpgcard.set_key_fingerprints("sig", sig) - if dec: - gpgcard.set_key_fingerprints("dec", dec) - if aut: - gpgcard.set_key_fingerprints("aut", aut) - print("OK", flush=True) - - if args.set_serial: - print("Set serial...", end='', flush=True) - if len(args.set_serial) != 8 : - error('Serial must be a 4 bytes hexa string value (8 characters)') - serial = binascii.unhexlify(args.set_serial) - gpgcard.set_serial(args.set_serial) - print("OK", flush=True) -except Exception as e: - error(str(e)) diff --git a/pytools/gpgcard/restore.py b/pytools/gpgcard/restore.py deleted file mode 100644 index d75b565..0000000 --- a/pytools/gpgcard/restore.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2017 Cedric Mesnil , Ledger SAS -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from gpgcard import GPGCard - -gpgcard = GPGCard() -gpgcard.connect("pcsc:Ledger") -gpgcard.get_all() - - -gpgcard.verify_pin(0x81, "123456") -gpgcard.verify_pin(0x83, "12345678") -gpgcard.restore("backup_card.pickle", True) diff --git a/pytools/gpgcli.py b/pytools/gpgcli.py new file mode 100755 index 0000000..4d85839 --- /dev/null +++ b/pytools/gpgcli.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +#***************************************************************************** +# Ledger App OpenPGP. +# (c) 2024 Ledger SAS. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#***************************************************************************** + +import sys +from pathlib import Path +from argparse import ArgumentParser, RawTextHelpFormatter, Namespace +from gpgapp.gpgcard import GPGCard, GPGCardExcpetion +from gpgapp.gpgcmd import ErrorCodes, KeyTypes, PassWord +from gpgapp.gpgcmd import KEY_OPERATIONS, KEY_TEMPLATES, USER_SALUTATION + + +# =============================================================================== +# Parse command line options +# =============================================================================== +def get_argparser() -> Namespace: + """Parse the commandline options""" + + parser = ArgumentParser( + description="Manage OpenPGP App on Ledger device", + formatter_class=RawTextHelpFormatter + ) + parser.add_argument("--info", action="store_true", + help="Get and display card information") + parser.add_argument("--reader", type=str, default="Ledger", + help="PCSC reader name (default is '%(default)s') or 'speculos'") + + parser.add_argument("--apdu", action="store_true", help="Log APDU exchange") + parser.add_argument("--slot", type=int, choices=range(1, 4), help="Select slot (1 to 3)") + parser.add_argument("--reset", action="store_true", + help="Reset the application (all data will be erased)") + + parser.add_argument("--pinpad", action="store_true", + help="PIN validation will be delegated to pinpad") + parser.add_argument("--adm-pin", metavar="PIN", + help="Admin PIN (if pinpad not used)", required="--pinpad" not in sys.argv) + parser.add_argument("--user-pin", metavar="PIN", + help="User PIN (if pinpad not used)", required="--pinpad" not in sys.argv) + parser.add_argument("--new-user-pin", metavar="PIN", + help="Change User PIN") + parser.add_argument("--new-adm-pin", metavar="PIN", + help="Change Admin PIN") + group = parser.add_mutually_exclusive_group() + group.add_argument("--reset-code", help="Update 'PW1 Resetting Code'") + group.add_argument("--reset-pw1", help="Reset the User PIN") + + parser.add_argument("--serial", help="Update the 'serial' data (4 bytes)") + parser.add_argument("--salutation",choices=list(USER_SALUTATION), help="Update 'salutation' data") + parser.add_argument("--name", help="Update 'name' data") + parser.add_argument("--url", help="Update 'url' data") + parser.add_argument("--login",help="Update 'login' data") + parser.add_argument("--lang", help="Update 'lang' data") + + parser.add_argument("--key-type", type=KeyTypes, choices=[k.value for k in KeyTypes], + help="Select key type SIG:DEC:AUT (default is all)") + + parser.add_argument("--key-action",choices=list(KEY_OPERATIONS), + help="Generate key pair or Read public key") + parser.add_argument("--set-fingerprints", metavar="SIG:DEC:AUT", + help="Set fingerprints for selected 'key-type'\n" + \ + "If 'key-type' is not specified, set for all keys (SIG:DEC:AUT)\n" + \ + "Each fingerprint is 20 hex bytes long") + parser.add_argument("--set-templates", metavar="SIG:DEC:AUT", + help="Set template identifier for selected 'key-type'\n" + \ + "If 'key-type' is not specified, set for all keys (SIG:DEC:AUT)\n" + \ + f"Valid values are {', '.join(list(KEY_TEMPLATES))}") + parser.add_argument("--seed-key", action="store_true", + help="Regenerate all keys, based on seed mode") + + parser.add_argument("--file", type=str, default="pubkey", + help="Public Key export file (default is '%(default)s')") + + return parser.parse_args() + + +# =============================================================================== +# Error handler +# =============================================================================== +def error(code: int, msg: str) -> None: + """Print error message and exit + + Args: + msg (str): Message to display + """ + + scode = f" {code:x}" if code else "" + if not msg: + if code in ErrorCodes.err_list: + msg = ErrorCodes.err_list[code] + print(f"\n### Error{scode}: {msg}\n") + sys.exit() + + +# =============================================================================== +# PIN codes verification +# =============================================================================== +def verify_pins(gpgcard: GPGCard, user_pin: str, adm_pin: str, pinpad: bool) -> None: + """Verify the pin codes + + Args: + gpgcard (GPGCard): smartcard object + user_pin (str): User pin code + adm_pin (str): Admin pin code + pinpad (bool): Indicates to use pinpad + """ + + print("Verify PINs...") + if not gpgcard.verify_pin(PassWord.PW1, user_pin, pinpad) or \ + not gpgcard.verify_pin(PassWord.PW2, user_pin, pinpad) or \ + not gpgcard.verify_pin(PassWord.PW3, adm_pin, pinpad): + error(ErrorCodes.ERR_INTERNAL, "PIN not verified") + + +# =============================================================================== +# Reset the Application +# =============================================================================== +def reset_app(gpgcard: GPGCard) -> None: + """Reset Application and re-init + + Args: + gpgcard (GPGCard): smartcard object + """ + + print("Reset application...") + gpgcard.terminate() + gpgcard.activate() + print(" -> OK") + + +# =============================================================================== +# Retrieve the OpenPGP Card information +# =============================================================================== +def get_info(gpgcard: GPGCard, display: bool=True) -> None: + """Retrieve and display Card information + + Args: + gpgcard (GPGCard): smartcard object + display (bool): Print Card info + """ + + print("Get card info...") + gpgcard.get_all() + + if not display: + return + + line = "=" * 15 + print(f"{line} Application Identifier {line}") + for k, v in gpgcard.decode_AID().items(): + if k == "AID": + print(f" # {k:20s}: {v}") + else: + print(f" - {k:18s}: {v}") + print(f"{line} Historical Bytes {line}") + for k, v in gpgcard.decode_histo().items(): + print(f" - {k:20s}: {v}") + print(f"{line} Max Extended Length {line}") + for k, v in gpgcard.decode_extlength().items(): + print(f" - {k:20s}: {v}") + print(f"{line} PIN Info {line}") + for k, v in gpgcard.decode_pws().items(): + print(f" - {k:20s}: {v}") + print(f"{line} Extended Capabilities {line}") + for k, v in gpgcard.decode_ext_capabilities().items(): + print(f" - {k:20s}: {v}") + print(f"{line} Hardware Features {line}") + for k, v in gpgcard.decode_hardware().items(): + print(f" - {k:20s}: {v}") + print(f"{line} User Info {line}") + print(f" - {'Name':20s}: {gpgcard.get_name()}") + print(f" - {'Login':20s}: {gpgcard.get_login()}") + print(f" - {'URL':20s}: {gpgcard.get_url()}") + print(f" - {'Salutation':20s}: {gpgcard.get_salutation()}") + print(f" - {'Lang':20s}: {gpgcard.get_lang()}") + print(f"{line} Slots Info {line}") + for k, v in gpgcard.decode_slot().items(): + print(f" - {k:20s}: {v}") + print(f"{line} Keys Info {line}") + print(f" - {'CDS counter':20s}: {gpgcard.get_sig_count()}") + print(f" - {'RSA Pub Exponent':20s}: 0x{gpgcard.get_rsa_pub_exp():06x}") + + for key in [k.value for k in KeyTypes]: + print(f" # {key}:") + print(f" - {'UIF':18s}: {gpgcard.decode_key_uif(key)}") + print(f" - {'Fingerprint':18s}: {gpgcard.get_key_fingerprint(key)}") + print(f" - {'CA fingerprint':18s}: {gpgcard.get_key_CA_fingerprint(key)}") + print(f" - {'Creation date':18s}: {gpgcard.get_key_date(key)}") + print(f" - {'Attribute':18s}: {gpgcard.decode_attributes(key)}") + print(f" - {'Certificate':18s}: {gpgcard.get_key_cert(key)}") + print(" - Key:") + for k, v in gpgcard.decode_key(key).items(): + print(f" * {k:16s}: {v}") + + +# =============================================================================== +# Set fingerprints +# =============================================================================== +def set_fingerprints(gpgcard: GPGCard, fingerprints: str, key_type: KeyTypes | None = None) -> None: + """Set Key template + + Args: + gpgcard (GPGCard): smartcard object + fingerprints (str): SIG, DEC, AUT fingerprints separated by ':' + key_type (KeyTypes): Key type selected + """ + + d = {} + if key_type is None: + # Consider all keys fingerprints are given + try: + d[KeyTypes.KEY_SIG], d[KeyTypes.KEY_DEC], d[KeyTypes.KEY_AUT] = fingerprints.split(":") + except ValueError as err: + raise GPGCardExcpetion(0, f"Wrong fingerprints arguments: {err}") from err + + else: + # a key_type is specified, using only this fingerprint + d[key_type] = fingerprints + + for k, v in d.items(): + print(f"Set fingerprints for '{k}' Key...") + gpgcard.set_key_fingerprint(k, bytes.fromhex(v)) + + +# =============================================================================== +# Set Key Templates +# =============================================================================== +def set_templates(gpgcard: GPGCard, templates: str, key_type: KeyTypes | None = None) -> None: + """Set Key template + + Args: + gpgcard (GPGCard): smartcard object + templates (str): SIG, DEC, AUT template separated by ':' + key_type (KeyTypes): Key type selected + """ + + d = {} + if key_type is None: + # Consider all keys template are given + try: + d[KeyTypes.KEY_SIG], d[KeyTypes.KEY_DEC], d[KeyTypes.KEY_AUT] = templates.split(":") + except ValueError as err: + raise GPGCardExcpetion(0, f"Wrong templates arguments: {err}") from err + else: + # a key_type is specified, using only this template + d[key_type] = templates + + for _, v in d.items(): + if v not in KEY_TEMPLATES: + raise GPGCardExcpetion(0, f"Invalid template: {v}") + + for k, v in d.items(): + print(f"Set template {v} for '{k}' Key...") + gpgcard.set_template(k, v) + + +# =============================================================================== +# Handle Asymmetric keys +# =============================================================================== +def handle_key(gpgcard: GPGCard, action: str, key_type: KeyTypes, file: str = "") -> None: + """Generate Key pair and/or Read Public key + + Args: + gpgcard (GPGCard): smartcard object + action (str): Generate or Read + key_type (KeyTypes): Key type selected + file (str): Public key export file + """ + + if action not in KEY_OPERATIONS: + raise GPGCardExcpetion(0, f"Invalid operation: {action}") + + key_list = [key_type] if key_type else list(KeyTypes) + for key in key_list: + print(f"{action} '{key}' Key...") + key_action = "Read" if action == "Export" else action + pubkey = gpgcard.asymmetric_key(key, key_action) + if action == "Export": + if len(key_list) > 1: + filename = key + "_" + file + else: + filename = file + path = Path(filename) + if path.suffix == "": + filename += ".pem" + gpgcard.export_pub_key(pubkey, filename) + else: + for k, v in pubkey.items(): + print(f" - {k:13s}: {v}") + + +# =============================================================================== +# MAIN +# =============================================================================== +def entrypoint() -> None: + """Main function""" + + # Arguments parsing + # ----------------- + args = get_argparser() + + # Arguments checking + # ------------------ + if not args.pinpad: + if not args.adm_pin or not args.user_pin: + error(ErrorCodes.ERR_INTERNAL, + "If 'pinpad' is not use, 'userpin' and 'admpin' must be provided") + + if args.serial and len(args.serial) != 8 : + error(ErrorCodes.ERR_INTERNAL, + "Serial must be a 4 bytes hex string value (8 characters)") + + if args.reset_code and len(args.reset_code) != 8: + error(ErrorCodes.ERR_INTERNAL, + "Reset Code must be a 4 bytes hex string value (8 characters)") + + if args.key_action == "Export" and not args.file: + error(ErrorCodes.ERR_INTERNAL, "Provide a file to export public key") + + # Processing + # ---------- + try: + print(f"Connect to card '{args.reader}'...") + gpgcard: GPGCard = GPGCard() + gpgcard.log_apdu(args.apdu) + gpgcard.connect(args.reader) + + verify_pins(gpgcard, args.user_pin, args.adm_pin, args.pinpad) + + if args.slot: + gpgcard.select_slot(args.slot - 1) + + if args.salutation: + gpgcard.set_salutation(args.salutation) + if args.name: + gpgcard.set_name(args.name) + if args.url: + gpgcard.set_url(args.url) + if args.login: + gpgcard.set_login(args.login) + if args.lang: + gpgcard.set_lang(args.lang) + + if args.new_user_pin: + gpgcard.change_pin(PassWord.PW1, args.user_pin, args.new_user_pin) + if args.new_adm_pin: + gpgcard.change_pin(PassWord.PW3, args.adm_pin, args.new_adm_pin) + if args.reset_pw1: + # Reset the User PIN with Resetting Code + gpgcard.reset_PW1(args.reset_code, args.reset_pw1) + elif args.reset_code: + # Use the Resetting code to set the value + gpgcard.set_RC(args.reset_code) + + get_info(gpgcard, args.info) + + if args.reset: + reset_app(gpgcard) + + if args.set_templates: + set_templates(gpgcard, args.set_templates, args.key_type) + + if args.seed_key: + gpgcard.seed_key() + + if args.set_fingerprints: + set_fingerprints(gpgcard, args.set_fingerprints, args.key_type) + + if args.serial: + gpgcard.set_serial(args.serial) + + if args.key_action: + handle_key(gpgcard, args.key_action, args.key_type, args.file) + + gpgcard.disconnect() + + except GPGCardExcpetion as err: + error(err.code, err.message) + + +if __name__ == "__main__": + + entrypoint() diff --git a/pytools/requirements.txt b/pytools/requirements.txt new file mode 100644 index 0000000..0c51a3d --- /dev/null +++ b/pytools/requirements.txt @@ -0,0 +1,2 @@ +pycryptodome +ledgercomm diff --git a/pytools/setup.cfg b/pytools/setup.cfg new file mode 100644 index 0000000..3422f54 --- /dev/null +++ b/pytools/setup.cfg @@ -0,0 +1,28 @@ +[tool:pytest] +addopts = --strict-markers + +[pylint] +disable = C0114, # missing-module-docstring + C0115, # missing-class-docstring + C0116, # missing-function-docstring + C0103, # invalid-name + C0302, # too-many-lines + R0801, # duplicate-code + R0902, # too-many-instances + R0903, # too-few-public-methods + R0904, # too-many-public-methods + R0912, # too-many-branches + R0913, # too-many-arguments + R0914, # too-many-statements + R0915 # too-many-local-variables +max-line-length=110 +extension-pkg-whitelist=hid + +[pycodestyle] +max-line-length = 100 + +[mypy-hid.*] +ignore_missing_imports = True + +[mypy-pytest.*] +ignore_missing_imports = True diff --git a/src/gpg_api.h b/src/gpg_api.h index 2622005..17c71cc 100644 --- a/src/gpg_api.h +++ b/src/gpg_api.h @@ -1,76 +1,112 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifndef GPG_API_H #define GPG_API_H -void USBD_CCID_activate_pinpad(int enabled); +/* ----------------------------------------------------------------------- */ +/* --- INIT ---- */ +/* ----------------------------------------------------------------------- */ -unsigned int gpg_oid2curve(unsigned char *oid, unsigned int len); +void gpg_activate_pinpad(uint8_t enabled); +unsigned int gpg_oid2curve(unsigned char *oid, unsigned int len); unsigned char *gpg_curve2oid(unsigned int cv, unsigned int *len); -unsigned int gpg_curve2domainlen(unsigned int cv); +unsigned int gpg_curve2domainlen(unsigned int cv); void gpg_init(void); -void gpg_init_ux(void); void gpg_install(unsigned char app_state); -void gpg_install_slot(gpg_key_slot_t *slot); -int gpg_dispatch(void); -int gpg_apdu_select_data(unsigned int ref, int reccord); +/* ----------------------------------------------------------------------- */ +/* --- DISPATCH ---- */ +/* ----------------------------------------------------------------------- */ + +int gpg_dispatch(void); + +/* ----------------------------------------------------------------------- */ +/* --- DATA ---- */ +/* ----------------------------------------------------------------------- */ + +void gpg_apdu_select_data(unsigned int ref, int record); int gpg_apdu_get_data(unsigned int ref); int gpg_apdu_get_next_data(unsigned int ref); int gpg_apdu_put_data(unsigned int ref); int gpg_apdu_get_key_data(unsigned int ref); int gpg_apdu_put_key_data(unsigned int ref); -void gpg_pso_derive_slot_seed(int slot, unsigned char *seed); -void gpg_pso_derive_key_seed(unsigned char *Sn, - unsigned char *key_name, - unsigned int idx, - unsigned char *Ski, - unsigned int Ski_len); -int gpg_apdu_pso(void); -int gpg_apdu_internal_authenticate(void); -int gpg_apdu_gen(void); -int gpg_apdu_get_challenge(void); +/* ----------------------------------------------------------------------- */ +/* --- PSO ---- */ +/* ----------------------------------------------------------------------- */ + +int gpg_pso_derive_slot_seed(int slot, unsigned char *seed); +int gpg_pso_derive_key_seed(unsigned char *Sn, + unsigned char *key_name, + unsigned int idx, + unsigned char *Ski, + unsigned int Ski_len); +int gpg_apdu_pso(void); +int gpg_apdu_internal_authenticate(void); + +/* ----------------------------------------------------------------------- */ +/* --- GEN ---- */ +/* ----------------------------------------------------------------------- */ + +int gpg_apdu_gen(void); + +/* ----------------------------------------------------------------------- */ +/* --- CHALLENGE ---- */ +/* ----------------------------------------------------------------------- */ + +int gpg_apdu_get_challenge(void); + +/* ----------------------------------------------------------------------- */ +/* --- SELECT ---- */ +/* ----------------------------------------------------------------------- */ int gpg_apdu_select(void); +/* ----------------------------------------------------------------------- */ +/* --- PIN ---- */ +/* ----------------------------------------------------------------------- */ + int gpg_apdu_verify(void); int gpg_apdu_change_ref_data(void); int gpg_apdu_reset_retry_counter(void); gpg_pin_t *gpg_pin_get_pin(int id); -int gpg_pin_is_blocked(gpg_pin_t *pin); -int gpg_pin_is_verified(int pinID); -int gpg_pin_set_verified(int pinID, int verified); -int gpg_pin_check(gpg_pin_t *pin, int pinID, unsigned char *pin_val, unsigned int pin_len); -void gpg_pin_set(gpg_pin_t *pin, unsigned char *pin_val, unsigned int pin_len); +int gpg_pin_is_verified(int pinID); +void gpg_pin_set_verified(int pinID, int verified); +int gpg_pin_check(gpg_pin_t *pin, int pinID, const unsigned char *pin_val, unsigned int pin_len); +int gpg_pin_set(gpg_pin_t *pin, unsigned char *pin_val, unsigned int pin_len); -int gpg_mse_reset(); +/* ----------------------------------------------------------------------- */ +/* --- MSE ---- */ +/* ----------------------------------------------------------------------- */ + +void gpg_mse_reset(); int gpg_apdu_mse(); /* ----------------------------------------------------------------------- */ -/* --- IO ---- */ +/* --- IO ---- */ /* ----------------------------------------------------------------------- */ void gpg_io_discard(int clear); void gpg_io_clear(void); void gpg_io_set_offset(unsigned int offset); void gpg_io_mark(void); void gpg_io_rewind(void); -void gpg_io_hole(unsigned int sz); void gpg_io_inserted(unsigned int len); void gpg_io_insert(unsigned char const *buffer, unsigned int len); void gpg_io_insert_u32(unsigned int v32); @@ -81,36 +117,17 @@ void gpg_io_insert_t(unsigned int T); void gpg_io_insert_tl(unsigned int T, unsigned int L); void gpg_io_insert_tlv(unsigned int T, unsigned int L, unsigned char const *V); -void gpg_io_fetch_buffer(unsigned char *buffer, unsigned int len); +void gpg_io_fetch_buffer(unsigned char *buffer, unsigned int len); unsigned int gpg_io_fetch_u32(void); unsigned int gpg_io_fetch_u24(void); unsigned int gpg_io_fetch_u16(void); unsigned int gpg_io_fetch_u8(void); -int gpg_io_fetch_t(unsigned int *T); -int gpg_io_fetch_l(unsigned int *L); -int gpg_io_fetch_tl(unsigned int *T, unsigned int *L); -int gpg_io_fetch_nv(unsigned char *buffer, int len); -int gpg_io_fetch(unsigned char *buffer, int len); - -int gpg_io_do(unsigned int io_flags); +void gpg_io_fetch_t(unsigned int *T); +void gpg_io_fetch_l(unsigned int *L); +void gpg_io_fetch_tl(unsigned int *T, unsigned int *L); +void gpg_io_fetch_nv(unsigned char *buffer, int len); +int gpg_io_fetch(unsigned char *buffer, int len); -/* ----------------------------------------------------------------------- */ -/* --- TMP ---- */ -/* ----------------------------------------------------------------------- */ -void io_usb_ccid_set_card_inserted(unsigned int inserted); - -/* ----------------------------------------------------------------------- */ -/* --- DEBUG ---- */ -/* ----------------------------------------------------------------------- */ -#ifdef GPG_DEBUG -#error "UNSUPPORTDED: to be fixed" -#include "gpg_debug.h" - -#else - -#define gpg_nvm_write nvm_write -#define gpg_io_exchange io_exchange - -#endif +void gpg_io_do(unsigned int io_flags); #endif diff --git a/src/gpg_challenge.c b/src/gpg_challenge.c index 04d2648..d4c7260 100644 --- a/src/gpg_challenge.c +++ b/src/gpg_challenge.c @@ -1,67 +1,95 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" +#include "cx_errors.h" +/** + * Generate a Random Number + * + * @return Status Word + * + */ int gpg_apdu_get_challenge() { - unsigned int olen, hlen; + unsigned int olen; + cx_err_t error = CX_INTERNAL_ERROR; + unsigned char Sr[64]; - if ((G_gpg_vstate.io_p1 & 0x80) == 0x80) { - olen = G_gpg_vstate.io_p2; - } else { - olen = G_gpg_vstate.io_le; - } - if (olen == 0 || olen > GPG_EXT_CHALLENGE_LENTH) { - THROW(SW_WRONG_LENGTH); - return SW_WRONG_LENGTH; - } + switch (G_gpg_vstate.io_p1) { + case CHALLENGE_NOMINAL: + case PRIME_MODE: + olen = G_gpg_vstate.io_le; + break; + case SEEDED_MODE: + olen = G_gpg_vstate.io_p2; + break; + default: + return SW_WRONG_P1P2; + } + if (olen == 0 || olen > GPG_EXT_CHALLENGE_LENTH) { + return SW_WRONG_LENGTH; + } - if ((G_gpg_vstate.io_p1 & 0x82) == 0x82) { - unsigned int path[2]; - unsigned char chain[32]; - unsigned char Sr[32]; + if (G_gpg_vstate.io_p1 == SEEDED_MODE) { + // Ledger Add-on: Seeded random + unsigned int path[2]; + unsigned char chain[32] = {0}; - os_memset(chain, 0, 32); - path[0] = 0x80475047; - path[1] = 0x0F0F0F0F; - os_perso_derive_node_bip32(CX_CURVE_SECP256K1, path, 2, Sr, chain); - chain[0] = 'r'; - chain[1] = 'n'; - chain[2] = 'd'; + explicit_bzero(chain, 32); + path[0] = 0x80475047; + path[1] = 0x0F0F0F0F; + CX_CHECK(os_derive_bip32_no_throw(CX_CURVE_SECP256K1, path, 2, Sr, chain)); + chain[0] = 'r'; + chain[1] = 'n'; + chain[2] = 'd'; - cx_sha256_init(&G_gpg_vstate.work.md.sha256); - cx_hash((cx_hash_t *)&G_gpg_vstate.work.md.sha256, 0, Sr, 32, NULL, 0); - cx_hash((cx_hash_t *)&G_gpg_vstate.work.md.sha256, 0, chain, 3, NULL, 0); - hlen = cx_hash((cx_hash_t *)&G_gpg_vstate.work.md.sha256, CX_LAST, G_gpg_vstate.work.io_buffer, - G_gpg_vstate.io_length, G_gpg_vstate.work.io_buffer, 32); + cx_sha256_init(&G_gpg_vstate.work.md.sha256); + CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, 0, Sr, 32, NULL, 0)); + CX_CHECK( + cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, 0, chain, 3, NULL, 0)); + CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, + CX_LAST, + G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length, + G_gpg_vstate.work.io_buffer, + 32)); + CX_CHECK(cx_sha3_xof_init_no_throw(&G_gpg_vstate.work.md.sha3, 256, olen)); + CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha3, + CX_LAST, + G_gpg_vstate.work.io_buffer, + 32, + G_gpg_vstate.work.io_buffer, + olen)); + } else { + cx_rng(G_gpg_vstate.work.io_buffer, olen); + error = CX_OK; + } - cx_sha3_xof_init(&G_gpg_vstate.work.md.sha3, 256, olen); - cx_hash((cx_hash_t *)&G_gpg_vstate.work.md.sha3, CX_LAST, G_gpg_vstate.work.io_buffer, hlen, - G_gpg_vstate.work.io_buffer, olen); - } else { - cx_rng(G_gpg_vstate.work.io_buffer, olen); - } + if (G_gpg_vstate.io_p1 == PRIME_MODE) { + // Ledger Add-on: Prime random + CX_CHECK(cx_math_next_prime_no_throw(G_gpg_vstate.work.io_buffer, olen)); + } - if ((G_gpg_vstate.io_p1 & 0x81) == 0x81) { - cx_math_next_prime(G_gpg_vstate.work.io_buffer, olen); - } - gpg_io_discard(0); - gpg_io_inserted(olen); - return SW_OK; +end: + explicit_bzero(&Sr, sizeof(Sr)); + if (error != CX_OK) { + return error; + } + gpg_io_discard(0); + gpg_io_inserted(olen); + return SW_OK; } diff --git a/src/gpg_data.c b/src/gpg_data.c index d47b790..436bde8 100644 --- a/src/gpg_data.c +++ b/src/gpg_data.c @@ -1,862 +1,1124 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" +#include "gpg_ux.h" +#include "cx_errors.h" -int gpg_apdu_select_data(unsigned int ref, int reccord) { - G_gpg_vstate.DO_current = ref; - G_gpg_vstate.DO_reccord = reccord; - G_gpg_vstate.DO_offset = 0; - return SW_OK; +/** + * Select a DO (Data Object) in the current template + * + * @param[in] ref DO tag + * @param[in] record Offset of the record + * + */ +void gpg_apdu_select_data(unsigned int ref, int record) { + G_gpg_vstate.DO_current = ref; + G_gpg_vstate.DO_reccord = record; + G_gpg_vstate.DO_offset = 0; } +/** + * Read a DO (Data Object) from the card + * + * @param[in] ref DO tag + * + * @return Status Word + * + */ int gpg_apdu_get_data(unsigned int ref) { - int sw; + int sw = SW_UNKNOWN; - if (G_gpg_vstate.DO_current != ref) { - G_gpg_vstate.DO_current = ref; - G_gpg_vstate.DO_reccord = 0; - G_gpg_vstate.DO_offset = 0; - } - sw = SW_OK; - - gpg_io_discard(1); - switch (ref) { - /* ----------------- Optional DO for private use ----------------- */ - case 0x0101: - gpg_io_insert(N_gpg_pstate->private_DO1.value, N_gpg_pstate->private_DO1.length); - break; - case 0x0102: - gpg_io_insert(N_gpg_pstate->private_DO2.value, N_gpg_pstate->private_DO2.length); - break; - case 0x0103: - gpg_io_insert(N_gpg_pstate->private_DO3.value, N_gpg_pstate->private_DO3.length); - break; - case 0x0104: - gpg_io_insert(N_gpg_pstate->private_DO4.value, N_gpg_pstate->private_DO4.length); - break; - - /* ----------------- Config key slot ----------------- */ - case 0x01F0: - gpg_io_insert(N_gpg_pstate->config_slot, 3); - gpg_io_insert_u8(G_gpg_vstate.slot); - break; - case 0x01F1: - gpg_io_insert(N_gpg_pstate->config_slot, 3); - break; - case 0x01F2: - gpg_io_insert_u8(G_gpg_vstate.slot); - break; - /* ----------------- Config RSA exponent ----------------- */ - case 0x01F8: - gpg_io_insert(N_gpg_pstate->default_RSA_exponent, 4); - break; - - /* ----------------- Application ----------------- */ - /* Full Application identifier */ - case 0x004F: - gpg_io_insert(N_gpg_pstate->AID, 10); - gpg_io_insert(G_gpg_vstate.kslot->serial, 4); - gpg_io_insert_u16(0x0000); - break; - /* Historical bytes, */ - case 0x5F52: - gpg_io_insert(N_gpg_pstate->histo, 15); - break; - /* Extended length information */ - case 0x7F66: - gpg_io_insert(C_ext_length, sizeof(C_ext_length)); - break; - - /* ----------------- User -----------------*/ - /* Login data */ - case 0x005E: - gpg_io_insert(N_gpg_pstate->login.value, N_gpg_pstate->login.length); - break; - /* Uniform resource locator */ - case 0x5F50: - gpg_io_insert(N_gpg_pstate->url.value, N_gpg_pstate->url.length); - break; - /* Name, Language, Sex */ - case 0x65: - gpg_io_insert_tlv(0x5B, N_gpg_pstate->name.length, N_gpg_pstate->name.value); - gpg_io_insert_tlv(0x5F2D, N_gpg_pstate->lang.length, N_gpg_pstate->lang.value); - gpg_io_insert_tlv(0x5F35, 1, N_gpg_pstate->sex); - break; - - /* ----------------- aid, histo, ext_length, ... ----------------- */ - case 0x6E: - gpg_io_insert_tlv(0x4F, 16, N_gpg_pstate->AID); - os_memmove(G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset - 6, G_gpg_vstate.kslot->serial, 4); - gpg_io_insert_tlv(0x5F52, 15, N_gpg_pstate->histo); - gpg_io_insert_tlv(0x7F66, sizeof(C_ext_length), C_ext_length); - - gpg_io_mark(); - - gpg_io_insert_tlv(0xC0, sizeof(C_ext_capabilities), C_ext_capabilities); - gpg_io_insert_tlv(0xC1, G_gpg_vstate.kslot->sig.attributes.length, G_gpg_vstate.kslot->sig.attributes.value); - gpg_io_insert_tlv(0xC2, G_gpg_vstate.kslot->dec.attributes.length, G_gpg_vstate.kslot->dec.attributes.value); - gpg_io_insert_tlv(0xC3, G_gpg_vstate.kslot->aut.attributes.length, G_gpg_vstate.kslot->aut.attributes.value); - gpg_io_insert_tl(0xC4, 7); - gpg_io_insert(N_gpg_pstate->PW_status, 4); - gpg_io_insert_u8(N_gpg_pstate->PW1.counter); - gpg_io_insert_u8(N_gpg_pstate->RC.counter); - gpg_io_insert_u8(N_gpg_pstate->PW3.counter); - gpg_io_insert_tl(0xC5, 60); - gpg_io_insert(G_gpg_vstate.kslot->sig.fingerprints, 20); - gpg_io_insert(G_gpg_vstate.kslot->dec.fingerprints, 20); - gpg_io_insert(G_gpg_vstate.kslot->aut.fingerprints, 20); - gpg_io_insert_tl(0xC6, 60); - gpg_io_insert(G_gpg_vstate.kslot->sig.CA_fingerprints, 20); - gpg_io_insert(G_gpg_vstate.kslot->dec.CA_fingerprints, 20); - gpg_io_insert(G_gpg_vstate.kslot->aut.CA_fingerprints, 20); - gpg_io_insert_tl(0xCD, 12); - gpg_io_insert(G_gpg_vstate.kslot->sig.date, 4); - gpg_io_insert(G_gpg_vstate.kslot->dec.date, 4); - gpg_io_insert(G_gpg_vstate.kslot->aut.date, 4); - gpg_io_set_offset(IO_OFFSET_MARK); - gpg_io_insert_tl(0x73, G_gpg_vstate.io_length - G_gpg_vstate.io_offset); - gpg_io_set_offset(IO_OFFSET_END); - break; - - /* ----------------- User Interaction Flag (UIF) for PSO:CDS ----------------- */ - case 0x00D6: - gpg_io_insert(G_gpg_vstate.kslot->sig.UIF, 2); - break; - case 0x00D7: - gpg_io_insert(G_gpg_vstate.kslot->dec.UIF, 2); - break; - case 0x00D8: - gpg_io_insert(G_gpg_vstate.kslot->aut.UIF, 2); - break; - - /* ----------------- Security support template ----------------- */ - case 0x7A: - gpg_io_insert_tl(0x93, 3); - gpg_io_insert_u24(G_gpg_vstate.kslot->sig_count); - break; - - /* ----------------- Cardholder certificate ----------------- */ - case 0x7F21: - switch (G_gpg_vstate.DO_reccord) { - case 0: - gpg_io_insert(G_gpg_vstate.kslot->aut.CA.value, G_gpg_vstate.kslot->aut.CA.length); - break; - case 1: - gpg_io_insert(G_gpg_vstate.kslot->dec.CA.value, G_gpg_vstate.kslot->dec.CA.length); - break; - case 2: - gpg_io_insert(G_gpg_vstate.kslot->sig.CA.value, G_gpg_vstate.kslot->sig.CA.length); - break; - default: - sw = SW_RECORD_NOT_FOUND; + if (G_gpg_vstate.DO_current != ref) { + G_gpg_vstate.DO_current = ref; + G_gpg_vstate.DO_reccord = 0; + G_gpg_vstate.DO_offset = 0; } - break; - - /* ----------------- PW Status Bytes ----------------- */ - case 0x00C4: - gpg_io_insert(N_gpg_pstate->PW_status, 4); - gpg_io_insert_u8(N_gpg_pstate->PW1.counter); - gpg_io_insert_u8(N_gpg_pstate->RC.counter); - gpg_io_insert_u8(N_gpg_pstate->PW3.counter); - break; - - /* WAT */ - default: - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return 0; - } - - return sw; -} + sw = SW_OK; -int gpg_apdu_get_next_data(unsigned int ref) { - int sw; - - if ((ref != 0x7F21) || (G_gpg_vstate.DO_current != 0x7F21)) { - return SW_CONDITIONS_NOT_SATISFIED; - } - sw = gpg_apdu_get_data(ref); - if (sw == SW_OK) { - G_gpg_vstate.DO_reccord++; - } - return sw; + gpg_io_discard(1); + switch (ref) { + /* ----------------- Optional DO for private use ----------------- */ + case 0x0101: + gpg_io_insert((const unsigned char *) N_gpg_pstate->private_DO1.value, + N_gpg_pstate->private_DO1.length); + break; + case 0x0102: + gpg_io_insert((const unsigned char *) N_gpg_pstate->private_DO2.value, + N_gpg_pstate->private_DO2.length); + break; + case 0x0103: + gpg_io_insert((const unsigned char *) N_gpg_pstate->private_DO3.value, + N_gpg_pstate->private_DO3.length); + break; + case 0x0104: + gpg_io_insert((const unsigned char *) N_gpg_pstate->private_DO4.value, + N_gpg_pstate->private_DO4.length); + break; + + /* ----------------- Config key slot ----------------- */ + case 0x01F0: + gpg_io_insert((const unsigned char *) N_gpg_pstate->config_slot, 3); + gpg_io_insert_u8(G_gpg_vstate.slot); + break; + case 0x01F1: + gpg_io_insert((const unsigned char *) N_gpg_pstate->config_slot, 3); + break; + case 0x01F2: + gpg_io_insert_u8(G_gpg_vstate.slot); + break; + /* ----------------- Config RSA exponent ----------------- */ + case 0x01F8: + gpg_io_insert((const unsigned char *) N_gpg_pstate->default_RSA_exponent, 4); + break; + + /* ----------------- Application ----------------- */ + case 0x004F: + /* Full Application identifier */ + gpg_io_insert((const unsigned char *) N_gpg_pstate->AID, 10); + gpg_io_insert(G_gpg_vstate.kslot->serial, 4); + gpg_io_insert_u16(0x0000); + break; + case 0x5F52: + /* Historical bytes */ + gpg_io_insert((const unsigned char *) N_gpg_pstate->histo, HISTO_LENGTH); + break; + case 0x7F66: + /* Extended length information */ + gpg_io_insert(C_ext_length, sizeof(C_ext_length)); + break; + + /* ----------------- User -----------------*/ + case 0x005E: + /* Login data */ + gpg_io_insert((const unsigned char *) N_gpg_pstate->login.value, + N_gpg_pstate->login.length); + break; + case 0x5F50: + /* Uniform resource locator */ + gpg_io_insert((const unsigned char *) N_gpg_pstate->keys[G_gpg_vstate.slot].url.value, + N_gpg_pstate->keys[G_gpg_vstate.slot].url.length); + break; + case 0x65: + /* Name, Language, salutation */ + gpg_io_insert_tlv(0x5B, + N_gpg_pstate->name.length, + (const unsigned char *) N_gpg_pstate->name.value); + gpg_io_insert_tlv(0x5F2D, + N_gpg_pstate->lang.length, + (const unsigned char *) N_gpg_pstate->lang.value); + gpg_io_insert_tlv(0x5F35, 1, (const unsigned char *) N_gpg_pstate->salutation); + break; + + /* ----------------- aid, histo, ext_length, ... ----------------- */ + case 0x6E: + gpg_io_insert_tlv(0x4F, AID_LENGTH, (const unsigned char *) N_gpg_pstate->AID); + memmove(G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset - 6, + G_gpg_vstate.kslot->serial, + 4); + gpg_io_insert_tlv(0x5F52, HISTO_LENGTH, (const unsigned char *) N_gpg_pstate->histo); + gpg_io_insert_tlv(0x7F66, sizeof(C_ext_length), C_ext_length); + + gpg_io_mark(); + + gpg_io_insert_tlv(0xC0, sizeof(C_ext_capabilities), C_ext_capabilities); + gpg_io_insert_tlv(0xC1, + G_gpg_vstate.kslot->sig.attributes.length, + G_gpg_vstate.kslot->sig.attributes.value); + gpg_io_insert_tlv(0xC2, + G_gpg_vstate.kslot->dec.attributes.length, + G_gpg_vstate.kslot->dec.attributes.value); + gpg_io_insert_tlv(0xC3, + G_gpg_vstate.kslot->aut.attributes.length, + G_gpg_vstate.kslot->aut.attributes.value); + gpg_io_insert_tl(0xC4, 7); + gpg_io_insert((const unsigned char *) N_gpg_pstate->PW_status, 4); + gpg_io_insert_u8(N_gpg_pstate->PW1.counter); + gpg_io_insert_u8(N_gpg_pstate->RC.counter); + gpg_io_insert_u8(N_gpg_pstate->PW3.counter); + gpg_io_insert_tl(0xC5, 60); + gpg_io_insert(G_gpg_vstate.kslot->sig.fingerprints, 20); + gpg_io_insert(G_gpg_vstate.kslot->dec.fingerprints, 20); + gpg_io_insert(G_gpg_vstate.kslot->aut.fingerprints, 20); + gpg_io_insert_tl(0xC6, 60); + gpg_io_insert(G_gpg_vstate.kslot->sig.CA_fingerprints, 20); + gpg_io_insert(G_gpg_vstate.kslot->dec.CA_fingerprints, 20); + gpg_io_insert(G_gpg_vstate.kslot->aut.CA_fingerprints, 20); + gpg_io_insert_tl(0xCD, 12); + gpg_io_insert(G_gpg_vstate.kslot->sig.date, 4); + gpg_io_insert(G_gpg_vstate.kslot->dec.date, 4); + gpg_io_insert(G_gpg_vstate.kslot->aut.date, 4); + gpg_io_set_offset(IO_OFFSET_MARK); + gpg_io_insert_tl(0x73, G_gpg_vstate.io_length - G_gpg_vstate.io_offset); + gpg_io_set_offset(IO_OFFSET_END); + break; + + /* ----------------- User Interaction Flag (UIF) for PSO:CDS ----------------- */ + case 0x00D6: + gpg_io_insert(G_gpg_vstate.kslot->sig.UIF, 2); + break; + case 0x00D7: + gpg_io_insert(G_gpg_vstate.kslot->dec.UIF, 2); + break; + case 0x00D8: + gpg_io_insert(G_gpg_vstate.kslot->aut.UIF, 2); + break; + + /* ----------------- Security support template ----------------- */ + case 0x7A: + gpg_io_insert_tl(0x93, 3); + gpg_io_insert_u24(G_gpg_vstate.kslot->sig_count); + break; + + /* ----------------- Cardholder certificate ----------------- */ + case 0x7F21: + switch (G_gpg_vstate.DO_reccord) { + case 0: + gpg_io_insert(G_gpg_vstate.kslot->aut.CA.value, + G_gpg_vstate.kslot->aut.CA.length); + break; + case 1: + gpg_io_insert(G_gpg_vstate.kslot->dec.CA.value, + G_gpg_vstate.kslot->dec.CA.length); + break; + case 2: + gpg_io_insert(G_gpg_vstate.kslot->sig.CA.value, + G_gpg_vstate.kslot->sig.CA.length); + break; + default: + sw = SW_FILE_NOT_FOUND; + } + break; + + /* ----------------- PW Status Bytes ----------------- */ + case 0x00C4: + gpg_io_insert((const unsigned char *) N_gpg_pstate->PW_status, 4); + gpg_io_insert_u8(N_gpg_pstate->PW1.counter); + gpg_io_insert_u8(N_gpg_pstate->RC.counter); + gpg_io_insert_u8(N_gpg_pstate->PW3.counter); + break; + + /* ----------------- General Feature management ----------------- */ + case 0x7F74: + gpg_io_insert_u8(C_gen_feature); + break; + + default: + /* WAT */ + sw = SW_REFERENCED_DATA_NOT_FOUND; + break; + } + + return sw; } -int gpg_apdu_put_data(unsigned int ref) { - unsigned int t, l, sw; - unsigned int * ptr_l; - unsigned char *ptr_v; - - G_gpg_vstate.DO_current = ref; - sw = SW_OK; - - switch (ref) { - /* ----------------- Optional DO for private use ----------------- */ - case 0x0101: - ptr_l = &N_gpg_pstate->private_DO1.length; - ptr_v = N_gpg_pstate->private_DO1.value; - goto WRITE_PRIVATE_DO; - case 0x0102: - ptr_l = &N_gpg_pstate->private_DO2.length; - ptr_v = N_gpg_pstate->private_DO2.value; - goto WRITE_PRIVATE_DO; - case 0x0103: - ptr_l = &N_gpg_pstate->private_DO3.length; - ptr_v = N_gpg_pstate->private_DO3.value; - goto WRITE_PRIVATE_DO; - case 0x0104: - ptr_l = &N_gpg_pstate->private_DO4.length; - ptr_v = N_gpg_pstate->private_DO4.value; - goto WRITE_PRIVATE_DO; - WRITE_PRIVATE_DO: - if (G_gpg_vstate.io_length > GPG_EXT_PRIVATE_DO_LENGTH) { - THROW(SW_WRONG_LENGTH); - return 0; - } - gpg_nvm_write(ptr_v, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, G_gpg_vstate.io_length); - gpg_nvm_write(ptr_l, &G_gpg_vstate.io_length, sizeof(unsigned int)); - sw = SW_OK; - break; - /* ----------------- Config key slot ----------------- */ - case 0x01F1: - if (G_gpg_vstate.io_length != 3) { - THROW(SW_WRONG_LENGTH); - return 0; - } - if ((G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] != GPG_KEYS_SLOTS) || - (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] >= GPG_KEYS_SLOTS) || - (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 2] > 3)) { - THROW(SW_WRONG_DATA); - return 0; - } - gpg_nvm_write(N_gpg_pstate->config_slot, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, 3); - break; +/** + * Read the next instance of the same DO (Data Object) from the card + * + * @param[in] ref DO tag + * + * @return Status Word + * + */ +int gpg_apdu_get_next_data(unsigned int ref) { + int sw = SW_UNKNOWN; - case 0x01F2: - if ((N_gpg_pstate->config_slot[2] & 2) == 0) { - THROW(SW_CONDITIONS_NOT_SATISFIED); - return 0; - } - if ((G_gpg_vstate.io_length != 1) || (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset] >= GPG_KEYS_SLOTS)) { - THROW(SW_WRONG_DATA); - return 0; - } - G_gpg_vstate.slot = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; - break; - - /* ----------------- Config RSA exponent ----------------- */ - case 0x01F8: { - unsigned int e; - if (G_gpg_vstate.io_length != 4) { - THROW(SW_WRONG_LENGTH); - return 0; - } - e = gpg_io_fetch_u32(); - nvm_write(&N_gpg_pstate->default_RSA_exponent, &e, sizeof(unsigned int)); - break; - } - - /* ----------------- Serial -----------------*/ - case 0x4f: - if (G_gpg_vstate.io_length != 4) { - THROW(SW_WRONG_LENGTH); - } - nvm_write(G_gpg_vstate.kslot->serial, &G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset], 4); - break; - - /* ----------------- Extended Header list -----------------*/ - case 0x3FFF: { - unsigned int len_e, len_p, len_q; - unsigned int endof, ksz, reset_cnt; - gpg_key_t * keygpg; - unsigned int dh; - // fecth 4D - gpg_io_fetch_tl(&t, &l); - if (t != 0x4D) { - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return 0; - } - // fecth B8/B6/A4 - gpg_io_fetch_tl(&t, &l); - dh = 0; - reset_cnt = 0; - switch (t) { - case 0xB6: - keygpg = &G_gpg_vstate.kslot->sig; - reset_cnt = 0x11111111; - break; - case 0xA4: - keygpg = &G_gpg_vstate.kslot->aut; - break; - case 0xB8: - keygpg = &G_gpg_vstate.kslot->dec; - dh = 0x11; - break; - default: - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return 0; - } - // fecth 7f78 - gpg_io_fetch_tl(&t, &l); - if (t != 0x7f48) { - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return 0; + if ((ref != 0x7F21) || (G_gpg_vstate.DO_current != 0x7F21)) { + return SW_CONDITIONS_NOT_SATISFIED; } - len_e = 0; - len_p = 0; - len_q = 0; - endof = G_gpg_vstate.io_offset + l; - while (G_gpg_vstate.io_offset < endof) { - gpg_io_fetch_tl(&t, &l); - switch (t) { - case 0x91: - len_e = l; - break; - case 0x92: - len_p = l; - break; - case 0x93: - len_q = l; - break; - break; - case 0x94: - case 0x95: - case 0x96: - case 0x97: - case 0x99: - break; - default: - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return 0; - } - } - // fecth 5f78 - gpg_io_fetch_tl(&t, &l); - if (t != 0x5f48) { - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return 0; + sw = gpg_apdu_get_data(ref); + if (sw == SW_OK) { + G_gpg_vstate.DO_reccord++; } + return sw; +} - // --- RSA KEY --- - if (keygpg->attributes.value[0] == 0x01) { - unsigned int e; - unsigned char * p, *q, *pq; - cx_rsa_public_key_t * rsa_pub; - cx_rsa_private_key_t *rsa_priv, *pkey; - unsigned int pkey_size; - // check length - ksz = (keygpg->attributes.value[1] << 8) | keygpg->attributes.value[2]; - ksz = ksz >> 3; - rsa_pub = (cx_rsa_public_key_t *)&G_gpg_vstate.work.rsa.public; - rsa_priv = (cx_rsa_private_key_t *)&G_gpg_vstate.work.rsa.private; - pkey = &keygpg->priv_key.rsa; - switch (ksz) { - case 1024 / 8: - pkey_size = sizeof(cx_rsa_1024_private_key_t); - pq = G_gpg_vstate.work.rsa.public1024.n; - break; - case 2048 / 8: - pkey_size = sizeof(cx_rsa_2048_private_key_t); - pq = G_gpg_vstate.work.rsa.public2048.n; - break; - case 3072 / 8: - pkey_size = sizeof(cx_rsa_3072_private_key_t); - pq = G_gpg_vstate.work.rsa.public3072.n; - break; - case 4096 / 8: - pkey_size = sizeof(cx_rsa_4096_private_key_t); - pq = G_gpg_vstate.work.rsa.public4096.n; - break; - } - ksz = ksz >> 1; - - // fetch e - e = 0; - switch (len_e) { - case 4: - e = gpg_io_fetch_u32(); - break; - case 3: - e = gpg_io_fetch_u24(); - break; - case 2: - e = gpg_io_fetch_u16(); - break; - case 1: - e = gpg_io_fetch_u8(); - break; - default: - THROW(SW_WRONG_DATA); - return 0; - } - - // move p,q over pub key, this only work because adr < adr

- if ((len_p > ksz) || (len_q > ksz)) { - THROW(SW_WRONG_DATA); - return 0; - } - p = G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset; - q = p + len_p; - os_memmove(pq + ksz - len_p, p, len_p); - os_memset(pq, 0, ksz - len_p); - os_memmove(pq + 2 * ksz - len_q, q, len_q); - os_memset(pq + ksz, 0, ksz - len_q); - - // regenerate RSA private key - unsigned char _e[4]; - _e[0] = e >> 24; - _e[1] = e >> 16; - _e[2] = e >> 8; - _e[3] = e >> 0; - cx_rsa_generate_pair(ksz << 1, rsa_pub, rsa_priv, _e, 4, pq); - - // write keys - nvm_write(&keygpg->pub_key.rsa, rsa_pub->e, 4); - nvm_write(pkey, rsa_priv, pkey_size); - if (reset_cnt) { - reset_cnt = 0; - nvm_write(&G_gpg_vstate.kslot->sig_count, &reset_cnt, sizeof(unsigned int)); - } - } - // --- ECC KEY --- - else if ((keygpg->attributes.value[0] == 19) || (keygpg->attributes.value[0] == 18) || - (keygpg->attributes.value[0] == 22)) { - unsigned int curve; - - ksz = 0; - curve = gpg_oid2curve(&keygpg->attributes.value[1], keygpg->attributes.length - 1); - if (curve == 0) { - THROW(SW_WRONG_DATA); - return 0; - } - ksz = gpg_curve2domainlen(curve); - if (ksz == len_p) { - G_gpg_vstate.work.ecfp.private.curve = curve; - G_gpg_vstate.work.ecfp.private.d_len = ksz; - os_memmove(G_gpg_vstate.work.ecfp.private.d, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, ksz); - cx_ecfp_generate_pair(curve, &G_gpg_vstate.work.ecfp.public, &G_gpg_vstate.work.ecfp.private, 1); - nvm_write(&keygpg->pub_key.ecfp, &G_gpg_vstate.work.ecfp.public, sizeof(cx_ecfp_public_key_t)); - nvm_write(&keygpg->priv_key.ecfp, &G_gpg_vstate.work.ecfp.private, sizeof(cx_ecfp_private_key_t)); - if (reset_cnt) { - reset_cnt = 0; - nvm_write(&G_gpg_vstate.kslot->sig_count, &reset_cnt, sizeof(unsigned int)); - } - } +/** + * Write a DO (Data Object) to the card + * + * @param[in] ref DO tag + * + * @return Status Word + * + */ +int gpg_apdu_put_data(unsigned int ref) { + unsigned int t, l, sw; + unsigned int *ptr_l = NULL; + unsigned char *ptr_v = NULL; + void *pkey = NULL; + cx_aes_key_t aes_key = {0}; + cx_err_t error = CX_INTERNAL_ERROR; + unsigned int pkey_size = 0; + unsigned int ksz, curve; - } - // --- UNSUPPORTED KEY --- - else { - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return 0; - } - break; - } // endof of 3fff - - /* ----------------- User -----------------*/ - /* Name */ - case 0x5B: - if (G_gpg_vstate.io_length > sizeof(N_gpg_pstate->name.value)) { - THROW(SW_WRONG_LENGTH); - return 0; - } - gpg_nvm_write(N_gpg_pstate->name.value, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); - gpg_nvm_write(&N_gpg_pstate->name.length, &G_gpg_vstate.io_length, sizeof(unsigned int)); - break; - /* Login data */ - case 0x5E: - if (G_gpg_vstate.io_length > sizeof(N_gpg_pstate->login.value)) { - THROW(SW_WRONG_LENGTH); - return 0; - } - gpg_nvm_write(N_gpg_pstate->login.value, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); - gpg_nvm_write(&N_gpg_pstate->login.length, &G_gpg_vstate.io_length, sizeof(unsigned int)); - break; - /* Language preferences */ - case 0x5F2D: - if (G_gpg_vstate.io_length > sizeof(N_gpg_pstate->lang.value)) { - THROW(SW_WRONG_LENGTH); - return 0; - } - gpg_nvm_write(N_gpg_pstate->lang.value, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); - gpg_nvm_write(&N_gpg_pstate->lang.length, &G_gpg_vstate.io_length, sizeof(unsigned int)); - break; - /* Sex */ - case 0x5F35: - if (G_gpg_vstate.io_length != sizeof(N_gpg_pstate->sex)) { - THROW(SW_WRONG_LENGTH); - return 0; - } - gpg_nvm_write(N_gpg_pstate->sex, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); - break; - /* Uniform resource locator */ - case 0x5F50: - if (G_gpg_vstate.io_length > sizeof(N_gpg_pstate->url.value)) { - THROW(SW_WRONG_LENGTH); - return 0; - } - gpg_nvm_write(N_gpg_pstate->url.value, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); - gpg_nvm_write(&N_gpg_pstate->url.length, &G_gpg_vstate.io_length, sizeof(unsigned int)); - break; - - /* ----------------- Cardholder certificate ----------------- */ - case 0x7F21: - ptr_v = NULL; - switch (G_gpg_vstate.DO_reccord) { - case 0: - ptr_l = &G_gpg_vstate.kslot->aut.CA.length; - ptr_v = G_gpg_vstate.kslot->aut.CA.value; - goto WRITE_CA; - case 1: - ptr_l = &G_gpg_vstate.kslot->sig.CA.length; - ptr_v = G_gpg_vstate.kslot->sig.CA.value; - goto WRITE_CA; - case 2: - ptr_l = &G_gpg_vstate.kslot->dec.CA.length; - ptr_v = G_gpg_vstate.kslot->dec.CA.value; - goto WRITE_CA; - default: - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return 0; - } - WRITE_CA: - if (G_gpg_vstate.io_length > GPG_EXT_CARD_HOLDER_CERT_LENTH) { - THROW(SW_WRONG_LENGTH); - } - gpg_nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); - gpg_nvm_write(ptr_l, &G_gpg_vstate.io_length, sizeof(unsigned int)); - break; - - /* ----------------- Algorithm attributes ----------------- */ - case 0xC1: - ptr_l = &G_gpg_vstate.kslot->sig.attributes.length; - ptr_v = G_gpg_vstate.kslot->sig.attributes.value; - goto WRITE_ATTRIBUTES; - case 0xC2: - ptr_l = &G_gpg_vstate.kslot->dec.attributes.length; - ptr_v = G_gpg_vstate.kslot->dec.attributes.value; - goto WRITE_ATTRIBUTES; - case 0xC3: - ptr_l = &G_gpg_vstate.kslot->aut.attributes.length; - ptr_v = G_gpg_vstate.kslot->aut.attributes.value; - goto WRITE_ATTRIBUTES; - WRITE_ATTRIBUTES: - if (G_gpg_vstate.io_length > 12) { - THROW(SW_WRONG_LENGTH); - return 0; - } - gpg_nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); - gpg_nvm_write(ptr_l, &G_gpg_vstate.io_length, sizeof(unsigned int)); - break; - - /* ----------------- PWS status ----------------- */ - case 0xC4: - gpg_io_fetch_nv(N_gpg_pstate->PW_status, 1); - break; - - /* ----------------- Fingerprints ----------------- */ - case 0xC7: - ptr_v = G_gpg_vstate.kslot->sig.fingerprints; - goto WRITE_FINGERPRINTS; - case 0xC8: - ptr_v = G_gpg_vstate.kslot->dec.fingerprints; - goto WRITE_FINGERPRINTS; - case 0xC9: - ptr_v = G_gpg_vstate.kslot->aut.fingerprints; - goto WRITE_FINGERPRINTS; - case 0xCA: - ptr_v = G_gpg_vstate.kslot->sig.CA_fingerprints; - goto WRITE_FINGERPRINTS; - case 0xCB: - ptr_v = G_gpg_vstate.kslot->dec.CA_fingerprints; - goto WRITE_FINGERPRINTS; - case 0xCC: - ptr_v = G_gpg_vstate.kslot->aut.CA_fingerprints; - goto WRITE_FINGERPRINTS; - WRITE_FINGERPRINTS: - if (G_gpg_vstate.io_length != 20) { - THROW(SW_WRONG_LENGTH); - return 0; - } - gpg_nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, 20); - break; - - /* ----------------- Generation date/time ----------------- */ - case 0xCE: - ptr_v = G_gpg_vstate.kslot->sig.date; - goto WRITE_DATE; - case 0xCF: - ptr_v = G_gpg_vstate.kslot->dec.date; - goto WRITE_DATE; - case 0xD0: - ptr_v = G_gpg_vstate.kslot->aut.date; - goto WRITE_DATE; - WRITE_DATE: - if (G_gpg_vstate.io_length != 4) { - THROW(SW_WRONG_LENGTH); - return 0; - } - gpg_nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, 4); - break; - - /* ----------------- AES key ----------------- */ - { - void * pkey; - cx_aes_key_t aes_key; - case 0xD1: - pkey = &N_gpg_pstate->SM_enc; - goto init_aes_key; - case 0xD2: - pkey = &N_gpg_pstate->SM_mac; - goto init_aes_key; - case 0xD5: - pkey = &G_gpg_vstate.kslot->AES_dec; - goto init_aes_key; - init_aes_key: - cx_aes_init_key(G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length, &aes_key); - gpg_nvm_write(pkey, &aes_key, sizeof(cx_aes_key_t)); - break; - - /* AES key: one shot */ - case 0xF4: - cx_aes_init_key(G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length, &aes_key); - gpg_nvm_write(&N_gpg_pstate->SM_enc, &aes_key, sizeof(cx_aes_key_t)); - cx_aes_init_key(G_gpg_vstate.work.io_buffer + 16, G_gpg_vstate.io_length, &aes_key); - gpg_nvm_write(&N_gpg_pstate->SM_mac, &aes_key, sizeof(cx_aes_key_t)); - break; - } + G_gpg_vstate.DO_current = ref; - /* ----------------- RC ----------------- */ - case 0xD3: { - gpg_pin_t *pin; + switch (ref) { + /* ----------------- Optional DO for private use ----------------- */ + case 0x0101: + ptr_l = (unsigned int *) &N_gpg_pstate->private_DO1.length; + ptr_v = (unsigned char *) N_gpg_pstate->private_DO1.value; + goto WRITE_PRIVATE_DO; + case 0x0102: + ptr_l = (unsigned int *) &N_gpg_pstate->private_DO2.length; + ptr_v = (unsigned char *) N_gpg_pstate->private_DO2.value; + goto WRITE_PRIVATE_DO; + case 0x0103: + ptr_l = (unsigned int *) &N_gpg_pstate->private_DO3.length; + ptr_v = (unsigned char *) N_gpg_pstate->private_DO3.value; + goto WRITE_PRIVATE_DO; + case 0x0104: + ptr_l = (unsigned int *) &N_gpg_pstate->private_DO4.length; + ptr_v = (unsigned char *) N_gpg_pstate->private_DO4.value; + goto WRITE_PRIVATE_DO; + WRITE_PRIVATE_DO: + if (G_gpg_vstate.io_length > GPG_EXT_PRIVATE_DO_LENGTH) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write(ptr_v, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + G_gpg_vstate.io_length); + nvm_write(ptr_l, &G_gpg_vstate.io_length, sizeof(unsigned int)); + sw = SW_OK; + break; + /* ----------------- Config key slot ----------------- */ + case 0x01F1: + if (G_gpg_vstate.io_length != 3) { + sw = SW_WRONG_LENGTH; + break; + } + if ((G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] != GPG_KEYS_SLOTS) || + (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] >= GPG_KEYS_SLOTS) || + (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 2] > 3)) { + sw = SW_WRONG_DATA; + break; + } + nvm_write((void *) N_gpg_pstate->config_slot, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + 3); + sw = SW_OK; + break; + + case 0x01F2: + if ((N_gpg_pstate->config_slot[2] & 1) == 0) { + sw = SW_CONDITIONS_NOT_SATISFIED; + break; + } + if ((G_gpg_vstate.io_length != 1) || + (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset] >= GPG_KEYS_SLOTS)) { + sw = SW_WRONG_DATA; + break; + } + G_gpg_vstate.slot = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; + G_gpg_vstate.kslot = (gpg_key_slot_t *) &N_gpg_pstate->keys[G_gpg_vstate.slot]; + gpg_mse_reset(); + ui_CCID_reset(); + sw = SW_OK; + break; + + /* ----------------- Config RSA exponent ----------------- */ + case 0x01F8: { + unsigned int e; + if (G_gpg_vstate.io_length != 4) { + sw = SW_WRONG_LENGTH; + break; + } + e = gpg_io_fetch_u32(); + nvm_write((void *) &N_gpg_pstate->default_RSA_exponent, &e, sizeof(unsigned int)); + sw = SW_OK; + break; + } - pin = gpg_pin_get_pin(PIN_ID_RC); - if (G_gpg_vstate.io_length == 0) { - gpg_nvm_write(pin, NULL, sizeof(gpg_pin_t)); + /* ----------------- Serial -----------------*/ + case 0x4f: + if (G_gpg_vstate.io_length != 4) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write(G_gpg_vstate.kslot->serial, + &G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset], + 4); + sw = SW_OK; + break; + + /* ----------------- Extended Header list -----------------*/ + case 0x3FFF: { + unsigned int len_e, len_p, len_q; + unsigned int endof, reset_cnt; + gpg_key_t *keygpg = NULL; + // fecth 4D + gpg_io_fetch_tl(&t, &l); + if (t != 0x4D) { + sw = SW_REFERENCED_DATA_NOT_FOUND; + break; + } + // fecth B8/B6/A4 + gpg_io_fetch_tl(&t, &l); + reset_cnt = 0; + switch (t) { + case KEY_SIG: + keygpg = &G_gpg_vstate.kslot->sig; + reset_cnt = 0x11111111; + break; + case KEY_AUT: + keygpg = &G_gpg_vstate.kslot->aut; + break; + case KEY_DEC: + keygpg = &G_gpg_vstate.kslot->dec; + break; + default: + break; + } + if (keygpg == NULL) { + sw = SW_REFERENCED_DATA_NOT_FOUND; + break; + } + // fecth 7f78 + gpg_io_fetch_tl(&t, &l); + if (t != 0x7f48) { + sw = SW_REFERENCED_DATA_NOT_FOUND; + break; + } + len_e = 0; + len_p = 0; + len_q = 0; + endof = G_gpg_vstate.io_offset + l; + while (G_gpg_vstate.io_offset < endof) { + gpg_io_fetch_tl(&t, &l); + switch (t) { + case 0x91: + len_e = l; + break; + case 0x92: + len_p = l; + break; + case 0x93: + len_q = l; + break; + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x99: + break; + default: + return SW_REFERENCED_DATA_NOT_FOUND; + } + } + // fecth 5f78 + gpg_io_fetch_tl(&t, &l); + if (t != 0x5f48) { + sw = SW_REFERENCED_DATA_NOT_FOUND; + break; + } + + if (keygpg->attributes.value[0] == KEY_ID_RSA) { + unsigned int e = 0; + unsigned char *p, *q, *pq; + cx_rsa_public_key_t *rsa_pub; + cx_rsa_private_key_t *rsa_priv; + // check length + ksz = U2BE(keygpg->attributes.value, 1) >> 3; + rsa_pub = (cx_rsa_public_key_t *) &G_gpg_vstate.work.rsa.public; + rsa_priv = (cx_rsa_private_key_t *) &G_gpg_vstate.work.rsa.private; + switch (ksz) { + case 2048 / 8: + pkey_size = sizeof(cx_rsa_2048_private_key_t); + pq = G_gpg_vstate.work.rsa.public2048.n; + break; + case 3072 / 8: + pkey_size = sizeof(cx_rsa_3072_private_key_t); + pq = G_gpg_vstate.work.rsa.public3072.n; + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096 / 8: + pkey_size = sizeof(cx_rsa_4096_private_key_t); + pq = G_gpg_vstate.work.rsa.public4096.n; + break; +#endif + default: + break; + } + if (pkey_size == 0) { + sw = SW_WRONG_DATA; + break; + } + ksz = ksz >> 1; + + // fetch e + switch (len_e) { + case 4: + e = gpg_io_fetch_u32(); + break; + case 3: + e = gpg_io_fetch_u24(); + break; + case 2: + e = gpg_io_fetch_u16(); + break; + case 1: + e = gpg_io_fetch_u8(); + break; + default: + break; + } + if (e == 0) { + sw = SW_WRONG_DATA; + break; + } + + // move p,q over pub key, this only work because adr < adr

+ if ((len_p > ksz) || (len_q > ksz)) { + sw = SW_WRONG_DATA; + break; + } + p = G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset; + q = p + len_p; + memmove(pq + ksz - len_p, p, len_p); + explicit_bzero(pq, ksz - len_p); + memmove(pq + 2 * ksz - len_q, q, len_q); + explicit_bzero(pq + ksz, ksz - len_q); + + // regenerate RSA private key + unsigned char _e[4]; + U4BE_ENCODE(_e, 0, e); + CX_CHECK(cx_rsa_generate_pair_no_throw(ksz << 1, rsa_pub, rsa_priv, _e, 4, pq)); + + // write keys + nvm_write(&keygpg->pub_key.rsa, rsa_pub->e, 4); + nvm_write(&keygpg->priv_key.rsa, rsa_priv, pkey_size); + if (reset_cnt) { + reset_cnt = 0; + nvm_write(&G_gpg_vstate.kslot->sig_count, &reset_cnt, sizeof(unsigned int)); + } + sw = SW_OK; + } else if ((keygpg->attributes.value[0] == KEY_ID_ECDH) || + (keygpg->attributes.value[0] == KEY_ID_ECDSA) || + (keygpg->attributes.value[0] == KEY_ID_EDDSA)) { + curve = gpg_oid2curve(&keygpg->attributes.value[1], keygpg->attributes.length - 1); + if (curve == CX_CURVE_NONE) { + sw = SW_WRONG_DATA; + break; + } + ksz = gpg_curve2domainlen(curve); + if (ksz == len_p) { + G_gpg_vstate.work.ecfp.private.curve = curve; + G_gpg_vstate.work.ecfp.private.d_len = ksz; + memmove(G_gpg_vstate.work.ecfp.private.d, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + ksz); + CX_CHECK(cx_ecfp_generate_pair_no_throw(curve, + &G_gpg_vstate.work.ecfp.public, + &G_gpg_vstate.work.ecfp.private, + 1)); + nvm_write(&keygpg->pub_key.ecfp, + &G_gpg_vstate.work.ecfp.public, + sizeof(cx_ecfp_public_key_t)); + nvm_write(&keygpg->priv_key.ecfp, + &G_gpg_vstate.work.ecfp.private, + sizeof(cx_ecfp_private_key_t)); + if (reset_cnt) { + reset_cnt = 0; + nvm_write(&G_gpg_vstate.kslot->sig_count, &reset_cnt, sizeof(unsigned int)); + } + } + sw = SW_OK; + } + // --- UNSUPPORTED KEY --- + else { + sw = SW_REFERENCED_DATA_NOT_FOUND; + } + break; + } // endof of 3fff + + /* ----------------- User -----------------*/ + /* Name */ + case 0x5B: + if (G_gpg_vstate.io_length > sizeof(N_gpg_pstate->name.value)) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write((void *) N_gpg_pstate->name.value, + G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length); + nvm_write((void *) &N_gpg_pstate->name.length, + &G_gpg_vstate.io_length, + sizeof(unsigned int)); + sw = SW_OK; + break; + /* Login data */ + case 0x5E: + if (G_gpg_vstate.io_length > sizeof(N_gpg_pstate->login.value)) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write((void *) N_gpg_pstate->login.value, + G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length); + nvm_write((void *) &N_gpg_pstate->login.length, + &G_gpg_vstate.io_length, + sizeof(unsigned int)); + sw = SW_OK; + break; + /* Language preferences */ + case 0x5F2D: + if (G_gpg_vstate.io_length > sizeof(N_gpg_pstate->lang.value)) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write((void *) N_gpg_pstate->lang.value, + G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length); + nvm_write((void *) &N_gpg_pstate->lang.length, + &G_gpg_vstate.io_length, + sizeof(unsigned int)); + sw = SW_OK; + break; + /* salutation */ + case 0x5F35: + if (G_gpg_vstate.io_length != sizeof(N_gpg_pstate->salutation)) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write((void *) N_gpg_pstate->salutation, + G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length); + sw = SW_OK; + break; + /* Uniform resource locator */ + case 0x5F50: + if (G_gpg_vstate.io_length > sizeof(N_gpg_pstate->keys[G_gpg_vstate.slot].url.value)) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write((void *) N_gpg_pstate->keys[G_gpg_vstate.slot].url.value, + G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length); + nvm_write((void *) &N_gpg_pstate->keys[G_gpg_vstate.slot].url.length, + &G_gpg_vstate.io_length, + sizeof(unsigned int)); + sw = SW_OK; + break; + + /* ----------------- Cardholder certificate ----------------- */ + case 0x7F21: + ptr_v = NULL; + switch (G_gpg_vstate.DO_reccord) { + case 0: + ptr_l = &G_gpg_vstate.kslot->aut.CA.length; + ptr_v = G_gpg_vstate.kslot->aut.CA.value; + break; + case 1: + ptr_l = &G_gpg_vstate.kslot->sig.CA.length; + ptr_v = G_gpg_vstate.kslot->sig.CA.value; + break; + case 2: + ptr_l = &G_gpg_vstate.kslot->dec.CA.length; + ptr_v = G_gpg_vstate.kslot->dec.CA.value; + break; + default: + break; + } + if (ptr_v == NULL) { + sw = SW_REFERENCED_DATA_NOT_FOUND; + break; + } + if (G_gpg_vstate.io_length > GPG_EXT_CARD_HOLDER_CERT_LENTH) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); + nvm_write(ptr_l, &G_gpg_vstate.io_length, sizeof(unsigned int)); + sw = SW_OK; + break; + + /* ----------------- Algorithm attributes ----------------- */ + case 0xC1: + ptr_l = &G_gpg_vstate.kslot->sig.attributes.length; + ptr_v = G_gpg_vstate.kslot->sig.attributes.value; + goto WRITE_ATTRIBUTES; + case 0xC2: + ptr_l = &G_gpg_vstate.kslot->dec.attributes.length; + ptr_v = G_gpg_vstate.kslot->dec.attributes.value; + goto WRITE_ATTRIBUTES; + case 0xC3: + ptr_l = &G_gpg_vstate.kslot->aut.attributes.length; + ptr_v = G_gpg_vstate.kslot->aut.attributes.value; + goto WRITE_ATTRIBUTES; + WRITE_ATTRIBUTES: + if (G_gpg_vstate.io_length > 12) { + sw = SW_WRONG_LENGTH; + break; + } + switch (G_gpg_vstate.work.io_buffer[0]) { + case KEY_ID_RSA: + ksz = U2BE(G_gpg_vstate.work.io_buffer, 1); + if ((ksz != 2048) && (ksz != 3072)) { + sw = SW_WRONG_DATA; + } else { + sw = SW_OK; + } + break; + case KEY_ID_ECDH: + case KEY_ID_ECDSA: + case KEY_ID_EDDSA: + curve = + gpg_oid2curve(G_gpg_vstate.work.io_buffer + 1, G_gpg_vstate.io_length - 1); + if (curve == CX_CURVE_NONE) { + sw = SW_WRONG_DATA; + } else { + sw = SW_OK; + } + break; + default: + sw = SW_WRONG_DATA; + break; + } + + if (sw == SW_OK) { + nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); + nvm_write(ptr_l, &G_gpg_vstate.io_length, sizeof(unsigned int)); + } + break; + + /* ----------------- PWS status ----------------- */ + case 0xC4: + gpg_io_fetch_nv((unsigned char *) N_gpg_pstate->PW_status, 1); + sw = SW_OK; + break; + + /* ----------------- Fingerprints ----------------- */ + case 0xC7: + ptr_v = G_gpg_vstate.kslot->sig.fingerprints; + goto WRITE_FINGERPRINTS; + case 0xC8: + ptr_v = G_gpg_vstate.kslot->dec.fingerprints; + goto WRITE_FINGERPRINTS; + case 0xC9: + ptr_v = G_gpg_vstate.kslot->aut.fingerprints; + goto WRITE_FINGERPRINTS; + case 0xCA: + ptr_v = G_gpg_vstate.kslot->sig.CA_fingerprints; + goto WRITE_FINGERPRINTS; + case 0xCB: + ptr_v = G_gpg_vstate.kslot->dec.CA_fingerprints; + goto WRITE_FINGERPRINTS; + case 0xCC: + ptr_v = G_gpg_vstate.kslot->aut.CA_fingerprints; + goto WRITE_FINGERPRINTS; + WRITE_FINGERPRINTS: + if (G_gpg_vstate.io_length != 20) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, 20); + sw = SW_OK; + break; + + /* ----------------- Generation date/time ----------------- */ + case 0xCE: + ptr_v = G_gpg_vstate.kslot->sig.date; + goto WRITE_DATE; + case 0xCF: + ptr_v = G_gpg_vstate.kslot->dec.date; + goto WRITE_DATE; + case 0xD0: + ptr_v = G_gpg_vstate.kslot->aut.date; + goto WRITE_DATE; + WRITE_DATE: + if (G_gpg_vstate.io_length != 4) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, 4); + sw = SW_OK; + break; + + /* ----------------- AES key ----------------- */ + case 0xD1: + pkey = (void *) &N_gpg_pstate->SM_enc; + goto init_aes_key; + case 0xD2: + pkey = (void *) &N_gpg_pstate->SM_mac; + goto init_aes_key; + case 0xD5: + pkey = &G_gpg_vstate.kslot->AES_dec; + goto init_aes_key; + init_aes_key: + CX_CHECK(cx_aes_init_key_no_throw(G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length, + &aes_key)); + nvm_write(pkey, &aes_key, sizeof(cx_aes_key_t)); + sw = SW_OK; + break; + + /* AES key: one shot */ + case 0xF4: + CX_CHECK(cx_aes_init_key_no_throw(G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length, + &aes_key)); + nvm_write((void *) &N_gpg_pstate->SM_enc, &aes_key, sizeof(cx_aes_key_t)); + CX_CHECK(cx_aes_init_key_no_throw(G_gpg_vstate.work.io_buffer + CX_AES_128_KEY_LEN, + G_gpg_vstate.io_length, + &aes_key)); + nvm_write((void *) &N_gpg_pstate->SM_mac, &aes_key, sizeof(cx_aes_key_t)); + sw = SW_OK; + break; + + /* ----------------- RC ----------------- */ + case 0xD3: { + gpg_pin_t *pin; + + pin = gpg_pin_get_pin(PIN_ID_RC); + if (G_gpg_vstate.io_length == 0) { + nvm_write(pin, NULL, sizeof(gpg_pin_t)); + sw = SW_OK; + } else if ((G_gpg_vstate.io_length > GPG_MAX_PW_LENGTH) || + (G_gpg_vstate.io_length < 8)) { + sw = SW_WRONG_DATA; + } else { + sw = gpg_pin_set(pin, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + G_gpg_vstate.io_length); + } + break; + } - } else if ((G_gpg_vstate.io_length > GPG_MAX_PW_LENGTH) || (G_gpg_vstate.io_length < 8)) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } else { - gpg_pin_set(pin, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, G_gpg_vstate.io_length); - } - sw = SW_OK; - break; - } - - /* ----------------- UIF ----------------- */ - case 0xD6: - ptr_v = G_gpg_vstate.kslot->sig.UIF; - goto WRITE_UIF; - case 0xD7: - ptr_v = G_gpg_vstate.kslot->dec.UIF; - goto WRITE_UIF; - case 0xD8: - ptr_v = G_gpg_vstate.kslot->aut.UIF; - goto WRITE_UIF; - WRITE_UIF: - if (G_gpg_vstate.io_length != 2) { - THROW(SW_WRONG_LENGTH); - return 0; + /* ----------------- UIF ----------------- */ + case 0xD6: + ptr_v = G_gpg_vstate.kslot->sig.UIF; + goto WRITE_UIF; + case 0xD7: + ptr_v = G_gpg_vstate.kslot->dec.UIF; + goto WRITE_UIF; + case 0xD8: + ptr_v = G_gpg_vstate.kslot->aut.UIF; + goto WRITE_UIF; + WRITE_UIF: + if (G_gpg_vstate.io_length != 2) { + sw = SW_WRONG_LENGTH; + break; + } + nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, 2); + sw = SW_OK; + break; + + /* ----------------- WAT ----------------- */ + default: + sw = SW_REFERENCED_DATA_NOT_FOUND; + break; } - gpg_nvm_write(ptr_v, G_gpg_vstate.work.io_buffer, 2); - break; - - /* ----------------- WAT ----------------- */ - default: - sw = SW_REFERENCED_DATA_NOT_FOUND; - break; - } - gpg_io_discard(1); - return sw; + gpg_io_discard(1); + return sw; +end: + return error; } -static void gpg_init_keyenc(cx_aes_key_t *keyenc) { - unsigned char seed[32]; +/** + * Init an encryption key to protect Private Key + * Used for Backup/Restore + * + * @param[out] keyenc aes encryption key + * + * @return Status Word + * + */ +static int gpg_init_keyenc(cx_aes_key_t *keyenc) { + int sw = SW_UNKNOWN; + unsigned char seed[32]; + cx_err_t error = CX_INTERNAL_ERROR; + + sw = gpg_pso_derive_slot_seed(G_gpg_vstate.slot, seed); + if (sw != SW_OK) { + return sw; + } + sw = gpg_pso_derive_key_seed(seed, (unsigned char *) PIC("key "), 1, seed, CX_AES_BLOCK_SIZE); + if (sw != SW_OK) { + return sw; + } + CX_CHECK(cx_aes_init_key_no_throw(seed, CX_AES_BLOCK_SIZE, keyenc)); - gpg_pso_derive_slot_seed(G_gpg_vstate.slot, seed); - gpg_pso_derive_key_seed(seed, (unsigned char *)PIC("key "), 1, seed, 16); - cx_aes_init_key(seed, 16, keyenc); +end: + if (error != CX_OK) { + return error; + } + return SW_OK; } -// cmd -// resp TID API COMPAT len_pub len_priv priv +/** + * Read a Key DO (Data Object) from the card + * for Backup + * + * @param[in] ref DO tag + * + * @return Status Word + * + */ int gpg_apdu_get_key_data(unsigned int ref) { - cx_aes_key_t keyenc; - gpg_key_t * keygpg; - unsigned int len = 0; - gpg_init_keyenc(&keyenc); - - switch (ref) { - case 0x00B6: - keygpg = &G_gpg_vstate.kslot->sig; - break; - case 0x00B8: - keygpg = &G_gpg_vstate.kslot->dec; - break; - case 0x00A4: - keygpg = &G_gpg_vstate.kslot->aut; - break; - default: - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - - gpg_io_discard(1); - // clear part - gpg_io_insert_u32(TARGET_ID); - gpg_io_insert_u32(CX_APILEVEL); - gpg_io_insert_u32(CX_COMPAT_APILEVEL); - // encrypted part - switch (keygpg->attributes.value[0]) { - case 0x01: // RSA - // insert pubkey; - gpg_io_insert_u32(4); - gpg_io_insert(keygpg->pub_key.rsa, 4); - - // insert privkey - gpg_io_mark(); - len = cx_aes(&keyenc, CX_ENCRYPT | CX_CHAIN_CBC | CX_PAD_ISO9797M2 | CX_LAST, - (unsigned char *)&keygpg->priv_key.rsa4096, sizeof(cx_rsa_4096_private_key_t), - G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, GPG_IO_BUFFER_LENGTH - G_gpg_vstate.io_offset); - gpg_io_inserted(len); - gpg_io_set_offset(IO_OFFSET_MARK); - gpg_io_insert_u32(len); - gpg_io_set_offset(IO_OFFSET_END); - break; - - case 18: // ECC - case 19: - case 22: - // insert pubkey; - gpg_io_insert_u32(sizeof(cx_ecfp_640_public_key_t)); - gpg_io_insert((unsigned char *)&keygpg->pub_key.ecfp640, sizeof(cx_ecfp_640_public_key_t)); - - // insert privkey - gpg_io_mark(); - len = cx_aes(&keyenc, CX_ENCRYPT | CX_CHAIN_CBC | CX_PAD_ISO9797M2 | CX_LAST, - (unsigned char *)&keygpg->priv_key.ecfp640, sizeof(cx_ecfp_640_private_key_t), - G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, GPG_IO_BUFFER_LENGTH - G_gpg_vstate.io_offset); - gpg_io_inserted(len); - gpg_io_set_offset(IO_OFFSET_MARK); - gpg_io_insert_u32(len); - gpg_io_set_offset(IO_OFFSET_END); - break; - - default: - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; - } - return SW_OK; -} - -// cmd TID API COMPAT len_pub len_priv priv -// resp - -int gpg_apdu_put_key_data(unsigned int ref) { - cx_aes_key_t keyenc; - gpg_key_t * keygpg; - unsigned int len; - unsigned int offset; - gpg_init_keyenc(&keyenc); - - switch (ref) { - case 0xB6: - keygpg = &G_gpg_vstate.kslot->sig; - break; - case 0xB8: - keygpg = &G_gpg_vstate.kslot->dec; - break; - case 0xA4: - keygpg = &G_gpg_vstate.kslot->aut; - break; - default: - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - - /* unsigned int target_id = */ - gpg_io_fetch_u32(); - /* unsigned int cx_apilevel = */ - gpg_io_fetch_u32(); - /* unsigned int cx_compat_apilevel = */ - gpg_io_fetch_u32(); - - switch (keygpg->attributes.value[0]) { - // RSA - case 0x01: - // insert pubkey; - len = gpg_io_fetch_u32(); - if (len != 4) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; + cx_aes_key_t keyenc = {0}; + gpg_key_t *keygpg = NULL; + cx_rsa_private_key_t *key = NULL; + unsigned int len = 0; + cx_err_t error = CX_INTERNAL_ERROR; + int sw = SW_UNKNOWN; + unsigned int ksz = 0; + + sw = gpg_init_keyenc(&keyenc); + if (sw != SW_OK) { + return sw; + } + switch (ref) { + case 0x00B6: + keygpg = &G_gpg_vstate.kslot->sig; + break; + case 0x00B8: + keygpg = &G_gpg_vstate.kslot->dec; + break; + case 0x00A4: + keygpg = &G_gpg_vstate.kslot->aut; + break; + default: + return SW_WRONG_DATA; } - gpg_io_fetch_nv(keygpg->pub_key.rsa, len); - // insert privkey - len = gpg_io_fetch_u32(); - if (len > (G_gpg_vstate.io_length - G_gpg_vstate.io_offset)) { - THROW(SW_WRONG_DATA); + gpg_io_discard(1); + // clear part + gpg_io_insert_u32(TARGET_ID); + gpg_io_insert_u32(get_api_level()); + + // encrypted part + switch (keygpg->attributes.value[0]) { + case KEY_ID_RSA: + ksz = U2BE(G_gpg_vstate.mse_dec->attributes.value, 1) >> 3; + switch (ksz) { + case 2048 / 8: + key = (cx_rsa_private_key_t *) &keygpg->priv_key.rsa2048; + len = sizeof(cx_rsa_2048_private_key_t); + break; + case 3072 / 8: + key = (cx_rsa_private_key_t *) &keygpg->priv_key.rsa3072; + len = sizeof(cx_rsa_3072_private_key_t); + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096 / 8: + key = (cx_rsa_private_key_t *) &keygpg->priv_key.rsa4096; + len = sizeof(cx_rsa_4096_private_key_t); + break; +#endif + } + + if ((key == NULL) || (key->size != ksz)) { + return SW_CONDITIONS_NOT_SATISFIED; + } + + // insert pubkey + gpg_io_insert_u32(4); + gpg_io_insert(keygpg->pub_key.rsa, 4); + + // insert privkey + gpg_io_mark(); + CX_CHECK(cx_aes_no_throw(&keyenc, + CX_ENCRYPT | CX_CHAIN_CBC | CX_PAD_ISO9797M2 | CX_LAST, + (unsigned char *) key, + len, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + &len)); + gpg_io_inserted(len); + gpg_io_set_offset(IO_OFFSET_MARK); + gpg_io_insert_u32(len); + gpg_io_set_offset(IO_OFFSET_END); + sw = SW_OK; + break; + + case KEY_ID_ECDH: + case KEY_ID_ECDSA: + case KEY_ID_EDDSA: + // insert pubkey + gpg_io_insert_u32(sizeof(cx_ecfp_640_public_key_t)); + gpg_io_insert((unsigned char *) &keygpg->pub_key.ecfp640, + sizeof(cx_ecfp_640_public_key_t)); + + // insert privkey + gpg_io_mark(); + len = GPG_IO_BUFFER_LENGTH - G_gpg_vstate.io_offset; + CX_CHECK(cx_aes_no_throw(&keyenc, + CX_ENCRYPT | CX_CHAIN_CBC | CX_PAD_ISO9797M2 | CX_LAST, + (unsigned char *) &keygpg->priv_key.ecfp640, + sizeof(cx_ecfp_640_private_key_t), + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + &len)); + gpg_io_inserted(len); + gpg_io_set_offset(IO_OFFSET_MARK); + gpg_io_insert_u32(len); + gpg_io_set_offset(IO_OFFSET_END); + sw = SW_OK; + break; + + default: + sw = SW_REFERENCED_DATA_NOT_FOUND; + break; } - offset = G_gpg_vstate.io_offset; - gpg_io_discard(0); - len = cx_aes(&keyenc, CX_DECRYPT | CX_CHAIN_CBC | CX_PAD_ISO9797M2 | CX_LAST, G_gpg_vstate.work.io_buffer + offset, - len, G_gpg_vstate.work.io_buffer, GPG_IO_BUFFER_LENGTH); - if (len != sizeof(cx_rsa_4096_private_key_t)) { - THROW(SW_WRONG_DATA); + return sw; +end: + return error; +} + +/** + * Write a key DO (Data Object) to the card + * For Restore + * + * @param[in] ref DO tag + * + * @return Status Word + * + */ +int gpg_apdu_put_key_data(unsigned int ref) { + cx_aes_key_t keyenc = {0}; + gpg_key_t *keygpg = NULL; + unsigned int len = 0; + cx_rsa_private_key_t *key = NULL; + unsigned int offset = 0; + cx_err_t error = CX_INTERNAL_ERROR; + int sw = SW_UNKNOWN; + unsigned int ksz = 0; + + sw = gpg_init_keyenc(&keyenc); + if (sw != SW_OK) { + return sw; } - gpg_nvm_write((unsigned char *)&keygpg->priv_key.rsa4096, G_gpg_vstate.work.io_buffer, len); - break; - - // ECC - case 18: /* 12h */ - case 19: /* 13h */ - case 22: /* 16h */ - // insert pubkey; - len = gpg_io_fetch_u32(); - if (len != sizeof(cx_ecfp_640_public_key_t)) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; + switch (ref) { + case KEY_SIG: + keygpg = &G_gpg_vstate.kslot->sig; + break; + case KEY_DEC: + keygpg = &G_gpg_vstate.kslot->dec; + break; + case KEY_AUT: + keygpg = &G_gpg_vstate.kslot->aut; + break; + default: + return SW_WRONG_DATA; } - gpg_io_fetch_nv((unsigned char *)&keygpg->pub_key.ecfp640, len); - // insert privkey - len = gpg_io_fetch_u32(); - if (len > (G_gpg_vstate.io_length - G_gpg_vstate.io_offset)) { - THROW(SW_WRONG_DATA); - } - offset = G_gpg_vstate.io_offset; - gpg_io_discard(0); - - len = cx_aes(&keyenc, CX_DECRYPT | CX_CHAIN_CBC | CX_PAD_ISO9797M2 | CX_LAST, G_gpg_vstate.work.io_buffer + offset, - len, G_gpg_vstate.work.io_buffer, GPG_IO_BUFFER_LENGTH); - if (len != sizeof(cx_ecfp_640_private_key_t)) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; + /* unsigned int target_id = */ + gpg_io_fetch_u32(); + /* unsigned int cx_apilevel = */ + gpg_io_fetch_u32(); + + switch (keygpg->attributes.value[0]) { + case KEY_ID_RSA: + // insert pubkey + len = gpg_io_fetch_u32(); + if (len != 4) { + sw = SW_WRONG_DATA; + break; + } + gpg_io_fetch_nv(keygpg->pub_key.rsa, len); + + // insert privkey + len = gpg_io_fetch_u32(); + if (len > (G_gpg_vstate.io_length - G_gpg_vstate.io_offset)) { + sw = SW_WRONG_DATA; + break; + } + offset = G_gpg_vstate.io_offset; + ksz = U2BE(G_gpg_vstate.mse_dec->attributes.value, 1) >> 3; + switch (ksz) { + case 2048 / 8: + key = (cx_rsa_private_key_t *) &keygpg->priv_key.rsa2048; + len = sizeof(cx_rsa_2048_private_key_t); + break; + case 3072 / 8: + key = (cx_rsa_private_key_t *) &keygpg->priv_key.rsa3072; + len = sizeof(cx_rsa_3072_private_key_t); + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096 / 8: + key = (cx_rsa_private_key_t *) &keygpg->priv_key.rsa4096; + len = sizeof(cx_rsa_4096_private_key_t); + break; +#endif + } + + if ((key == NULL) || (key->size != ksz)) { + sw = SW_CONDITIONS_NOT_SATISFIED; + break; + } + + gpg_io_discard(0); + CX_CHECK(cx_aes_no_throw(&keyenc, + CX_DECRYPT | CX_CHAIN_CBC | CX_PAD_ISO9797M2 | CX_LAST, + G_gpg_vstate.work.io_buffer + offset, + len, + G_gpg_vstate.work.io_buffer, + &ksz)); + if (len != ksz) { + sw = SW_WRONG_DATA; + break; + } + nvm_write((unsigned char *) key, G_gpg_vstate.work.io_buffer, len); + sw = SW_OK; + break; + + case KEY_ID_ECDH: + case KEY_ID_ECDSA: + case KEY_ID_EDDSA: + // insert pubkey + len = gpg_io_fetch_u32(); + if (len != sizeof(cx_ecfp_640_public_key_t)) { + sw = SW_WRONG_DATA; + break; + } + gpg_io_fetch_nv((unsigned char *) &keygpg->pub_key.ecfp640, len); + + // insert privkey + len = gpg_io_fetch_u32(); + if (len > (G_gpg_vstate.io_length - G_gpg_vstate.io_offset)) { + sw = SW_WRONG_DATA; + break; + } + offset = G_gpg_vstate.io_offset; + gpg_io_discard(0); + + len = GPG_IO_BUFFER_LENGTH; + CX_CHECK(cx_aes_no_throw(&keyenc, + CX_DECRYPT | CX_CHAIN_CBC | CX_PAD_ISO9797M2 | CX_LAST, + G_gpg_vstate.work.io_buffer + offset, + len, + G_gpg_vstate.work.io_buffer, + &len)); + if (len != sizeof(cx_ecfp_640_private_key_t)) { + sw = SW_WRONG_DATA; + break; + } + nvm_write((unsigned char *) &keygpg->priv_key.ecfp640, + G_gpg_vstate.work.io_buffer, + len); + sw = SW_OK; + break; + + default: + sw = SW_REFERENCED_DATA_NOT_FOUND; + break; } - gpg_nvm_write((unsigned char *)&keygpg->priv_key.ecfp640, G_gpg_vstate.work.io_buffer, len); - break; - - default: - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; - } - gpg_io_discard(1); - return SW_OK; + gpg_io_discard(1); + return sw; +end: + return error; } diff --git a/src/gpg_dispatch.c b/src/gpg_dispatch.c index 61ff626..6f2eed3 100644 --- a/src/gpg_dispatch.c +++ b/src/gpg_dispatch.c @@ -1,429 +1,442 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" -void gpg_check_access_ins() { - unsigned int ref; - - ref = (G_gpg_vstate.io_p1 << 8) | G_gpg_vstate.io_p2; - - switch (G_gpg_vstate.io_ins) { - case INS_EXIT: - if (gpg_pin_is_verified(PIN_ID_PW2)) { - return; - } - break; - #ifdef GPG_LOG - #warning GPG_LOG activated - case INS_GET_LOG: - return; - #endif - - case INS_SELECT: - return; - case INS_GET_DATA: - case INS_GET_NEXT_DATA: - return; - - case INS_VERIFY: - return; - - case INS_CHANGE_REFERENCE_DATA: - return; - - case INS_RESET_RETRY_COUNTER: - if (gpg_pin_is_verified(PIN_ID_PW3) || gpg_pin_is_verified(PIN_ID_RC)) { - return; - } - break; - - case INS_PUT_DATA: - case INS_PUT_DATA_ODD: - return; - - case INS_GEN_ASYM_KEYPAIR: - if (G_gpg_vstate.io_p1 == 0x81) { - return; - } - if (gpg_pin_is_verified(PIN_ID_PW3)) { - return; - } - break; - - case INS_MSE: - return; - - case INS_PSO: - if ((ref == 0x9e9a) && gpg_pin_is_verified(PIN_ID_PW1)) { - // pso:sign - if (N_gpg_pstate->PW_status[0] == 0) { - gpg_pin_set_verified(PIN_ID_PW1, 0); - } - return; - } - if (((ref == 0x8086) || (ref == 0x8680)) && gpg_pin_is_verified(PIN_ID_PW2)) { - // pso:dec/enc - return; - } - break; - - case INS_INTERNAL_AUTHENTICATE: - if (gpg_pin_is_verified(PIN_ID_PW2)) { - return; - } - break; - - case INS_GET_CHALLENGE: - return; - - case INS_TERMINATE_DF: - if (gpg_pin_is_verified(PIN_ID_PW3)) { - return; +/** + * Check INS access condition + * Verify if the corresponding PW is verified + * + * @return Status Word + * + */ +static int gpg_check_access_ins() { + int sw = SW_UNKNOWN; + + switch (G_gpg_vstate.io_ins) { + case INS_EXIT: + if (gpg_pin_is_verified(PIN_ID_PW2)) { + sw = SW_OK; + } + break; + + case INS_RESET_RETRY_COUNTER: + if (gpg_pin_is_verified(PIN_ID_PW3) || gpg_pin_is_verified(PIN_ID_RC)) { + sw = SW_OK; + } + break; + + case INS_GEN_ASYM_KEYPAIR: + if ((G_gpg_vstate.io_p1 == ((READ_ASYM_KEY >> 8) & 0xFF)) || + (gpg_pin_is_verified(PIN_ID_PW3))) { + sw = SW_OK; + } + break; + + case INS_PSO: + if ((G_gpg_vstate.io_p1p2 == PSO_CDS) && gpg_pin_is_verified(PIN_ID_PW1)) { + // pso:sign + if (N_gpg_pstate->PW_status[0] == 0) { + gpg_pin_set_verified(PIN_ID_PW1, 0); + } + sw = SW_OK; + break; + } + if (((G_gpg_vstate.io_p1p2 == PSO_DEC) || (G_gpg_vstate.io_p1p2 == PSO_ENC)) && + gpg_pin_is_verified(PIN_ID_PW2)) { + // pso:dec/enc + sw = SW_OK; + } + break; + + case INS_INTERNAL_AUTHENTICATE: + if (gpg_pin_is_verified(PIN_ID_PW2)) { + sw = SW_OK; + } + break; + + case INS_TERMINATE_DF: + if (gpg_pin_is_verified(PIN_ID_PW3)) { + sw = SW_OK; + } + break; + +#ifdef GPG_LOG +#warning GPG_LOG activated + case INS_GET_LOG: +#endif + case INS_SELECT: + case INS_GET_DATA: + case INS_GET_NEXT_DATA: + case INS_VERIFY: + case INS_CHANGE_REFERENCE_DATA: + case INS_PUT_DATA: + case INS_PUT_DATA_ODD: + case INS_MSE: + case INS_GET_CHALLENGE: + case INS_ACTIVATE_FILE: + sw = SW_OK; + break; + + default: + sw = SW_INS_NOT_SUPPORTED; + break; } - break; - - case INS_ACTIVATE_FILE: - return; - - default: - THROW(SW_INS_NOT_SUPPORTED); - break; - } - THROW(SW_CONDITIONS_NOT_SATISFIED); + return sw; } -void gpg_check_access_read_DO() { - unsigned int ref; - - ref = (G_gpg_vstate.io_p1 << 8) | G_gpg_vstate.io_p2; - - switch (ref) { - // ALWAYS - case 0x0101: - case 0x0102: - case 0x01F0: - case 0x01F1: - case 0x01F2: - case 0x01F8: - case 0x006E: - case 0x0065: - case 0x0073: - case 0x007A: - case 0x004F: - case 0x005E: - case 0x005B: - case 0x5F2D: - case 0x5F35: - case 0x5F50: - case 0x5F52: - case 0x7F21: - case 0x0093: - case 0x00C0: - case 0x00C1: - case 0x00C2: - case 0x00C3: - case 0x00C4: - case 0x00C5: - case 0x00C7: - case 0x00C8: - case 0x00C9: - case 0x00C6: - case 0x00CA: - case 0x00CD: - case 0x00CC: - case 0x00CE: - case 0x00CF: - case 0x00D0: - case 0x7F74: - case 0x7F66: - case 0x00D6: - case 0x00D7: - case 0x00D8: - return; - - // PW2 - case 0x0103: - if (gpg_pin_is_verified(PIN_ID_PW2)) { - return; - } - break; - - // PW3 - case 0x00B6: - case 0x00A4: - case 0x00B8: - case 0x0104: - if (gpg_pin_is_verified(PIN_ID_PW3)) { - return; +/** + * Check INS Read access condition + * Verify if the corresponding PW is verified + * + * @return Status Word + * + */ +static int gpg_check_access_read_DO() { + int sw = SW_UNKNOWN; + + switch (G_gpg_vstate.io_p1p2) { + // ALWAYS + case 0x0101: + case 0x0102: + case 0x01F0: + case 0x01F1: + case 0x01F2: + case 0x01F8: + case 0x006E: + case 0x0065: + case 0x0073: + case 0x007A: + case 0x004F: + case 0x005E: + case 0x005B: + case 0x5F2D: + case 0x5F35: + case 0x5F50: + case 0x5F52: + case 0x7F21: + case 0x0093: + case 0x00C0: + case 0x00C1: + case 0x00C2: + case 0x00C3: + case 0x00C4: + case 0x00C5: + case 0x00C7: + case 0x00C8: + case 0x00C9: + case 0x00C6: + case 0x00CA: + case 0x00CD: + case 0x00CC: + case 0x00CE: + case 0x00CF: + case 0x00D0: + case 0x7F74: + case 0x7F66: + case 0x00D6: + case 0x00D7: + case 0x00D8: + sw = SW_OK; + break; + + // PW2 + case 0x0103: + if (gpg_pin_is_verified(PIN_ID_PW2)) { + sw = SW_OK; + } + break; + + // PW3 + case 0x00B6: + case 0x00A4: + case 0x00B8: + case 0x0104: + if (gpg_pin_is_verified(PIN_ID_PW3)) { + sw = SW_OK; + } + break; + default: + sw = SW_CONDITIONS_NOT_SATISFIED; + break; } - break; - } - - THROW(SW_CONDITIONS_NOT_SATISFIED); + return sw; } -char debugbuff[5]; - -void gpg_check_access_write_DO() { - unsigned int ref; - gpg_pin_t * pin_pw2, *pin_pw3; - - pin_pw2 = gpg_pin_get_pin(PIN_ID_PW2); - pin_pw3 = gpg_pin_get_pin(PIN_ID_PW3); - ref = (G_gpg_vstate.io_p1 << 8) | G_gpg_vstate.io_p2; - - switch (ref) { - // PW2 - case 0x0101: - case 0x0103: - case 0x01F2: - if (gpg_pin_is_verified(PIN_ID_PW2)) { - return; - } - break; - - // PW3 - case 0x3FFF: // only used for putkey under PW3 control - case 0x4f: - case 0x0102: - case 0x0104: - case 0x01F1: - case 0x01F8: - case 0x005E: - case 0x005B: - case 0x5F2D: - case 0x5F35: - case 0x5F50: - case 0x5F48: - case 0x7F21: - case 0x00B6: - case 0x00A4: - case 0x00B8: - case 0x00C1: - case 0x00C2: - case 0x00C3: - case 0x00C4: - case 0x00C5: - case 0x00C7: - case 0x00C8: - case 0x00C9: - case 0x00C6: - case 0x00CA: - case 0x00CB: - case 0x00CC: - case 0x00CD: - case 0x00CE: - case 0x00CF: - case 0x00D0: - case 0x00D1: - case 0x00D2: - case 0x00D3: - case 0x00D5: - case 0x00F4: - case 0x00D6: - case 0x00D7: - case 0x00D8: - if (gpg_pin_is_verified(PIN_ID_PW3)) { - return; +/** + * Check INS Write access condition + * Verify if the corresponding PW is verified + * + * @return Status Word + * + */ +static int gpg_check_access_write_DO() { + int sw = SW_UNKNOWN; + + switch (G_gpg_vstate.io_p1p2) { + // PW2 + case 0x0101: + case 0x0103: + case 0x01F2: + if (gpg_pin_is_verified(PIN_ID_PW2)) { + sw = SW_OK; + } + break; + + // PW3 + case 0x3FFF: // only used for putkey under PW3 control + case 0x4f: + case 0x0102: + case 0x0104: + case 0x01F1: + case 0x01F8: + case 0x005E: + case 0x005B: + case 0x5F2D: + case 0x5F35: + case 0x5F50: + case 0x5F48: + case 0x7F21: + case 0x00B6: + case 0x00A4: + case 0x00B8: + case 0x00C1: + case 0x00C2: + case 0x00C3: + case 0x00C4: + case 0x00C5: + case 0x00C7: + case 0x00C8: + case 0x00C9: + case 0x00C6: + case 0x00CA: + case 0x00CB: + case 0x00CC: + case 0x00CD: + case 0x00CE: + case 0x00CF: + case 0x00D0: + case 0x00D1: + case 0x00D2: + case 0x00D3: + case 0x00D5: + case 0x00F4: + case 0x00D6: + case 0x00D7: + case 0x00D8: + if (gpg_pin_is_verified(PIN_ID_PW3)) { + sw = SW_OK; + } + break; + default: + sw = SW_CONDITIONS_NOT_SATISFIED; + break; } - break; - } - THROW(SW_CONDITIONS_NOT_SATISFIED); + return sw; } -/* assume command is fully received */ +/** + * APDU Handler: dispatch command + * + * @return Status Word + * + */ int gpg_dispatch() { - unsigned int tag, t, l; - int sw; - - if ((G_gpg_vstate.io_cla != 0x00) && (G_gpg_vstate.io_cla != 0x10) && (G_gpg_vstate.io_cla != 0xEF)) { - THROW(SW_CLA_NOT_SUPPORTED); - return SW_CLA_NOT_SUPPORTED; - } - - tag = (G_gpg_vstate.io_p1 << 8) | G_gpg_vstate.io_p2; - - switch (G_gpg_vstate.io_ins) { - #ifdef GPG_LOG - case INS_GET_LOG: - gpg_io_discard(1); - gpg_io_insert(G_gpg_vstate.log_buffer, 32); - return SW_OK; - #endif - - /* --- SELECT --- */ - case INS_SELECT: - sw = gpg_apdu_select(); - return sw; - break; + unsigned int tag, t, l; + int sw = SW_UNKNOWN; - /* --- ACTIVATE/TERMINATE FILE --- */ - case INS_ACTIVATE_FILE: - gpg_io_discard(0); - if (N_gpg_pstate->histo[7] == STATE_TERMINATE) { - gpg_install(STATE_ACTIVATE); + if ((G_gpg_vstate.io_cla != CLA_APP_DEF) && (G_gpg_vstate.io_cla != CLA_APP_CHAIN) && + (G_gpg_vstate.io_cla != CLA_APP_APDU_PIN)) { + return SW_CLA_NOT_SUPPORTED; } - return (SW_OK); - break; - - case INS_TERMINATE_DF: - gpg_io_discard(0); - if (gpg_pin_is_verified(PIN_ID_PW3) || (N_gpg_pstate->PW3.counter == 0)) { - gpg_install(STATE_TERMINATE); - return (SW_OK); - break; - } - THROW(SW_CONDITIONS_NOT_SATISFIED); - break; - } - - /* Other commands allowed if not terminated */ - if (N_gpg_pstate->histo[7] != 0x07) { - THROW(SW_STATE_TERMINATED); - } - - /* Process */ - gpg_check_access_ins(); - - switch (G_gpg_vstate.io_ins) { -#ifdef GPG_DEBUG_APDU - case 0x42: - sw = debug_apdu(); - break; -#endif - - case INS_EXIT: - os_sched_exit(0); - sw = SW_OK; - break; - /* --- CHALLENGE --- */ - case INS_GET_CHALLENGE: - sw = gpg_apdu_get_challenge(); - break; - - /* --- DATA --- */ + switch (G_gpg_vstate.io_ins) { +#ifdef GPG_LOG + case INS_GET_LOG: + gpg_io_discard(1); + gpg_io_insert(G_gpg_vstate.log_buffer, sizeof(G_gpg_vstate.log_buffer)); + return SW_OK; +#endif - case INS_SELECT_DATA: - if ((G_gpg_vstate.io_p1 > 2) || (G_gpg_vstate.io_p2 != 0x04)) { - THROW(SW_WRONG_P1P2); - } - gpg_io_fetch_tl(&t, &l); - if (t != 0x60) { - // TODO add l check - THROW(SW_DATA_INVALID); - } - gpg_io_fetch_tl(&t, &l); - if (t != 0x5C) { - // TODO add l check - THROW(SW_WRONG_DATA); - } - if (l == 1) { - tag = gpg_io_fetch_u8(); - } else if (l == 2) { - tag = gpg_io_fetch_u16(); - } else { - THROW(SW_WRONG_DATA); - } - sw = gpg_apdu_select_data(tag, G_gpg_vstate.io_p1); - break; - - case INS_GET_DATA: - gpg_check_access_read_DO(); - switch (tag) { - case 0x00B6: - case 0x00A4: - case 0x00B8: - sw = gpg_apdu_get_key_data(tag); - break; - default: - sw = gpg_apdu_get_data(tag); - break; - } - break; - - case INS_GET_NEXT_DATA: - gpg_check_access_read_DO(); - sw = gpg_apdu_get_next_data(tag); - break; - - case INS_PUT_DATA_ODD: - case INS_PUT_DATA: - gpg_check_access_write_DO(); - switch (tag) { - case 0x00B6: - case 0x00A4: - case 0x00B8: - sw = gpg_apdu_put_key_data(tag); - break; - default: - sw = gpg_apdu_put_data(tag); - break; + /* --- SELECT --- */ + case INS_SELECT: + return gpg_apdu_select(); + + /* --- ACTIVATE/TERMINATE FILE --- */ + case INS_ACTIVATE_FILE: + gpg_io_discard(0); + if (N_gpg_pstate->histo[HISTO_OFFSET_STATE] == STATE_TERMINATE) { + gpg_install(STATE_ACTIVATE); + } + return SW_OK; + + case INS_TERMINATE_DF: + gpg_io_discard(0); + if (gpg_pin_is_verified(PIN_ID_PW3) || (N_gpg_pstate->PW3.counter == 0)) { + gpg_install(STATE_TERMINATE); + return SW_OK; + } + return SW_CONDITIONS_NOT_SATISFIED; } - break; - /* --- PIN -- */ - case INS_VERIFY: - if ((G_gpg_vstate.io_p2 == 0x81) || (G_gpg_vstate.io_p2 == 0x82) || (G_gpg_vstate.io_p2 == 0x83)) { - sw = gpg_apdu_verify(); - break; + /* Other commands allowed if not terminated */ + if (N_gpg_pstate->histo[HISTO_OFFSET_STATE] != STATE_ACTIVATE) { + return SW_STATE_TERMINATED; } - THROW(SW_INCORRECT_P1P2); - case INS_CHANGE_REFERENCE_DATA: - if ((G_gpg_vstate.io_p2 == 0x81) || (G_gpg_vstate.io_p2 == 0x83)) { - sw = gpg_apdu_change_ref_data(); - break; + /* Process */ + sw = gpg_check_access_ins(); + if (sw != SW_OK) { + return sw; } - THROW(SW_INCORRECT_P1P2); - case INS_RESET_RETRY_COUNTER: - if ((G_gpg_vstate.io_p2 == 0x81) && ((G_gpg_vstate.io_p1 == 0) || (G_gpg_vstate.io_p1 == 2))) { - sw = gpg_apdu_reset_retry_counter(); - break; + switch (G_gpg_vstate.io_ins) { + case INS_EXIT: + os_sched_exit(0); + sw = SW_OK; + break; + + /* --- CHALLENGE --- */ + case INS_GET_CHALLENGE: + sw = gpg_apdu_get_challenge(); + break; + + /* --- DATA --- */ + + case INS_SELECT_DATA: + if ((G_gpg_vstate.io_p1 > SELECT_MAX_INSTANCE) || (G_gpg_vstate.io_p2 != SELECT_SKIP)) { + sw = SW_WRONG_P1P2; + break; + } + gpg_io_fetch_tl(&t, &l); + if (t != 0x60) { + sw = SW_WRONG_DATA; + break; + } + gpg_io_fetch_tl(&t, &l); + if (t != 0x5C) { + sw = SW_WRONG_DATA; + break; + } + if (l == 1) { + tag = gpg_io_fetch_u8(); + } else if (l == 2) { + tag = gpg_io_fetch_u16(); + } else { + sw = SW_WRONG_DATA; + break; + } + gpg_apdu_select_data(tag, G_gpg_vstate.io_p1); + sw = SW_OK; + break; + + case INS_GET_DATA: + sw = gpg_check_access_read_DO(); + if (sw != SW_OK) { + break; + } + switch (G_gpg_vstate.io_p1p2) { + case 0x00B6: + case 0x00A4: + case 0x00B8: + sw = gpg_apdu_get_key_data(G_gpg_vstate.io_p1p2); + break; + default: + sw = gpg_apdu_get_data(G_gpg_vstate.io_p1p2); + break; + } + break; + + case INS_GET_NEXT_DATA: + sw = gpg_check_access_read_DO(); + if (sw != SW_OK) { + break; + } + sw = gpg_apdu_get_next_data(G_gpg_vstate.io_p1p2); + break; + + case INS_PUT_DATA_ODD: + case INS_PUT_DATA: + sw = gpg_check_access_write_DO(); + if (sw != SW_OK) { + break; + } + switch (G_gpg_vstate.io_p1p2) { + case 0x00B6: + case 0x00A4: + case 0x00B8: + sw = gpg_apdu_put_key_data(G_gpg_vstate.io_p1p2); + break; + default: + sw = gpg_apdu_put_data(G_gpg_vstate.io_p1p2); + break; + } + break; + + /* --- PIN -- */ + case INS_VERIFY: + if ((G_gpg_vstate.io_p2 == PIN_ID_PW1) || (G_gpg_vstate.io_p2 == PIN_ID_PW2) || + (G_gpg_vstate.io_p2 == PIN_ID_PW3)) { + sw = gpg_apdu_verify(); + break; + } + sw = SW_WRONG_P1P2; + break; + + case INS_CHANGE_REFERENCE_DATA: + if ((G_gpg_vstate.io_p2 == PIN_ID_PW1) || (G_gpg_vstate.io_p2 == PIN_ID_PW3)) { + sw = gpg_apdu_change_ref_data(); + break; + } + sw = SW_WRONG_P1P2; + break; + + case INS_RESET_RETRY_COUNTER: + if ((G_gpg_vstate.io_p2 == PIN_ID_PW1) && + ((G_gpg_vstate.io_p1 == RESET_RETRY_WITH_CODE) || + (G_gpg_vstate.io_p1 == RESET_RETRY_WITH_PW3))) { + sw = gpg_apdu_reset_retry_counter(); + break; + } + sw = SW_WRONG_P1P2; + break; + + /* --- Key Management --- */ + case INS_GEN_ASYM_KEYPAIR: + sw = gpg_apdu_gen(); + break; + + /* --- MSE --- */ + case INS_MSE: + sw = gpg_apdu_mse(); + break; + + /* --- PSO --- */ + case INS_PSO: + sw = gpg_apdu_pso(); + break; + + case INS_INTERNAL_AUTHENTICATE: + sw = gpg_apdu_internal_authenticate(); + break; + + default: + sw = SW_INS_NOT_SUPPORTED; + break; } - THROW(SW_INCORRECT_P1P2); - - /* --- Key Management --- */ - case INS_GEN_ASYM_KEYPAIR: - sw = gpg_apdu_gen(); - break; - - /* --- MSE --- */ - case INS_MSE: - sw = gpg_apdu_mse(tag); - break; - - /* --- PSO --- */ - case INS_PSO: - sw = gpg_apdu_pso(); - break; - - case INS_INTERNAL_AUTHENTICATE: - sw = gpg_apdu_internal_authenticate(); - break; - - default: - THROW(SW_INS_NOT_SUPPORTED); - break; - } - - return sw; + return sw; } diff --git a/src/gpg_gen.c b/src/gpg_gen.c index 9c323fe..bd9b451 100644 --- a/src/gpg_gen.c +++ b/src/gpg_gen.c @@ -1,284 +1,394 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" +#include "cx_ram.h" +#include "cx_errors.h" -/* @in slot slot num [0 ; GPG_KEYS_SLOTS[ - * @out seed 32 bytes master seed for given slot +/** + * Derivate the App Path from the Master Seed for a specific slot + * + * @param[in] slot Selected slot + * @param[out] seed 32 bytes seed for given slot + * + * @return Status Word + * */ -void gpg_pso_derive_slot_seed(int slot, unsigned char *seed) { - unsigned int path[2]; - unsigned char chain[32]; - - os_memset(chain, 0, 32); - path[0] = 0x80475047; - path[1] = slot + 1; - os_perso_derive_node_bip32(CX_CURVE_SECP256K1, path, 2, seed, chain); +int gpg_pso_derive_slot_seed(int slot, unsigned char *seed) { + unsigned int path[2]; + unsigned char chain[32]; + cx_err_t error = CX_INTERNAL_ERROR; + + explicit_bzero(chain, 32); + path[0] = 0x80475047; + path[1] = slot + 1; + CX_CHECK(os_derive_bip32_no_throw(CX_CURVE_SECP256K1, path, 2, seed, chain)); + +end: + if (error != CX_OK) { + return error; + } + return SW_OK; } -/* @in Sn master seed slot number - * @in key_name key name: 'sig ', 'auth ', 'dec ' - * @in idx sub-seed index - * @out Ski generated sub_seed - * @in Ski_len sub-seed length +/** + * Derivate the Key from the Generated Seed + * + * @param[in] Sn Seed for the selected slot + * @param[in] key_name key name: 'sig ', 'auth ', 'dec ' + * @param[in] idx sub-seed index + * @param[out] Ski generated sub_seed + * @param[in] Ski_len sub-seed length + * + * @return Status Word + * */ -void gpg_pso_derive_key_seed(unsigned char *Sn, - unsigned char *key_name, - unsigned int idx, - unsigned char *Ski, - unsigned int Ski_len) { - unsigned char h[32]; - h[0] = idx >> 8; - h[1] = idx; - - cx_sha256_init(&G_gpg_vstate.work.md.sha256); - cx_hash((cx_hash_t *)&G_gpg_vstate.work.md.sha256, 0, Sn, 32, NULL, 0); - cx_hash((cx_hash_t *)&G_gpg_vstate.work.md.sha256, 0, (unsigned char *)key_name, 4, NULL, 0); - cx_hash((cx_hash_t *)&G_gpg_vstate.work.md.sha256, CX_LAST, h, 2, h, 32); -#ifdef GPG_SHAKE256 - cx_shake256_init(&G_gpg_vstate.work.md.sha3, Ski_len); - cx_sha3_update(&G_gpg_vstate.work.md.sha3, h, 32); - cx_sha3_final(&G_gpg_vstate.work.md.sha3, Ski); -#else - cx_sha3_xof_init(&G_gpg_vstate.work.md.sha3, 256, Ski_len); - cx_hash((cx_hash_t *)&G_gpg_vstate.work.md.sha3, CX_LAST, h, 32, Ski, Ski_len); -#endif +int gpg_pso_derive_key_seed(unsigned char *Sn, + unsigned char *key_name, + unsigned int idx, + unsigned char *Ski, + unsigned int Ski_len) { + unsigned char h[32]; + cx_err_t error = CX_INTERNAL_ERROR; + U2BE_ENCODE(h, 0, idx); + + cx_sha256_init(&G_gpg_vstate.work.md.sha256); + CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, 0, Sn, 32, NULL, 0)); + CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, + 0, + (unsigned char *) key_name, + 4, + NULL, + 0)); + CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, CX_LAST, h, 2, h, 32)); + CX_CHECK(cx_shake256_init_no_throw(&G_gpg_vstate.work.md.sha3, Ski_len)); + CX_CHECK(cx_sha3_update(&G_gpg_vstate.work.md.sha3, h, 32)); + CX_CHECK(cx_sha3_final(&G_gpg_vstate.work.md.sha3, Ski)); + +end: + if (error != CX_OK) { + return error; + } + return SW_OK; } -/* assume command is fully received */ -int gpg_apdu_gen() { - unsigned int t, l, ksz, reset_cnt; - gpg_key_t * keygpg; - unsigned char seed[66]; - unsigned char *name; - - switch ((G_gpg_vstate.io_p1 << 8) | G_gpg_vstate.io_p2) { - case 0x8000: - case 0x8001: - case 0x8100: - break; - default: - THROW(SW_INCORRECT_P1P2); - return SW_INCORRECT_P1P2; - } - - if (G_gpg_vstate.io_lc != 2) { - THROW(SW_WRONG_LENGTH); - return SW_WRONG_LENGTH; - } - - gpg_io_fetch_tl(&t, &l); - gpg_io_discard(1); - reset_cnt = 0; - switch (t) { - case 0xB6: - keygpg = &G_gpg_vstate.kslot->sig; - name = (unsigned char *)PIC("sig "); - reset_cnt = 0; - break; - case 0xA4: - keygpg = &G_gpg_vstate.kslot->aut; - name = (unsigned char *)PIC("aut "); - break; - case 0xB8: - keygpg = &G_gpg_vstate.kslot->dec; - name = (unsigned char *)PIC("dec "); - break; - default: - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - - switch ((G_gpg_vstate.io_p1 << 8) | G_gpg_vstate.io_p2) { - // -- generate keypair --- - case 0x8000: - case 0x8001: - // RSA - if (keygpg->attributes.value[0] == 0x01) { - unsigned char * pq; - cx_rsa_public_key_t * rsa_pub; - cx_rsa_private_key_t *rsa_priv, *pkey; - unsigned int pkey_size; - - ksz = (keygpg->attributes.value[1] << 8) | keygpg->attributes.value[2]; - ksz = ksz >> 3; - rsa_pub = (cx_rsa_public_key_t *)&G_gpg_vstate.work.rsa.public; - rsa_priv = (cx_rsa_private_key_t *)&G_gpg_vstate.work.rsa.private; - pkey = &keygpg->priv_key.rsa; - switch (ksz) { - case 1024 / 8: - pkey_size = sizeof(cx_rsa_1024_private_key_t); - break; - case 2048 / 8: - pkey_size = sizeof(cx_rsa_2048_private_key_t); - break; - case 3072 / 8: - pkey_size = sizeof(cx_rsa_3072_private_key_t); - break; - case 4096 / 8: - pkey_size = sizeof(cx_rsa_4096_private_key_t); - break; - default: - THROW(SW_WRONG_DATA); - } - pq = NULL; - if ((G_gpg_vstate.io_p2 == 0x01) || (G_gpg_vstate.seed_mode)) { +/** + * Generate a RSA key pair and writes it in NVRam + * + * @param[in] keygpg pointer on key structure + * @param[in] name key name: 'sig ', 'auth ', 'dec ' + * + * @return Status Word + * + */ +static int gpg_gen_rsa_kyey(gpg_key_t *keygpg, uint8_t *name) { + cx_rsa_public_key_t *rsa_pub = NULL; + cx_rsa_private_key_t *rsa_priv = NULL; + uint8_t *pq = NULL; + uint32_t ksz = 0, reset_cnt = 0, pkey_size = 0; + int sw = SW_UNKNOWN; + cx_err_t error = CX_INTERNAL_ERROR; + uint8_t seed[66] = {0}; + + ksz = U2BE(keygpg->attributes.value, 1) >> 3; + rsa_pub = (cx_rsa_public_key_t *) &G_gpg_vstate.work.rsa.public; + rsa_priv = (cx_rsa_private_key_t *) &G_gpg_vstate.work.rsa.private; + switch (ksz) { + case 2048 / 8: + pkey_size = sizeof(cx_rsa_2048_private_key_t); + break; + case 3072 / 8: + pkey_size = sizeof(cx_rsa_3072_private_key_t); + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096 / 8: + pkey_size = sizeof(cx_rsa_4096_private_key_t); + break; +#endif + default: + break; + } + if (pkey_size == 0) { + return SW_WRONG_DATA; + } + + if ((G_gpg_vstate.io_p2 == SEEDED_MODE) || (G_gpg_vstate.seed_mode)) { pq = &rsa_pub->n[0]; unsigned int size; size = ksz >> 1; - gpg_pso_derive_slot_seed(G_gpg_vstate.slot, seed); - gpg_pso_derive_key_seed(seed, name, 1, pq, size); - gpg_pso_derive_key_seed(seed, name, 2, pq + size, size); + sw = gpg_pso_derive_slot_seed(G_gpg_vstate.slot, seed); + if (sw != SW_OK) { + return sw; + } + sw = gpg_pso_derive_key_seed(seed, name, 1, pq, size); + if (sw != SW_OK) { + return sw; + } + sw = gpg_pso_derive_key_seed(seed, name, 2, pq + size, size); + if (sw != SW_OK) { + return sw; + } *pq |= 0x80; *(pq + size) |= 0x80; - cx_math_next_prime(pq, size); - cx_math_next_prime(pq + size, size); - } + CX_CHECK(cx_math_next_prime_no_throw(pq, size)); + CX_CHECK(cx_math_next_prime_no_throw(pq + size, size)); + } + + CX_CHECK( + cx_rsa_generate_pair_no_throw(ksz, + rsa_pub, + rsa_priv, + (const unsigned char *) N_gpg_pstate->default_RSA_exponent, + 4, + pq)); + + nvm_write(&keygpg->priv_key.rsa, rsa_priv, pkey_size); + nvm_write(&keygpg->pub_key.rsa[0], rsa_pub->e, 4); - cx_rsa_generate_pair(ksz, rsa_pub, rsa_priv, N_gpg_pstate->default_RSA_exponent, 4, pq); + nvm_write(&G_gpg_vstate.kslot->sig_count, &reset_cnt, sizeof(unsigned int)); + gpg_io_clear(); + return SW_OK; - nvm_write(pkey, rsa_priv, pkey_size); - nvm_write(&keygpg->pub_key.rsa[0], rsa_pub->e, 4); - if (reset_cnt) { - reset_cnt = 0; - nvm_write(&G_gpg_vstate.kslot->sig_count, &reset_cnt, sizeof(unsigned int)); - } - gpg_io_clear(); +end: + return error; +} + +/** + * Read a RSA key pair + * + * @param[in] keygpg pointer on key structure + * + * @return Status Word + * + */ +static int gpg_read_rsa_kyey(gpg_key_t *keygpg) { + uint32_t ksz = 0; - goto send_rsa_pub; + gpg_io_discard(1); + // check length + ksz = U2BE(keygpg->attributes.value, 1) >> 3; + gpg_io_mark(); + switch (ksz) { + case 2048 / 8: + if (keygpg->priv_key.rsa2048.size == 0) { + return SW_REFERENCED_DATA_NOT_FOUND; + } + gpg_io_insert_tlv(0x81, ksz, (unsigned char *) &keygpg->priv_key.rsa2048.n); + break; + case 3072 / 8: + if (keygpg->priv_key.rsa3072.size == 0) { + return SW_REFERENCED_DATA_NOT_FOUND; + } + gpg_io_insert_tlv(0x81, ksz, (unsigned char *) &keygpg->priv_key.rsa3072.n); + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096 / 8: + if (keygpg->priv_key.rsa4096.size == 0) { + return SW_REFERENCED_DATA_NOT_FOUND; + } + gpg_io_insert_tlv(0x81, ksz, (unsigned char *) &keygpg->priv_key.rsa4096.n); + break; +#endif + default: + return SW_REFERENCED_DATA_NOT_FOUND; } - // ECC - if ((keygpg->attributes.value[0] == 18) || (keygpg->attributes.value[0] == 19) || - (keygpg->attributes.value[0] == 22)) { - unsigned int curve, keepprivate; - keepprivate = 0; - curve = gpg_oid2curve(keygpg->attributes.value + 1, keygpg->attributes.length - 1); - if (curve == CX_CURVE_NONE) { - THROW(SW_REFERENCED_DATA_NOT_FOUND); + gpg_io_insert_tlv(0x82, 4, keygpg->pub_key.rsa); + + return SW_OK; +} + +/** + * Generate an Elliptic Curve key pair and writes it in NVRam + * + * @param[in] keygpg pointer on key structure + * @param[in] name key name: 'sig ', 'auth ', 'dec ' + * + * @return Status Word + * + */ +static int gpg_gen_ecc_kyey(gpg_key_t *keygpg, uint8_t *name) { + uint32_t curve = 0, keepprivate = 0; + uint32_t ksz = 0, reset_cnt = 0; + int sw = SW_UNKNOWN; + cx_err_t error = CX_INTERNAL_ERROR; + uint8_t seed[66] = {0}; + + curve = gpg_oid2curve(keygpg->attributes.value + 1, keygpg->attributes.length - 1); + if (curve == CX_CURVE_NONE) { return SW_REFERENCED_DATA_NOT_FOUND; - } - if ((G_gpg_vstate.io_p2 == 0x01) || (G_gpg_vstate.seed_mode)) { - ksz = gpg_curve2domainlen(curve); - gpg_pso_derive_slot_seed(G_gpg_vstate.slot, seed); - gpg_pso_derive_key_seed(seed, name, 1, seed, ksz); - cx_ecfp_init_private_key(curve, seed, ksz, &G_gpg_vstate.work.ecfp.private); - keepprivate = 1; - } - - cx_ecfp_generate_pair(curve, &G_gpg_vstate.work.ecfp.public, &G_gpg_vstate.work.ecfp.private, keepprivate); - nvm_write(&keygpg->priv_key.ecfp, &G_gpg_vstate.work.ecfp.private, sizeof(cx_ecfp_private_key_t)); - nvm_write(&keygpg->pub_key.ecfp, &G_gpg_vstate.work.ecfp.public, sizeof(cx_ecfp_public_key_t)); - if (reset_cnt) { - reset_cnt = 0; - nvm_write(&G_gpg_vstate.kslot->sig_count, &reset_cnt, sizeof(unsigned int)); - } - gpg_io_clear(); - goto send_ecc_pub; } - break; - - // --- read pubkey --- - case 0x8100: - if (keygpg->attributes.value[0] == 0x01) { - /// read RSA - send_rsa_pub: - gpg_io_discard(1); - // check length - ksz = (keygpg->attributes.value[1] << 8) | keygpg->attributes.value[2]; - ksz = ksz >> 3; - gpg_io_mark(); - switch (ksz) { - case 1024 / 8: - if (keygpg->priv_key.rsa1024.size == 0) { - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; - } - gpg_io_insert_tlv(0x81, ksz, (unsigned char *)&keygpg->priv_key.rsa1024.n); - break; - case 2048 / 8: - if (keygpg->priv_key.rsa2048.size == 0) { - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; - } - gpg_io_insert_tlv(0x81, ksz, (unsigned char *)&keygpg->priv_key.rsa2048.n); - break; - case 3072 / 8: - if (keygpg->priv_key.rsa3072.size == 0) { - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; + if ((G_gpg_vstate.io_p2 == SEEDED_MODE) || (G_gpg_vstate.seed_mode)) { + ksz = gpg_curve2domainlen(curve); + sw = gpg_pso_derive_slot_seed(G_gpg_vstate.slot, seed); + if (sw != SW_OK) { + return sw; } - gpg_io_insert_tlv(0x81, ksz, (unsigned char *)&keygpg->priv_key.rsa3072.n); - break; - case 4096 / 8: - if (keygpg->priv_key.rsa4096.size == 0) { - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; + sw = gpg_pso_derive_key_seed(seed, name, 1, seed, ksz); + if (sw != SW_OK) { + return sw; } - gpg_io_insert_tlv(0x81, ksz, (unsigned char *)&keygpg->priv_key.rsa4096.n); - break; - } - gpg_io_insert_tlv(0x82, 4, keygpg->pub_key.rsa); + CX_CHECK( + cx_ecfp_init_private_key_no_throw(curve, seed, ksz, &G_gpg_vstate.work.ecfp.private)); + keepprivate = 1; + } - l = G_gpg_vstate.io_length; - gpg_io_set_offset(IO_OFFSET_MARK); - gpg_io_insert_tl(0x7f49, l); - gpg_io_set_offset(IO_OFFSET_END); + CX_CHECK(cx_ecfp_generate_pair_no_throw(curve, + &G_gpg_vstate.work.ecfp.public, + &G_gpg_vstate.work.ecfp.private, + keepprivate)); + nvm_write(&keygpg->priv_key.ecfp, + &G_gpg_vstate.work.ecfp.private, + sizeof(cx_ecfp_private_key_t)); + nvm_write(&keygpg->pub_key.ecfp, &G_gpg_vstate.work.ecfp.public, sizeof(cx_ecfp_public_key_t)); - return SW_OK; - } + nvm_write(&G_gpg_vstate.kslot->sig_count, &reset_cnt, sizeof(unsigned int)); + gpg_io_clear(); + error = SW_OK; + +end: + return error; +} + +/** + * Read an Elliptic Curve key pair + * + * @param[in] keygpg pointer on key structure + * + * @return Status Word + * + */ +static int gpg_read_ecc_kyey(gpg_key_t *keygpg) { + uint32_t curve = 0; + uint32_t i, len; + cx_err_t error = CX_INTERNAL_ERROR; - if ((keygpg->attributes.value[0] == 18) || (keygpg->attributes.value[0] == 19) || - (keygpg->attributes.value[0] == 22)) { - unsigned int curve; - /// read ECC - send_ecc_pub: - - if (keygpg->pub_key.ecfp256.W_len == 0) { - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return 0; - } - gpg_io_discard(1); - gpg_io_mark(); - curve = gpg_oid2curve(keygpg->attributes.value + 1, keygpg->attributes.length - 1); - if (curve == CX_CURVE_Ed25519) { - os_memmove(G_gpg_vstate.work.io_buffer + 128, keygpg->pub_key.ecfp256.W, keygpg->pub_key.ecfp256.W_len); - cx_edward_compress_point(CX_CURVE_Ed25519, G_gpg_vstate.work.io_buffer + 128, 65); - gpg_io_insert_tlv(0x86, 32, G_gpg_vstate.work.io_buffer + 129); // 129: discard 02 - } else if (curve == CX_CURVE_Curve25519) { - unsigned int i, len; - len = keygpg->pub_key.ecfp256.W_len - 1; + if (keygpg->pub_key.ecfp.W_len == 0) { + return SW_REFERENCED_DATA_NOT_FOUND; + } + gpg_io_discard(1); + gpg_io_mark(); + curve = gpg_oid2curve(keygpg->attributes.value + 1, keygpg->attributes.length - 1); + if (curve == CX_CURVE_Ed25519) { + memmove(G_gpg_vstate.work.io_buffer + 128, + keygpg->pub_key.ecfp.W, + keygpg->pub_key.ecfp.W_len); + CX_CHECK(cx_edwards_compress_point_no_throw(CX_CURVE_Ed25519, + G_gpg_vstate.work.io_buffer + 128, + 65)); + gpg_io_insert_tlv(0x86, 32, + G_gpg_vstate.work.io_buffer + 129); // 129: discard 02 + } else if (curve == CX_CURVE_Curve25519) { + len = keygpg->pub_key.ecfp.W_len - 1; for (i = 0; i <= len; i++) { - G_gpg_vstate.work.io_buffer[128 + i] = keygpg->pub_key.ecfp256.W[len - i]; + G_gpg_vstate.work.io_buffer[128 + i] = keygpg->pub_key.ecfp.W[len - i]; } gpg_io_insert_tlv(0x86, 32, G_gpg_vstate.work.io_buffer + 128); - } else { - gpg_io_insert_tlv(0x86, keygpg->pub_key.ecfp256.W_len, (unsigned char *)&keygpg->pub_key.ecfp256.W); - } - l = G_gpg_vstate.io_length; - gpg_io_set_offset(IO_OFFSET_MARK); - gpg_io_insert_tl(0x7f49, l); - gpg_io_set_offset(IO_OFFSET_END); - return SW_OK; + } else { + gpg_io_insert_tlv(0x86, + keygpg->pub_key.ecfp.W_len, + (unsigned char *) &keygpg->pub_key.ecfp.W); } - break; - } + error = SW_OK; + +end: + return error; +} + +/** + * APDU handler to Generate/Read key pair + * + * @return Status Word + * + */ +int gpg_apdu_gen() { + uint32_t t, l; + gpg_key_t *keygpg = NULL; + uint8_t *name = NULL; + int sw = SW_UNKNOWN; - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; + switch (G_gpg_vstate.io_p1p2) { + case GEN_ASYM_KEY: + case GEN_ASYM_KEY_SEED: + case READ_ASYM_KEY: + break; + default: + return SW_WRONG_P1P2; + } + + if (G_gpg_vstate.io_lc != 2) { + return SW_WRONG_LENGTH; + } + + gpg_io_fetch_tl(&t, &l); + gpg_io_discard(1); + switch (t) { + case KEY_SIG: + keygpg = &G_gpg_vstate.kslot->sig; + name = (unsigned char *) PIC("sig "); + break; + case KEY_AUT: + keygpg = &G_gpg_vstate.kslot->aut; + name = (unsigned char *) PIC("aut "); + break; + case KEY_DEC: + keygpg = &G_gpg_vstate.kslot->dec; + name = (unsigned char *) PIC("dec "); + break; + default: + break; + } + if (keygpg == NULL) { + return SW_WRONG_DATA; + } + + switch (G_gpg_vstate.io_p1p2) { + // -- generate keypair --- + case GEN_ASYM_KEY: + case GEN_ASYM_KEY_SEED: + + if (keygpg->attributes.value[0] == KEY_ID_RSA) { + sw = gpg_gen_rsa_kyey(keygpg, name); + if (sw != SW_OK) { + break; + } + } else if ((keygpg->attributes.value[0] == KEY_ID_ECDH) || + (keygpg->attributes.value[0] == KEY_ID_ECDSA) || + (keygpg->attributes.value[0] == KEY_ID_EDDSA)) { + sw = gpg_gen_ecc_kyey(keygpg, name); + if (sw != SW_OK) { + break; + } + } + + __attribute__((fallthrough)); + // --- read pubkey --- + case READ_ASYM_KEY: + if (keygpg->attributes.value[0] == KEY_ID_RSA) { + sw = gpg_read_rsa_kyey(keygpg); + } else if ((keygpg->attributes.value[0] == KEY_ID_ECDH) || + (keygpg->attributes.value[0] == KEY_ID_ECDSA) || + (keygpg->attributes.value[0] == KEY_ID_EDDSA)) { + sw = gpg_read_ecc_kyey(keygpg); + } + l = G_gpg_vstate.io_length; + gpg_io_set_offset(IO_OFFSET_MARK); + gpg_io_insert_tl(0x7f49, l); + gpg_io_set_offset(IO_OFFSET_END); + break; + } + return sw; } diff --git a/src/gpg_init.c b/src/gpg_init.c index d98da0a..454cf59 100644 --- a/src/gpg_init.c +++ b/src/gpg_init.c @@ -1,183 +1,215 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" -#include "usbd_impl.h" +#include "usbd_ccid_if.h" +#include "ox_ec.h" -#define SHORT(x) ((x) >> 8) & 0xFF, (x)&0xFF +#define SHORT(x) ((x) >> 8) & 0xFF, (x) &0xFF /* ----------------------*/ /* -- A Kind of Magic -- */ /* ----------------------*/ -const unsigned char C_MAGIC[8] = {'G', 'P', 'G', 'C', 'A', 'R', 'D', '3'}; +const unsigned char C_MAGIC[MAGIC_LENGTH] = {'G', 'P', 'G', 'C', 'A', 'R', 'D', '3'}; /* ----------------------*/ /* --ECC OID -- */ /* ----------------------*/ // secp256r1 / NIST P256 /ansi-x9.62 : 1.2.840.10045.3.1.7 const unsigned char C_OID_SECP256R1[8] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}; -/* -//secp384r1 / NIST P384 /ansi-x9.62 :1.3.132.0.34 + +/* Unsupported (yet) Curves +// secp384r1 / NIST P384 /ansi-x9.62 :1.3.132.0.34 const unsigned char C_OID_SECP384R1[5] = { 0x2B, 0x81, 0x04, 0x00 , 0x22 }; -//secp521r1 / NIST P521 /ansi-x9.62 : 1.3.132.0.35 +// secp521r1 / NIST P521 /ansi-x9.62 : 1.3.132.0.35 const unsigned char C_OID_SECP521R1[5] = { 0x2B, 0x81, 0x04, 0x00, 0x23 }; - -//secp256k1: 1.3.132.0.10 -const unsigned char C_OID_SECP256K1[5] = { - 0x2B, 0x81, 0x04, 0x00, 0x0A -}; */ +// secp256k1: 1.3.132.0.10 +const unsigned char C_OID_SECP256K1[5] = {0x2B, 0x81, 0x04, 0x00, 0x0A}; -/* -//brainpool 256t1: 1.3.36.3.3.2.8.1.1.8 +/* Unsupported (yet) Curves +// brainpool 256t1: 1.3.36.3.3.2.8.1.1.8 const unsigned char C_OID_BRAINPOOL256T1[9] = { 0x2B,0x24,0x03,0x03,0x02,0x08,0x01,0x01,0x07 }; -//brainpool 256r1: 1.3.36.3.3.2.8.1.1.7 +// brainpool 256r1: 1.3.36.3.3.2.8.1.1.7 const unsigned char C_OID_BRAINPOOL256R1[9] = { 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x08 }; -//brainpool 284r1: 1.3.36.3.3.2.8.1.1.11 +// brainpool 384r1: 1.3.36.3.3.2.8.1.1.11 const unsigned char C_OID_BRAINPOOL384R1[9] = { 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B }; -//brainpool 512r1: 1.3.36.3.3.2.8.1.1.13 +// brainpool 512r1: 1.3.36.3.3.2.8.1.1.13 const unsigned char C_OID_BRAINPOOL512R1[9] = { 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D }; */ -// Ed25519/curve25519: 1.3.6.1.4.1.11591.15.1 +// "twisted" curve25519 for Ed25519: 1.3.6.1.4.1.11591.15.1 const unsigned char C_OID_Ed25519[9] = { - 0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01, + 0x2B, + 0x06, + 0x01, + 0x04, + 0x01, + 0xDA, + 0x47, + 0x0F, + 0x01, }; -// Ed25519/curve25519: 1.3.6.1.4.1.11591.15.1 +// "Montgomery" curve25519 for X25519: 1.3.6.1.4.1.11591.1.5.1 const unsigned char C_OID_cv25519[10] = { - 0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01, + 0x2B, + 0x06, + 0x01, + 0x04, + 0x01, + 0x97, + 0x55, + 0x01, + 0x05, + 0x01, }; -unsigned int gpg_oid2curve(unsigned char *oid, unsigned int len) { - if ((len == sizeof(C_OID_SECP256R1)) && (os_memcmp(oid, C_OID_SECP256R1, len) == 0)) { - return CX_CURVE_SECP256R1; - } - /* - if ( (len == sizeof(C_OID_SECP256K1)) && (os_memcmp(oid, C_OID_SECP256K1, len)==0) ) { - return CX_CURVE_SECP256K1; - } - - if ( (len == sizeof(C_OID_SECP384R1)) && (os_memcmp(oid, C_OID_SECP384R1, len)==0) ) { - return CX_CURVE_SECP384R1; - } - if ( (len == sizeof(C_OID_SECP521R1)) && (os_memcmp(oid, C_OID_SECP521R1, len)==0) ) { - return CX_CURVE_SECP521R1; - } +/** + * Retrieve Curve associated to a given OID + * + * @param[in] oid Selected OID as a reference + * @param[in] len OID length + * + * @return Found Curve, or CX_CURVE_NONE if not supported + * */ +unsigned int gpg_oid2curve(unsigned char *oid, unsigned int len) { + if ((len == sizeof(C_OID_SECP256R1)) && (memcmp(oid, C_OID_SECP256R1, len) == 0)) { + return CX_CURVE_SECP256R1; + } - /* - if ( (len == sizeof(C_OID_BRAINPOOL256R1)) && (os_memcmp(oid, C_OID_BRAINPOOL256R1, len)==0) ) { - return CX_CURVE_BrainPoolP256R1; - } - if ( (len == sizeof(C_OID_BRAINPOOL384R1)) && (os_memcmp(oid, C_OID_BRAINPOOL384R1, len)==0) ) { - return CX_CURVE_BrainPoolP384R1; - } - if ( (len == sizeof(C_OID_BRAINPOOL512R1)) && (os_memcmp(oid, C_OID_BRAINPOOL512R1, len)==0) ) { - return CX_CURVE_BrainPoolP512R1; - } - */ - if ((len == sizeof(C_OID_Ed25519)) && (os_memcmp(oid, C_OID_Ed25519, len) == 0)) { - return CX_CURVE_Ed25519; - } - - if ((len == sizeof(C_OID_cv25519)) && (os_memcmp(oid, C_OID_cv25519, len) == 0)) { - return CX_CURVE_Curve25519; - } - - /* - if ( (len == sizeof(C_OID_SECP256K1)) && (os_memcmp(oid, C_OID_SECP256K1, len)==0) ) { - return CX_CURVE_256K1; - } - if ( (len == sizeof(C_OID_BRAINPOOL256T1)) && (os_memcmp(oid, C_OID_BRAINPOOL256T1, len)==0) ) { - return CX_CURVE_BrainPoolP256T1; - } - */ - return CX_CURVE_NONE; + if ((len == sizeof(C_OID_SECP256K1)) && (memcmp(oid, C_OID_SECP256K1, len) == 0)) { + return CX_CURVE_SECP256K1; + } + /* Unsupported (yet) Curves + if ( (len == sizeof(C_OID_SECP384R1)) && (memcmp(oid, C_OID_SECP384R1, len)==0) ) { + return CX_CURVE_SECP384R1; + } + if ( (len == sizeof(C_OID_SECP521R1)) && (memcmp(oid, C_OID_SECP521R1, len)==0) ) { + return CX_CURVE_SECP521R1; + } + + if ( (len == sizeof(C_OID_BRAINPOOL256T1)) && (memcmp(oid, C_OID_BRAINPOOL256T1, len)==0) ) { + return CX_CURVE_BrainPoolP256T1; + } + if ( (len == sizeof(C_OID_BRAINPOOL256R1)) && (memcmp(oid, C_OID_BRAINPOOL256R1, len)==0) ) { + return CX_CURVE_BrainPoolP256R1; + } + if ( (len == sizeof(C_OID_BRAINPOOL384R1)) && (memcmp(oid, C_OID_BRAINPOOL384R1, len)==0) ) { + return CX_CURVE_BrainPoolP384R1; + } + if ( (len == sizeof(C_OID_BRAINPOOL512R1)) && (memcmp(oid, C_OID_BRAINPOOL512R1, len)==0) ) { + return CX_CURVE_BrainPoolP512R1; + } + */ + if ((len == sizeof(C_OID_Ed25519)) && (memcmp(oid, C_OID_Ed25519, len) == 0)) { + return CX_CURVE_Ed25519; + } + + if ((len == sizeof(C_OID_cv25519)) && (memcmp(oid, C_OID_cv25519, len) == 0)) { + return CX_CURVE_Curve25519; + } + + return CX_CURVE_NONE; } +/** + * Retrieve OID of the selected Curve + * + * @param[in] cv Selected Curve as a reference + * @param[out] len OID length + * + * @return Found OID, or NULL if not supported + * + */ unsigned char *gpg_curve2oid(unsigned int cv, unsigned int *len) { - switch (cv) { - case CX_CURVE_SECP256R1: - *len = sizeof(C_OID_SECP256R1); - return (unsigned char *)PIC(C_OID_SECP256R1); - - /* - case CX_CURVE_SECP256K1: - *len = sizeof(C_OID_SECP256K1); - return (unsigned char*)PIC(C_OID_SECP256K1); - - case CX_CURVE_SECP384R1: - *len = sizeof(C_OID_SECP384R1); - return (unsigned char*)PIC(C_OID_SECP384R1); - - case CX_CURVE_SECP521R1: - *len = sizeof(C_OID_SECP521R1); - return (unsigned char*)PIC(C_OID_SECP521R1); - */ - - /* - case CX_CURVE_BrainPoolP256R1: - *len = sizeof(C_OID_SECP256R1); - return (unsigned char*)PIC(C_OID_SECP256R1); - - case CX_CURVE_BrainPoolP384R1: - *len = sizeof(C_OID_SECP384R1); - return (unsigned char*)PIC(C_OID_SECP384R1); - - case CX_CURVE_BrainPoolP512R1: - *len = sizeof(C_OID_SECP521R1); - return (unsigned char*)PIC(C_OID_SECP521R1); - */ - case CX_CURVE_Ed25519: - *len = sizeof(C_OID_Ed25519); - return (unsigned char *)PIC(C_OID_Ed25519); - - case CX_CURVE_Curve25519: - *len = sizeof(C_OID_cv25519); - return (unsigned char *)PIC(C_OID_cv25519); - } - - *len = 0; - return NULL; + switch (cv) { + case CX_CURVE_SECP256R1: + *len = sizeof(C_OID_SECP256R1); + return (unsigned char *) PIC(C_OID_SECP256R1); + + case CX_CURVE_SECP256K1: + *len = sizeof(C_OID_SECP256K1); + return (unsigned char *) PIC(C_OID_SECP256K1); + + /* Unsupported (yet) Curves + case CX_CURVE_SECP384R1: + *len = sizeof(C_OID_SECP384R1); + return (unsigned char*)PIC(C_OID_SECP384R1); + + case CX_CURVE_SECP521R1: + *len = sizeof(C_OID_SECP521R1); + return (unsigned char*)PIC(C_OID_SECP521R1); + + case CX_CURVE_BrainPoolP256R1: + *len = sizeof(C_OID_SECP256R1); + return (unsigned char*)PIC(C_OID_SECP256R1); + + case CX_CURVE_BrainPoolP384R1: + *len = sizeof(C_OID_SECP384R1); + return (unsigned char*)PIC(C_OID_SECP384R1); + + case CX_CURVE_BrainPoolP512R1: + *len = sizeof(C_OID_SECP521R1); + return (unsigned char*)PIC(C_OID_SECP521R1); + */ + case CX_CURVE_Ed25519: + *len = sizeof(C_OID_Ed25519); + return (unsigned char *) PIC(C_OID_Ed25519); + + case CX_CURVE_Curve25519: + *len = sizeof(C_OID_cv25519); + return (unsigned char *) PIC(C_OID_cv25519); + } + + *len = 0; + return NULL; } +/** + * Retrieve the selected Curve length + * + * @param[in] cv Selected Curve as a reference + * + * @return Length, or 0 if not supported + * + */ unsigned int gpg_curve2domainlen(unsigned int cv) { - switch (cv) { - case CX_CURVE_SECP256R1: - case CX_CURVE_Ed25519: - case CX_CURVE_Curve25519: - return 32; - } - return 0; + switch (cv) { + case CX_CURVE_SECP256K1: + case CX_CURVE_SECP256R1: + case CX_CURVE_Ed25519: + case CX_CURVE_Curve25519: + return 32; + } + return 0; } /* -------------------------------*/ @@ -202,30 +234,65 @@ const unsigned char C_ext_capabilities[10] = { }; -const unsigned char C_ext_length[8] = {0x02, 0x02, SHORT(GPG_APDU_LENGTH), 0x02, 0x02, SHORT(GPG_APDU_LENGTH)}; +const unsigned char C_ext_length[8] = + {0x02, 0x02, SHORT(GPG_APDU_LENGTH), 0x02, 0x02, SHORT(GPG_APDU_LENGTH)}; + +// General feature management +// - b8: Display (defined by ISO/IEC 7816-4) +// - b7: Biometric input sensor (defined by ISO/IEC 7816-4) +// - b6: Button +// - b5: Keypad +// - b4: LED +// - b3: Loudspeaker +// - b2: Microphone +// - b1: Touchscreen +const unsigned char C_gen_feature = 0x20; /* ---------------------*/ /* -- default values -- */ /* ---------------------*/ -const unsigned char C_default_AID[] = {0xD2, 0x76, 0x00, 0x01, 0x24, 0x01, - // version - 0x03, 0x03, - // manufacturer - 0x2C, 0x97, - // serial - 0x00, 0x00, 0x00, 0x00, - // RFU - 0x00, 0x00}; - -const unsigned char C_default_Histo[] = {0x00, 0x31, - 0xC5, // select method: by DF/partialDF; IO-file:readbinary; RFU??? - 0x73, - 0xC0, // select method: by DF/partialDF , - 0x01, // data coding style: ontime/byte - 0x80, // chaining - 0x7F, // zero state - 0x90, 0x00}; +const unsigned char C_default_AID[] = { + // RID: Registered application provider Identifier + 0xD2, + 0x76, + 0x00, + 0x01, + 0x24, + // PIX: Proprietary application identifier extension + // application + 0x01, + // version + 0x03, + 0x03, + // manufacturer + 0x2C, + 0x97, + // serial + 0x00, + 0x00, + 0x00, + 0x00, + // RFU + 0x00, + 0x00}; + +const unsigned char C_default_Histo[HISTO_LENGTH] = { + 0x00, + 0x31, + 0xC5, // select method: by DF/partialDF; IO-file:readbinary; RFU??? + 0x73, + 0xC0, // select method: by DF/partialDF , + 0x01, // data coding style: ontime/byte + 0x80, // chaining + 0x00, // Padding zero bytes + 0x00, + 0x00, + 0x00, + 0x00, + 0x7F, // zero state + 0x90, + 0x00}; // Default template: RSA2048 010800002001 / 010800002001 #if 1 @@ -233,18 +300,22 @@ const unsigned char C_default_AlgoAttr_sig[] = { // RSA 0x01, // Modulus default length 2048 - 0x08, 0x00, + 0x08, + 0x00, // PubExp length 32 - 0x00, 0x20, + 0x00, + 0x20, // std: e,p,q with modulus (n) 0x01}; const unsigned char C_default_AlgoAttr_dec[] = { // RSA 0x01, // Modulus default length 2048 - 0x08, 0x00, + 0x08, + 0x00, // PubExp length 32 - 0x00, 0x20, + 0x00, + 0x20, // std: e,p,q with modulus (n) 0x01}; #endif @@ -296,135 +367,147 @@ const unsigned char C_sha256_PW2[] = { /* --- boot init --- */ /* ----------------------------------------------------------------------- */ +/** + * App global config + * + */ void gpg_init() { - os_memset(&G_gpg_vstate, 0, sizeof(gpg_v_state_t)); - // first init ? - if (os_memcmp((void *)(N_gpg_pstate->magic), (void *)C_MAGIC, sizeof(C_MAGIC)) != 0) { - gpg_install(STATE_ACTIVATE); - gpg_nvm_write((void *)(N_gpg_pstate->magic), (void *)C_MAGIC, sizeof(C_MAGIC)); - os_memset(&G_gpg_vstate, 0, sizeof(gpg_v_state_t)); - } - - // key conf - G_gpg_vstate.slot = N_gpg_pstate->config_slot[1]; - G_gpg_vstate.kslot = &N_gpg_pstate->keys[G_gpg_vstate.slot]; - gpg_mse_reset(); - // pin conf - G_gpg_vstate.pinmode = N_gpg_pstate->config_pin[0]; - // ux conf - gpg_init_ux(); -} + explicit_bzero(&G_gpg_vstate, sizeof(gpg_v_state_t)); + // first init ? + if (memcmp((void *) (N_gpg_pstate->magic), (void *) C_MAGIC, MAGIC_LENGTH) != 0) { + gpg_install(STATE_ACTIVATE); + nvm_write((void *) (N_gpg_pstate->magic), (void *) C_MAGIC, MAGIC_LENGTH); + explicit_bzero(&G_gpg_vstate, sizeof(gpg_v_state_t)); + } -void gpg_init_ux() { - G_gpg_vstate.ux_type = -1; - G_gpg_vstate.ux_key = -1; + // key conf + G_gpg_vstate.slot = N_gpg_pstate->config_slot[1]; + G_gpg_vstate.kslot = (gpg_key_slot_t *) &N_gpg_pstate->keys[G_gpg_vstate.slot]; + gpg_mse_reset(); + // pin conf + G_gpg_vstate.pinmode = N_gpg_pstate->config_pin[0]; + // seed conf + G_gpg_vstate.seed_mode = 1; + // ux conf + G_gpg_vstate.ux_type = -1; + G_gpg_vstate.ux_key = -1; } /* ----------------------------------------------------------------------- */ /* --- Install/ReInstall GPGapp --- */ /* ----------------------------------------------------------------------- */ + +/** + * App dedicated slot config + * + * @param[in] slot Selected slot to configure + * + */ void gpg_install_slot(gpg_key_slot_t *slot) { - unsigned char tmp[4]; - unsigned int l; + unsigned char tmp[4]; + unsigned int l; - gpg_nvm_write(slot, 0, sizeof(gpg_key_slot_t)); + nvm_write(slot, 0, sizeof(gpg_key_slot_t)); - cx_rng(tmp, 4); - gpg_nvm_write((void *)(slot->serial), tmp, 4); + cx_rng(tmp, 4); + nvm_write((void *) (slot->serial), tmp, 4); - l = sizeof(C_default_AlgoAttr_sig); - gpg_nvm_write((void *)(&slot->sig.attributes.value), (void *)C_default_AlgoAttr_sig, l); - gpg_nvm_write((void *)(&slot->sig.attributes.length), &l, sizeof(unsigned int)); - gpg_nvm_write((void *)(&slot->aut.attributes.value), (void *)C_default_AlgoAttr_sig, l); - gpg_nvm_write((void *)(&slot->aut.attributes.length), &l, sizeof(unsigned int)); + l = sizeof(C_default_AlgoAttr_sig); + nvm_write((void *) (&slot->sig.attributes.value), (void *) C_default_AlgoAttr_sig, l); + nvm_write((void *) (&slot->sig.attributes.length), &l, sizeof(unsigned int)); + nvm_write((void *) (&slot->aut.attributes.value), (void *) C_default_AlgoAttr_sig, l); + nvm_write((void *) (&slot->aut.attributes.length), &l, sizeof(unsigned int)); - l = sizeof(C_default_AlgoAttr_dec); - gpg_nvm_write((void *)(&slot->dec.attributes.value), (void *)C_default_AlgoAttr_dec, l); - gpg_nvm_write((void *)(&slot->dec.attributes.length), &l, sizeof(unsigned int)); + l = sizeof(C_default_AlgoAttr_dec); + nvm_write((void *) (&slot->dec.attributes.value), (void *) C_default_AlgoAttr_dec, l); + nvm_write((void *) (&slot->dec.attributes.length), &l, sizeof(unsigned int)); - tmp[0] = 0x00; - tmp[1] = 0x20; - gpg_nvm_write((void *)(&slot->sig.UIF), &tmp, 2); - gpg_nvm_write((void *)(&slot->dec.UIF), &tmp, 2); - gpg_nvm_write((void *)(&slot->aut.UIF), &tmp, 2); + tmp[0] = 0x00; + tmp[1] = C_gen_feature; + nvm_write((void *) (&slot->sig.UIF), &tmp, 2); + nvm_write((void *) (&slot->dec.UIF), &tmp, 2); + nvm_write((void *) (&slot->aut.UIF), &tmp, 2); } +/** + * App 1st installation or reinstallation + * + * @param[in] app_state Current App (card) state + * + */ void gpg_install(unsigned char app_state) { - gpg_pin_t pin; - - // full reset data - gpg_nvm_write((void *)(N_gpg_pstate), NULL, sizeof(gpg_nv_state_t)); - - // historical bytes - os_memmove(G_gpg_vstate.work.io_buffer, C_default_Histo, sizeof(C_default_Histo)); - G_gpg_vstate.work.io_buffer[7] = app_state; - gpg_nvm_write((void *)(N_gpg_pstate->histo), G_gpg_vstate.work.io_buffer, sizeof(C_default_Histo)); - - // AID - os_memmove(G_gpg_vstate.work.io_buffer, C_default_AID, sizeof(C_default_AID)); - gpg_nvm_write((void *)(N_gpg_pstate->AID), &G_gpg_vstate.work.io_buffer, sizeof(C_default_AID)); - - - if (app_state == STATE_ACTIVATE) { - // default sex: none - G_gpg_vstate.work.io_buffer[0] = 0x39; - gpg_nvm_write((void *)(&N_gpg_pstate->sex), G_gpg_vstate.work.io_buffer, 1); - - // default PW1/PW2: 1 2 3 4 5 6 - os_memmove(pin.value, C_sha256_PW1, sizeof(C_sha256_PW1)); - pin.length = 6; - pin.counter = 3; - pin.ref = PIN_ID_PW1; - gpg_nvm_write((void *)(&N_gpg_pstate->PW1), &pin, sizeof(gpg_pin_t)); - - // default PW3: 1 2 3 4 5 6 7 8 - os_memmove(pin.value, C_sha256_PW2, sizeof(C_sha256_PW2)); - pin.length = 8; - pin.counter = 3; - pin.ref = PIN_ID_PW3; - gpg_nvm_write((void *)(&N_gpg_pstate->PW3), &pin, sizeof(gpg_pin_t)); - - // PWs status - G_gpg_vstate.work.io_buffer[0] = 1; - G_gpg_vstate.work.io_buffer[1] = GPG_MAX_PW_LENGTH; - G_gpg_vstate.work.io_buffer[2] = GPG_MAX_PW_LENGTH; - G_gpg_vstate.work.io_buffer[3] = GPG_MAX_PW_LENGTH; - gpg_nvm_write((void *)(&N_gpg_pstate->PW_status), G_gpg_vstate.work.io_buffer, 4); - - // config slot - G_gpg_vstate.work.io_buffer[0] = GPG_KEYS_SLOTS; - G_gpg_vstate.work.io_buffer[1] = 0; - G_gpg_vstate.work.io_buffer[2] = 3; // 3: selection by APDU and screen - gpg_nvm_write((void *)(&N_gpg_pstate->config_slot), G_gpg_vstate.work.io_buffer, 3); - - // config rsa pub - G_gpg_vstate.work.io_buffer[0] = (GPG_RSA_DEFAULT_PUB >> 24) & 0xFF; - G_gpg_vstate.work.io_buffer[1] = (GPG_RSA_DEFAULT_PUB >> 16) & 0xFF; - G_gpg_vstate.work.io_buffer[2] = (GPG_RSA_DEFAULT_PUB >> 8) & 0xFF; - G_gpg_vstate.work.io_buffer[3] = (GPG_RSA_DEFAULT_PUB >> 0) & 0xFF; - nvm_write((void *)(&N_gpg_pstate->default_RSA_exponent), G_gpg_vstate.work.io_buffer, 4); - - // config pin - G_gpg_vstate.work.io_buffer[0] = PIN_MODE_CONFIRM; - gpg_nvm_write((void *)(&N_gpg_pstate->config_pin), G_gpg_vstate.work.io_buffer, 1); - USBD_CCID_activate_pinpad(3); - - // default key template: RSA 2048) - for (int s = 0; s < GPG_KEYS_SLOTS; s++) { - gpg_install_slot(&N_gpg_pstate->keys[s]); + gpg_pin_t pin; + + // full reset data + nvm_write((void *) (N_gpg_pstate), NULL, sizeof(gpg_nv_state_t)); + + // historical bytes + memmove(G_gpg_vstate.work.io_buffer, C_default_Histo, HISTO_LENGTH); + G_gpg_vstate.work.io_buffer[HISTO_OFFSET_STATE] = app_state; + nvm_write((void *) (N_gpg_pstate->histo), G_gpg_vstate.work.io_buffer, HISTO_LENGTH); + + // AID + memmove(G_gpg_vstate.work.io_buffer, C_default_AID, sizeof(C_default_AID)); + nvm_write((void *) (N_gpg_pstate->AID), &G_gpg_vstate.work.io_buffer, sizeof(C_default_AID)); + + if (app_state == STATE_ACTIVATE) { + // default salutation: none + G_gpg_vstate.work.io_buffer[0] = 0x39; + nvm_write((void *) (&N_gpg_pstate->salutation), G_gpg_vstate.work.io_buffer, 1); + + // default PW1/PW2: 1 2 3 4 5 6 + memmove(pin.value, C_sha256_PW1, sizeof(C_sha256_PW1)); + pin.length = GPG_MIN_PW1_LENGTH; + pin.counter = 3; + pin.ref = PIN_ID_PW1; + nvm_write((void *) (&N_gpg_pstate->PW1), &pin, sizeof(gpg_pin_t)); + + // default PW3: 1 2 3 4 5 6 7 8 + memmove(pin.value, C_sha256_PW2, sizeof(C_sha256_PW2)); + pin.length = GPG_MIN_PW3_LENGTH; + pin.counter = 3; + pin.ref = PIN_ID_PW3; + nvm_write((void *) (&N_gpg_pstate->PW3), &pin, sizeof(gpg_pin_t)); + + // PWs status + G_gpg_vstate.work.io_buffer[0] = 1; + G_gpg_vstate.work.io_buffer[1] = GPG_MAX_PW_LENGTH; + G_gpg_vstate.work.io_buffer[2] = GPG_MAX_PW_LENGTH; + G_gpg_vstate.work.io_buffer[3] = GPG_MAX_PW_LENGTH; + nvm_write((void *) (&N_gpg_pstate->PW_status), G_gpg_vstate.work.io_buffer, 4); + + // config slot + G_gpg_vstate.work.io_buffer[0] = GPG_KEYS_SLOTS; + G_gpg_vstate.work.io_buffer[1] = 0; + G_gpg_vstate.work.io_buffer[2] = 3; // 3: selection by APDU and screen + nvm_write((void *) (&N_gpg_pstate->config_slot), G_gpg_vstate.work.io_buffer, 3); + + // config rsa pub + U4BE_ENCODE(G_gpg_vstate.work.io_buffer, 0, GPG_RSA_DEFAULT_PUB); + nvm_write((void *) (&N_gpg_pstate->default_RSA_exponent), G_gpg_vstate.work.io_buffer, 4); + + // config pin + G_gpg_vstate.work.io_buffer[0] = PIN_MODE_CONFIRM; + nvm_write((void *) (&N_gpg_pstate->config_pin), G_gpg_vstate.work.io_buffer, 1); + gpg_activate_pinpad(3); + + // default key template + for (int s = 0; s < GPG_KEYS_SLOTS; s++) { + gpg_install_slot((gpg_key_slot_t *) &N_gpg_pstate->keys[s]); + } } - } } -#define USBD_OFFSET_CfgDesc_bPINSupport (sizeof(USBD_CfgDesc) - 16) -void USBD_CCID_activate_pinpad(int enabled) { -#ifdef HAVE_USB_CLASS_CCID - unsigned short length; - uint8_t * cfgDesc; - unsigned char e; - e = enabled ? 3 : 0; - length = 0; - cfgDesc = USBD_GetCfgDesc_impl(&length); - nvm_write(cfgDesc + (length - 16), &e, 1); -#endif +/** + * Setup pinpad configuration + * + * @param[in] enabled pinpad configuration + * + * @return N/A + * + */ +void gpg_activate_pinpad(uint8_t enabled) { + uint8_t e = enabled ? 3 : 0; + + io_usb_ccid_configure_pinpad(e); } diff --git a/src/gpg_io.c b/src/gpg_io.c index e6e4258..96c63e9 100644 --- a/src/gpg_io.c +++ b/src/gpg_io.c @@ -1,27 +1,28 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" +#include "offsets.h" +#include "ledger_assert.h" +#include "os_utils.h" /* - * io_buff: contains current message part - * io_off: offset in current message part + * io_buffer: contains current message part + * io_offset: offset in current message part * io_length: length of current message part */ @@ -29,200 +30,340 @@ /* MISC */ /* ----------------------------------------------------------------------- */ +/** + * Set Offset in APDU buffer + * + * @param[in] offset value to set + * + */ void gpg_io_set_offset(unsigned int offset) { - if (offset == IO_OFFSET_END) { - G_gpg_vstate.io_offset = G_gpg_vstate.io_length; - } else if (offset == IO_OFFSET_MARK) { - G_gpg_vstate.io_offset = G_gpg_vstate.io_mark; - } else if (offset < G_gpg_vstate.io_length) { - G_gpg_vstate.io_offset = G_gpg_vstate.io_length; - } else { - THROW(ERROR_IO_OFFSET); - return; - } + switch (offset) { + case IO_OFFSET_END: + G_gpg_vstate.io_offset = G_gpg_vstate.io_length; + break; + case IO_OFFSET_MARK: + G_gpg_vstate.io_offset = G_gpg_vstate.io_mark; + break; + default: + LEDGER_ASSERT(offset < G_gpg_vstate.io_length, "Bad offset!"); + G_gpg_vstate.io_offset = offset; + break; + } } +/** + * Mark current offset in APDU buffer + * + */ void gpg_io_mark() { - G_gpg_vstate.io_mark = G_gpg_vstate.io_offset; + G_gpg_vstate.io_mark = G_gpg_vstate.io_offset; } +/** + * Shift empty space in APDU buffer + * + * @param[in] len space length + * + */ void gpg_io_inserted(unsigned int len) { - G_gpg_vstate.io_offset += len; - G_gpg_vstate.io_length += len; + G_gpg_vstate.io_offset += len; + G_gpg_vstate.io_length += len; } +/** + * Discard APDU buffer values + * Set Length, Offset and Mark to 0 + * + * @param[in] clear request to fully zeroed the buffer + * + */ void gpg_io_discard(int clear) { - G_gpg_vstate.io_length = 0; - G_gpg_vstate.io_offset = 0; - G_gpg_vstate.io_mark = 0; - if (clear) { - gpg_io_clear(); - } + G_gpg_vstate.io_length = 0; + G_gpg_vstate.io_offset = 0; + G_gpg_vstate.io_mark = 0; + if (clear) { + gpg_io_clear(); + } } +/** + * Clear (zeroed) the APDU buffer + * + */ void gpg_io_clear() { - os_memset(G_gpg_vstate.work.io_buffer, 0, GPG_IO_BUFFER_LENGTH); + explicit_bzero(G_gpg_vstate.work.io_buffer, GPG_IO_BUFFER_LENGTH); } /* ----------------------------------------------------------------------- */ /* INSERT data to be sent */ /* ----------------------------------------------------------------------- */ -void gpg_io_hole(unsigned int sz) { - if ((G_gpg_vstate.io_length + sz) > GPG_IO_BUFFER_LENGTH) { - THROW(ERROR_IO_FULL); - return; - } - os_memmove(G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset + sz, - G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, G_gpg_vstate.io_length - G_gpg_vstate.io_offset); - G_gpg_vstate.io_length += sz; +/** + * Move APDU buffer content after a hole + * + * @param[in] sz hole length + * + */ +static void gpg_io_hole(unsigned int sz) { + LEDGER_ASSERT((G_gpg_vstate.io_length + sz) <= GPG_IO_BUFFER_LENGTH, "Bad hole!"); + memmove(G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset + sz, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + G_gpg_vstate.io_length - G_gpg_vstate.io_offset); + G_gpg_vstate.io_length += sz; } +/** + * Insert a data buffer into the APDU buffer + * + * @param[in] buff data buffer + * @param[in] len buffer length + * + */ void gpg_io_insert(unsigned char const *buff, unsigned int len) { - gpg_io_hole(len); - os_memmove(G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, buff, len); - G_gpg_vstate.io_offset += len; + gpg_io_hole(len); + memmove(G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, buff, len); + G_gpg_vstate.io_offset += len; } +/** + * Insert a u32 value into the APDU buffer + * + * @param[in] v32 value to insert + * + */ void gpg_io_insert_u32(unsigned int v32) { - gpg_io_hole(4); - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] = v32 >> 24; - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] = v32 >> 16; - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 2] = v32 >> 8; - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 3] = v32 >> 0; - G_gpg_vstate.io_offset += 4; + gpg_io_hole(4); + U4BE_ENCODE(G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_offset, v32); + G_gpg_vstate.io_offset += 4; } +/** + * Insert a u24 value into the APDU buffer + * + * @param[in] v24 value to insert + * + */ void gpg_io_insert_u24(unsigned int v24) { - gpg_io_hole(3); - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] = v24 >> 16; - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] = v24 >> 8; - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 2] = v24 >> 0; - G_gpg_vstate.io_offset += 3; + gpg_io_hole(3); + G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] = v24 >> 16; + G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] = v24 >> 8; + G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 2] = v24 >> 0; + G_gpg_vstate.io_offset += 3; } + +/** + * Insert a u16 value into the APDU buffer + * + * @param[in] v16 value to insert + * + */ void gpg_io_insert_u16(unsigned int v16) { - gpg_io_hole(2); - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] = v16 >> 8; - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] = v16 >> 0; - G_gpg_vstate.io_offset += 2; + gpg_io_hole(2); + U2BE_ENCODE(G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_offset, v16); + G_gpg_vstate.io_offset += 2; } + +/** + * Insert a u8 value into the APDU buffer + * + * @param[in] v8 value to insert + * + */ void gpg_io_insert_u8(unsigned int v8) { - gpg_io_hole(1); - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] = v8; - G_gpg_vstate.io_offset += 1; + gpg_io_hole(1); + G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] = v8; + G_gpg_vstate.io_offset += 1; } +/** + * Insert a TAG into the APDU buffer + * (To handle TLV) + * + * @param[in] T tag to insert + * + */ void gpg_io_insert_t(unsigned int T) { - if (T & 0xFF00) { - gpg_io_insert_u16(T); - } else { - gpg_io_insert_u8(T); - } + if (T & 0xFF00) { + gpg_io_insert_u16(T); + } else { + gpg_io_insert_u8(T); + } } +/** + * Insert a TAG/LENGTH into the APDU buffer + * (To handle TLV) + * + * @param[in] T tag to insert + * @param[in] L length to insert + * + */ void gpg_io_insert_tl(unsigned int T, unsigned int L) { - gpg_io_insert_t(T); - if (L < 128) { - gpg_io_insert_u8(L); - } else if (L < 256) { - gpg_io_insert_u16(0x8100 | L); - } else { - gpg_io_insert_u8(0x82); - gpg_io_insert_u16(L); - } + gpg_io_insert_t(T); + if (L < 128) { + gpg_io_insert_u8(L); + } else if (L < 256) { + gpg_io_insert_u16(0x8100 | L); + } else { + gpg_io_insert_u8(0x82); + gpg_io_insert_u16(L); + } } +/** + * Insert a TAG/LENGTH/VALUE into the APDU buffer + * (To handle TLV) + * + * @param[in] T tag to insert + * @param[in] L length to insert + * @param[in] V data to insert + * + */ void gpg_io_insert_tlv(unsigned int T, unsigned int L, unsigned char const *V) { - gpg_io_insert_tl(T, L); - gpg_io_insert(V, L); + gpg_io_insert_tl(T, L); + gpg_io_insert(V, L); } /* ----------------------------------------------------------------------- */ /* FECTH data from received buffer */ /* ----------------------------------------------------------------------- */ +/** + * Read a u32 value from the APDU buffer + * + * @return value retrieved + * + */ unsigned int gpg_io_fetch_u32() { - unsigned int v32; - v32 = ((G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] << 24) | - (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] << 16) | - (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 2] << 8) | - (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 3] << 0)); - G_gpg_vstate.io_offset += 4; - return v32; + unsigned int v32; + v32 = U4BE(G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_offset); + G_gpg_vstate.io_offset += 4; + return v32; } +/** + * Read a u24 value from the APDU buffer + * + * @return value retrieved + * + */ unsigned int gpg_io_fetch_u24() { - unsigned int v24; - v24 = ((G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] << 16) | - (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] << 8) | - (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 2] << 0)); - G_gpg_vstate.io_offset += 3; - return v24; + unsigned int v24; + v24 = ((G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] << 16) | + (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] << 8) | + (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 2] << 0)); + G_gpg_vstate.io_offset += 3; + return v24; } +/** + * Read a u16 value from the APDU buffer + * + * @return value retrieved + * + */ unsigned int gpg_io_fetch_u16() { - unsigned int v16; - v16 = ((G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 0] << 8) | - (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] << 0)); - G_gpg_vstate.io_offset += 2; - return v16; + unsigned int v16; + v16 = U2BE(G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_offset); + G_gpg_vstate.io_offset += 2; + return v16; } +/** + * Read a u8 value from the APDU buffer + * + * @return value retrieved + * + */ unsigned int gpg_io_fetch_u8() { - unsigned int v8; - v8 = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; - G_gpg_vstate.io_offset += 1; - return v8; + unsigned int v8; + v8 = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; + G_gpg_vstate.io_offset += 1; + return v8; } -int gpg_io_fetch_t(unsigned int *T) { - *T = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; - G_gpg_vstate.io_offset++; - if ((*T & 0x1F) == 0x1F) { - *T = (*T << 8) | G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; +/** + * Read a TAG from the APDU buffer + * (To handle TLV) + * + * @param[out] T read tag + * + */ +void gpg_io_fetch_t(unsigned int *T) { + *T = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; G_gpg_vstate.io_offset++; - } - return 0; + if ((*T & 0x1F) == 0x1F) { + *T = (*T << 8) | G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; + G_gpg_vstate.io_offset++; + } } -int gpg_io_fetch_l(unsigned int *L) { - *L = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; - - if ((*L & 0x80) != 0) { - *L &= 0x7F; - if (*L == 1) { - *L = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1]; - G_gpg_vstate.io_offset += 2; - } else if (*L == 2) { - *L = (G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1] << 8) | - G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 2]; - G_gpg_vstate.io_offset += 3; +/** + * Read a LENGTH from the APDU buffer + * (To handle TLV) + * + * @param[out] L read length + * + */ +void gpg_io_fetch_l(unsigned int *L) { + *L = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset]; + + if ((*L & 0x80) != 0) { + *L &= 0x7F; + if (*L == 1) { + *L = G_gpg_vstate.work.io_buffer[G_gpg_vstate.io_offset + 1]; + G_gpg_vstate.io_offset += 2; + } else if (*L == 2) { + *L = U2BE(G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_offset + 1); + G_gpg_vstate.io_offset += 3; + } else { + *L = -1; + } } else { - *L = -1; + G_gpg_vstate.io_offset += 1; } - } else { - G_gpg_vstate.io_offset += 1; - } - return 0; } -int gpg_io_fetch_tl(unsigned int *T, unsigned int *L) { - gpg_io_fetch_t(T); - gpg_io_fetch_l(L); - return 0; +/** + * Read a TAG/LENGTH from the APDU buffer + * (To handle TLV) + * + * @param[out] T read tag + * @param[out] L read length + * + */ +void gpg_io_fetch_tl(unsigned int *T, unsigned int *L) { + gpg_io_fetch_t(T); + gpg_io_fetch_l(L); } -int gpg_io_fetch_nv(unsigned char *buffer, int len) { - gpg_nvm_write(buffer, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, len); - G_gpg_vstate.io_offset += len; - return len; +/** + * Read a buffer from the APDU buffer and write it in NVRam + * (To handle TLV) + * + * @param[in] buffer NVRAM address + * @param[in] len buffer length + * + */ +void gpg_io_fetch_nv(unsigned char *buffer, int len) { + nvm_write(buffer, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, len); + G_gpg_vstate.io_offset += len; } + +/** + * Read a buffer from the APDU buffer + * (To handle TLV) + * + * @param[out] buffer data buffer + * @param[in] len buffer length + * + */ int gpg_io_fetch(unsigned char *buffer, int len) { - if (buffer) { - os_memmove(buffer, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, len); - } - G_gpg_vstate.io_offset += len; - return len; + if (buffer) { + memmove(buffer, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, len); + } + G_gpg_vstate.io_offset += len; + return len; } /* ----------------------------------------------------------------------- */ @@ -231,123 +372,147 @@ int gpg_io_fetch(unsigned char *buffer, int len) { #define MAX_OUT GPG_APDU_LENGTH -int gpg_io_do(unsigned int io_flags) { - unsigned int rx = 0; - - // if pending input chaining - if (G_gpg_vstate.io_cla & 0x10) { - goto in_chaining; - } +/** + * APDU Receive/transmit + * + * @param[in] flag io buffer flag + * + */ +void gpg_io_do(unsigned int io_flags) { + unsigned int rx = 0; - if (io_flags & IO_ASYNCH_REPLY) { - // if IO_ASYNCH_REPLY has been set, - // gpg_io_exchange will return when IO_RETURN_AFTER_TX will set in ui - rx = gpg_io_exchange(CHANNEL_APDU | IO_ASYNCH_REPLY, 0); - } else { - // --- full out chaining --- - G_gpg_vstate.io_offset = 0; - while (G_gpg_vstate.io_length > MAX_OUT) { - unsigned int tx, xx; - // send chunk - tx = MAX_OUT - 2; - os_memmove(G_io_apdu_buffer, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, tx); - G_gpg_vstate.io_length -= tx; - G_gpg_vstate.io_offset += tx; - G_io_apdu_buffer[tx] = 0x61; - if (G_gpg_vstate.io_length > MAX_OUT - 2) { - xx = MAX_OUT - 2; - } else { - xx = G_gpg_vstate.io_length - 2; - } - G_io_apdu_buffer[tx + 1] = xx; - rx = gpg_io_exchange(CHANNEL_APDU, tx + 2); - // check get response - if ((G_io_apdu_buffer[0] != 0x00) || (G_io_apdu_buffer[1] != 0xc0) || (G_io_apdu_buffer[2] != 0x00) || - (G_io_apdu_buffer[3] != 0x00)) { - THROW(SW_COMMAND_NOT_ALLOWED); - return 0; - } + // if pending input chaining + if (G_gpg_vstate.io_cla & CLA_APP_CHAIN) { + goto in_chaining; } - os_memmove(G_io_apdu_buffer, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, G_gpg_vstate.io_length); - if (io_flags & IO_RETURN_AFTER_TX) { - rx = gpg_io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, G_gpg_vstate.io_length); - return 0; + if (io_flags & IO_ASYNCH_REPLY) { + // if IO_ASYNCH_REPLY has been set, + // io_exchange will return when IO_RETURN_AFTER_TX will set in ui + rx = io_exchange(CHANNEL_APDU | IO_ASYNCH_REPLY, 0); } else { - rx = gpg_io_exchange(CHANNEL_APDU, G_gpg_vstate.io_length); - } - } - - //--- full in chaining --- - if (rx < 4) { - THROW(SW_COMMAND_NOT_ALLOWED); - return SW_COMMAND_NOT_ALLOWED; - } - if (rx == 4) { - G_io_apdu_buffer[4] = 0; - rx = 4; - } - G_gpg_vstate.io_offset = 0; - G_gpg_vstate.io_length = 0; - G_gpg_vstate.io_cla = G_io_apdu_buffer[0]; - G_gpg_vstate.io_ins = G_io_apdu_buffer[1]; - G_gpg_vstate.io_p1 = G_io_apdu_buffer[2]; - G_gpg_vstate.io_p2 = G_io_apdu_buffer[3]; - G_gpg_vstate.io_lc = 0; - G_gpg_vstate.io_le = 0; - - switch (G_gpg_vstate.io_ins) { - case INS_GET_DATA: - case INS_GET_RESPONSE: - case INS_TERMINATE_DF: - case INS_ACTIVATE_FILE: - G_gpg_vstate.io_le = G_io_apdu_buffer[4]; - break; - - case INS_GET_CHALLENGE: - if (G_gpg_vstate.io_p1 == 0) { - G_gpg_vstate.io_le = G_io_apdu_buffer[4]; - break; + // --- full out chaining --- + G_gpg_vstate.io_offset = 0; + while (G_gpg_vstate.io_length > MAX_OUT) { + unsigned int tx, xx; + // send chunk + tx = MAX_OUT - 2; + memmove(G_io_apdu_buffer, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, tx); + G_gpg_vstate.io_length -= tx; + G_gpg_vstate.io_offset += tx; + G_io_apdu_buffer[tx] = (SW_CORRECT_BYTES_AVAILABLE >> 8) & 0xFF; + if (G_gpg_vstate.io_length > MAX_OUT - 2) { + xx = MAX_OUT - 2; + } else { + xx = G_gpg_vstate.io_length - 2; + } + G_io_apdu_buffer[tx + 1] = xx; + io_exchange(CHANNEL_APDU, tx + 2); + // check get response APDU + if ((G_io_apdu_buffer[OFFSET_CLA] != CLA_APP_DEF) || + (G_io_apdu_buffer[OFFSET_INS] != INS_GET_RESPONSE) || + (G_io_apdu_buffer[OFFSET_P1] != GET_RESPONSE) || + (G_io_apdu_buffer[OFFSET_P2] != GET_RESPONSE)) { + return; + } + } + memmove(G_io_apdu_buffer, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + G_gpg_vstate.io_length); + + if (io_flags & IO_RETURN_AFTER_TX) { + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, G_gpg_vstate.io_length); + return; + } + rx = io_exchange(CHANNEL_APDU, G_gpg_vstate.io_length); } - case INS_VERIFY: - case INS_CHANGE_REFERENCE_DATA: - if (G_io_apdu_buffer[4] == 0) { - break; - } - goto _default; - - default: - _default: - G_gpg_vstate.io_lc = G_io_apdu_buffer[4]; - os_memmove(G_gpg_vstate.work.io_buffer, G_io_apdu_buffer + 5, G_gpg_vstate.io_lc); - G_gpg_vstate.io_length = G_gpg_vstate.io_lc; - break; - } - - while (G_gpg_vstate.io_cla & 0x10) { - G_io_apdu_buffer[0] = 0x90; - G_io_apdu_buffer[1] = 0x00; - rx = gpg_io_exchange(CHANNEL_APDU, 2); - in_chaining: - if ((rx < 4) || ((G_io_apdu_buffer[0] & 0xEF) != (G_gpg_vstate.io_cla & 0xEF)) || - (G_io_apdu_buffer[1] != G_gpg_vstate.io_ins) || (G_io_apdu_buffer[2] != G_gpg_vstate.io_p1) || - (G_io_apdu_buffer[3] != G_gpg_vstate.io_p2)) { - THROW(SW_COMMAND_NOT_ALLOWED); - return SW_COMMAND_NOT_ALLOWED; + //--- full in chaining --- + if (rx < 4) { + return; } if (rx == 4) { - G_io_apdu_buffer[4] = 0; - rx = 4; + G_io_apdu_buffer[OFFSET_LC] = 0; } - G_gpg_vstate.io_cla = G_io_apdu_buffer[0]; - G_gpg_vstate.io_lc = G_io_apdu_buffer[4]; - if ((G_gpg_vstate.io_length + G_gpg_vstate.io_lc) > GPG_IO_BUFFER_LENGTH) { - return 1; + G_gpg_vstate.io_offset = 0; + G_gpg_vstate.io_length = 0; + G_gpg_vstate.io_cla = G_io_apdu_buffer[OFFSET_CLA]; + G_gpg_vstate.io_ins = G_io_apdu_buffer[OFFSET_INS]; + G_gpg_vstate.io_p1 = G_io_apdu_buffer[OFFSET_P1]; + G_gpg_vstate.io_p2 = G_io_apdu_buffer[OFFSET_P2]; + G_gpg_vstate.io_lc = 0; + G_gpg_vstate.io_le = 0; + G_gpg_vstate.io_p1p2 = U2(G_gpg_vstate.io_p1, G_gpg_vstate.io_p2); + + switch (G_gpg_vstate.io_ins) { + case INS_GET_DATA: + case INS_GET_RESPONSE: + case INS_TERMINATE_DF: + case INS_ACTIVATE_FILE: + G_gpg_vstate.io_le = G_io_apdu_buffer[OFFSET_LC]; + break; + + case INS_GET_CHALLENGE: + if (G_gpg_vstate.io_p1 == CHALLENGE_NOMINAL) { + G_gpg_vstate.io_le = G_io_apdu_buffer[OFFSET_LC]; + break; + } + + __attribute__((fallthrough)); + case INS_VERIFY: + case INS_CHANGE_REFERENCE_DATA: + if (G_io_apdu_buffer[OFFSET_LC] == 0) { + break; + } + + __attribute__((fallthrough)); + default: + G_gpg_vstate.io_lc = G_io_apdu_buffer[OFFSET_LC]; + memmove(G_gpg_vstate.work.io_buffer, + G_io_apdu_buffer + OFFSET_CDATA, + G_gpg_vstate.io_lc); + G_gpg_vstate.io_length = G_gpg_vstate.io_lc; + break; } - os_memmove(G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_length, G_io_apdu_buffer + 5, G_gpg_vstate.io_lc); - G_gpg_vstate.io_length += G_gpg_vstate.io_lc; - } - return 0; + PRINTF("[IO] - io_do: 1st APDU=0x %02x.%02x.%02x.%02x - %d (0x%x)\n", + G_gpg_vstate.io_cla, + G_gpg_vstate.io_ins, + G_gpg_vstate.io_p1, + G_gpg_vstate.io_p2, + G_gpg_vstate.io_lc, + G_gpg_vstate.io_lc); + while (G_gpg_vstate.io_cla & CLA_APP_CHAIN) { + G_io_apdu_buffer[0] = ((SW_OK >> 8) & 0xFF); + G_io_apdu_buffer[1] = (SW_OK & 0xFF); + rx = io_exchange(CHANNEL_APDU, 2); + in_chaining: + if ((rx < 4) || + ((G_io_apdu_buffer[OFFSET_CLA] & CLA_APP_APDU_PIN) != + (G_gpg_vstate.io_cla & CLA_APP_APDU_PIN)) || + (G_io_apdu_buffer[OFFSET_INS] != G_gpg_vstate.io_ins) || + (G_io_apdu_buffer[OFFSET_P1] != G_gpg_vstate.io_p1) || + (G_io_apdu_buffer[OFFSET_P2] != G_gpg_vstate.io_p2)) { + return; + } + if (rx == 4) { + G_io_apdu_buffer[OFFSET_LC] = 0; + } + G_gpg_vstate.io_cla = G_io_apdu_buffer[OFFSET_CLA]; + G_gpg_vstate.io_lc = G_io_apdu_buffer[OFFSET_LC]; + if ((G_gpg_vstate.io_length + G_gpg_vstate.io_lc) > GPG_IO_BUFFER_LENGTH) { + return; + } + PRINTF("[IO] - io_do: Next APDU=0x %02x.%02x.%02x.%02x - %d (0x%x)\n", + G_gpg_vstate.io_cla, + G_gpg_vstate.io_ins, + G_gpg_vstate.io_p1, + G_gpg_vstate.io_p2, + G_gpg_vstate.io_lc, + G_gpg_vstate.io_lc); + memmove(G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_length, + G_io_apdu_buffer + OFFSET_CDATA, + G_gpg_vstate.io_lc); + G_gpg_vstate.io_length += G_gpg_vstate.io_lc; + } } diff --git a/src/gpg_main.c b/src/gpg_main.c index cd3ee69..d0da2fd 100644 --- a/src/gpg_main.c +++ b/src/gpg_main.c @@ -1,192 +1,62 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#ifndef GPG_DEBUG_MAIN - -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" - -#include "gpg_ux_nanos.h" -//#include "gpg_ux_blue.h" - -#include "os_io_seproxyhal.h" -#include "string.h" -#include "glyphs.h" +#include "gpg_ux.h" +#include "io.h" +#include "usbd_ccid_if.h" /* ----------------------------------------------------------------------- */ /* --- Application Entry --- */ /* ----------------------------------------------------------------------- */ -void gpg_main(void) { - unsigned int io_flags; - io_flags = 0; - for (;;) { - volatile unsigned short sw = 0; - BEGIN_TRY { - TRY { - gpg_io_do(io_flags); - sw = gpg_dispatch(); - } - CATCH_OTHER(e) { - gpg_io_discard(1); - if ((e & 0xFFFF0000) || (((e & 0xF000) != 0x6000) && ((e & 0xF000) != 0x9000))) { - gpg_io_insert_u32(e); - sw = 0x6f42; - } else { - sw = e; - } - } - FINALLY { - if (sw) { - gpg_io_insert_u16(sw); - io_flags = 0; - } else { - io_flags = IO_ASYNCH_REPLY; - } - } - } - END_TRY; - } -} - -unsigned char io_event(unsigned char channel) { - // nothing done with the event, throw an error on the transport layer if - // needed - // can't have more than one tag in the reply, not supported yet. - switch (G_io_seproxyhal_spi_buffer[0]) { - case SEPROXYHAL_TAG_FINGER_EVENT: - UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); - break; - // power off if long push, else pass to the application callback if any - case SEPROXYHAL_TAG_BUTTON_PUSH_EVENT: // for Nano S - UX_BUTTON_PUSH_EVENT(G_io_seproxyhal_spi_buffer); - break; - - // other events are propagated to the UX just in case - default: - UX_DEFAULT_EVENT(); - break; - - case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: - UX_DISPLAYED_EVENT({}); - break; - case SEPROXYHAL_TAG_TICKER_EVENT: - UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, { - // only allow display when not locked of overlayed by an OS UX. - if (UX_ALLOWED) { - UX_REDISPLAY(); - } - }); - break; - } - - // close the event if not done previously (by a display or whatever) - if (!io_seproxyhal_spi_is_status_sent()) { - io_seproxyhal_general_status(); - } - // command has been processed, DO NOT reset the current APDU transport - return 1; -} - -unsigned short io_exchange_al(unsigned char channel, unsigned short tx_len) { - switch (channel & ~(IO_FLAGS)) { - case CHANNEL_KEYBOARD: - break; +void app_main(void) { + unsigned int io_flags = 0; + io_flags = 0; + volatile unsigned short sw = SW_UNKNOWN; - // multiplexed io exchange over a SPI channel and TLV encapsulated protocol - case CHANNEL_SPI: - if (tx_len) { - io_seproxyhal_spi_send(G_io_apdu_buffer, tx_len); + // start communication with MCU + ui_CCID_reset(); - if (channel & IO_RESET_AFTER_REPLIED) { - reset(); - } - return 0; // nothing received from the master so far (it's a tx - // transaction) - } else { - return io_seproxyhal_spi_recv(G_io_apdu_buffer, sizeof(G_io_apdu_buffer), 0); - } - - default: - THROW(INVALID_PARAMETER); - return 0; - } - return 0; -} - -void app_exit(void) { - BEGIN_TRY_L(exit) { - TRY_L(exit) { - os_sched_exit(-1); - } - FINALLY_L(exit) { - } - } - END_TRY_L(exit); -} - -/* -------------------------------------------------------------- */ - -__attribute__((section(".boot"))) int main(void) { - // exit critical section - __asm volatile("cpsie i"); + // set up + io_init(); - // ensure exception will work as planned - os_boot(); - for (;;) { - UX_INIT(); + gpg_init(); - BEGIN_TRY { - TRY { - // start communication with MCU - io_seproxyhal_init(); + // set up initial screen + ui_init(); - USB_power(1); -#if HAVE_USB_CLASS_CCID - io_usb_ccid_set_card_inserted(1); -#endif - - // set up - gpg_init(); - - // set up initial screen - ui_init(); - - // start the application - // the first exchange will: - // - display the initial screen - // - send the ATR - // - receive the first command - gpg_main(); - } - CATCH(EXCEPTION_IO_RESET) { - // reset IO and UX - continue; - } - CATCH_ALL { - break; - } - FINALLY { - } + // start the application + // the first exchange will: + // - display the initial screen + // - send the ATR + // - receive the first command + for (;;) { + gpg_io_do(io_flags); + sw = gpg_dispatch(); + if (sw) { + PRINTF("[MAIN] - FINALLY INSERT sw=0x%x\n", sw); + if ((sw != SW_OK) && ((sw & 0xFF00) != SW_CORRECT_BYTES_AVAILABLE)) { + gpg_io_discard(1); + } + gpg_io_insert_u16(sw); + io_flags = 0; + } else { + io_flags = IO_ASYNCH_REPLY; + } } - END_TRY; - } - app_exit(); } - -#endif diff --git a/src/gpg_mse.c b/src/gpg_mse.c index c9d2db8..83d79ac 100644 --- a/src/gpg_mse.c +++ b/src/gpg_mse.c @@ -1,72 +1,83 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" -static int gpg_mse_set(int crt, int ref) { - if (crt == 0xA4) { - if (ref == 0x02) { - G_gpg_vstate.mse_aut = &G_gpg_vstate.kslot->dec; - } - if (ref == 0x03) { - G_gpg_vstate.mse_aut = &G_gpg_vstate.kslot->aut; +/** + * Set a new MSE configuration + * + * @param[in] crt selected key + * @param[in] ref new operation type + * + */ +static void gpg_mse_set(int crt, int ref) { + if (crt == KEY_AUT) { + if (ref == 0x02) { + G_gpg_vstate.mse_aut = &G_gpg_vstate.kslot->dec; + } + if (ref == 0x03) { + G_gpg_vstate.mse_aut = &G_gpg_vstate.kslot->aut; + } } - } - if (crt == 0xB8) { - if (ref == 0x02) { - G_gpg_vstate.mse_dec = &G_gpg_vstate.kslot->dec; - } - if (ref == 0x03) { - G_gpg_vstate.mse_dec = &G_gpg_vstate.kslot->aut; + if (crt == KEY_DEC) { + if (ref == 0x02) { + G_gpg_vstate.mse_dec = &G_gpg_vstate.kslot->dec; + } + if (ref == 0x03) { + G_gpg_vstate.mse_dec = &G_gpg_vstate.kslot->aut; + } } - } - return 0; } -int gpg_mse_reset() { - gpg_mse_set(0xA4, 0x03); - gpg_mse_set(0xB8, 0x02); - return 0; +/** + * Reset MSE config + * + */ +void gpg_mse_reset() { + gpg_mse_set(KEY_AUT, 0x03); + gpg_mse_set(KEY_DEC, 0x02); } +/** + * APDU handler to Manage Security Environment + * + * @return Status Word + * + */ int gpg_apdu_mse() { - int crt, ref; + int crt, ref; - if ((G_gpg_vstate.io_p1 != 0x41) || ((G_gpg_vstate.io_p2 != 0xA4) && (G_gpg_vstate.io_p2 != 0xB8))) { - THROW(SW_INCORRECT_P1P2); - return SW_INCORRECT_P1P2; - } + if ((G_gpg_vstate.io_p1 != MSE_SET) || + ((G_gpg_vstate.io_p2 != KEY_AUT) && (G_gpg_vstate.io_p2 != KEY_DEC))) { + return SW_WRONG_P1P2; + } - crt = gpg_io_fetch_u16(); - if (crt != 0x8301) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } + crt = gpg_io_fetch_u16(); + if (crt != 0x8301) { + return SW_WRONG_DATA; + } - ref = gpg_io_fetch_u8(); - if ((ref != 0x02) && (ref != 0x03)) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } + ref = gpg_io_fetch_u8(); + if ((ref != 0x02) && (ref != 0x03)) { + return SW_WRONG_DATA; + } - gpg_mse_set(crt, ref); - gpg_io_discard(1); - return SW_OK; -} \ No newline at end of file + gpg_mse_set(G_gpg_vstate.io_p2, ref); + gpg_io_discard(1); + return SW_OK; +} diff --git a/src/gpg_nvram.c b/src/gpg_nvram.c deleted file mode 100644 index 040e04a..0000000 --- a/src/gpg_nvram.c +++ /dev/null @@ -1,26 +0,0 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" -#include "gpg_vars.h" - -#if defined(TARGET_NANOX) || defined(TARGET_NANOS2) -const gpg_nv_state_t N_state_pic; -#else -gpg_nv_state_t N_state_pic; -#endif diff --git a/src/gpg_pin.c b/src/gpg_pin.c index fdfe903..7f74d3c 100644 --- a/src/gpg_pin.c +++ b/src/gpg_pin.c @@ -1,280 +1,365 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" +#include "gpg_ux.h" -#include "gpg_ux_nanos.h" - +/** + * Get Pin structure from reference ID value + * + * @param[in] pinref PinCode reference ID + * + * @return Pin structure, or NULL if invalid + * + */ gpg_pin_t *gpg_pin_get_pin(int pinref) { - switch (pinref) { - case PIN_ID_PW1: - case PIN_ID_PW2: - return &N_gpg_pstate->PW1; - case PIN_ID_PW3: - return &N_gpg_pstate->PW3; - case PIN_ID_RC: - return &N_gpg_pstate->RC; - } - return NULL; + switch (pinref) { + case PIN_ID_PW1: + case PIN_ID_PW2: + return (gpg_pin_t *) &N_gpg_pstate->PW1; + case PIN_ID_PW3: + return (gpg_pin_t *) &N_gpg_pstate->PW3; + case PIN_ID_RC: + return (gpg_pin_t *) &N_gpg_pstate->RC; + } + return NULL; } +/** + * Get Pin index from reference ID value + * + * @param[in] pinref PinCode reference ID + * + * @return Pin index + * + */ static int gpg_pin_get_state_index(unsigned int pinref) { - switch (pinref) { - case PIN_ID_PW1: - return 1; - case PIN_ID_PW2: - return 2; - case PIN_ID_PW3: - return 3; - case PIN_ID_RC: - return 4; - } - return -1; -} - -static int gpg_pin_check_internal(gpg_pin_t *pin, unsigned char *pin_val, int pin_len) { - unsigned int counter; - - if (pin->counter == 0) { - return SW_PIN_BLOCKED; - } - - counter = pin->counter - 1; - gpg_nvm_write(&(pin->counter), &counter, sizeof(int)); - cx_sha256_init(&G_gpg_vstate.work.md.sha256); - cx_hash((cx_hash_t *)&G_gpg_vstate.work.md.sha256, CX_LAST, pin_val, pin_len, G_gpg_vstate.work.md.H, - sizeof(G_gpg_vstate.work.md.H)); - if (os_memcmp(G_gpg_vstate.work.md.H, pin->value, 32)) { - return SW_SECURITY_STATUS_NOT_SATISFIED; - } - - counter = 3; - gpg_nvm_write(&(pin->counter), &counter, sizeof(int)); - return SW_OK; + switch (pinref) { + case PIN_ID_PW1: + return 1; + case PIN_ID_PW2: + return 2; + case PIN_ID_PW3: + return 3; + case PIN_ID_RC: + return 4; + } + return -1; } -static void gpg_pin_check_throw(gpg_pin_t *pin, int pinID, unsigned char *pin_val, int pin_len) { - int sw; - gpg_pin_set_verified(pinID, 0); - sw = gpg_pin_check_internal(pin, pin_val, pin_len); - if (sw == SW_OK) { - gpg_pin_set_verified(pinID, 1); - return; - } - THROW(sw); - return; -} +/** + * Compare the PinCode hash and handle the associated counter + * + * @param[in] pin PinCode reference to check + * @param[in] pin_val PinCode value + * @param[in] pin_len PinCode length + * + * @return Status Word + * + */ +static int gpg_pin_check_internal(gpg_pin_t *pin, const unsigned char *pin_val, int pin_len) { + unsigned int counter; + cx_err_t error = CX_INTERNAL_ERROR; -int gpg_pin_check(gpg_pin_t *pin, int pinID, unsigned char *pin_val, unsigned int pin_len) { - int sw; - gpg_pin_set_verified(pinID, 0); - sw = gpg_pin_check_internal(pin, pin_val, pin_len); - if (sw == SW_OK) { - gpg_pin_set_verified(pinID, 1); - } - return sw; -} + if (pin->counter == 0) { + return SW_PIN_BLOCKED; + } -void gpg_pin_set(gpg_pin_t *pin, unsigned char *pin_val, unsigned int pin_len) { - cx_sha256_t sha256; + counter = pin->counter - 1; + cx_sha256_init(&G_gpg_vstate.work.md.sha256); + CX_CHECK(cx_hash_no_throw((cx_hash_t *) &G_gpg_vstate.work.md.sha256, + CX_LAST, + pin_val, + pin_len, + G_gpg_vstate.work.md.H, + sizeof(G_gpg_vstate.work.md.H))); + if (memcmp(G_gpg_vstate.work.md.H, pin->value, 32)) { + error = (counter == 0) ? SW_PIN_BLOCKED : SW_SECURITY_STATUS_NOT_SATISFIED; + } else { + counter = 3; + error = SW_OK; + } - gpg_pin_t newpin; +end: + if (counter != pin->counter) { + nvm_write(&(pin->counter), &counter, sizeof(int)); + } + return error; +} - cx_sha256_init(&sha256); - cx_hash((cx_hash_t *)&sha256, CX_LAST, pin_val, pin_len, newpin.value, 32); - newpin.length = pin_len; - newpin.counter = 3; +/** + * Check the PinCode value and set verification status + * + * @param[in] pin PinCode reference to check + * @param[in] pin_val PinCode value + * @param[in] pin_len PinCode length + * + * @return Status Word + * + */ +int gpg_pin_check(gpg_pin_t *pin, int pinID, const unsigned char *pin_val, unsigned int pin_len) { + int sw = SW_UNKNOWN; + gpg_pin_set_verified(pinID, 0); + sw = gpg_pin_check_internal(pin, pin_val, pin_len); + if (sw == SW_OK) { + gpg_pin_set_verified(pinID, 1); + } + return sw; +} - gpg_nvm_write(pin, &newpin, sizeof(gpg_pin_t)); +/** + * Set the PinCode value in NVRam + * + * @param[in] pin PinCode reference to set + * @param[in] pin_val PinCode value + * @param[in] pin_len PinCode length + * + * @return Status Word + * + */ +int gpg_pin_set(gpg_pin_t *pin, unsigned char *pin_val, unsigned int pin_len) { + cx_sha256_t sha256; + cx_err_t error = CX_INTERNAL_ERROR; + gpg_pin_t newpin; + + cx_sha256_init(&sha256); + CX_CHECK(cx_hash_no_throw((cx_hash_t *) &sha256, CX_LAST, pin_val, pin_len, newpin.value, 32)); + newpin.length = pin_len; + newpin.counter = 3; + + nvm_write(pin, &newpin, sizeof(gpg_pin_t)); +end: + if (error != CX_OK) { + return error; + } + return SW_OK; } -int gpg_pin_set_verified(int pinID, int verified) { - int idx; - idx = gpg_pin_get_state_index(pinID); - if (idx >= 0) { - G_gpg_vstate.verified_pin[idx] = verified; - return verified; - } - return 0; +/** + * Change the Pin verification status + * + * @param[in] pinID PinCode ID to change + * @param[in] verified new status + * + */ +void gpg_pin_set_verified(int pinID, int verified) { + int idx; + idx = gpg_pin_get_state_index(pinID); + if (idx >= 0) { + G_gpg_vstate.verified_pin[idx] = verified; + } } +/** + * Check if the selected Pin is verified + * + * @param[in] pinID PinCode ID to check + * + * @return 0 or 1 (false or true) + * + */ int gpg_pin_is_verified(int pinID) { - int idx; - idx = gpg_pin_get_state_index(pinID); - if (idx >= 0) { - return G_gpg_vstate.verified_pin[idx]; - } - return 0; + int idx; + idx = gpg_pin_get_state_index(pinID); + if (idx >= 0) { + return G_gpg_vstate.verified_pin[idx]; + } + return 0; } +/** + * Check if the selected Pin is blocked + * + * @param[in] pin PinCode reference to check + * + * @return 0 or 1 (false or true) + * + */ int gpg_pin_is_blocked(gpg_pin_t *pin) { - return pin->counter == 0; + return pin->counter == 0; } +/** + * APDU handler to Verify PinCode + * + * @return Status Word + * + */ int gpg_apdu_verify() { - gpg_pin_t *pin; + int sw = SW_UNKNOWN; + gpg_pin_t *pin; - pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); - if (pin == NULL) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - - // PINPAD - if (G_gpg_vstate.io_cla == 0xEF) { - if (gpg_pin_is_blocked(pin)) { - THROW(SW_PIN_BLOCKED); - return SW_PIN_BLOCKED; + pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); + if (pin == NULL) { + return SW_WRONG_DATA; } - if (G_gpg_vstate.pinmode == PIN_MODE_SCREEN) { - // Delegate pin check to ui - gpg_io_discard(1); - ui_menu_pinentry_display(0); - return 0; - } - if (G_gpg_vstate.pinmode == PIN_MODE_CONFIRM) { - // Delegate pin check to ui - gpg_io_discard(1); - ui_menu_pinconfirm_display(0); - return 0; + // PINPAD + if (G_gpg_vstate.io_cla == CLA_APP_APDU_PIN) { + if (gpg_pin_is_blocked(pin)) { + return SW_PIN_BLOCKED; + } + + switch (G_gpg_vstate.pinmode) { + case PIN_MODE_SCREEN: + // Delegate pin check to ui + gpg_io_discard(1); + ui_menu_pinentry_display(0); + sw = 0; + break; + case PIN_MODE_CONFIRM: + // Delegate pin check to ui + gpg_io_discard(1); + ui_menu_pinconfirm_display(G_gpg_vstate.io_p2); + sw = 0; + break; + case PIN_MODE_TRUST: + gpg_pin_set_verified(G_gpg_vstate.io_p2, 1); + gpg_io_discard(1); + sw = 0; + break; + default: + sw = SW_WRONG_DATA; + break; + } + return sw; } - if (G_gpg_vstate.pinmode == PIN_MODE_TRUST) { - gpg_pin_set_verified(G_gpg_vstate.io_p2, 1); - gpg_io_discard(1); - return SW_OK; - } - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - // NORMAL CHECK - if ((G_gpg_vstate.io_p1 == 0) && G_gpg_vstate.io_length) { - if (gpg_pin_is_blocked(pin)) { - THROW(SW_PIN_BLOCKED); - return SW_PIN_BLOCKED; + // NORMAL CHECK + if ((G_gpg_vstate.io_p1 == PIN_VERIFY) && G_gpg_vstate.io_length) { + if (gpg_pin_is_blocked(pin)) { + return SW_PIN_BLOCKED; + } + sw = gpg_pin_check(pin, + G_gpg_vstate.io_p2, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + G_gpg_vstate.io_length); + gpg_io_discard(1); + return sw; } - gpg_pin_check_throw(pin, G_gpg_vstate.io_p2, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, - G_gpg_vstate.io_length); - gpg_io_discard(1); - return SW_OK; - } - gpg_io_discard(1); + gpg_io_discard(1); - // STATUS REQUEST - if ((G_gpg_vstate.io_p1 == 0) && G_gpg_vstate.io_length == 0) { - if (gpg_pin_is_verified(G_gpg_vstate.io_p2)) { - return SW_OK; + // STATUS REQUEST + if ((G_gpg_vstate.io_p1 == PIN_VERIFY) && G_gpg_vstate.io_length == 0) { + if (gpg_pin_is_verified(G_gpg_vstate.io_p2)) { + return SW_OK; + } + return SW_PWD_NOT_CHECKED | pin->counter; } - return 0x63C0 | pin->counter; - } - // RESET REQUEST - if ((G_gpg_vstate.io_p1 == 0xFF) && G_gpg_vstate.io_length == 0) { - gpg_pin_set_verified(G_gpg_vstate.io_p2, 0); - return SW_OK; - } + // RESET REQUEST + if ((G_gpg_vstate.io_p1 == PIN_NOT_VERIFIED) && G_gpg_vstate.io_length == 0) { + gpg_pin_set_verified(G_gpg_vstate.io_p2, 0); + return SW_OK; + } - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; + return SW_WRONG_DATA; } +/** + * APDU handler to Change PinCode + * + * @return Status Word + * + */ int gpg_apdu_change_ref_data() { - gpg_pin_t *pin; - int len, newlen; + int sw = SW_UNKNOWN; + gpg_pin_t *pin; + int len, newlen; - pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); - if (pin == NULL) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - - gpg_pin_set_verified(pin->ref, 0); - - // --- PW1/PW3 pin --- - if (gpg_pin_is_blocked(pin)) { - THROW(SW_PIN_BLOCKED); - return SW_PIN_BLOCKED; - } - // avoid any-overflow whitout giving info - if (G_gpg_vstate.io_length == 0) { - if (G_gpg_vstate.pinmode != PIN_MODE_HOST) { - // Delegate pin change to ui - gpg_io_discard(1); - ui_menu_pinentry_display(0); - return 0; + pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); + if (pin == NULL) { + return SW_WRONG_DATA; } - } - if (pin->length > G_gpg_vstate.io_length) { - len = G_gpg_vstate.io_length; - } else { - len = pin->length; - } + gpg_pin_set_verified(pin->ref, 0); - gpg_pin_check_throw(pin, pin->ref, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, len); + // --- PW1/PW3 pin --- + if (gpg_pin_is_blocked(pin)) { + return SW_PIN_BLOCKED; + } + // avoid any-overflow without giving info + if (G_gpg_vstate.io_length == 0) { + // Delegate pin change to ui + gpg_io_discard(1); + ui_menu_pinentry_display(0); + return 0; + } - newlen = G_gpg_vstate.io_length - len; - if ((newlen > GPG_MAX_PW_LENGTH) || ((pin->ref == PIN_ID_PW1) && (newlen < 6)) || - ((pin->ref == PIN_ID_PW3) && (newlen < 8))) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - gpg_pin_set(pin, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset + len, newlen); - gpg_io_discard(1); - return SW_OK; + len = MIN(G_gpg_vstate.io_length, pin->length); + sw = gpg_pin_check(pin, pin->ref, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, len); + if (sw != SW_OK) { + return sw; + } + + newlen = G_gpg_vstate.io_length - len; + if ((newlen > GPG_MAX_PW_LENGTH) || + ((pin->ref == PIN_ID_PW1) && (newlen < GPG_MIN_PW1_LENGTH)) || + ((pin->ref == PIN_ID_PW3) && (newlen < GPG_MIN_PW3_LENGTH))) { + return SW_WRONG_DATA; + } + sw = gpg_pin_set(pin, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset + len, newlen); + gpg_io_discard(1); + return sw; } +/** + * APDU handler to Reste PinCode or Counter + * + * @return Status Word + * + */ int gpg_apdu_reset_retry_counter() { - gpg_pin_t *pin_pw1; - gpg_pin_t *pin_pw3; - gpg_pin_t *pin_rc; - int rc_len, pw1_len; - - pin_pw1 = gpg_pin_get_pin(PIN_ID_PW1); - pin_pw3 = gpg_pin_get_pin(PIN_ID_PW3); - pin_rc = gpg_pin_get_pin(PIN_ID_RC); - - if (G_gpg_vstate.io_p1 == 2) { - if (!gpg_pin_is_verified(PIN_ID_PW3)) { - THROW(SW_SECURITY_STATUS_NOT_SATISFIED); - return SW_SECURITY_STATUS_NOT_SATISFIED; - } - rc_len = 0; - pw1_len = G_gpg_vstate.io_length; - } else { - // avoid any-overflow whitout giving info - if (pin_rc->length > G_gpg_vstate.io_length) { - rc_len = G_gpg_vstate.io_length; + int sw = SW_UNKNOWN; + gpg_pin_t *pin_pw1; + gpg_pin_t *pin_rc; + int rc_len, pw1_len; + + pin_pw1 = gpg_pin_get_pin(PIN_ID_PW1); + pin_rc = gpg_pin_get_pin(PIN_ID_RC); + + if (G_gpg_vstate.io_p1 == RESET_RETRY_WITH_PW3) { + // PW3 must be verified, and the data contain the new PW1 + if (!gpg_pin_is_verified(PIN_ID_PW3)) { + return SW_SECURITY_STATUS_NOT_SATISFIED; + } + rc_len = 0; + pw1_len = G_gpg_vstate.io_length; } else { - rc_len = pin_rc->length; + // The data contain the Resetting Code + the new PW1 + // avoid any-overflow without giving info + rc_len = MIN(G_gpg_vstate.io_length, pin_rc->length); + pw1_len = G_gpg_vstate.io_length - rc_len; + sw = gpg_pin_check(pin_rc, + pin_rc->ref, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + rc_len); + if (sw != SW_OK) { + return sw; + } } - pw1_len = G_gpg_vstate.io_length - rc_len; - gpg_pin_check_throw(pin_rc, pin_rc->ref, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, rc_len); - } - if ((pw1_len > GPG_MAX_PW_LENGTH) || (pw1_len < 6)) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - gpg_pin_set(pin_pw1, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset + rc_len, pw1_len); - gpg_io_discard(1); - return SW_OK; + if ((pw1_len > GPG_MAX_PW_LENGTH) || (pw1_len < GPG_MIN_PW1_LENGTH)) { + return SW_WRONG_DATA; + } + sw = gpg_pin_set(pin_pw1, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset + rc_len, + pw1_len); + gpg_io_discard(1); + return sw; } diff --git a/src/gpg_pso.c b/src/gpg_pso.c index 3ee1f68..360c720 100644 --- a/src/gpg_pso.c +++ b/src/gpg_pso.c @@ -1,338 +1,433 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" -#include "gpg_ux_nanos.h" +#include "gpg_ux.h" +#include "cx_errors.h" -const unsigned char gpg_oid_sha256[] = {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, - 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}; -const unsigned char gpg_oid_sha512[] = {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, - 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}; +const unsigned char gpg_oid_sha256[] = {0x30, + 0x31, + 0x30, + 0x0d, + 0x06, + 0x09, + 0x60, + 0x86, + 0x48, + 0x01, + 0x65, + 0x03, + 0x04, + 0x02, + 0x01, + 0x05, + 0x00, + 0x04, + 0x20}; +const unsigned char gpg_oid_sha512[] = {0x30, + 0x51, + 0x30, + 0x0d, + 0x06, + 0x09, + 0x60, + 0x86, + 0x48, + 0x01, + 0x65, + 0x03, + 0x04, + 0x02, + 0x03, + 0x05, + 0x00, + 0x04, + 0x40}; +/** + * Reset PW1 verified status + * + */ static void gpg_pso_reset_PW1() { - if (N_gpg_pstate->PW_status[0] == 0) { - gpg_pin_set_verified(PIN_ID_PW1, 0); - } + if (N_gpg_pstate->PW_status[0] == 0) { + gpg_pin_set_verified(PIN_ID_PW1, 0); + } } +/** + * Perform a Digital Signature + * + * @param[in] sigKey signing key + * + * @return Status Word + * + */ static int gpg_sign(gpg_key_t *sigkey) { - // --- RSA - if (sigkey->attributes.value[0] == 1) { - cx_rsa_private_key_t *key; - unsigned int ksz, l; - ksz = (sigkey->attributes.value[1] << 8) | sigkey->attributes.value[2]; - ksz = ksz >> 3; - switch (ksz) { - case 1024 / 8: - key = (cx_rsa_private_key_t *)&sigkey->priv_key.rsa1024; - break; - case 2048 / 8: - key = (cx_rsa_private_key_t *)&sigkey->priv_key.rsa2048; - break; - case 3072 / 8: - key = (cx_rsa_private_key_t *)&sigkey->priv_key.rsa3072; - break; - case 4096 / 8: - key = (cx_rsa_private_key_t *)&sigkey->priv_key.rsa4096; - break; - } - if (key->size != ksz) { - THROW(SW_CONDITIONS_NOT_SATISFIED); - return SW_CONDITIONS_NOT_SATISFIED; - } + cx_err_t error = CX_INTERNAL_ERROR; + cx_rsa_private_key_t *rsa_key = NULL; + unsigned int ksz, l; + cx_ecfp_private_key_t *ecfp_key = NULL; + unsigned int s_len, i, rs_len, info; + unsigned char *rs; - // sign - if (ksz < G_gpg_vstate.io_length) { - THROW(SW_WRONG_LENGTH); - } - l = ksz - G_gpg_vstate.io_length; - os_memmove(G_gpg_vstate.work.io_buffer + l, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length); - os_memset(G_gpg_vstate.work.io_buffer, 0xFF, l); - G_gpg_vstate.work.io_buffer[0] = 0; - G_gpg_vstate.work.io_buffer[1] = 1; - G_gpg_vstate.work.io_buffer[l - 1] = 0; - ksz = cx_rsa_decrypt(key, CX_PAD_NONE, CX_NONE, G_gpg_vstate.work.io_buffer, ksz, G_gpg_vstate.work.io_buffer, ksz); - // send - gpg_io_discard(0); - gpg_io_inserted(ksz); - gpg_pso_reset_PW1(); - return SW_OK; - } - // --- ECDSA/EdDSA - if ((sigkey->attributes.value[0] == 19) || (sigkey->attributes.value[0] == 22)) { - cx_ecfp_private_key_t *key; - unsigned int sz, i, rs_len, info; - unsigned char * rs; + switch (sigkey->attributes.value[0]) { + case KEY_ID_RSA: + ksz = U2BE(sigkey->attributes.value, 1) >> 3; + switch (ksz) { + case 2048 / 8: + rsa_key = (cx_rsa_private_key_t *) &sigkey->priv_key.rsa2048; + break; + case 3072 / 8: + rsa_key = (cx_rsa_private_key_t *) &sigkey->priv_key.rsa3072; + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096 / 8: + rsa_key = (cx_rsa_private_key_t *) &sigkey->priv_key.rsa4096; + break; +#endif + default: + break; + } + if ((rsa_key == NULL) || (rsa_key->size != ksz)) { + error = SW_CONDITIONS_NOT_SATISFIED; + break; + } - key = &sigkey->priv_key.ecfp; + // sign + if (ksz < G_gpg_vstate.io_length) { + error = SW_WRONG_LENGTH; + break; + } + l = ksz - G_gpg_vstate.io_length; + memmove(G_gpg_vstate.work.io_buffer + l, + G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length); + memset(G_gpg_vstate.work.io_buffer, 0xFF, l); + G_gpg_vstate.work.io_buffer[0] = 0; + G_gpg_vstate.work.io_buffer[1] = 1; + G_gpg_vstate.work.io_buffer[l - 1] = 0; + CX_CHECK(cx_rsa_decrypt_no_throw(rsa_key, + CX_PAD_NONE, + CX_NONE, + G_gpg_vstate.work.io_buffer, + ksz, + G_gpg_vstate.work.io_buffer, + &ksz)); + // send + gpg_io_discard(0); + gpg_io_inserted(ksz); + gpg_pso_reset_PW1(); + error = SW_OK; + break; -// sign #define RS (G_gpg_vstate.work.io_buffer + (GPG_IO_BUFFER_LENGTH - 256)) - if (sigkey->attributes.value[0] == 19) { - sz = gpg_curve2domainlen(key->curve); - if ((sz == 0) || (key->d_len != sz)) { - THROW(SW_CONDITIONS_NOT_SATISFIED); - return SW_CONDITIONS_NOT_SATISFIED; - } - sz = cx_ecdsa_sign(key, CX_RND_TRNG, CX_NONE, G_gpg_vstate.work.io_buffer, sz, RS, 256, &info); - // reencode r,s in MPI format - gpg_io_discard(0); + case KEY_ID_ECDSA: + ecfp_key = &sigkey->priv_key.ecfp; + ksz = (unsigned int) gpg_curve2domainlen(ecfp_key->curve); + if ((ksz == 0) || (ecfp_key->d_len != ksz)) { + error = SW_CONDITIONS_NOT_SATISFIED; + break; + } + s_len = 256; + CX_CHECK(cx_ecdsa_sign_no_throw(ecfp_key, + CX_RND_TRNG, + CX_NONE, + G_gpg_vstate.work.io_buffer, + ksz, + RS, + &s_len, + &info)); + // re-encode r,s in MPI format + gpg_io_discard(0); - rs_len = RS[3]; - rs = &RS[4]; + rs_len = RS[3]; + rs = &RS[4]; - for (i = 0; i < 2; i++) { - if (*rs == 0) { - rs++; - rs_len--; - } - gpg_io_insert_u8(0); - gpg_io_insert(rs, rs_len); - rs = rs + rs_len; - rs_len = rs[1]; - rs += 2; - } - } else { - sz = cx_eddsa_sign(key, CX_NONE, CX_SHA512, G_gpg_vstate.work.io_buffer, G_gpg_vstate.io_length, NULL, 0, RS, 256, - NULL); - gpg_io_discard(0); - gpg_io_insert(RS, sz); + for (i = 0; i < 2; i++) { + if (*rs == 0) { + rs++; + rs_len--; + } + gpg_io_insert_u8(0); + gpg_io_insert(rs, rs_len); + rs = rs + rs_len; + rs_len = rs[1]; + rs += 2; + } + error = SW_OK; + break; + + case KEY_ID_EDDSA: + ecfp_key = &sigkey->priv_key.ecfp; + ksz = 256; + CX_CHECK(cx_eddsa_sign_no_throw(ecfp_key, + CX_SHA512, + G_gpg_vstate.work.io_buffer, + G_gpg_vstate.io_length, + RS, + ksz)); + CX_CHECK(cx_ecdomain_parameters_length(ecfp_key->curve, &ksz)); + ksz *= 2; + gpg_io_discard(0); + gpg_io_insert(RS, ksz); + error = SW_OK; + break; + + default: + // --- PSO:CDS NOT SUPPORTED + error = SW_REFERENCED_DATA_NOT_FOUND; + break; } -#undef RS - // send +end: gpg_pso_reset_PW1(); - return SW_OK; - } - // --- PSO:CDS NOT SUPPORTED - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; + return error; } +/** + * APDU handler to Perform Security Operation + * + * @return Status Word + * + */ int gpg_apdu_pso() { - unsigned int t, l, ksz; + cx_err_t error = CX_INTERNAL_ERROR; + unsigned int t, l, ksz; + unsigned int cnt, pad_byte; + unsigned int msg_len; + unsigned int curve; + cx_aes_key_t *aes_key = NULL; + cx_rsa_private_key_t *rsa_key = NULL; + cx_ecfp_private_key_t *ecfp_key = NULL; - unsigned int pso; + // UIF HANDLE + switch (G_gpg_vstate.io_p1p2) { + // --- PSO:CDS --- + case PSO_CDS: + if (G_gpg_vstate.kslot->sig.UIF[0]) { + if ((G_gpg_vstate.UIF_flags) == 0) { + ui_menu_uifconfirm_display(0); + return 0; + } + G_gpg_vstate.UIF_flags = 0; + } + break; + // --- PSO:DEC --- + case PSO_DEC: + case PSO_ENC: + if (G_gpg_vstate.kslot->dec.UIF[0]) { + if ((G_gpg_vstate.UIF_flags) == 0) { + ui_menu_uifconfirm_display(0); + return 0; + } + G_gpg_vstate.UIF_flags = 0; + } + break; + } - pso = (G_gpg_vstate.io_p1 << 8) | G_gpg_vstate.io_p2; + // --- PSO:ENC --- + switch (G_gpg_vstate.io_p1p2) { + case PSO_CDS: + error = gpg_sign(&G_gpg_vstate.kslot->sig); + cnt = G_gpg_vstate.kslot->sig_count + 1; + nvm_write(&G_gpg_vstate.kslot->sig_count, &cnt, sizeof(unsigned int)); + break; - // UIF HANDLE - switch (pso) { - // --- PSO:CDS --- - case 0x9e9a: - if (G_gpg_vstate.kslot->sig.UIF[0]) { - if ((G_gpg_vstate.UIF_flags) == 0) { - ui_menu_uifconfirm_display(0); - return 0; - } - G_gpg_vstate.UIF_flags = 0; - } - break; - // --- PSO:DEC --- - case 0x8086: - case 0x8680: - if (G_gpg_vstate.kslot->dec.UIF[0]) { - if ((G_gpg_vstate.UIF_flags) == 0) { - ui_menu_uifconfirm_display(0); - return 0; - } - G_gpg_vstate.UIF_flags = 0; - } - break; - } + case PSO_ENC: + aes_key = &G_gpg_vstate.kslot->AES_dec; + if (!(aes_key->size != CX_AES_128_KEY_LEN)) { + return SW_CONDITIONS_NOT_SATISFIED; + } + msg_len = G_gpg_vstate.io_length - G_gpg_vstate.io_offset; + ksz = GPG_IO_BUFFER_LENGTH - 1; + CX_CHECK(cx_aes_no_throw(aes_key, + CX_ENCRYPT | CX_CHAIN_CBC | CX_LAST, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + msg_len, + G_gpg_vstate.work.io_buffer + 1, + &ksz)); + // send + gpg_io_discard(0); + G_gpg_vstate.work.io_buffer[0] = PAD_AES; + gpg_io_inserted(1 + ksz); + error = SW_OK; + break; - // --- PSO:ENC --- - switch (pso) { - // --- PSO:CDS --- - case 0x9e9a: { - unsigned int cnt; - int sw; - sw = gpg_sign(&G_gpg_vstate.kslot->sig); - cnt = G_gpg_vstate.kslot->sig_count + 1; - nvm_write(&G_gpg_vstate.kslot->sig_count, &cnt, sizeof(unsigned int)); - return sw; - } - // --- PSO:ENC --- - case 0x8680: { - unsigned int msg_len; - cx_aes_key_t *key; - unsigned int sz; - key = &G_gpg_vstate.kslot->AES_dec; - if (!(key->size != 16)) { - THROW(SW_CONDITIONS_NOT_SATISFIED + 5); - return SW_CONDITIONS_NOT_SATISFIED; - } - msg_len = G_gpg_vstate.io_length - G_gpg_vstate.io_offset; - sz = cx_aes(key, CX_ENCRYPT | CX_CHAIN_CBC | CX_LAST, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, msg_len, - G_gpg_vstate.work.io_buffer + 1, GPG_IO_BUFFER_LENGTH - 1); - // send - gpg_io_discard(0); - G_gpg_vstate.work.io_buffer[0] = 0x02; - gpg_io_inserted(1 + sz); - return SW_OK; - } + case PSO_DEC: + pad_byte = gpg_io_fetch_u8(); - // --- PSO:DEC --- - case 0x8086: { - unsigned int msg_len; - unsigned int pad_byte; - unsigned int sz; - pad_byte = gpg_io_fetch_u8(); + switch (pad_byte) { + case PAD_RSA: + if (G_gpg_vstate.mse_dec->attributes.value[0] != KEY_ID_RSA) { + error = SW_CONDITIONS_NOT_SATISFIED; + break; + } + ksz = U2BE(G_gpg_vstate.mse_dec->attributes.value, 1) >> 3; + switch (ksz) { + case 2048 / 8: + rsa_key = + (cx_rsa_private_key_t *) &G_gpg_vstate.mse_dec->priv_key.rsa2048; + break; + case 3072 / 8: + rsa_key = + (cx_rsa_private_key_t *) &G_gpg_vstate.mse_dec->priv_key.rsa3072; + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096 / 8: + rsa_key = + (cx_rsa_private_key_t *) &G_gpg_vstate.mse_dec->priv_key.rsa4096; + break; +#endif + } - switch (pad_byte) { - // --- PSO:DEC:RSA - case 0x00: { - cx_rsa_private_key_t *key; - if (G_gpg_vstate.mse_dec->attributes.value[0] != 0x01) { - THROW(SW_CONDITIONS_NOT_SATISFIED); - return SW_CONDITIONS_NOT_SATISFIED; - } - ksz = (G_gpg_vstate.mse_dec->attributes.value[1] << 8) | G_gpg_vstate.mse_dec->attributes.value[2]; - ksz = ksz >> 3; - key = NULL; - switch (ksz) { - case 1024 / 8: - key = (cx_rsa_private_key_t *)&G_gpg_vstate.mse_dec->priv_key.rsa1024; - break; - case 2048 / 8: - key = (cx_rsa_private_key_t *)&G_gpg_vstate.mse_dec->priv_key.rsa2048; - break; - case 3072 / 8: - key = (cx_rsa_private_key_t *)&G_gpg_vstate.mse_dec->priv_key.rsa3072; - break; - case 4096 / 8: - key = (cx_rsa_private_key_t *)&G_gpg_vstate.mse_dec->priv_key.rsa4096; - break; - } + if ((rsa_key == NULL) || (rsa_key->size != ksz)) { + error = SW_CONDITIONS_NOT_SATISFIED; + break; + } + msg_len = G_gpg_vstate.io_length - G_gpg_vstate.io_offset; + CX_CHECK(cx_rsa_decrypt_no_throw( + rsa_key, + CX_PAD_PKCS1_1o5, + CX_NONE, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + msg_len, + G_gpg_vstate.work.io_buffer, + &ksz)); + // send + gpg_io_discard(0); + gpg_io_inserted(ksz); + error = SW_OK; + break; - if ((key == NULL) || (key->size != ksz)) { - THROW(SW_CONDITIONS_NOT_SATISFIED); - return SW_CONDITIONS_NOT_SATISFIED; - } - msg_len = G_gpg_vstate.io_length - G_gpg_vstate.io_offset; - sz = cx_rsa_decrypt(key, CX_PAD_PKCS1_1o5, CX_NONE, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, msg_len, - G_gpg_vstate.work.io_buffer, ksz); - // send - gpg_io_discard(0); - gpg_io_inserted(sz); - return SW_OK; - } + case PAD_AES: + aes_key = &G_gpg_vstate.kslot->AES_dec; + if (!(aes_key->size != CX_AES_128_KEY_LEN)) { + error = SW_CONDITIONS_NOT_SATISFIED; + break; + } + msg_len = G_gpg_vstate.io_length - G_gpg_vstate.io_offset; + ksz = GPG_IO_BUFFER_LENGTH; + CX_CHECK(cx_aes_no_throw(aes_key, + CX_DECRYPT | CX_CHAIN_CBC | CX_LAST, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + msg_len, + G_gpg_vstate.work.io_buffer, + &ksz)); + // send + gpg_io_discard(0); + gpg_io_inserted(ksz); + error = SW_OK; + break; - // --- PSO:DEC:AES - case 0x02: { - cx_aes_key_t *key; - unsigned int sz; - key = &G_gpg_vstate.kslot->AES_dec; - if (!(key->size != 16)) { - THROW(SW_CONDITIONS_NOT_SATISFIED + 5); - return SW_CONDITIONS_NOT_SATISFIED; - } - msg_len = G_gpg_vstate.io_length - G_gpg_vstate.io_offset; - sz = cx_aes(key, CX_DECRYPT | CX_CHAIN_CBC | CX_LAST, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, - msg_len, G_gpg_vstate.work.io_buffer, GPG_IO_BUFFER_LENGTH); - // send - gpg_io_discard(0); - gpg_io_inserted(sz); - return SW_OK; - } + case PAD_ECDH: + if (G_gpg_vstate.mse_dec->attributes.value[0] != KEY_ID_ECDH) { + error = SW_CONDITIONS_NOT_SATISFIED; + break; + } + ecfp_key = &G_gpg_vstate.mse_dec->priv_key.ecfp; + gpg_io_fetch_l(&l); + gpg_io_fetch_tl(&t, &l); + if (t != 0x7f49) { + error = SW_WRONG_DATA; + break; + } + gpg_io_fetch_tl(&t, &l); + if (t != 0x86) { + error = SW_WRONG_DATA; + break; + } - // --- PSO:DEC:ECDH - case 0xA6: { - cx_ecfp_private_key_t *key; - unsigned int sz; - unsigned int curve; - if (G_gpg_vstate.mse_dec->attributes.value[0] != 0x12) { - THROW(SW_CONDITIONS_NOT_SATISFIED); - return SW_CONDITIONS_NOT_SATISFIED; - } - key = &G_gpg_vstate.mse_dec->priv_key.ecfp; - gpg_io_fetch_l(&l); - gpg_io_fetch_tl(&t, &l); - if (t != 0x7f49) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } - gpg_io_fetch_tl(&t, &l); - if (t != 0x86) { - THROW(SW_WRONG_DATA); - return SW_WRONG_DATA; - } + curve = gpg_oid2curve(G_gpg_vstate.mse_dec->attributes.value + 1, + G_gpg_vstate.mse_dec->attributes.length - 1); + if (ecfp_key->curve != curve) { + error = SW_CONDITIONS_NOT_SATISFIED; + break; + } + if (curve == CX_CURVE_Curve25519) { + for (cnt = 0; cnt <= 31; cnt++) { + G_gpg_vstate.work.io_buffer[512 + cnt] = + (G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset)[31 - cnt]; + } + G_gpg_vstate.work.io_buffer[511] = 0x02; + CX_CHECK(cx_ecdh_no_throw(ecfp_key, + CX_ECDH_X, + G_gpg_vstate.work.io_buffer + 511, + 65, + G_gpg_vstate.work.io_buffer + 256, + 160)); + CX_CHECK(cx_ecdomain_parameters_length(ecfp_key->curve, &ksz)); - curve = gpg_oid2curve(G_gpg_vstate.mse_dec->attributes.value + 1, G_gpg_vstate.mse_dec->attributes.length - 1); - if (key->curve != curve) { - THROW(SW_CONDITIONS_NOT_SATISFIED); - return SW_CONDITIONS_NOT_SATISFIED; - } - if (curve == CX_CURVE_Curve25519) { - unsigned int i; + for (cnt = 0; cnt <= 31; cnt++) { + G_gpg_vstate.work.io_buffer[128 + cnt] = + G_gpg_vstate.work.io_buffer[287 - cnt]; + } + ksz = 32; + } else { + CX_CHECK( + cx_ecdh_no_throw(ecfp_key, + CX_ECDH_X, + G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, + 65, + G_gpg_vstate.work.io_buffer + 128, + 160)); + CX_CHECK(cx_ecdomain_parameters_length(ecfp_key->curve, &ksz)); + } + // send + gpg_io_discard(0); + gpg_io_insert(G_gpg_vstate.work.io_buffer + 128, ksz); + error = SW_OK; + break; - for (i = 0; i <= 31; i++) { - G_gpg_vstate.work.io_buffer[512 + i] = (G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset)[31 - i]; - } - G_gpg_vstate.work.io_buffer[511] = 0x02; - sz = cx_ecdh(key, CX_ECDH_X, G_gpg_vstate.work.io_buffer + 511, 65, G_gpg_vstate.work.io_buffer + 256, 160); - for (i = 0; i <= 31; i++) { - G_gpg_vstate.work.io_buffer[128 + i] = G_gpg_vstate.work.io_buffer[287 - i]; - } - sz = 32; - } else { - sz = cx_ecdh(key, CX_ECDH_X, G_gpg_vstate.work.io_buffer + G_gpg_vstate.io_offset, 65, - G_gpg_vstate.work.io_buffer + 128, 160); - } - // send - gpg_io_discard(0); - gpg_io_insert(G_gpg_vstate.work.io_buffer + 128, sz); - return SW_OK; - } + // --- PSO:DEC:xx NOT SUPPORTED + default: + error = SW_REFERENCED_DATA_NOT_FOUND; + break; + } + break; - // --- PSO:DEC:xx NOT SUPPORTDED - default: - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; + //--- PSO:yy NOT SUPPORTED --- + default: + error = SW_REFERENCED_DATA_NOT_FOUND; + break; } - } - - //--- PSO:yy NOT SUPPPORTED --- - default: - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; - } - THROW(SW_REFERENCED_DATA_NOT_FOUND); - return SW_REFERENCED_DATA_NOT_FOUND; +end: + return error; } +/** + * APDU handler to Internal Authentication + * + * @return Status Word + * + */ int gpg_apdu_internal_authenticate() { - // --- PSO:AUTH --- - if (G_gpg_vstate.kslot->aut.UIF[0]) { - if ((G_gpg_vstate.UIF_flags) == 0) { - ui_menu_uifconfirm_display(0); - return 0; + // --- PSO:AUTH --- + if (G_gpg_vstate.kslot->aut.UIF[0]) { + if ((G_gpg_vstate.UIF_flags) == 0) { + ui_menu_uifconfirm_display(0); + return 0; + } + G_gpg_vstate.UIF_flags = 0; } - G_gpg_vstate.UIF_flags = 0; - } - if (G_gpg_vstate.mse_aut->attributes.value[0] == 1) { - if (G_gpg_vstate.io_length > - ((G_gpg_vstate.mse_aut->attributes.value[1] << 8) | G_gpg_vstate.mse_aut->attributes.value[2]) * 40 / 100) { - THROW(SW_WRONG_LENGTH); - return SW_WRONG_LENGTH; + if (G_gpg_vstate.mse_aut->attributes.value[0] == KEY_ID_RSA) { + if (G_gpg_vstate.io_length > U2BE(G_gpg_vstate.mse_aut->attributes.value, 1) * 40 / 100) { + return SW_WRONG_LENGTH; + } } - } - return gpg_sign(G_gpg_vstate.mse_aut); + return gpg_sign(G_gpg_vstate.mse_aut); } diff --git a/src/gpg_ram.c b/src/gpg_ram.c deleted file mode 100644 index 6a7b16b..0000000 --- a/src/gpg_ram.c +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" -#include "gpg_vars.h" -#include "os_io_seproxyhal.h" - -#if defined(TARGET_NANOX) || defined(TARGET_NANOS2) -#include "ux.h" -ux_state_t G_ux; -bolos_ux_params_t G_ux_params; -#else -ux_state_t ux; -#endif - -#ifndef GPG_DEBUG_MAIN -unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; -#else -extern unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; -int apdu_n; -#endif - -gpg_v_state_t G_gpg_vstate; - -#ifdef HAVE_RSA -union cx_u G_cx; -#endif // HAVE_RSA \ No newline at end of file diff --git a/src/gpg_select.c b/src/gpg_select.c index be9ce51..80d3ec2 100644 --- a/src/gpg_select.c +++ b/src/gpg_select.c @@ -1,57 +1,71 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" + const unsigned char C_MF[] = {0x3F, 0x00}; +const unsigned char C_ATR[] = {0x2F, 0x02}; +/** + * APDU handler to Select the card + * + * @return Status Word + * + */ int gpg_apdu_select() { - int sw; + int sw = SW_UNKNOWN; - // MF - if ((G_gpg_vstate.io_length == 2) && (os_memcmp(G_gpg_vstate.work.io_buffer, C_MF, G_gpg_vstate.io_length) == 0)) { - gpg_io_discard(0); - sw = SW_OK; - } - // AID APP - else if ((G_gpg_vstate.io_length == 6) && - (os_memcmp(G_gpg_vstate.work.io_buffer, N_gpg_pstate->AID, G_gpg_vstate.io_length) == 0)) { - G_gpg_vstate.DO_current = 0; - G_gpg_vstate.DO_reccord = 0; - G_gpg_vstate.DO_offset = 0; - if (G_gpg_vstate.selected == 0) { - G_gpg_vstate.verified_pin[0] = 0; - G_gpg_vstate.verified_pin[1] = 0; - G_gpg_vstate.verified_pin[2] = 0; - G_gpg_vstate.verified_pin[3] = 0; - G_gpg_vstate.verified_pin[4] = 0; + // MF + if ((G_gpg_vstate.io_length == sizeof(C_MF)) && + (memcmp(G_gpg_vstate.work.io_buffer, C_MF, G_gpg_vstate.io_length) == 0)) { + gpg_io_discard(0); + sw = SW_OK; + } + // EF.ATR + else if ((G_gpg_vstate.io_length == sizeof(C_ATR)) && + (memcmp(G_gpg_vstate.work.io_buffer, C_ATR, G_gpg_vstate.io_length) == 0)) { + gpg_io_discard(0); + sw = SW_OK; } + // AID APP + else if ((G_gpg_vstate.io_length == 6) && (memcmp(G_gpg_vstate.work.io_buffer, + (const void *) N_gpg_pstate->AID, + G_gpg_vstate.io_length) == 0)) { + G_gpg_vstate.DO_current = 0; + G_gpg_vstate.DO_reccord = 0; + G_gpg_vstate.DO_offset = 0; + if (G_gpg_vstate.selected == 0) { + G_gpg_vstate.verified_pin[0] = 0; + G_gpg_vstate.verified_pin[1] = 0; + G_gpg_vstate.verified_pin[2] = 0; + G_gpg_vstate.verified_pin[3] = 0; + G_gpg_vstate.verified_pin[4] = 0; + } - gpg_io_discard(0); - if (N_gpg_pstate->histo[7] != 0x07) { - THROW(SW_STATE_TERMINATED); + gpg_io_discard(0); + if (N_gpg_pstate->histo[HISTO_OFFSET_STATE] != STATE_ACTIVATE) { + sw = SW_STATE_TERMINATED; + } else { + sw = SW_OK; + } + } + // NOT FOUND + else { + sw = SW_FILE_NOT_FOUND; } - sw = SW_OK; - } - // NOT FOUND - else { - THROW(SW_FILE_NOT_FOUND); - return SW_FILE_NOT_FOUND; - } - return sw; + return sw; } diff --git a/src/gpg_types.h b/src/gpg_types.h index ec89bf2..1a693d8 100644 --- a/src/gpg_types.h +++ b/src/gpg_types.h @@ -1,23 +1,29 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifndef GPG_TYPES_H #define GPG_TYPES_H -#include "os_io_seproxyhal.h" -#include "ux.h" +#include "lcx_sha3.h" +#include "usbd_ccid_if.h" +#include "bolos_target.h" +#ifdef HAVE_NBGL +#include "nbgl_layout.h" +#endif /* cannot send more that F0 bytes in CCID, why? do not know for now * So set up length to F0 minus 2 bytes for SW @@ -26,155 +32,160 @@ /* big private DO */ #define GPG_EXT_PRIVATE_DO_LENGTH 512 -/* will be fixed..1024 is not enougth */ +/* will be fixed..1024 is not enough */ #define GPG_EXT_CARD_HOLDER_CERT_LENTH 2560 /* random choice */ #define GPG_EXT_CHALLENGE_LENTH 254 -/* accpet long PW, but less than one sha256 block */ -#define GPG_MAX_PW_LENGTH 12 - -#if GPG_MULTISLOT -#define GPG_KEYS_SLOTS 3 -#else +/* accept long PW, but less than one sha256 block */ +#define GPG_MAX_PW_LENGTH 12 +#define GPG_MIN_PW1_LENGTH 6 +#define GPG_MIN_PW3_LENGTH 8 + +#define MAGIC_LENGTH 8 +#define AID_LENGTH 16 +#define HISTO_LENGTH 15 +#define HISTO_OFFSET_STATE 12 // 3rd byte from last (buffer size is 15) +#ifdef TARGET_NANOS #define GPG_KEYS_SLOTS 1 +#else +#define GPG_KEYS_SLOTS 3 #endif #define GPG_KEY_ATTRIBUTES_LENGTH 12 #define GPG_RSA_DEFAULT_PUB 0x00010001U +#ifndef CX_AES_128_KEY_LEN +#define CX_AES_128_KEY_LEN CX_AES_BLOCK_SIZE +#endif + +/* --- Keys IDs --- */ +// https://www.rfc-editor.org/rfc/rfc4880#section-9.1 +#define KEY_ID_RSA 1 // RSA (Encrypt or Sign) +#define KEY_ID_ECDH 18 // Elliptic Curve Diffie-Hellman +#define KEY_ID_ECDSA 19 // Elliptic Curve Digital Signature Algorithm +#define KEY_ID_EDDSA 22 // Edwards-curve Digital Signature Algorithm + struct gpg_pin_s { - unsigned int ref; - // initial pin length, 0 means not set - unsigned int length; - unsigned int counter; - // only store sha256 of PIN/RC - unsigned char value[32]; + unsigned int ref; + // initial pin length, 0 means not set + unsigned int length; + unsigned int counter; + // only store sha256 of PIN/RC + unsigned char value[32]; }; typedef struct gpg_pin_s gpg_pin_t; -#define LV(name, maxlen) \ - struct { \ - unsigned int length; \ - unsigned char value[maxlen]; \ - } name - -typedef struct gpg_lv_s { - unsigned int length; - unsigned char value[]; -} gpg_lv_t; +#define LV(name, maxlen) \ + struct { \ + unsigned int length; \ + unsigned char value[maxlen]; \ + } name typedef struct gpg_key_s { - /* C1 C2 C3 */ - LV(attributes, GPG_KEY_ATTRIBUTES_LENGTH); - /* key value */ - /* WARN: changing the cx__t structures breaks backup/restore. Adapt backup/restore code - * to ensure backward compatibility. - */ - union { - cx_rsa_private_key_t rsa; - cx_rsa_1024_private_key_t rsa1024; - cx_rsa_2048_private_key_t rsa2048; - cx_rsa_3072_private_key_t rsa3072; - cx_rsa_4096_private_key_t rsa4096; - - cx_ecfp_private_key_t ecfp; - cx_ecfp_256_private_key_t ecfp256; - cx_ecfp_384_private_key_t ecfp384; - cx_ecfp_512_private_key_t ecfp512; - cx_ecfp_640_private_key_t ecfp640; - } priv_key; - union { - unsigned char rsa[4]; - cx_ecfp_public_key_t ecfp; - cx_ecfp_256_public_key_t ecfp256; - cx_ecfp_384_public_key_t ecfp384; - cx_ecfp_512_public_key_t ecfp512; - cx_ecfp_640_public_key_t ecfp640; - } pub_key; - /* C7 C8 C9 , C5 = C7|C8|C9*/ - unsigned char fingerprints[20]; - /* 7F21 */ - LV(CA, GPG_EXT_CARD_HOLDER_CERT_LENTH); - /* C7 C8 C9, C6 = C7|C8|C9*/ - unsigned char CA_fingerprints[20]; - /* CE CF D0, CD = CE|CF|D0 */ - unsigned char date[4]; - /* D6/D7/D8- */ - unsigned char UIF[2]; + /* C1 C2 C3 */ + LV(attributes, GPG_KEY_ATTRIBUTES_LENGTH); + /* key value */ + union { + cx_rsa_private_key_t rsa; + cx_rsa_2048_private_key_t rsa2048; + cx_rsa_3072_private_key_t rsa3072; +#ifdef WITH_SUPPORT_RSA4096 + cx_rsa_4096_private_key_t rsa4096; +#endif + cx_ecfp_private_key_t ecfp; + cx_ecfp_256_private_key_t ecfp256; + cx_ecfp_384_private_key_t ecfp384; + cx_ecfp_512_private_key_t ecfp512; + cx_ecfp_640_private_key_t ecfp640; + } priv_key; + union { + unsigned char rsa[4]; + cx_ecfp_public_key_t ecfp; + cx_ecfp_256_public_key_t ecfp256; + cx_ecfp_384_public_key_t ecfp384; + cx_ecfp_512_public_key_t ecfp512; + cx_ecfp_640_public_key_t ecfp640; + } pub_key; + /* C7 C8 C9 , C5 = C7|C8|C9*/ + unsigned char fingerprints[20]; + /* 7F21 */ + LV(CA, GPG_EXT_CARD_HOLDER_CERT_LENTH); + /* C7 C8 C9, C6 = C7|C8|C9*/ + unsigned char CA_fingerprints[20]; + /* CE CF D0, CD = CE|CF|D0 */ + unsigned char date[4]; + /* D6/D7/D8- */ + unsigned char UIF[2]; } gpg_key_t; typedef struct gpg_key_slot_s { - unsigned char serial[4]; - /* */ - gpg_key_t sig; - gpg_key_t aut; - gpg_key_t dec; - /* -- Security support template -- */ - /* 93 */ - unsigned int sig_count; - /* D5 */ - cx_aes_key_t AES_dec; + unsigned char serial[4]; + /* */ + gpg_key_t sig; + gpg_key_t aut; + gpg_key_t dec; + /* -- Security support template -- */ + /* 93 */ + unsigned int sig_count; + /* D5 */ + cx_aes_key_t AES_dec; + /* 5F50 */ + LV(url, GPG_EXT_PRIVATE_DO_LENGTH); } gpg_key_slot_t; struct gpg_nv_state_s { - /* magic */ - unsigned char magic[8]; - - /* pin mode */ - unsigned char config_pin[1]; - - /* 01F1 (01F2 is volatile)*/ - unsigned char config_slot[3]; - /* RSA exponent */ - unsigned char default_RSA_exponent[4]; - - /* 0101 0102 0103 0104 */ - LV(private_DO1, GPG_EXT_PRIVATE_DO_LENGTH); - LV(private_DO2, GPG_EXT_PRIVATE_DO_LENGTH); - LV(private_DO3, GPG_EXT_PRIVATE_DO_LENGTH); - LV(private_DO4, GPG_EXT_PRIVATE_DO_LENGTH); - - /* 5E */ - LV(login, GPG_EXT_PRIVATE_DO_LENGTH); - /* 5F50 */ - LV(url, GPG_EXT_PRIVATE_DO_LENGTH); - - /* -- Cardholder Related Data -- */ - /* 5B */ - LV(name, 39); - /* 5F2D */ - LV(lang, 8); - /* 5F35 */ - unsigned char sex[1]; - - /* -- Application Related Data -- */ - /* 4F */ - unsigned char AID[16]; - /* 5F52 */ - unsigned char histo[15]; - /* 7f66 */ - // unsigned char ext_length_info[8]; - /* C0 */ - // unsigned char ext_capabilities[10]; - - /* C4 */ - unsigned char PW_status[4]; - - /* PINs */ - gpg_pin_t PW1; - gpg_pin_t PW3; - gpg_pin_t RC; - - /* gpg keys */ - gpg_key_slot_t keys[GPG_KEYS_SLOTS]; - - /* --- SM --- */ - /* D1 */ - cx_aes_key_t SM_enc; - /* D2 */ - cx_aes_key_t SM_mac; + /* magic */ + unsigned char magic[MAGIC_LENGTH]; + + /* pin mode */ + unsigned char config_pin[1]; + + /* 01F1 (01F2 is volatile)*/ + unsigned char config_slot[3]; + /* RSA exponent */ + unsigned char default_RSA_exponent[4]; + + /* 0101 0102 0103 0104 */ + LV(private_DO1, GPG_EXT_PRIVATE_DO_LENGTH); + LV(private_DO2, GPG_EXT_PRIVATE_DO_LENGTH); + LV(private_DO3, GPG_EXT_PRIVATE_DO_LENGTH); + LV(private_DO4, GPG_EXT_PRIVATE_DO_LENGTH); + + /* 5E */ + LV(login, GPG_EXT_PRIVATE_DO_LENGTH); + + /* -- Cardholder Related Data -- */ + /* 5B */ + LV(name, 39); + /* 5F2D */ + LV(lang, 8); + /* 5F35 */ + unsigned char salutation[1]; + + /* -- Application Related Data -- */ + /* 4F */ + unsigned char AID[AID_LENGTH]; + /* 5F52 */ + unsigned char histo[HISTO_LENGTH]; + + /* C4 */ + unsigned char PW_status[4]; + + /* PINs */ + gpg_pin_t PW1; + gpg_pin_t PW3; + gpg_pin_t RC; + + /* gpg keys */ + gpg_key_slot_t keys[GPG_KEYS_SLOTS]; + + /* --- SM --- */ + /* D1 */ + cx_aes_key_t SM_enc; + /* D2 */ + cx_aes_key_t SM_mac; }; typedef struct gpg_nv_state_s gpg_nv_state_t; @@ -182,215 +193,217 @@ typedef struct gpg_nv_state_s gpg_nv_state_t; #define GPG_IO_BUFFER_LENGTH (1512) struct gpg_v_state_s { - /* app state */ - unsigned char selected; - unsigned char slot; /* DO 01F2 */ - gpg_key_slot_t *kslot; - gpg_key_t * mse_aut; - gpg_key_t * mse_dec; - unsigned char seed_mode; - - unsigned char UIF_flags; - - /* io state*/ - - unsigned char io_cla; - unsigned char io_ins; - unsigned char io_p1; - unsigned char io_p2; - unsigned char io_lc; - unsigned char io_le; - unsigned short io_length; - unsigned short io_offset; - unsigned short io_mark; - union { - unsigned char io_buffer[GPG_IO_BUFFER_LENGTH]; - struct { - union { - cx_rsa_public_key_t public; - cx_rsa_1024_public_key_t public1024; - cx_rsa_2048_public_key_t public2048; - cx_rsa_3072_public_key_t public3072; - cx_rsa_4096_public_key_t public4096; - }; - union { - cx_rsa_private_key_t private; - cx_rsa_1024_private_key_t private1024; - cx_rsa_2048_private_key_t private2048; - cx_rsa_3072_private_key_t private3072; - cx_rsa_4096_private_key_t private4096; - }; - } rsa; - - struct { - union { - cx_ecfp_public_key_t public; - cx_ecfp_256_public_key_t public256; - cx_ecfp_384_public_key_t public384; - cx_ecfp_512_public_key_t public512; - cx_ecfp_640_public_key_t public640; - }; - union { - cx_ecfp_private_key_t private; - cx_ecfp_256_private_key_t private256; - cx_ecfp_384_private_key_t private384; - cx_ecfp_512_private_key_t private512; - cx_ecfp_640_private_key_t private640; - }; - } ecfp; - - struct { - unsigned char md_buffer[GPG_IO_BUFFER_LENGTH - (32 + MAX(sizeof(cx_sha3_t), sizeof(cx_sha256_t)))]; - unsigned char H[32]; - union { - cx_sha3_t sha3; - cx_sha256_t sha256; - }; - } md; - } work; - - /* data state */ - unsigned short DO_current; - unsigned short DO_reccord; - unsigned short DO_offset; - - /* PINs state */ - unsigned char verified_pin[5]; - unsigned char pinmode; - - /* ux menus */ - char menu[112]; - unsigned char ux_pinentry[12]; - unsigned int ux_key; - unsigned int ux_type; - -#ifdef UI_NANO_S - ux_menu_entry_t ui_dogsays[2]; + /* app state */ + unsigned char selected; + unsigned char slot; /* DO 01F2 */ + gpg_key_slot_t *kslot; + gpg_key_t *mse_aut; + gpg_key_t *mse_dec; + unsigned char seed_mode; + + unsigned char UIF_flags; + + /* io state*/ + + unsigned char io_cla; + unsigned char io_ins; + unsigned char io_p1; + unsigned char io_p2; + unsigned char io_lc; + unsigned char io_le; + unsigned short io_length; + unsigned short io_offset; + unsigned short io_mark; + unsigned short io_p1p2; + union { + unsigned char io_buffer[GPG_IO_BUFFER_LENGTH]; + struct { + union { + cx_rsa_public_key_t public; + cx_rsa_2048_public_key_t public2048; + cx_rsa_3072_public_key_t public3072; +#ifdef WITH_SUPPORT_RSA4096 + cx_rsa_4096_public_key_t public4096; +#endif + }; + union { + cx_rsa_private_key_t private; + cx_rsa_2048_private_key_t private2048; + cx_rsa_3072_private_key_t private3072; +#ifdef WITH_SUPPORT_RSA4096 + cx_rsa_4096_private_key_t private4096; +#endif + }; + } rsa; + + struct { + union { + cx_ecfp_public_key_t public; + cx_ecfp_256_public_key_t public256; + cx_ecfp_384_public_key_t public384; + cx_ecfp_512_public_key_t public512; + cx_ecfp_640_public_key_t public640; + }; + union { + cx_ecfp_private_key_t private; + cx_ecfp_256_private_key_t private256; + cx_ecfp_384_private_key_t private384; + cx_ecfp_512_private_key_t private512; + cx_ecfp_640_private_key_t private640; + }; + } ecfp; + + struct { + unsigned char md_buffer[GPG_IO_BUFFER_LENGTH - + (32 + MAX(sizeof(cx_sha3_t), sizeof(cx_sha256_t)))]; + unsigned char H[32]; + union { + cx_sha3_t sha3; + cx_sha256_t sha256; + }; + } md; + } work; + + /* data state */ + unsigned short DO_current; + unsigned short DO_reccord; + unsigned short DO_offset; + + /* PINs state */ + unsigned char verified_pin[5]; + unsigned char pinmode; + unsigned char pinmode_req; + + /* ux menus */ + char menu[112]; + unsigned char ux_pinentry[GPG_MAX_PW_LENGTH]; + unsigned char ux_pinLen; + unsigned int ux_key; + unsigned int ux_type; + +#ifdef TARGET_NANOS + ux_menu_entry_t ui_dogsays[2]; #endif -#ifdef UI_NANO_X - char ux_buff1[32]; - char ux_buff2[32]; - char ux_buff3[32]; - char ux_buff4[32]; - char ux_buff5[32]; +#if defined(TARGET_NANOX) || defined(TARGET_NANOS2) + char ux_buff1[32]; + char ux_buff2[32]; + char ux_buff3[32]; #endif - #ifdef GPG_LOG - unsigned char log_buffer[32]; - #endif -#ifdef GPG_DEBUG - unsigned char print; +#ifdef HAVE_NBGL + char line[112]; + unsigned int ux_step; + nbgl_layout_t *layoutCtx; +#endif + +#ifdef GPG_LOG + unsigned char log_buffer[256]; #endif }; typedef struct gpg_v_state_s gpg_v_state_t; -/* --- Errors --- */ - -#define ERROR(x) ((x) << 16) +/* --- Identifiers --- */ -#define ERROR_IO_OFFSET ERROR(1) -#define ERROR_IO_FULL ERROR(2) - -/* --- IDentifiers --- */ - -#define ID_AUTH 1 -#define ID_DEC 2 -#define ID_SIG 3 - -#define STATE_ACTIVATE 0x07 +#define STATE_ACTIVATE 0x05 #define STATE_TERMINATE 0x03 -#define IO_OFFSET_END (unsigned int)-1 -#define IO_OFFSET_MARK (unsigned int)-2 +#define IO_OFFSET_END (unsigned int) -1 +#define IO_OFFSET_MARK (unsigned int) -2 #define PIN_ID_PW1 0x81 #define PIN_ID_PW2 0x82 #define PIN_ID_PW3 0x83 -#define PIN_ID_RC 0x84 +#define PIN_ID_RC 0x84 -#define PIN_MODE_HOST 1 -#define PIN_MODE_SCREEN 2 -#define PIN_MODE_CONFIRM 3 -#define PIN_MODE_TRUST 4 +// PIN_MODE_HOST not supported by Ledger App +#define PIN_MODE_SCREEN 0 +#define PIN_MODE_CONFIRM 1 +#define PIN_MODE_TRUST 2 + +/* --- CLA --- */ +#define CLA_APP_DEF 0x00 +#define CLA_APP_SM 0x0C +#define CLA_APP_CHAIN 0x10 +#define CLA_APP_CHAIN_SM 0x1C +#define CLA_APP_APDU_PIN PIN_OPR_APDU_CLA /* --- INS --- */ -#define INS_EXIT 0x02 #ifdef GPG_LOG #define INS_GET_LOG 0x04 #endif - -#define INS_SELECT 0xa4 -#define INS_TERMINATE_DF 0xe6 -#define INS_ACTIVATE_FILE 0x44 - -#define INS_SELECT_DATA 0xa5 -#define INS_GET_DATA 0xca -#define INS_GET_NEXT_DATA 0xcc -#define INS_PUT_DATA 0xda -#define INS_PUT_DATA_ODD 0xdb - -#define INS_VERIFY 0x20 -#define INS_MSE 0x22 +#define INS_EXIT 0x02 +#define INS_VERIFY 0x20 +#define INS_MSE 0x22 #define INS_CHANGE_REFERENCE_DATA 0x24 -#define INS_RESET_RETRY_COUNTER 0x2c - -#define INS_GEN_ASYM_KEYPAIR 0x47 -#define INS_PSO 0x2a -//#define INS_COMPUTEDIGSIG 0x2a -//#define INS_DECIPHER 0x2a +#define INS_PSO 0x2a +#define INS_RESET_RETRY_COUNTER 0x2c +#define INS_ACTIVATE_FILE 0x44 +#define INS_GEN_ASYM_KEYPAIR 0x47 +#define INS_GET_CHALLENGE 0x84 #define INS_INTERNAL_AUTHENTICATE 0x88 - -#define INS_GET_CHALLENGE 0x84 -#define INS_GET_RESPONSE 0xc0 - -/* --- IO constants --- */ -#define OFFSET_CLA 0 -#define OFFSET_INS 1 -#define OFFSET_P1 2 -#define OFFSET_P2 3 -#define OFFSET_P3 4 -#define OFFSET_CDATA 5 -#define OFFSET_EXT_CDATA 7 - -#define SW_OK 0x9000 -#define SW_ALGORITHM_UNSUPPORTED 0x9484 - -#define SW_BYTES_REMAINING_00 0x6100 - -#define SW_WARNING_STATE_UNCHANGED 0x6200 -#define SW_STATE_TERMINATED 0x6285 - -#define SW_MORE_DATA_AVAILABLE 0x6310 - -#define SW_WRONG_LENGTH 0x6700 - -#define SW_LOGICAL_CHANNEL_NOT_SUPPORTED 0x6881 -#define SW_SECURE_MESSAGING_NOT_SUPPORTED 0x6882 -#define SW_LAST_COMMAND_EXPECTED 0x6883 -#define SW_COMMAND_CHAINING_NOT_SUPPORTED 0x6884 - +#define INS_SELECT 0xa4 +#define INS_SELECT_DATA 0xa5 +#define INS_GET_RESPONSE 0xc0 +#define INS_GET_DATA 0xca +#define INS_GET_NEXT_DATA 0xcc +#define INS_PUT_DATA 0xda +#define INS_PUT_DATA_ODD 0xdb +#define INS_TERMINATE_DF 0xe6 + +/* --- Error constants --- */ +// #define SW_LOGICAL_CHANNEL_NOT_SUPPORTED 0x6881 +// #define SW_SECURE_MESSAGING_NOT_SUPPORTED 0x6882 +// #define SW_COMMAND_CHAINING_NOT_SUPPORTED 0x6884 +// #define SW_SM_DATA_MISSING 0x6987 +// #define SW_SM_DATA_INCORRECT 0x6988 +#define SW_STATE_TERMINATED 0x6285 +#define SW_PWD_NOT_CHECKED 0x63c0 +#define SW_MEMORY_FAILURE 0x6581 +#define SW_SECURITY_UIF_ISSUE 0x6600 +#define SW_WRONG_LENGTH 0x6700 +#define SW_LAST_COMMAND_CHAIN_EXPECTED 0x6883 #define SW_SECURITY_STATUS_NOT_SATISFIED 0x6982 -#define SW_FILE_INVALID 0x6983 -#define SW_PIN_BLOCKED 0x6983 -#define SW_DATA_INVALID 0x6984 -#define SW_CONDITIONS_NOT_SATISFIED 0x6985 -#define SW_COMMAND_NOT_ALLOWED 0x6986 -#define SW_APPLET_SELECT_FAILED 0x6999 - -#define SW_WRONG_DATA 0x6a80 -#define SW_FUNC_NOT_SUPPORTED 0x6a81 -#define SW_FILE_NOT_FOUND 0x6a82 -#define SW_RECORD_NOT_FOUND 0x6a83 -#define SW_FILE_FULL 0x6a84 -#define SW_INCORRECT_P1P2 0x6a86 -#define SW_REFERENCED_DATA_NOT_FOUND 0x6a88 - -#define SW_WRONG_P1P2 0x6b00 -#define SW_CORRECT_LENGTH_00 0x6c00 -#define SW_INS_NOT_SUPPORTED 0x6d00 -#define SW_CLA_NOT_SUPPORTED 0x6e00 - -#define SW_UNKNOWN 0x6f00 +#define SW_PIN_BLOCKED 0x6983 +#define SW_CONDITIONS_NOT_SATISFIED 0x6985 +#define SW_WRONG_DATA 0x6a80 +#define SW_FILE_NOT_FOUND 0x6a82 +#define SW_REFERENCED_DATA_NOT_FOUND 0x6a88 +#define SW_WRONG_P1P2 0x6b00 +#define SW_INS_NOT_SUPPORTED 0x6d00 +#define SW_CLA_NOT_SUPPORTED 0x6e00 +#define SW_UNKNOWN 0x6f00 +#define SW_CORRECT_BYTES_AVAILABLE 0x6100 +#define SW_OK 0x9000 + +/* --- P1/P2 constants --- */ +#define GEN_ASYM_KEY 0x8000 +#define SEEDED_MODE 0x01 +#define GEN_ASYM_KEY_SEED (GEN_ASYM_KEY | SEEDED_MODE) +#define READ_ASYM_KEY 0x8100 +#define PSO_CDS 0x9e9a +#define PSO_DEC 0x8086 +#define PSO_ENC 0x8680 +#define MSE_SET 0x41 +#define GET_RESPONSE 0x00 +#define PIN_NOT_VERIFIED 0xFF +#define PIN_VERIFY 0x00 +#define SELECT_MAX_INSTANCE 0x02 +#define SELECT_SKIP 0x04 +#define RESET_RETRY_WITH_PW3 0x02 +#define RESET_RETRY_WITH_CODE 0x00 +#define PRIME_MODE 0x02 +#define CHALLENGE_NOMINAL 0x00 + +/* --- Keys constants --- */ +#define KEY_SIG 0xb6 +#define KEY_DEC 0xb8 +#define KEY_AUT 0xa4 +#define KEY_NB 3 + +/* --- Padding indicators --- */ +#define PAD_RSA 0x00 +#define PAD_AES 0x02 +#define PAD_ECDH 0xa6 #endif diff --git a/src/gpg_ux.c b/src/gpg_ux.c new file mode 100644 index 0000000..eca47c5 --- /dev/null +++ b/src/gpg_ux.c @@ -0,0 +1,53 @@ + +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +#include "gpg_vars.h" +#include "gpg_ux.h" +#include "usbd_ccid_if.h" + +/** + * Reset CCID + * + */ +void ui_CCID_reset(void) { + io_usb_ccid_set_card_inserted(0); + io_usb_ccid_set_card_inserted(1); +} + +/** + * Exit app + * + */ +void app_quit(void) { + // exit app here + os_sched_exit(0); +} + +/** + * Reset app + * + */ +void app_reset(void) { + unsigned char magic[MAGIC_LENGTH]; + + explicit_bzero(magic, MAGIC_LENGTH); + nvm_write((void*) (N_gpg_pstate->magic), magic, MAGIC_LENGTH); + gpg_init(); + ui_CCID_reset(); + ui_init(); +} diff --git a/src/gpg_ux.h b/src/gpg_ux.h new file mode 100644 index 0000000..19effc8 --- /dev/null +++ b/src/gpg_ux.h @@ -0,0 +1,44 @@ + +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +#ifndef GPG_UX_H +#define GPG_UX_H + +#define STR(x) #x +#define XSTR(x) STR(x) + +#define LABEL_SIG "Signature" +#define LABEL_AUT "Authentication" +#define LABEL_DEC "Decryption" + +#define LABEL_RSA2048 "RSA 2048" +#define LABEL_RSA3072 "RSA 3072" +#define LABEL_RSA4096 "RSA 4096" +#define LABEL_SECP256K1 "SECP 256K1" +#define LABEL_SECP256R1 "SECP 256R1" +#define LABEL_Ed25519 "Ed25519" + +void ui_CCID_reset(void); +void app_quit(void); +void app_reset(void); +void ui_init(void); +void ui_menu_pinconfirm_display(unsigned int value); +void ui_menu_pinentry_display(unsigned int value); +void ui_menu_uifconfirm_display(unsigned int value); + +#endif // GPG_UX_H diff --git a/src/gpg_ux_blue.c b/src/gpg_ux_blue.c deleted file mode 100644 index 56e741d..0000000 --- a/src/gpg_ux_blue.c +++ /dev/null @@ -1,151 +0,0 @@ - -/* Copyright 2017 Cedric Mesnil , Ledger SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" -#include "gpg_vars.h" - -#include "os_io_seproxyhal.h" -#include "string.h" -#include "glyphs.h" - -/* ----------------------------------------------------------------------- */ -/* --- Blue UI layout --- */ -/* ----------------------------------------------------------------------- */ -/* screeen size: - blue; 320x480 - nanoS: 128x32 -*/ -#if 0 -static const bagl_element_t const ui_idle_blue[] = { - {{BAGL_RECTANGLE, 0x00, 0, 60, 320, 420, 0, 0, - BAGL_FILL, 0xf9f9f9, 0xf9f9f9, - 0, 0}, - NULL, - 0, - 0, - 0, - NULL, - NULL, - NULL}, - - {{BAGL_RECTANGLE, 0x00, 0, 0, 320, 60, 0, 0, - BAGL_FILL, 0x1d2028, 0x1d2028, - 0, 0}, - NULL, - 0, - 0, - 0, - NULL, - NULL, - NULL}, - - {{BAGL_LABEL, 0x00, 20, 0, 320, 60, 0, 0, - BAGL_FILL, 0xFFFFFF, 0x1d2028, - BAGL_FONT_OPEN_SANS_LIGHT_14px | BAGL_FONT_ALIGNMENT_MIDDLE, 0}, - "GPG Card", - 0, - 0, - 0, - NULL, - NULL, - NULL}, - - {{BAGL_BUTTON | BAGL_FLAG_TOUCHABLE, 0x00, 190, 215, 120, 40, 0, 6, - BAGL_FILL, 0x41ccb4, 0xF9F9F9, - BAGL_FONT_OPEN_SANS_LIGHT_14px | BAGL_FONT_ALIGNMENT_CENTER | BAGL_FONT_ALIGNMENT_MIDDLE, - 0}, - "Exit", - 0, - 0x37ae99, - 0xF9F9F9, - gpg_io_seproxyhal_touch_exit, - NULL, - NULL}, - -#ifdef GPG_DEBUG - {{BAGL_BUTTON | BAGL_FLAG_TOUCHABLE, 0x00, 20, 215, 120, 40, 0, 6, - BAGL_FILL, 0x41ccb4, 0xF9F9F9, - BAGL_FONT_OPEN_SANS_LIGHT_14px | BAGL_FONT_ALIGNMENT_CENTER | BAGL_FONT_ALIGNMENT_MIDDLE, - 0}, - "Init", - 0, - 0x37ae99, - 0xF9F9F9, - gpg_io_seproxyhal_touch_debug, - NULL, - NULL} -#endif - -}; - - -const bagl_element_t ui_idle_nanos[] = { - // type - {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, - BAGL_FILL, 0x000000, 0xFFFFFF, - 0, - 0}, - NULL, - 0, - 0, - 0, - NULL, - NULL, - NULL}, - - {{BAGL_LABELINE, 0x00, 0, 12, 128, 32, 0, 0, - 0 , 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px|BAGL_FONT_ALIGNMENT_CENTER, - 0 }, - "GPGCard", - 0, - 0, - 0, - NULL, - NULL, - NULL }, - //{{BAGL_LABELINE , 0x02, 0, 26, 128, 32, 0, 0, 0 , 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_REGULAR_11px|BAGL_FONT_ALIGNMENT_CENTER, 0 }, "Waiting for requests...", 0, 0, 0, NULL, NULL, NULL }, - - {{BAGL_ICON , 0x00, 3, 12, 7, - 7, 0, 0, - 0 , 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS }, - NULL, - 0, - 0, - 0, - NULL, - NULL, - NULL }, -}; - -unsigned int gpg_io_seproxyhal_touch_exit(const bagl_element_t *e) { - // Go back to the dashboard - os_sched_exit(0); - return 0; // do not redraw the widget -} - - - - -unsigned int ui_idle_blue_button(unsigned int button_mask, - unsigned int button_mask_counter) { - return 0; -} - -#endif \ No newline at end of file diff --git a/src/gpg_ux_msg.c b/src/gpg_ux_msg.c index f60d7fd..d1c3a2d 100644 --- a/src/gpg_ux_msg.c +++ b/src/gpg_ux_msg.c @@ -1,37 +1,42 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -const char *const C_TEMPLATE_TYPE = "Key type"; -const char *const C_TEMPLATE_KEY = "Key"; +const char *const C_TEMPLATE_TYPE = "Key type"; +const char *const C_TEMPLATE_KEY = "Key"; const char *const C_INVALID_SELECTION = "Invalid selection"; -const char *const C_OK = "OK"; +const char *const C_OK = "OK"; const char *const C_NOK = "NOK"; -const char *const C_WRONG_PIN = "PIN Incorrect"; -const char *const C_RIGHT_PIN = "PIN Correct"; +const char *const C_WRONG_PIN = "PIN Incorrect"; +const char *const C_RIGHT_PIN = "PIN Correct"; const char *const C_PIN_CHANGED = "PIN changed"; +const char *const C_PIN_LOCKED = "PIN locked"; const char *const C_PIN_DIFFERS = "2 PINs differs"; -const char *const C_PIN_USER = "User PIN"; -const char *const C_PIN_ADMIN = "Admin PIN"; +const char *const C_PIN_USER = "User PIN"; +const char *const C_PIN_ADMIN = "Admin PIN"; -const char *const C_VERIFIED = "Verified"; +const char *const C_VERIFIED = "Verified"; const char *const C_NOT_VERIFIED = "Not Verified"; -const char *const C_ALLOWED = "Allowed"; -const char *const C_NOT_ALLOWED = "Not Allowed "; +const char *const C_ALLOWED = "Allowed"; +const char *const C_NOT_ALLOWED = "Not Allowed "; const char *const C_DEFAULT_MODE = "Default mode"; const char *const C_UIF_LOCKED = "UIF locked"; + +const char *const C_EMPTY = ""; diff --git a/src/gpg_ux_msg.h b/src/gpg_ux_msg.h index 7e4fa32..a3e0a9f 100644 --- a/src/gpg_ux_msg.h +++ b/src/gpg_ux_msg.h @@ -1,17 +1,19 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifndef GPG_UX_MSG_H #define GPG_UX_MSG_H @@ -26,6 +28,7 @@ extern const char *const C_NOK; extern const char *const C_WRONG_PIN; extern const char *const C_RIGHT_PIN; extern const char *const C_PIN_CHANGED; +extern const char *const C_PIN_LOCKED; extern const char *const C_PIN_DIFFERS; extern const char *const C_PIN_USER; extern const char *const C_PIN_ADMIN; @@ -39,25 +42,29 @@ extern const char *const C_DEFAULT_MODE; extern const char *const C_UIF_LOCKED; extern const char *const C_UIF_INVALID; -#define PICSTR(x) ((char *)PIC(x)) +extern const char *const C_EMPTY; -#define TEMPLATE_TYPE PICSTR(C_TEMPLATE_TYPE) -#define TEMPLATE_KEY PICSTR(C_TEMPLATE_KEY) +#define PICSTR(x) ((char *) PIC(x)) + +#define TEMPLATE_TYPE PICSTR(C_TEMPLATE_TYPE) +#define TEMPLATE_KEY PICSTR(C_TEMPLATE_KEY) #define INVALID_SELECTION PICSTR(C_INVALID_SELECTION) -#define OK PICSTR(C_OK) -#define NOK PICSTR(C_NOK) -#define WRONG_PIN PICSTR(C_WRONG_PIN) -#define RIGHT_PIN PICSTR(C_RIGHT_PIN) -#define PIN_CHANGED PICSTR(C_PIN_CHANGED) -#define PIN_DIFFERS PICSTR(C_PIN_DIFFERS) -#define PIN_USER PICSTR(C_PIN_USER) -#define PIN_ADMIN PICSTR(C_PIN_ADMIN) -#define VERIFIED PICSTR(C_VERIFIED) -#define NOT_VERIFIED PICSTR(C_NOT_VERIFIED) -#define ALLOWED PICSTR(C_ALLOWED) -#define NOT_ALLOWED PICSTR(C_NOT_ALLOWED) -#define DEFAULT_MODE PICSTR(C_DEFAULT_MODE) -#define UIF_LOCKED PICSTR(C_UIF_LOCKED) -#define UIF_INVALID PICSTR(C_UIF_INVALID) +#define OK PICSTR(C_OK) +#define NOK PICSTR(C_NOK) +#define WRONG_PIN PICSTR(C_WRONG_PIN) +#define RIGHT_PIN PICSTR(C_RIGHT_PIN) +#define PIN_CHANGED PICSTR(C_PIN_CHANGED) +#define PIN_LOCKED PICSTR(C_PIN_LOCKED) +#define PIN_DIFFERS PICSTR(C_PIN_DIFFERS) +#define PIN_USER PICSTR(C_PIN_USER) +#define PIN_ADMIN PICSTR(C_PIN_ADMIN) +#define VERIFIED PICSTR(C_VERIFIED) +#define NOT_VERIFIED PICSTR(C_NOT_VERIFIED) +#define ALLOWED PICSTR(C_ALLOWED) +#define NOT_ALLOWED PICSTR(C_NOT_ALLOWED) +#define DEFAULT_MODE PICSTR(C_DEFAULT_MODE) +#define UIF_LOCKED PICSTR(C_UIF_LOCKED) +#define UIF_INVALID PICSTR(C_UIF_INVALID) +#define EMPTY PICSTR(C_EMPTY) #endif diff --git a/src/gpg_ux_nanos.c b/src/gpg_ux_nanos.c index cf6f022..4c31f23 100644 --- a/src/gpg_ux_nanos.c +++ b/src/gpg_ux_nanos.c @@ -1,1027 +1,1236 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#ifdef UI_NANO_S +#include "bolos_target.h" +#if defined(HAVE_BAGL) && defined(TARGET_NANOS) -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" - #include "gpg_ux_msg.h" -#include "os_io_seproxyhal.h" -#include "usbd_ccid_impl.h" -#include "string.h" -#include "glyphs.h" +#include "gpg_ux.h" +#include "usbd_ccid_if.h" /* ----------------------------------------------------------------------- */ /* --- NanoS UI layout --- */ /* ----------------------------------------------------------------------- */ -const ux_menu_entry_t ui_menu_template[]; -void ui_menu_template_display(unsigned int value); -const bagl_element_t *ui_menu_template_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element); -void ui_menu_tmpl_set_action(unsigned int value); -const ux_menu_entry_t ui_menu_tmpl_key[]; -void ui_menu_tmpl_key_action(unsigned int value); -const ux_menu_entry_t ui_menu_tmpl_type[]; -void ui_menu_tmpl_type_action(unsigned int value); - -const ux_menu_entry_t ui_menu_seed[]; -void ui_menu_seed_display(unsigned int value); -const bagl_element_t *ui_menu_seed_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element); -void ui_menu_seed_action(unsigned int value); - -const ux_menu_entry_t ui_menu_reset[]; -void ui_menu_reset_action(unsigned int value); - -#if GPG_MULTISLOT -const ux_menu_entry_t ui_menu_slot[]; -void ui_menu_slot_display(unsigned int value); -const bagl_element_t *ui_menu_slot_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element); -void ui_menu_slot_action(unsigned int value); -#endif +void ui_menu_tmpl_set_action(unsigned int value); +void ui_menu_tmpl_key_action(unsigned int value); +void ui_menu_tmpl_type_action(unsigned int value); +void ui_menu_seedmode_action(unsigned int value); +void ui_menu_reset_action(unsigned int value); const ux_menu_entry_t ui_menu_settings[]; - -const ux_menu_entry_t ui_menu_main[]; -void ui_menu_main_display(unsigned int value); -const bagl_element_t *ui_menu_main_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element); - -const bagl_element_t ui_pinconfirm_nanos[]; -void ui_menu_pinconfirm_action(unsigned int value); -unsigned int ui_pinconfirm_nanos_button(unsigned int button_mask, unsigned int button_mask_counter); -unsigned int ui_pinconfirm_prepro(const bagl_element_t *element); - -const bagl_element_t ui_pinentry_nanos[]; -void ui_menu_pinentry_display(unsigned int value); -void ui_menu_pinentry_action(unsigned int value); -unsigned int ui_pinentry_nanos_button(unsigned int button_mask, unsigned int button_mask_counter); -unsigned int ui_pinentry_prepro(const bagl_element_t *element); -static unsigned int validate_pin(); +void ui_menu_main_display(unsigned int value); +unsigned int ui_pinentry_action_button(unsigned int button_mask, unsigned int button_mask_counter); /* ------------------------------- Helpers UX ------------------------------- */ -void ui_CCID_reset(void) { - io_usb_ccid_set_card_inserted(0); - io_usb_ccid_set_card_inserted(1); -} - -void ui_info(const char *msg1, const char *msg2, const void *menu_display, unsigned int value) { - os_memset(&G_gpg_vstate.ui_dogsays[0], 0, sizeof(ux_menu_entry_t)); - G_gpg_vstate.ui_dogsays[0].callback = menu_display; - G_gpg_vstate.ui_dogsays[0].userid = value; - G_gpg_vstate.ui_dogsays[0].line1 = msg1; - G_gpg_vstate.ui_dogsays[0].line2 = msg2; - os_memset(&G_gpg_vstate.ui_dogsays[1], 0, sizeof(ux_menu_entry_t)); - UX_MENU_DISPLAY(0, G_gpg_vstate.ui_dogsays, NULL); +/** + * Display popup message on screen + * + * @param[in] msg1 1st part of the message + * @param[in] msg2 2nd part of the message + * @param[in] menu_display next page display callback + * + */ +void ui_info(const char *msg1, const char *msg2, const void *menu_display) { + explicit_bzero(&G_gpg_vstate.ui_dogsays[0], sizeof(ux_menu_entry_t)); + G_gpg_vstate.ui_dogsays[0].callback = menu_display; + G_gpg_vstate.ui_dogsays[0].userid = 0; + G_gpg_vstate.ui_dogsays[0].line1 = msg1; + G_gpg_vstate.ui_dogsays[0].line2 = msg2; + + explicit_bzero(&G_gpg_vstate.ui_dogsays[1], sizeof(ux_menu_entry_t)); + UX_MENU_DISPLAY(0, G_gpg_vstate.ui_dogsays, NULL); }; /* ------------------------------ UIF CONFIRM UX ----------------------------- */ -unsigned int ui_uifconfirm_nanos_button(unsigned int button_mask, unsigned int button_mask_counter); -unsigned int ui_uifconfirm_prepro(const bagl_element_t *element); - -const bagl_element_t ui_uifconfirm_nanos[] = { - // type userid x y w h str rad fill fg bg font_id icon_id - {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, - NULL}, - - {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS}, - NULL}, - - {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK}, - NULL}, - {{BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, +unsigned int ui_uifconfirm_action_button(unsigned int button_mask, + unsigned int button_mask_counter); +unsigned int ui_uifconfirm_predisplay(const bagl_element_t *element); + +const bagl_element_t ui_uifconfirm_action[] = { + // type userid x y w h str rad fill fg bg + // font_id icon_id + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, NULL}, + + {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS}, NULL}, + + {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK}, NULL}, + + {{BAGL_LABELINE, + 0x01, + 0, + 12, + 128, + 32, + 0, + 0, + 0, + 0xFFFFFF, + 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, + 0}, G_gpg_vstate.menu}, - {{BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + {{BAGL_LABELINE, + 0x02, + 0, + 26, + 128, + 32, + 0, + 0, + 0, + 0xFFFFFF, + 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, + 0}, G_gpg_vstate.menu}, }; +/** + * UIF page display + * + * @param[in] value unused + * + */ void ui_menu_uifconfirm_display(unsigned int value) { - UX_DISPLAY(ui_uifconfirm_nanos, (void *)ui_uifconfirm_prepro); + UNUSED(value); + UX_DISPLAY(ui_uifconfirm_action, (void *) ui_uifconfirm_predisplay); } -unsigned int ui_uifconfirm_prepro(const bagl_element_t *element) { - if (element->component.userid == 1) { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Confirm:"); - return 1; - } - if (element->component.userid == 2) { - unsigned int uif_case = (G_gpg_vstate.io_ins << 16) | (G_gpg_vstate.io_p1 << 8) | (G_gpg_vstate.io_p2); - switch (uif_case) { - case 0x002A9E9A: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Signature"); - return 1; - case 0x002A8680: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Encryption"); - return 1; - case 0x002A8086: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Decryption"); - return 1; - case 0x00880000: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Authentication"); - return 1; +/** + * UIF page display preparation callback + * + * @param[in] element selected element to display + * + * @return Error code + * + */ +unsigned int ui_uifconfirm_predisplay(const bagl_element_t *element) { + explicit_bzero(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)); + + switch (element->component.userid) { + case 1: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Confirm:"); + break; + case 2: + switch (G_gpg_vstate.io_ins) { + case INS_INTERNAL_AUTHENTICATE: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Authentication"); + break; + case INS_PSO: + switch (G_gpg_vstate.io_p1p2) { + case PSO_CDS: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Signature"); + break; + case PSO_ENC: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Encryption"); + break; + case PSO_DEC: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Decryption"); + break; + default: + break; + } + break; + default: + break; + } + break; + default: + break; + } + if (G_gpg_vstate.menu[0] == 0) { + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Please Cancel"); } - } - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Please Cancel"); - return 1; + return 1; } -unsigned int ui_uifconfirm_nanos_button(unsigned int button_mask, unsigned int button_mask_counter) { - unsigned int sw; - - sw = 0x6985; - switch (button_mask) { - case BUTTON_EVT_RELEASED | BUTTON_LEFT: // CANCEL - gpg_io_discard(1); +/** + * UIF page Action callback + * + * @param[in] button_mask selected button + * @param[in] button_mask_counter unused + * + * @return Error code + * + */ +unsigned int ui_uifconfirm_action_button(unsigned int button_mask, + unsigned int button_mask_counter) { + UNUSED(button_mask_counter); + unsigned int sw = SW_SECURITY_UIF_ISSUE; + + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: // CANCEL + gpg_io_discard(1); + break; + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // OK + G_gpg_vstate.UIF_flags = 1; + switch (G_gpg_vstate.io_ins) { + case INS_PSO: + sw = gpg_apdu_pso(); + break; + case INS_INTERNAL_AUTHENTICATE: + sw = gpg_apdu_internal_authenticate(); + break; + default: + gpg_io_discard(1); + sw = SW_CONDITIONS_NOT_SATISFIED; + break; + } + G_gpg_vstate.UIF_flags = 0; + break; + } gpg_io_insert_u16(sw); gpg_io_do(IO_RETURN_AFTER_TX); ui_menu_main_display(0); - sw = 0x6985; - break; - - case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // OK - BEGIN_TRY { - TRY { - G_gpg_vstate.UIF_flags = 1; - if (G_gpg_vstate.io_ins == INS_PSO) { - sw = gpg_apdu_pso(); - } else if (G_gpg_vstate.io_ins == INS_INTERNAL_AUTHENTICATE) { - sw = gpg_apdu_internal_authenticate(); - } else { - gpg_io_discard(1); - sw = 0x6985; - } - } - CATCH_OTHER(e) { - gpg_io_discard(1); - if ((e & 0xFFFF0000) || (((e & 0xF000) != 0x6000) && ((e & 0xF000) != 0x9000))) { - gpg_io_insert_u32(e); - sw = 0x6f42; - } else { - sw = e; - } - } - FINALLY { - G_gpg_vstate.UIF_flags = 0; - gpg_io_insert_u16(sw); - gpg_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); - } - break; - } - END_TRY; - } - return 0; + return 0; } /* ------------------------------ PIN CONFIRM UX ----------------------------- */ -const bagl_element_t ui_pinconfirm_nanos[] = { - // type userid x y w h str rad fill fg bg font_id icon_id - {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, - NULL}, - - {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS}, - NULL}, - - {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK}, - NULL}, - - {{BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, +unsigned int ui_pinconfirm_action_button(unsigned int button_mask, + unsigned int button_mask_counter); + +const bagl_element_t ui_pinconfirm_action[] = { + // type userid x y w h str rad fill fg bg + // font_id icon_id + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, NULL}, + + {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS}, NULL}, + + {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK}, NULL}, + + {{BAGL_LABELINE, + 0x01, + 0, + 12, + 128, + 32, + 0, + 0, + 0, + 0xFFFFFF, + 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, + 0}, G_gpg_vstate.menu}, - {{BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + {{BAGL_LABELINE, + 0x02, + 0, + 26, + 128, + 32, + 0, + 0, + 0, + 0xFFFFFF, + 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, + 0}, G_gpg_vstate.menu}, }; -void ui_menu_pinconfirm_display(unsigned int value) { - UX_DISPLAY(ui_pinconfirm_nanos, (void *)ui_pinconfirm_prepro); -} - -unsigned int ui_pinconfirm_prepro(const bagl_element_t *element) { - if (element->component.userid == 1) { - if ((G_gpg_vstate.io_p2 == 0x81) || (G_gpg_vstate.io_p2 == 0x82) || (G_gpg_vstate.io_p2 == 0x83)) { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Confirm PIN"); - return 1; +/** + * Pin Confirm display preparation callback + * + * @param[in] element selected element to display + * + * @return Error code + * + */ +unsigned int ui_pinconfirm_predisplay(const bagl_element_t *element) { + if (element->component.userid == 1) { + if ((G_gpg_vstate.io_p2 == PIN_ID_PW1) || (G_gpg_vstate.io_p2 == PIN_ID_PW2) || + (G_gpg_vstate.io_p2 == PIN_ID_PW3)) { + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Confirm PIN"); + return 1; + } } - } - if (element->component.userid == 2) { - if ((G_gpg_vstate.io_p2 == 0x81) || (G_gpg_vstate.io_p2 == 0x82) || (G_gpg_vstate.io_p2 == 0x83)) { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s %x", G_gpg_vstate.io_p2 == 0x83 ? "Admin" : "User", - G_gpg_vstate.io_p2); - return 1; + if (element->component.userid == 2) { + if ((G_gpg_vstate.io_p2 == PIN_ID_PW1) || (G_gpg_vstate.io_p2 == PIN_ID_PW2) || + (G_gpg_vstate.io_p2 == PIN_ID_PW3)) { + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "%s %x", + G_gpg_vstate.io_p2 == PIN_ID_PW3 ? "Admin" : "User", + G_gpg_vstate.io_p2); + return 1; + } } - } - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Please Cancel"); - return 1; + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Please Cancel"); + return 1; +} + +/** + * Pin Confirm page display + * + * @param[in] value unused + * + */ +void ui_menu_pinconfirm_display(unsigned int value) { + UNUSED(value); + UX_DISPLAY(ui_pinconfirm_action, (void *) ui_pinconfirm_predisplay); } -unsigned int ui_pinconfirm_nanos_button(unsigned int button_mask, unsigned int button_mask_counter) { - unsigned int sw; - - sw = 0x6985; - switch (button_mask) { - case BUTTON_EVT_RELEASED | BUTTON_LEFT: // CANCEL - gpg_pin_set_verified(G_gpg_vstate.io_p2, 0); - sw = 0x6985; - break; - - case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // OK - gpg_pin_set_verified(G_gpg_vstate.io_p2, 1); - sw = 0x9000; - break; - default: +/** + * Pin Confirm Action callback + * + * @param[in] button_mask selected button + * @param[in] button_mask_counter unused + * + * @return Error code + * + */ +unsigned int ui_pinconfirm_action_button(unsigned int button_mask, + unsigned int button_mask_counter) { + UNUSED(button_mask_counter); + unsigned int sw = SW_CONDITIONS_NOT_SATISFIED; + + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: // CANCEL + gpg_pin_set_verified(G_gpg_vstate.io_p2, 0); + sw = SW_CONDITIONS_NOT_SATISFIED; + break; + + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // OK + gpg_pin_set_verified(G_gpg_vstate.io_p2, 1); + sw = SW_OK; + break; + default: + return 0; + } + gpg_io_discard(0); + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_menu_main_display(0); return 0; - } - gpg_io_discard(0); - gpg_io_insert_u16(sw); - gpg_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); - return 0; } /* ------------------------------- PIN ENTRY UX ------------------------------ */ -const bagl_element_t ui_pinentry_nanos[] = { - // type userid x y w h str rad fill fg bg font_id icon_id - {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, - NULL}, - - {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_DOWN}, - NULL}, +const bagl_element_t ui_pinentry_action[] = { + // type userid x y w h str rad fill fg bg + // font_id icon_id - {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_UP}, + // clear screen + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, BAGL_HEIGHT, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, NULL}, - {{BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, + // left/rights icons + {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_DOWN}, NULL}, + {{BAGL_ICON, 0x00, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_UP}, NULL}, + + // PIN text identifier + {{BAGL_LABELINE, + 0x01, + 0, + 12, + 128, + 32, + 0, + 0, + 0, + 0xFFFFFF, + 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, + 0}, G_gpg_vstate.menu}, - {{BAGL_LABELINE, 0x02, 0, 26, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - G_gpg_vstate.menu}, -}; -static const char C_pin_digit[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '<', 'A', 'V'}; + // PIN Value + {{BAGL_LABELINE, + 0x02, + 0, + 26, + 128, + 32, + 0, + 0, + 0, + 0xFFFFFF, + 0x000000, + BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, + 0}, + G_gpg_vstate.menu}}; -void ui_menu_pinentry_display(unsigned int value) { - if (value == 0) { - os_memset(G_gpg_vstate.ux_pinentry, 0, sizeof(G_gpg_vstate.ux_pinentry)); - G_gpg_vstate.ux_pinentry[0] = 1; - G_gpg_vstate.ux_pinentry[1] = 5; - } - UX_DISPLAY(ui_pinentry_nanos, (void *)ui_pinentry_prepro); -} +static const char C_pin_digit[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '<', 'A', 'V'}; -unsigned int ui_pinentry_prepro(const bagl_element_t *element) { - if (element->component.userid == 1) { - if (G_gpg_vstate.io_ins == 0x24) { - switch (G_gpg_vstate.io_p1) { - case 0: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Current %s PIN", - (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - break; - case 1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "New %s PIN", - (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - break; - case 2: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Confirm %s PIN", - (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - break; - default: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "WAT %s PIN", - (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - break; - } - } else { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s PIN", (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - } - } else if (element->component.userid == 2) { - unsigned int i; - G_gpg_vstate.menu[0] = ' '; -#if 0 - for (i = 1; i <= G_gpg_vstate.ux_pinentry[0]; i++) { - G_gpg_vstate.menu[i] = C_pin_digit[G_gpg_vstate.ux_pinentry[i]]; - } -#else - for (i = 1; i < G_gpg_vstate.ux_pinentry[0]; i++) { - G_gpg_vstate.menu[i] = '*'; - } - G_gpg_vstate.menu[i] = C_pin_digit[G_gpg_vstate.ux_pinentry[i]]; - i++; -#endif - for (; i <= GPG_MAX_PW_LENGTH; i++) { - G_gpg_vstate.menu[i] = '-'; +/** + * Pin Entry display preparation callback + * + * @param[in] element selected element to display + * + * @return Error code + * + */ +unsigned int ui_pinentry_predisplay(const bagl_element_t *element) { + if (element->component.userid == 1) { + if (G_gpg_vstate.io_ins == INS_CHANGE_REFERENCE_DATA) { + switch (G_gpg_vstate.io_p1) { + case 0: + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "Current %s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + break; + case 1: + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "New %s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + break; + case 2: + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "Confirm %s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + break; + default: + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "WAT %s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + break; + } + } else { + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "%s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + } + } else if (element->component.userid == 2) { + unsigned int i; + for (i = 0; i < G_gpg_vstate.ux_pinLen; i++) { + G_gpg_vstate.menu[i] = '*'; + } + G_gpg_vstate.menu[i] = C_pin_digit[G_gpg_vstate.ux_pinentry[i]]; + i++; + for (; i <= GPG_MAX_PW_LENGTH; i++) { + G_gpg_vstate.menu[i] = '-'; + } + G_gpg_vstate.menu[i] = 0; } - G_gpg_vstate.menu[i] = 0; - } - return 1; + return 1; } -unsigned int ui_pinentry_nanos_button(unsigned int button_mask, unsigned int button_mask_counter) { - unsigned int offset = G_gpg_vstate.ux_pinentry[0]; - unsigned m_pinentry; - char digit; +/** + * Pin Entry page display + * + * @param[in] value indicate if pin is reset + * + */ +void ui_menu_pinentry_display(unsigned int value) { + if (value == 0) { + explicit_bzero(G_gpg_vstate.ux_pinentry, sizeof(G_gpg_vstate.ux_pinentry)); + G_gpg_vstate.ux_pinLen = 0; + G_gpg_vstate.ux_pinentry[0] = 5; + } + UX_DISPLAY(ui_pinentry_action, (void *) ui_pinentry_predisplay); +} - m_pinentry = 1; +/** + * Pin Entry Validation callback + * + */ +static void validate_pin() { + unsigned int offset, len, sw = SW_UNKNOWN; + gpg_pin_t *pin; - switch (button_mask) { - case BUTTON_EVT_RELEASED | BUTTON_LEFT: // Down - if (G_gpg_vstate.ux_pinentry[offset]) { - G_gpg_vstate.ux_pinentry[offset]--; - } else { - G_gpg_vstate.ux_pinentry[offset] = sizeof(C_pin_digit) - 1; + for (offset = 0; offset <= G_gpg_vstate.ux_pinLen; offset++) { + G_gpg_vstate.menu[offset] = C_pin_digit[G_gpg_vstate.ux_pinentry[offset]]; } - ui_menu_pinentry_display(1); - break; - case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // up - G_gpg_vstate.ux_pinentry[offset]++; - if (G_gpg_vstate.ux_pinentry[offset] == sizeof(C_pin_digit)) { - G_gpg_vstate.ux_pinentry[offset] = 0; - } - ui_menu_pinentry_display(1); - break; - - case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: - digit = C_pin_digit[G_gpg_vstate.ux_pinentry[offset]]; - // next digit - if ((digit >= '0') && (digit <= '9')) { - offset++; - G_gpg_vstate.ux_pinentry[0] = offset; - if (offset == GPG_MAX_PW_LENGTH + 1) { - validate_pin(); - } else { - G_gpg_vstate.ux_pinentry[offset] = 5; - ui_menu_pinentry_display(1); - } - } - // cancel digit - else if (digit == '<') { - if (offset > 1) { - offset--; - G_gpg_vstate.ux_pinentry[0] = offset; - } - ui_menu_pinentry_display(1); - } - // validate pin - else if (digit == 'V') { - G_gpg_vstate.ux_pinentry[0] = offset - 1; - validate_pin(); + if (G_gpg_vstate.io_ins == INS_VERIFY) { + pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); + sw = gpg_pin_check(pin, + G_gpg_vstate.io_p2, + (unsigned char *) G_gpg_vstate.menu, + G_gpg_vstate.ux_pinLen); + gpg_io_discard(1); + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + if (sw != SW_OK) { + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + " %d tries remaining", + pin->counter); + ui_info(WRONG_PIN, G_gpg_vstate.menu, ui_menu_main_display); + } else { + ui_menu_main_display(0); + } } - // cancel input without check - else { //(digit == 'A') - gpg_io_discard(0); - gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); - gpg_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); + + if (G_gpg_vstate.io_ins == INS_CHANGE_REFERENCE_DATA) { + if (G_gpg_vstate.io_p1 <= 2) { + gpg_io_insert_u8(G_gpg_vstate.ux_pinLen); + gpg_io_insert((unsigned char *) G_gpg_vstate.menu, G_gpg_vstate.ux_pinLen); + G_gpg_vstate.io_p1++; + } + if (G_gpg_vstate.io_p1 == 3) { + pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); + if (gpg_pin_check(pin, + G_gpg_vstate.io_p2, + G_gpg_vstate.work.io_buffer + 1, + G_gpg_vstate.work.io_buffer[0]) != SW_OK) { + gpg_io_discard(1); + gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); + gpg_io_do(IO_RETURN_AFTER_TX); + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + " %d tries remaining", + pin->counter); + ui_info(WRONG_PIN, G_gpg_vstate.menu, ui_menu_main_display); + return; + } + offset = 1 + G_gpg_vstate.work.io_buffer[0]; + len = G_gpg_vstate.work.io_buffer[offset]; + if ((len != G_gpg_vstate.work.io_buffer[offset + 1 + len]) || + (memcmp(G_gpg_vstate.work.io_buffer + offset + 1, + G_gpg_vstate.work.io_buffer + offset + 1 + len + 1, + len) != 0)) { + gpg_io_discard(1); + gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_info(PIN_DIFFERS, EMPTY, ui_menu_main_display); + } else { + sw = gpg_pin_set(gpg_pin_get_pin(G_gpg_vstate.io_p2), + G_gpg_vstate.work.io_buffer + offset + 1, + len); + gpg_io_discard(1); + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_menu_main_display(0); + } + } else { + ui_menu_pinentry_display(0); + } } - break; - } - return 0; } -// >= 0 -static unsigned int validate_pin() { - unsigned int offset, len, sw; - gpg_pin_t * pin; - - for (offset = 1; offset <= G_gpg_vstate.ux_pinentry[0]; offset++) { - G_gpg_vstate.menu[offset] = C_pin_digit[G_gpg_vstate.ux_pinentry[offset]]; - } - - if (G_gpg_vstate.io_ins == 0x20) { - pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); - sw = gpg_pin_check(pin, G_gpg_vstate.io_p2, (unsigned char *)(G_gpg_vstate.menu + 1), G_gpg_vstate.ux_pinentry[0]); - gpg_io_discard(1); - gpg_io_insert_u16(sw); - gpg_io_do(IO_RETURN_AFTER_TX); - if (sw != SW_OK) { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %d tries remaining", pin->counter); - ui_info(WRONG_PIN, G_gpg_vstate.menu, ui_menu_main_display, 0); - } else { - ui_menu_main_display(0); - } - } - if (G_gpg_vstate.io_ins == 0x24) { - if (G_gpg_vstate.io_p1 <= 2) { - gpg_io_insert_u8(G_gpg_vstate.ux_pinentry[0]); - gpg_io_insert((unsigned char *)(G_gpg_vstate.menu + 1), G_gpg_vstate.ux_pinentry[0]); - G_gpg_vstate.io_p1++; - } - if (G_gpg_vstate.io_p1 == 3) { - pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); - if (gpg_pin_check(pin, G_gpg_vstate.io_p2, G_gpg_vstate.work.io_buffer + 1, G_gpg_vstate.work.io_buffer[0]) != - SW_OK) { - gpg_io_discard(1); - gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); - gpg_io_do(IO_RETURN_AFTER_TX); - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %d tries remaining", pin->counter); - ui_info(WRONG_PIN, NULL, ui_menu_main_display, 0); - return 0; - } - offset = 1 + G_gpg_vstate.work.io_buffer[0]; - len = G_gpg_vstate.work.io_buffer[offset]; - if ((len != G_gpg_vstate.work.io_buffer[offset + 1 + len]) || - (os_memcmp(G_gpg_vstate.work.io_buffer + offset + 1, G_gpg_vstate.work.io_buffer + offset + 1 + len + 1, - len) != 0)) { - gpg_io_discard(1); - gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); - gpg_io_do(IO_RETURN_AFTER_TX); - ui_info(PIN_DIFFERS, NULL, ui_menu_main_display, 0); - } else { - gpg_pin_set(gpg_pin_get_pin(G_gpg_vstate.io_p2), G_gpg_vstate.work.io_buffer + offset + 1, len); - gpg_io_discard(1); - gpg_io_insert_u16(SW_OK); - gpg_io_do(IO_RETURN_AFTER_TX); - // ui_info(PIN_CHANGED, NULL, ui_menu_main_display, 0); - ui_menu_main_display(0); - } - return 0; - } else { - ui_menu_pinentry_display(0); +/** + * Pin Entry page Action callback + * + * @param[in] button_mask selected button + * @param[in] button_mask_counter unused + * + * @return Error code + * + */ +unsigned int ui_pinentry_action_button(unsigned int button_mask, unsigned int button_mask_counter) { + UNUSED(button_mask_counter); + unsigned int offset = G_gpg_vstate.ux_pinLen; + char digit; + + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: // Down + if (G_gpg_vstate.ux_pinentry[offset]) { + G_gpg_vstate.ux_pinentry[offset]--; + } else { + G_gpg_vstate.ux_pinentry[offset] = sizeof(C_pin_digit) - 1; + } + ui_menu_pinentry_display(1); + break; + + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // up + G_gpg_vstate.ux_pinentry[offset]++; + if (G_gpg_vstate.ux_pinentry[offset] == sizeof(C_pin_digit)) { + G_gpg_vstate.ux_pinentry[offset] = 0; + } + ui_menu_pinentry_display(1); + break; + + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: + digit = C_pin_digit[G_gpg_vstate.ux_pinentry[offset]]; + // next digit + if ((digit >= '0') && (digit <= '9')) { + G_gpg_vstate.ux_pinLen = ++offset; + if (offset == GPG_MAX_PW_LENGTH) { + validate_pin(); + } else { + G_gpg_vstate.ux_pinentry[offset] = 5; + ui_menu_pinentry_display(1); + } + } + // cancel digit + else if (digit == '<') { + if (offset > 0) { + G_gpg_vstate.ux_pinLen--; + } + ui_menu_pinentry_display(1); + } + // validate pin + else if (digit == 'V') { + validate_pin(); + } + // cancel input without check + else { //(digit == 'A') + gpg_io_discard(0); + gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_menu_main_display(0); + } + break; } - } - return 0; + return 0; } + /* ------------------------------- template UX ------------------------------- */ -#define LABEL_SIG "Signature" -#define LABEL_AUT "Authentication" -#define LABEL_DEC "Decryption" - -#define LABEL_RSA2048 "RSA 2048" -#define LABEL_RSA3072 "RSA 3072" -#define LABEL_RSA4096 "RSA 4096" -#define LABEL_NISTP256 "NIST P256" -//#define LABEL_NISTP384 "NIST P384" -//#define LABEL_NISTP521 "NIST P521" -#define LABEL_SECP256K1 "SEPC 256K1" -//#define LABEL_BPOOL256R1 "Brainpool 256R1" -//#define LABEL_BPOOL384R1 "Brainpool 384R1" -//#define LABEL_BPOOL512R1 "Brainpool 512R1" -#define LABEL_Ed25519 "Ed25519" - -const ux_menu_entry_t ui_menu_template[] = {{ui_menu_tmpl_key, NULL, -1, NULL, "Choose key...", NULL, 0, 0}, - {ui_menu_tmpl_type, NULL, -1, NULL, "Choose type...", NULL, 0, 0}, - {NULL, ui_menu_tmpl_set_action, -1, NULL, "Set template", NULL, 0, 0}, - {ui_menu_settings, NULL, 0, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END}; +const ux_menu_entry_t ui_menu_tmpl_key[]; +const ux_menu_entry_t ui_menu_tmpl_type[]; + +const ux_menu_entry_t ui_menu_template[] = { + {ui_menu_tmpl_key, NULL, -1, NULL, "Choose key...", NULL, 0, 0}, + {ui_menu_tmpl_type, NULL, -1, NULL, "Choose type...", NULL, 0, 0}, + {NULL, ui_menu_tmpl_set_action, -1, NULL, "Set template", NULL, 0, 0}, + {ui_menu_settings, NULL, 0, &C_icon_back, "Back", NULL, 61, 40}, + UX_MENU_END}; + +const ux_menu_entry_t ui_menu_tmpl_key[] = { + {NULL, ui_menu_tmpl_key_action, 1, NULL, LABEL_SIG, NULL, 0, 0}, + {NULL, ui_menu_tmpl_key_action, 2, NULL, LABEL_DEC, NULL, 0, 0}, + {NULL, ui_menu_tmpl_key_action, 3, NULL, LABEL_AUT, NULL, 0, 0}, + {ui_menu_template, NULL, 0, &C_icon_back, "Back", NULL, 61, 40}, + UX_MENU_END}; +const ux_menu_entry_t ui_menu_tmpl_type[] = { + {NULL, ui_menu_tmpl_type_action, 2048, NULL, LABEL_RSA2048, NULL, 0, 0}, + {NULL, ui_menu_tmpl_type_action, 3072, NULL, LABEL_RSA3072, NULL, 0, 0}, +#ifdef WITH_SUPPORT_RSA4096 + {NULL, ui_menu_tmpl_type_action, 4096, NULL, LABEL_RSA4096, NULL, 0, 0}, +#endif + {NULL, ui_menu_tmpl_type_action, CX_CURVE_SECP256K1, NULL, LABEL_SECP256K1, NULL, 0, 0}, + {NULL, ui_menu_tmpl_type_action, CX_CURVE_SECP256R1, NULL, LABEL_SECP256R1, NULL, 0, 0}, + {NULL, ui_menu_tmpl_type_action, CX_CURVE_Ed25519, NULL, LABEL_Ed25519, NULL, 0, 0}, + {ui_menu_template, NULL, 0, &C_icon_back, "Back", NULL, 61, 40}, + UX_MENU_END}; + +/** + * Template page display preparation callback + * + * @param[in] entry selected menu to display + * @param[in] element selected element to display + * + * @return Eelement to display + * + */ +const bagl_element_t *ui_menu_template_predisplay(const ux_menu_entry_t *entry, + bagl_element_t *element) { + if (element->component.userid == 0x20) { + if (entry == &ui_menu_template[0]) { + switch (G_gpg_vstate.ux_key) { + case 1: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s", LABEL_SIG); + break; + case 2: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s", LABEL_DEC); + break; + case 3: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s", LABEL_AUT); + break; + default: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Choose key..."); + break; + } + element->text = G_gpg_vstate.menu; + } + if (entry == &ui_menu_template[1]) { + switch (G_gpg_vstate.ux_type) { + case 2048: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_RSA2048); + break; + case 3072: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_RSA3072); + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_RSA4096); + break; +#endif + case CX_CURVE_SECP256K1: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_SECP256K1); + break; + case CX_CURVE_SECP256R1: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_SECP256R1); + break; + case CX_CURVE_Ed25519: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_Ed25519); + break; + default: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Choose type..."); + break; + } + element->text = G_gpg_vstate.menu; + } + } + return element; +} + +/** + * Template page display + * + * @param[in] value flow step + * + */ void ui_menu_template_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_template, ui_menu_template_preprocessor); + UX_MENU_DISPLAY(value, ui_menu_template, ui_menu_template_predisplay); } -const bagl_element_t *ui_menu_template_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element) { - if (element->component.userid == 0x20) { - if (entry == &ui_menu_template[0]) { - switch (G_gpg_vstate.ux_key) { - case 1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s", LABEL_SIG); - break; - case 2: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s", LABEL_DEC); - break; - case 3: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s", LABEL_AUT); - break; - default: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Choose key..."); - break; - } - element->text = G_gpg_vstate.menu; +/** + * Template Action callback + * + * @param[in] value unused + * + */ +void ui_menu_tmpl_set_action(unsigned int value) { + UNUSED(value); + LV(attributes, GPG_KEY_ATTRIBUTES_LENGTH); + gpg_key_t *dest = NULL; + const unsigned char *oid; + unsigned int oid_len; + + explicit_bzero(&attributes, sizeof(attributes)); + switch (G_gpg_vstate.ux_type) { + case 2048: + case 3072: +#ifdef WITH_SUPPORT_RSA4096 + case 4096: +#endif + attributes.value[0] = KEY_ID_RSA; + U2BE_ENCODE(attributes.value, 1, G_gpg_vstate.ux_type); + attributes.value[3] = 0x00; + attributes.value[4] = 0x20; + attributes.value[5] = 0x01; + attributes.length = 6; + break; + + case CX_CURVE_SECP256K1: + case CX_CURVE_SECP256R1: + if (G_gpg_vstate.ux_key == 2) { + attributes.value[0] = KEY_ID_ECDH; + } else { + attributes.value[0] = KEY_ID_ECDSA; + } + oid = gpg_curve2oid(G_gpg_vstate.ux_type, &oid_len); + memmove(attributes.value + 1, oid, oid_len); + attributes.length = 1 + oid_len; + break; + + case CX_CURVE_Ed25519: + if (G_gpg_vstate.ux_key == 2) { + attributes.value[0] = KEY_ID_ECDH; + memmove(attributes.value + 1, C_OID_cv25519, sizeof(C_OID_cv25519)); + attributes.length = 1 + sizeof(C_OID_cv25519); + } else { + attributes.value[0] = KEY_ID_EDDSA; + memmove(attributes.value + 1, C_OID_Ed25519, sizeof(C_OID_Ed25519)); + attributes.length = 1 + sizeof(C_OID_Ed25519); + } + break; + + default: + break; } - if (entry == &ui_menu_template[1]) { - switch (G_gpg_vstate.ux_type) { - case 2048: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_RSA2048); - break; - case 3072: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_RSA3072); - break; - case 4096: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_RSA4096); - break; - - case CX_CURVE_SECP256R1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_NISTP256); - break; - /* - case CX_CURVE_SECP384R1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)," %s", LABEL_NISTP384); - break; - case CX_CURVE_SECP521R1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)," %s", LABEL_NISTP521); - break; - case CX_CURVE_SECP256K1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)," %s", LABEL_SECP256K1); - break; - */ - /* - case CX_CURVE_BrainPoolP256R1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)," %s", LABEL_BPOOL256R1); - break; - case CX_CURVE_BrainPoolP384R1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)," %s", LABEL_BPOOL384R1); - break; - case CX_CURVE_BrainPoolP512R1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)," %s", LABEL_BPOOL512R1); - break; - */ - - case CX_CURVE_Ed25519: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %s", LABEL_Ed25519); - break; - default: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Choose type..."); - break; - } - element->text = G_gpg_vstate.menu; + if (attributes.value[0] == 0) { + ui_info(INVALID_SELECTION, TEMPLATE_TYPE, ui_menu_template_display); + return; } - } - return element; -} -void ui_menu_tmpl_set_action(unsigned int value) { - LV(attributes, GPG_KEY_ATTRIBUTES_LENGTH); - gpg_key_t * dest; - const char * err; - const unsigned char *oid; - unsigned int oid_len; - err = NULL; - - os_memset(&attributes, 0, sizeof(attributes)); - switch (G_gpg_vstate.ux_type) { - case 2048: - case 3072: - case 4096: - attributes.value[0] = 0x01; - attributes.value[1] = (G_gpg_vstate.ux_type >> 8) & 0xFF; - attributes.value[2] = G_gpg_vstate.ux_type & 0xFF; - attributes.value[3] = 0x00; - attributes.value[4] = 0x20; - attributes.value[5] = 0x01; - attributes.length = 6; - break; - - case CX_CURVE_SECP256R1: - // case CX_CURVE_SECP256K1: - // case CX_CURVE_SECP384R1: - // case CX_CURVE_SECP521R1: - // case CX_CURVE_BrainPoolP256R1: - // case CX_CURVE_BrainPoolP384R1: - // case CX_CURVE_BrainPoolP512R1: - if (G_gpg_vstate.ux_key == 2) { - attributes.value[0] = 18; // ecdh - } else { - attributes.value[0] = 19; // ecdsa + switch (G_gpg_vstate.ux_key) { + case 1: + dest = &G_gpg_vstate.kslot->sig; + break; + case 2: + dest = &G_gpg_vstate.kslot->dec; + break; + case 3: + dest = &G_gpg_vstate.kslot->aut; + break; + default: + break; } - oid = gpg_curve2oid(G_gpg_vstate.ux_type, &oid_len); - os_memmove(attributes.value + 1, oid, sizeof(oid_len)); - attributes.length = 1 + oid_len; - break; - - case CX_CURVE_Ed25519: - if (G_gpg_vstate.ux_key == 2) { - attributes.value[0] = 18; // ecdh - os_memmove(attributes.value + 1, C_OID_cv25519, sizeof(C_OID_cv25519)); - attributes.length = 1 + sizeof(C_OID_cv25519); + + if (dest != NULL) { + nvm_write(dest, NULL, sizeof(gpg_key_t)); + nvm_write(&dest->attributes, &attributes, sizeof(attributes)); + ui_info(OK, EMPTY, ui_menu_template_display); } else { - attributes.value[0] = 22; // eddsa - os_memmove(attributes.value + 1, C_OID_Ed25519, sizeof(C_OID_Ed25519)); - attributes.length = 1 + sizeof(C_OID_Ed25519); + ui_info(INVALID_SELECTION, TEMPLATE_KEY, ui_menu_template_display); } - break; - - default: - err = TEMPLATE_TYPE; - goto ERROR; - } - - dest = NULL; - switch (G_gpg_vstate.ux_key) { - case 1: - dest = &G_gpg_vstate.kslot->sig; - break; - case 2: - dest = &G_gpg_vstate.kslot->dec; - break; - case 3: - dest = &G_gpg_vstate.kslot->aut; - break; - default: - err = TEMPLATE_KEY; - goto ERROR; - } - - gpg_nvm_write(dest, NULL, sizeof(gpg_key_t)); - gpg_nvm_write(&dest->attributes, &attributes, sizeof(attributes)); - ui_info(OK, NULL, ui_menu_template_display, 0); - return; - -ERROR: - ui_info(INVALID_SELECTION, err, ui_menu_template_display, 0); } -const ux_menu_entry_t ui_menu_tmpl_key[] = {{NULL, ui_menu_tmpl_key_action, 1, NULL, LABEL_SIG, NULL, 0, 0}, - {NULL, ui_menu_tmpl_key_action, 2, NULL, LABEL_DEC, NULL, 0, 0}, - {NULL, ui_menu_tmpl_key_action, 3, NULL, LABEL_AUT, NULL, 0, 0}, - {ui_menu_template, NULL, 0, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END}; - +/** + * Key Name Template Action callback + * + * @param[in] value selected key + * + */ void ui_menu_tmpl_key_action(unsigned int value) { - G_gpg_vstate.ux_key = value; - ui_menu_template_display(0); + G_gpg_vstate.ux_key = value; + ui_menu_template_display(0); } -const ux_menu_entry_t ui_menu_tmpl_type[] = { - {NULL, ui_menu_tmpl_type_action, 2048, NULL, LABEL_RSA2048, NULL, 0, 0}, - {NULL, ui_menu_tmpl_type_action, 3072, NULL, LABEL_RSA3072, NULL, 0, 0}, - {NULL, ui_menu_tmpl_type_action, 4096, NULL, LABEL_RSA4096, NULL, 0, 0}, - {NULL, ui_menu_tmpl_type_action, CX_CURVE_SECP256R1, NULL, LABEL_NISTP256, NULL, 0, 0}, - // {NULL, ui_menu_tmpl_type_action, CX_CURVE_SECP384R1, NULL, LABEL_NISTP384, NULL, 0, 0}, - // {NULL, ui_menu_tmpl_type_action, CX_CURVE_SECP521R1, NULL, LABEL_NISTP521, NULL, 0, 0}, - // {NULL, ui_menu_tmpl_type_action, CX_CURVE_SECP256K1, NULL, LABEL_SECP256K1, NULL, 0, 0}, - // {NULL, ui_menu_tmpl_type_action, CX_CURVE_BrainPoolP256R1, NULL, LABEL_BPOOL256R1, NULL, 0, 0}, - // {NULL, ui_menu_tmpl_type_action, CX_CURVE_BrainPoolP384R1, NULL, LABEL_BPOOL384R1, NULL, 0, 0}, - // {NULL, ui_menu_tmpl_type_action, CX_CURVE_BrainPoolP512R1, NULL, LABEL_BPOOL512R1, NULL, 0, 0}, - {NULL, ui_menu_tmpl_type_action, CX_CURVE_Ed25519, NULL, LABEL_Ed25519, NULL, 0, 0}, - {ui_menu_template, NULL, 0, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END}; - +/** + * Key Type Template Action callback + * + * @param[in] value selected key + * + */ void ui_menu_tmpl_type_action(unsigned int value) { - G_gpg_vstate.ux_type = value; - ui_menu_template_display(1); + G_gpg_vstate.ux_type = value; + ui_menu_template_display(1); } /* --------------------------------- SEED UX --------------------------------- */ -const ux_menu_entry_t ui_menu_seed[] = {{NULL, NULL, 0, NULL, "", NULL, 0, 0}, - {NULL, ui_menu_seed_action, 1, NULL, "Set on", NULL, 0, 0}, - {NULL, ui_menu_seed_action, 0, NULL, "Set off", NULL, 0, 0}, - {ui_menu_settings, NULL, 0, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END}; +const ux_menu_entry_t ui_menu_seedmode[] = { + {NULL, NULL, 0, NULL, "", NULL, 0, 0}, + {NULL, ui_menu_seedmode_action, 1, NULL, "Set on", NULL, 0, 0}, + {NULL, ui_menu_seedmode_action, 0, NULL, "Set off", NULL, 0, 0}, + {ui_menu_settings, NULL, 0, &C_icon_back, "Back", NULL, 61, 40}, + UX_MENU_END}; -void ui_menu_seed_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_seed, ui_menu_seed_preprocessor); +/** + * Seed Mode page display preparation callback + * + * @param[in] entry selected menu to display + * @param[in] element selected element to display + * + * @return Eelement to display + * + */ +const bagl_element_t *ui_menu_seedmode_predisplay(const ux_menu_entry_t *entry, + bagl_element_t *element) { + if (element->component.userid == 0x20) { + if (entry == &ui_menu_seedmode[0]) { + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "< %s >", + G_gpg_vstate.seed_mode ? "ON" : "OFF"); + element->text = G_gpg_vstate.menu; + } + } + return element; } -const bagl_element_t *ui_menu_seed_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element) { - if (element->component.userid == 0x20) { - if (entry == &ui_menu_seed[0]) { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "< %s >", G_gpg_vstate.seed_mode ? "ON" : "OFF"); - element->text = G_gpg_vstate.menu; +/** + * Seed Mode page display + * + * @param[in] value flow step + * + */ +void ui_menu_seedmode_display(unsigned int value) { + UX_MENU_DISPLAY(value, ui_menu_seedmode, ui_menu_seedmode_predisplay); +} + +/** + * Seed Mode toggle callback + * + * @param[in] value selected seed mode + * + */ +static void toggle_seed(unsigned int value) { + if (value != 128) { + return; } - } - return element; + if (G_gpg_vstate.seed_mode) { + G_gpg_vstate.seed_mode = 0; + } else { + G_gpg_vstate.seed_mode = 1; + } + ui_menu_seedmode_display(0); } -void ui_menu_seed_action(unsigned int value) { - G_gpg_vstate.seed_mode = value; - ui_menu_seed_display(0); +const ux_menu_entry_t ui_seed_warning[] = { + {NULL, NULL, -1, &C_icon_warning, "Warning", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "SEED mode", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "allows to", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "derive your", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "key from", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "Master SEED.", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "Without such", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "mode, an OS", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "or App update", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "will cause", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "your private", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "key to be", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "lost!", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "Are you sure", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "you want to", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "disable", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "SEED mode?", NULL, 0, 0}, + {NULL, ui_menu_seedmode_display, 0, &C_icon_back, "Cancel", NULL, 61, 40}, + {NULL, toggle_seed, 128, &C_icon_validate_14, "Disable", NULL, 0, 0}, + UX_MENU_END}; + +/** + * Seed Mode Action callback + * + * @param[in] value selected mode + * + */ +void ui_menu_seedmode_action(unsigned int value) { + if (value == 0) { + // Request deactivate + UX_MENU_DISPLAY(0, ui_seed_warning, NULL); + } else { + // Reactivate + G_gpg_vstate.seed_mode = 1; + ui_menu_seedmode_display(0); + } } /* ------------------------------- PIN MODE UX ------------------------------ */ -const ux_menu_entry_t ui_menu_pinmode[]; -void ui_menu_pinmode_display(unsigned int value); -const bagl_element_t *ui_menu_pinmode_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element); -void ui_menu_pinmode_action(unsigned int value); + +void ui_menu_pinmode_action(unsigned int value); +const bagl_element_t *ui_menu_pinmode_predisplay(const ux_menu_entry_t *entry, + bagl_element_t *element); const ux_menu_entry_t ui_menu_pinmode[] = { {NULL, NULL, -1, NULL, "Choose:", NULL, 0, 0}, - {NULL, ui_menu_pinmode_action, 0x8000 | PIN_MODE_HOST, NULL, "Host", NULL, 0, 0}, {NULL, ui_menu_pinmode_action, 0x8000 | PIN_MODE_SCREEN, NULL, "On Screen", NULL, 0, 0}, {NULL, ui_menu_pinmode_action, 0x8000 | PIN_MODE_CONFIRM, NULL, "Confirm only", NULL, 0, 0}, {NULL, ui_menu_pinmode_action, 0x8000 | PIN_MODE_TRUST, NULL, "Trust", NULL, 0, 0}, {NULL, ui_menu_pinmode_action, 128, NULL, "Set Default", NULL, 0, 0}, - {ui_menu_settings, NULL, 0, &C_badge_back, "Back", NULL, 61, 40}, + {ui_menu_settings, NULL, 0, &C_icon_back, "Back", NULL, 61, 40}, UX_MENU_END}; +/** + * Pin Mode page display + * + * @param[in] value flow step + * + */ void ui_menu_pinmode_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_pinmode, ui_menu_pinmode_preprocessor); + UX_MENU_DISPLAY(value, ui_menu_pinmode, ui_menu_pinmode_predisplay); } -const bagl_element_t *ui_menu_pinmode_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element) { - if (element->component.userid == 0x20) { - if ((entry->userid >= (0x8000 | PIN_MODE_HOST)) && (entry->userid <= (0x8000 | PIN_MODE_TRUST))) { - unsigned char id = entry->userid & 0x7FFFF; - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s %s %s", (char *)PIC(entry->line1), - id == N_gpg_pstate->config_pin[0] ? "#" : " ", /* default */ - id == G_gpg_vstate.pinmode ? "+" : " " /* selected*/); - element->text = G_gpg_vstate.menu; - element->component.height = 32; +/** + * Pin Mode page display preparation callback + * + * @param[in] entry selected menu to display + * @param[in] element selected element to display + * + * @return element to display + * + */ +const bagl_element_t *ui_menu_pinmode_predisplay(const ux_menu_entry_t *entry, + bagl_element_t *element) { + if (element->component.userid == 0x20) { + if ((entry->userid >= (0x8000 | PIN_MODE_SCREEN)) && + (entry->userid <= (0x8000 | PIN_MODE_TRUST))) { + unsigned char id = entry->userid & 0x7FFFF; + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "%s %s %s", + (char *) PIC(entry->line1), + id == N_gpg_pstate->config_pin[0] ? "#" : " ", /* default */ + id == G_gpg_vstate.pinmode ? "+" : " " /* selected*/); + element->text = G_gpg_vstate.menu; + element->component.height = 32; + } } - } - return element; + return element; } +const ux_menu_entry_t ui_trust_warning[] = { + {NULL, NULL, -1, &C_icon_warning, "Warning", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "TRUST mode", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "won't request", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "any more PINs", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "or validation", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "before", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "operations!", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "Are you sure", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "you want to", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "select", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "TRUST mode?", NULL, 0, 0}, + {NULL, ui_menu_pinmode_display, 3, &C_icon_back, "Cancel", NULL, 61, 40}, + {NULL, ui_menu_pinmode_action, 127, &C_icon_validate_14, "Select", NULL, 61, 40}, + UX_MENU_END}; + +/** + * Pin Mode Action callback + * + * @param[in] value selected mode + * + */ void ui_menu_pinmode_action(unsigned int value) { - unsigned char s; - value = value & 0x7FFF; - if (value == 128) { - if (G_gpg_vstate.pinmode != N_gpg_pstate->config_pin[0]) { - if (G_gpg_vstate.pinmode == PIN_MODE_TRUST) { - ui_info(DEFAULT_MODE, NOT_ALLOWED, ui_menu_pinmode_display, 0); - return; - } - // set new mode - s = G_gpg_vstate.pinmode; - gpg_nvm_write(&N_gpg_pstate->config_pin[0], &s, 1); - // disactivate pinpad if any - if (G_gpg_vstate.pinmode == PIN_MODE_HOST) { - s = 0; - } else { - s = 3; - } - //#warning USBD_CCID_activate_pinpad commented - USBD_CCID_activate_pinpad(s); - } - } else { - switch (value) { - case PIN_MODE_HOST: - case PIN_MODE_SCREEN: - case PIN_MODE_CONFIRM: - if (!gpg_pin_is_verified(PIN_ID_PW2)) { - ui_info(PIN_USER, NOT_VERIFIED, ui_menu_pinmode_display, 0); - return; - } - break; + unsigned char s; + value = value & 0x7FFF; - case PIN_MODE_TRUST: - if (!gpg_pin_is_verified(PIN_ID_PW3)) { - ui_info(PIN_ADMIN, NOT_VERIFIED, ui_menu_pinmode_display, 0); - return; - } - break; - default: - ui_info(INVALID_SELECTION, NULL, ui_menu_pinmode_display, 0); - return; + switch (value) { + case 128: + if (G_gpg_vstate.pinmode != N_gpg_pstate->config_pin[0]) { + if (G_gpg_vstate.pinmode == PIN_MODE_TRUST) { + ui_info(DEFAULT_MODE, NOT_ALLOWED, ui_menu_pinmode_display); + return; + } + // set new mode + s = G_gpg_vstate.pinmode; + nvm_write((void *) (&N_gpg_pstate->config_pin[0]), &s, 1); + gpg_activate_pinpad(3); + } + value = G_gpg_vstate.pinmode + 1; + break; + case PIN_MODE_SCREEN: + case PIN_MODE_CONFIRM: + if (value == G_gpg_vstate.pinmode) { + // Current selected mode + value++; + break; + } + if ((gpg_pin_is_verified(PIN_ID_PW1) == 0) && (gpg_pin_is_verified(PIN_ID_PW2) == 0)) { + ui_info(PIN_USER, NOT_VERIFIED, ui_menu_pinmode_display); + return; + } + G_gpg_vstate.pinmode = value; + value++; + break; + case PIN_MODE_TRUST: + if (value == G_gpg_vstate.pinmode) { + // Current selected mode + value++; + break; + } + if (!gpg_pin_is_verified(PIN_ID_PW3)) { + ui_info(PIN_ADMIN, NOT_VERIFIED, ui_menu_pinmode_display); + return; + } + // Confirm request + UX_MENU_DISPLAY(0, ui_trust_warning, NULL); + return; + case 127: + G_gpg_vstate.pinmode = PIN_MODE_TRUST; + value = PIN_MODE_TRUST + 1; + break; + default: + value = 0; + ui_info(INVALID_SELECTION, EMPTY, ui_menu_pinmode_display); + break; } - G_gpg_vstate.pinmode = value; - } - // redisplay first entry of the idle menu - ui_menu_pinmode_display(0); + ui_menu_pinmode_display(value); } /* ------------------------------- UIF MODE UX ------------------------------ */ -const ux_menu_entry_t ui_menu_uifmode[]; -void ui_menu_uifmode_display(unsigned int value); -const bagl_element_t *ui_menu_uifmode_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element); -void ui_menu_uifmode_action(unsigned int value); - -const ux_menu_entry_t ui_menu_uifmode[] = {{NULL, NULL, -1, NULL, "Activate (+) for:", NULL, 0, 0}, - {NULL, ui_menu_uifmode_action, 1, NULL, "Signature", NULL, 0, 0}, - {NULL, ui_menu_uifmode_action, 2, NULL, "Decryption", NULL, 0, 0}, - {NULL, ui_menu_uifmode_action, 3, NULL, "Authentication", NULL, 0, 0}, - {ui_menu_settings, NULL, 0, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END}; + +void ui_menu_uifmode_action(unsigned int value); +const bagl_element_t *ui_menu_uifmode_predisplay(const ux_menu_entry_t *entry, + bagl_element_t *element); + +const ux_menu_entry_t ui_menu_uifmode[] = { + {NULL, NULL, -1, NULL, "Activate (+) for:", NULL, 0, 0}, + {NULL, ui_menu_uifmode_action, 1, NULL, "Signature", NULL, 0, 0}, + {NULL, ui_menu_uifmode_action, 2, NULL, "Decryption", NULL, 0, 0}, + {NULL, ui_menu_uifmode_action, 3, NULL, "Authentication", NULL, 0, 0}, + {ui_menu_settings, NULL, 0, &C_icon_back, "Back", NULL, 61, 40}, + UX_MENU_END}; void ui_menu_uifmode_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_uifmode, ui_menu_uifmode_preprocessor); + UX_MENU_DISPLAY(value, ui_menu_uifmode, ui_menu_uifmode_predisplay); } -const bagl_element_t *ui_menu_uifmode_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element) { - if (element->component.userid == 0x20) { - if ((entry->userid >= 1) && (entry->userid <= 3)) { - unsigned char uif[2]; - uif[0] = 0; - uif[1] = 0; - switch (entry->userid) { - case 1: - *uif = G_gpg_vstate.kslot->sig.UIF[0] ? '+' : ' '; - break; - case 2: - *uif = G_gpg_vstate.kslot->dec.UIF[0] ? '+' : ' '; - break; - case 3: - *uif = G_gpg_vstate.kslot->aut.UIF[0] ? '+' : ' '; - break; - } - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s %s", (char *)PIC(entry->line1), uif); - element->text = G_gpg_vstate.menu; - element->component.height = 32; +/** + * UIF page display preparation callback + * + * @param[in] entry selected menu to display + * @param[in] element selected element to display + * + * @return element to display + * + */ +const bagl_element_t *ui_menu_uifmode_predisplay(const ux_menu_entry_t *entry, + bagl_element_t *element) { + if (element->component.userid == 0x20) { + if ((entry->userid >= 1) && (entry->userid <= 3)) { + unsigned char uif[2]; + uif[0] = 0; + uif[1] = 0; + switch (entry->userid) { + case 1: + *uif = G_gpg_vstate.kslot->sig.UIF[0] ? '+' : ' '; + break; + case 2: + *uif = G_gpg_vstate.kslot->dec.UIF[0] ? '+' : ' '; + break; + case 3: + *uif = G_gpg_vstate.kslot->aut.UIF[0] ? '+' : ' '; + break; + } + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "%s %s", + (char *) PIC(entry->line1), + uif); + element->text = G_gpg_vstate.menu; + element->component.height = 32; + } } - } - return element; + return element; } +/** + * UIF Action callback + * + * @param[in] value selected key + * + */ void ui_menu_uifmode_action(unsigned int value) { - unsigned char *uif; - unsigned char new_uif; - switch (value) { - case 1: - uif = &G_gpg_vstate.kslot->sig.UIF[0]; - break; - case 2: - uif = &G_gpg_vstate.kslot->dec.UIF[0]; - break; - case 3: - uif = &G_gpg_vstate.kslot->aut.UIF[0]; - break; - default: - ui_info(INVALID_SELECTION, NULL, ui_menu_uifmode_display, 0); - return; - } - if (uif[0] == 0) { - new_uif = 1; - gpg_nvm_write(&uif[0], &new_uif, 1); - } else if (uif[0] == 1) { - new_uif = 0; - gpg_nvm_write(&uif[0], &new_uif, 1); - } else /*if (uif[0] == 2 )*/ { - ui_info(UIF_LOCKED, NULL, ui_menu_uifmode_display, 0); - return; - } - ui_menu_uifmode_display(value); + unsigned char *uif; + unsigned char new_uif; + switch (value) { + case 1: + uif = &G_gpg_vstate.kslot->sig.UIF[0]; + break; + case 2: + uif = &G_gpg_vstate.kslot->dec.UIF[0]; + break; + case 3: + uif = &G_gpg_vstate.kslot->aut.UIF[0]; + break; + default: + ui_info(INVALID_SELECTION, EMPTY, ui_menu_uifmode_display); + return; + } + if (uif[0] == 0) { + new_uif = 1; + nvm_write(&uif[0], &new_uif, 1); + } else if (uif[0] == 1) { + new_uif = 0; + nvm_write(&uif[0], &new_uif, 1); + } else /*if (uif[0] == 2 )*/ { + ui_info(UIF_LOCKED, EMPTY, ui_menu_uifmode_display); + return; + } + ui_menu_uifmode_display(value); } /* -------------------------------- RESET UX --------------------------------- */ -const ux_menu_entry_t ui_menu_reset[] = {{NULL, NULL, 0, NULL, "Really Reset ?", NULL, 0, 0}, - {NULL, ui_menu_main_display, 0, &C_badge_back, "No", NULL, 61, 40}, - {NULL, ui_menu_reset_action, 0, NULL, "Yes", NULL, 0, 0}, - UX_MENU_END}; +const ux_menu_entry_t ui_menu_reset[] = { + {NULL, NULL, 0, NULL, "Really Reset ?", NULL, 0, 0}, + {NULL, ui_menu_main_display, 0, &C_icon_back, "No", NULL, 61, 40}, + {NULL, ui_menu_reset_action, 0, NULL, "Yes", NULL, 0, 0}, + UX_MENU_END}; +/** + * Reset Action callback + * + * @param[in] value unused + * + */ void ui_menu_reset_action(unsigned int value) { - unsigned char magic[4]; - magic[0] = 0; - magic[1] = 0; - magic[2] = 0; - magic[3] = 0; - gpg_nvm_write(N_gpg_pstate->magic, magic, 4); - gpg_init(); - ui_CCID_reset(); - ui_menu_main_display(0); -} - -/* ------------------------------ RESET KEY SLOT ----------------------------- */ -void ui_menu_reset_slot_action(unsigned int value); - -const ux_menu_entry_t ui_menu_reset_slot[] = {{NULL, NULL, 0, NULL, "Really Reset ?", NULL, 0, 0}, - {NULL, ui_menu_main_display, 0, &C_badge_back, "No", NULL, 61, 40}, - {NULL, ui_menu_reset_slot_action, 0, NULL, "Yes", NULL, 0, 0}, - UX_MENU_END}; + UNUSED(value); -void ui_menu_reset_slot_action(unsigned int value) { - gpg_install_slot(G_gpg_vstate.kslot); - ui_menu_main_display(0); + app_reset(); } - /* ------------------------------- SETTINGS UX ------------------------------- */ -const ux_menu_entry_t ui_menu_settings[] = {{NULL, ui_menu_template_display, 0, NULL, "Key template", NULL, 0, 0}, - {NULL, ui_menu_seed_display, 0, NULL, "Seed mode", NULL, 0, 0}, - {NULL, ui_menu_pinmode_display, 0, NULL, "PIN mode", NULL, 0, 0}, - {NULL, ui_menu_uifmode_display, 0, NULL, "UIF mode", NULL, 0, 0}, - {ui_menu_reset, NULL, 0, NULL, "Reset App", NULL, 0, 0}, - {ui_menu_reset_slot, NULL, 0, NULL, "Reset Slot", NULL, 0, 0}, - {NULL, ui_menu_main_display, 2, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END}; - -/* --------------------------------- SLOT UX --------------------------------- */ - -#if GPG_MULTISLOT -#if GPG_KEYS_SLOTS != 3 -#error menu definition not correct for current value of GPG_KEYS_SLOTS -#endif - -const ux_menu_entry_t ui_menu_slot[] = {{NULL, NULL, -1, NULL, "Choose:", NULL, 0, 0}, - {NULL, ui_menu_slot_action, 1, NULL, "", NULL, 0, 0}, - {NULL, ui_menu_slot_action, 2, NULL, "", NULL, 0, 0}, - {NULL, ui_menu_slot_action, 3, NULL, "", NULL, 0, 0}, - {NULL, ui_menu_slot_action, 128, NULL, "Set Default", NULL, 0, 0}, - {NULL, ui_menu_main_display, 1, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END}; -void ui_menu_slot_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_slot, ui_menu_slot_preprocessor); -} - -const bagl_element_t *ui_menu_slot_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element) { - unsigned int slot; - if (element->component.userid == 0x20) { - for (slot = 1; slot <= 3; slot++) { - if (entry == &ui_menu_slot[slot]) { - break; - } - } - if (slot != 4) { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Slot %d %s %s", slot, - slot == N_gpg_pstate->config_slot[1] + 1 ? "#" : " ", /* default */ - slot == G_gpg_vstate.slot + 1 ? "+" : " " /* selected*/); - element->text = G_gpg_vstate.menu; - } - } - return element; -} -void ui_menu_slot_action(unsigned int value) { - unsigned char s; - - if (value == 128) { - s = G_gpg_vstate.slot; - gpg_nvm_write(&N_gpg_pstate->config_slot[1], &s, 1); - value = s + 1; - } else { - s = (unsigned char)(value - 1); - if (s != G_gpg_vstate.slot) { - G_gpg_vstate.slot = s; - G_gpg_vstate.kslot = &N_gpg_pstate->keys[G_gpg_vstate.slot]; - gpg_mse_reset(); - ui_CCID_reset(); - } - } - // redisplay first entry of the idle menu - ui_menu_slot_display(value); -} -#endif +const ux_menu_entry_t ui_menu_settings[] = { + {NULL, ui_menu_template_display, 0, NULL, "Key template", NULL, 0, 0}, + {NULL, ui_menu_seedmode_display, 0, NULL, "Seed mode", NULL, 0, 0}, + {NULL, ui_menu_pinmode_display, 0, NULL, "PIN mode", NULL, 0, 0}, + {NULL, ui_menu_uifmode_display, 0, NULL, "UIF mode", NULL, 0, 0}, + {ui_menu_reset, NULL, 0, NULL, "Reset App", NULL, 0, 0}, + {NULL, ui_menu_main_display, 2, &C_icon_back, "Back", NULL, 61, 40}, + UX_MENU_END}; /* --------------------------------- INFO UX --------------------------------- */ -#define STR(x) #x -#define XSTR(x) STR(x) - -const ux_menu_entry_t ui_menu_info[] = {{NULL, NULL, -1, NULL, "OpenPGP Card", NULL, 0, 0}, - {NULL, NULL, -1, NULL, "(c) Ledger SAS", NULL, 0, 0}, - {NULL, NULL, -1, NULL, "Spec " XSTR(SPEC_VERSION), NULL, 0, 0}, - {NULL, NULL, -1, NULL, "App " XSTR(OPENPGP_VERSION), NULL, 0, 0}, - {NULL, ui_menu_main_display, 3, &C_badge_back, "Back", NULL, 61, 40}, - UX_MENU_END}; - -#undef STR -#undef XSTR +const ux_menu_entry_t ui_menu_info[] = { + {NULL, NULL, -1, NULL, "OpenPGP Card", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "(c) Ledger SAS", NULL, 0, 0}, + {NULL, NULL, -1, NULL, "Spec " XSTR(SPEC_VERSION), NULL, 0, 0}, +#ifdef HAVE_PRINTF + {NULL, NULL, -1, NULL, "[DBG] App " XSTR(APPVERSION), NULL, 0, 0}, +#else + {NULL, NULL, -1, NULL, "App " XSTR(APPVERSION), NULL, 0, 0}, +#endif + {NULL, ui_menu_main_display, 3, &C_icon_back, "Back", NULL, 61, 40}, + UX_MENU_END}; /* --------------------------------- MAIN UX --------------------------------- */ -const ux_menu_entry_t ui_menu_main[] = {{NULL, NULL, 0, NULL, "", "", 0, 0}, -#if GPG_MULTISLOT - {NULL, ui_menu_slot_display, 0, NULL, "Select slot", NULL, 0, 0}, -#endif - {ui_menu_settings, NULL, 0, NULL, "Settings", NULL, 0, 0}, - {ui_menu_info, NULL, 0, NULL, "About", NULL, 0, 0}, - {NULL, (void*)os_sched_exit, 0, &C_icon_dashboard, "Quit app", NULL, 50, 29}, - UX_MENU_END}; -extern const uint8_t N_USBD_CfgDesc[]; -const bagl_element_t *ui_menu_main_preprocessor(const ux_menu_entry_t *entry, bagl_element_t *element) { - if (entry == &ui_menu_main[0]) { - if (element->component.userid == 0x21) { - os_memset(G_gpg_vstate.menu, 0, sizeof(G_gpg_vstate.menu)); - os_memmove(G_gpg_vstate.menu, N_gpg_pstate->name.value, 12); - if (G_gpg_vstate.menu[0] == 0) { - os_memmove(G_gpg_vstate.menu, "", 9); - } else { - for (int i = 0; i < 12; i++) { - if (G_gpg_vstate.menu[i] == 0x3c) { - G_gpg_vstate.menu[i] = ' '; - } +const ux_menu_entry_t ui_menu_main[] = { + {NULL, NULL, 0, NULL, "", "", 0, 0}, + {ui_menu_settings, NULL, 0, NULL, "Settings", NULL, 0, 0}, + {ui_menu_info, NULL, 0, NULL, "About", NULL, 0, 0}, + {NULL, (void *) app_quit, 0, &C_icon_dashboard, "Quit app", NULL, 50, 29}, + UX_MENU_END}; + +/** + * Main page display preparation callback + * + * @param[in] entry selected menu to display + * @param[in] element selected element to display + * + * @return Eelement to display + * + */ +const bagl_element_t *ui_menu_main_predisplay(const ux_menu_entry_t *entry, + bagl_element_t *element) { + if (entry == &ui_menu_main[0]) { + explicit_bzero(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)); + if (element->component.userid == 0x21) { + memmove(G_gpg_vstate.menu, (void *) (N_gpg_pstate->name.value), 12); + if (G_gpg_vstate.menu[0] != 0) { + for (int i = 0; i < 12; i++) { + if ((G_gpg_vstate.menu[i] == '<') || (G_gpg_vstate.menu[i] == '>')) { + G_gpg_vstate.menu[i] = ' '; + } + } + } + } + if (element->component.userid == 0x22) { + unsigned int serial = U4BE(G_gpg_vstate.kslot->serial, 0); + explicit_bzero(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)); + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "ID: %x", serial); + } + if (G_gpg_vstate.menu[0] != 0) { + element->text = G_gpg_vstate.menu; } - } - } - if (element->component.userid == 0x22) { - unsigned int serial; - serial = (G_gpg_vstate.kslot->serial[0] << 24) | (G_gpg_vstate.kslot->serial[1] << 16) | - (G_gpg_vstate.kslot->serial[2] << 8) | (G_gpg_vstate.kslot->serial[3]); - os_memset(G_gpg_vstate.menu, 0, sizeof(G_gpg_vstate.menu)); -#if GPG_MULTISLOT - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "ID: %x / %d", serial, G_gpg_vstate.slot + 1); -#else - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "ID: %x", serial); -#endif } - element->text = G_gpg_vstate.menu; - } - return element; + return element; } +/** + * Main page display + * + * @param[in] value flow step + * + */ void ui_menu_main_display(unsigned int value) { - UX_MENU_DISPLAY(value, ui_menu_main, ui_menu_main_preprocessor); + UX_MENU_DISPLAY(value, ui_menu_main, ui_menu_main_predisplay); } -void ui_init(void) { - ui_menu_main_display(0); - // setup the first screen changing - UX_CALLBACK_SET_INTERVAL(1000); -} +/* --- INIT --- */ -void io_seproxyhal_display(const bagl_element_t *element) { - io_seproxyhal_display_default((bagl_element_t *)element); +/** + * home page definition + * + */ +void ui_init(void) { + ui_menu_main_display(0); + // setup the first screen changing + UX_CALLBACK_SET_INTERVAL(1000); } -#endif +#endif // defined(HAVE_BAGL) && defined(TARGET_NANOS) diff --git a/src/gpg_ux_nanos.h b/src/gpg_ux_nanos.h deleted file mode 100644 index cf33333..0000000 --- a/src/gpg_ux_nanos.h +++ /dev/null @@ -1,25 +0,0 @@ - -/* Copyright 2017 Cedric Mesnil , Ledger SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef GPG_UX_NANOS_H -#define GPG_UX_NANOS_H - -void ui_init(void); -void ui_main_display(unsigned int value); -void ui_menu_pinconfirm_display(unsigned int value); -void ui_menu_pinentry_display(unsigned int value); -void ui_menu_uifconfirm_display(unsigned int value); -#endif \ No newline at end of file diff --git a/src/gpg_ux_nanox.c b/src/gpg_ux_nanox.c index ab0429e..8280098 100644 --- a/src/gpg_ux_nanox.c +++ b/src/gpg_ux_nanox.c @@ -1,92 +1,73 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ -#ifdef UI_NANO_X +#include "bolos_target.h" +#if defined(HAVE_BAGL) && (defined(TARGET_NANOX) || defined(TARGET_NANOS2)) -#include "os.h" -#include "cx.h" -#include "gpg_types.h" -#include "gpg_api.h" #include "gpg_vars.h" - #include "gpg_ux_msg.h" -#include "os_io_seproxyhal.h" -#include "usbd_ccid_impl.h" -#include "string.h" -#include "glyphs.h" +#include "gpg_ux.h" +#include "usbd_ccid_if.h" /* ----------------------------------------------------------------------- */ -/* --- NanoS UI layout --- */ +/* --- NanoX UI layout --- */ /* ----------------------------------------------------------------------- */ -void ui_menu_settings_display(unsigned int value); -void ui_menu_template_display(unsigned int value); void ui_menu_tmpl_set_action(unsigned int value); void ui_menu_tmpl_key_action(unsigned int value); void ui_menu_tmpl_type_action(unsigned int value); - -void ui_menu_seed_display(unsigned int value); -void ui_menu_seed_action(unsigned int value); - +void ui_menu_seedmode_action(unsigned int value); void ui_menu_reset_action(unsigned int value); -#if GPG_MULTISLOT -void ui_menu_slot_display(unsigned int value); -void ui_menu_slot_action(unsigned int value); -#endif - +void ui_menu_settings_display(unsigned int value); void ui_menu_main_display(unsigned int value); - -void ui_menu_pinconfirm_action(unsigned int value); -unsigned int ui_pinconfirm_nanos_button(unsigned int button_mask, unsigned int button_mask_counter); -unsigned int ui_pinconfirm_prepro(const bagl_element_t *element); - -const bagl_element_t ui_pinentry_nanos[]; -void ui_menu_pinentry_display(unsigned int value); -void ui_menu_pinentry_action(unsigned int value); -unsigned int ui_pinentry_nanos_button(unsigned int button_mask, unsigned int button_mask_counter); -unsigned int ui_pinentry_prepro(const bagl_element_t *element); -static unsigned int validate_pin(); +unsigned int ui_pinentry_action_button(unsigned int button_mask, unsigned int button_mask_counter); /* ------------------------------- Helpers UX ------------------------------- */ -#define ui_flow_display(f, i) \ - if ((i) < ARRAYLEN(f)) \ - ux_flow_init(0, f, f[i]); \ - else \ - ux_flow_init(0, f, NULL) - -void ui_CCID_reset(void) { -#ifdef HAVE_USB_CLASS_CCID - io_usb_ccid_set_card_inserted(0); - io_usb_ccid_set_card_inserted(1); -#endif -} -UX_STEP_CB(ux_menu_popup_1_step, bnnn_paging, ui_menu_main_display(0), {.title = "Info", .text = G_gpg_vstate.menu}); +#define ui_flow_display(f, i) \ + if ((i) < ARRAYLEN(f)) \ + ux_flow_init(0, f, f[i]); \ + else \ + ux_flow_init(0, f, NULL) + +UX_STEP_CB(ux_menu_popup_1_step, + bnnn_paging, + ui_menu_main_display(0), + {.title = "Info", .text = G_gpg_vstate.menu}); UX_FLOW(ux_flow_popup, &ux_menu_popup_1_step); -void ui_info(const char *msg1, const char *msg2, const void *menu_display, unsigned int value) { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s. %s", msg1, msg2); - ux_flow_init(0, ux_flow_popup, NULL); +/** + * Display popup message on screen + * + * @param[in] msg1 1st part of the message + * @param[in] msg2 2nd part of the message + * + */ +void ui_info(const char *msg1, const char *msg2) { + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s. %s", msg1, msg2); + ux_flow_init(0, ux_flow_popup, NULL); }; /* ------------------------------ UIF CONFIRM UX ----------------------------- */ unsigned int ui_uifconfirm_action(unsigned int value); -void ui_menu_uifconfirm_predisplay(void); +void ui_menu_uifconfirm_predisplay(void); UX_STEP_NOCB_INIT(ux_menu_uifconfirm_1_step, nnn, @@ -97,80 +78,92 @@ UX_STEP_CB(ux_menu_uifconfirm_2_step, pb, ui_uifconfirm_action(0), {&C_icon_cros UX_STEP_CB(ux_menu_uifconfirm_3_step, pb, ui_uifconfirm_action(1), {&C_icon_validate_14, "Yes"}); -UX_FLOW(ux_flow_uifconfirm, &ux_menu_uifconfirm_1_step, &ux_menu_uifconfirm_3_step, &ux_menu_uifconfirm_2_step); +UX_FLOW(ux_flow_uifconfirm, + &ux_menu_uifconfirm_1_step, + &ux_menu_uifconfirm_3_step, + &ux_menu_uifconfirm_2_step); +/** + * UIF page display preparation callback + * + */ void ui_menu_uifconfirm_predisplay() { - unsigned int uif_case = (G_gpg_vstate.io_ins << 16) | (G_gpg_vstate.io_p1 << 8) | (G_gpg_vstate.io_p2); - switch (uif_case) { - case 0x002A9E9A: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Signature"); - break; - case 0x002A8680: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Encryption"); - break; - case 0x002A8086: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Decryption"); - break; - case 0x00880000: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Authentication"); - break; - default: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Please Cancel"); - } + switch (G_gpg_vstate.io_ins) { + case INS_INTERNAL_AUTHENTICATE: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Authentication"); + break; + case INS_PSO: + switch (G_gpg_vstate.io_p1p2) { + case PSO_CDS: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Signature"); + break; + case PSO_ENC: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Encryption"); + break; + case PSO_DEC: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Decryption"); + break; + default: + break; + } + break; + default: + break; + } + if (G_gpg_vstate.menu[0] == 0) { + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Please Cancel"); + } } +/** + * UIF page display + * + * @param[in] value flow step + * + */ void ui_menu_uifconfirm_display(unsigned int value) { - ui_flow_display(ux_flow_uifconfirm, value); + ui_flow_display(ux_flow_uifconfirm, value); } +/** + * UIF Confirmation Action callback + * + * @param[in] confirm indicate if the user press 'Confirm' or 'Cancel' + * + * @return Error code + * + */ unsigned int ui_uifconfirm_action(unsigned int value) { - unsigned int sw; + unsigned int sw = SW_SECURITY_UIF_ISSUE; - sw = 0x6985; - if (value == 1) { - BEGIN_TRY { - TRY { + if (value == 1) { G_gpg_vstate.UIF_flags = 1; - if (G_gpg_vstate.io_ins == INS_PSO) { - sw = gpg_apdu_pso(); - } else if (G_gpg_vstate.io_ins == INS_INTERNAL_AUTHENTICATE) { - sw = gpg_apdu_internal_authenticate(); - } else { - gpg_io_discard(1); - sw = 0x6985; + switch (G_gpg_vstate.io_ins) { + case INS_PSO: + sw = gpg_apdu_pso(); + break; + case INS_INTERNAL_AUTHENTICATE: + sw = gpg_apdu_internal_authenticate(); + break; + default: + gpg_io_discard(1); + sw = SW_CONDITIONS_NOT_SATISFIED; + break; } - } - CATCH_OTHER(e) { - gpg_io_discard(1); - if ((e & 0xFFFF0000) || (((e & 0xF000) != 0x6000) && ((e & 0xF000) != 0x9000))) { - gpg_io_insert_u32(e); - sw = 0x6f42; - } else { - sw = e; - } - } - FINALLY { G_gpg_vstate.UIF_flags = 0; - gpg_io_insert_u16(sw); - gpg_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); - } + } else { + gpg_io_discard(1); } - END_TRY; - } else { - gpg_io_discard(1); gpg_io_insert_u16(sw); gpg_io_do(IO_RETURN_AFTER_TX); ui_menu_main_display(0); - sw = 0x6985; - } - return 0; + return 0; } /* ------------------------------ PIN CONFIRM UX ----------------------------- */ + unsigned int ui_pinconfirm_action(unsigned int value); -void ui_menu_pinconfirm_predisplay(void); -void ui_menu_pinconfirm_display(unsigned int value); +void ui_menu_pinconfirm_predisplay(void); UX_STEP_NOCB_INIT(ux_menu_pinconfirm_1_step, nnn, @@ -193,327 +186,423 @@ UX_STEP_CB(ux_menu_pinconfirm_3_step, "Yes", }); -UX_FLOW(ux_flow_pinconfirm, &ux_menu_pinconfirm_1_step, &ux_menu_pinconfirm_2_step, &ux_menu_pinconfirm_3_step); +UX_FLOW(ux_flow_pinconfirm, + &ux_menu_pinconfirm_1_step, + &ux_menu_pinconfirm_2_step, + &ux_menu_pinconfirm_3_step); +/** + * Pin Confirm page display preparation callback + * + */ void ui_menu_pinconfirm_predisplay() { - if ((G_gpg_vstate.io_p2 == 0x81) || (G_gpg_vstate.io_p2 == 0x82) || (G_gpg_vstate.io_p2 == 0x83)) { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s %x", G_gpg_vstate.io_p2 == 0x83 ? "Admin" : "User", - G_gpg_vstate.io_p2); - } else { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Please Cancel"); - } + if ((G_gpg_vstate.io_p2 == PIN_ID_PW1) || (G_gpg_vstate.io_p2 == PIN_ID_PW2) || + (G_gpg_vstate.io_p2 == PIN_ID_PW3)) { + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "%s %x", + G_gpg_vstate.io_p2 == PIN_ID_PW3 ? "Admin" : "User", + G_gpg_vstate.io_p2); + } else { + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Please Cancel"); + } } +/** + * Pin Confirm page display + * + * @param[in] value flow step + * + */ void ui_menu_pinconfirm_display(unsigned int value) { - ux_flow_init(0, ux_flow_pinconfirm, NULL); + UNUSED(value); + ux_flow_init(0, ux_flow_pinconfirm, NULL); } +/** + * Pin Confirm Confirmation Action callback + * + * @param[in] confirm indicate if the user press 'Confirm' or 'Cancel' + * + * @return Error code + * + */ unsigned int ui_pinconfirm_action(unsigned int value) { - unsigned int sw; - - sw = 0x6985; - if (value == 1) { - gpg_pin_set_verified(G_gpg_vstate.io_p2, 1); - sw = 0x9000; - } else { - gpg_pin_set_verified(G_gpg_vstate.io_p2, 0); - sw = 0x6985; - } - gpg_io_discard(0); - gpg_io_insert_u16(sw); - gpg_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); - return 0; + unsigned int sw = SW_UNKNOWN; + + if (value == 1) { + gpg_pin_set_verified(G_gpg_vstate.io_p2, 1); + sw = SW_OK; + } else { + gpg_pin_set_verified(G_gpg_vstate.io_p2, 0); + sw = SW_CONDITIONS_NOT_SATISFIED; + } + gpg_io_discard(0); + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_menu_main_display(0); + return 0; } /* ------------------------------- PIN ENTRY UX ------------------------------ */ -const bagl_element_t ui_pinentry_nanos[] = { - // type userid x y w h str rad fill fg bg font_id icon_id +const bagl_element_t ui_pinentry_action[] = { + // type userid x y w h str rad fill fg bg + // font_id icon_id - // clrar screen - {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 64, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, NULL, 0, 0, 0, NULL, NULL, NULL}, + // clear screen + {{BAGL_RECTANGLE, 0x00, 0, 0, 128, BAGL_HEIGHT, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, + NULL}, // left/rights icons {{BAGL_ICON, 0x00, 0, 30, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, 0}, - (const char *)&C_icon_down, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + (const char *) &C_icon_down}, {{BAGL_ICON, 0x00, 120, 30, 7, 4, 0, 0, 0, 0xFFFFFF, 0x000000, 0, 0}, - (const char *)&C_icon_up, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + (const char *) &C_icon_up}, // PIN text identifier - {{BAGL_LABELINE, 0x01, 10, 25, 117, 15, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - G_gpg_vstate.menu, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + {{BAGL_LABELINE, + 0x01, + 10, + 25, + 117, + 15, + 0, + 0, + 0, + 0xFFFFFF, + 0x000000, + BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, + 0}, + G_gpg_vstate.menu}, // PIN Value - {{BAGL_LABELINE, 0x02, 10, 45, 117, 15, 0, 0, 0, 0xFFFFFF, 0x000000, - BAGL_FONT_OPEN_SANS_LIGHT_16px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - G_gpg_vstate.menu, - 0, - 0, - 0, - NULL, - NULL, - NULL}, -}; -static const char C_pin_digit[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '<', 'A', 'V'}; + {{BAGL_LABELINE, + 0x02, + 10, + 45, + 117, + 15, + 0, + 0, + 0, + 0xFFFFFF, + 0x000000, + BAGL_FONT_OPEN_SANS_LIGHT_16px | BAGL_FONT_ALIGNMENT_CENTER, + 0}, + G_gpg_vstate.menu}}; -void ui_menu_pinentry_display(unsigned int value) { - if (value == 0) { - os_memset(G_gpg_vstate.ux_pinentry, 0, sizeof(G_gpg_vstate.ux_pinentry)); - G_gpg_vstate.ux_pinentry[0] = 1; - G_gpg_vstate.ux_pinentry[1] = 5; - } - UX_DISPLAY(ui_pinentry_nanos, (void *)ui_pinentry_prepro); -} +static const char C_pin_digit[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '<', 'A', 'V'}; -unsigned int ui_pinentry_prepro(const bagl_element_t *element) { - if (element->component.userid == 1) { - if (G_gpg_vstate.io_ins == 0x24) { - switch (G_gpg_vstate.io_p1) { - case 0: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Current %s PIN", - (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - break; - case 1: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "New %s PIN", - (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - break; - case 2: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Confirm %s PIN", - (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - break; - default: - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "WAT %s PIN", - (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - break; - } - } else { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s PIN", (G_gpg_vstate.io_p2 == 0x83) ? "Admin" : "User"); - } - } else if (element->component.userid == 2) { - unsigned int i; - G_gpg_vstate.menu[0] = ' '; -#if 0 - for (i = 1; i <= G_gpg_vstate.ux_pinentry[0]; i++) { - G_gpg_vstate.menu[i] = C_pin_digit[G_gpg_vstate.ux_pinentry[i]]; - } -#else - for (i = 1; i < G_gpg_vstate.ux_pinentry[0]; i++) { - G_gpg_vstate.menu[i] = '*'; - } - G_gpg_vstate.menu[i] = C_pin_digit[G_gpg_vstate.ux_pinentry[i]]; - i++; -#endif - for (; i <= GPG_MAX_PW_LENGTH; i++) { - G_gpg_vstate.menu[i] = '-'; +/** + * Pin Entry page display preparation callback + * + * @param[in] element selected element to display + * + * @return Error code + * + */ +unsigned int ui_pinentry_predisplay(const bagl_element_t *element) { + if (element->component.userid == 1) { + if (G_gpg_vstate.io_ins == INS_CHANGE_REFERENCE_DATA) { + switch (G_gpg_vstate.io_p1) { + case 0: + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "Current %s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + break; + case 1: + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "New %s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + break; + case 2: + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "Confirm %s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + break; + default: + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "WAT %s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + break; + } + } else { + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "%s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + } + } else if (element->component.userid == 2) { + unsigned int i; + for (i = 0; i < G_gpg_vstate.ux_pinLen; i++) { + G_gpg_vstate.menu[i] = '*'; + } + G_gpg_vstate.menu[i] = C_pin_digit[G_gpg_vstate.ux_pinentry[i]]; + i++; + for (; i <= GPG_MAX_PW_LENGTH; i++) { + G_gpg_vstate.menu[i] = '-'; + } + G_gpg_vstate.menu[i] = 0; } - G_gpg_vstate.menu[i] = 0; - } - return 1; + return 1; } -unsigned int ui_pinentry_nanos_button(unsigned int button_mask, unsigned int button_mask_counter) { - unsigned int offset = G_gpg_vstate.ux_pinentry[0]; - unsigned m_pinentry; - char digit; +/** + * Pin Entry page display + * + * @param[in] value indicate if pin is reset + * + */ +void ui_menu_pinentry_display(unsigned int value) { + if (value == 0) { + explicit_bzero(G_gpg_vstate.ux_pinentry, sizeof(G_gpg_vstate.ux_pinentry)); + G_gpg_vstate.ux_pinLen = 0; + G_gpg_vstate.ux_pinentry[0] = 5; + } + UX_DISPLAY(ui_pinentry_action, (void *) ui_pinentry_predisplay); +} - m_pinentry = 1; +/** + * Pin Entry Validation callback + * + */ +static void validate_pin() { + unsigned int offset, len, sw = SW_UNKNOWN; + gpg_pin_t *pin; - switch (button_mask) { - case BUTTON_EVT_RELEASED | BUTTON_LEFT: // Down - if (G_gpg_vstate.ux_pinentry[offset]) { - G_gpg_vstate.ux_pinentry[offset]--; - } else { - G_gpg_vstate.ux_pinentry[offset] = sizeof(C_pin_digit) - 1; + for (offset = 0; offset <= G_gpg_vstate.ux_pinLen; offset++) { + G_gpg_vstate.menu[offset] = C_pin_digit[G_gpg_vstate.ux_pinentry[offset]]; } - ui_menu_pinentry_display(1); - break; - case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // up - G_gpg_vstate.ux_pinentry[offset]++; - if (G_gpg_vstate.ux_pinentry[offset] == sizeof(C_pin_digit)) { - G_gpg_vstate.ux_pinentry[offset] = 0; - } - ui_menu_pinentry_display(1); - break; - - case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: - digit = C_pin_digit[G_gpg_vstate.ux_pinentry[offset]]; - // next digit - if ((digit >= '0') && (digit <= '9')) { - offset++; - G_gpg_vstate.ux_pinentry[0] = offset; - if (offset == GPG_MAX_PW_LENGTH + 1) { - validate_pin(); - } else { - G_gpg_vstate.ux_pinentry[offset] = 5; - ui_menu_pinentry_display(1); - } - } - // cancel digit - else if (digit == '<') { - if (offset > 1) { - offset--; - G_gpg_vstate.ux_pinentry[0] = offset; - } - ui_menu_pinentry_display(1); - } - // validate pin - else if (digit == 'V') { - G_gpg_vstate.ux_pinentry[0] = offset - 1; - validate_pin(); + if (G_gpg_vstate.io_ins == INS_VERIFY) { + pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); + sw = gpg_pin_check(pin, + G_gpg_vstate.io_p2, + (unsigned char *) G_gpg_vstate.menu, + G_gpg_vstate.ux_pinLen); + gpg_io_discard(1); + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + if (sw != SW_OK) { + snprintf(G_gpg_vstate.ux_buff1, + sizeof(G_gpg_vstate.ux_buff1), + " %d tries remaining", + pin->counter); + ui_info(WRONG_PIN, G_gpg_vstate.ux_buff1); + } else { + ui_menu_main_display(0); + } } - // cancel input without check - else { //(digit == 'A') - gpg_io_discard(0); - gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); - gpg_io_do(IO_RETURN_AFTER_TX); - ui_menu_main_display(0); + + if (G_gpg_vstate.io_ins == INS_CHANGE_REFERENCE_DATA) { + if (G_gpg_vstate.io_p1 <= 2) { + gpg_io_insert_u8(G_gpg_vstate.ux_pinLen); + gpg_io_insert((unsigned char *) G_gpg_vstate.menu, G_gpg_vstate.ux_pinLen); + G_gpg_vstate.io_p1++; + } + if (G_gpg_vstate.io_p1 == 3) { + pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); + if (gpg_pin_check(pin, + G_gpg_vstate.io_p2, + G_gpg_vstate.work.io_buffer + 1, + G_gpg_vstate.work.io_buffer[0]) != SW_OK) { + gpg_io_discard(1); + gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); + gpg_io_do(IO_RETURN_AFTER_TX); + snprintf(G_gpg_vstate.ux_buff1, + sizeof(G_gpg_vstate.ux_buff1), + " %d tries remaining", + pin->counter); + ui_info(WRONG_PIN, G_gpg_vstate.ux_buff1); + return; + } + offset = 1 + G_gpg_vstate.work.io_buffer[0]; + len = G_gpg_vstate.work.io_buffer[offset]; + if ((len != G_gpg_vstate.work.io_buffer[offset + 1 + len]) || + (memcmp(G_gpg_vstate.work.io_buffer + offset + 1, + G_gpg_vstate.work.io_buffer + offset + 1 + len + 1, + len) != 0)) { + gpg_io_discard(1); + gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_info(PIN_DIFFERS, EMPTY); + } else { + sw = gpg_pin_set(gpg_pin_get_pin(G_gpg_vstate.io_p2), + G_gpg_vstate.work.io_buffer + offset + 1, + len); + gpg_io_discard(1); + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_menu_main_display(0); + } + } else { + ui_menu_pinentry_display(0); + } } - break; - } - return 0; } -// >= 0 -static unsigned int validate_pin() { - unsigned int offset, len, sw; - gpg_pin_t * pin; - - for (offset = 1; offset <= G_gpg_vstate.ux_pinentry[0]; offset++) { - G_gpg_vstate.menu[offset] = C_pin_digit[G_gpg_vstate.ux_pinentry[offset]]; - } - - if (G_gpg_vstate.io_ins == 0x20) { - pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); - sw = gpg_pin_check(pin, G_gpg_vstate.io_p2, (unsigned char *)(G_gpg_vstate.menu + 1), G_gpg_vstate.ux_pinentry[0]); - gpg_io_discard(1); - gpg_io_insert_u16(sw); - gpg_io_do(IO_RETURN_AFTER_TX); - if (sw != SW_OK) { - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %d tries remaining", pin->counter); - ui_info(WRONG_PIN, G_gpg_vstate.menu, ui_menu_main_display, 0); - } else { - ui_menu_main_display(0); - } - } - if (G_gpg_vstate.io_ins == 0x24) { - if (G_gpg_vstate.io_p1 <= 2) { - gpg_io_insert_u8(G_gpg_vstate.ux_pinentry[0]); - gpg_io_insert((unsigned char *)(G_gpg_vstate.menu + 1), G_gpg_vstate.ux_pinentry[0]); - G_gpg_vstate.io_p1++; - } - if (G_gpg_vstate.io_p1 == 3) { - pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); - if (gpg_pin_check(pin, G_gpg_vstate.io_p2, G_gpg_vstate.work.io_buffer + 1, G_gpg_vstate.work.io_buffer[0]) != - SW_OK) { - gpg_io_discard(1); - gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); - gpg_io_do(IO_RETURN_AFTER_TX); - snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), " %d tries remaining", pin->counter); - ui_info(WRONG_PIN, NULL, ui_menu_main_display, 0); - return 0; - } - offset = 1 + G_gpg_vstate.work.io_buffer[0]; - len = G_gpg_vstate.work.io_buffer[offset]; - if ((len != G_gpg_vstate.work.io_buffer[offset + 1 + len]) || - (os_memcmp(G_gpg_vstate.work.io_buffer + offset + 1, G_gpg_vstate.work.io_buffer + offset + 1 + len + 1, - len) != 0)) { - gpg_io_discard(1); - gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); - gpg_io_do(IO_RETURN_AFTER_TX); - ui_info(PIN_DIFFERS, NULL, ui_menu_main_display, 0); - } else { - gpg_pin_set(gpg_pin_get_pin(G_gpg_vstate.io_p2), G_gpg_vstate.work.io_buffer + offset + 1, len); - gpg_io_discard(1); - gpg_io_insert_u16(SW_OK); - gpg_io_do(IO_RETURN_AFTER_TX); - // ui_info(PIN_CHANGED, NULL, ui_menu_main_display, 0); - ui_menu_main_display(0); - } - return 0; - } else { - ui_menu_pinentry_display(0); +/** + * Pin Entry page Action callback + * + * @param[in] button_mask selected button + * @param[in] button_mask_counter unused + * + * @return Error code + * + */ +unsigned int ui_pinentry_action_button(unsigned int button_mask, unsigned int button_mask_counter) { + UNUSED(button_mask_counter); + unsigned int offset = G_gpg_vstate.ux_pinLen; + char digit; + + switch (button_mask) { + case BUTTON_EVT_RELEASED | BUTTON_LEFT: // Down + if (G_gpg_vstate.ux_pinentry[offset]) { + G_gpg_vstate.ux_pinentry[offset]--; + } else { + G_gpg_vstate.ux_pinentry[offset] = sizeof(C_pin_digit) - 1; + } + ui_menu_pinentry_display(1); + break; + + case BUTTON_EVT_RELEASED | BUTTON_RIGHT: // up + G_gpg_vstate.ux_pinentry[offset]++; + if (G_gpg_vstate.ux_pinentry[offset] == sizeof(C_pin_digit)) { + G_gpg_vstate.ux_pinentry[offset] = 0; + } + ui_menu_pinentry_display(1); + break; + + case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: + digit = C_pin_digit[G_gpg_vstate.ux_pinentry[offset]]; + // next digit + if ((digit >= '0') && (digit <= '9')) { + G_gpg_vstate.ux_pinLen = ++offset; + if (offset == GPG_MAX_PW_LENGTH) { + validate_pin(); + } else { + G_gpg_vstate.ux_pinentry[offset] = 5; + ui_menu_pinentry_display(1); + } + } + // cancel digit + else if (digit == '<') { + if (offset > 0) { + G_gpg_vstate.ux_pinLen--; + } + ui_menu_pinentry_display(1); + } + // validate pin + else if (digit == 'V') { + validate_pin(); + } + // cancel input without check + else { //(digit == 'A') + gpg_io_discard(0); + gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_menu_main_display(0); + } + break; } - } - return 0; + return 0; } /* ------------------------------- template UX ------------------------------- */ -#define LABEL_SIG "Signature" -#define LABEL_AUT "Authentication" -#define LABEL_DEC "Decryption" -#define LABEL_RSA2048 "RSA 2048" -#define LABEL_RSA3072 "RSA 3072" -#define LABEL_RSA4096 "RSA 4096" -#define LABEL_SECP256K1 "SEPC 256K1" -#define LABEL_Ed25519 "Ed25519" +void ui_menu_template_display(unsigned int value); const char *const tmpl_key_getter_values[] = {LABEL_SIG, LABEL_DEC, LABEL_AUT}; const unsigned int tmpl_key_getter_values_map[] = {1, 2, 3}; -const char *tmpl_key_getter(unsigned int idx) { - if (idx < ARRAYLEN(tmpl_key_getter_values)) { - return tmpl_key_getter_values[idx]; - } - return NULL; +/** + * Helper to get the key name + * + * @param[in] idx key index + * + * @return key name, or NULL if not found + * + */ +static const char *tmpl_key_getter(unsigned int idx) { + if (idx < ARRAYLEN(tmpl_key_getter_values)) { + return tmpl_key_getter_values[idx]; + } + return NULL; } -void tmpl_key_selector(unsigned int idx) { - if (idx < ARRAYLEN(tmpl_key_getter_values)) { - idx = tmpl_key_getter_values_map[idx]; - } else { - idx = 0; - } - G_gpg_vstate.ux_key = idx; - ui_menu_template_display(0); +/** + * Helper to select the key name and display the Template page + * + * @param[in] idx key index + * + */ +static void tmpl_key_selector(unsigned int idx) { + if (idx < ARRAYLEN(tmpl_key_getter_values)) { + idx = tmpl_key_getter_values_map[idx]; + } else { + idx = 0; + } + G_gpg_vstate.ux_key = idx; + ui_menu_template_display(0); } -const char *const tmpl_type_getter_values[] = {LABEL_RSA2048, LABEL_RSA3072, LABEL_RSA4096, LABEL_SECP256K1, +const char *const tmpl_type_getter_values[] = {LABEL_RSA2048, + LABEL_RSA3072, +#ifdef WITH_SUPPORT_RSA4096 + LABEL_RSA4096, +#endif + LABEL_SECP256K1, + LABEL_SECP256R1, LABEL_Ed25519}; -const unsigned int tmpl_type_getter_values_map[] = {2048, 3072, 4096, CX_CURVE_SECP256R1, CX_CURVE_Ed25519}; +const unsigned int tmpl_type_getter_values_map[] = {2048, + 3072, +#ifdef WITH_SUPPORT_RSA4096 + 4096, +#endif + CX_CURVE_SECP256K1, + CX_CURVE_SECP256R1, + CX_CURVE_Ed25519}; -const char *tmpl_type_getter(unsigned int idx) { - if (idx < ARRAYLEN(tmpl_type_getter_values)) { - return tmpl_type_getter_values[idx]; - } - return NULL; +/** + * Helper to get the key type + * + * @param[in] idx key index + * + * @return key type, or NULL if not found + * + */ +static const char *tmpl_type_getter(unsigned int idx) { + if (idx < ARRAYLEN(tmpl_type_getter_values)) { + return tmpl_type_getter_values[idx]; + } + return NULL; } -void tmpl_type_selector(unsigned int idx) { - if (idx < ARRAYLEN(tmpl_type_getter_values)) { - idx = tmpl_type_getter_values_map[idx]; - } else { - idx = 0; - } - G_gpg_vstate.ux_type = idx; - ui_menu_template_display(1); +/** + * Helper to select the key type and display the Template page + * + * @param[in] idx key index + * + */ +static void tmpl_type_selector(unsigned int idx) { + if (idx < ARRAYLEN(tmpl_type_getter_values)) { + idx = tmpl_type_getter_values_map[idx]; + } else { + idx = 0; + } + G_gpg_vstate.ux_type = idx; + ui_menu_template_display(1); } -#define KEY_KEY G_gpg_vstate.ux_buff1 +#define KEY_KEY G_gpg_vstate.ux_buff1 #define KEY_TYPE G_gpg_vstate.ux_buff2 void ui_menu_templet_action(); @@ -537,13 +626,16 @@ UX_STEP_CB_INIT(ux_menu_template_2_step, KEY_TYPE, }); -UX_STEP_CB(ux_menu_template_3_step, nnbnn, ui_menu_tmpl_set_action(0), {NULL, NULL, "Set Template", NULL, NULL}); +UX_STEP_CB(ux_menu_template_3_step, + nnbnn, + ui_menu_tmpl_set_action(0), + {NULL, NULL, "Set Template", NULL, NULL}); UX_STEP_CB(ux_menu_template_4_step, pb, ui_menu_settings_display(0), { - &C_icon_back, + &C_icon_back_x, "Back", }); @@ -553,214 +645,280 @@ UX_FLOW(ux_flow_template, &ux_menu_template_3_step, &ux_menu_template_4_step); +/** + * Template page display preparation callback + * + */ void ui_menu_template_predisplay() { - switch (G_gpg_vstate.ux_key) { - case 1: - snprintf(KEY_KEY, sizeof(KEY_KEY), "%s", LABEL_SIG); - break; - case 2: - snprintf(KEY_KEY, sizeof(KEY_KEY), "%s", LABEL_DEC); - break; - case 3: - snprintf(KEY_KEY, sizeof(KEY_KEY), "%s", LABEL_AUT); - break; - default: - snprintf(KEY_KEY, sizeof(KEY_KEY), "Choose key..."); - break; - } - - switch (G_gpg_vstate.ux_type) { - case 2048: - snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_RSA2048); - break; - case 3072: - snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_RSA3072); - break; - case 4096: - snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_RSA4096); - break; - case CX_CURVE_SECP256R1: - snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_SECP256K1); - break; - case CX_CURVE_Ed25519: - snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_Ed25519); - break; - default: - snprintf(KEY_TYPE, sizeof(KEY_TYPE), "Choose type..."); - break; - } + switch (G_gpg_vstate.ux_key) { + case 1: + snprintf(KEY_KEY, sizeof(KEY_KEY), "%s", LABEL_SIG); + break; + case 2: + snprintf(KEY_KEY, sizeof(KEY_KEY), "%s", LABEL_DEC); + break; + case 3: + snprintf(KEY_KEY, sizeof(KEY_KEY), "%s", LABEL_AUT); + break; + default: + snprintf(KEY_KEY, sizeof(KEY_KEY), "Choose key..."); + break; + } + + switch (G_gpg_vstate.ux_type) { + case 2048: + snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_RSA2048); + break; + case 3072: + snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_RSA3072); + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096: + snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_RSA4096); + break; +#endif + case CX_CURVE_SECP256K1: + snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_SECP256K1); + break; + case CX_CURVE_SECP256R1: + snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_SECP256R1); + break; + case CX_CURVE_Ed25519: + snprintf(KEY_TYPE, sizeof(KEY_TYPE), " %s", LABEL_Ed25519); + break; + default: + snprintf(KEY_TYPE, sizeof(KEY_TYPE), "Choose type..."); + break; + } } +/** + * Template page display + * + * @param[in] value flow step + * + */ void ui_menu_template_display(unsigned int value) { - ui_flow_display(ux_flow_template, value); + ui_flow_display(ux_flow_template, value); } +/** + * Template Action callback + * + * @param[in] value unused + */ void ui_menu_tmpl_set_action(unsigned int value) { - LV(attributes, GPG_KEY_ATTRIBUTES_LENGTH); - gpg_key_t * dest; - const char * err; - const unsigned char *oid; - unsigned int oid_len; - err = NULL; - - os_memset(&attributes, 0, sizeof(attributes)); - switch (G_gpg_vstate.ux_type) { - case 2048: - case 3072: - case 4096: - attributes.value[0] = 0x01; - attributes.value[1] = (G_gpg_vstate.ux_type >> 8) & 0xFF; - attributes.value[2] = G_gpg_vstate.ux_type & 0xFF; - attributes.value[3] = 0x00; - attributes.value[4] = 0x20; - attributes.value[5] = 0x01; - attributes.length = 6; - break; - - case CX_CURVE_SECP256R1: - if (G_gpg_vstate.ux_key == 2) { - attributes.value[0] = 18; // ecdh - } else { - attributes.value[0] = 19; // ecdsa + UNUSED(value); + LV(attributes, GPG_KEY_ATTRIBUTES_LENGTH); + gpg_key_t *dest = NULL; + const unsigned char *oid = NULL; + unsigned int oid_len; + + explicit_bzero(&attributes, sizeof(attributes)); + switch (G_gpg_vstate.ux_type) { + case 2048: + case 3072: +#ifdef WITH_SUPPORT_RSA4096 + case 4096: +#endif + attributes.value[0] = KEY_ID_RSA; + U2BE_ENCODE(attributes.value, 1, G_gpg_vstate.ux_type); + attributes.value[3] = 0x00; + attributes.value[4] = 0x20; + attributes.value[5] = 0x01; + attributes.length = 6; + break; + + case CX_CURVE_SECP256K1: + case CX_CURVE_SECP256R1: + oid = gpg_curve2oid(G_gpg_vstate.ux_type, &oid_len); + if (oid == NULL) { + break; + } + if (G_gpg_vstate.ux_key == 2) { + attributes.value[0] = KEY_ID_ECDH; + } else { + attributes.value[0] = KEY_ID_ECDSA; + } + memmove(attributes.value + 1, oid, oid_len); + attributes.length = 1 + oid_len; + break; + + case CX_CURVE_Ed25519: + if (G_gpg_vstate.ux_key == 2) { + attributes.value[0] = KEY_ID_ECDH; + memmove(attributes.value + 1, C_OID_cv25519, sizeof(C_OID_cv25519)); + attributes.length = 1 + sizeof(C_OID_cv25519); + } else { + attributes.value[0] = KEY_ID_EDDSA; + memmove(attributes.value + 1, C_OID_Ed25519, sizeof(C_OID_Ed25519)); + attributes.length = 1 + sizeof(C_OID_Ed25519); + } + break; + + default: + break; } - oid = gpg_curve2oid(G_gpg_vstate.ux_type, &oid_len); - os_memmove(attributes.value + 1, oid, sizeof(oid_len)); - attributes.length = 1 + oid_len; - break; - - case CX_CURVE_Ed25519: - if (G_gpg_vstate.ux_key == 2) { - attributes.value[0] = 18; // ecdh - os_memmove(attributes.value + 1, C_OID_cv25519, sizeof(C_OID_cv25519)); - attributes.length = 1 + sizeof(C_OID_cv25519); - } else { - attributes.value[0] = 22; // eddsa - os_memmove(attributes.value + 1, C_OID_Ed25519, sizeof(C_OID_Ed25519)); - attributes.length = 1 + sizeof(C_OID_Ed25519); + if (attributes.value[0] == 0) { + ui_info(INVALID_SELECTION, TEMPLATE_TYPE); + return; } - break; - - default: - err = TEMPLATE_TYPE; - goto ERROR; - } - - dest = NULL; - switch (G_gpg_vstate.ux_key) { - case 1: - dest = &G_gpg_vstate.kslot->sig; - break; - case 2: - dest = &G_gpg_vstate.kslot->dec; - break; - case 3: - dest = &G_gpg_vstate.kslot->aut; - break; - default: - err = TEMPLATE_KEY; - goto ERROR; - } - - gpg_nvm_write(dest, NULL, sizeof(gpg_key_t)); - gpg_nvm_write(&dest->attributes, &attributes, sizeof(attributes)); - ui_info(OK, NULL, ui_menu_template_display, 0); - return; - -ERROR: - ui_info(INVALID_SELECTION, err, ui_menu_template_display, 0); -} - -#undef KEY_KEY -#undef KEY_TYPE -#undef LABEL_SIG -#undef LABEL_AUT -#undef LABEL_DEC + switch (G_gpg_vstate.ux_key) { + case 1: + dest = &G_gpg_vstate.kslot->sig; + break; + case 2: + dest = &G_gpg_vstate.kslot->dec; + break; + case 3: + dest = &G_gpg_vstate.kslot->aut; + break; + default: + break; + } -#undef LABEL_RSA2048 -#undef LABEL_RSA3072 -#undef LABEL_RSA4096 -#undef LABEL_NISTP256 -#undef LABEL_SECP256K1 -#undef LABEL_Ed25519 + if (dest != NULL) { + nvm_write(dest, NULL, sizeof(gpg_key_t)); + nvm_write(&dest->attributes, &attributes, sizeof(attributes)); + ui_menu_template_display(1); + } else { + ui_info(INVALID_SELECTION, TEMPLATE_KEY); + } +} /* --------------------------------- SEED UX --------------------------------- */ + #define CUR_SEED_MODE G_gpg_vstate.ux_buff1 -void ui_menu_seed_action(unsigned int); -void ui_menu_seedmode_display(unsigned int); +void ui_menu_seedmode_action(unsigned int); void ui_menu_seedmode_predisplay(void); UX_STEP_CB_INIT(ux_menu_seedmode_1_step, bn, ui_menu_seedmode_predisplay(), - ui_menu_seed_action(0), + ui_menu_seedmode_action(G_gpg_vstate.seed_mode), {"Toggle seed mode", CUR_SEED_MODE}); UX_STEP_CB(ux_menu_seedmode_2_step, pb, ui_menu_settings_display(1), { - &C_icon_back, + &C_icon_back_x, "Back", }); UX_FLOW(ux_flow_seedmode, &ux_menu_seedmode_1_step, &ux_menu_seedmode_2_step); +/** + * Seed Mode page display preparation callback + * + */ void ui_menu_seedmode_predisplay() { - snprintf(CUR_SEED_MODE, sizeof(CUR_SEED_MODE), "%s", G_gpg_vstate.seed_mode ? "ON" : "OFF"); + snprintf(CUR_SEED_MODE, sizeof(CUR_SEED_MODE), "%s", G_gpg_vstate.seed_mode ? "ON" : "OFF"); } +/** + * Seed Mode page display + * + * @param[in] value flow step + * + */ void ui_menu_seedmode_display(unsigned int value) { - ui_flow_display(ux_flow_seedmode, value); + ui_flow_display(ux_flow_seedmode, value); } -void ui_menu_seed_action(unsigned int value) { - if (G_gpg_vstate.seed_mode) { - G_gpg_vstate.seed_mode = 0; - } else { - G_gpg_vstate.seed_mode = 1; - } - ui_menu_seedmode_display(0); +/** + * Seed Mode toggle callback + * + */ +static void toggle_seed() { + if (G_gpg_vstate.seed_mode) { + G_gpg_vstate.seed_mode = 0; + } else { + G_gpg_vstate.seed_mode = 1; + } + ui_menu_seedmode_display(0); } -#undef CUR_SEED_MODE +UX_STEP_NOCB(ui_seed_warning_step, + paging, + {.title = "Warning", + .text = "SEED mode allows to derive " + "your key from Master SEED.\n" + "Without such mode,\n" + "an OS or App update\n" + "will cause your private key to be lost!\n\n" + "Are you sure you want " + "to disable SEED mode?"}); + +UX_STEP_CB(ui_seed_warning_flow_cancel_step, + pb, + ui_menu_seedmode_display(0), + { + &C_icon_crossmark, + "Cancel", + }); + +UX_STEP_CB(ui_seed_disabling_flow_confirm_step, + pbb, + toggle_seed(), + { + &C_icon_validate_14, + "Disable", + "SEED Mode", + }); + +UX_FLOW(ui_seed_disabling_flow, + &ui_seed_warning_step, + &ui_seed_warning_flow_cancel_step, + &ui_seed_disabling_flow_confirm_step); + +/** + * Seed Mode Action callback + * + * @param[in] value seed mode state + * + */ +void ui_menu_seedmode_action(unsigned int value) { + if (value == 1) { + // Current value is 'enable' -> Confirm deactivate + ux_flow_init(0, ui_seed_disabling_flow, NULL); + } else { + // Current value is 'disable' -> Reactivate + G_gpg_vstate.seed_mode = 1; + ui_menu_seedmode_display(0); + } +} /* ------------------------------- PIN MODE UX ------------------------------ */ + void ui_menu_pinmode_action(unsigned int value); -void ui_menu_pinmode_display(unsigned int value); void ui_menu_pinmode_predisplay(void); -#define ONHST_BUFF G_gpg_vstate.ux_buff1 -#define ONSCR_BUFF G_gpg_vstate.ux_buff2 -#define CONFI_BUFF G_gpg_vstate.ux_buff3 -#define TRUST_BUFF G_gpg_vstate.ux_buff4 +#define ONSCR_BUFF G_gpg_vstate.ux_buff1 +#define CONFI_BUFF G_gpg_vstate.ux_buff2 +#define TRUST_BUFF G_gpg_vstate.ux_buff3 UX_STEP_CB_INIT(ux_menu_pinmode_1_step, - bnn, - ui_menu_pinmode_predisplay(), - ui_menu_pinmode_action(PIN_MODE_HOST), - {"On Host", ONHST_BUFF, ONHST_BUFF + 5}); - -UX_STEP_CB_INIT(ux_menu_pinmode_2_step, bnn, ui_menu_pinmode_predisplay(), ui_menu_pinmode_action(PIN_MODE_SCREEN), {"On Screen", ONSCR_BUFF, ONSCR_BUFF + 5}); -UX_STEP_CB_INIT(ux_menu_pinmode_3_step, +UX_STEP_CB_INIT(ux_menu_pinmode_2_step, bnn, ui_menu_pinmode_predisplay(), ui_menu_pinmode_action(PIN_MODE_CONFIRM), {"Confirm Only", CONFI_BUFF, CONFI_BUFF + 5}); -UX_STEP_CB_INIT(ux_menu_pinmode_4_step, +UX_STEP_CB_INIT(ux_menu_pinmode_3_step, bnn, ui_menu_pinmode_predisplay(), ui_menu_pinmode_action(PIN_MODE_TRUST), {"Trust", TRUST_BUFF, TRUST_BUFF + 5}); -UX_STEP_CB(ux_menu_pinmode_6_step, +UX_STEP_CB(ux_menu_pinmode_4_step, pb, ui_menu_pinmode_action(128), { @@ -768,11 +926,11 @@ UX_STEP_CB(ux_menu_pinmode_6_step, "Set as Default", }); -UX_STEP_CB(ux_menu_pinmode_7_step, +UX_STEP_CB(ux_menu_pinmode_5_step, pb, ui_menu_settings_display(2), { - &C_icon_back, + &C_icon_back_x, "Back", }); @@ -781,96 +939,130 @@ UX_FLOW(ux_flow_pinmode, &ux_menu_pinmode_2_step, &ux_menu_pinmode_3_step, &ux_menu_pinmode_4_step, - &ux_menu_pinmode_6_step, - &ux_menu_pinmode_7_step); + &ux_menu_pinmode_5_step); +/** + * Pin Mode page display preparation callback + * + */ void ui_menu_pinmode_predisplay() { - snprintf(ONHST_BUFF, 5, "%s", PIN_MODE_HOST == G_gpg_vstate.pinmode ? "ON" : "OFF"); - snprintf(ONSCR_BUFF, 5, "%s", PIN_MODE_SCREEN == G_gpg_vstate.pinmode ? "ON" : "OFF"); - snprintf(CONFI_BUFF, 5, "%s", PIN_MODE_CONFIRM == G_gpg_vstate.pinmode ? "ON" : "OFF"); - snprintf(TRUST_BUFF, 5, "%s", PIN_MODE_TRUST == G_gpg_vstate.pinmode ? "ON" : "OFF"); - - snprintf(ONHST_BUFF + 5, sizeof(ONHST_BUFF) - 5, "%s", - PIN_MODE_HOST == N_gpg_pstate->config_pin[0] ? "(Default)" : ""); - snprintf(ONSCR_BUFF + 5, sizeof(ONSCR_BUFF) - 5, "%s", - PIN_MODE_SCREEN == N_gpg_pstate->config_pin[0] ? "(Default)" : ""); - snprintf(CONFI_BUFF + 5, sizeof(CONFI_BUFF) - 5, "%s", - PIN_MODE_CONFIRM == N_gpg_pstate->config_pin[0] ? "(Default)" : ""); - snprintf(TRUST_BUFF + 5, sizeof(TRUST_BUFF) - 5, "%s", - PIN_MODE_TRUST == N_gpg_pstate->config_pin[0] ? "(Default)" : ""); + snprintf(ONSCR_BUFF, 5, "%s", PIN_MODE_SCREEN == G_gpg_vstate.pinmode ? "ON" : "OFF"); + snprintf(CONFI_BUFF, 5, "%s", PIN_MODE_CONFIRM == G_gpg_vstate.pinmode ? "ON" : "OFF"); + snprintf(TRUST_BUFF, 5, "%s", PIN_MODE_TRUST == G_gpg_vstate.pinmode ? "ON" : "OFF"); + + snprintf(ONSCR_BUFF + 5, + sizeof(ONSCR_BUFF) - 5, + "%s", + PIN_MODE_SCREEN == N_gpg_pstate->config_pin[0] ? "(Default)" : ""); + snprintf(CONFI_BUFF + 5, + sizeof(CONFI_BUFF) - 5, + "%s", + PIN_MODE_CONFIRM == N_gpg_pstate->config_pin[0] ? "(Default)" : ""); + snprintf(TRUST_BUFF + 5, + sizeof(TRUST_BUFF) - 5, + "%s", + PIN_MODE_TRUST == N_gpg_pstate->config_pin[0] ? "(Default)" : ""); } +/** + * Pin Mode page display + * + * @param[in] value flow step + * + */ void ui_menu_pinmode_display(unsigned int value) { - ui_flow_display(ux_flow_pinmode, value); + ui_flow_display(ux_flow_pinmode, value); } -#undef ONHST_BUFF -#undef ONSCR_BUFF -#undef CONFI_BUFF -#undef TRUST_BUFF +UX_STEP_NOCB(ui_trust_warning_step, + paging, + {.title = "Warning", + .text = "TRUST mode won't request any more PINs " + "or validation before operations!\n\n" + "Are you sure you want " + "to select TRUST mode?"}); +UX_STEP_CB(ui_trust_warning_flow_cancel_step, + pb, + ui_menu_pinmode_display(PIN_MODE_TRUST), + { + &C_icon_crossmark, + "Cancel", + }); + +UX_STEP_CB(ui_trust_selecting_flow_confirm_step, + pbb, + ui_menu_pinmode_action(127), + { + &C_icon_validate_14, + "Select", + "TRUST Mode", + }); + +UX_FLOW(ui_trust_selecting_flow, + &ui_trust_warning_step, + &ui_trust_warning_flow_cancel_step, + &ui_trust_selecting_flow_confirm_step); + +/** + * Pin Mode Action callback + * + * @param[in] value token indication the selected action + * + */ void ui_menu_pinmode_action(unsigned int value) { - unsigned char s; - if (value == 128) { - if (G_gpg_vstate.pinmode != N_gpg_pstate->config_pin[0]) { - if (G_gpg_vstate.pinmode == PIN_MODE_TRUST) { - ui_info(DEFAULT_MODE, NOT_ALLOWED, ui_menu_pinmode_display, 0); - return; - } - // set new mode - s = G_gpg_vstate.pinmode; - gpg_nvm_write((void *)(&N_gpg_pstate->config_pin[0]), &s, 1); - // disactivate pinpad if any - if (G_gpg_vstate.pinmode == PIN_MODE_HOST) { - s = 0; - } else { - s = 3; - } - //#warning USBD_CCID_activate_pinpad commented - USBD_CCID_activate_pinpad(s); - value = G_gpg_vstate.pinmode; - } - } else { - switch (value) { - case PIN_MODE_HOST: - case PIN_MODE_SCREEN: - case PIN_MODE_CONFIRM: - if (!gpg_pin_is_verified(PIN_ID_PW2)) { - ui_info(PIN_USER, NOT_VERIFIED, ui_menu_pinmode_display, 0); - return; - } - break; + unsigned char s; - case PIN_MODE_TRUST: - if (!gpg_pin_is_verified(PIN_ID_PW3)) { - ui_info(PIN_ADMIN, NOT_VERIFIED, ui_menu_pinmode_display, 0); - return; - } - break; - default: - ui_info(INVALID_SELECTION, NULL, ui_menu_pinmode_display, 0); - return; + switch (value) { + case 128: + if (G_gpg_vstate.pinmode != N_gpg_pstate->config_pin[0]) { + if (G_gpg_vstate.pinmode == PIN_MODE_TRUST) { + ui_info(DEFAULT_MODE, NOT_ALLOWED); + return; + } + // set new mode + s = G_gpg_vstate.pinmode; + nvm_write((void *) (&N_gpg_pstate->config_pin[0]), &s, 1); + gpg_activate_pinpad(3); + } + value = G_gpg_vstate.pinmode; + break; + case PIN_MODE_SCREEN: + case PIN_MODE_CONFIRM: + if (value == G_gpg_vstate.pinmode) { + // Current selected mode + break; + } + if ((gpg_pin_is_verified(PIN_ID_PW1) == 0) && (gpg_pin_is_verified(PIN_ID_PW2) == 0)) { + ui_info(PIN_USER, NOT_VERIFIED); + return; + } + G_gpg_vstate.pinmode = value; + break; + case PIN_MODE_TRUST: + if (value == G_gpg_vstate.pinmode) { + // Current selected mode + break; + } + if (!gpg_pin_is_verified(PIN_ID_PW3)) { + ui_info(PIN_ADMIN, NOT_VERIFIED); + return; + } + // Confirm request + ux_flow_init(0, ui_trust_selecting_flow, NULL); + return; + case 127: + G_gpg_vstate.pinmode = PIN_MODE_TRUST; + value = PIN_MODE_TRUST; + break; + default: + value = 0; + ui_info(INVALID_SELECTION, EMPTY); + break; } - G_gpg_vstate.pinmode = value; - } - // redisplay active pin mode entry - switch (value) { - case PIN_MODE_HOST: - ui_menu_pinmode_display(0); - break; - case PIN_MODE_SCREEN: - ui_menu_pinmode_display(1); - break; - case PIN_MODE_CONFIRM: - ui_menu_pinmode_display(2); - break; - case PIN_MODE_TRUST: - ui_menu_pinmode_display(3); - break; - default: - ui_menu_pinmode_display(0); - break; - } + + // redisplay active pin mode entry + ui_menu_pinmode_display(value); } /* ------------------------------- UIF MODE UX ------------------------------ */ @@ -904,145 +1096,168 @@ UX_STEP_CB(ux_menu_uif_4_step, pb, ui_menu_settings_display(3), { - &C_icon_back, + &C_icon_back_x, "Back", }); -UX_FLOW(ux_flow_uif, &ux_menu_uif_1_step, &ux_menu_uif_2_step, &ux_menu_uif_3_step, &ux_menu_uif_4_step); +UX_FLOW(ux_flow_uif, + &ux_menu_uif_1_step, + &ux_menu_uif_2_step, + &ux_menu_uif_3_step, + &ux_menu_uif_4_step); +/** + * UIF page display preparation callback + * + */ void ui_menu_uifmode_predisplay() { - snprintf(SIG_BUFF, sizeof(SIG_BUFF), "%s", G_gpg_vstate.kslot->sig.UIF[0] ? "ON" : "OFF"); - snprintf(DEC_BUFF, sizeof(DEC_BUFF), "%s", G_gpg_vstate.kslot->dec.UIF[0] ? "ON" : "OFF"); - snprintf(AUT_BUFF, sizeof(AUT_BUFF), "%s", G_gpg_vstate.kslot->aut.UIF[0] ? "ON" : "OFF"); + snprintf(SIG_BUFF, sizeof(SIG_BUFF), "%s", G_gpg_vstate.kslot->sig.UIF[0] ? "ON" : "OFF"); + snprintf(DEC_BUFF, sizeof(DEC_BUFF), "%s", G_gpg_vstate.kslot->dec.UIF[0] ? "ON" : "OFF"); + snprintf(AUT_BUFF, sizeof(AUT_BUFF), "%s", G_gpg_vstate.kslot->aut.UIF[0] ? "ON" : "OFF"); } +/** + * UIF page display + * + * @param[in] value flow step + * + */ void ui_menu_uifmode_display(unsigned int value) { - ui_flow_display(ux_flow_uif, value); + ui_flow_display(ux_flow_uif, value); } -#undef SIG_BUFF -#undef DEC_BUFF -#undef AUT_BUFF - +/** + * UIF Confirmation Action callback + * + * @param[in] value indicate the targeted key + * + */ void ui_menu_uifmode_action(unsigned int value) { - unsigned char *uif; - unsigned char new_uif; - switch (value) { - case 0: - uif = &G_gpg_vstate.kslot->sig.UIF[0]; - break; - case 1: - uif = &G_gpg_vstate.kslot->dec.UIF[0]; - break; - case 2: - uif = &G_gpg_vstate.kslot->aut.UIF[0]; - break; - default: - ui_info(INVALID_SELECTION, NULL, ui_menu_uifmode_display, 0); - return; - } - if (uif[0] == 0) { - new_uif = 1; - gpg_nvm_write(&uif[0], &new_uif, 1); - } else if (uif[0] == 1) { - new_uif = 0; - gpg_nvm_write(&uif[0], &new_uif, 1); - } else /*if (uif[0] == 2 )*/ { - ui_info(UIF_LOCKED, NULL, ui_menu_uifmode_display, 0); - return; - } - ui_menu_uifmode_display(value); + unsigned char *uif; + unsigned char new_uif; + switch (value) { + case 0: + uif = &G_gpg_vstate.kslot->sig.UIF[0]; + break; + case 1: + uif = &G_gpg_vstate.kslot->dec.UIF[0]; + break; + case 2: + uif = &G_gpg_vstate.kslot->aut.UIF[0]; + break; + default: + ui_info(INVALID_SELECTION, EMPTY); + return; + } + if (uif[0] == 0) { + new_uif = 1; + nvm_write(&uif[0], &new_uif, 1); + } else if (uif[0] == 1) { + new_uif = 0; + nvm_write(&uif[0], &new_uif, 1); + } else /*if (uif[0] == 2 )*/ { + ui_info(UIF_LOCKED, EMPTY); + return; + } + ui_menu_uifmode_display(value); } /* -------------------------------- RESET UX --------------------------------- */ void ui_menu_reset_action(unsigned int value); -UX_STEP_CB(ux_menu_reset_1_step, bnn, ui_menu_settings_display(4), {"Ooops, NO!", "Do not reset", "the application"}); +UX_STEP_CB(ux_menu_reset_1_step, + bnn, + ui_menu_settings_display(4), + {"Ooops, NO!", "Do not reset", "the application"}); UX_STEP_CB(ux_menu_reset_2_step, bn, ui_menu_reset_action(0), {"YES!", "Reset the application"}); UX_FLOW(ux_flow_reset, &ux_menu_reset_1_step, &ux_menu_reset_2_step); +/** + * Reset page display + * + * @param[in] value flow step + * + */ void ui_menu_reset_display(unsigned int value) { - ux_flow_init(value, ux_flow_reset, NULL); + ux_flow_init(value, ux_flow_reset, NULL); } +/** + * Reset Action callback + * + * @param[in] value unused + * + */ void ui_menu_reset_action(unsigned int value) { - unsigned char magic[4]; - magic[0] = 0; - magic[1] = 0; - magic[2] = 0; - magic[3] = 0; - gpg_nvm_write((void *)(N_gpg_pstate->magic), magic, 4); - gpg_init(); - ui_CCID_reset(); - ui_menu_main_display(0); -} + UNUSED(value); -/* ------------------------------ RESET SLOT UX ------------------------------ */ - -void ui_menu_reset_slot_action(unsigned int value); - -UX_STEP_CB(ux_menu_reset_slot_1_step, bnn, ui_menu_settings_display(4), {"Ooops, NO!", "Do not reset", "the key slot"}); - -UX_STEP_CB(ux_menu_reset_slot_2_step, bn, ui_menu_reset_slot_action(0), {"YES!", "Reset the slot"}); - -UX_FLOW(ux_flow_reset_slot, &ux_menu_reset_slot_1_step, &ux_menu_reset_slot_2_step); - -void ui_menu_reset_slot_display(unsigned int value) { - ux_flow_init(value, ux_flow_reset_slot, NULL); -} - -void ui_menu_reset_slot_action(unsigned int value) { - gpg_install_slot(G_gpg_vstate.kslot); - ui_menu_main_display(0); + app_reset(); } /* ------------------------------- SETTINGS UX ------------------------------- */ -const char *const settings_getter_values[] = {"Key template", "Seed mode", "PIN mode", "UIF mode", "Reset", "Back"}; +const char *const settings_getter_values[] = + {"Key template", "Seed mode", "PIN mode", "UIF mode", "Reset", "Back"}; +/** + * Helper to retrieve page title + * + * @param[in] idx page index + * + * @return page title, or NULL + * + */ const char *settings_getter(unsigned int idx) { - if (idx < ARRAYLEN(settings_getter_values)) { - return settings_getter_values[idx]; - } - return NULL; + if (idx < ARRAYLEN(settings_getter_values)) { + return settings_getter_values[idx]; + } + return NULL; } +/** + * Settings page display + * + * @param[in] idx page index + * + */ void settings_selector(unsigned int idx) { - switch (idx) { - case 0: - ui_menu_template_display(0); - break; - case 1: - ui_menu_seedmode_display(0); - break; - case 2: - ui_menu_pinmode_display(0); - break; - case 3: - ui_menu_uifmode_display(0); - break; - case 4: - ui_menu_reset_display(0); - break; - default: - ui_menu_main_display(1); - break; - } + switch (idx) { + case 0: + ui_menu_template_display(0); + break; + case 1: + ui_menu_seedmode_display(0); + break; + case 2: + ui_menu_pinmode_display(0); + break; + case 3: + ui_menu_uifmode_display(0); + break; + case 4: + ui_menu_reset_display(0); + break; + default: + ui_menu_main_display(1); + break; + } } + +/** + * Settings page display + * + * @param[in] value flow step + * + */ void ui_menu_settings_display(unsigned int value) { - ux_menulist_init_select(G_ux.stack_count - 1, settings_getter, settings_selector, value); + ux_menulist_init_select(G_ux.stack_count - 1, settings_getter, settings_selector, value); } /* --------------------------------- SLOT UX --------------------------------- */ -#if GPG_MULTISLOT -#if GPG_KEYS_SLOTS != 3 -#error menu definition not correct for current value of GPG_KEYS_SLOTS -#endif - void ui_menu_slot_action(unsigned int value); void ui_menu_slot_predisplay(void); @@ -1050,19 +1265,34 @@ void ui_menu_slot_predisplay(void); #define SLOT2 G_gpg_vstate.ux_buff2 #define SLOT3 G_gpg_vstate.ux_buff3 -UX_STEP_CB_INIT(ux_menu_slot_1_step, bn, ui_menu_slot_predisplay(), ui_menu_slot_action(1), {"Select Slot", SLOT1}); +UX_STEP_CB_INIT(ux_menu_slot_1_step, + bn, + ui_menu_slot_predisplay(), + ui_menu_slot_action(1), + {"Select Slot", SLOT1}); -UX_STEP_CB_INIT(ux_menu_slot_2_step, bn, ui_menu_slot_predisplay(), ui_menu_slot_action(2), {"Select Slot", SLOT2}); +UX_STEP_CB_INIT(ux_menu_slot_2_step, + bn, + ui_menu_slot_predisplay(), + ui_menu_slot_action(2), + {"Select Slot", SLOT2}); -UX_STEP_CB_INIT(ux_menu_slot_3_step, bn, ui_menu_slot_predisplay(), ui_menu_slot_action(3), {"Select Slot", SLOT3}); +UX_STEP_CB_INIT(ux_menu_slot_3_step, + bn, + ui_menu_slot_predisplay(), + ui_menu_slot_action(3), + {"Select Slot", SLOT3}); -UX_STEP_CB(ux_menu_slot_4_step, bn, ui_menu_slot_action(128), {"Set selected Slot", "as default slot"}); +UX_STEP_CB(ux_menu_slot_4_step, + bn, + ui_menu_slot_action(128), + {"Set selected Slot", "as default slot"}); - UX_STEP_CB(ux_menu_slot_5_step, +UX_STEP_CB(ux_menu_slot_5_step, pn, ui_menu_main_display(1), { - &C_icon_back, + &C_icon_back_x, "Back", }); @@ -1073,146 +1303,173 @@ UX_FLOW(ux_flow_slot, &ux_menu_slot_4_step, &ux_menu_slot_5_step); +/** + * Slot page display preparation callback + * + */ void ui_menu_slot_predisplay() { - snprintf(SLOT1, sizeof(SLOT1), "1 %s %s", 1 == N_gpg_pstate->config_slot[1] + 1 ? "#" : " ", - 1 == G_gpg_vstate.slot + 1 ? "+" : " "); - snprintf(SLOT2, sizeof(SLOT2), "2 %s %s", 2 == N_gpg_pstate->config_slot[1] + 1 ? "#" : " ", - 2 == G_gpg_vstate.slot + 1 ? "+" : " "); - snprintf(SLOT3, sizeof(SLOT3), "3 %s %s", 3 == N_gpg_pstate->config_slot[1] + 1 ? "#" : " ", - 3 == G_gpg_vstate.slot + 1 ? "+" : " "); + snprintf(SLOT1, + sizeof(SLOT1), + "1 %s %s", + 1 == N_gpg_pstate->config_slot[1] + 1 ? "#" : " ", + 1 == G_gpg_vstate.slot + 1 ? "+" : " "); + snprintf(SLOT2, + sizeof(SLOT2), + "2 %s %s", + 2 == N_gpg_pstate->config_slot[1] + 1 ? "#" : " ", + 2 == G_gpg_vstate.slot + 1 ? "+" : " "); + snprintf(SLOT3, + sizeof(SLOT3), + "3 %s %s", + 3 == N_gpg_pstate->config_slot[1] + 1 ? "#" : " ", + 3 == G_gpg_vstate.slot + 1 ? "+" : " "); } +/** + * Slot page display + * + * @param[in] value flow step + * + */ void ui_menu_slot_display(unsigned int value) { - ui_flow_display(ux_flow_slot, value); + ui_flow_display(ux_flow_slot, value); } -#undef SLOT1 -#undef SLOT2 -#undef SLOT3 - +/** + * Slot Action callback + * + * @param[in] value token indication the selected action + * + */ void ui_menu_slot_action(unsigned int value) { - unsigned char s; - - if (value == 128) { - s = G_gpg_vstate.slot; - gpg_nvm_write(&N_gpg_pstate->config_slot[1], &s, 1); - } else { - s = (unsigned char)(value - 1); - if (s != G_gpg_vstate.slot) { - G_gpg_vstate.slot = s; - G_gpg_vstate.kslot = &N_gpg_pstate->keys[G_gpg_vstate.slot]; - gpg_mse_reset(); - ui_CCID_reset(); + unsigned char s; + + if (value == 128) { + s = G_gpg_vstate.slot; + nvm_write((void *) (&N_gpg_pstate->config_slot[1]), &s, 1); + } else { + s = (unsigned char) (value - 1); + if (s != G_gpg_vstate.slot) { + G_gpg_vstate.slot = s; + G_gpg_vstate.kslot = (gpg_key_slot_t *) &N_gpg_pstate->keys[G_gpg_vstate.slot]; + gpg_mse_reset(); + ui_CCID_reset(); + } } - } - ui_menu_slot_display(G_gpg_vstate.slot); + ui_menu_slot_display(G_gpg_vstate.slot); } -#endif /* --------------------------------- INFO UX --------------------------------- */ -#define STR(x) #x -#define XSTR(x) STR(x) - UX_STEP_NOCB(ux_menu_info_1_step, bnnn, { "OpenPGP Card", "(c) Ledger SAS", "Spec " XSTR(SPEC_VERSION), - "App " XSTR(OPENPGP_VERSION), +#ifdef HAVE_PRINTF + "[DBG] App " XSTR(APPVERSION), +#else + "App " XSTR(APPVERSION), +#endif }); UX_STEP_CB(ux_menu_info_2_step, pb, ui_menu_main_display(0), { - &C_icon_back, + &C_icon_back_x, "Back", }); UX_FLOW(ux_flow_info, &ux_menu_info_1_step, &ux_menu_info_2_step); +/** + * Info page display + * + * @param[in] value flow step + * + */ void ui_menu_info_display(unsigned int value) { - ux_flow_init(0, ux_flow_info, NULL); + UNUSED(value); + ux_flow_init(0, ux_flow_info, NULL); } -#undef STR -#undef XSTR - /* --------------------------------- MAIN UX --------------------------------- */ + void ui_menu_main_predisplay(void); UX_STEP_NOCB_INIT(ux_menu_main_1_step, pnn, ui_menu_main_predisplay(), { - &C_icon_pgp, + &C_gpg_16px, G_gpg_vstate.ux_buff1, G_gpg_vstate.ux_buff2, }); -#if GPG_MULTISLOT UX_STEP_CB(ux_menu_main_2_step, pb, ui_menu_slot_display(0), {&C_icon_coggle, "Select Slot"}); -#endif UX_STEP_CB(ux_menu_main_3_step, pb, ui_menu_settings_display(0), {&C_icon_coggle, "Settings"}); UX_STEP_CB(ux_menu_main_4_step, pb, ui_menu_info_display(0), {&C_icon_certificate, "About"}); -UX_STEP_CB(ux_menu_main_5_step, pb, os_sched_exit(0), {&C_icon_dashboard_x, "Quit app"}); +UX_STEP_CB(ux_menu_main_5_step, pb, app_quit(), {&C_icon_dashboard_x, "Quit app"}); UX_FLOW(ux_flow_main, &ux_menu_main_1_step, -#if GPG_MULTISLOT &ux_menu_main_2_step, -#endif &ux_menu_main_3_step, &ux_menu_main_4_step, &ux_menu_main_5_step); +/** + * Main page display preparation callback + * + */ void ui_menu_main_predisplay() { - os_memset(G_gpg_vstate.ux_buff1, 0, sizeof(G_gpg_vstate.ux_buff1)); - os_memmove(G_gpg_vstate.ux_buff1, (void *)(N_gpg_pstate->name.value), 20); - if (G_gpg_vstate.ux_buff1[0] == 0) { - os_memmove(G_gpg_vstate.ux_buff1, "", 9); - } else { - for (int i = 0; i < 12; i++) { - if (G_gpg_vstate.ux_buff1[i] == 0x3c) { - G_gpg_vstate.ux_buff1[i] = ' '; - } + explicit_bzero(G_gpg_vstate.ux_buff1, sizeof(G_gpg_vstate.ux_buff1)); + memmove(G_gpg_vstate.ux_buff1, (void *) (N_gpg_pstate->name.value), 20); + if (G_gpg_vstate.ux_buff1[0] != 0) { + for (int i = 0; i < 12; i++) { + if ((G_gpg_vstate.menu[i] == '<') || (G_gpg_vstate.menu[i] == '>')) { + G_gpg_vstate.ux_buff1[i] = ' '; + } + } } - } - - unsigned int serial; - serial = (G_gpg_vstate.kslot->serial[0] << 24) | (G_gpg_vstate.kslot->serial[1] << 16) | - (G_gpg_vstate.kslot->serial[2] << 8) | (G_gpg_vstate.kslot->serial[3]); - os_memset(G_gpg_vstate.ux_buff2, 0, sizeof(G_gpg_vstate.ux_buff2)); -#if GPG_MULTISLOT - snprintf(G_gpg_vstate.ux_buff2, sizeof(G_gpg_vstate.ux_buff2), "ID: %x / %d", serial, G_gpg_vstate.slot + 1); -#else - snprintf(G_gpg_vstate.ux_buff2, sizeof(G_gpg_vstate.ux_buff2), "ID: %x", serial); -#endif + + unsigned int serial = U4BE(G_gpg_vstate.kslot->serial, 0); + explicit_bzero(G_gpg_vstate.ux_buff2, sizeof(G_gpg_vstate.ux_buff2)); + snprintf(G_gpg_vstate.ux_buff2, + sizeof(G_gpg_vstate.ux_buff2), + "ID: %x / %d", + serial, + G_gpg_vstate.slot + 1); } +/** + * Main page display + * + * @param[in] value flow step + * + */ void ui_menu_main_display(unsigned int value) { - // reserve a display stack slot if none yet - if (G_ux.stack_count == 0) { - ux_stack_push(); - } + // reserve a display stack slot if none yet + if (G_ux.stack_count == 0) { + ux_stack_push(); + } - ui_flow_display(ux_flow_main, value); + ui_flow_display(ux_flow_main, value); } + /* --- INIT --- */ +/** + * home page definition + * + */ void ui_init(void) { - ui_menu_main_display(0); -} - -void io_seproxyhal_display(const bagl_element_t *element) { - io_seproxyhal_display_default((bagl_element_t *)element); + ui_menu_main_display(0); } -///----- - -#endif // UI_NANOX +#endif // defined(HAVE_BAGL) && (defined(TARGET_NANOX) || defined(TARGET_NANOS2)) diff --git a/src/gpg_ux_nbgl.c b/src/gpg_ux_nbgl.c new file mode 100644 index 0000000..4a90294 --- /dev/null +++ b/src/gpg_ux_nbgl.c @@ -0,0 +1,1249 @@ + +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +#include "bolos_target.h" +#if defined(HAVE_NBGL) && defined(TARGET_STAX) + +#include "os.h" +#include "glyphs.h" +#include "nbgl_use_case.h" + +#include "gpg_vars.h" +#include "gpg_ux_msg.h" +#include "gpg_ux.h" +#include "usbd_ccid_if.h" + +/* ----------------------------------------------------------------------- */ +/* --- NBGL UI layout --- */ +/* ----------------------------------------------------------------------- */ +static void ui_menu_settings(); +static void ui_menu_slot_action(); +static void settings_ctrl_cb(int token, uint8_t index); +static void ui_settings_template(void); +static void ui_settings_seed(void); +static void ui_settings_pin(void); + +// context for background and modal pages +static nbgl_layout_t layoutCtx = {0}; + +/* ------------------------------- Helpers UX ------------------------------- */ + +/** + * Display popup message on screen + * + * @param[in] msg1 1st part of the message + * @param[in] msg2 2nd part of the message + * + */ +static void ui_info(const char* msg1, const char* msg2, nbgl_callback_t cb, bool isSuccess) { + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "%s\n%s", msg1, msg2); + + nbgl_useCaseStatus((const char*) G_gpg_vstate.menu, isSuccess, cb); +}; + +/** + * Display Setting page header + * + * @param[in] title page title + * @param[in] back_token token for back button + * @param[in] touch_cb action callback + * + */ +static void ui_setting_header(const char* title, + uint8_t back_token, + nbgl_layoutTouchCallback_t touch_cb) { + nbgl_layoutDescription_t layoutDescription = {0}; + nbgl_layoutBar_t bar = {0}; + + layoutDescription.onActionCallback = touch_cb; + layoutDescription.modal = false; + layoutCtx = nbgl_layoutGet(&layoutDescription); + + explicit_bzero(&bar, sizeof(nbgl_layoutBar_t)); + bar.text = PIC(title); + bar.iconLeft = &C_leftArrow32px; + bar.token = back_token; + bar.centered = true; + bar.inactive = false; + bar.tuneId = TUNE_TAP_CASUAL; + nbgl_layoutAddTouchableBar(layoutCtx, &bar); + nbgl_layoutAddSeparationLine(layoutCtx); +} + +// ----------------------------------------------------------- +// ----------------------- HOME PAGE ------------------------- +// ----------------------------------------------------------- + +/** + * home page definition + * + */ +void ui_init(void) { + char name[32]; + unsigned int serial = U4BE(G_gpg_vstate.kslot->serial, 0); + + explicit_bzero(name, sizeof(name)); + memmove(name, (void*) (N_gpg_pstate->name.value), 20); + if (name[0] != 0) { + for (int i = 0; i < 12; i++) { + if ((name[i] == '<') || (name[i] == '>')) { + name[i] = ' '; + } + } + } + explicit_bzero(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu)); + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "%s\nID: %x / %d", + name, + serial, + G_gpg_vstate.slot + 1); + + nbgl_useCaseHomeExt(APPNAME, + &C_gpg_64px, + G_gpg_vstate.menu, + true, + "Select Slot", + ui_menu_slot_action, + ui_menu_settings, + app_quit); +} + +// ----------------------------------------------------------- +// ------------------------ SLOT UX -------------------------- +// ----------------------------------------------------------- + +enum { + TOKEN_SLOT_SELECT = FIRST_USER_TOKEN, + TOKEN_SLOT_DEF, + TOKEN_SLOT_BACK, +}; + +/** + * Slot Action callback + * + * @param[in] token button Id pressed + * @param[in] index widget index on the page + * + */ +static void slot_cb(int token, uint8_t index) { + switch (token) { + case TOKEN_SLOT_BACK: + ui_init(); + break; + case TOKEN_SLOT_SELECT: + if (index != G_gpg_vstate.slot) { + G_gpg_vstate.slot = index; + G_gpg_vstate.kslot = (gpg_key_slot_t*) &N_gpg_pstate->keys[G_gpg_vstate.slot]; + gpg_mse_reset(); + ui_CCID_reset(); + } + break; + case TOKEN_SLOT_DEF: + nvm_write((void*) (&N_gpg_pstate->config_slot[1]), &G_gpg_vstate.slot, 1); + ui_menu_slot_action(); + break; + default: + break; + } +} + +/** + * Slot Navigation callback + * + */ +static void ui_menu_slot_action(void) { + nbgl_layoutRadioChoice_t choices = {0}; + nbgl_layoutButton_t buttonInfo = {0}; + static char* names[GPG_KEYS_SLOTS] = {0}; + static char text[GPG_KEYS_SLOTS][32]; + uint32_t slot; + + ui_setting_header("Slots configuration", TOKEN_SLOT_BACK, slot_cb); + + for (slot = 0; slot < GPG_KEYS_SLOTS; slot++) { + snprintf(text[slot], + sizeof(text[slot]), + "Slot %d %s", + (slot + 1), + (N_gpg_pstate->config_slot[1] == slot) ? "[default]" : ""); + names[slot] = text[slot]; + } + choices.names = (const char* const*) names; + choices.localized = false; + choices.nbChoices = GPG_KEYS_SLOTS; + choices.initChoice = G_gpg_vstate.slot; + choices.token = TOKEN_SLOT_SELECT; + nbgl_layoutAddRadioChoice(layoutCtx, &choices); + + buttonInfo.fittingContent = false; + buttonInfo.onBottom = true; + buttonInfo.style = BLACK_BACKGROUND; + buttonInfo.text = "Set default"; + buttonInfo.token = TOKEN_SLOT_DEF; + buttonInfo.tuneId = TUNE_TAP_CASUAL; + nbgl_layoutAddButton(layoutCtx, &buttonInfo); + + nbgl_layoutDraw(layoutCtx); +} + +// ----------------------------------------------------------- +// --------------------- SETTINGS MENU ----------------------- +// ----------------------------------------------------------- + +/* ------------------------------- TEMPLATE UX ------------------------------- */ + +enum { + TOKEN_TEMPLATE_SIG = FIRST_USER_TOKEN, + TOKEN_TEMPLATE_DEC, + TOKEN_TEMPLATE_AUT, + TOKEN_TEMPLATE_SET, + TOKEN_TEMPLATE_BACK +}; + +static const char* const keyNameTexts[] = {LABEL_SIG, LABEL_DEC, LABEL_AUT}; + +enum { + TOKEN_TYPE_RSA2048 = FIRST_USER_TOKEN, + TOKEN_TYPE_RSA3072, +#ifdef WITH_SUPPORT_RSA4096 + TOKEN_TYPE_RSA4096, +#endif + TOKEN_TYPE_SECP256K1, + TOKEN_TYPE_SECP256R1, + TOKEN_TYPE_Ed25519, + TOKEN_TYPE_BACK +}; + +static const char* const keyTypeTexts[] = {LABEL_RSA2048, + LABEL_RSA3072, +#ifdef WITH_SUPPORT_RSA4096 + LABEL_RSA4096, +#endif + LABEL_SECP256K1, + LABEL_SECP256R1, + LABEL_Ed25519}; + +/** + * Determine the selected key type from its attributes + * + * @param[in] key token describing the selected key + * + * @return token describing the selected key type + * + */ +static uint32_t _getKeyType(const uint8_t key) { + uint8_t* attributes = NULL; + uint32_t token = 0; + + switch (key) { + case TOKEN_TEMPLATE_SIG: + attributes = G_gpg_vstate.kslot->sig.attributes.value; + break; + case TOKEN_TEMPLATE_DEC: + attributes = G_gpg_vstate.kslot->dec.attributes.value; + break; + case TOKEN_TEMPLATE_AUT: + attributes = G_gpg_vstate.kslot->aut.attributes.value; + break; + } + if (attributes == NULL) { + return 0; + } + switch (attributes[0]) { + case KEY_ID_RSA: + switch (U2BE(attributes, 1)) { + case 2048: + token = TOKEN_TYPE_RSA2048; + break; + case 3072: + token = TOKEN_TYPE_RSA3072; + break; +#ifdef WITH_SUPPORT_RSA4096 + case 4096: + token = TOKEN_TYPE_RSA4096; + break; +#endif + } + break; + case KEY_ID_ECDH: + switch (attributes[1]) { + case 0x2A: + token = TOKEN_TYPE_SECP256R1; + break; + case 0x2B: + switch (attributes[2]) { + case 0x06: + token = TOKEN_TYPE_Ed25519; + break; + case 0x81: + token = TOKEN_TYPE_SECP256K1; + break; + } + break; + } + break; + case KEY_ID_ECDSA: + switch (attributes[1]) { + case 0x2A: + token = TOKEN_TYPE_SECP256R1; + break; + case 0x2B: + token = TOKEN_TYPE_SECP256K1; + break; + } + break; + case KEY_ID_EDDSA: + token = TOKEN_TYPE_Ed25519; + break; + } + return token; +} + +/** + * Key Template Action callback + * + * @param[in] token button Id pressed + * @param[in] index widget index on the page + * + */ +static void template_key_cb(int token, uint8_t index) { + LV(attributes, GPG_KEY_ATTRIBUTES_LENGTH); + gpg_key_t* dest = NULL; + static uint8_t* oid = NULL; + uint32_t oid_len = 0; + uint32_t size = 0; + uint8_t key_type = index + FIRST_USER_TOKEN; + + if (token != TOKEN_TYPE_BACK) { + explicit_bzero(&attributes, sizeof(attributes)); + switch (key_type) { + case TOKEN_TYPE_RSA2048: + case TOKEN_TYPE_RSA3072: +#ifdef WITH_SUPPORT_RSA4096 + case TOKEN_TYPE_RSA4096: +#endif + switch (key_type) { + case TOKEN_TYPE_RSA2048: + size = 2048; + break; + case TOKEN_TYPE_RSA3072: + size = 3072; + break; +#ifdef WITH_SUPPORT_RSA4096 + case TOKEN_TYPE_RSA4096: + size = 4096; + break; +#endif + } + attributes.value[0] = KEY_ID_RSA; + U2BE_ENCODE(attributes.value, 1, size); + attributes.value[3] = 0x00; + attributes.value[4] = 0x20; + attributes.value[5] = 0x01; + attributes.length = 6; + oid_len = 6; + break; + + case TOKEN_TYPE_SECP256K1: + if (G_gpg_vstate.ux_key == TOKEN_TEMPLATE_DEC) { + attributes.value[0] = KEY_ID_ECDH; + } else { + attributes.value[0] = KEY_ID_ECDSA; + } + oid = gpg_curve2oid(CX_CURVE_SECP256K1, &oid_len); + memmove(attributes.value + 1, oid, oid_len); + attributes.length = 1 + oid_len; + break; + + case TOKEN_TYPE_SECP256R1: + if (G_gpg_vstate.ux_key == TOKEN_TEMPLATE_DEC) { + attributes.value[0] = KEY_ID_ECDH; + } else { + attributes.value[0] = KEY_ID_ECDSA; + } + oid = gpg_curve2oid(CX_CURVE_SECP256R1, &oid_len); + memmove(attributes.value + 1, oid, oid_len); + attributes.length = 1 + oid_len; + break; + + case TOKEN_TYPE_Ed25519: + if (G_gpg_vstate.ux_key == TOKEN_TEMPLATE_DEC) { + attributes.value[0] = KEY_ID_ECDH; + oid = gpg_curve2oid(CX_CURVE_Curve25519, &oid_len); + } else { + attributes.value[0] = KEY_ID_EDDSA; + oid = gpg_curve2oid(CX_CURVE_Ed25519, &oid_len); + } + memmove(attributes.value + 1, oid, oid_len); + attributes.length = 1 + oid_len; + break; + } + + switch (G_gpg_vstate.ux_key) { + case TOKEN_TEMPLATE_SIG: + dest = &G_gpg_vstate.kslot->sig; + break; + case TOKEN_TEMPLATE_DEC: + dest = &G_gpg_vstate.kslot->dec; + break; + case TOKEN_TEMPLATE_AUT: + dest = &G_gpg_vstate.kslot->aut; + break; + } + + if (dest && attributes.value[0] && + memcmp(&dest->attributes, &attributes, sizeof(attributes)) != 0) { + nvm_write(dest, NULL, sizeof(gpg_key_t)); + nvm_write(&dest->attributes, &attributes, sizeof(attributes)); + } + } + ui_settings_template(); +} + +/** + * Template Action callback + * + * @param[in] token button Id pressed + * @param[in] index widget index on the page + * + */ +static void template_cb(int token, uint8_t index) { + UNUSED(index); + static nbgl_layoutRadioChoice_t choices = {0}; + + switch (token) { + case TOKEN_TEMPLATE_BACK: + ui_menu_settings(); + break; + case TOKEN_TEMPLATE_SIG: + case TOKEN_TEMPLATE_DEC: + case TOKEN_TEMPLATE_AUT: + G_gpg_vstate.ux_key = token; + ui_setting_header(keyNameTexts[token - FIRST_USER_TOKEN], + TOKEN_TYPE_BACK, + template_key_cb); + + choices.names = (const char* const*) keyTypeTexts; + choices.nbChoices = ARRAYLEN(keyTypeTexts); + choices.initChoice = _getKeyType(token) - FIRST_USER_TOKEN; + choices.token = token; + nbgl_layoutAddRadioChoice(layoutCtx, &choices); + + nbgl_layoutDraw(layoutCtx); + break; + } +} + +/** + * Template Navigation callback + * + */ +static void ui_settings_template(void) { + nbgl_layoutBar_t bar = {0}; + uint32_t i; + + G_gpg_vstate.ux_key = 0; + + ui_setting_header("Keys templates", TOKEN_TEMPLATE_BACK, template_cb); + + for (i = 0; i < KEY_NB; i++) { + explicit_bzero(&bar, sizeof(nbgl_layoutBar_t)); + switch (_getKeyType(TOKEN_TEMPLATE_SIG + i)) { + case TOKEN_TYPE_RSA2048: + bar.subText = PIC(LABEL_RSA2048); + break; + case TOKEN_TYPE_RSA3072: + bar.subText = PIC(LABEL_RSA3072); + break; +#ifdef WITH_SUPPORT_RSA4096 + case TOKEN_TYPE_RSA4096: + bar.subText = PIC(LABEL_RSA4096); + break; +#endif + case TOKEN_TYPE_SECP256K1: + bar.subText = PIC(LABEL_SECP256K1); + break; + case TOKEN_TYPE_SECP256R1: + bar.subText = PIC(LABEL_SECP256R1); + break; + case TOKEN_TYPE_Ed25519: + bar.subText = PIC(LABEL_Ed25519); + break; + default: + break; + } + bar.text = PIC(keyNameTexts[i]); + bar.iconRight = &C_Next32px; + bar.token = TOKEN_TEMPLATE_SIG + i; + bar.centered = false; + bar.tuneId = TUNE_TAP_CASUAL; + nbgl_layoutAddTouchableBar(layoutCtx, &bar); + nbgl_layoutAddSeparationLine(layoutCtx); + } + + nbgl_layoutDraw(layoutCtx); +} + +/* --------------------------------- SEED UX --------------------------------- */ + +enum { + TOKEN_SEED = FIRST_USER_TOKEN, + TOKEN_SEED_BACK, +}; + +/** + * Seed Mode Confirmation callback + * + * @param[in] confirm indicate if the user press 'Confirm' or 'Cancel' + * + */ +void seed_confirm_cb(bool confirm) { + if (confirm) { + G_gpg_vstate.seed_mode = 0; + ui_info("SEED MODE", "DEACTIVATED", ui_settings_seed, true); + } else { + G_gpg_vstate.seed_mode = 1; + ui_settings_seed(); + } +} + +/** + * Seed Action callback + * + * @param[in] token button Id pressed + * @param[in] index widget index on the page + * + */ +static void seed_cb(int token, uint8_t index) { + switch (token) { + case TOKEN_SEED_BACK: + ui_menu_settings(); + break; + case TOKEN_SEED: + if (index == 0) { + nbgl_useCaseChoice(NULL, + "SEED mode", + "This mode allows to derive your key from Master SEED.\n" + "Without such configuration, an OS or App update " + "will cause your private key to be lost!\n" + "Are you sure you want to disable SEED mode?", + "Deactivate", + "Cancel", + seed_confirm_cb); + } + break; + } +} + +/** + * Seed Navigation callback + * + */ +static void ui_settings_seed(void) { + static nbgl_layoutSwitch_t option = {0}; + + ui_setting_header("Seed mode", TOKEN_SEED_BACK, seed_cb); + + option.initState = G_gpg_vstate.seed_mode; + option.text = "Seed Mode"; + option.subText = "Key derivation from Master seed"; + option.token = TOKEN_SEED; + option.tuneId = TUNE_TAP_CASUAL; + nbgl_layoutAddSwitch(layoutCtx, &option); + + nbgl_layoutDraw(layoutCtx); +} + +/* --------------------------------- PIN UX ---------------------------------- */ + +enum { + TOKEN_PIN_SET = FIRST_USER_TOKEN, + TOKEN_PIN_DEF, + TOKEN_PIN_BACK, +}; + +/** + * Trust Mode Confirmation callback + * + * @param[in] confirm indicate if the user press 'Confirm' or 'Cancel' + * + */ +void trust_cb(bool confirm) { + if (confirm) { + G_gpg_vstate.pinmode = G_gpg_vstate.pinmode_req; + ui_info("TRUST MODE", "SELECTED", ui_settings_pin, true); + } else { + ui_settings_pin(); + } +} + +/** + * Pin Action callback + * + * @param[in] token button Id pressed + * @param[in] index widget index on the page + * + */ +static void pin_cb(int token, uint8_t index) { + const char* err = NULL; + switch (token) { + case TOKEN_PIN_BACK: + ui_menu_settings(); + break; + case TOKEN_PIN_SET: + if (G_gpg_vstate.pinmode == index) { + break; + } + switch (index) { + case PIN_MODE_SCREEN: + case PIN_MODE_CONFIRM: + if ((gpg_pin_is_verified(PIN_ID_PW1) == 0) && + (gpg_pin_is_verified(PIN_ID_PW2) == 0)) { + err = PIN_USER; + } + break; + case PIN_MODE_TRUST: + if (gpg_pin_is_verified(PIN_ID_PW3) == 0) { + err = PIN_ADMIN; + } + break; + } + if (err != NULL) { + ui_info(err, NOT_VERIFIED, ui_settings_pin, false); + break; + } + if ((G_gpg_vstate.pinmode != PIN_MODE_TRUST) && (index == PIN_MODE_TRUST)) { + G_gpg_vstate.pinmode_req = index; + nbgl_useCaseChoice(NULL, + "TRUST mode", + "This mode won't request any more PINs " + "or validation before operations!\n" + "Are you sure you want to select TRUST mode?", + "Select", + "Cancel", + trust_cb); + } else { + G_gpg_vstate.pinmode = index; + } + break; + case TOKEN_PIN_DEF: + if (G_gpg_vstate.pinmode == PIN_MODE_TRUST) { + ui_info(DEFAULT_MODE, NOT_ALLOWED, ui_settings_pin, false); + break; + } else if (G_gpg_vstate.pinmode != N_gpg_pstate->config_pin[0]) { + // set new mode + nvm_write((void*) (&N_gpg_pstate->config_pin[0]), &G_gpg_vstate.pinmode, 1); + gpg_activate_pinpad(3); + } + ui_settings_pin(); + break; + } +} + +/** + * Pin Navigation callback + * + */ +static void ui_settings_pin(void) { + static nbgl_layoutRadioChoice_t choices = {0}; + nbgl_layoutButton_t buttonInfo = {0}; + static char* names[3] = {0}; + static char text[3][64]; + uint32_t i; + + static const char* const PinNameTexts[] = { + "On Screen", + "Confirm Only", + "Trust", + }; + + ui_setting_header("PIN mode", TOKEN_PIN_BACK, pin_cb); + + for (i = 0; i < ARRAYLEN(PinNameTexts); i++) { + snprintf(text[i], + sizeof(text[i]), + "%s %s", + (const char*) PIC(PinNameTexts[i]), + (N_gpg_pstate->config_pin[0] == i) ? "[default]" : ""); + names[i] = text[i]; + } + choices.names = (const char* const*) names; + choices.localized = false; + choices.nbChoices = ARRAYLEN(PinNameTexts); + choices.initChoice = G_gpg_vstate.pinmode; + choices.token = TOKEN_PIN_SET; + nbgl_layoutAddRadioChoice(layoutCtx, &choices); + + buttonInfo.fittingContent = false; + buttonInfo.onBottom = true; + buttonInfo.style = BLACK_BACKGROUND; + buttonInfo.text = "Set default"; + buttonInfo.token = TOKEN_PIN_DEF; + buttonInfo.tuneId = TUNE_TAP_CASUAL; + nbgl_layoutAddButton(layoutCtx, &buttonInfo); + + nbgl_layoutDraw(layoutCtx); +} + +/* --------------------------------- UIF UX ---------------------------------- */ + +enum { + TOKEN_UIF_SIG = FIRST_USER_TOKEN, + TOKEN_UIF_DEC, + TOKEN_UIF_AUT, + TOKEN_UIF_BACK, +}; + +/** + * UIF Action callback + * + * @param[in] token button Id pressed + * @param[in] index widget index on the page + * + */ +static void uif_cb(int token, uint8_t index) { + unsigned char* uif = NULL; + switch (token) { + case TOKEN_UIF_BACK: + ui_menu_settings(); + break; + case TOKEN_UIF_SIG: + uif = &G_gpg_vstate.kslot->sig.UIF[0]; + break; + case TOKEN_UIF_DEC: + uif = &G_gpg_vstate.kslot->dec.UIF[0]; + break; + case TOKEN_UIF_AUT: + uif = &G_gpg_vstate.kslot->aut.UIF[0]; + break; + } + if (uif == NULL) { + return; + } + if (uif[0] == 2) { + ui_info(UIF_LOCKED, EMPTY, ui_menu_settings, false); + } else if (uif[0] != index) { + nvm_write(&uif[0], &index, 1); + } +} + +/** + * UIF Navigation callback + * + */ +static void ui_settings_uif(void) { + static nbgl_layoutSwitch_t option = {0}; + uint8_t nbOptions = 0; + + ui_setting_header("User Interaction Flags", TOKEN_UIF_BACK, uif_cb); + + if (G_gpg_vstate.kslot->sig.UIF[0] != 2) { + explicit_bzero(&option, sizeof(nbgl_layoutSwitch_t)); + option.initState = G_gpg_vstate.kslot->sig.UIF[0]; + option.text = "UIF for Signature"; + option.token = TOKEN_UIF_SIG; + option.tuneId = TUNE_TAP_CASUAL; + nbgl_layoutAddSwitch(layoutCtx, &option); + nbOptions++; + } + + if (G_gpg_vstate.kslot->dec.UIF[0] != 2) { + explicit_bzero(&option, sizeof(nbgl_layoutSwitch_t)); + option.initState = G_gpg_vstate.kslot->dec.UIF[0]; + option.text = "UIF for Decryption"; + option.token = TOKEN_UIF_DEC; + option.tuneId = TUNE_TAP_CASUAL; + nbgl_layoutAddSwitch(layoutCtx, &option); + nbOptions++; + } + + if (G_gpg_vstate.kslot->aut.UIF[0] != 2) { + explicit_bzero(&option, sizeof(nbgl_layoutSwitch_t)); + option.initState = G_gpg_vstate.kslot->aut.UIF[0]; + option.text = "UIF for Authentication"; + option.token = TOKEN_UIF_AUT; + option.tuneId = TUNE_TAP_CASUAL; + nbgl_layoutAddSwitch(layoutCtx, &option); + nbOptions++; + } + if (nbOptions == 0) { + // UIF flags are all "Permanent Enable", just display for information + static const char* const infoTypes[] = {"UIF for Signature", + "UIF for Decryption", + "UIF for Authentication"}; + static const char* const infoContents[] = {"Permanently Enabled", + "Permanently Enabled", + "Permanently Enabled"}; + + for (nbOptions = 0; nbOptions < ARRAYLEN(infoTypes); nbOptions++) { + nbgl_layoutAddText(layoutCtx, infoTypes[nbOptions], infoContents[nbOptions]); + nbgl_layoutAddSeparationLine(layoutCtx); + } + } + + nbgl_layoutDraw(layoutCtx); +} + +/* -------------------------------- RESET UX --------------------------------- */ + +enum { + TOKEN_RESET = FIRST_USER_TOKEN, +}; + +/** + * Reset Navigation callback + * + * @param[in] page selected page to display + * @param[in] content describe the widgets to display on the page + * + */ +static bool reset_nav_cb(uint8_t page, nbgl_pageContent_t* content) { + UNUSED(page); + explicit_bzero(content, sizeof(nbgl_pageContent_t)); + content->type = INFO_LONG_PRESS; + content->infoLongPress.text = + "Reset the app to factory default?\nThis will delete ALL the keys!!!"; + content->infoLongPress.icon = NULL; + content->infoLongPress.longPressText = "Yes"; + content->infoLongPress.longPressToken = TOKEN_RESET; + content->infoLongPress.tuneId = TUNE_TAP_CASUAL; + return true; +} + +/** + * Reset Action callback + * + * @param[in] token button Id pressed + * @param[in] index widget index on the page + * + */ +static void reset_ctrl_cb(int token, uint8_t index) { + UNUSED(index); + + if (token != TOKEN_RESET) { + return; + } + + app_reset(); +} + +/* ------------------------------- SETTINGS UX ------------------------------- */ + +enum { + TOKEN_SETTINGS_TEMPLATE = FIRST_USER_TOKEN, + TOKEN_SETTINGS_SEED, + TOKEN_SETTINGS_PIN, + TOKEN_SETTINGS_UIF, + TOKEN_SETTINGS_RESET, +}; + +enum { + SETTINGS_PAGE_PARAMS, + SETTINGS_PAGE_INFO, + SETTINGS_PAGE_NB, +}; + +#ifdef HAVE_PRINTF +#define VERSION_STR "[DBG] App " XSTR(APPVERSION) +#else +#define VERSION_STR "App " XSTR(APPVERSION) +#endif + +/** + * Settings Navigation callback + * + * @param[in] page selected page to display + * @param[in] content describe the widgets to display on the page + * + */ +static bool settings_nav_cb(uint8_t page, nbgl_pageContent_t* content) { + bool ret = false; + + static const char* const infoTypes[] = {"Name", "Developer", "Specifications", "Version"}; + static const char* const infoContents[] = {"OpenPGP Card", + "(c) Ledger SAS", + XSTR(SPEC_VERSION), + VERSION_STR}; + static const char* const barTexts[] = {"Key Template", + "Seed mode", + "Pin mode", + "UIF mode", + "Reset"}; + static const uint8_t barTokens[] = {TOKEN_SETTINGS_TEMPLATE, + TOKEN_SETTINGS_SEED, + TOKEN_SETTINGS_PIN, + TOKEN_SETTINGS_UIF, + TOKEN_SETTINGS_RESET}; + explicit_bzero(content, sizeof(nbgl_pageContent_t)); + switch (page) { + case SETTINGS_PAGE_INFO: + content->type = INFOS_LIST; + content->infosList.nbInfos = ARRAYLEN(infoTypes); + content->infosList.infoTypes = infoTypes; + content->infosList.infoContents = infoContents; + ret = true; + break; + case SETTINGS_PAGE_PARAMS: + content->type = BARS_LIST; + content->barsList.barTexts = barTexts; + content->barsList.tokens = barTokens; + content->barsList.nbBars = ARRAYLEN(barTokens); + content->barsList.tuneId = TUNE_TAP_CASUAL; + ret = true; + break; + } + return ret; +} + +/** + * Settings Action callback + * + * @param[in] token button Id pressed + * @param[in] index widget index on the page + * + */ +static void settings_ctrl_cb(int token, uint8_t index) { + UNUSED(index); + switch (token) { + case TOKEN_SETTINGS_TEMPLATE: + ui_settings_template(); + break; + case TOKEN_SETTINGS_SEED: + ui_settings_seed(); + break; + case TOKEN_SETTINGS_PIN: + ui_settings_pin(); + break; + case TOKEN_SETTINGS_UIF: + ui_settings_uif(); + break; + case TOKEN_SETTINGS_RESET: + nbgl_useCaseSettings("Reset to Default", + 0, + 1, + true, + ui_menu_settings, + reset_nav_cb, + reset_ctrl_cb); + break; + } +} + +/** + * Settings menu definition + * + */ +static void ui_menu_settings() { + nbgl_useCaseSettings(APPNAME, + SETTINGS_PAGE_PARAMS, + SETTINGS_PAGE_NB, + false, + ui_init, + settings_nav_cb, + settings_ctrl_cb); +} + +/* ------------------------------ PIN CONFIRM UX ----------------------------- */ + +/** + * Pin Confirmation callback + * + * @param[in] confirm indicate if the user press 'Confirm' or 'Cancel' + * + */ +void pin_confirm_cb(bool confirm) { + gpg_pin_set_verified(G_gpg_vstate.io_p2, confirm); + + gpg_io_discard(0); + gpg_io_insert_u16(confirm ? SW_OK : SW_CONDITIONS_NOT_SATISFIED); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_init(); +} + +/** + * Pin Confirmation page display + * + * @param[in] value PinCode ID to confirm + * + */ +void ui_menu_pinconfirm_display(unsigned int value) { + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "%s %x", + value == 0x83 ? "Admin" : "User", + value); + nbgl_useCaseChoice(NULL, "Confirm PIN", G_gpg_vstate.menu, "Yes", "No", pin_confirm_cb); +} + +/* ------------------------------ PIN ENTRY UX ----------------------------- */ + +enum { + TOKEN_PIN_ENTRY_BACK = FIRST_USER_TOKEN, +}; + +static void ui_menu_pinentry_cb(void); + +/** + * Pin Entry Validation callback + * + * @param[in] value PinCode ID to confirm + * + */ +static void pinentry_validate_cb(const uint8_t* pinentry, uint8_t length) { + unsigned int sw = SW_UNKNOWN; + unsigned int len1 = 0; + unsigned char* pin1 = NULL; + gpg_pin_t* pin = NULL; + + switch (G_gpg_vstate.io_ins) { + case INS_VERIFY: + pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); + sw = gpg_pin_check(pin, G_gpg_vstate.io_p2, pinentry, length); + gpg_io_discard(1); + if (sw == SW_PIN_BLOCKED) { + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_info(PIN_LOCKED, EMPTY, ui_init, false); + break; + } else if (sw != SW_OK) { + snprintf(G_gpg_vstate.line, + sizeof(G_gpg_vstate.line), + "%d tries remaining", + pin->counter); + ui_info(WRONG_PIN, G_gpg_vstate.line, ui_menu_pinentry_cb, false); + break; + } + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + snprintf(G_gpg_vstate.line, + sizeof(G_gpg_vstate.line), + "%s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "ADMIN" : "USER"); + ui_info(G_gpg_vstate.line, "VERIFIED", ui_init, true); + break; + + case INS_CHANGE_REFERENCE_DATA: + switch (G_gpg_vstate.ux_step) { + case 0: + // Check Current pin code + pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); + sw = gpg_pin_check(pin, G_gpg_vstate.io_p2, pinentry, length); + gpg_io_discard(1); + if (sw == SW_PIN_BLOCKED) { + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_info(PIN_LOCKED, EMPTY, ui_init, false); + break; + } else if (sw != SW_OK) { + snprintf(G_gpg_vstate.line, + sizeof(G_gpg_vstate.line), + " %d tries remaining", + pin->counter); + ui_info(WRONG_PIN, G_gpg_vstate.line, ui_menu_pinentry_cb, false); + break; + } + ui_menu_pinentry_display(++G_gpg_vstate.ux_step); + break; + case 1: + // Store the New pin codes + gpg_io_insert_u8(length); + gpg_io_insert(pinentry, length); + ui_menu_pinentry_display(++G_gpg_vstate.ux_step); + break; + case 2: + // Compare the 2 pin codes (New + Confirm) + len1 = G_gpg_vstate.work.io_buffer[0]; + pin1 = G_gpg_vstate.work.io_buffer + 1; + if ((len1 != length) || (memcmp(pin1, pinentry, length) != 0)) { + gpg_io_discard(1); + ui_info(PIN_DIFFERS, EMPTY, ui_menu_pinentry_cb, false); + } else { + pin = gpg_pin_get_pin(G_gpg_vstate.io_p2); + sw = gpg_pin_set(pin, G_gpg_vstate.work.io_buffer + 1, length); + gpg_io_discard(1); + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + if (sw != SW_OK) { + ui_info("Process Error", EMPTY, ui_init, false); + } else { + snprintf(G_gpg_vstate.line, + sizeof(G_gpg_vstate.line), + "%s PIN", + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "ADMIN" : "USER"); + ui_info(G_gpg_vstate.line, "CHANGED", ui_init, true); + } + } + break; + default: + break; + } + break; + + default: + break; + } +} + +/** + * Pin Entry Action callback + * + * @param[in] token button Id pressed + * @param[in] index widget index on the page + * + */ +static void pinentry_cb(int token, uint8_t index) { + UNUSED(index); + if (token == TOKEN_PIN_ENTRY_BACK) { + gpg_io_discard(0); + gpg_io_insert_u16(SW_CONDITIONS_NOT_SATISFIED); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_init(); + } +} + +/** + * Pin Entry page display + * + * @param[in] step Pin Entry step + * + */ +void ui_menu_pinentry_display(unsigned int step) { + uint8_t minLen; + char line[10]; + + // Init the page title + explicit_bzero(G_gpg_vstate.line, sizeof(G_gpg_vstate.line)); + if (G_gpg_vstate.io_ins == INS_CHANGE_REFERENCE_DATA) { + switch (step) { + case 0: + // Default or initial case + snprintf(line, sizeof(line), "Current"); + break; + case 1: + snprintf(line, sizeof(line), "New"); + break; + case 2: + snprintf(line, sizeof(line), "Confirm"); + break; + default: + break; + } + G_gpg_vstate.ux_step = step; + } else { + snprintf(line, sizeof(line), "Enter"); + } + snprintf(G_gpg_vstate.menu, + sizeof(G_gpg_vstate.menu), + "%s %s PIN", + line, + (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? "Admin" : "User"); + + minLen = (G_gpg_vstate.io_p2 == PIN_ID_PW3) ? GPG_MIN_PW3_LENGTH : GPG_MIN_PW1_LENGTH; + // Draw the keypad + nbgl_useCaseKeypad(G_gpg_vstate.menu, + minLen, + GPG_MAX_PW_LENGTH, + TOKEN_PIN_ENTRY_BACK, + false, + TUNE_TAP_CASUAL, + pinentry_validate_cb, + pinentry_cb); +} + +/** + * Pin Entry Navigation callback + * + */ +static void ui_menu_pinentry_cb(void) { + unsigned int value = 0; + + if ((G_gpg_vstate.io_ins == INS_CHANGE_REFERENCE_DATA) && (G_gpg_vstate.ux_step == 2)) { + // Current step is Change Password with PINs differ + value = 1; + } + ui_menu_pinentry_display(value); +} + +/* ------------------------------ UIF CONFIRM UX ----------------------------- */ + +/** + * UIF Confirmation callback + * + * @param[in] confirm indicate if the user press 'Confirm' or 'Cancel' + * + */ +void uif_confirm_cb(bool confirm) { + unsigned int sw = SW_SECURITY_UIF_ISSUE; + + if (confirm) { + G_gpg_vstate.UIF_flags = 1; + if (G_gpg_vstate.io_ins == INS_PSO) { + sw = gpg_apdu_pso(); + } else if (G_gpg_vstate.io_ins == INS_INTERNAL_AUTHENTICATE) { + sw = gpg_apdu_internal_authenticate(); + } else { + gpg_io_discard(1); + } + G_gpg_vstate.UIF_flags = 0; + } else { + gpg_io_discard(1); + } + gpg_io_insert_u16(sw); + gpg_io_do(IO_RETURN_AFTER_TX); + ui_init(); +} + +/** + * UIF page display + * + * @param[in] step unused + * + */ +void ui_menu_uifconfirm_display(unsigned int value) { + UNUSED(value); + + switch (G_gpg_vstate.io_ins) { + case INS_INTERNAL_AUTHENTICATE: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Authentication"); + break; + case INS_PSO: + switch (G_gpg_vstate.io_p1p2) { + case PSO_CDS: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Signature"); + break; + case PSO_ENC: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Encryption"); + break; + case PSO_DEC: + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Decryption"); + break; + default: + break; + } + break; + default: + break; + } + if (G_gpg_vstate.menu[0] == 0) { + snprintf(G_gpg_vstate.menu, sizeof(G_gpg_vstate.menu), "Please Cancel"); + } + nbgl_useCaseChoice(NULL, "Confirm operation", G_gpg_vstate.menu, "Yes", "No", uif_confirm_cb); +} + +#endif // defined(HAVE_NBGL) && defined(TARGET_STAX) diff --git a/src/gpg_vars.c b/src/gpg_vars.c new file mode 100644 index 0000000..2cb0a4a --- /dev/null +++ b/src/gpg_vars.c @@ -0,0 +1,22 @@ +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +#include "gpg_vars.h" + +gpg_v_state_t G_gpg_vstate; + +const gpg_nv_state_t N_state_pic; diff --git a/src/gpg_vars.h b/src/gpg_vars.h index 4b0e33c..4313607 100644 --- a/src/gpg_vars.h +++ b/src/gpg_vars.h @@ -1,17 +1,19 @@ -/* Copyright 2017 Cedric Mesnil , Ledger SAS +/***************************************************************************** + * Ledger App OpenPGP. + * (c) 2024 Ledger SAS. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ #ifndef GPG_VARS_H #define GPG_VARS_H @@ -19,7 +21,6 @@ #include "os.h" #include "cx.h" #include "ux.h" -#include "os_io_seproxyhal.h" #include "gpg_types.h" #include "gpg_api.h" @@ -31,25 +32,11 @@ extern const unsigned char C_OID_BRAINPOOL256R1[9]; extern const unsigned char C_OID_BRAINPOOL256T1[9]; extern const unsigned char C_OID_Ed25519[9]; extern const unsigned char C_OID_cv25519[10]; +extern const unsigned char C_gen_feature; extern gpg_v_state_t G_gpg_vstate; -#if defined(TARGET_NANOX) || defined(TARGET_NANOS2) extern const gpg_nv_state_t N_state_pic; -#define N_gpg_pstate ((volatile gpg_nv_state_t *)PIC(&N_state_pic)) -#else -extern gpg_nv_state_t N_state_pic; -#define N_gpg_pstate ((WIDE gpg_nv_state_t *)PIC(&N_state_pic)) -#endif - -#ifdef GPG_DEBUG_MAIN -extern int apdu_n; -#endif - -extern ux_state_t ux; +#define N_gpg_pstate ((volatile gpg_nv_state_t *) PIC(&N_state_pic)) -#ifdef HAVE_RSA -#include "cx_ram.h" -extern union cx_u G_cx; -#endif // HAVE_RSA #endif diff --git a/src/sdk/usbd_ccid_cmd.c b/src/sdk/usbd_ccid_cmd.c deleted file mode 100755 index f8d36ed..0000000 --- a/src/sdk/usbd_ccid_cmd.c +++ /dev/null @@ -1,1057 +0,0 @@ -/** - ****************************************************************************** - * @file usbd_ccid_cmd.c - * @author MCD Application Team - * @version V1.0.1 - * @date 31-January-2014 - * @brief CCID Commands handling - ****************************************************************************** - * @attention - * - *

© COPYRIGHT 2014 STMicroelectronics

- * - * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/software_license_agreement_liberty_v2 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ****************************************************************************** - */ - -#pragma message "Override SDK source file :" __FILE__ - -#ifdef HAVE_USB_CLASS_CCID - - -/* Includes ------------------------------------------------------------------*/ -#include "usbd_ccid_cmd.h" - -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private macro -------------------------------------------------------------*/ -#define CCID_UpdateCommandStatus(cmd_status,icc_status)\ - G_io_ccid.bulk_header.bulkin.bStatus=(cmd_status|icc_status) - /* - The Above Macro can take any of following Values - #define BM_ICC_PRESENT_ACTIVE 0x00 - #define BM_ICC_PRESENT_INACTIVE 0x01 - #define BM_ICC_NO_ICC_PRESENT 0x02 - - #define BM_COMMAND_STATUS_OFFSET 0x06 - #define BM_COMMAND_STATUS_NO_ERROR 0x00 - #define BM_COMMAND_STATUS_FAILED (0x01 << BM_COMMAND_STATUS_OFFSET) - #define BM_COMMAND_STATUS_TIME_EXTN (0x02 << BM_COMMAND_STATUS_OFFSET) - */ - -/* Private function prototypes -----------------------------------------------*/ -static uint8_t CCID_CheckCommandParams (uint32_t param_type); - -/* Private functions ---------------------------------------------------------*/ - -/** - * @brief PC_to_RDR_IccPowerOn - * PC_TO_RDR_ICCPOWERON message execution, apply voltage and get ATR - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_to_RDR_IccPowerOn(void) -{ - /* Apply the ICC VCC - Fills the Response buffer with ICC ATR - This Command is returned with RDR_to_PC_DataBlock(); - */ - - uint8_t voltage; - uint8_t sc_voltage = 0; - uint8_t error; - - G_io_ccid.bulk_header.bulkin.dwLength = 0; /* Reset Number of Bytes in abData */ - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_DWLENGTH | \ - CHK_PARAM_abRFU2 |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_ABORT ); - if (error != 0) - { - return error; - } - - /* Voltage that is applied to the ICC - 00h Automatic Voltage Selection - 01h 5.0 volts - 02h 3.0 volts - 03h 1.8 volts - */ - /* G_io_ccid.bulk_header.bulkout.bSpecific_0 Contains bPowerSelect */ - voltage = G_io_ccid.bulk_header.bulkout.bSpecific_0; - if (voltage >= VOLTAGE_SELECTION_1V8) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - return SLOTERROR_BAD_POWERSELECT; /* The Voltage specified is out of Spec */ - } - - /* Correct Voltage Requested by the Host */ - if ((voltage == VOLTAGE_SELECTION_AUTOMATIC) || - (voltage == VOLTAGE_SELECTION_3V)) - { - /* voltage == 00 Voltage Automatic - voltage == 01 Voltage Automatic = 5.0V - voltage == 02 Voltage Automatic = 3V - voltage == 03 Voltage Automatic = 1.8V - */ - sc_voltage = SC_VOLTAGE_3V; - } - else if (voltage == VOLTAGE_SELECTION_5V) - { - sc_voltage = SC_VOLTAGE_5V; - } - - G_io_ccid.bulk_header.bulkin.dwLength = SC_AnswerToReset(sc_voltage, G_io_ccid_data_buffer); - - /* Check if the Card has come to Active State*/ - error = CCID_CheckCommandParams(CHK_ACTIVE_STATE); - if (error != 0) - { - /* Check if Voltage is not Automatic */ - if (voltage != 0) - { /* If Specific Voltage requested by Host i.e 3V or 5V*/ - return error; - } - else - {/* Automatic Voltage selection requested by Host */ - - if (sc_voltage != SC_VOLTAGE_5V) - { /* If voltage selected was Automatic and 5V is not yet tried */ - sc_voltage = SC_VOLTAGE_5V; - G_io_ccid.bulk_header.bulkin.dwLength = SC_AnswerToReset(sc_voltage, G_io_ccid_data_buffer); - - /* Check again the State */ - error = CCID_CheckCommandParams(CHK_ACTIVE_STATE); - if (error != 0) - return error; - - } - else - { /* Voltage requested from Host was 5V already*/ - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_INACTIVE); - return error; - } - } /* Voltage Selection was automatic */ - } /* If Active State */ - - /* ATR is received, No Error Condition Found */ - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - - return SLOT_NO_ERROR; -} - -/** - * @brief PC_to_RDR_IccPowerOff - * Icc VCC is switched Off - * @param None - * @retval uint8_t error: status of the command execution - */ -uint8_t PC_to_RDR_IccPowerOff(void) -{ - /* The response to this command message is the RDR_to_PC_SlotStatus - response message. */ - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_abRFU3 |\ - CHK_PARAM_DWLENGTH ); - if (error != 0) - { - return error; - } - - /* Command is ok, Check for Card Presence */ - if (SC_Detect()) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR,BM_ICC_PRESENT_INACTIVE); - } - else - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR,BM_ICC_NO_ICC_PRESENT); - } - - /* Power OFF the card */ - SC_Poweroff(); - - return SLOT_NO_ERROR; -} - -/** - * @brief PC_to_RDR_GetSlotStatus - * Provides the Slot status to the host - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_to_RDR_GetSlotStatus(void) -{ - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_DWLENGTH |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_abRFU3 ); - if (error != 0) - { - return error; - } - - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR,BM_ICC_PRESENT_ACTIVE); - return SLOT_NO_ERROR; -} - - -/** - * @brief PC_to_RDR_XfrBlock - * Handles the Block transfer from Host. - * Response to this command message is the RDR_to_PC_DataBlock - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_to_RDR_XfrBlock(void) -{ - uint16_t expectedLength, reqlen; - - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_abRFU3 |\ - CHK_PARAM_ABORT |\ - CHK_ACTIVE_STATE ); - if (error != 0) - return error; - - if (G_io_ccid.bulk_header.bulkout.dwLength > IO_CCID_DATA_BUFFER_SIZE) - { /* Check amount of Data Sent by Host is > than memory allocated ? */ - - return SLOTERROR_BAD_DWLENGTH; - } - - - /* wLevelParameter = Size of expected data to be returned by the - bulk-IN endpoint */ - expectedLength = (G_io_ccid.bulk_header.bulkout.bSpecific_2 << 8) | - G_io_ccid.bulk_header.bulkout.bSpecific_1; - - reqlen = G_io_ccid.bulk_header.bulkout.dwLength; - - G_io_ccid.bulk_header.bulkin.dwLength = (uint16_t)expectedLength; - - - error = SC_XferBlock(&G_io_ccid_data_buffer[0], - reqlen, - &expectedLength); - - if (error != SLOT_NO_ERROR) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - else - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - error = SLOT_NO_ERROR; - } - - return error; -} - - -/** - * @brief PC_to_RDR_GetParameters - * Provides the ICC parameters to the host - * Response to this command message is the RDR_to_PC_Parameters - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_to_RDR_GetParameters(void) -{ - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_DWLENGTH |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_abRFU3 ); - if (error != 0) - return error; - - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - - return SLOT_NO_ERROR; -} - - -/** - * @brief PC_to_RDR_ResetParameters - * Set the ICC parameters to the default - * Response to this command message is the RDR_to_PC_Parameters - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_to_RDR_ResetParameters(void) -{ - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_DWLENGTH |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_abRFU3 |\ - CHK_ACTIVE_STATE); - if (error != 0) - return error; - - /* This command resets the slot parameters to their default values */ - G_io_ccid.Protocol0_DataStructure.bmFindexDindex = DEFAULT_FIDI; - G_io_ccid.Protocol0_DataStructure.bmTCCKST0 = DEFAULT_T01CONVCHECKSUM; - G_io_ccid.Protocol0_DataStructure.bGuardTimeT0 = DEFAULT_EXTRA_GUARDTIME; - G_io_ccid.Protocol0_DataStructure.bWaitingIntegerT0 = DEFAULT_WAITINGINTEGER; - G_io_ccid.Protocol0_DataStructure.bClockStop = DEFAULT_CLOCKSTOP; - - error = SC_SetParams(&G_io_ccid.Protocol0_DataStructure); - - if (error != SLOT_NO_ERROR) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - else - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - error = SLOT_NO_ERROR; - } - - return error; -} - - -/** - * @brief PC_to_RDR_SetParameters - * Set the ICC parameters to the host defined parameters - * Response to this command message is the RDR_to_PC_Parameters - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_to_RDR_SetParameters(void) -{ - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_abRFU2 |\ - CHK_ACTIVE_STATE); - if (error != 0) - return error; - - error = SLOT_NO_ERROR; - - /* for Protocol T=0 (bProtocolNum=0) (dwLength=00000005h) */ - if ( (G_io_ccid.bulk_header.bulkout.dwLength == 5) && - (G_io_ccid.bulk_header.bulkout.bSpecific_0 != 0)) - error = SLOTERROR_BAD_PROTOCOLNUM; - - /* for Protocol T=1 (bProtocolNum=1) (dwLength=00000007h) */ - if ( (G_io_ccid.bulk_header.bulkout.dwLength == 7) && - (G_io_ccid.bulk_header.bulkout.bSpecific_0 != 1)) - error = SLOTERROR_CMD_NOT_SUPPORTED; - - /* For T0, Waiting Integer 0 supported */ - if (G_io_ccid_data_buffer[3] != 0) - error = SLOTERROR_BAD_WAITINGINTEGER; - - if (G_io_ccid_data_buffer[4] != DEFAULT_CLOCKSTOP) - error = SLOTERROR_BAD_CLOCKSTOP; - - if (error != SLOT_NO_ERROR) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - - os_memmove(&G_io_ccid.Protocol0_DataStructure, (Protocol0_DataStructure_t*)(&(G_io_ccid_data_buffer[0])), sizeof(Protocol0_DataStructure_t)); - error = SC_SetParams(&G_io_ccid.Protocol0_DataStructure); - - if (error != SLOT_NO_ERROR) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - else - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - error = SLOT_NO_ERROR; - } - - return error; -} - - -/** - * @brief PC_to_RDR_Escape - * Execute the Escape command. This is user specific Implementation - * Response to this command message is the RDR_to_PC_Escape - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_to_RDR_Escape(void) -{ - uint8_t error; - uint16_t size; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_abRFU3 |\ - CHK_PARAM_ABORT |\ - CHK_ACTIVE_STATE); - - if (error != 0) - return error; - - error = SC_ExecuteEscape(&G_io_ccid_data_buffer[0], - G_io_ccid.bulk_header.bulkout.dwLength, - &G_io_ccid_data_buffer[0], - &size); - - G_io_ccid.bulk_header.bulkin.dwLength = size; - - if (error != SLOT_NO_ERROR) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - else - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - } - - return error; -} - - -/** - * @brief PC_to_RDR_IccClock - * Execute the Clock specific command from host - * Response to this command message is the RDR_to_PC_SlotStatus - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_to_RDR_IccClock(void) -{ - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_abRFU2 |\ - CHK_PARAM_DWLENGTH|\ - CHK_ACTIVE_STATE); - if (error != 0) - return error; - - /* bClockCommand 00h restarts Clock - 01h Stops Clock in the state shown in the bClockStop - field of the PC_to_RDR_SetParameters command - and RDR_to_PC_Parameters message.*/ - if (G_io_ccid.bulk_header.bulkout.bSpecific_0 > 1) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - return SLOTERROR_BAD_CLOCKCOMMAND; - } - - error = SC_SetClock(G_io_ccid.bulk_header.bulkout.bSpecific_0); - - if (error != SLOT_NO_ERROR) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - else - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - } - - return error; -} - - -/** - * @brief PC_to_RDR_Abort - * Execute the Abort command from host, This stops all Bulk transfers - * from host and ICC - * Response to this command message is the RDR_to_PC_SlotStatus - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_to_RDR_Abort(void) -{ - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_abRFU3 |\ - CHK_PARAM_DWLENGTH); - if (error != 0) - return error; - - CCID_CmdAbort (G_io_ccid.bulk_header.bulkout.bSlot, G_io_ccid.bulk_header.bulkout.bSeq); - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR,BM_ICC_PRESENT_ACTIVE); - return SLOT_NO_ERROR; -} - -/** - * @brief CCID_CmdAbort - * Execute the Abort command from Bulk EP or from Control EP, - * This stops all Bulk transfers from host and ICC - * @param uint8_t slot: slot number that host wants to abort - * @param uint8_t seq : Seq number for PC_to_RDR_Abort - * @retval uint8_t status of the command execution - */ -uint8_t CCID_CmdAbort(uint8_t slot, uint8_t seq) -{ - /* This function is called for REQUEST_ABORT & PC_to_RDR_Abort */ - - if (slot >= CCID_NUMBER_OF_SLOTS) - { /* This error condition is possible only from CLASS_REQUEST, otherwise - Slot is already checked in parameters from PC_to_RDR_Abort request */ - /* Slot requested is more than supported by Firmware */ - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED,BM_ICC_NO_ICC_PRESENT); - return SLOTERROR_BAD_SLOT; - } - - if ( G_io_ccid.usb_ccid_param.bAbortRequestFlag == 1) - { /* Abort Command was already received from ClassReq or PC_to_RDR */ - if (( G_io_ccid.usb_ccid_param.bSeq == seq) && (G_io_ccid.usb_ccid_param.bSlot == slot)) - { - /* CLASS Specific request is already Received, Reset the abort flag */ - G_io_ccid.usb_ccid_param.bAbortRequestFlag = 0; - } - } - else - { - /* Abort Command was NOT received from ClassReq or PC_to_RDR, - so save them for next ABORT command to verify */ - G_io_ccid.usb_ccid_param.bAbortRequestFlag = 1; - G_io_ccid.usb_ccid_param.bSeq = seq ; - G_io_ccid.usb_ccid_param.bSlot = slot; - } - - return 0; -} - -/** - * @brief PC_TO_RDR_T0Apdu - * Execute the PC_TO_RDR_T0APDU command from host - * Response to this command message is the RDR_to_PC_SlotStatus - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_TO_RDR_T0Apdu(void) -{ - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_DWLENGTH | - CHK_PARAM_ABORT ); - if (error != 0) - return error; - - if (G_io_ccid.bulk_header.bulkout.bSpecific_0 > 0x03) - {/* Bit 0 is associated with field bClassGetResponse - Bit 1 is associated with field bClassEnvelope - Other bits are RFU.*/ - - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - return SLOTERROR_BAD_BMCHANGES; - } - - error = SC_T0Apdu(G_io_ccid.bulk_header.bulkout.bSpecific_0, - G_io_ccid.bulk_header.bulkout.bSpecific_1, - G_io_ccid.bulk_header.bulkout.bSpecific_2); - - if (error != SLOT_NO_ERROR) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - else - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - } - - return error; -} - -/** - * @brief PC_TO_RDR_Mechanical - * Execute the PC_TO_RDR_MECHANICAL command from host - * Response to this command message is the RDR_to_PC_SlotStatus - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_TO_RDR_Mechanical(void) -{ - uint8_t error; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_abRFU2 |\ - CHK_PARAM_DWLENGTH - ); - if (error != 0) - return error; - - if (G_io_ccid.bulk_header.bulkout.bSpecific_0 > 0x05) - {/* 01h Accept Card - 02h Eject Card - 03h Capture Card - 04h Lock Card - 05h Unlock Card*/ - - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - return SLOTERROR_BAD_BFUNCTION_MECHANICAL; - } - - error = SC_Mechanical(G_io_ccid.bulk_header.bulkout.bSpecific_0); - - if (error != SLOT_NO_ERROR) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - else - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - } - - return error; -} - -/** - * @brief PC_TO_RDR_SetDataRateAndClockFrequency - * Set the required Card Frequency and Data rate from the host. - * Response to this command message is the - * RDR_to_PC_DataRateAndClockFrequency - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_TO_RDR_SetDataRateAndClockFrequency(void) -{ - uint8_t error; - uint32_t clockFrequency; - uint32_t dataRate; - uint32_t temp =0; - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_abRFU3); - if (error != 0) - return error; - - if (G_io_ccid.bulk_header.bulkout.dwLength != 0x08) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED,BM_ICC_PRESENT_ACTIVE); - return SLOTERROR_BAD_LENTGH; - } - - /* HERE we avoiding to an unaligned memory access*/ - clockFrequency = U4LE(G_io_ccid_data_buffer, 0); - dataRate = U4LE(G_io_ccid_data_buffer, 4); - - error = SC_SetDataRateAndClockFrequency(clockFrequency, dataRate); - G_io_ccid.bulk_header.bulkin.bError = error; - - if (error != SLOT_NO_ERROR) - { - G_io_ccid.bulk_header.bulkin.dwLength = 0; - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - else - { - G_io_ccid.bulk_header.bulkin.dwLength = 8; - - (G_io_ccid_data_buffer[0]) = clockFrequency & 0x000000FF ; - (G_io_ccid_data_buffer[1]) = (clockFrequency & 0x0000FF00) >> 8; - (G_io_ccid_data_buffer[2]) = (clockFrequency & 0x00FF0000) >> 16; - (G_io_ccid_data_buffer[3]) = (clockFrequency & 0xFF000000) >> 24; - (G_io_ccid_data_buffer[4]) = dataRate & 0x000000FF ; - (G_io_ccid_data_buffer[5]) = (dataRate & 0x0000FF00) >> 8; - (G_io_ccid_data_buffer[6]) = (dataRate & 0x00FF0000) >> 16; - (G_io_ccid_data_buffer[7]) = (dataRate & 0xFF000000) >> 24; - - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - } - - return error; -} - -/** - * @brief PC_TO_RDR_Secure - * Execute the Secure Command from the host. - * Response to this command message is the RDR_to_PC_DataBlock - * @param None - * @retval uint8_t status of the command execution - */ -uint8_t PC_TO_RDR_Secure(void) -{ - uint8_t error; - uint8_t bBWI; - uint16_t wLevelParameter; - uint32_t responseLen; - - - error = CCID_CheckCommandParams(CHK_PARAM_SLOT |\ - CHK_PARAM_CARD_PRESENT |\ - CHK_PARAM_ABORT ); - - if (error != 0) { - G_io_ccid.bulk_header.bulkin.dwLength = 0; - return error; - } - - bBWI = G_io_ccid.bulk_header.bulkout.bSpecific_0; - wLevelParameter = (G_io_ccid.bulk_header.bulkout.bSpecific_1 + ((uint16_t)G_io_ccid.bulk_header.bulkout.bSpecific_2<<8)); - - if ((EXCHANGE_LEVEL_FEATURE == TPDU_EXCHANGE) || - (EXCHANGE_LEVEL_FEATURE == SHORT_APDU_EXCHANGE)) - { - /* TPDU level & short APDU level, wLevelParameter is RFU, = 0000h */ - if (wLevelParameter != 0 ) - { - G_io_ccid.bulk_header.bulkin.dwLength = 0; - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - error = SLOTERROR_BAD_LEVELPARAMETER; - return error; - } - } - - error = SC_Secure(G_io_ccid.bulk_header.bulkout.dwLength - CCID_HEADER_SIZE, bBWI, wLevelParameter, - &G_io_ccid_data_buffer[0], &responseLen); - - G_io_ccid.bulk_header.bulkin.dwLength = responseLen; - - if (error != SLOT_NO_ERROR) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED, BM_ICC_PRESENT_ACTIVE); - } - else - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_NO_ERROR, BM_ICC_PRESENT_ACTIVE); - } - - return error; -} - -/******************************************************************************/ -/* BULK IN ROUTINES */ -/******************************************************************************/ - -/** - * @brief RDR_to_PC_DataBlock - * Provide the data block response to the host - * Response for PC_to_RDR_IccPowerOn, PC_to_RDR_XfrBlock - * @param uint8_t errorCode: code to be returned to the host - * @retval None - */ -void RDR_to_PC_DataBlock(uint8_t errorCode) -{ - G_io_ccid.bulk_header.bulkin.bMessageType = RDR_TO_PC_DATABLOCK; - G_io_ccid.bulk_header.bulkin.bError = errorCode; - G_io_ccid.bulk_header.bulkin.bSpecific=0; /* bChainParameter */ - - /* void the Length Specified in Command */ - if(errorCode != SLOT_NO_ERROR) - { - G_io_ccid.bulk_header.bulkin.dwLength = 0; - } - - Transfer_Data_Request(); - -} - - -/** - * @brief RDR_to_PC_SlotStatus - * Provide the Slot status response to the host - * Response for PC_to_RDR_IccPowerOff - * PC_to_RDR_GetSlotStatus - * PC_to_RDR_IccClock - * PC_to_RDR_T0APDU - * PC_to_RDR_Mechanical - * Also the device sends this response message when it has completed - * aborting a slot after receiving both the Class Specific ABORT request - * and PC_to_RDR_Abort command message. - * @param uint8_t errorCode: code to be returned to the host - * @retval None - */ -void RDR_to_PC_SlotStatus(uint8_t errorCode) -{ - - G_io_ccid.bulk_header.bulkin.bMessageType = RDR_TO_PC_SLOTSTATUS; - G_io_ccid.bulk_header.bulkin.dwLength =0; - G_io_ccid.bulk_header.bulkin.bError = errorCode; - G_io_ccid.bulk_header.bulkin.bSpecific=0; /* bClockStatus = 00h Clock running - 01h Clock stopped in state L - 02h Clock stopped in state H - 03h Clock stopped in an unknown state - All other values are RFU. */ - - Transfer_Data_Request(); - -} - -/** - * @brief RDR_to_PC_Parameters - * Provide the data block response to the host - * Response for PC_to_RDR_GetParameters, PC_to_RDR_ResetParameters - * PC_to_RDR_SetParameters - * @param uint8_t errorCode: code to be returned to the host - * @retval None - */ -void RDR_to_PC_Parameters(uint8_t errorCode) -{ - - G_io_ccid.bulk_header.bulkin.bMessageType = RDR_TO_PC_PARAMETERS; - G_io_ccid.bulk_header.bulkin.bError = errorCode; - - if(errorCode == SLOT_NO_ERROR) - { - G_io_ccid.bulk_header.bulkin.dwLength = LEN_PROTOCOL_STRUCT_T0; - } - else - { - G_io_ccid.bulk_header.bulkin.dwLength = 0; - } - - os_memmove(G_io_ccid_data_buffer, &G_io_ccid.Protocol0_DataStructure, sizeof(G_io_ccid.Protocol0_DataStructure)); - - /* bProtocolNum */ - G_io_ccid.bulk_header.bulkin.bSpecific = BPROTOCOL_NUM_T0; - - Transfer_Data_Request(); -} - -/** - * @brief RDR_to_PC_Escape - * Provide the Escaped data block response to the host - * Response for PC_to_RDR_Escape - * @param uint8_t errorCode: code to be returned to the host - * @retval None - */ -void RDR_to_PC_Escape(uint8_t errorCode) -{ - G_io_ccid.bulk_header.bulkin.bMessageType = RDR_TO_PC_ESCAPE; - - G_io_ccid.bulk_header.bulkin.bSpecific=0; /* Reserved for Future Use */ - G_io_ccid.bulk_header.bulkin.bError = errorCode; - - /* void the Length Specified in Command */ - if(errorCode != SLOT_NO_ERROR) - { - G_io_ccid.bulk_header.bulkin.dwLength = 0; - } - - Transfer_Data_Request(); -} - - - -/** - * @brief RDR_to_PC_DataRateAndClockFrequency - * Provide the Clock and Data Rate information to host - * Response for PC_TO_RDR_SetDataRateAndClockFrequency - * @param uint8_t errorCode: code to be returned to the host - * @retval None - */ -void RDR_to_PC_DataRateAndClockFrequency(uint8_t errorCode) -{ - /* - uint16_t length = CCID_RESPONSE_HEADER_SIZE; - */ - - G_io_ccid.bulk_header.bulkin.bMessageType = RDR_TO_PC_DATARATEANDCLOCKFREQUENCY; - G_io_ccid.bulk_header.bulkin.bError = errorCode; - G_io_ccid.bulk_header.bulkin.bSpecific=0; /* Reserved for Future Use */ - - /* void the Length Specified in Command */ - if(errorCode != SLOT_NO_ERROR) - { - G_io_ccid.bulk_header.bulkin.dwLength = 0; - } - - Transfer_Data_Request(); -} - -#ifdef HAVE_CCID_INTERRUPT -/** - * @brief RDR_to_PC_NotifySlotChange - * Interrupt message to be sent to the host, Checks the card presence - * status and update the buffer accordingly - * @param None - * @retval None - */ -void RDR_to_PC_NotifySlotChange(void) -{ - G_io_ccid.UsbIntMessageBuffer[OFFSET_INT_BMESSAGETYPE] = RDR_TO_PC_NOTIFYSLOTCHANGE; - - if (SC_Detect() ) - { - /* - SLOT_ICC_PRESENT 0x01 : LSb : (0b = no ICC present, 1b = ICC present) - SLOT_ICC_CHANGE 0x02 : MSb : (0b = no change, 1b = change). - */ - G_io_ccid.UsbIntMessageBuffer[OFFSET_INT_BMSLOTICCSTATE] = SLOT_ICC_PRESENT | - SLOT_ICC_CHANGE; - } - else - { - G_io_ccid.UsbIntMessageBuffer[OFFSET_INT_BMSLOTICCSTATE] = SLOT_ICC_CHANGE; - - /* Power OFF the card */ - SC_Poweroff(); - } -} -#endif // HAVE_CCID_INTERRUPT - - -/** - * @brief CCID_UpdSlotStatus - * Updates the variable for the slot status - * @param uint8_t slotStatus : slot status from the calling function - * @retval None - */ -void CCID_UpdSlotStatus (uint8_t slotStatus) -{ - G_io_ccid.Ccid_SlotStatus.SlotStatus = slotStatus; -} - -/** - * @brief CCID_UpdSlotChange - * Updates the variable for the slot change status - * @param uint8_t changeStatus : slot change status from the calling function - * @retval None - */ -void CCID_UpdSlotChange (uint8_t changeStatus) -{ - G_io_ccid.Ccid_SlotStatus.SlotStatusChange = changeStatus; -} - -/** - * @brief CCID_IsSlotStatusChange - * Provides the value of the variable for the slot change status - * @param None - * @retval uint8_t slot change status - */ -uint8_t CCID_IsSlotStatusChange (void) -{ - return G_io_ccid.Ccid_SlotStatus.SlotStatusChange; -} - -/** - * @brief CCID_CheckCommandParams - * Checks the specific parameters requested by the function and update - * status accordingly. This function is called from all - * PC_to_RDR functions - * @param uint32_t param_type : Parameter enum to be checked by calling function - * @retval uint8_t status - */ -static uint8_t CCID_CheckCommandParams (uint32_t param_type) -{ - uint32_t parameter; - - G_io_ccid.bulk_header.bulkin.bStatus = BM_ICC_PRESENT_ACTIVE | BM_COMMAND_STATUS_NO_ERROR ; - - parameter = (uint32_t)param_type; - - if (parameter & CHK_PARAM_SLOT) - { - /* - The slot number (bSlot) identifies which ICC slot is being addressed - by the message, if the CCID supports multiple slots. - The slot number is zero-relative, and is in the range of zero to FFh. - */ - - /* SLOT Number is 0 onwards, so always < CCID_NUMBER_OF_SLOTs */ - /* Error Condition !!! */ - if (G_io_ccid.bulk_header.bulkout.bSlot >= CCID_NUMBER_OF_SLOTS) - { /* Slot requested is more than supported by Firmware */ - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED,BM_ICC_NO_ICC_PRESENT); - return SLOTERROR_BAD_SLOT; - } - } - - if (parameter & CHK_PARAM_CARD_PRESENT) - { - /* Commands Parameters ok, Check the Card Status */ - if (SC_Detect() == 0) - { /* Card is Not detected */ - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED,BM_ICC_NO_ICC_PRESENT); - return SLOTERROR_ICC_MUTE; - } - } - - /* Check that DwLength is 0 */ - if (parameter & CHK_PARAM_DWLENGTH) - { - if (G_io_ccid.bulk_header.bulkout.dwLength != 0) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED,BM_ICC_PRESENT_ACTIVE); - return SLOTERROR_BAD_LENTGH; - } - } - - /* abRFU 2 : Reserved for Future Use*/ - if (parameter & CHK_PARAM_abRFU2) - { - - if ((G_io_ccid.bulk_header.bulkout.bSpecific_1 != 0) || - (G_io_ccid.bulk_header.bulkout.bSpecific_2 != 0)) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED,BM_ICC_PRESENT_ACTIVE); - return SLOTERROR_BAD_ABRFU_2B; /* bSpecific_1 */ - } - } - - if (parameter & CHK_PARAM_abRFU3) - { - /* abRFU 3 : Reserved for Future Use*/ - if ((G_io_ccid.bulk_header.bulkout.bSpecific_0 != 0) || - (G_io_ccid.bulk_header.bulkout.bSpecific_1 != 0) || - (G_io_ccid.bulk_header.bulkout.bSpecific_2 != 0)) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED,BM_ICC_PRESENT_ACTIVE); - return SLOTERROR_BAD_ABRFU_3B; - } - } - - - if (parameter & CHK_PARAM_ABORT) - { - if( G_io_ccid.usb_ccid_param.bAbortRequestFlag ) - { - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED,BM_ICC_PRESENT_INACTIVE); - return SLOTERROR_CMD_ABORTED; - } - } - - if (parameter & CHK_ACTIVE_STATE) - { - /* Commands Parameters ok, Check the Card Status */ - /* Card is detected */ - if (! SC_Detect()) - { - /* Check that from Lower Layers, the SmartCard come to known state */ - CCID_UpdateCommandStatus(BM_COMMAND_STATUS_FAILED,BM_ICC_PRESENT_INACTIVE); - return SLOTERROR_HW_ERROR; - } - } - - return 0; -} - -#endif // HAVE_USB_CLASS_CCID - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ - diff --git a/src/sdk/usbd_ccid_if.c b/src/sdk/usbd_ccid_if.c deleted file mode 100755 index 7b32035..0000000 --- a/src/sdk/usbd_ccid_if.c +++ /dev/null @@ -1,619 +0,0 @@ -/** - ****************************************************************************** - * @file usbd_ccid_if.c - * @author MCD Application Team - * @version V1.0.1 - * @date 31-January-2014 - * @brief This file provides all the functions for USB Interface for CCID - ****************************************************************************** - * @attention - * - *

© COPYRIGHT 2014 STMicroelectronics

- * - * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/software_license_agreement_liberty_v2 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ****************************************************************************** - */ - -#pragma message "Override SDK source file :" __FILE__ - -/* Includes ------------------------------------------------------------------*/ -#include "os.h" - -#ifdef HAVE_USB_CLASS_CCID - -#include "usbd_ccid_if.h" - -#if CCID_BULK_EPIN_SIZE > USB_SEGMENT_SIZE - #error configuration error, the USB MAX SEGMENT SIZE does not support the CCID endpoint (CCID_BULK_EPIN_SIZE vs USB_SEGMENT_SIZE) -#endif - -/* Private typedef -----------------------------------------------------------*/ -/* Private define ------------------------------------------------------------*/ -/* Private macro -------------------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -usb_class_ccid_t G_io_ccid; - -/* Private function prototypes -----------------------------------------------*/ -static void CCID_Response_SendData (USBD_HandleTypeDef *pdev, - uint8_t* pbuf, - uint16_t len); -/* Private function ----------------------------------------------------------*/ -/** - * @brief CCID_Init - * Initialize the CCID USB Layer - * @param pdev: device instance - * @retval None - */ -void CCID_Init (USBD_HandleTypeDef *pdev) -{ - memset(&G_io_ccid, 0, sizeof(G_io_ccid)); - - /* CCID Related Initialization */ -#ifdef HAVE_CCID_INTERRUPT - CCID_SetIntrTransferStatus(1); /* Transfer Complete Status */ -#endif // HAVE_CCID_INTERRUPT - CCID_UpdSlotChange(1); - SC_InitParams(); - - /* Prepare Out endpoint to receive 1st packet */ - G_io_ccid.Ccid_BulkState = CCID_STATE_IDLE; - USBD_LL_PrepareReceive(pdev, CCID_BULK_OUT_EP, CCID_BULK_EPOUT_SIZE); - - // send the smartcard as inserted state at boot time - io_usb_ccid_set_card_inserted(1); -} - -/** - * @brief CCID_DeInit - * Uninitialize the CCID Machine - * @param pdev: device instance - * @retval None - */ -void CCID_DeInit (USBD_HandleTypeDef *pdev) -{ - UNUSED(pdev); - G_io_ccid.Ccid_BulkState = CCID_STATE_IDLE; -} - -/** - * @brief CCID_Message_In - * Handle Bulk IN & Intr IN data stage - * @param pdev: device instance - * @param uint8_t epnum: endpoint index - * @retval None - */ -void CCID_BulkMessage_In (USBD_HandleTypeDef *pdev, - uint8_t epnum) -{ - if (epnum == (CCID_BULK_IN_EP & 0x7F)) - {/* Filter the epnum by masking with 0x7f (mask of IN Direction) */ - - /*************** Handle Bulk Transfer IN data completion *****************/ - - switch (G_io_ccid.Ccid_BulkState) - { - case CCID_STATE_SEND_RESP: { - unsigned int remLen = G_io_ccid.UsbMessageLength; - - // advance with acknowledged sent chunk - if (G_io_ccid.pUsbMessageBuffer == &G_io_ccid.bulk_header) { - // first part of the bulk in sent. - // advance in the data buffer to transmit. (mixed source leap) - G_io_ccid.pUsbMessageBuffer = G_io_ccid_data_buffer+MIN(CCID_BULK_EPIN_SIZE, G_io_ccid.UsbMessageLength)-CCID_HEADER_SIZE; - } - else { - G_io_ccid.pUsbMessageBuffer += MIN(CCID_BULK_EPIN_SIZE, G_io_ccid.UsbMessageLength); - } - G_io_ccid.UsbMessageLength -= MIN(CCID_BULK_EPIN_SIZE, G_io_ccid.UsbMessageLength); - - // if remaining length is > EPIN_SIZE: send a filled bulk packet - if (G_io_ccid.UsbMessageLength >= CCID_BULK_EPIN_SIZE) { - CCID_Response_SendData(pdev, G_io_ccid.pUsbMessageBuffer, - // use the header declared size packet must be well formed - CCID_BULK_EPIN_SIZE); - } - - // if remaining length is 0; send an empty packet and prepare to receive a new command - else if (G_io_ccid.UsbMessageLength == 0 && remLen == CCID_BULK_EPIN_SIZE) { - CCID_Response_SendData(pdev, G_io_ccid.pUsbMessageBuffer, - // use the header declared size packet must be well formed - 0); - goto last_xfer; // won't wait ack to avoid missing a command - } - // else if no more data, then last packet sent, go back to idle (done on transfer ack) - else if (G_io_ccid.UsbMessageLength == 0) { // robustness only - last_xfer: - G_io_ccid.Ccid_BulkState = CCID_STATE_IDLE; - - /* Prepare EP to Receive First Cmd */ - // not timeout compliant // USBD_LL_PrepareReceive(pdev, CCID_BULK_OUT_EP, CCID_BULK_EPOUT_SIZE); - - // mark transfer as completed - G_io_app.apdu_state = APDU_IDLE; - } - - // if remaining length is < EPIN_SIZE: send packet and prepare to receive a new command - else if (G_io_ccid.UsbMessageLength < CCID_BULK_EPIN_SIZE) { - CCID_Response_SendData(pdev, G_io_ccid.pUsbMessageBuffer, - // use the header declared size packet must be well formed - G_io_ccid.UsbMessageLength); - goto last_xfer; // won't wait ack to avoid missing a command - } - - break; - } - - default: - break; - } - } -#ifdef HAVE_CCID_INTERRUPT - else if (epnum == (CCID_INTR_IN_EP & 0x7F)) - { - /* Filter the epnum by masking with 0x7f (mask of IN Direction) */ - CCID_SetIntrTransferStatus(1); /* Transfer Complete Status */ - } -#endif // HAVE_CCID_INTERRUPT -} - -void CCID_Send_Reply(USBD_HandleTypeDef *pdev) { - /********** Decide for all commands ***************/ - if (G_io_ccid.Ccid_BulkState == CCID_STATE_SEND_RESP) - { - G_io_ccid.UsbMessageLength = G_io_ccid.bulk_header.bulkin.dwLength+CCID_HEADER_SIZE; /* Store for future use */ - - /* Expected Data Length Packet Received */ - G_io_ccid.pUsbMessageBuffer = (uint8_t*) &G_io_ccid.bulk_header; - - // send bulk header and first pat of the message at once - os_memmove(G_io_usb_ep_buffer, &G_io_ccid.bulk_header, CCID_HEADER_SIZE); - if (G_io_ccid.UsbMessageLength>CCID_HEADER_SIZE) { - // copy start of data if bigger size than a header - os_memmove(G_io_usb_ep_buffer+CCID_HEADER_SIZE, G_io_ccid_data_buffer, MIN(CCID_BULK_EPIN_SIZE, G_io_ccid.UsbMessageLength)-CCID_HEADER_SIZE); - } - // send the first mixed source chunk - CCID_Response_SendData(pdev, G_io_usb_ep_buffer, - // use the header declared size packet must be well formed - MIN(CCID_BULK_EPIN_SIZE, G_io_ccid.UsbMessageLength)); - } -} - -/** - * @brief CCID_BulkMessage_Out - * Proccess CCID OUT data - * @param pdev: device instance - * @param uint8_t epnum: endpoint index - * @retval None - */ -void CCID_BulkMessage_Out (USBD_HandleTypeDef *pdev, - uint8_t epnum, uint8_t* buffer, uint16_t dataLen) -{ - if (epnum == (CCID_BULK_OUT_EP & 0x7F)) { - switch (G_io_ccid.Ccid_BulkState) - { - - // after a timeout, could be in almost any state :) therefore, clean it and process the newly received command - default: - G_io_ccid.Ccid_BulkState = CCID_STATE_IDLE; - // no break is intentional - - case CCID_STATE_IDLE: - // prepare to receive another packet later on (to avoid troubles with timeout due to other hid command timeouting the ccid endpoint reply) - USBD_LL_PrepareReceive(pdev, CCID_BULK_OUT_EP, CCID_BULK_EPOUT_SIZE); - - if (dataLen == 0x00) - { /* Zero Length Packet Received, end of transfer */ - G_io_ccid.Ccid_BulkState = CCID_STATE_IDLE; - } - else if (dataLen >= CCID_HEADER_SIZE) - { - G_io_ccid.UsbMessageLength = dataLen; /* Store for future use */ - - /* Expected Data Length Packet Received */ - // endianness is little :) useful for our ARM convention - G_io_ccid.pUsbMessageBuffer = (uint8_t*) &G_io_ccid.bulk_header; - - // copy the ccid bulk header only - os_memmove(G_io_ccid.pUsbMessageBuffer, buffer, CCID_HEADER_SIZE); - // copy remaining part in the data buffer (split from the ccid to allow for overlaying with another ressource buffer) - if (dataLen>CCID_HEADER_SIZE) { - os_memmove(G_io_ccid_data_buffer, buffer+CCID_HEADER_SIZE, dataLen-CCID_HEADER_SIZE); - // we're now receiving in the data buffer (all subsequent calls) - G_io_ccid.pUsbMessageBuffer = G_io_ccid_data_buffer; - } - - if (G_io_ccid.bulk_header.bulkout.dwLength > IO_CCID_DATA_BUFFER_SIZE) - { /* Check if length of data to be sent by host is > buffer size */ - - /* Too long data received.... Error ! */ - G_io_ccid.Ccid_BulkState = CCID_STATE_UNCORRECT_LENGTH; - } - else - // everything received in the first packet - if (G_io_ccid.UsbMessageLength == (G_io_ccid.bulk_header.bulkout.dwLength + CCID_HEADER_SIZE)) { - /* Short message, less than the EP Out Size, execute the command, - if parameter like dwLength is too big, the appropriate command will - give an error */ - CCID_CmdDecode(pdev); - } - else - { /* Long message, receive additional data with command */ - G_io_ccid.Ccid_BulkState = CCID_STATE_RECEIVE_DATA; - G_io_ccid.pUsbMessageBuffer += dataLen-CCID_HEADER_SIZE; /* Point to new offset */ - } - } - break; - - case CCID_STATE_RECEIVE_DATA: - - USBD_LL_PrepareReceive(pdev, CCID_BULK_OUT_EP, CCID_BULK_EPOUT_SIZE); - - G_io_ccid.UsbMessageLength += dataLen; - - if (dataLen < CCID_BULK_EPOUT_SIZE) - {/* Short message, less than the EP Out Size, execute the command, - if parameter like dwLength is too big, the appropriate command will - give an error */ - - /* Full command is received, process the Command */ - os_memmove(G_io_ccid.pUsbMessageBuffer, buffer, dataLen); - CCID_CmdDecode(pdev); - } - else //if (dataLen == CCID_BULK_EPOUT_SIZE) - { - if (G_io_ccid.UsbMessageLength < (G_io_ccid.bulk_header.bulkout.dwLength + CCID_HEADER_SIZE)) - { - os_memmove(G_io_ccid.pUsbMessageBuffer, buffer, dataLen); - G_io_ccid.pUsbMessageBuffer += dataLen; - /* Increment the pointer to receive more data */ - - /* Prepare EP to Receive next Cmd */ - // not timeout compliant // USBD_LL_PrepareReceive(pdev, CCID_BULK_OUT_EP, CCID_BULK_EPOUT_SIZE); - } - else if (G_io_ccid.UsbMessageLength == (G_io_ccid.bulk_header.bulkout.dwLength + CCID_HEADER_SIZE)) - { - /* Full command is received, process the Command */ - os_memmove(G_io_ccid.pUsbMessageBuffer, buffer, dataLen); - CCID_CmdDecode(pdev); - } - else - { - /* Too long data received.... Error ! */ - G_io_ccid.Ccid_BulkState = CCID_STATE_UNCORRECT_LENGTH; - } - } - - break; - - /* - case CCID_STATE_UNCORRECT_LENGTH: - G_io_ccid.Ccid_BulkState = CCID_STATE_IDLE; - break; - - default: - - break; - */ - } - } -} - -/** - * @brief CCID_CmdDecode - * Parse the commands and Proccess command - * @param pdev: device instance - * @retval None - */ -void CCID_CmdDecode(USBD_HandleTypeDef *pdev) -{ - uint8_t errorCode; - - switch (G_io_ccid.bulk_header.bulkout.bMessageType) - { - case PC_TO_RDR_ICCPOWERON: - errorCode = PC_to_RDR_IccPowerOn(); - RDR_to_PC_DataBlock(errorCode); - break; - case PC_TO_RDR_ICCPOWEROFF: - errorCode = PC_to_RDR_IccPowerOff(); - RDR_to_PC_SlotStatus(errorCode); - break; - case PC_TO_RDR_GETSLOTSTATUS: - errorCode = PC_to_RDR_GetSlotStatus(); - RDR_to_PC_SlotStatus(errorCode); - break; - case PC_TO_RDR_XFRBLOCK: - errorCode = PC_to_RDR_XfrBlock(); - // asynchronous // RDR_to_PC_DataBlock(errorCode); - break; - case PC_TO_RDR_GETPARAMETERS: - errorCode = PC_to_RDR_GetParameters(); - RDR_to_PC_Parameters(errorCode); - break; - case PC_TO_RDR_RESETPARAMETERS: - errorCode = PC_to_RDR_ResetParameters(); - RDR_to_PC_Parameters(errorCode); - break; - case PC_TO_RDR_SETPARAMETERS: - errorCode = PC_to_RDR_SetParameters(); - RDR_to_PC_Parameters(errorCode); - break; - case PC_TO_RDR_ESCAPE: - errorCode = PC_to_RDR_Escape(); - RDR_to_PC_Escape(errorCode); - break; - case PC_TO_RDR_ICCCLOCK: - errorCode = PC_to_RDR_IccClock(); - RDR_to_PC_SlotStatus(errorCode); - break; - case PC_TO_RDR_ABORT: - errorCode = PC_to_RDR_Abort(); - RDR_to_PC_SlotStatus(errorCode); - break; - case PC_TO_RDR_T0APDU: - errorCode = PC_TO_RDR_T0Apdu(); - RDR_to_PC_SlotStatus(errorCode); - break; - case PC_TO_RDR_MECHANICAL: - errorCode = PC_TO_RDR_Mechanical(); - RDR_to_PC_SlotStatus(errorCode); - break; - case PC_TO_RDR_SETDATARATEANDCLOCKFREQUENCY: - errorCode = PC_TO_RDR_SetDataRateAndClockFrequency(); - RDR_to_PC_DataRateAndClockFrequency(errorCode); - break; - case PC_TO_RDR_SECURE: - errorCode = PC_TO_RDR_Secure(); - // asynchronous // RDR_to_PC_DataBlock(errorCode); - break; - default: - RDR_to_PC_SlotStatus(SLOTERROR_CMD_NOT_SUPPORTED); - break; - } - - CCID_Send_Reply(pdev); -} - -/** - * @brief Transfer_Data_Request - * Prepare the request response to be sent to the host - * @param uint8_t* dataPointer: Pointer to the data buffer to send - * @param uint16_t dataLen : number of bytes to send - * @retval None - */ -void Transfer_Data_Request(void) -{ - /********** Update Global Variables ***************/ - G_io_ccid.Ccid_BulkState = CCID_STATE_SEND_RESP; -} - - -/** - * @brief CCID_Response_SendData - * Send the data on bulk-in EP - * @param pdev: device instance - * @param uint8_t* buf: pointer to data buffer - * @param uint16_t len: Data Length - * @retval None - */ -static void CCID_Response_SendData(USBD_HandleTypeDef *pdev, - uint8_t* buf, - uint16_t len) -{ - UNUSED(pdev); - // don't ask the MCU to perform bulk split, we could quickly get into a buffer overflow - if (len > CCID_BULK_EPIN_SIZE) { - THROW(EXCEPTION_IO_OVERFLOW); - } - - G_io_seproxyhal_spi_buffer[0] = SEPROXYHAL_TAG_USB_EP_PREPARE; - G_io_seproxyhal_spi_buffer[1] = (3+len)>>8; - G_io_seproxyhal_spi_buffer[2] = (3+len); - G_io_seproxyhal_spi_buffer[3] = CCID_BULK_IN_EP; - G_io_seproxyhal_spi_buffer[4] = SEPROXYHAL_TAG_USB_EP_PREPARE_DIR_IN; - G_io_seproxyhal_spi_buffer[5] = len; - io_seproxyhal_spi_send(G_io_seproxyhal_spi_buffer, 6); - io_seproxyhal_spi_send(buf, len); -} - -#ifdef HAVE_CCID_INTERRUPT -/** - * @brief CCID_IntMessage - * Send the Interrupt-IN data to the host - * @param pdev: device instance - * @retval None - */ -void CCID_IntMessage(USBD_HandleTypeDef *pdev) -{ - UNUSED(pdev); - /* Check if there us change in Smartcard Slot status */ - if ( CCID_IsSlotStatusChange() && CCID_IsIntrTransferComplete() ) - { -#ifdef HAVE_CCID_INTERRUPT - /* Check Slot Status is changed. Card is Removed/ Fitted */ - RDR_to_PC_NotifySlotChange(); -#endif // HAVE_CCID_INTERRUPT - - CCID_SetIntrTransferStatus(0); /* Reset the Status */ - CCID_UpdSlotChange(0); /* Reset the Status of Slot Change */ - - G_io_seproxyhal_spi_buffer[0] = SEPROXYHAL_TAG_USB_EP_PREPARE; - G_io_seproxyhal_spi_buffer[1] = (3+2)>>8; - G_io_seproxyhal_spi_buffer[2] = (3+2); - G_io_seproxyhal_spi_buffer[3] = CCID_INTR_IN_EP; - G_io_seproxyhal_spi_buffer[4] = SEPROXYHAL_TAG_USB_EP_PREPARE_DIR_IN; - G_io_seproxyhal_spi_buffer[5] = 2; - io_seproxyhal_spi_send(G_io_seproxyhal_spi_buffer, 6); - io_seproxyhal_spi_send(G_io_ccid.UsbIntMessageBuffer, 2); - } -} - -/** - * @brief CCID_IsIntrTransferComplete - * Provides the status of previous Interrupt transfer status - * @param None - * @retval uint8_t PrevXferComplete_IntrIn: Value of the previous transfer status - */ -uint8_t CCID_IsIntrTransferComplete (void) -{ - return G_io_ccid.PrevXferComplete_IntrIn; -} - -/** - * @brief CCID_IsIntrTransferComplete - * Set the value of the Interrupt transfer status - * @param uint8_t xfer_Status: Value of the Interrupt transfer status to set - * @retval None - */ -void CCID_SetIntrTransferStatus (uint8_t xfer_Status) -{ - G_io_ccid.PrevXferComplete_IntrIn = xfer_Status; -} -#endif // HAVE_CCID_INTERRUPT - - - - - - -uint8_t SC_Detect(void) { - return G_io_ccid.ccid_card_inserted; -} - -void SC_InitParams (void) { - // nothing to do -} - -uint8_t SC_SetParams (Protocol0_DataStructure_t* pt0) { - UNUSED(pt0); - return SLOT_NO_ERROR; -} - - -uint8_t SC_SetClock (uint8_t bClockCommand) { - UNUSED(bClockCommand); - return SLOT_NO_ERROR; -} - -uint8_t SC_Request_GetClockFrequencies(uint8_t* pbuf, uint16_t* len); -uint8_t SC_Request_GetDataRates(uint8_t* pbuf, uint16_t* len); -uint8_t SC_T0Apdu(uint8_t bmChanges, uint8_t bClassGetResponse, - uint8_t bClassEnvelope) { - UNUSED(bmChanges); - UNUSED(bClassGetResponse); - UNUSED(bClassEnvelope); - return SLOTERROR_CMD_NOT_SUPPORTED; -} -uint8_t SC_Mechanical(uint8_t bFunction) { - UNUSED(bFunction); - return SLOTERROR_CMD_NOT_SUPPORTED; -} -uint8_t SC_SetDataRateAndClockFrequency(uint32_t dwClockFrequency, - uint32_t dwDataRate) { - UNUSED(dwClockFrequency); - UNUSED(dwDataRate); - return SLOT_NO_ERROR; -} -uint8_t SC_Secure(uint32_t dwLength, uint8_t bBWI, uint16_t wLevelParameter, - uint8_t* pbuf, uint32_t* returnLen ) { - UNUSED(bBWI); - UNUSED(wLevelParameter); - UNUSED(returnLen); - // return SLOTERROR_CMD_NOT_SUPPORTED; - uint16_t ret_len,off; - switch(pbuf[0]) { - case 0: // verify pin - off = 15; - //ret_len = dwLength - 15; - ret_len = 5; - break; - case 1: // modify pin - switch(pbuf[11]) { - case 3: - off = 20; - break; - case 2: - case 1: - off = 19; - break; - // 0 and 4-0xFF - default: - off = 18; - break; - } - //ret_len = dwLength - off; - ret_len = 5; - break; - default: // unsupported - G_io_ccid.bulk_header.bulkin.dwLength = 0; - RDR_to_PC_DataBlock(SLOTERROR_CMD_NOT_SUPPORTED); - CCID_Send_Reply(&USBD_Device); - return SLOTERROR_CMD_NOT_SUPPORTED; - } - pbuf += off; - pbuf[0] = 0xEF; - return SC_XferBlock(pbuf, ret_len, &ret_len); -} - -// prepare the apdu to be processed by the application -uint8_t SC_XferBlock (uint8_t* ptrBlock, uint32_t blockLen, uint16_t* expectedLen) { - UNUSED(expectedLen); - - // check for overflow - if (blockLen > IO_APDU_BUFFER_SIZE) { - return SLOTERROR_BAD_LENTGH; - } - - // copy received apdu // if G_io_ccid_data_buffer is the buffer apdu, then the memmove will do nothing - os_memmove(G_io_apdu_buffer, ptrBlock, blockLen); - G_io_app.apdu_length = blockLen; - G_io_app.apdu_media = IO_APDU_MEDIA_USB_CCID; // for application code - G_io_app.apdu_state = APDU_USB_CCID; // for next call to io_exchange - - return SLOT_NO_ERROR; -} - -void io_usb_ccid_reply(unsigned char* buffer, unsigned short length) { - // avoid memory overflow - if (length > IO_CCID_DATA_BUFFER_SIZE) { - THROW(EXCEPTION_IO_OVERFLOW); - } - // copy the responde apdu - os_memmove(G_io_ccid_data_buffer, buffer, length); - G_io_ccid.bulk_header.bulkin.dwLength = length; - // forge reply - RDR_to_PC_DataBlock(SLOT_NO_ERROR); - - // start sending rpely - CCID_Send_Reply(&USBD_Device); -} - -// ask for power on -void io_usb_ccid_set_card_inserted(unsigned int inserted) { - G_io_ccid.ccid_card_inserted = inserted; - CCID_UpdSlotChange(1); -#ifdef HAVE_CCID_INTERRUPT - CCID_IntMessage(&USBD_Device); -#endif // HAVE_CCID_INTERRUPT -} - - - - - - - -#endif // HAVE_USB_CLASS_CCID - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/src/sdk/usbd_ccid_impl.h b/src/sdk/usbd_ccid_impl.h deleted file mode 100644 index eb525e6..0000000 --- a/src/sdk/usbd_ccid_impl.h +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* -* Ledger Nano S - Secure firmware -* (c) 2019 Ledger -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ -#ifndef USBD_CCID_IMPL_H -#define USBD_CCID_IMPL_H - -#ifdef HAVE_USB_CLASS_CCID - -// ================================================ -// CCID - -#define TPDU_EXCHANGE 0x01 -#define SHORT_APDU_EXCHANGE 0x02 -#define EXTENDED_APDU_EXCHANGE 0x04 -#define CHARACTER_EXCHANGE 0x00 - -#define EXCHANGE_LEVEL_FEATURE SHORT_APDU_EXCHANGE - -#define CCID_INTF 2 -#define CCID_BULK_IN_EP 0x83 -#define CCID_BULK_EPIN_SIZE 64 -#define CCID_BULK_OUT_EP 0x03 -#define CCID_BULK_EPOUT_SIZE 64 - -#ifdef HAVE_CCID_INTERRUPT -#define CCID_INTR_IN_EP 0x84 -#define CCID_INTR_EPIN_SIZE 16 -#endif // HAVE_CCID_INTERRUPT - -#define IO_CCID_DATA_BUFFER_SIZE IO_APDU_BUFFER_SIZE -#define G_io_ccid_data_buffer G_io_apdu_buffer - -#endif // HAVE_USB_CLASS_CCID - -#endif // USBD_CCID_IMPL_H diff --git a/src/sdk/usbd_hid_impl.h b/src/sdk/usbd_hid_impl.h deleted file mode 100644 index 35fdab6..0000000 --- a/src/sdk/usbd_hid_impl.h +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************* -* Ledger Nano S - Secure firmware -* (c) 2019 Ledger -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -********************************************************************************/ -#ifndef USBD_HID_IMPL_H -#define USBD_HID_IMPL_H - -// ================================================ -// HIDGEN - -#define HID_INTF 0 - -#define HID_EPIN_ADDR 0x82 -#define HID_EPIN_SIZE 0x40 - -#define HID_EPOUT_ADDR 0x02 -#define HID_EPOUT_SIZE 0x40 - -#ifdef HAVE_IO_U2F -// ================================================ -// HID U2F - -#define U2F_INTF 1 - -#define U2F_EPIN_ADDR 0x81 -#define U2F_EPIN_SIZE 0x40 - -#define U2F_EPOUT_ADDR 0x01 -#define U2F_EPOUT_SIZE 0x40 -#endif // HAVE_IO_U2F - -#ifdef HAVE_WEBUSB - -#define WEBUSB_EPIN_ADDR 0x83 -#define WEBUSB_EPIN_SIZE 0x40 -#define WEBUSB_EPOUT_ADDR 0x03 -#define WEBUSB_EPOUT_SIZE 0x40 - -#ifdef HAVE_USB_CLASS_CCID - #error Unsupported CCID+WEBUSB, not enough endpoints -#endif // HAVE_USB_CLASS_CCID - -#ifdef HAVE_IO_U2F -#define WEBUSB_INTF 2 -#else // HAVE_IO_U2F -#define WEBUSB_INTF 1 -#endif // HAVE_IO_U2F -#endif // HAVE_WEBUSB - -#endif // USBD_HID_IMPL_H - diff --git a/src/sdk/usbd_impl.c b/src/sdk/usbd_impl.c deleted file mode 100644 index b2be118..0000000 --- a/src/sdk/usbd_impl.c +++ /dev/null @@ -1,1455 +0,0 @@ -/** - ****************************************************************************** - * @file usbd_hid.c - * @author MCD Application Team - * @version V2.2.0 - * @date 13-June-2014 - * @brief This file provides the HID core functions. - * - * @verbatim - * - * =================================================================== - * HID Class Description - * =================================================================== - * This module manages the HID class V1.11 following the "Device Class Definition - * for Human Interface Devices (HID) Version 1.11 Jun 27, 2001". - * This driver implements the following aspects of the specification: - * - The Boot Interface Subclass - * - Usage Page : Generic Desktop - * - Usage : Vendor - * - Collection : Application - * - * @note In HS mode and when the DMA is used, all variables and data structures - * dealing with the DMA during the transaction process should be 32-bit aligned. - * - * - * @endverbatim - * - ****************************************************************************** - * @attention - * - *

© COPYRIGHT 2014 STMicroelectronics

- * - * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.st.com/software_license_agreement_liberty_v2 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - ****************************************************************************** - */ -#pragma message "Override SDK source file :" __FILE__ - -/* With https://github.com/LedgerHQ/ledger-secure-sdk/pull/188 - * a new feature was implemented to allow HID communication with a - * manually passed destination buffer. - * This feature changed the API of HID communication and was backported - * on all SDK version starting from: - * - nanos: v2.1.0-12 - * - API_LEVEL_1: v1.9.0 - * - API_LEVEL_5: v5.7.0 - * - API_LEVEL >= 9 always been available - */ -#define HAVE_LOCAL_APDU_BUFFER_FEATURE - -#include "os.h" - -#include "os_io_usb.h" - -/* Includes ------------------------------------------------------------------*/ - -#include "usbd_hid.h" -#include "usbd_hid_impl.h" - -#include "usbd_ctlreq.h" - -#include "usbd_core.h" -#include "usbd_conf.h" - -#include "usbd_def.h" -#include "os_io_seproxyhal.h" - -#ifdef HAVE_IO_U2F -#include "u2f_transport.h" -#include "u2f_impl.h" -#endif // HAVE_IO_U2F - -#ifdef HAVE_USB_CLASS_CCID -#include "usbd_ccid_core.h" -#endif // HAVE_USB_CLASS_CCID - - -/** @addtogroup STM32_USB_DEVICE_LIBRARY - * @{ - */ - - -/** @defgroup USBD_HID - * @brief usbd core module - * @{ - */ - -/** @defgroup USBD_HID_Private_TypesDefinitions - * @{ - */ -/** - * @} - */ - - -/** @defgroup USBD_HID_Private_Defines - * @{ - */ - -/** - * @} - */ - - -/** @defgroup USBD_HID_Private_Macros - * @{ - */ -/** - * @} - */ -/** @defgroup USBD_HID_Private_FunctionPrototypes - * @{ - */ - - -/** - * @} - */ - -/** @defgroup USBD_HID_Private_Variables - * @{ - */ - -#define USBD_LANGID_STRING 0x409 - -#ifdef HAVE_VID_PID_PROBER -#define USBD_VID 0x2581 -#define USBD_PID 0xf1d1 -#else -#define USBD_VID 0x2C97 -#if defined(TARGET_BLUE) -#define USBD_PID 0x0000 -static uint8_t const USBD_PRODUCT_FS_STRING[] = { - 4*2+2, - USB_DESC_TYPE_STRING, - 'B', 0, - 'l', 0, - 'u', 0, - 'e', 0, -}; - -#elif defined(TARGET_NANOS) -#ifndef HAVE_LEGACY_PID -#define USBD_PID 0x1000 -#else // HAVE_LEGACY_PID -#define USBD_PID 0x0001 -#endif // HAVE_LEGACY_PID -static uint8_t const USBD_PRODUCT_FS_STRING[] = { - 6*2+2, - USB_DESC_TYPE_STRING, - 'N', 0, - 'a', 0, - 'n', 0, - 'o', 0, - ' ', 0, - 'S', 0, -}; -#elif defined(TARGET_ARAMIS) -#ifndef HAVE_LEGACY_PID -#define USBD_PID 0x2000 -#else // HAVE_LEGACY_PID -#define USBD_PID 0x0002 -#endif // HAVE_LEGACY_PID -static uint8_t const USBD_PRODUCT_FS_STRING[] = { - 6*2+2, - USB_DESC_TYPE_STRING, - 'A', 0, - 'r', 0, - 'a', 0, - 'm', 0, - 'i', 0, - 's', 0, -}; -#elif defined(TARGET_HW2) -#ifndef HAVE_LEGACY_PID -#define USBD_PID 0x3000 -#else // HAVE_LEGACY_PID -#define USBD_PID 0x0003 -#endif // HAVE_LEGACY_PID -static uint8_t const USBD_PRODUCT_FS_STRING[] = { - 3*2+2, - USB_DESC_TYPE_STRING, - 'H', 0, - 'W', 0, - '2', 0, -}; -#elif defined(TARGET_NANOX) -#ifndef HAVE_LEGACY_PID -#define USBD_PID 0x4000 -#else // HAVE_LEGACY_PID -#define USBD_PID 0x0004 -#endif // HAVE_LEGACY_PID -static uint8_t const USBD_PRODUCT_FS_STRING[] = { - 6*2+2, - USB_DESC_TYPE_STRING, - 'N', 0, - 'a', 0, - 'n', 0, - 'o', 0, - ' ', 0, - 'X', 0, -}; -#elif defined(TARGET_NANOS2) -#ifndef HAVE_LEGACY_PID -#define USBD_PID 0x5000 -#else // HAVE_LEGACY_PID -#define USBD_PID 0x0005 -#endif // HAVE_LEGACY_PID -static uint8_t const USBD_PRODUCT_FS_STRING[] = { - 7*2+2, - USB_DESC_TYPE_STRING, - 'N', 0, - 'a', 0, - 'n', 0, - 'o', 0, - ' ', 0, - 'S', 0, - 'P', 0, -}; -#else -#error unknown TARGET_ID -#endif -#endif - -/* USB Standard Device Descriptor */ -static uint8_t const USBD_LangIDDesc[]= -{ - USB_LEN_LANGID_STR_DESC, - USB_DESC_TYPE_STRING, - LOBYTE(USBD_LANGID_STRING), - HIBYTE(USBD_LANGID_STRING), -}; - -static uint8_t const USB_SERIAL_STRING[] = -{ - 4*2+2, - USB_DESC_TYPE_STRING, - '0', 0, - '0', 0, - '0', 0, - '1', 0, -}; - -static uint8_t const USBD_MANUFACTURER_STRING[] = { - 6*2+2, - USB_DESC_TYPE_STRING, - 'L', 0, - 'e', 0, - 'd', 0, - 'g', 0, - 'e', 0, - 'r', 0, -}; - -#define USBD_INTERFACE_FS_STRING USBD_PRODUCT_FS_STRING -#define USBD_CONFIGURATION_FS_STRING USBD_PRODUCT_FS_STRING - -static uint8_t const HID_ReportDesc[] = { - 0x06, 0xA0, 0xFF, // Usage page (vendor defined) - 0x09, 0x01, // Usage ID (vendor defined) - 0xA1, 0x01, // Collection (application) - - // The Input report - 0x09, 0x03, // Usage ID - vendor defined - 0x15, 0x00, // Logical Minimum (0) - 0x26, 0xFF, 0x00, // Logical Maximum (255) - 0x75, 0x08, // Report Size (8 bits) - 0x95, HID_EPIN_SIZE, // Report Count (64 fields) - 0x81, 0x08, // Input (Data, Variable, Absolute) - - // The Output report - 0x09, 0x04, // Usage ID - vendor defined - 0x15, 0x00, // Logical Minimum (0) - 0x26, 0xFF, 0x00, // Logical Maximum (255) - 0x75, 0x08, // Report Size (8 bits) - 0x95, HID_EPOUT_SIZE, // Report Count (64 fields) - 0x91, 0x08, // Output (Data, Variable, Absolute) - 0xC0 -}; - -#ifdef HAVE_IO_U2F -static uint8_t const HID_ReportDesc_fido[] = { - 0x06, 0xD0, 0xF1, // Usage page (vendor defined) - 0x09, 0x01, // Usage ID (vendor defined) - 0xA1, 0x01, // Collection (application) - - // The Input report - 0x09, 0x03, // Usage ID - vendor defined - 0x15, 0x00, // Logical Minimum (0) - 0x26, 0xFF, 0x00, // Logical Maximum (255) - 0x75, 0x08, // Report Size (8 bits) - 0x95, U2F_EPIN_SIZE, // Report Count (64 fields) - 0x81, 0x08, // Input (Data, Variable, Absolute) - - // The Output report - 0x09, 0x04, // Usage ID - vendor defined - 0x15, 0x00, // Logical Minimum (0) - 0x26, 0xFF, 0x00, // Logical Maximum (255) - 0x75, 0x08, // Report Size (8 bits) - 0x95, U2F_EPOUT_SIZE, // Report Count (64 fields) - 0x91, 0x08, // Output (Data, Variable, Absolute) - 0xC0 -}; -#endif // HAVE_IO_U2F - -#define ARRAY_U2LE(l) (l)&0xFF, (l)>>8 - -/* USB HID device Configuration Descriptor */ -static __ALIGN_BEGIN uint8_t const N_USBD_CfgDesc[] __ALIGN_END = -{ - 0x09, /* bLength: Configuration Descriptor size */ - USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ - ARRAY_U2LE(0x9 /* wTotalLength: Bytes returned */ - +0x9+0x9+0x7+0x7 -#ifdef HAVE_IO_U2F - +0x9+0x9+0x7+0x7 -#endif // HAVE_IO_U2F -#ifdef HAVE_USB_CLASS_CCID - +0x9+0x36+0x7+0x7 -#endif // HAVE_USB_CLASS_CCID -#ifdef HAVE_WEBUSB - +0x9+0x7+0x7 -#endif // HAVE_WEBUSB - ), - 1 -#ifdef HAVE_IO_U2F - +1 -#endif // HAVE_IO_U2F -#ifdef HAVE_USB_CLASS_CCID - +1 -#endif // HAVE_USB_CLASS_CCID -#ifdef HAVE_WEBUSB - +1 -#endif // HAVE_WEBUSB - , /*bNumInterfaces */ - 0x01, /*bConfigurationValue: Configuration value*/ - USBD_IDX_PRODUCT_STR, /*iConfiguration: Index of string descriptor describing the configuration*/ - 0xC0, /*bmAttributes: bus powered */ - 0x32, /*MaxPower 100 mA: this current is used for detecting Vbus*/ - - /* HIDGEN ################################################################################################ */ - - /************** Descriptor of KBD HID interface ****************/ - 0x09, /*bLength: Interface Descriptor size*/ - USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/ - HID_INTF, /*bInterfaceNumber: Number of Interface*/ - 0x00, /*bAlternateSetting: Alternate setting*/ - 0x02, /*bNumEndpoints*/ - 0x03, /*bInterfaceClass: HID*/ - 0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/ - 0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ - USBD_IDX_PRODUCT_STR, /*iInterface: Index of string descriptor*/ - - /******************** Descriptor of HID *************************/ - 0x09, /*bLength: HID Descriptor size*/ - HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ - 0x11, /*bHIDUSTOM_HID: HID Class Spec release number*/ - 0x01, - 0x00, /*bCountryCode: Hardware target country*/ - 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ - 0x22, /*bDescriptorType*/ - sizeof(HID_ReportDesc),/*wItemLength: Total length of Report descriptor*/ - 0x00, - - /******************** Descriptor of Custom HID endpoints ********************/ - 0x07, /*bLength: Endpoint Descriptor size*/ - USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ - HID_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ - 0x03, /*bmAttributes: Interrupt endpoint*/ - HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */ - 0x00, - 0x01, /*bInterval: Polling Interval (20 ms)*/ - - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: */ - HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/ - 0x03, /* bmAttributes: Interrupt endpoint */ - HID_EPOUT_SIZE, /* wMaxPacketSize: 2 Bytes max */ - 0x00, - 0x01, /* bInterval: Polling Interval (20 ms) */ - -#ifdef HAVE_IO_U2F - /* HID FIDO ################################################################################################ */ - - /************** Descriptor of HID FIDO interface ****************/ - 0x09, /*bLength: Interface Descriptor size*/ - USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/ - U2F_INTF, /*bInterfaceNumber: Number of Interface*/ - 0x00, /*bAlternateSetting: Alternate setting*/ - 0x02, /*bNumEndpoints*/ - 0x03, /*bInterfaceClass: HID*/ - 0x01, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/ - 0x01, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ - USBD_IDX_PRODUCT_STR, /*iInterface: Index of string descriptor*/ - - /******************** Descriptor of HID *************************/ - 0x09, /*bLength: HID Descriptor size*/ - HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ - 0x11, /*bHIDUSTOM_HID: HID Class Spec release number*/ - 0x01, - 0x21, /*bCountryCode: Hardware target country*/ // 0x21: US, 0x08: FR, 0x0D: ISO Intl - 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ - 0x22, /*bDescriptorType*/ - sizeof(HID_ReportDesc_fido),/*wItemLength: Total length of Report descriptor*/ - 0x00, - /******************** Descriptor of Custom HID endpoints ********************/ - 0x07, /*bLength: Endpoint Descriptor size*/ - USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ - U2F_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ - 0x03, /*bmAttributes: Interrupt endpoint*/ - U2F_EPIN_SIZE, /*wMaxPacketSize: */ - 0x00, - 0x01, /*bInterval: Polling Interval */ - - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: */ - U2F_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/ - 0x03, /* bmAttributes: Interrupt endpoint */ - U2F_EPOUT_SIZE, /* wMaxPacketSize: */ - 0x00, - 0x01,/* bInterval: Polling Interval */ -#endif // HAVE_IO_U2F - -#ifdef HAVE_USB_CLASS_CCID - /* CCID ################################################################################################ */ - - /******************** CCID **** interface ********************/ - 0x09, /* bLength: Interface Descriptor size */ - 0x04, /* bDescriptorType: */ - CCID_INTF, /* bInterfaceNumber: Number of Interface */ - 0x00, /* bAlternateSetting: Alternate setting */ - 0x02, /* bNumEndpoints: endpoints used */ - 0x0B, /* bInterfaceClass: user's interface for CCID */ - 0x00, /* bInterfaceSubClass : */ - 0x00, /* nInterfaceProtocol : None */ - 0x05, /* iInterface: */ - - /******************* CCID class descriptor ********************/ - 0x36, /* bLength: CCID Descriptor size */ - 0x21, /* bDescriptorType: Functional Descriptor type. */ - 0x10, /* bcdCCID(LSB): CCID Class Spec release number (1.00) */ - 0x01, /* bcdCCID(MSB) */ - - 0x00, /* bMaxSlotIndex :highest available slot on this device */ - 0x03, /* bVoltageSupport: bit Wise OR for 01h-5.0V 02h-3.0V - 04h 1.8V*/ - - 0x01,0x00,0x00,0x00, /* dwProtocols: 0001h = Protocol T=0 */ - 0x10,0x0E,0x00,0x00, /* dwDefaultClock: 3.6Mhz = 3600kHz = 0x0E10, - for 4 Mhz the value is (0x00000FA0) : - This is used in ETU and waiting time calculations*/ - 0x10,0x0E,0x00,0x00, /* dwMaximumClock: Maximum supported ICC clock frequency - in KHz. So, 3.6Mhz = 3600kHz = 0x0E10, - 4 Mhz (0x00000FA0) : */ - 0x00, /* bNumClockSupported : no setting from PC - If the value is 00h, the - supported clock frequencies are assumed to be the - default clock frequency defined by dwDefaultClock - and the maximum clock frequency defined by - dwMaximumClock */ - - 0xCD,0x25,0x00,0x00, /* dwDataRate: Default ICC I/O data rate in bps - 9677 bps = 0x25CD - for example 10752 bps (0x00002A00) */ - - 0xCD,0x25,0x00,0x00, /* dwMaxDataRate: Maximum supported ICC I/O data - rate in bps */ - 0x00, /* bNumDataRatesSupported : - The number of data rates that are supported by the CCID - If the value is 00h, all data rates between the default - data rate dwDataRate and the maximum data rate - dwMaxDataRate are supported. - Dont support GET_CLOCK_FREQUENCIES - */ - //46 - 0x00,0x00,0x00,0x00, /* dwMaxIFSD: 0 (T=0 only) */ - 0x00,0x00,0x00,0x00, /* dwSynchProtocols */ - 0x00,0x00,0x00,0x00, /* dwMechanical: no special characteristics */ - - 0xBA, 0x06, 0x02, 0x00, - //0x38,0x00,EXCHANGE_LEVEL_FEATURE,0x00, - /* dwFeatures: clk, baud rate, voltage : automatic */ - /* 00000008h Automatic ICC voltage selection - 00000010h Automatic ICC clock frequency change - 00000020h Automatic baud rate change according to - active parameters provided by the Host or self - determined 00000100h CCID can set - ICC in clock stop mode - - Only one of the following values may be present to - select a level of exchange: - 00010000h TPDU level exchanges with CCID - 00020000h Short APDU level exchange with CCID - 00040000h Short and Extended APDU level exchange - If none of those values : character level of exchange*/ - 0x0F,0x01,0x00,0x00, /* dwMaxCCIDMessageLength: Maximum block size + header*/ - /* 261 + 10 */ - - 0x00, /* bClassGetResponse*/ - 0x00, /* bClassEnvelope */ - 0x00,0x00, /* wLcdLayout : 0000h no LCD. */ - 0x00, /* bPINSupport : no PIN verif and modif */ - 0x01, /* bMaxCCIDBusySlots */ - - /******************** CCID Endpoints ********************/ - 0x07, /*Endpoint descriptor length = 7*/ - 0x05, /*Endpoint descriptor type */ - CCID_BULK_IN_EP, /*Endpoint address (IN, address 1) */ - 0x02, /*Bulk endpoint type */ - LOBYTE(CCID_BULK_EPIN_SIZE), - HIBYTE(CCID_BULK_EPIN_SIZE), - 0x00, /*Polling interval in milliseconds */ - - 0x07, /*Endpoint descriptor length = 7 */ - 0x05, /*Endpoint descriptor type */ - CCID_BULK_OUT_EP, /*Endpoint address (OUT, address 1) */ - 0x02, /*Bulk endpoint type */ - LOBYTE(CCID_BULK_EPOUT_SIZE), - HIBYTE(CCID_BULK_EPOUT_SIZE), - 0x00, /*Polling interval in milliseconds*/ -#endif // HAVE_USB_CLASS_CCID - -#ifdef HAVE_WEBUSB - /* WEBUSB ################################################################################################ */ - - /************** Descriptor of WEBUSB interface ****************/ - 0x09, /*bLength: Interface Descriptor size*/ - USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/ - WEBUSB_INTF, /*bInterfaceNumber: Number of Interface*/ - 0x00, /*bAlternateSetting: Alternate setting*/ - 0x02, /*bNumEndpoints*/ - 0xFF, /*bInterfaceClass: WINUSB*/ - 0xFF, /*bInterfaceSubClass : WINUSB*/ - 0xFF, /*nInterfaceProtocol : WINUSB*/ - USBD_IDX_PRODUCT_STR, /*iInterface: Index of string descriptor*/ - - /******************** Descriptor of endpoints ********************/ - 0x07, /*bLength: Endpoint Descriptor size*/ - USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ - WEBUSB_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ - 0x03, /*bmAttributes: Interrupt endpoint*/ - WEBUSB_EPIN_SIZE, /*wMaxPacketSize: */ - 0x00, - 0x01, /*bInterval: Polling Interval */ - - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: */ - WEBUSB_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/ - 0x03, /* bmAttributes: Interrupt endpoint */ - WEBUSB_EPOUT_SIZE, /* wMaxPacketSize: */ - 0x00, - 0x01,/* bInterval: Polling Interval */ -#endif // HAVE_WEBUSB -} ; - -#ifdef HAVE_IO_U2F -/* USB HID device Configuration Descriptor */ -__ALIGN_BEGIN uint8_t const USBD_HID_Desc_fido[] __ALIGN_END = -{ - /******************** Descriptor of HID *************************/ - 0x09, /*bLength: HID Descriptor size*/ - HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ - 0x11, /*bHIDUSTOM_HID: HID Class Spec release number*/ - 0x01, - 0x21, /*bCountryCode: Hardware target country*/ // 0x21: US, 0x08: FR, 0x0D: ISO Intl - 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ - 0x22, /*bDescriptorType*/ - sizeof(HID_ReportDesc_fido),/*wItemLength: Total length of Report descriptor*/ - 0x00, -}; -#endif // HAVE_IO_U2F - -/* USB HID device Configuration Descriptor */ -__ALIGN_BEGIN uint8_t const USBD_HID_Desc[] __ALIGN_END = -{ - /* 18 */ - 0x09, /*bLength: HID Descriptor size*/ - HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/ - 0x11, /*bHIDUSTOM_HID: HID Class Spec release number*/ - 0x01, - 0x00, /*bCountryCode: Hardware target country*/ - 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/ - 0x22, /*bDescriptorType*/ - sizeof(HID_ReportDesc),/*wItemLength: Total length of Report descriptor*/ - 0x00, -}; - -/* USB Standard Device Descriptor */ -static __ALIGN_BEGIN uint8_t const USBD_DeviceQualifierDesc[] __ALIGN_END = -{ - USB_LEN_DEV_QUALIFIER_DESC, - USB_DESC_TYPE_DEVICE_QUALIFIER, - 0x00, - 0x02, - 0x00, - 0x00, - 0x00, - 0x40, - 0x01, - 0x00, -}; - -/* USB Standard Device Descriptor */ -static uint8_t const USBD_DeviceDesc[]= { - 0x12, /* bLength */ - USB_DESC_TYPE_DEVICE, /* bDescriptorType */ -#ifdef HAVE_WEBUSB - 0x10, /* bcdUSB */ -#else // HAVE_WEBUSB - 0x00, /* bcdUSB */ -#endif // HAVE_WEBUSB - 0x02, - 0x00, /* bDeviceClass */ - 0x00, /* bDeviceSubClass */ - 0x00, /* bDeviceProtocol */ - USB_MAX_EP0_SIZE, /* bMaxPacketSize */ - LOBYTE(USBD_VID), /* idVendor */ - HIBYTE(USBD_VID), /* idVendor */ -#if defined(HAVE_VID_PID_PROBER) || defined(HAVE_LEGACY_PID) - LOBYTE(USBD_PID), /* idProduct */ -#else // HAVE_VID_PID_PROBER || defined(HAVE_LEGACY_PID) - LOBYTE(USBD_PID | 0x01 -#ifdef HAVE_IO_U2F - | 0x04 -#endif // HAVE_IO_U2F -#ifdef HAVE_USB_CLASS_CCID - | 0x08 -#endif // HAVE_USB_CLASS_CCID -#ifdef HAVE_WEBUSB - | 0x10 -#endif // HAVE_WEBUSB - ), -#endif // HAVE_VID_PID_PROBER || HAVE_LEGACY_PID - HIBYTE(USBD_PID), /* idProduct */ - - - // Change this ID to make windows WINUSB/WEBUSB reenumerate when the - // descriptor changes and the PID/VID are not changed. - 0x01, /* bcdDevice rel. 2.01 */ - 0x02, - USBD_IDX_MFC_STR, /* Index of manufacturer string */ - USBD_IDX_PRODUCT_STR, /* Index of product string */ - USBD_IDX_SERIAL_STR, /* Index of serial number string */ - 1 /* bNumConfigurations */ -}; /* USB_DeviceDescriptor */ - - -/** - * @brief Returns the device descriptor. - * @param speed: Current device speed - * @param length: Pointer to data length variable - * @retval Pointer to descriptor buffer - */ -uint8_t *USBD_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) -{ - UNUSED(speed); - *length = sizeof(USBD_DeviceDesc); - return (uint8_t*)USBD_DeviceDesc; -} - -/** - * @brief Returns the LangID string descriptor. - * @param speed: Current device speed - * @param length: Pointer to data length variable - * @retval Pointer to descriptor buffer - */ -uint8_t *USBD_LangIDStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) -{ - UNUSED(speed); - *length = sizeof(USBD_LangIDDesc); - return (uint8_t*)USBD_LangIDDesc; -} - -/** - * @brief Returns the product string descriptor. - * @param speed: Current device speed - * @param length: Pointer to data length variable - * @retval Pointer to descriptor buffer - */ -uint8_t *USBD_ProductStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) -{ - UNUSED(speed); - *length = sizeof(USBD_PRODUCT_FS_STRING); - return (uint8_t*)USBD_PRODUCT_FS_STRING; -} - -/** - * @brief Returns the manufacturer string descriptor. - * @param speed: Current device speed - * @param length: Pointer to data length variable - * @retval Pointer to descriptor buffer - */ -uint8_t *USBD_ManufacturerStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) -{ - UNUSED(speed); - *length = sizeof(USBD_MANUFACTURER_STRING); - return (uint8_t*)USBD_MANUFACTURER_STRING; -} - -/** - * @brief Returns the serial number string descriptor. - * @param speed: Current device speed - * @param length: Pointer to data length variable - * @retval Pointer to descriptor buffer - */ -uint8_t *USBD_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) -{ - UNUSED(speed); - *length = sizeof(USB_SERIAL_STRING); - return (uint8_t*)USB_SERIAL_STRING; -} - -/** - * @brief Returns the configuration string descriptor. - * @param speed: Current device speed - * @param length: Pointer to data length variable - * @retval Pointer to descriptor buffer - */ -uint8_t *USBD_ConfigStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) -{ - UNUSED(speed); - *length = sizeof(USBD_CONFIGURATION_FS_STRING); - return (uint8_t*)USBD_CONFIGURATION_FS_STRING; -} - -/** - * @brief Returns the interface string descriptor. - * @param speed: Current device speed - * @param length: Pointer to data length variable - * @retval Pointer to descriptor buffer - */ -uint8_t *USBD_InterfaceStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) -{ - UNUSED(speed); - *length = sizeof(USBD_INTERFACE_FS_STRING); - return (uint8_t*)USBD_INTERFACE_FS_STRING; -} - -/** -* @brief DeviceQualifierDescriptor -* return Device Qualifier descriptor -* @param length : pointer data length -* @retval pointer to descriptor buffer -*/ -uint8_t *USBD_GetDeviceQualifierDesc_impl (uint16_t *length) -{ - *length = sizeof (USBD_DeviceQualifierDesc); - return (uint8_t*)USBD_DeviceQualifierDesc; -} - -/** - * @brief USBD_CUSTOM_HID_GetCfgDesc - * return configuration descriptor - * @param speed : current device speed - * @param length : pointer data length - * @retval pointer to descriptor buffer - */ -uint8_t *USBD_GetCfgDesc_impl (uint16_t *length) -{ - *length = sizeof (N_USBD_CfgDesc); - return (uint8_t*)N_USBD_CfgDesc; -} - -uint8_t* USBD_HID_GetHidDescriptor_impl(uint16_t* len) { - switch (USBD_Device.request.wIndex&0xFF) { -#ifdef HAVE_IO_U2F - case U2F_INTF: - *len = sizeof(USBD_HID_Desc_fido); - return (uint8_t*)USBD_HID_Desc_fido; -#endif // HAVE_IO_U2F - case HID_INTF: - *len = sizeof(USBD_HID_Desc); - return (uint8_t*)USBD_HID_Desc; - } - *len = 0; - return 0; -} - -uint8_t* USBD_HID_GetReportDescriptor_impl(uint16_t* len) { - switch (USBD_Device.request.wIndex&0xFF) { -#ifdef HAVE_IO_U2F - case U2F_INTF: - - // very dirty work due to lack of callback when USB_HID_Init is called - USBD_LL_OpenEP(&USBD_Device, - U2F_EPIN_ADDR, - USBD_EP_TYPE_INTR, - U2F_EPIN_SIZE); - - USBD_LL_OpenEP(&USBD_Device, - U2F_EPOUT_ADDR, - USBD_EP_TYPE_INTR, - U2F_EPOUT_SIZE); - - /* Prepare Out endpoint to receive 1st packet */ - USBD_LL_PrepareReceive(&USBD_Device, U2F_EPOUT_ADDR, U2F_EPOUT_SIZE); - - - *len = sizeof(HID_ReportDesc_fido); - return (uint8_t*)HID_ReportDesc_fido; -#endif // HAVE_IO_U2F - case HID_INTF: - *len = sizeof(HID_ReportDesc); - return (uint8_t*)HID_ReportDesc; - } - *len = 0; - return 0; -} - -/** - * @} - */ - - -/** - * @brief USBD_HID_DataOut - * handle data OUT Stage - * @param pdev: device instance - * @param epnum: endpoint index - * @retval status - * - * This function is the default behavior for our implementation when data are sent over the out hid endpoint - */ - -#ifdef HAVE_IO_U2F - -/** - * @brief USBD_HID_Init - * Initialize the HID interface - * @param pdev: device instance - * @param cfgidx: Configuration index - * @retval status - */ -uint8_t USBD_U2F_Init (USBD_HandleTypeDef *pdev, - uint8_t cfgidx) -{ - UNUSED(cfgidx); - - /* Open EP IN */ - USBD_LL_OpenEP(pdev, - U2F_EPIN_ADDR, - USBD_EP_TYPE_INTR, - U2F_EPIN_SIZE); - - /* Open EP OUT */ - USBD_LL_OpenEP(pdev, - U2F_EPOUT_ADDR, - USBD_EP_TYPE_INTR, - U2F_EPOUT_SIZE); - - /* Prepare Out endpoint to receive 1st packet */ - USBD_LL_PrepareReceive(pdev, U2F_EPOUT_ADDR, U2F_EPOUT_SIZE); - - return USBD_OK; -} - -uint8_t USBD_U2F_DataIn_impl (USBD_HandleTypeDef *pdev, - uint8_t epnum) -{ - UNUSED(pdev); - // only the data hid endpoint will receive data - switch (epnum) { - // FIDO endpoint - case (U2F_EPIN_ADDR&0x7F): - // advance the u2f sending machine state - u2f_transport_sent(&G_io_u2f, U2F_MEDIA_USB); - break; - } - return USBD_OK; -} - -uint8_t USBD_U2F_DataOut_impl (USBD_HandleTypeDef *pdev, - uint8_t epnum, uint8_t* buffer) -{ - switch (epnum) { - // FIDO endpoint - case (U2F_EPOUT_ADDR&0x7F): - USBD_LL_PrepareReceive(pdev, U2F_EPOUT_ADDR , U2F_EPOUT_SIZE); - u2f_transport_received(&G_io_u2f, buffer, io_seproxyhal_get_ep_rx_size(U2F_EPOUT_ADDR), U2F_MEDIA_USB); - break; - } - - return USBD_OK; -} -#endif // HAVE_IO_U2F - -uint8_t USBD_HID_DataIn_impl (USBD_HandleTypeDef *pdev, - uint8_t epnum) -{ - UNUSED(pdev); - switch (epnum) { - // HID gen endpoint - case (HID_EPIN_ADDR&0x7F): - io_usb_hid_sent(io_usb_send_apdu_data); - break; - } - - return USBD_OK; -} - - -#ifdef HAVE_LOCAL_APDU_BUFFER_FEATURE -uint8_t USBD_HID_DataOut_impl (USBD_HandleTypeDef *pdev, - uint8_t epnum, uint8_t* buffer, - apdu_buffer_t * apdu_buf) -#else -uint8_t USBD_HID_DataOut_impl (USBD_HandleTypeDef *pdev, - uint8_t epnum, uint8_t* buffer) -#endif -{ - // only the data hid endpoint will receive data - switch (epnum) { - - // HID gen endpoint - case (HID_EPOUT_ADDR&0x7F): - // prepare receiving the next chunk (masked time) - USBD_LL_PrepareReceive(pdev, HID_EPOUT_ADDR , HID_EPOUT_SIZE); - - // avoid troubles when an apdu has not been replied yet - if (G_io_app.apdu_media == IO_APDU_MEDIA_NONE) { - // add to the hid transport -#ifdef HAVE_LOCAL_APDU_BUFFER_FEATURE - switch(io_usb_hid_receive(io_usb_send_apdu_data, buffer, io_seproxyhal_get_ep_rx_size(HID_EPOUT_ADDR), apdu_buf)) { -#else - switch(io_usb_hid_receive(io_usb_send_apdu_data, buffer, io_seproxyhal_get_ep_rx_size(HID_EPOUT_ADDR))) { -#endif - default: - break; - - case IO_USB_APDU_RECEIVED: - G_io_app.apdu_media = IO_APDU_MEDIA_USB_HID; // for application code - G_io_app.apdu_state = APDU_USB_HID; // for next call to io_exchange - G_io_app.apdu_length = G_io_usb_hid_total_length; - break; - } - } - break; - } - - return USBD_OK; -} - -#ifdef HAVE_WEBUSB - -uint8_t USBD_WEBUSB_Init (USBD_HandleTypeDef *pdev, - uint8_t cfgidx) -{ - UNUSED(cfgidx); - - /* Open EP IN */ - USBD_LL_OpenEP(pdev, - WEBUSB_EPIN_ADDR, - USBD_EP_TYPE_INTR, - WEBUSB_EPIN_SIZE); - - /* Open EP OUT */ - USBD_LL_OpenEP(pdev, - WEBUSB_EPOUT_ADDR, - USBD_EP_TYPE_INTR, - WEBUSB_EPOUT_SIZE); - - /* Prepare Out endpoint to receive 1st packet */ - USBD_LL_PrepareReceive(pdev, WEBUSB_EPOUT_ADDR, WEBUSB_EPOUT_SIZE); - - return USBD_OK; -} - -uint8_t USBD_WEBUSB_DeInit (USBD_HandleTypeDef *pdev, - uint8_t cfgidx) { - UNUSED(pdev); - UNUSED(cfgidx); - return USBD_OK; -} - -uint8_t USBD_WEBUSB_Setup (USBD_HandleTypeDef *pdev, - USBD_SetupReqTypedef *req) -{ - UNUSED(pdev); - UNUSED(req); - return USBD_OK; -} - -uint8_t USBD_WEBUSB_DataIn (USBD_HandleTypeDef *pdev, - uint8_t epnum) -{ - UNUSED(pdev); - switch (epnum) { - // HID gen endpoint - case (WEBUSB_EPIN_ADDR&0x7F): - io_usb_hid_sent(io_usb_send_apdu_data_ep0x83); - break; - } - return USBD_OK; -} - -uint8_t USBD_WEBUSB_DataOut (USBD_HandleTypeDef *pdev, - uint8_t epnum, uint8_t* buffer) -{ - // only the data hid endpoint will receive data - switch (epnum) { - - // HID gen endpoint - case (WEBUSB_EPOUT_ADDR&0x7F): - // prepare receiving the next chunk (masked time) - USBD_LL_PrepareReceive(pdev, WEBUSB_EPOUT_ADDR, WEBUSB_EPOUT_SIZE); - - // avoid troubles when an apdu has not been replied yet - if (G_io_app.apdu_media == IO_APDU_MEDIA_NONE) { - // add to the hid transport - switch(io_usb_hid_receive(io_usb_send_apdu_data_ep0x83, buffer, io_seproxyhal_get_ep_rx_size(WEBUSB_EPOUT_ADDR))) { - default: - break; - - case IO_USB_APDU_RECEIVED: - G_io_app.apdu_media = IO_APDU_MEDIA_USB_WEBUSB; // for application code - G_io_app.apdu_state = APDU_USB_WEBUSB; // for next call to io_exchange - G_io_app.apdu_length = G_io_usb_hid_total_length; - break; - } - } - break; - } - - return USBD_OK; -} -// arbitrary vendor choosen -#define WEBUSB_VENDOR_CODE 0x1E - -// from https://wicg.github.io/webusb/#webusb-platform-capability-descriptor -// see also this (for endianness explanation) -// https://github.com/WICG/webusb/issues/115#issuecomment-352206549 -#define WEBUSB_UUID 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47,0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65 - -#define WEBUSB_REQ_GET_URL 0x02 - -#define WEBUSB_DT_DESCRIPTOR_SET_HEADER 0 -#define WEBUSB_DT_CONFIGURATION_SUBSET_HEADER 1 -#define WEBUSB_DT_FUNCTION_SUBSET_HEADER 2 -#define WEBUSB_DT_URL 3 - -#define WEBUSB_URL_SCHEME_HTTP 0 -#define WEBUSB_URL_SCHEME_HTTPS 1 -#define WEBUSB_URL_SCHEME_CUSTOM 255 - -unsigned char const C_webusb_url_descriptor[] = { - // bLength - 3 + WEBUSB_URL_SIZE_B, - // bDescriptorType - WEBUSB_DT_URL, - // bScheme - WEBUSB_URL_SCHEME_HTTPS, - // URL - WEBUSB_URL -}; - -/* USB 3.1 Descriptor Types - Table 9-6 */ -#define USB_DT_BOS 15 -#define USB_DT_DEVICE_CAPABILITY 16 - -#define USB_DT_BOS_SIZE 5 - -/* USB Device Capability Types - USB 3.1 Table 9-14 */ -#define USB_DC_PLATFORM 5 - -#define MS_OS_20_DESCRIPTOR_LENGTH (0xb2) - -#define WINUSB_VENDOR_CODE 0x77 - -unsigned char const C_usb_bos[] = { - USB_DT_BOS_SIZE, // bLength (5) - USB_DT_BOS, // bDescriptorType - 0x39, 0x00, // wTotalLength - 2, //bNumberDeviceCapabilities - - // capability descriptor - 8+16, // bLength - USB_DT_DEVICE_CAPABILITY, // bDescriptorType - USB_DC_PLATFORM, // bDevCapability - 0, // bReserved - WEBUSB_UUID, // UUID[16] - 0x00, // bcdVersion - 0x01, - WEBUSB_VENDOR_CODE, // bVencordCode -#if WEBUSB_URL_SIZE_B > 0 - 1, // iLandingPage -#else // WEBUSB_URL_SIZE_B - 0, // iLandingPage, no url to retrieve -#endif // WEBUSB_URL_SIZE_B - - // Microsoft OS 2.0 Platform Capability Descriptor - 0x1C, // Descriptor size (28 bytes) - 0x10, // Descriptor type (Device Capability) - 0x05, // Capability type (Platform) - 0x00, // Reserved - - // MS OS 2.0 Platform Capability ID (D8DD60DF-4589-4CC7-9CD2-659D9E648A9F) - 0xDF, 0x60, 0xDD, 0xD8, - 0x89, 0x45, - 0xC7, 0x4C, - 0x9C, 0xD2, - 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, - - 0x00, 0x00, 0x03, 0x06, // Windows version (8.1) (0x06030000) - MS_OS_20_DESCRIPTOR_LENGTH, 0x00, - WINUSB_VENDOR_CODE, // Vendor-assigned bMS_VendorCode - 0x00 // Doesn’t support alternate enumeration -}; -#endif // HAVE_WEBUSB -static uint8_t *USBD_BOSDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) -{ - UNUSED(speed); -#ifdef HAVE_WEBUSB - *length = sizeof(C_usb_bos); - return (uint8_t*)C_usb_bos; -#else - *length = 0; - return NULL; -#endif -} - -/** @defgroup USBD_HID_Private_Functions - * @{ - */ - -// note: how core lib usb calls the hid class -USBD_DescriptorsTypeDef const HID_Desc = { - USBD_DeviceDescriptor, - USBD_LangIDStrDescriptor, - USBD_ManufacturerStrDescriptor, - USBD_ProductStrDescriptor, - USBD_SerialStrDescriptor, - USBD_ConfigStrDescriptor, - USBD_InterfaceStrDescriptor, - USBD_BOSDescriptor, -}; - -#ifdef HAVE_IO_U2F -static USBD_ClassTypeDef const USBD_U2F = -{ - USBD_U2F_Init, - USBD_HID_DeInit, - USBD_HID_Setup, - NULL, /*EP0_TxSent*/ - NULL, /*EP0_RxReady*/ /* STATUS STAGE IN */ - USBD_U2F_DataIn_impl, /*DataIn*/ - USBD_U2F_DataOut_impl, /*DataOut*/ - NULL, /*SOF */ - NULL, - NULL, - USBD_GetCfgDesc_impl, - USBD_GetCfgDesc_impl, - USBD_GetCfgDesc_impl, - USBD_GetDeviceQualifierDesc_impl, -}; -#endif // HAVE_IO_U2F - -static USBD_ClassTypeDef const USBD_HID = -{ - USBD_HID_Init, - USBD_HID_DeInit, - USBD_HID_Setup, - NULL, /*EP0_TxSent*/ - NULL, /*EP0_RxReady*/ /* STATUS STAGE IN */ - USBD_HID_DataIn_impl, /*DataIn*/ - USBD_HID_DataOut_impl, /*DataOut*/ - NULL, /*SOF */ - NULL, - NULL, - USBD_GetCfgDesc_impl, - USBD_GetCfgDesc_impl, - USBD_GetCfgDesc_impl, - USBD_GetDeviceQualifierDesc_impl, -}; - -#ifdef HAVE_WEBUSB - -static const unsigned char C_winusb_string_descriptor[] = { - // bLength - 0x12, - // bDescriptorType - USB_DESC_TYPE_STRING, - // wData - 'M', 0x00, 'S', 0x00, 'F', 0x00, 'T', 0x00, '1', 0x00, '0', 0x00, '0', 0x00, WINUSB_VENDOR_CODE, 0x00, // MSFT100 -}; - -// Microsoft OS 2.0 descriptor wIndex values -#define MS_OS_20_DESCRIPTOR_INDEX 0x07 - -// Microsoft OS 2.0 descriptor types -#define MS_OS_20_SUBSET_HEADER_CONFIGURATION 0x01 -#define MS_OS_20_SUBSET_HEADER_FUNCTION 0x02 -#define MS_OS_20_FEATURE_COMPATIBLE_ID 0x03 -#define MS_OS_20_FEATURE_REG_PROPERTY 0x04 - -static const unsigned char C_winusb_request_descriptor[] = { - // Microsoft OS 2.0 descriptor set header (table 10) - 0x0A, - 0x00, // Descriptor size (10 bytes) - 0x00, - 0x00, // MS OS 2.0 descriptor set header - 0x00, - 0x00, - 0x03, - 0x06, // Windows version (8.1) (0x06030000) - MS_OS_20_DESCRIPTOR_LENGTH, - 0x00, // Size, MS OS 2.0 descriptor set - - // Microsoft OS 2.0 configuration subset header - 0x08, 0x00, // Descriptor size (8 bytes) - MS_OS_20_SUBSET_HEADER_CONFIGURATION, 0x00, // MS OS 2.0 configuration subset header - 0x00, // bConfigurationValue - 0x00, // Reserved - 0xA8, 0x00, // Size, MS OS 2.0 configuration subset - - // Microsoft OS 2.0 function subset header - 0x08, 0x00, // Descriptor size (8 bytes) - MS_OS_20_SUBSET_HEADER_FUNCTION, 0x00, // MS OS 2.0 function subset header - WEBUSB_INTF, // first Interface impacted by this function - - 0x00, // Reserved - 0xA0, 0x00, // Size, MS OS 2.0 function subset - - // Microsoft OS 2.0 compatible ID descriptor (table 13) - 0x14, 0x00, // wLength - MS_OS_20_FEATURE_COMPATIBLE_ID, 0x00, // MS_OS_20_FEATURE_COMPATIBLE_ID - 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x84, 0x00, //wLength: - MS_OS_20_FEATURE_REG_PROPERTY, 0x00, // wDescriptorType: MS_OS_20_FEATURE_REG_PROPERTY: 0x04 (Table 9) - 0x07, 0x00, //wPropertyDataType: REG_MULTI_SZ (Table 15) - 0x2a, 0x00, //wPropertyNameLength: - //bPropertyName: “DeviceInterfaceGUID” - 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00, - 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, - 0x00, 0x00, - 0x50, 0x00, // wPropertyDataLength - //bPropertyData: “{CE809264-4B24-4E81-A8B2-57ED01D580E1}”. - '{', 0x00, 'C', 0x00, 'E', 0x00, '8', 0x00, '0', 0x00, '9', 0x00, '2', 0x00, '6', 0x00, '4', 0x00, '-', 0x00, - '4', 0x00, 'B', 0x00, '2', 0x00, '4', 0x00, '-', 0x00, '4', 0x00, 'E', 0x00, '8', 0x00, '1', 0x00, '-', 0x00, - 'A', 0x00, '8', 0x00, 'B', 0x00, '2', 0x00, '-', 0x00, '5', 0x00, '7', 0x00, 'E', 0x00, 'D', 0x00, '0', 0x00, - '1', 0x00, 'D', 0x00, '5', 0x00, '8', 0x00, '0', 0x00, 'E', 0x00, '1', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00 -}; - -#define WINUSB_GET_COMPATIBLE_ID_FEATURE 0x04 -static const unsigned char C_winusb_wcid[] = { - // header - 0x28, 0x00, 0x00, 0x00, // dwLength - 0x00, 0x01, // bcdVersion - 0x04, 0x00, // wIndex - 0x01, // bNumSections - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved - // functions - WEBUSB_INTF, // bInterfaceNumber - 0x01, // reserved - 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, // compatibleId - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, // subCompatibleId - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved -}; - -#define WINUSB_GET_EXTENDED_PROPERTIES_OS_FEATURE 0x05 -static const unsigned char C_winusb_guid[] = { - // header - 0x92, 0x00, 0x00, 0x00, // dwLength - 0x00, 0x01, // bcdVersion - 0x05, 0x00, // wIndex - 0x01, 0x00, // wNumFeatures - // features - 0x88, 0x00, 0x00, 0x00, // dwLength - 0x07, 0x00, 0x00, 0x00, // dwPropertyDataType - 0x2A, 0x00, // wNameLength - 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, - 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, - 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, - 'D', 0x00, 's', 0x00, 0x00, 0x00, // .name, unicode nul terminated - 0x50, 0x00, 0x00, 0x00, // dwPropertyDataLength - // Same as BLE char: 13d63400-2C97-0004-0000-4c6564676572 - '{', 0x00, '1', 0x00, '3', 0x00, 'd', 0x00, '6', 0x00, '3', 0x00, - '4', 0x00, '0', 0x00, '0', 0x00, '-', 0x00, '2', 0x00, 'C', 0x00, - '9', 0x00, '7', 0x00, '-', 0x00, '0', 0x00, '0', 0x00, '0', 0x00, - '4', 0x00, '-', 0x00, '0', 0x00, '0', 0x00, '0', 0x00, '0', 0x00, - '-', 0x00, '4', 0x00, 'c', 0x00, '6', 0x00, '5', 0x00, '6', 0x00, - '4', 0x00, '6', 0x00, '7', 0x00, '6', 0x00, '5', 0x00, '7', 0x00, - '2', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00 // propertyData, double unicode nul terminated -}; - -// upon unsupported request, check for webusb request -void USBD_CtlError( USBD_HandleTypeDef *pdev , USBD_SetupReqTypedef *req) { -#if WEBUSB_URL_SIZE_B > 0 - if ((req->bmRequest & 0x80) && req->bRequest == WEBUSB_VENDOR_CODE && req->wIndex == WEBUSB_REQ_GET_URL - // HTTPS url - && req->wValue == 1) { - // return the URL descriptor - USBD_CtlSendData (pdev, (unsigned char*)C_webusb_url_descriptor, MIN(req->wLength, sizeof(C_webusb_url_descriptor))); - } - else -#endif // WEBUSB_URL_SIZE_B - // SETUP (LE): 0x80 0x06 0x03 0x77 0x00 0x00 0xXX 0xXX - if ((req->bmRequest & 0x80) - && req->bRequest == USB_REQ_GET_DESCRIPTOR - && (req->wValue>>8) == USB_DESC_TYPE_STRING - && (req->wValue & 0xFF) == 0xEE) { - USBD_CtlSendData(pdev, (unsigned char*)C_winusb_string_descriptor, MIN(req->wLength, sizeof(C_winusb_string_descriptor))); - } - // SETUP (LE): 0x80 0x77 0x04 0x00 0x00 0x00 0xXX 0xXX - else if ((req->bmRequest & 0x80) - && req->bRequest == WINUSB_VENDOR_CODE - && req->wIndex == WINUSB_GET_COMPATIBLE_ID_FEATURE) { - USBD_CtlSendData(pdev, (unsigned char*)C_winusb_wcid, MIN(req->wLength, sizeof(C_winusb_wcid))); - } - // SETUP (LE): 0x80 0x77 0x05 0x00 0x00 0x00 0xXX 0xXX - else if ((req->bmRequest & 0x80) - && req->bRequest == WINUSB_VENDOR_CODE - && req->wIndex == WINUSB_GET_EXTENDED_PROPERTIES_OS_FEATURE - ) { - USBD_CtlSendData(pdev, (unsigned char*)C_winusb_guid, MIN(req->wLength, sizeof(C_winusb_guid))); - } - // Microsoft OS 2.0 Descriptors for Windows 8.1 and Windows 10 - else if ((req->bmRequest & 0x80) - && req->bRequest == WINUSB_VENDOR_CODE - && req->wIndex == MS_OS_20_DESCRIPTOR_INDEX) { - USBD_CtlSendData(pdev, (unsigned char*)C_winusb_request_descriptor, MIN(req->wLength, sizeof(C_winusb_request_descriptor))); - } - else { - USBD_CtlStall(pdev); - } -} - -static const USBD_ClassTypeDef USBD_WEBUSB = -{ - USBD_WEBUSB_Init, - USBD_WEBUSB_DeInit, - USBD_WEBUSB_Setup, - NULL, /*EP0_TxSent*/ - NULL, /*EP0_RxReady*/ - USBD_WEBUSB_DataIn, - USBD_WEBUSB_DataOut, - NULL, /*SOF */ - NULL, /*ISOIn*/ - NULL, /*ISOOut*/ - USBD_GetCfgDesc_impl, - USBD_GetCfgDesc_impl, - USBD_GetCfgDesc_impl, - USBD_GetDeviceQualifierDesc_impl, -}; - -#endif // HAVE_WEBUSB - -#ifdef HAVE_USB_CLASS_CCID -static const USBD_ClassTypeDef USBD_CCID = -{ - USBD_CCID_Init, - USBD_CCID_DeInit, - USBD_CCID_Setup, - NULL, /*EP0_TxSent*/ - NULL, /*EP0_RxReady*/ - USBD_CCID_DataIn, - USBD_CCID_DataOut, - NULL, /*SOF */ - NULL, /*ISOIn*/ - NULL, /*ISOOut*/ - USBD_GetCfgDesc_impl, - USBD_GetCfgDesc_impl, - USBD_GetCfgDesc_impl, - USBD_GetDeviceQualifierDesc_impl, -}; - -uint8_t SC_AnswerToReset (uint8_t voltage, uint8_t* atr_buffer) { - UNUSED(voltage); - // return the atr length - atr_buffer[0] = 0x3B; - atr_buffer[1] = 0; - return 2; -} - -void SC_Poweroff(void) { - // nothing to do ? -} - -uint8_t SC_ExecuteEscape (uint8_t* escapePtr, uint32_t escapeLen, - uint8_t* responseBuff, - uint16_t* responseLen) { - UNUSED(escapePtr); - UNUSED(escapeLen); - UNUSED(responseBuff); - UNUSED(responseLen); - // nothing to do ? - return 0; -} -#endif // HAVE_USB_CLASS_CCID - -void USB_power(unsigned char enabled) { - os_memset(&USBD_Device, 0, sizeof(USBD_Device)); - -//#if TARGET_ID == 0x33000004 - // init timeouts and other global fields - os_memset(G_io_app.usb_ep_xfer_len, 0, sizeof(G_io_app.usb_ep_xfer_len)); - os_memset(G_io_app.usb_ep_timeouts, 0, sizeof(G_io_app.usb_ep_timeouts)); -//#endif - - if (enabled) { - os_memset(&USBD_Device, 0, sizeof(USBD_Device)); - /* Init Device Library */ - USBD_Init(&USBD_Device, (USBD_DescriptorsTypeDef*)&HID_Desc, 0); - - /* Register the HID class */ - USBD_RegisterClassForInterface(HID_INTF, &USBD_Device, (USBD_ClassTypeDef*)&USBD_HID); -#ifdef HAVE_IO_U2F - USBD_RegisterClassForInterface(U2F_INTF, &USBD_Device, (USBD_ClassTypeDef*)&USBD_U2F); - // initialize the U2F tunnel transport - u2f_transport_init(&G_io_u2f, G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); -#endif // HAVE_IO_U2F -#ifdef HAVE_USB_CLASS_CCID - USBD_RegisterClassForInterface(CCID_INTF, &USBD_Device, (USBD_ClassTypeDef*)&USBD_CCID); -#endif // HAVE_USB_CLASS_CCID - -#ifdef HAVE_WEBUSB - USBD_RegisterClassForInterface(WEBUSB_INTF, &USBD_Device, (USBD_ClassTypeDef*)&USBD_WEBUSB); - USBD_LL_PrepareReceive(&USBD_Device, WEBUSB_EPOUT_ADDR , WEBUSB_EPOUT_SIZE); -#endif // HAVE_WEBUSB - - /* Start Device Process */ - USBD_Start(&USBD_Device); - } - else { - USBD_DeInit(&USBD_Device); - } -} - -/** - * @} - */ - - -/** - * @} - */ - - -/** - * @} - */ - -/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/src/sdk/usbd_impl.h b/src/sdk/usbd_impl.h deleted file mode 100644 index a20edef..0000000 --- a/src/sdk/usbd_impl.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef USBD_IMPL_H -#define USBD_IMPL_H - - -uint8_t *USBD_GetCfgDesc_impl (uint16_t *length); - -#endif //USBD_IMPL_H \ No newline at end of file diff --git a/tests/application_client/__init__.py b/tests/application_client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/application_client/app_def.py b/tests/application_client/app_def.py new file mode 100644 index 0000000..443a0d3 --- /dev/null +++ b/tests/application_client/app_def.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests Client application. +It contains the applicatuion definitions. +""" +from enum import IntEnum + + +class ClaType(IntEnum): + """Application ID definitions""" + # Application CLA + CLA_APP = 0x00 + CLA_APP_CHAIN = 0x10 + # Special CLA for Verify with pinpad + CLA_APP_VERIFY = 0xEF + + +class InsType(IntEnum): + """Application Command ID definitions""" + INS_ACTIVATE_FILE = 0x44 + INS_SELECT = 0xA4 + INS_TERMINATE_DF = 0xE6 + INS_VERIFY = 0x20 + INS_CHANGE_REF_DATA = 0x24 + INS_RESET_RC = 0x2C + INS_GET_DATA = 0xCA + INS_PUT_DATA = 0xDA + INS_GEN_ASYM_KEYPAIR = 0x47 + INS_GET_RESPONSE = 0xC0 + INS_PSO = 0x2A + INS_INT_AUTHENTICATE = 0x88 + INS_GET_CHALLENGE = 0x84 + INS_MSE = 0x22 + + +class PubkeyAlgo(IntEnum): + """Public-Key Algorithm IDs definition""" + # https://www.rfc-editor.org/rfc/rfc4880#section-9.1 + + INVALID = 0 + + # RSA (Encrypt or Sign) + RSA = 1 + # Elliptic Curve Diffie-Hellman + ECDH = 18 + # Elliptic Curve Digital Signature Algorithm + ECDSA = 19 + # Edwards-curve Digital Signature Algorithm + EDDSA = 22 + + +class PassWord(IntEnum): + """Password type definition""" + # USER_PIN for only one PSO:CDS command + PW1 = 0x81 + # USER_PIN for several attempts + PW2 = 0x82 + # Admin PIN + PW3 = 0x83 + + +class Errors(IntEnum): + """Application Errors definitions""" + SW_STATE_TERMINATED = 0x6285 + SW_MEMORY = 0x6581 + SW_SECURITY = 0x6600 + SW_WRONG_LENGTH = 0x6700 + SW_LOGICAL_CHANNEL_NOT_SUPPORTED = 0x6881 + SW_SECURE_MESSAGING_NOT_SUPPORTED = 0x6882 + SW_LAST_COMMAND_EXPECTED = 0x6883 + SW_COMMAND_CHAINING_NOT_SUPPORTED = 0x6884 + SW_SECURITY_STATUS_NOT_SATISFIED = 0x6982 + SW_AUTH_METHOD_BLOCKED = 0x6983 + SW_DATA_INVALID = 0x6984 + SW_CONDITIONS_NOT_SATISFIED = 0x6985 + SW_COMMAND_NOT_ALLOWED = 0x6986 + SW_EXPECTED_SM_MISSING = 0x6987 + SW_SM_DATA_INCORRECT = 0x6988 + SW_WRONG_DATA = 0x6a80 + SW_FILE_NOT_FOUND = 0x6a82 + SW_INCORRECT_P1P2 = 0x6a86 + SW_REFERENCED_DATA_NOT_FOUND = 0x6a88 + SW_WRONG_P1P2 = 0x6b00 + SW_INS_NOT_SUPPORTED = 0x6d00 + SW_CLA_NOT_SUPPORTED = 0x6e00 + SW_UNKNOWN = 0x6f00 + SW_OK = 0x9000 + SW_CORRECT_LONG_RESPONSE = 0x6100 + + +class DataObject(IntEnum): + """Data Objects definition""" + + # [Read] Full Application identifier (AID), ISO 7816-4 + DO_AID = 0x4F + + # [Read/Write] Name according to ISO/IEC 7501-1) + DO_CARD_NAME = 0x5B + # [Read/Write] Login data + DO_LOGIN = 0x5E + # [Read] Cardholder Related Data + DO_CARDHOLDER_DATA = 0x65 + # [Read] Application Related Data + DO_APP_DATA = 0x6E + # [Read] Discretionary data objects + DO_DISCRET_DATA = 0x73 + + # [Read/Write] Digital signature + DO_SIG_KEY = 0xB6 + # [Read/Write] Confidentiality + DO_DEC_KEY = 0xB8 + # [Read/Write] Authentication + DO_AUT_KEY = 0xA4 + + # [Read/Write] Algorithm attributes SIGnature + DO_SIG_ATTR = 0xC1 + # [Read/Write] Algorithm attributes DECryption + DO_DEC_ATTR = 0xC2 + # [Read/Write] Algorithm attributes AUThentication + DO_AUT_ATTR = 0xC3 + # [Write] AES symmetric key + DO_KEY_AES = 0xD5 + + # [Read/Write] User Interaction Flag (UIF) for PSO:CDS + DO_UIF_SIG = 0xD6 + # [Read/Write] User Interaction Flag (UIF) for PSO:DEC + DO_UIF_DEC = 0xD7 + # [Read/Write] User Interaction Flag (UIF) for PSO:AUT + DO_UIF_AUT = 0xD8 + + # [Read/Write] Asymmetric Key Pair + DO_PUB_KEY = 0x7F49 + + # [Read/Write] Slot config + CMD_SLOT_CFG = 0x01F1 + # [Read/Write] Slot selection + CMD_SLOT_CUR = 0x01F2 + + # [Read/Write] Language preferences (according to ISO 639) + DO_CARD_LANG = 0x5F2D + # [Read/Write] Salutation (according to ISO 5218) + DO_CARD_SALUTATION = 0x5F35 + # [Read/Write] Uniform resource locator (URL, as defined in RFC 1738) + DO_URL = 0x5F50 diff --git a/tests/application_client/command_sender.py b/tests/application_client/command_sender.py new file mode 100644 index 0000000..475aedd --- /dev/null +++ b/tests/application_client/command_sender.py @@ -0,0 +1,516 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests Client application. +It contains the command sending part. +""" +from typing import Generator, Optional, Tuple +from contextlib import contextmanager + +import binascii + +from ragger.backend.interface import BackendInterface, RAPDU +from ragger.error import ExceptionRAPDU + +from application_client.app_def import ClaType, InsType, PassWord, Errors, DataObject, PubkeyAlgo + + +class CommandSender: + """Base class to send APDU to the selected backend""" + + def __init__(self, backend: BackendInterface) -> None: + self.backend = backend + + + ############### CARD interface ############### + def send_select(self) -> RAPDU: + """APDU Select + + Returns: + Response APDU + """ + + data = binascii.unhexlify(b"06D27600012401") + return self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_SELECT, + p1=0x04, + data=data) + + def send_activate(self) -> RAPDU: + """APDU Activate + + Returns: + Response APDU + """ + + return self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_ACTIVATE_FILE) + + def send_terminate(self) -> RAPDU: + """APDU Terminate + + Returns: + Response APDU + """ + + return self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_TERMINATE_DF) + + + ############### API interfaces ############### + def get_data(self, tag: DataObject) -> RAPDU: + """APDU Get Data + + Args: + tag (DataObject): Tag identifying the data to process + + Returns: + Response APDU + """ + + p1 = 0x00 if tag <= 0xFF else (tag >> 8) & 0xFF + p2 = tag & 0xFF + return self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_GET_DATA, + p1=p1, + p2=p2) + + def put_data(self, tag: DataObject, data: bytes) -> RAPDU: + """APDU Put Data + + Args: + tag (DataObject): Tag identifying the data to process + frame (bytes): Data to process + + Returns: + Response APDU + """ + + p1 = 0x00 if tag <= 0xFF else (tag >> 8) & 0xFF + p2 = tag & 0xFF + return self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_PUT_DATA, + p1=p1, + p2=p2, + data=data) + + + ############### SLOT interface ############### + def get_slot(self) -> int: + """APDU Get Slot + + Returns: + Response APDU + """ + + rapdu = self.get_data(DataObject.CMD_SLOT_CUR) + assert rapdu.status == Errors.SW_OK + return int.from_bytes(rapdu.data, "big") + + + def get_slot_config(self) -> Tuple[int, int]: + """APDU Get Slot config + + Returns: + Number of slots available and default one + """ + + rapdu = self.get_data(DataObject.CMD_SLOT_CFG) + assert rapdu.status == Errors.SW_OK + nb_slots = rapdu.data[0] + def_slot = rapdu.data[1] + return nb_slots, def_slot + + + def set_slot(self, slot: int) -> RAPDU: + """APDU Set Slot + + Args: + slot (int): Slot number (0 - 2) + + Returns: + Response APDU + """ + + assert slot >= 0 + assert slot <= 3 + data = slot.to_bytes(1, "big") + return self.put_data(DataObject.CMD_SLOT_CUR, data) + + + ############### PASSWORD interface ############### + def send_verify_pw(self, pwd: PassWord, value: str="", reset: bool=False) -> RAPDU: + """APDU Verify Pincode + + Args: + pwd (PassWord): Password type + value (str): Pincode value + reset (bool): Set the Pincode status to 'not verified' + + Returns: + Response APDU + """ + + if value: + assert reset is False + data = value.encode("utf-8") + else: + data = b"" + p1 = 0xFF if reset else 0x00 + return self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_VERIFY, + p1=p1, + p2=pwd, + data=data) + + + @contextmanager + def send_verify_pw_with_confirmation(self, pwd: PassWord) -> Generator[None, None, None]: + """APDU Verify Pincode - with confirmation + + Args: + pwd (PassWord): Password type + + Returns: + Response APDU + """ + + with self.backend.exchange_async(cla=ClaType.CLA_APP_VERIFY, + ins=InsType.INS_VERIFY, + p2=pwd) as response: + yield response + + def send_change_pw(self, pwd: PassWord, actual: str, new: str) -> RAPDU: + """APDU Change Pincode + + Args: + pwd (PassWord): Password type + actual (str): Current pincode value + new (str): New pincode value + + Returns: + Response APDU + """ + + assert actual + assert new + data = actual.encode("utf-8") + new.encode("utf-8") + return self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_CHANGE_REF_DATA, + p2=pwd, + data=data) + + def send_reset_pw(self, value: str) -> RAPDU: + """APDU Reset Retry Counter + + Args: + value (str): Reset Code + new PW1 Pincode value + + Returns: + Response APDU + """ + + assert value + data = value.encode("utf-8") + return self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_RESET_RC, + p1=0x02, + p2=PassWord.PW1, + data=data) + + + ############### Key TEMPLATE interface ############### + def set_template(self, key: DataObject, value: str): + """APDU Set Key Template + + Args: + key (DataObject): Tag identifying the key to process + value (str): String representing the OID of the key template + + Returns: + Response APDU + """ + + data = binascii.unhexlify(value) + return self.put_data(key, data) + + + ############### Perform Security Operation ############### + def set_uif(self, tag: DataObject, uif: bool): + """APDU Set User Interaction Flag + + Args: + tag (DataObject): Tag identifying the key to process + uif (bool): Enable/disable + + Returns: + Response APDU + """ + value = 1 if uif else 0 + data = value.to_bytes(2, "little") + return self.put_data(tag, data) + + + def manage_security_env(self, key: DataObject, ref: int) -> RAPDU: + """APDU Manage Security Environment + + Args: + key (DataObject): Tag identifying the key to process + ref (int): New key usage + + Returns: + Response APDU + """ + + data = b"\x83\x01" + bytes.fromhex(f"{ref:02x}") + return self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_MSE, + p1=0x41, + p2=key, + data=data) + + + def get_challenge(self, size: int) -> RAPDU: + """APDU Get Challenge + + Args: + size (int): requested Challenge size + + Returns: + Response APDU + """ + + cla = ClaType.CLA_APP + ins = InsType.INS_GET_CHALLENGE + Le = f"{size:02x}" if size < 255 else f"{size:04x}" + data = bytes.fromhex(f"{cla:02x}{ins:02x}0000{Le}") + try: + rapdu = self.backend.exchange_raw(data) + except ExceptionRAPDU as err: + rapdu = RAPDU(err.status, err.data) + + # Receive long response + return self.get_long_response(rapdu) + + + def read_key(self, key: DataObject) -> RAPDU: + """APDU Read Asymmetric Public Key + + Args: + key (DataObject): Tag identifying the key to process + + Returns: + Response APDU + """ + + return self.__key(0x81, key, False) + + + def generate_key(self, key: DataObject, seed: bool = False) -> RAPDU: + """APDU Generate Asymmetric Key pair + + Args: + key (DataObject): Tag identifying the key to process + seed (bool): Generate a key in SEED mode + + Returns: + Response APDU + """ + + return self.__key(0x80, key, seed) + + + def authenticate(self, frame: bytes) -> RAPDU: + """APDU Internal Authenticate + + Args: + frame (bytes): Data to process + + Returns: + Response APDU + """ + + return self.__pso(InsType.INS_INT_AUTHENTICATE, 0x0000, frame) + + def sign(self, frame: bytes) -> RAPDU: + """APDU Sign + + Args: + frame (bytes): Data to process + + Returns: + Response APDU + """ + + return self.__pso(InsType.INS_PSO, 0x9e9a, frame) + + def encrypt(self, frame: bytes) -> RAPDU: + """APDU Encipher + + Args: + frame (bytes): Data to process + + Returns: + Response APDU + """ + + return self.__pso(InsType.INS_PSO, 0x8680, frame) + + def decrypt(self, frame: bytes) -> RAPDU: + """APDU Decipher + + Args: + frame (bytes): Data to process + + Returns: + Response APDU + """ + + return self.__pso(InsType.INS_PSO, 0x8086, frame) + + def decrypt_asym(self, frame: bytes, algo: PubkeyAlgo = PubkeyAlgo.RSA) -> RAPDU: + """APDU Decipher with RSA + + Args: + frame (bytes): Data to process + algo (PubkeyAlgo): Public Key Algorithm + + Returns: + Response APDU + """ + + ins = InsType.INS_PSO + size = len(frame) + bFist: bool = True + # Max input size is 254 B + # Longer message uses Chaining mode APDU + # Including a Padding Indicator for the 1st message + # Including a 0x00 suffix at the end of the message + while size > 0: + if bFist: + bFist = False + # On the 1st message, add padding indicator + if algo == PubkeyAlgo.RSA: + pad_ind = b"\x00" + elif algo == PubkeyAlgo.ECDH: + pad_ind = b"\xa6" + else: + rapdu = RAPDU(Errors.SW_WRONG_DATA, b"") + break + max_len = 253 + else: + pad_ind = b"" + max_len = 254 + + if size > max_len: + d_len = max_len + cla = ClaType.CLA_APP_CHAIN + m_frame = frame[:d_len] + else: + d_len = size # Remaining len + cla = ClaType.CLA_APP + m_frame = frame[:d_len] + b"\x00" # 0x00 suffix on the last message + + data = bytes.fromhex(f"{cla:02x}{ins:02x}8086{d_len + len(pad_ind):02x}") + pad_ind + m_frame + try: + rapdu = self.backend.exchange_raw(data) + frame = frame[d_len:] + size -= d_len + except ExceptionRAPDU as err: + rapdu = RAPDU(err.status, err.data) + if rapdu.status != Errors.SW_OK: + break + + # Return result + return rapdu + + + ############### Responses ############### + def get_long_response(self, rapdu: RAPDU) -> RAPDU: + """Retrieve a synchronous Long Buffer Response + + Args: + rapdu (RAPDU): Initial Response APDU from the command + + Returns: + Response APDU + """ + + cla = ClaType.CLA_APP + ins = InsType.INS_GET_RESPONSE + rdata = rapdu.data + + # Receive long response + while (rapdu.status & 0xFF00) == Errors.SW_CORRECT_LONG_RESPONSE: + data = bytes.fromhex(f"{cla:02x}{ins:02x}0000{(rapdu.status & 0xFF):02x}") + try: + rapdu = self.backend.exchange_raw(data) + except ExceptionRAPDU as err: + if (err.status & 0xFF00) != Errors.SW_CORRECT_LONG_RESPONSE: + rapdu = RAPDU(err.status, err.data) + break + + rdata += rapdu.data + return RAPDU(rapdu.status, rdata) + + + def get_async_response(self) -> Optional[RAPDU]: + """Asynchronous APDU response + + Returns: + Response APDU + """ + + return self.backend.last_async_response + + + ############### Internal functions ############### + def __pso(self, ins: int, tag: int, frame: bytes) -> RAPDU: + """APDU Perform Security Operation + + Args: + ins (int): Value for INS parameter in APDU + tag (int): Value for P1/P2 parameter in APDU + frame (bytes): Data to process + + Returns: + Response APDU + """ + + cla = ClaType.CLA_APP + data = bytes.fromhex(f"{cla:02x}{ins:02x}{tag:04x}{len(frame):02x}") + frame + b"\x00" + try: + rapdu = self.backend.exchange_raw(data) + except ExceptionRAPDU as err: + rapdu = RAPDU(err.status, err.data) + + # Receive long response + return self.get_long_response(rapdu) + + + def __key(self, p1: int, key: DataObject, seed: bool = False) -> RAPDU: + """APDU Asymmetric Key pair + + Args: + p1 (int): Value for P1 parameter in APDU + key (DataObject): Tag identifying the key + seed (bool): Generate a key in SEED mode + + Returns: + Response APDU + """ + + data = bytes.fromhex(f"{key:02x}00") + p2 = 0x01 if seed else 0x00 + try: + rapdu = self.backend.exchange(cla=ClaType.CLA_APP, + ins=InsType.INS_GEN_ASYM_KEYPAIR, + p1=p1, + p2=p2, + data=data) + except ExceptionRAPDU as err: + rapdu = RAPDU(err.status, err.data) + + # Receive long response + return self.get_long_response(rapdu) diff --git a/tests/application_client/response_unpacker.py b/tests/application_client/response_unpacker.py new file mode 100644 index 0000000..7bb0e43 --- /dev/null +++ b/tests/application_client/response_unpacker.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests Client application. +It contains the response parsing part. +""" +from typing import Tuple + + +def _pop_sized_buf_from_buffer(buffer:bytes, size:int) -> Tuple[bytes, bytes]: + """Parse buffer and returns: remainder, data[size]""" + + return buffer[size:], buffer[0:size] + + +def unpack_info_response(response: bytes) -> Tuple[str, str]: + """Unpack response for AID: + RID (5) + Application (1) + Version (2) + Manufacturer (2) + Serial (4) + RFU (2) + """ + + assert len(response) == 16 + response, rid = _pop_sized_buf_from_buffer(response, 5) + response, app = _pop_sized_buf_from_buffer(response, 1) + response, version = _pop_sized_buf_from_buffer(response, 2) + response, manuf = _pop_sized_buf_from_buffer(response, 2) + response, serial = _pop_sized_buf_from_buffer(response, 4) + assert rid.hex() == "d276000124" + assert app.hex() == "01" + assert manuf.hex() == "2c97" + + return (version.hex(), serial.hex()) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5c67c7b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,26 @@ +# from ragger.conftest import configuration + +########################### +### CONFIGURATION START ### +########################### + +# You can configure optional parameters by overriding the value of +# ragger.configuration.OPTIONAL_CONFIGURATION +# Please refer to ragger/conftest/configuration.py for their descriptions and accepted values + +######################### +### CONFIGURATION END ### +######################### + +# Pull all features from the base ragger conftest using the overridden configuration +pytest_plugins = ("ragger.conftest.base_conftest", ) + + +########################## +# CONFIGURATION OVERRIDE # +########################## + +BACKENDS = ["speculos"] + +def pytest_addoption(parser): + parser.addoption("--full", action="store_true", help="Run full tests") diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..8220cbc --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,2 @@ +pytest +ragger[speculos,ledgerwallet] diff --git a/tests/setup.cfg b/tests/setup.cfg new file mode 100644 index 0000000..9ce679e --- /dev/null +++ b/tests/setup.cfg @@ -0,0 +1,24 @@ +[tool:pytest] +addopts = --strict-markers --strict-config +console_output_style = count +# log_cli = True + +[pylint] +disable = C0114, # missing-module-docstring + C0115, # missing-class-docstring + C0116, # missing-function-docstring + C0103, # invalid-name + R0801, # duplicate-code + R0904, # too-many-public-methods + R0913 # too-many-arguments +max-line-length=120 +extension-pkg-whitelist=hid + +[pycodestyle] +max-line-length = 100 + +[mypy-hid.*] +ignore_missing_imports = True + +[mypy-pytest.*] +ignore_missing_imports = True diff --git a/tests/snapshots/nanos/test_menu_settings/00000.png b/tests/snapshots/nanos/test_menu_settings/00000.png new file mode 100644 index 0000000..d28e716 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00000.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00001.png b/tests/snapshots/nanos/test_menu_settings/00001.png new file mode 100644 index 0000000..9e9b3b4 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00001.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00002.png b/tests/snapshots/nanos/test_menu_settings/00002.png new file mode 100644 index 0000000..ab1951c Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00002.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00003.png b/tests/snapshots/nanos/test_menu_settings/00003.png new file mode 100644 index 0000000..9ce0074 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00003.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00004.png b/tests/snapshots/nanos/test_menu_settings/00004.png new file mode 100644 index 0000000..fdf2ccd Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00004.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00005.png b/tests/snapshots/nanos/test_menu_settings/00005.png new file mode 100644 index 0000000..2129ece Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00005.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00006.png b/tests/snapshots/nanos/test_menu_settings/00006.png new file mode 100644 index 0000000..f434eb1 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00006.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00007.png b/tests/snapshots/nanos/test_menu_settings/00007.png new file mode 100644 index 0000000..280b683 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00007.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00008.png b/tests/snapshots/nanos/test_menu_settings/00008.png new file mode 100644 index 0000000..41bbb72 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00008.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00009.png b/tests/snapshots/nanos/test_menu_settings/00009.png new file mode 100644 index 0000000..81ce431 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00009.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00010.png b/tests/snapshots/nanos/test_menu_settings/00010.png new file mode 100644 index 0000000..4f7c9a6 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00010.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00011.png b/tests/snapshots/nanos/test_menu_settings/00011.png new file mode 100644 index 0000000..ae6843c Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00011.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00012.png b/tests/snapshots/nanos/test_menu_settings/00012.png new file mode 100644 index 0000000..fdf2ccd Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00012.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00013.png b/tests/snapshots/nanos/test_menu_settings/00013.png new file mode 100644 index 0000000..81ce431 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00013.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00014.png b/tests/snapshots/nanos/test_menu_settings/00014.png new file mode 100644 index 0000000..4f7c9a6 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00014.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00015.png b/tests/snapshots/nanos/test_menu_settings/00015.png new file mode 100644 index 0000000..fb3a2f4 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00015.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00016.png b/tests/snapshots/nanos/test_menu_settings/00016.png new file mode 100644 index 0000000..d28e716 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00016.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00017.png b/tests/snapshots/nanos/test_menu_settings/00017.png new file mode 100644 index 0000000..f587c79 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00017.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00018.png b/tests/snapshots/nanos/test_menu_settings/00018.png new file mode 100644 index 0000000..9f4ba09 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00018.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00019.png b/tests/snapshots/nanos/test_menu_settings/00019.png new file mode 100644 index 0000000..58b6fba Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00019.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00020.png b/tests/snapshots/nanos/test_menu_settings/00020.png new file mode 100644 index 0000000..14b8dd7 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00020.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00021.png b/tests/snapshots/nanos/test_menu_settings/00021.png new file mode 100644 index 0000000..fb3a2f4 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00021.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00022.png b/tests/snapshots/nanos/test_menu_settings/00022.png new file mode 100644 index 0000000..d28e716 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00022.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00023.png b/tests/snapshots/nanos/test_menu_settings/00023.png new file mode 100644 index 0000000..f587c79 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00023.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00024.png b/tests/snapshots/nanos/test_menu_settings/00024.png new file mode 100644 index 0000000..5a48081 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00024.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00025.png b/tests/snapshots/nanos/test_menu_settings/00025.png new file mode 100644 index 0000000..2d0e93e Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00025.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00026.png b/tests/snapshots/nanos/test_menu_settings/00026.png new file mode 100644 index 0000000..13b0211 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00026.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00027.png b/tests/snapshots/nanos/test_menu_settings/00027.png new file mode 100644 index 0000000..e943171 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00027.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00028.png b/tests/snapshots/nanos/test_menu_settings/00028.png new file mode 100644 index 0000000..1d567b9 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00028.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00029.png b/tests/snapshots/nanos/test_menu_settings/00029.png new file mode 100644 index 0000000..ff8457f Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00029.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00030.png b/tests/snapshots/nanos/test_menu_settings/00030.png new file mode 100644 index 0000000..e95ac16 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00030.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00031.png b/tests/snapshots/nanos/test_menu_settings/00031.png new file mode 100644 index 0000000..7dd11a4 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00031.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00032.png b/tests/snapshots/nanos/test_menu_settings/00032.png new file mode 100644 index 0000000..fd243cd Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00032.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00033.png b/tests/snapshots/nanos/test_menu_settings/00033.png new file mode 100644 index 0000000..ff8457f Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00033.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00034.png b/tests/snapshots/nanos/test_menu_settings/00034.png new file mode 100644 index 0000000..e95ac16 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00034.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00035.png b/tests/snapshots/nanos/test_menu_settings/00035.png new file mode 100644 index 0000000..fb3a2f4 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00035.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00036.png b/tests/snapshots/nanos/test_menu_settings/00036.png new file mode 100644 index 0000000..d28e716 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00036.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00037.png b/tests/snapshots/nanos/test_menu_settings/00037.png new file mode 100644 index 0000000..f587c79 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00037.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00038.png b/tests/snapshots/nanos/test_menu_settings/00038.png new file mode 100644 index 0000000..5a48081 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00038.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00039.png b/tests/snapshots/nanos/test_menu_settings/00039.png new file mode 100644 index 0000000..925b60b Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00039.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00040.png b/tests/snapshots/nanos/test_menu_settings/00040.png new file mode 100644 index 0000000..e6530b7 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00040.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00041.png b/tests/snapshots/nanos/test_menu_settings/00041.png new file mode 100644 index 0000000..a6dc1d0 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00041.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00042.png b/tests/snapshots/nanos/test_menu_settings/00042.png new file mode 100644 index 0000000..9131618 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00042.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00043.png b/tests/snapshots/nanos/test_menu_settings/00043.png new file mode 100644 index 0000000..aeff3d5 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00043.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00044.png b/tests/snapshots/nanos/test_menu_settings/00044.png new file mode 100644 index 0000000..2f73d32 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00044.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00045.png b/tests/snapshots/nanos/test_menu_settings/00045.png new file mode 100644 index 0000000..fb3a2f4 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00045.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00046.png b/tests/snapshots/nanos/test_menu_settings/00046.png new file mode 100644 index 0000000..d28e716 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00046.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00047.png b/tests/snapshots/nanos/test_menu_settings/00047.png new file mode 100644 index 0000000..f587c79 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00047.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00048.png b/tests/snapshots/nanos/test_menu_settings/00048.png new file mode 100644 index 0000000..5a48081 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00048.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00049.png b/tests/snapshots/nanos/test_menu_settings/00049.png new file mode 100644 index 0000000..925b60b Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00049.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00050.png b/tests/snapshots/nanos/test_menu_settings/00050.png new file mode 100644 index 0000000..686a164 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00050.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00051.png b/tests/snapshots/nanos/test_menu_settings/00051.png new file mode 100644 index 0000000..815d526 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00051.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00052.png b/tests/snapshots/nanos/test_menu_settings/00052.png new file mode 100644 index 0000000..bbbf430 Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00052.png differ diff --git a/tests/snapshots/nanos/test_menu_settings/00053.png b/tests/snapshots/nanos/test_menu_settings/00053.png new file mode 100644 index 0000000..b1d458d Binary files /dev/null and b/tests/snapshots/nanos/test_menu_settings/00053.png differ diff --git a/tests/snapshots/nanos/test_verify_confirm_accepted/00000.png b/tests/snapshots/nanos/test_verify_confirm_accepted/00000.png new file mode 100644 index 0000000..8a6820c Binary files /dev/null and b/tests/snapshots/nanos/test_verify_confirm_accepted/00000.png differ diff --git a/tests/snapshots/nanos/test_verify_confirm_refused/00000.png b/tests/snapshots/nanos/test_verify_confirm_refused/00000.png new file mode 100644 index 0000000..8a6820c Binary files /dev/null and b/tests/snapshots/nanos/test_verify_confirm_refused/00000.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00000.png b/tests/snapshots/nanosp/test_menu_settings/00000.png new file mode 100644 index 0000000..993d953 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00000.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00001.png b/tests/snapshots/nanosp/test_menu_settings/00001.png new file mode 100644 index 0000000..09d597f Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00001.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00002.png b/tests/snapshots/nanosp/test_menu_settings/00002.png new file mode 100644 index 0000000..1a2e198 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00002.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00003.png b/tests/snapshots/nanosp/test_menu_settings/00003.png new file mode 100644 index 0000000..19f4bcc Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00003.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00004.png b/tests/snapshots/nanosp/test_menu_settings/00004.png new file mode 100644 index 0000000..4f44ce5 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00004.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00005.png b/tests/snapshots/nanosp/test_menu_settings/00005.png new file mode 100644 index 0000000..fb049ec Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00005.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00006.png b/tests/snapshots/nanosp/test_menu_settings/00006.png new file mode 100644 index 0000000..99fc174 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00006.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00007.png b/tests/snapshots/nanosp/test_menu_settings/00007.png new file mode 100644 index 0000000..3ad71f8 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00007.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00008.png b/tests/snapshots/nanosp/test_menu_settings/00008.png new file mode 100644 index 0000000..e678a83 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00008.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00009.png b/tests/snapshots/nanosp/test_menu_settings/00009.png new file mode 100644 index 0000000..03a2ed8 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00009.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00010.png b/tests/snapshots/nanosp/test_menu_settings/00010.png new file mode 100644 index 0000000..ae682df Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00010.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00011.png b/tests/snapshots/nanosp/test_menu_settings/00011.png new file mode 100644 index 0000000..160a0ea Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00011.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00012.png b/tests/snapshots/nanosp/test_menu_settings/00012.png new file mode 100644 index 0000000..ae682df Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00012.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00013.png b/tests/snapshots/nanosp/test_menu_settings/00013.png new file mode 100644 index 0000000..160a0ea Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00013.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00014.png b/tests/snapshots/nanosp/test_menu_settings/00014.png new file mode 100644 index 0000000..61861f2 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00014.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00015.png b/tests/snapshots/nanosp/test_menu_settings/00015.png new file mode 100644 index 0000000..993d953 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00015.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00016.png b/tests/snapshots/nanosp/test_menu_settings/00016.png new file mode 100644 index 0000000..f14db00 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00016.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00017.png b/tests/snapshots/nanosp/test_menu_settings/00017.png new file mode 100644 index 0000000..39f8d72 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00017.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00018.png b/tests/snapshots/nanosp/test_menu_settings/00018.png new file mode 100644 index 0000000..61861f2 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00018.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00019.png b/tests/snapshots/nanosp/test_menu_settings/00019.png new file mode 100644 index 0000000..f14db00 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00019.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00020.png b/tests/snapshots/nanosp/test_menu_settings/00020.png new file mode 100644 index 0000000..f853849 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00020.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00021.png b/tests/snapshots/nanosp/test_menu_settings/00021.png new file mode 100644 index 0000000..21c1ee6 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00021.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00022.png b/tests/snapshots/nanosp/test_menu_settings/00022.png new file mode 100644 index 0000000..6d667a9 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00022.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00023.png b/tests/snapshots/nanosp/test_menu_settings/00023.png new file mode 100644 index 0000000..2d4b009 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00023.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00024.png b/tests/snapshots/nanosp/test_menu_settings/00024.png new file mode 100644 index 0000000..181fcd2 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00024.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00025.png b/tests/snapshots/nanosp/test_menu_settings/00025.png new file mode 100644 index 0000000..31947f4 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00025.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00026.png b/tests/snapshots/nanosp/test_menu_settings/00026.png new file mode 100644 index 0000000..6dfd92d Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00026.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00027.png b/tests/snapshots/nanosp/test_menu_settings/00027.png new file mode 100644 index 0000000..42a79ad Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00027.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00028.png b/tests/snapshots/nanosp/test_menu_settings/00028.png new file mode 100644 index 0000000..181fcd2 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00028.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00029.png b/tests/snapshots/nanosp/test_menu_settings/00029.png new file mode 100644 index 0000000..31947f4 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00029.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00030.png b/tests/snapshots/nanosp/test_menu_settings/00030.png new file mode 100644 index 0000000..61861f2 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00030.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00031.png b/tests/snapshots/nanosp/test_menu_settings/00031.png new file mode 100644 index 0000000..f853849 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00031.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00032.png b/tests/snapshots/nanosp/test_menu_settings/00032.png new file mode 100644 index 0000000..09f86e3 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00032.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00033.png b/tests/snapshots/nanosp/test_menu_settings/00033.png new file mode 100644 index 0000000..82790bc Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00033.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00034.png b/tests/snapshots/nanosp/test_menu_settings/00034.png new file mode 100644 index 0000000..e525706 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00034.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00035.png b/tests/snapshots/nanosp/test_menu_settings/00035.png new file mode 100644 index 0000000..1418d03 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00035.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00036.png b/tests/snapshots/nanosp/test_menu_settings/00036.png new file mode 100644 index 0000000..ecec8c2 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00036.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00037.png b/tests/snapshots/nanosp/test_menu_settings/00037.png new file mode 100644 index 0000000..61861f2 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00037.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00038.png b/tests/snapshots/nanosp/test_menu_settings/00038.png new file mode 100644 index 0000000..09f86e3 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00038.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00039.png b/tests/snapshots/nanosp/test_menu_settings/00039.png new file mode 100644 index 0000000..15222f2 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00039.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00040.png b/tests/snapshots/nanosp/test_menu_settings/00040.png new file mode 100644 index 0000000..ad5e1c1 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00040.png differ diff --git a/tests/snapshots/nanosp/test_menu_settings/00041.png b/tests/snapshots/nanosp/test_menu_settings/00041.png new file mode 100644 index 0000000..d993684 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_settings/00041.png differ diff --git a/tests/snapshots/nanosp/test_menu_slot/00000.png b/tests/snapshots/nanosp/test_menu_slot/00000.png new file mode 100644 index 0000000..e722c27 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_slot/00000.png differ diff --git a/tests/snapshots/nanosp/test_menu_slot/00001.png b/tests/snapshots/nanosp/test_menu_slot/00001.png new file mode 100644 index 0000000..a6dbaf7 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_slot/00001.png differ diff --git a/tests/snapshots/nanosp/test_menu_slot/00002.png b/tests/snapshots/nanosp/test_menu_slot/00002.png new file mode 100644 index 0000000..2c9b31c Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_slot/00002.png differ diff --git a/tests/snapshots/nanosp/test_menu_slot/00003.png b/tests/snapshots/nanosp/test_menu_slot/00003.png new file mode 100644 index 0000000..71b4a6e Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_slot/00003.png differ diff --git a/tests/snapshots/nanosp/test_menu_slot/00004.png b/tests/snapshots/nanosp/test_menu_slot/00004.png new file mode 100644 index 0000000..2dcf40b Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_slot/00004.png differ diff --git a/tests/snapshots/nanosp/test_menu_slot/00005.png b/tests/snapshots/nanosp/test_menu_slot/00005.png new file mode 100644 index 0000000..1682ad8 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_slot/00005.png differ diff --git a/tests/snapshots/nanosp/test_menu_slot/00006.png b/tests/snapshots/nanosp/test_menu_slot/00006.png new file mode 100644 index 0000000..71b4a6e Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_slot/00006.png differ diff --git a/tests/snapshots/nanosp/test_menu_slot/00007.png b/tests/snapshots/nanosp/test_menu_slot/00007.png new file mode 100644 index 0000000..2dcf40b Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_slot/00007.png differ diff --git a/tests/snapshots/nanosp/test_menu_slot/00008.png b/tests/snapshots/nanosp/test_menu_slot/00008.png new file mode 100644 index 0000000..c58dbf5 Binary files /dev/null and b/tests/snapshots/nanosp/test_menu_slot/00008.png differ diff --git a/tests/snapshots/nanosp/test_verify_confirm_accepted/00000.png b/tests/snapshots/nanosp/test_verify_confirm_accepted/00000.png new file mode 100644 index 0000000..0afdb58 Binary files /dev/null and b/tests/snapshots/nanosp/test_verify_confirm_accepted/00000.png differ diff --git a/tests/snapshots/nanosp/test_verify_confirm_accepted/00001.png b/tests/snapshots/nanosp/test_verify_confirm_accepted/00001.png new file mode 100644 index 0000000..5a6d125 Binary files /dev/null and b/tests/snapshots/nanosp/test_verify_confirm_accepted/00001.png differ diff --git a/tests/snapshots/nanosp/test_verify_confirm_accepted/00002.png b/tests/snapshots/nanosp/test_verify_confirm_accepted/00002.png new file mode 100644 index 0000000..c01a756 Binary files /dev/null and b/tests/snapshots/nanosp/test_verify_confirm_accepted/00002.png differ diff --git a/tests/snapshots/nanosp/test_verify_confirm_refused/00000.png b/tests/snapshots/nanosp/test_verify_confirm_refused/00000.png new file mode 100644 index 0000000..0afdb58 Binary files /dev/null and b/tests/snapshots/nanosp/test_verify_confirm_refused/00000.png differ diff --git a/tests/snapshots/nanosp/test_verify_confirm_refused/00001.png b/tests/snapshots/nanosp/test_verify_confirm_refused/00001.png new file mode 100644 index 0000000..5a6d125 Binary files /dev/null and b/tests/snapshots/nanosp/test_verify_confirm_refused/00001.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00000.png b/tests/snapshots/nanox/test_menu_settings/00000.png new file mode 100644 index 0000000..993d953 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00000.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00001.png b/tests/snapshots/nanox/test_menu_settings/00001.png new file mode 100644 index 0000000..09d597f Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00001.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00002.png b/tests/snapshots/nanox/test_menu_settings/00002.png new file mode 100644 index 0000000..1a2e198 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00002.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00003.png b/tests/snapshots/nanox/test_menu_settings/00003.png new file mode 100644 index 0000000..19f4bcc Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00003.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00004.png b/tests/snapshots/nanox/test_menu_settings/00004.png new file mode 100644 index 0000000..4f44ce5 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00004.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00005.png b/tests/snapshots/nanox/test_menu_settings/00005.png new file mode 100644 index 0000000..fb049ec Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00005.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00006.png b/tests/snapshots/nanox/test_menu_settings/00006.png new file mode 100644 index 0000000..99fc174 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00006.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00007.png b/tests/snapshots/nanox/test_menu_settings/00007.png new file mode 100644 index 0000000..3ad71f8 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00007.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00008.png b/tests/snapshots/nanox/test_menu_settings/00008.png new file mode 100644 index 0000000..e678a83 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00008.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00009.png b/tests/snapshots/nanox/test_menu_settings/00009.png new file mode 100644 index 0000000..03a2ed8 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00009.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00010.png b/tests/snapshots/nanox/test_menu_settings/00010.png new file mode 100644 index 0000000..ae682df Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00010.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00011.png b/tests/snapshots/nanox/test_menu_settings/00011.png new file mode 100644 index 0000000..160a0ea Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00011.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00012.png b/tests/snapshots/nanox/test_menu_settings/00012.png new file mode 100644 index 0000000..ae682df Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00012.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00013.png b/tests/snapshots/nanox/test_menu_settings/00013.png new file mode 100644 index 0000000..160a0ea Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00013.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00014.png b/tests/snapshots/nanox/test_menu_settings/00014.png new file mode 100644 index 0000000..61861f2 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00014.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00015.png b/tests/snapshots/nanox/test_menu_settings/00015.png new file mode 100644 index 0000000..993d953 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00015.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00016.png b/tests/snapshots/nanox/test_menu_settings/00016.png new file mode 100644 index 0000000..f14db00 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00016.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00017.png b/tests/snapshots/nanox/test_menu_settings/00017.png new file mode 100644 index 0000000..39f8d72 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00017.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00018.png b/tests/snapshots/nanox/test_menu_settings/00018.png new file mode 100644 index 0000000..61861f2 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00018.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00019.png b/tests/snapshots/nanox/test_menu_settings/00019.png new file mode 100644 index 0000000..f14db00 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00019.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00020.png b/tests/snapshots/nanox/test_menu_settings/00020.png new file mode 100644 index 0000000..f853849 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00020.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00021.png b/tests/snapshots/nanox/test_menu_settings/00021.png new file mode 100644 index 0000000..21c1ee6 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00021.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00022.png b/tests/snapshots/nanox/test_menu_settings/00022.png new file mode 100644 index 0000000..6d667a9 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00022.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00023.png b/tests/snapshots/nanox/test_menu_settings/00023.png new file mode 100644 index 0000000..2d4b009 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00023.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00024.png b/tests/snapshots/nanox/test_menu_settings/00024.png new file mode 100644 index 0000000..181fcd2 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00024.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00025.png b/tests/snapshots/nanox/test_menu_settings/00025.png new file mode 100644 index 0000000..31947f4 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00025.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00026.png b/tests/snapshots/nanox/test_menu_settings/00026.png new file mode 100644 index 0000000..6dfd92d Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00026.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00027.png b/tests/snapshots/nanox/test_menu_settings/00027.png new file mode 100644 index 0000000..42a79ad Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00027.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00028.png b/tests/snapshots/nanox/test_menu_settings/00028.png new file mode 100644 index 0000000..181fcd2 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00028.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00029.png b/tests/snapshots/nanox/test_menu_settings/00029.png new file mode 100644 index 0000000..31947f4 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00029.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00030.png b/tests/snapshots/nanox/test_menu_settings/00030.png new file mode 100644 index 0000000..61861f2 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00030.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00031.png b/tests/snapshots/nanox/test_menu_settings/00031.png new file mode 100644 index 0000000..f853849 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00031.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00032.png b/tests/snapshots/nanox/test_menu_settings/00032.png new file mode 100644 index 0000000..09f86e3 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00032.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00033.png b/tests/snapshots/nanox/test_menu_settings/00033.png new file mode 100644 index 0000000..82790bc Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00033.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00034.png b/tests/snapshots/nanox/test_menu_settings/00034.png new file mode 100644 index 0000000..e525706 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00034.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00035.png b/tests/snapshots/nanox/test_menu_settings/00035.png new file mode 100644 index 0000000..1418d03 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00035.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00036.png b/tests/snapshots/nanox/test_menu_settings/00036.png new file mode 100644 index 0000000..ecec8c2 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00036.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00037.png b/tests/snapshots/nanox/test_menu_settings/00037.png new file mode 100644 index 0000000..61861f2 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00037.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00038.png b/tests/snapshots/nanox/test_menu_settings/00038.png new file mode 100644 index 0000000..09f86e3 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00038.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00039.png b/tests/snapshots/nanox/test_menu_settings/00039.png new file mode 100644 index 0000000..15222f2 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00039.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00040.png b/tests/snapshots/nanox/test_menu_settings/00040.png new file mode 100644 index 0000000..ad5e1c1 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00040.png differ diff --git a/tests/snapshots/nanox/test_menu_settings/00041.png b/tests/snapshots/nanox/test_menu_settings/00041.png new file mode 100644 index 0000000..d993684 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_settings/00041.png differ diff --git a/tests/snapshots/nanox/test_menu_slot/00000.png b/tests/snapshots/nanox/test_menu_slot/00000.png new file mode 100644 index 0000000..e722c27 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_slot/00000.png differ diff --git a/tests/snapshots/nanox/test_menu_slot/00001.png b/tests/snapshots/nanox/test_menu_slot/00001.png new file mode 100644 index 0000000..a6dbaf7 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_slot/00001.png differ diff --git a/tests/snapshots/nanox/test_menu_slot/00002.png b/tests/snapshots/nanox/test_menu_slot/00002.png new file mode 100644 index 0000000..2c9b31c Binary files /dev/null and b/tests/snapshots/nanox/test_menu_slot/00002.png differ diff --git a/tests/snapshots/nanox/test_menu_slot/00003.png b/tests/snapshots/nanox/test_menu_slot/00003.png new file mode 100644 index 0000000..71b4a6e Binary files /dev/null and b/tests/snapshots/nanox/test_menu_slot/00003.png differ diff --git a/tests/snapshots/nanox/test_menu_slot/00004.png b/tests/snapshots/nanox/test_menu_slot/00004.png new file mode 100644 index 0000000..2dcf40b Binary files /dev/null and b/tests/snapshots/nanox/test_menu_slot/00004.png differ diff --git a/tests/snapshots/nanox/test_menu_slot/00005.png b/tests/snapshots/nanox/test_menu_slot/00005.png new file mode 100644 index 0000000..1682ad8 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_slot/00005.png differ diff --git a/tests/snapshots/nanox/test_menu_slot/00006.png b/tests/snapshots/nanox/test_menu_slot/00006.png new file mode 100644 index 0000000..71b4a6e Binary files /dev/null and b/tests/snapshots/nanox/test_menu_slot/00006.png differ diff --git a/tests/snapshots/nanox/test_menu_slot/00007.png b/tests/snapshots/nanox/test_menu_slot/00007.png new file mode 100644 index 0000000..2dcf40b Binary files /dev/null and b/tests/snapshots/nanox/test_menu_slot/00007.png differ diff --git a/tests/snapshots/nanox/test_menu_slot/00008.png b/tests/snapshots/nanox/test_menu_slot/00008.png new file mode 100644 index 0000000..c58dbf5 Binary files /dev/null and b/tests/snapshots/nanox/test_menu_slot/00008.png differ diff --git a/tests/snapshots/nanox/test_verify_confirm_accepted/00000.png b/tests/snapshots/nanox/test_verify_confirm_accepted/00000.png new file mode 100644 index 0000000..0afdb58 Binary files /dev/null and b/tests/snapshots/nanox/test_verify_confirm_accepted/00000.png differ diff --git a/tests/snapshots/nanox/test_verify_confirm_accepted/00001.png b/tests/snapshots/nanox/test_verify_confirm_accepted/00001.png new file mode 100644 index 0000000..5a6d125 Binary files /dev/null and b/tests/snapshots/nanox/test_verify_confirm_accepted/00001.png differ diff --git a/tests/snapshots/nanox/test_verify_confirm_accepted/00002.png b/tests/snapshots/nanox/test_verify_confirm_accepted/00002.png new file mode 100644 index 0000000..c01a756 Binary files /dev/null and b/tests/snapshots/nanox/test_verify_confirm_accepted/00002.png differ diff --git a/tests/snapshots/nanox/test_verify_confirm_refused/00000.png b/tests/snapshots/nanox/test_verify_confirm_refused/00000.png new file mode 100644 index 0000000..0afdb58 Binary files /dev/null and b/tests/snapshots/nanox/test_verify_confirm_refused/00000.png differ diff --git a/tests/snapshots/nanox/test_verify_confirm_refused/00001.png b/tests/snapshots/nanox/test_verify_confirm_refused/00001.png new file mode 100644 index 0000000..5a6d125 Binary files /dev/null and b/tests/snapshots/nanox/test_verify_confirm_refused/00001.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00000.png b/tests/snapshots/stax/test_menu_settings/00000.png new file mode 100644 index 0000000..9a586a4 Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00000.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00001.png b/tests/snapshots/stax/test_menu_settings/00001.png new file mode 100644 index 0000000..ea5bea4 Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00001.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00002.png b/tests/snapshots/stax/test_menu_settings/00002.png new file mode 100644 index 0000000..ac4f499 Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00002.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00003.png b/tests/snapshots/stax/test_menu_settings/00003.png new file mode 100644 index 0000000..083b413 Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00003.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00004.png b/tests/snapshots/stax/test_menu_settings/00004.png new file mode 100644 index 0000000..9a586a4 Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00004.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00005.png b/tests/snapshots/stax/test_menu_settings/00005.png new file mode 100644 index 0000000..4641f1e Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00005.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00006.png b/tests/snapshots/stax/test_menu_settings/00006.png new file mode 100644 index 0000000..9a586a4 Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00006.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00007.png b/tests/snapshots/stax/test_menu_settings/00007.png new file mode 100644 index 0000000..5d4e90c Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00007.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00008.png b/tests/snapshots/stax/test_menu_settings/00008.png new file mode 100644 index 0000000..770b9cc Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00008.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00009.png b/tests/snapshots/stax/test_menu_settings/00009.png new file mode 100644 index 0000000..673d6b6 Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00009.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00010.png b/tests/snapshots/stax/test_menu_settings/00010.png new file mode 100644 index 0000000..9a586a4 Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00010.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00011.png b/tests/snapshots/stax/test_menu_settings/00011.png new file mode 100644 index 0000000..ed5921a Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00011.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00012.png b/tests/snapshots/stax/test_menu_settings/00012.png new file mode 100644 index 0000000..213818d Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00012.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00013.png b/tests/snapshots/stax/test_menu_settings/00013.png new file mode 100644 index 0000000..9a586a4 Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00013.png differ diff --git a/tests/snapshots/stax/test_menu_settings/00014.png b/tests/snapshots/stax/test_menu_settings/00014.png new file mode 100644 index 0000000..4941e0d Binary files /dev/null and b/tests/snapshots/stax/test_menu_settings/00014.png differ diff --git a/tests/snapshots/stax/test_menu_slot/00000.png b/tests/snapshots/stax/test_menu_slot/00000.png new file mode 100644 index 0000000..7cb169a Binary files /dev/null and b/tests/snapshots/stax/test_menu_slot/00000.png differ diff --git a/tests/snapshots/stax/test_menu_slot/00001.png b/tests/snapshots/stax/test_menu_slot/00001.png new file mode 100644 index 0000000..85c4b77 Binary files /dev/null and b/tests/snapshots/stax/test_menu_slot/00001.png differ diff --git a/tests/snapshots/stax/test_menu_slot/00002.png b/tests/snapshots/stax/test_menu_slot/00002.png new file mode 100644 index 0000000..a0811b4 Binary files /dev/null and b/tests/snapshots/stax/test_menu_slot/00002.png differ diff --git a/tests/snapshots/stax/test_verify_confirm_accepted/00000.png b/tests/snapshots/stax/test_verify_confirm_accepted/00000.png new file mode 100644 index 0000000..38dd8e0 Binary files /dev/null and b/tests/snapshots/stax/test_verify_confirm_accepted/00000.png differ diff --git a/tests/snapshots/stax/test_verify_confirm_refused/00000.png b/tests/snapshots/stax/test_verify_confirm_refused/00000.png new file mode 100644 index 0000000..38dd8e0 Binary files /dev/null and b/tests/snapshots/stax/test_verify_confirm_refused/00000.png differ diff --git a/tests/test_cipher.py b/tests/test_cipher.py new file mode 100644 index 0000000..f81088b --- /dev/null +++ b/tests/test_cipher.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests for Cipher feature +""" +from Crypto.Random import get_random_bytes +from Crypto.Cipher import PKCS1_v1_5 + +from application_client.command_sender import CommandSender +from application_client.app_def import Errors, DataObject, PassWord + +from utils import check_pincode, generate_key, get_RSA_pub_key + + +# In this test we check the symmetric key encryption +def test_AES(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Verify PW3 (Admin) + check_pincode(client, PassWord.PW3) + + key = get_random_bytes(32) + # Store the AES Key + rapdu = client.put_data(DataObject.DO_KEY_AES, key) + assert rapdu.status == Errors.SW_OK + + # Verify PW2 (User) + check_pincode(client, PassWord.PW2) + + # Encrypt the data + plain = get_random_bytes(16) + rapdu = client.encrypt(plain) + assert rapdu.status == Errors.SW_OK + + # Decrypt the data + rapdu = client.decrypt(rapdu.data) + assert rapdu.status == Errors.SW_OK + + assert rapdu.data == plain + + +# In this test we check the symmetric key encryption +def test_Asym(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Generate the DEC Key Pair + generate_key(client, DataObject.DO_DEC_KEY) + + # Verify PW2 (User) + check_pincode(client, PassWord.PW2) + + # Read the DEC pub Key + pubkey = get_RSA_pub_key(client, DataObject.DO_DEC_KEY) + + # Encrypt random bytes with Pub Key + plain = get_random_bytes(32) + cipher = PKCS1_v1_5.new(pubkey) + ciphertext = cipher.encrypt(plain) + + # Decrypt the data with the Private key + rapdu = client.decrypt_asym(ciphertext) + assert rapdu.status == Errors.SW_OK + + assert rapdu.data == plain + + +# In this test we check the symmetric key encryption with MSE +def test_MSE(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Generate the AUT Key Pair + generate_key(client, DataObject.DO_AUT_KEY) + + # Verify PW2 (User) + check_pincode(client, PassWord.PW2) + + # Read the AUT pub Key + pubkey = get_RSA_pub_key(client, DataObject.DO_AUT_KEY) + + # Encrypt random bytes with Pub Key + plain = get_random_bytes(32) + cipher = PKCS1_v1_5.new(pubkey) + ciphertext = cipher.encrypt(plain) + + # Change default DEC key by AUT + rapdu = client.manage_security_env(DataObject.DO_DEC_KEY, 3) + assert rapdu.status == Errors.SW_OK + + # Decrypt the data with the Private key + rapdu = client.decrypt_asym(ciphertext) + assert rapdu.status == Errors.SW_OK + + assert rapdu.data == plain diff --git a/tests/test_menus.py b/tests/test_menus.py new file mode 100644 index 0000000..7b29162 --- /dev/null +++ b/tests/test_menus.py @@ -0,0 +1,235 @@ +import pytest +from ragger.navigator import NavInsID, NavIns + +from application_client.command_sender import CommandSender +from application_client.app_def import PassWord + +from utils import check_pincode +from utils import ROOT_SCREENSHOT_PATH + + +# In this test we check the behavior of the Slot menu +# The Navigations go and check: +# - Select slot / Slot 2 / (next page) / Set default +def test_menu_slot(firmware, backend, navigator, test_name): + + # Use the app interface instead of raw interface + client = CommandSender(backend) + # Check slots availability + nb_slots, def_slot = client.get_slot_config() + print("Slots configuration:") + print(f" Nb: {nb_slots}") + print(f" default: {def_slot}") + if nb_slots == 1: + pytest.skip("single slot configuration") + + # Navigate in the main menu + if firmware.device.startswith("nano"): + initial_instructions = [ + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Select slot + ] + instructions = [ + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Slot 2 + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Set as default) + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Back) + ] + else: + initial_instructions = [ + NavInsID.USE_CASE_CHOICE_CONFIRM, # Slots + ] + instructions = [ + NavIns(NavInsID.TOUCH, (350, 220)), # Slot 2 + NavInsID.CENTERED_FOOTER_TAP, # Set default + ] + + # Navigate to settings menu to avoid 1st screen with random serial no + navigator.navigate(initial_instructions, + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=True) + + navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, test_name, instructions, + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False) + + +# In this test we check the behavior of the Setting menus +# The Navigations go and check: +# - Settings / Key Template / Decryption / SECP 256K1 / Confirm +# Seed mode +# (back) +# PIN mode / On Screen / (next page) / Set default +# UIF / Enable UIF for Signature +# (back) +# Reset / Long press 'Yes' +def test_menu_settings(firmware, backend, navigator, test_name): + # Navigate in the main menu + if firmware.device == "nanos": + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Check slots availability + nb_slots, _ = client.get_slot_config() + + initial_instructions = [] + if nb_slots > 1: + initial_instructions.append(NavInsID.RIGHT_CLICK) + initial_instructions.append(NavInsID.RIGHT_CLICK) + initial_instructions.append(NavInsID.BOTH_CLICK) # Settings + + instructions = [ + NavInsID.BOTH_CLICK, # Key Template + NavInsID.BOTH_CLICK, # Choose Key + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Decryption + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Choose Type + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # NIST P256 + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Set Template + NavInsID.BOTH_CLICK, # OK + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Back to settings) + + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Seed mode ON + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Back to settings) + + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # PIN mode + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # On Screen + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Set as default + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Back to settings) + + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # UIF + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # UIF for Signature + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Back to settings) + + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Reset + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Validate + ] + + elif firmware.device.startswith("nano"): + initial_instructions = [ + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Settings + ] + instructions = [ + NavInsID.BOTH_CLICK, # Key Template + NavInsID.BOTH_CLICK, # Choose Key + NavInsID.RIGHT_CLICK, # Decryption + NavInsID.BOTH_CLICK, # Key Decryption + NavInsID.RIGHT_CLICK, # Choose Type + NavInsID.BOTH_CLICK, # (Select) + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # SECP 256K1 + NavInsID.RIGHT_CLICK, # Type SECP 256K1 + NavInsID.BOTH_CLICK, # Set Template + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Back) + + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Seed mode ON + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Back to settings) + + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # PIN mode + NavInsID.BOTH_CLICK, # On Screen + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Set as default + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Back to settings) + + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # UIF + NavInsID.BOTH_CLICK, # UIF for Signature + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # (Back to settings) + + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Reset + NavInsID.RIGHT_CLICK, + NavInsID.BOTH_CLICK, # Validate + ] + else: + initial_instructions = [ + NavInsID.USE_CASE_HOME_SETTINGS, # Settings + ] + instructions = [ + NavIns(NavInsID.TOUCH, (350, 130)), # Key Template + NavIns(NavInsID.TOUCH, (350, 300)), # Decryption + NavIns(NavInsID.TOUCH, (350, 390)), # SECP 256K1 + NavInsID.NAVIGATION_HEADER_TAP, # (Back) + NavIns(NavInsID.TOUCH, (350, 220)), # Seed mode + NavInsID.NAVIGATION_HEADER_TAP, # (Back) + NavIns(NavInsID.TOUCH, (350, 300)), # PIN mode + NavIns(NavInsID.TOUCH, (350, 130)), # On Screen + NavInsID.CENTERED_FOOTER_TAP, # Set default + NavInsID.NAVIGATION_HEADER_TAP, # (Back) + NavIns(NavInsID.TOUCH, (350, 390)), # UIF + NavIns(NavInsID.TOUCH, (350, 130)), # UIF for Signature + NavInsID.NAVIGATION_HEADER_TAP, # (Back) + NavIns(NavInsID.TOUCH, (350, 480)), # Reset + NavInsID.USE_CASE_REVIEW_CONFIRM, # Long press 'Yes' + ] + + # Use the app interface instead of raw interface + client = CommandSender(backend) + # Verify PW2 (User) + check_pincode(client, PassWord.PW2) + + # Navigate to settings menu to avoid 1st screen with random serial no + navigator.navigate(initial_instructions, + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=True) + + navigator.navigate_and_compare(ROOT_SCREENSHOT_PATH, test_name, instructions, + screen_change_before_first_instruction=False, + screen_change_after_last_instruction=False) diff --git a/tests/test_password.py b/tests/test_password.py new file mode 100644 index 0000000..c115602 --- /dev/null +++ b/tests/test_password.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests for Password feature +""" +import pytest +from application_client.command_sender import CommandSender +from application_client.app_def import Errors, PassWord + +from ragger.error import ExceptionRAPDU + +from utils import util_navigate + + +# In this test we check the card Password verification +@pytest.mark.parametrize( + "pwd, value", + [ + (PassWord.PW1, "123456"), + (PassWord.PW2, "123456"), + (PassWord.PW3, "12345678"), + ], +) +def test_verify(backend, pwd, value): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Verify PW status - Not yet verified + with pytest.raises(ExceptionRAPDU) as err: + client.send_verify_pw(pwd) + assert err.value.status & 0xFFF0 == 0x63c0 + + # Verify PW with its value + rapdu = client.send_verify_pw(pwd, value) + assert rapdu.status == Errors.SW_OK + + # Verify PW status + rapdu = client.send_verify_pw(pwd) + assert rapdu.status == Errors.SW_OK + + # Verify PW Reset Status + rapdu = client.send_verify_pw(pwd, reset=True) + assert rapdu.status == Errors.SW_OK + + # Verify PW status - Not yet verified + with pytest.raises(ExceptionRAPDU) as err: + client.send_verify_pw(pwd) + assert err.value.status & 0xFFF0 == 0x63c0 + + +def test_verify_wrong(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Verify PW status - Wrong Password + with pytest.raises(ExceptionRAPDU) as err: + client.send_verify_pw(PassWord.PW1, "999999") + assert err.value.status == Errors.SW_SECURITY_STATUS_NOT_SATISFIED + + +# In this test we check the card Password verification with Pinpad +def test_verify_confirm_accepted(firmware, backend, navigator, test_name): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Send the APDU (Asynchronous) + with client.send_verify_pw_with_confirmation(PassWord.PW1): + util_navigate(firmware, navigator, test_name, "Confirm_Yes") + + # Check the status (Asynchronous) + response = client.get_async_response() + assert response.status == Errors.SW_OK + + +# In this test we check the Rejected card Password verification with Pinpad +def test_verify_confirm_refused(firmware, backend, navigator, test_name): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Send the APDU (Asynchronous) + with pytest.raises(ExceptionRAPDU) as err: + with client.send_verify_pw_with_confirmation(PassWord.PW1): + util_navigate(firmware, navigator, test_name, "Confirm_No") + + # Assert we have received a refusal + assert err.value.status == Errors.SW_CONDITIONS_NOT_SATISFIED + assert len(err.value.data) == 0 + + +# In this test we check the Password Update +@pytest.mark.parametrize( + "pwd, actual, new", + [ + (PassWord.PW1, "123456", "654321"), + (PassWord.PW3, "12345678", "87654321"), + ], +) +def test_change(backend, pwd, actual, new): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Verify PW with its value + rapdu = client.send_verify_pw(pwd, actual) + assert rapdu.status == Errors.SW_OK + + # Change PW value + rapdu = client.send_change_pw(pwd, actual, new) + assert rapdu.status == Errors.SW_OK + + # Verify PW status + rapdu = client.send_verify_pw(pwd, new) + assert rapdu.status == Errors.SW_OK + + +# In this test we check the Password Reset +def test_reset(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Verify PW1 + rapdu = client.send_verify_pw(PassWord.PW1, "123456") + assert rapdu.status == Errors.SW_OK + + # Verify PW3 (Admin) + rapdu = client.send_verify_pw(PassWord.PW3, "12345678") + assert rapdu.status == Errors.SW_OK + + # Reset PW1 with a new value + rapdu = client.send_reset_pw("654321") + assert rapdu.status == Errors.SW_OK + + # Verify PW status + rapdu = client.send_verify_pw(PassWord.PW1, "654321") + assert rapdu.status == Errors.SW_OK + + +# In this test we check the Get Challenge +def test_challenge(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Get Random number + rapdu = client.get_challenge(32) + assert rapdu.status == Errors.SW_OK + print(f"Random: {rapdu.data.hex()}") diff --git a/tests/test_seed.py b/tests/test_seed.py new file mode 100644 index 0000000..987bf67 --- /dev/null +++ b/tests/test_seed.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests for Signing feature with SEED mode +""" +import sys +import pytest + +from application_client.command_sender import CommandSender +from application_client.app_def import Errors, DataObject, PassWord, PubkeyAlgo + +from utils import get_RSA_pub_key, get_ECDSA_pub_key, get_EDDSA_pub_key +from utils import check_pincode, generate_key, get_key_attributes, KEY_TEMPLATES + + +def _gen_key(client: CommandSender, template: str): + + # Verify PW3 (Admin) + check_pincode(client, PassWord.PW3) + + # Set SIG key template + rapdu = client.set_template(DataObject.DO_SIG_ATTR, KEY_TEMPLATES[template]) + assert rapdu.status == Errors.SW_OK + + # Generate the SIG Key Pair in SEED mode + generate_key(client, DataObject.DO_SIG_KEY, True) + + key_algo, _ = get_key_attributes(client, DataObject.DO_SIG_ATTR) + + # Read the SIG pub Key + if key_algo == PubkeyAlgo.RSA: + return get_RSA_pub_key(client, DataObject.DO_SIG_KEY) + if key_algo == PubkeyAlgo.ECDSA: + return get_ECDSA_pub_key(client, DataObject.DO_SIG_KEY) + if key_algo == PubkeyAlgo.EDDSA: + return get_EDDSA_pub_key(client, DataObject.DO_SIG_KEY) + + raise ValueError + + +@pytest.mark.parametrize( + "template", + [ + "rsa2048", + pytest.param("rsa3072", marks=pytest.mark.skipif("--full" not in sys.argv, reason="skipping long test")), + # pytest.param("rsa4096", marks=pytest.mark.skipif("--full" not in sys.argv, reason="skipping long test")), + "nistp256", # ECDSA + "ed25519", # EdDSA + # "cv25519", # ECDH, SDK returns CX_EC_INVALID_CURVE + ], +) +def test_seed_key(backend, template): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Generate the key + pubkey1 = _gen_key(client, template) + + # Reset the App (delete the key) + client.send_terminate() + client.send_activate() + + # Ensure the SIG Key is no more available + rapdu = client.read_key(DataObject.DO_SIG_KEY) + assert rapdu.status == Errors.SW_REFERENCED_DATA_NOT_FOUND + + # Generate the key again + pubkey2 = _gen_key(client, template) + + # Check generated keys + assert pubkey1 == pubkey2 diff --git a/tests/test_sign.py b/tests/test_sign.py new file mode 100644 index 0000000..41ec111 --- /dev/null +++ b/tests/test_sign.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests for Signing feature +""" +from Crypto.Hash import SHA256 +from Crypto.PublicKey.RSA import RsaKey +from Crypto.Signature import pkcs1_15 +from Crypto.Random import get_random_bytes + +from application_client.command_sender import CommandSender +from application_client.app_def import Errors, DataObject, PassWord, PubkeyAlgo + +from utils import check_pincode, get_key_attributes, get_RSA_pub_key, generate_key, SHA256_DIGEST_INFO + + +# In this test we check the key pair generation +def test_sign(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Generate the SIG Key Pair + generate_key(client, DataObject.DO_SIG_KEY) + + # SIG Key attributes + key_algo, key_size = get_key_attributes(client, DataObject.DO_SIG_ATTR) + # Check default config values + assert key_algo == PubkeyAlgo.RSA + assert key_size == 2048 + + # Verify PW1 (User) + check_pincode(client, PassWord.PW1) + + # Hash data buffer + hash_obj = SHA256.new(get_random_bytes(16)) + + rapdu = client.sign(SHA256_DIGEST_INFO + hash_obj.digest()) + assert rapdu.status == Errors.SW_OK + + # Verify the signature + _verify_signature(client, hash_obj, DataObject.DO_SIG_KEY, rapdu.data) + + +# In this test we check the key pair generation +def test_auth(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Generate the AUT Key Pair + generate_key(client, DataObject.DO_AUT_KEY) + + # Verify PW2 (User) + check_pincode(client, PassWord.PW2) + + # Hash data buffer + hash_obj = SHA256.new(get_random_bytes(16)) + + rapdu = client.authenticate(SHA256_DIGEST_INFO + hash_obj.digest()) + assert rapdu.status == Errors.SW_OK + + # Verify the signature + _verify_signature(client, hash_obj, DataObject.DO_AUT_KEY, rapdu.data) + + +def _verify_signature(client: CommandSender, hash_obj: SHA256.SHA256Hash, key_tag: DataObject, signature: bytes): + + # Read the SIG pub Key + pubkey: RsaKey = get_RSA_pub_key(client, key_tag) + # Verify the signature + verifier = pkcs1_15.new(pubkey) + verifier.verify(hash_obj, signature) diff --git a/tests/test_slot.py b/tests/test_slot.py new file mode 100644 index 0000000..aff7eec --- /dev/null +++ b/tests/test_slot.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests for Slots feature +""" +import pytest + +from application_client.command_sender import CommandSender +from application_client.app_def import Errors, DataObject, PassWord + +from utils import check_pincode, generate_key + +def test_slot(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Check slots availability + nb_slots, def_slot = client.get_slot_config() + print("Slots configuration:") + print(f" Nb: {nb_slots}") + print(f" default: {def_slot}") + if nb_slots == 1: + pytest.skip("single slot configuration") + + # Generate the SIG Key Pair + generate_key(client, DataObject.DO_SIG_KEY) + + # Read slot + slot = client.get_slot() + assert slot == 0 + + # Read the SIG pub Key + rapdu = client.read_key(DataObject.DO_SIG_KEY) + assert rapdu.status == Errors.SW_OK + + # Verify PW2 + check_pincode(client, PassWord.PW2) + + # Change slot + rapdu = client.set_slot(2) + assert rapdu.status == Errors.SW_OK + + # Read slot + slot = client.get_slot() + assert slot == 2 + + # Read an empty pub key + rapdu = client.read_key(DataObject.DO_SIG_KEY) + assert rapdu.status == Errors.SW_REFERENCED_DATA_NOT_FOUND diff --git a/tests/test_template.py b/tests/test_template.py new file mode 100644 index 0000000..63325ab --- /dev/null +++ b/tests/test_template.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests for Key Templates feature +""" +import sys +import pytest +from Crypto.Hash import SHA256 +from Crypto.Signature import pkcs1_15, eddsa +from Crypto.Random import get_random_bytes +from Crypto.Signature import DSS + +from application_client.command_sender import CommandSender +from application_client.app_def import Errors, DataObject, PassWord, PubkeyAlgo + +from utils import get_RSA_pub_key, get_ECDSA_pub_key, get_EDDSA_pub_key +from utils import check_pincode, generate_key, get_key_attributes +from utils import KEY_TEMPLATES, SHA256_DIGEST_INFO + + +@pytest.mark.parametrize( + "template", + [ + "rsa2048", + pytest.param("rsa3072", marks=pytest.mark.skipif("--full" not in sys.argv, reason="skipping long test")), + # "rsa4096", # Invalid signature? + # "nistp256", # ECDSA, Pb with Pubkey generation? + "ed25519", # EdDSA + # "cv25519", # ECDH, SDK returns CX_EC_INVALID_CURVE + ], +) +def test_sign(backend, template): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Verify PW3 (Admin) + check_pincode(client, PassWord.PW3) + + # Set SIG key template + rapdu = client.set_template(DataObject.DO_SIG_ATTR, KEY_TEMPLATES[template]) + assert rapdu.status == Errors.SW_OK + + # Generate the SIG Key Pair + generate_key(client, DataObject.DO_SIG_KEY) + + key_algo, _ = get_key_attributes(client, DataObject.DO_SIG_ATTR) + + # Hash data buffer + plain = get_random_bytes(16) + hash_obj = SHA256.new(plain) + + # Sign data buffer + if key_algo == PubkeyAlgo.RSA: + digest_info = SHA256_DIGEST_INFO + data = digest_info + hash_obj.digest() + elif key_algo == PubkeyAlgo.ECDSA: + data = hash_obj.digest() + else: + data = plain + + # Verify PW1 (User) + check_pincode(client, PassWord.PW1) + + rapdu = client.sign(data) + assert rapdu.status == Errors.SW_OK + + # Read the SIG pub Key and Verify the signature + if key_algo == PubkeyAlgo.RSA: + pubkey = get_RSA_pub_key(client, DataObject.DO_SIG_KEY) + verifier = pkcs1_15.new(pubkey) + verifier.verify(hash_obj, rapdu.data) + elif key_algo == PubkeyAlgo.ECDSA: + pubkey = get_ECDSA_pub_key(client, DataObject.DO_SIG_KEY) + verifier = DSS.new(pubkey, 'fips-186-3') + verifier.verify(hash_obj, rapdu.data[2:]) + elif key_algo == PubkeyAlgo.EDDSA: + pubkey = get_EDDSA_pub_key(client, DataObject.DO_SIG_KEY) + verifier = eddsa.new(pubkey, 'rfc8032') + verifier.verify(plain, rapdu.data) + else: + raise ValueError diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..45ddc3c --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests for Version check +""" +from application_client.command_sender import CommandSender +from application_client.app_def import Errors, DataObject, PassWord +from application_client.response_unpacker import unpack_info_response + +from ragger.utils.misc import get_current_app_name_and_version + +from utils import verify_name, verify_version, decode_tlv, check_pincode + + +# In this test we check the App name and version +def test_check_version(backend): + """Check version and name""" + + # Send the APDU + app_name, version = get_current_app_name_and_version(backend) + print(f" Name: {app_name}") + print(f" Version: {version}") + verify_name(app_name) + verify_version(version) + + +# In this test we check the Card activation +def test_activate(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Activate the Card + rapdu = client.send_activate() + assert rapdu.status == Errors.SW_OK + + +# In this test we get the Card Application ID value +def test_info(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Get info from the Card + rapdu = client.get_data(DataObject.DO_AID) + assert rapdu.status == Errors.SW_OK + aid = rapdu.data.hex() + print(f" AID: {aid}") + assert aid[:12] == "d27600012401" + + # Parse the response + version, serial = unpack_info_response(rapdu.data) + print(f" Version: {int(version[0:2]):d}.{int(version[2:4]):d}") + print(f" Serial: {serial}") + + # Check expected value + assert version == "0303" + + +# In this test we test the User Data information +def test_user(backend): + # Use the app interface instead of raw interface + client = CommandSender(backend) + + # Verify PW3 (Admin) + check_pincode(client, PassWord.PW3) + + # Write and Read the 'Login' + _check_user_value(client, DataObject.DO_LOGIN, "John.Doe") + + # Write and Read the 'URL' + _check_user_value(client, DataObject.DO_URL, "This is John Doe URL") + + # Write and Read the 'name' + _check_card_value(client, DataObject.DO_CARD_NAME, "John Doe") + + # Write and Read the 'Lang' + _check_card_value(client, DataObject.DO_CARD_LANG, "fr") + + # Write and Read the 'Salutation' + _check_card_value(client, DataObject.DO_CARD_SALUTATION, "1") + + # Write and Read the 'Serial number' + serial = "12345678" + rapdu = client.put_data(DataObject.DO_AID, bytes.fromhex(serial)) + assert rapdu.status == Errors.SW_OK + + rapdu = client.get_data(DataObject.DO_AID) + assert rapdu.status == Errors.SW_OK + assert rapdu.data.hex()[20:28] == serial + + +def _check_card_value(client, tag: DataObject, value: str): + + rapdu = client.put_data(tag, value.encode("utf-8")) + assert rapdu.status == Errors.SW_OK + + rapdu = client.get_data(DataObject.DO_CARDHOLDER_DATA) + assert rapdu.status == Errors.SW_OK + tags = decode_tlv(rapdu.data) + rvalue = tags.get(tag, b"").decode("utf-8") + assert rvalue == value + + +def _check_user_value(client, tag: DataObject, value: str): + + rapdu = client.put_data(tag, value.encode("utf-8")) + assert rapdu.status == Errors.SW_OK + + rapdu = client.get_data(tag) + assert rapdu.status == Errors.SW_OK + assert rapdu.data.decode("utf-8") == value diff --git a/tests/usage.md b/tests/usage.md new file mode 100644 index 0000000..75e9664 --- /dev/null +++ b/tests/usage.md @@ -0,0 +1,82 @@ +# How to use the Ragger test framework + +This framework allows testing the application on the Speculos emulator or on a real device using LedgerComm or LedgerWallet + +## Quickly get started with Ragger and Speculos + +### Install ragger and dependencies + +```shell +pip install --extra-index-url https://test.pypi.org/simple/ -r requirements.txt +sudo apt-get update && sudo apt-get install qemu-user-static +``` + +### Compile the application + +The application to test must be compiled for all required devices. +You can use for this the container `ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite`: + +```shell +docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest +cd + +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 +make clean && make BOLOS_SDK=$_SDK # replace with one of [NANOS, NANOX, NANOSP, STAX] +exit +``` + +### Run a simple test using the Speculos emulator + +You can use the following command to get your first experience with Ragger and Speculos + +```shell +pytest -v --tb=short --device nanox --display +``` + +Or you can refer to the section `Available pytest options` to configure the options you want to use + +### Run a simple test using a real device + +The application to test must be loaded and started on a Ledger device plugged in USB. +You can use for this the container `ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite`: + +```shell +docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest +cd app-/ + +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 +make clean && make BOLOS_SDK=$_SDK load # replace with one of [NANOS, NANOX, NANOSP, STAX] +exit +``` + +You can use the following command to get your first experience with Ragger and Ledgerwallet on a NANOX. +Make sure that the device is plugged, unlocked, and that the tested application is open. + +```shell +pytest -v --tb=short --device nanox --backend ledgerwallet +``` + +Or you can refer to the section `Available pytest options` to configure the options you want to use + +## Available pytest options + +Standard useful pytest options + +```shell + -v formats the test summary in a readable way + -s enable logs for successful tests, on Speculos it will enable app logs if compiled with DEBUG=1 + -k only run the tests that contain in their names + --tb=short in case of errors, formats the test traceback in a readable way +``` + +Custom pytest options + +```shell + --full Run full tests + --device Run the test on the specified device [nanos,nanox,nanosp,stax,all]. This parameter is mandatory + --backend Run the tests against the backend [speculos, ledgercomm, ledgerwallet]. Speculos is the default + --display On Speculos, enables the display of the app screen using QT + --golden_run Pn Speculos, screen comparison functions will save the current screen instead of comparing + --log_apdu_file Log all apdu exchanges to the file in parameter. The previous file content is erased + --seed=SEED Set a custom seed +``` diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..5d5e770 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +# SPDX-FileCopyrightText: 2023 Ledger SAS +# SPDX-License-Identifier: LicenseRef-LEDGER +""" +This module provides Ragger tests utility functions +""" +from pathlib import Path +from typing import List, Tuple +import re +from Crypto.PublicKey import RSA, ECC +from Crypto.Util.number import bytes_to_long +from Crypto.Signature import eddsa + +from ragger.navigator import NavInsID, NavIns, Navigator +from ragger.firmware import Firmware + +from application_client.command_sender import CommandSender +from application_client.app_def import Errors, PassWord, DataObject, PubkeyAlgo + +ROOT_SCREENSHOT_PATH = Path(__file__).parent.resolve() + +KEY_TEMPLATES = { + "rsa2048" : "010800002001", + "rsa3072" : "010C00002001", + # "rsa4096" : "011000002001", Not supported yet + "nistp256": "132A8648CE3D030107", + "ed25519" : "162B06010401DA470F01", + "cv25519" : "122B060104019755010501" +} + +# digestInfo header: https://www.rfc-editor.org/rfc/rfc8017#section-9.2 +SHA256_DIGEST_INFO = b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20" + + +def util_navigate( + firmware: Firmware, + navigator: Navigator, + test_name: Path, + text: str = "", +) -> None: + """Navigate in the menus with conditions """ + + assert text + valid_instr: list[NavIns | NavInsID] = [] + + if firmware.device == "nanos": + text, txt_cfg = text.split("_") + nav_inst = NavInsID.RIGHT_CLICK + if txt_cfg == "Yes": + valid_instr.append(NavInsID.RIGHT_CLICK) + elif txt_cfg == "No": + valid_instr.append(NavInsID.LEFT_CLICK) + else: + raise ValueError(f'Wrong text "{text}"') + + elif firmware.device.startswith("nano"): + text = text.split("_")[1] + nav_inst = NavInsID.RIGHT_CLICK + valid_instr.append(NavInsID.BOTH_CLICK) + + else: + text, txt_cfg = text.split("_") + if txt_cfg == "Yes": + nav_inst = NavInsID.USE_CASE_CHOICE_CONFIRM + valid_instr.append(NavInsID.USE_CASE_CHOICE_CONFIRM) + elif txt_cfg == "No": + nav_inst = NavInsID.USE_CASE_REVIEW_REJECT + valid_instr.append(NavInsID.USE_CASE_CHOICE_REJECT) + else: + raise ValueError(f'Wrong text "{text}"') + + # Do not wait last screen change because home screen contain a "random" ID + navigator.navigate_until_text_and_compare(nav_inst, + valid_instr, + text, + ROOT_SCREENSHOT_PATH, + test_name, + screen_change_after_last_instruction=False) + + +def generate_key(client: CommandSender, key_tag: DataObject, seed: bool = False) -> None: + """Generate a Asymmetric key + + Args: + client (CommandSender): Application object + key_tag (DataObject): Tag identifying the key + seed (bool): Generate a key in SEED mode + """ + + # Verify PW3 (Admin) + check_pincode(client, PassWord.PW3) + + # Generate the SIG Key Pair + rapdu = client.generate_key(key_tag, seed) + assert rapdu.status == Errors.SW_OK + + +def get_key_attributes(client: CommandSender, key_attr: int) -> Tuple[PubkeyAlgo, int]: + """Send and check the pincode + + Args: + client (CommandSender): Application object + key_attr (int): Key related attribute to be parsed + + Returns: + Public-Key Algorithm ID and Key size + """ + + rapdu = client.get_data(DataObject.DO_APP_DATA) + assert rapdu.status == Errors.SW_OK + + tags = decode_tlv(rapdu.data) + data1 = tags.get(DataObject.DO_DISCRET_DATA, b"") + if not data1: + return PubkeyAlgo.INVALID, 0 + data2 = decode_tlv(data1) + attr = data2.get(key_attr, b"") + if not attr: + return PubkeyAlgo.INVALID, 0 + key_algo = attr[0] + print(f" Key ID: {key_algo}") + if key_algo == PubkeyAlgo.RSA: + key_size = (attr[1] << 8) | attr[2] + print(f" Key size: {key_size}") + else: + key_size = 0 + return key_algo, key_size + + +def get_EDDSA_pub_key(client: CommandSender, key_tag: DataObject) -> ECC.EccKey: + """Read the Public Key and generate a EccKey object + + Args: + client (CommandSender): Application object + key_tag (DataObject): Tag identifying the key to read + + Returns: + EccKey + """ + + # Extract Pub key parameters + data = _get_pub_key(client, key_tag) + + oid = data[0x86] + print(f" OID[{len(oid)}]: {oid.hex()}") + assert len(oid) == 32 + return eddsa.import_public_key(oid) + + +def get_ECDSA_pub_key(client: CommandSender, key_tag: DataObject) -> ECC.EccKey: + """Read the Public Key and generate a EccKey object + + Args: + client (CommandSender): Application object + key_tag (DataObject): Tag identifying the key to read + + Returns: + EccKey + """ + + # Extract Pub key parameters + data = _get_pub_key(client, key_tag) + + oid = data[0x86][1:] + pt_len = int(len(oid) / 2) + x = oid[:pt_len] + y = oid[pt_len:] + print(f" X[{len(x)}]: {x.hex()}") + print(f" Y[{len(y)}]: {y.hex()}") + assert len(x) == len(y) + return ECC.construct(curve="P-256", point_x=bytes_to_long(x), point_y=bytes_to_long(y)) + + +def get_RSA_pub_key(client: CommandSender, key_tag: DataObject) -> RSA.RsaKey: + """Read the Public Key and generate a RsaKey object + + Args: + client (CommandSender): Application object + key_tag (DataObject): Tag identifying the key to read + + Returns: + RsaKey + """ + + # Extract Pub key parameters + data = _get_pub_key(client, key_tag) + + exponent = data[0x82] + modulus = data[0x81] + print(f" Key Exponent: 0x{exponent.hex()}") + print(f" Key Modulus[{len(modulus)}]: {modulus.hex()}") + + return RSA.construct((int.from_bytes(modulus, 'big'), int.from_bytes(exponent, 'big'))) + + +def check_pincode(client: CommandSender, pwd: PassWord) -> None: + """Send and check the pincode + + Args: + client (CommandSender): Application object + pwd (PassWord): Password to be verified + """ + + if pwd in (PassWord.PW1, PassWord.PW2): + pincode = "123456" + elif pwd == PassWord.PW3: + pincode = "12345678" + # Verify PW2 (User) + rapdu = client.send_verify_pw(pwd, pincode) + assert rapdu.status == Errors.SW_OK + + +def verify_version(version: str) -> None: + """Verify the app version, based on defines in Makefile + + Args: + Version (str): Version to be checked + """ + + vers_dict = {} + vers_str = "" + lines = _read_makefile() + version_re = re.compile(r"^APPVERSION_(?P\w)\s?=\s?(?P\d)", re.I) + for line in lines: + info = version_re.match(line) + if info: + dinfo = info.groupdict() + vers_dict[dinfo["part"]] = dinfo["val"] + try: + vers_str = f"{vers_dict['M']}.{vers_dict['N']}.{vers_dict['P']}" + except KeyError: + pass + assert version == vers_str + + +def verify_name(name: str) -> None: + """Verify the app name, based on defines in Makefile + + Args: + name (str): Name to be checked + """ + + name_str = "" + lines = _read_makefile() + name_re = re.compile(r"^APPNAME\s?=\s?(?P\w+)", re.I) + for line in lines: + info = name_re.match(line) + if info: + dinfo = info.groupdict() + name_str = dinfo["val"] + assert name == name_str + + +def decode_tlv(tlv: bytes) -> dict: + """Decode TLV fields + + Args: + tlv (bytes): Input data bytes to parse + + Returns: + dict {t: v, t:v, ...} + """ + + tags = {} + while len(tlv): + o = 0 + l = 0 + if (tlv[0] & 0x1F) == 0x1F: + t = (tlv[0] << 8) | tlv[1] + o = 2 + else: + t = tlv[0] + o = 1 + l = tlv[o] + if l & 0x80 : + if (l & 0x7f) == 1: + l = tlv[o + 1] + o += 2 + if (l & 0x7f) == 2: + l = (tlv[o + 1] << 8) | tlv[o + 2] + o += 3 + else: + o += 1 + v = tlv[o:o + l] + tags[t] = v + tlv = tlv[o + l:] + return tags + + +def _read_makefile() -> List[str]: + """Read lines from the parent Makefile """ + + parent = Path(ROOT_SCREENSHOT_PATH).parent.resolve() + makefile = f"{parent}/Makefile" + print(f"Analyzing {makefile}...") + with open(makefile, "r", encoding="utf-8") as f_p: + lines = f_p.readlines() + + return lines + + +def _get_pub_key(client: CommandSender, key_tag: DataObject) -> dict: + """Read the Public Key parameters + + Args: + client (CommandSender): Application object + key_tag (DataObject): Tag identifying the key to read + + Returns: + Dictionary with key parameters + """ + + rapdu = client.read_key(key_tag) + assert rapdu.status == Errors.SW_OK + + # Extract Pub key parameters + tags = decode_tlv(rapdu.data) + return decode_tlv(tags[DataObject.DO_PUB_KEY]) diff --git a/unit-tests/CMakeLists.txt b/unit-tests/CMakeLists.txt new file mode 100644 index 0000000..7e64b05 --- /dev/null +++ b/unit-tests/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.10) + +if(${CMAKE_VERSION} VERSION_LESS 3.10) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif() + +# project information +project(unit_tests + VERSION 0.1 + DESCRIPTION "Unit tests for Ledger OpenPGP application" + LANGUAGES C) + + +# guard against bad build-type strings +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif() + +# guard against in-source builds +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + 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. ") +endif() + +include(CTest) + +# specify C standard +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED True) + +add_compile_options(-Wall -Wextra -g -pedantic --coverage) +# Flag depending on the Build Type +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}-O3") + +set(GCC_COVERAGE_LINK_FLAGS "--coverage -lgcov") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") + +set(APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../src") +set(SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") +set(MOCK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/mocks") + +add_compile_definitions(TEST) + +include_directories( + ${MOCK_DIR} + ${APP_DIR} +) +# include_directories($ENV{BOLOS_SDK}/lib_standard_app) + +add_executable(test_io + ${SRC_DIR}/test_io.c + ${MOCK_DIR}/mocks.c + ${APP_DIR}/gpg_io.c + ${APP_DIR}/gpg_vars.c +) + +target_link_libraries(test_io PUBLIC + cmocka + gcov) + +add_test(test_io test_io) diff --git a/unit-tests/README.md b/unit-tests/README.md new file mode 100644 index 0000000..133fad0 --- /dev/null +++ b/unit-tests/README.md @@ -0,0 +1,48 @@ +# Unit tests + +## Prerequisite + +Be sure to have installed: + +- CMake >= 3.10 +- CMocka >= 1.1.5 + +and for code coverage generation: + +- lcov >= 1.14 + +## Overview + +In `unit-tests` folder, compile with: + +```shell +cmake -Bbuild -H. && make -C build +``` + +and run tests with: + +```shell +CTEST_OUTPUT_ON_FAILURE=1 make -C build test +``` + +To get more verbose output, use: + +```shell +CTEST_OUTPUT_ON_FAILURE=1 make -C build test ARGS="-V" +``` + +Or also directly with: + +```shell +CTEST_OUTPUT_ON_FAILURE=1 build/test_io +``` + +## Generate code coverage + +Just execute in `unit-tests` folder: + +```shell +./gen_coverage.sh +``` + +it will output `coverage.total` and `coverage/` folder with HTML details (in `coverage/index.html`). diff --git a/unit-tests/gen_coverage.sh b/unit-tests/gen_coverage.sh new file mode 100755 index 0000000..8048270 --- /dev/null +++ b/unit-tests/gen_coverage.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -x +set -e + +BUILD_DIRECTORY=$(realpath build/) + +lcov --directory . -b "${BUILD_DIRECTORY}" --capture --initial -o coverage.base && +lcov --rc lcov_branch_coverage=1 --directory . -b "${BUILD_DIRECTORY}" --capture -o coverage.capture && +lcov --directory . -b "${BUILD_DIRECTORY}" --add-tracefile coverage.base --add-tracefile coverage.capture -o coverage.info && +lcov --directory . -b "${BUILD_DIRECTORY}" --remove coverage.info '*/unit-tests/*' -o coverage.info && +echo "Generated 'coverage.info'." && +genhtml coverage.info -o coverage + +rm -f coverage.base coverage.capture diff --git a/unit-tests/mocks/bolos_target.h b/unit-tests/mocks/bolos_target.h new file mode 100644 index 0000000..e69de29 diff --git a/unit-tests/mocks/cx.h b/unit-tests/mocks/cx.h new file mode 100644 index 0000000..b475a99 --- /dev/null +++ b/unit-tests/mocks/cx.h @@ -0,0 +1,30 @@ + + +#define cx_rsa_public_key_t char +#define cx_rsa_1024_public_key_t char +#define cx_rsa_2048_public_key_t char +#define cx_rsa_3072_public_key_t char +#define cx_rsa_4096_public_key_t char + +#define cx_rsa_private_key_t char +#define cx_rsa_1024_private_key_t char +#define cx_rsa_2048_private_key_t char +#define cx_rsa_3072_private_key_t char +#define cx_rsa_4096_private_key_t char + +#define cx_ecfp_public_key_t char +#define cx_ecfp_256_public_key_t char +#define cx_ecfp_384_public_key_t char +#define cx_ecfp_512_public_key_t char +#define cx_ecfp_640_public_key_t char + +#define cx_ecfp_private_key_t char +#define cx_ecfp_256_private_key_t char +#define cx_ecfp_384_private_key_t char +#define cx_ecfp_512_private_key_t char +#define cx_ecfp_640_private_key_t char + +#define cx_sha3_t char +#define cx_sha256_t char + +#define cx_aes_key_t char diff --git a/unit-tests/mocks/lcx_sha3.h b/unit-tests/mocks/lcx_sha3.h new file mode 100644 index 0000000..e69de29 diff --git a/unit-tests/mocks/ledger_assert.h b/unit-tests/mocks/ledger_assert.h new file mode 100644 index 0000000..90e97d4 --- /dev/null +++ b/unit-tests/mocks/ledger_assert.h @@ -0,0 +1,6 @@ +#define LEDGER_ASSERT(test, message) \ + do { \ + if (!(test)) { \ + return; \ + } \ + } while (0) diff --git a/unit-tests/mocks/mocks.c b/unit-tests/mocks/mocks.c new file mode 100644 index 0000000..ebb703d --- /dev/null +++ b/unit-tests/mocks/mocks.c @@ -0,0 +1,17 @@ + +#include "os.h" + +unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; + +unsigned short io_exchange(unsigned char channel_and_flags, unsigned short tx_len) { + (void) channel_and_flags; + (void) tx_len; + return 0; +} + +void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len) { + (void) dst_adr; + (void) src_adr; + (void) src_len; + return; +} diff --git a/unit-tests/mocks/offsets.h b/unit-tests/mocks/offsets.h new file mode 100644 index 0000000..3db4bc1 --- /dev/null +++ b/unit-tests/mocks/offsets.h @@ -0,0 +1,26 @@ +#pragma once + +/** + * Offset of instruction class. + */ +#define OFFSET_CLA 0 +/** + * Offset of instruction code. + */ +#define OFFSET_INS 1 +/** + * Offset of instruction parameter 1. + */ +#define OFFSET_P1 2 +/** + * Offset of instruction parameter 2. + */ +#define OFFSET_P2 3 +/** + * Offset of command data length. + */ +#define OFFSET_LC 4 +/** + * Offset of command data. + */ +#define OFFSET_CDATA 5 diff --git a/unit-tests/mocks/os.h b/unit-tests/mocks/os.h new file mode 100644 index 0000000..a6c7cad --- /dev/null +++ b/unit-tests/mocks/os.h @@ -0,0 +1,22 @@ + +#include +#include + +#undef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#define PRINTF(...) +#define THROW(x) + +// send tx_len bytes (atr or rapdu) and retrieve the length of the next command apdu (over the +// requested channel) +#define CHANNEL_APDU 0 +#define IO_RETURN_AFTER_TX 0x20 +#define IO_ASYNCH_REPLY 0x10 // avoid apdu state reset if tx_len == 0 when we're expected to reply + +#define IO_APDU_BUFFER_SIZE (255 + 5 + 64) + +extern unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; + +extern unsigned short io_exchange(unsigned char channel_and_flags, unsigned short tx_len); +extern void nvm_write(void *dst_adr, void *src_adr, unsigned int src_len); diff --git a/unit-tests/mocks/os_utils.h b/unit-tests/mocks/os_utils.h new file mode 100644 index 0000000..510f2ef --- /dev/null +++ b/unit-tests/mocks/os_utils.h @@ -0,0 +1,23 @@ +#pragma once + +#define U2(hi, lo) ((((hi) &0xFFu) << 8) | ((lo) &0xFFu)) +#define U4(hi3, hi2, lo1, lo0) \ + ((((hi3) &0xFFu) << 24) | (((hi2) &0xFFu) << 16) | (((lo1) &0xFFu) << 8) | ((lo0) &0xFFu)) +static inline uint16_t U2BE(const uint8_t *buf, size_t off) { + return (buf[off] << 8) | buf[off + 1]; +} +static inline uint32_t U4BE(const uint8_t *buf, size_t off) { + return (((uint32_t) buf[off]) << 24) | (buf[off + 1] << 16) | (buf[off + 2] << 8) | + buf[off + 3]; +} + +static inline void U2BE_ENCODE(uint8_t *buf, size_t off, uint32_t value) { + buf[off + 0] = (value >> 8) & 0xFF; + buf[off + 1] = value & 0xFF; +} +static inline void U4BE_ENCODE(uint8_t *buf, size_t off, uint32_t value) { + buf[off + 0] = (value >> 24) & 0xFF; + buf[off + 1] = (value >> 16) & 0xFF; + buf[off + 2] = (value >> 8) & 0xFF; + buf[off + 3] = value & 0xFF; +} diff --git a/unit-tests/mocks/usbd_ccid_if.h b/unit-tests/mocks/usbd_ccid_if.h new file mode 100644 index 0000000..37dc501 --- /dev/null +++ b/unit-tests/mocks/usbd_ccid_if.h @@ -0,0 +1 @@ +#define PIN_OPR_APDU_CLA 0xEF diff --git a/unit-tests/mocks/ux.h b/unit-tests/mocks/ux.h new file mode 100644 index 0000000..7256169 --- /dev/null +++ b/unit-tests/mocks/ux.h @@ -0,0 +1 @@ +#define ux_state_t char diff --git a/unit-tests/src/test_io.c b/unit-tests/src/test_io.c new file mode 100644 index 0000000..e7d4fbe --- /dev/null +++ b/unit-tests/src/test_io.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "gpg_vars.h" + +static int setup(void **state) { + (void) state; + + // Init tests + gpg_io_discard(1); + return 0; +} + +static void test_io(void **state) { + (void) state; + + unsigned int v32 = 0x789ABCDE; + unsigned int v16 = 0x3456; + unsigned int v8 = 0x12; + + gpg_io_insert_u8(v8); + + gpg_io_insert_u16(v16); + + // Mark the current offset + gpg_io_mark(); + + gpg_io_insert_u32(v32); + + // rewind offset to the beginning to the buffer + gpg_io_set_offset(0); + + assert_int_equal(gpg_io_fetch_u8(), v8); + assert_int_equal(gpg_io_fetch_u16(), v16); + assert_int_equal(gpg_io_fetch_u32(), v32); + + // rewind offset to the mark + gpg_io_set_offset(IO_OFFSET_MARK); + + assert_int_equal(gpg_io_fetch_u32(), v32); +} + +int main() { + const struct CMUnitTest tests[] = {cmocka_unit_test_setup(test_io, setup)}; + + return cmocka_run_group_tests(tests, NULL, NULL); +}