commit f91d2fd3dbe7d97794702ff274b1f44a49ca6cd1 Author: Jean-Michel Picod Date: Tue Jan 28 15:09:10 2020 +0100 Initial commit diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..92823f7 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,7 @@ +# Target configuration for the CTAP2 app on the nRF52840 chip +[target.thumbv7em-none-eabi] +rustflags = [ + "-C", "link-arg=-Tnrf52840dk_layout.ld", + "-C", "relocation-model=static", + "-D", "warnings", +] diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..3c52212 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,16 @@ +## Expected Behavior + + +## Actual Behavior + + +## Steps to Reproduce the Problem + +1. +1. +1. + +## Specifications + +- Version: +- Platform: \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0787bd9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +Fixes # + +> It's a good idea to open an issue first for discussion. + +- [ ] Tests pass +- [ ] Appropriate changes to README are included in PR \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..625fa19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +target/ +Cargo.lock + +# Prevent people from commiting sensitive files. +crypto_data/ +src/ctap/key_material.rs + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b70a516 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "third_party/libtock-rs"] + path = third_party/libtock-rs + url = https://github.com/tock/libtock-rs +[submodule "third_party/tock"] + path = third_party/tock + url = https://github.com/tock/tock diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..e1d84fb --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,35 @@ +{ + "default": true, + "heading-style": { + "style": "atx" + }, + "no-trailing-spaces": { + "br_spaces": 0, + "strict": true + }, + "ul-indent": { + "indent": 4 + }, + "line-length": { + "line_length": 80, + "code_blocks": false + }, + "list-marker-space": { + "ol_single": 2, + "ol_multi": 2, + "ul_single": 3, + "ul_multi": 3 + }, + "no-inline-html": { + "allowed_elements": [ + "img" + ] + }, + "fenced-code-language": true, + "code-block-style": { + "style": "fenced" + }, + "code-fence-style": { + "style": "backtick" + } +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3cb7c35 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,45 @@ +# Copyright 2019 Google LLC +# +# 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. + +language: rust +rust: + - nightly-2020-01-16 + +os: + - linux + +dist: "trusty" # we need python3-pip + +addons: + apt: + packages: + - "python3" + - "python3-pip" + +cache: + - rust + - cargo + +before-install: + - openssl version + +install: + - rustup target add thumbv7em-none-eabi + - rustup component add rustfmt + - cargo install cargo-audit + +script: + - ./setup.sh + - cargo audit + - ./run_desktop_tests.sh diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..755027f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "davidanson.vscode-markdownlint", + "rust-lang.rust" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f502ab1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.formatOnSave": true, + "rust-client.channel": "nightly", + // The toolchain is updated from time to time so let's make sure that RLS is updated too + "rust-client.updateOnStartup": true, + "rust.clippy_preference": "on" +} diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..624c37a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "ctap2" +version = "0.1.0" +authors = [ + "Fabian Kaczmarczyck ", + "Guillaume Endignoux ", + "Jean-Michel Picod ", +] +license = "Apache-2.0" +edition = "2018" + +[dependencies] +libtock = { path = "third_party/libtock-rs" } +cbor = { path = "libraries/cbor" } +crypto = { path = "libraries/crypto" } +byteorder = { version = "1", default-features = false } +arrayref = "0.3.6" +subtle = { version = "2.2", default-features = false, features = ["nightly"] } + +[features] +std = ["cbor/std", "crypto/std", "crypto/derive_debug"] +debug_ctap = ["crypto/derive_debug"] +with_ctap1 = ["crypto/with_ctap1"] +panic_console = ["libtock/panic_console"] + +[dev-dependencies] +elf2tab = "0.4.0" + +[profile.dev] +panic = "abort" +lto = true # Link Time Optimization usually reduces size of binaries and static libraries + +[profile.release] +panic = "abort" +lto = true # Link Time Optimization usually reduces size of binaries and static libraries diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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/README.md b/README.md new file mode 100644 index 0000000..ade3a63 --- /dev/null +++ b/README.md @@ -0,0 +1,116 @@ +# OpenSK logo + +[![Build Status](https://travis-ci.org/google/OpenSK.svg?branch=master)](https://travis-ci.org/google/OpenSK) + +## OpenSK + +This repository contains a Rust implementation of a +[FIDO2](https://fidoalliance.org/fido2/) authenticator. + +We developed this as a [Tock OS](https://tockos.org) application and it has been +successfully tested on the following boards: + +* [Nordic nRF52840-DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) +* [Nordic nRF52840-dongle](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle) + +## Disclaimer + +This project is proof-of-concept and a research platform. It's still under +development and as such comes with a few limitations: + +### FIDO2 + +Although we tested and implemented our firmware based on the published +[CTAP2.0 specifications](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html), +our implementation was not reviewed nor officially tested and doesn't claim to +be FIDO Certified. + +### Cryptography + +We're currently still in the process on making the +[ARM® CryptoCell-310](https://developer.arm.com/ip-products/security-ip/cryptocell-300-family) +embedded in the +[Nordic nRF52840 chip](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fcryptocell.html) +work to get hardware-accelerated cryptography. In the meantime we implemented +the required cryptography algorithms (ECDSA, ECC secp256r1, HMAC-SHA256 and +AES256) in Rust as a placeholder. Those implementations are research-quality +code and haven't been reviewed. They don't provide constant-time guarantees and +are not designed to be resistant against side-channel attacks. + +## Installation + +For a more detailed guide, please refer to our +[installation guide](docs/install.md). + +1. If you just cloned this repository, run the following script (**Note**: you + only need to do this once): + + ```shell + ./setup.sh + ``` + +2. If you have a Nordic development board that doesn't already have Tock OS + installed, you can install both TockOS and the OpenSK application by running + the following command, depending on the board you have: + + ```shell + # Nordic nRF52840-DK board + board=nrf52840dk ./deploy.sh os app + # Nordic nRF52840-Dongle + board=nrf52840_dongle ./deploy.sh os app + ``` + +3. If Tock OS is already installed and you want to install/update the OpenSK + application on your board (**Warning**: it will erase the locally stored + credentials), run: + + ```shell + ./deploy.sh app + ``` + +4. On Linux, you may want to avoid the need for `root` privileges to interact + with the key. For that purpose we provide a udev rule file that can be + installed with the following command: + + ```shell + sudo cp rules.d/55-opensk.rules /etc/udev/rules.d/ && + sudo udevadm control --reload + ``` + +### Customization + +If you build your own security key, depending on the hardware you use, there are +a few things you can personalize: + +1. If you have multiple buttons, choose the buttons responsible for user + presence in main.rs. +2. Decide whether you want to use batch attestation. There is a boolean flag in + `ctap/mod.rs`. It is mandatory for U2F, and you can create your own + self-signed certificate. The flag is used for FIDO2 and has some privacy + implications. Please check + [WebAuthn](https://www.w3.org/TR/webauthn/#attestation) for more + information. +3. Decide whether you want to use signature counters. Currently, only global + signature counters are implemented, as they are the default option for U2F. + The flag in `ctap/mod.rs` only turns them off for FIDO2. The most privacy + preserving solution is individual or no signature counters. Again, please + check [WebAuthn](https://www.w3.org/TR/webauthn/#signature-counter) for + documentation. +4. Depending on your available flash storage, choose an appropriate maximum + number of supported residential keys and number of pages in + `ctap/storage.rs`. + +### 3D printed enclosure + +To protect and carry your key, we partnered with a professional designer and we +are providing a custom enclosure that can be printed on both professional 3D +printers and hobbyist models. + +All the required files can be downloaded from +[Thingiverse](https://www.thingiverse.com/thing:4132768) including the STEP +file, allowing you to easily make the modifications you need to further +customize it. + +## Contributing + +See [Contributing.md](docs/contributing.md). diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..974888d --- /dev/null +++ b/deploy.sh @@ -0,0 +1,321 @@ +#!/usr/bin/env bash +# Copyright 2019 Google LLC +# +# 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. + + +set -e + +if [ "x$VERBOSE" != "x" ] +then + set -x +fi + +info_text="$(tput bold)info:$(tput sgr0)" +error_text="$(tput bold)error:$(tput sgr0)" + +tab_folder="target/tab" +# elf2tab requires a file named "cortex-m4", so this path is used for all +# target applications. +elf_file_name="${tab_folder}/cortex-m4.elf" + +# elf2tab 0.4.0 and below uses "-n" flag but 0.5.0-dev changes that "-p" or +# "--package-name" +# We try to be compatible with both versions. +elf2tab_package_param="-n" +if which elf2tab > /dev/null 2>&1 +then + if [ "$(elf2tab --version | cut -d' ' -f2)" = "0.5.0-dev" ] + then + # Short parameter is "-p" but long parameter names should be prefered + # when they are used in scripts. + elf2tab_package_param="--package-name" + fi +else + echo "" + echo "Command elf2tab not found. Have you run the setup.sh script?" + exit 2 +fi + +# We need to specify the board explicitly to be able to flash after 0x80000. +tockloader_flags=( + --jlink + --board="${board:-nrf52840}" + --arch=cortex-m4 + --jlink-device=nrf52840_xxaa + --page-size=4096 +) + +declare -A supported_boards +supported_boards["nrf52840dk"]="Y" +supported_boards["nrf52840_dongle"]="Y" + +declare -A enabled_features=( [with_ctap1]=Y ) + +print_usage () { + cat < + +Example: + In order to install TockOS and a debug version of OpenSK on a Nordic nRF52840-DK + board, you need to run the following command: + board=nrf52840dk $0 --panic-console os app_debug + +Actions: + os + Compiles and installs Tock OS on the selected board. + The target board must be indicated by setting the environment + variable \$board + + app + Compiles and installs OpenSK application. + + app_debug + Compiles and installs OpenSK application in debug mode (i.e. more debug messages + will be sent over the console port such as hexdumps of packets) + + crypto_bench + Compiles and installs the crypto_bench example that tests the performance + of the cryptographic algorithms on the board. + +Options: + --dont-clear-apps + When installing an application, previously installed applications won't + be erased from the board. + + --no-u2f + Compiles OpenSK application without backward compatible support for + U2F/CTAP1 protocol. + + + --regen-keys + Forces the generation of src/ctap/key_materials.rs file. + This won't force re-generate OpenSSL files under crypto_data/ directory. + If the OpenSSL files needs to be re-generated, simply delete them (or + the whole directory). + + --panic-console + In case of application panic, the console will be used to output messages + before starting blinking the LEDs on the board. + +EOH +} + +display_supported_boards () { + echo "$info_text Currently supported boards are:" + for b in ${!supported_boards[@]} + do + if [ -d "third_party/tock/boards/nordic/$b" -a \ + -e "third_party/tock/boards/nordic/$b/Cargo.toml" ] + then + echo " - $b" + fi + done +} + +# Import generate_crypto_materials function +source tools/gen_key_materials.sh + +build_app_padding () { + # On nRF52840, the MMU can have 8 sub-regions and the flash size is 0x1000000. + # By default, applications are flashed at 0x30000 which means the maximum size + # for an application is 0x40000 (an application of size 0x80000 would need 16 + # sub-regions of size 0x10000; sub-regions need to be aligned on their size). + # This padding permits to have the application start at 0x40000 and increase + # the maximum application size to 0x80000 (with 4 sub-regions of size + # 0x40000). + ( + # Version: 2 + echo -n "0200" + # Header size: 0x10 + echo -n "1000" + # Total size: 0x10000 + echo -n "00000100" + # Flags: 0 + echo -n "00000000" + # Checksum + echo -n "02001100" + ) | xxd -p -r > "${tab_folder}/padding.bin" +} + +build_app () { + # Flatten the array + # This is equivalent to the following python snippet: ' '.join(arr).replace(' ', ',') + local feature_list=$(IFS=$'\n'; echo "$@") + if [ "X${feature_list}" != "X" ] + then + feature_list="${feature_list// /,}" + fi + + cargo build \ + --release \ + --target=thumbv7em-none-eabi \ + --features="${feature_list}" + + mkdir -p "target/tab" + cp "target/thumbv7em-none-eabi/release/ctap2" "$elf_file_name" + + elf2tab \ + "${elf2tab_package_param}" "ctap2" \ + -o "${tab_folder}/ctap2.tab" \ + "$elf_file_name" \ + --stack 16384 \ + --app-heap 90000 \ + --kernel-heap 1024 \ + --protected-region-size=64 +} + +build_crypto_bench () { + cargo build \ + --release \ + --target=thumbv7em-none-eabi \ + --example crypto_bench + + mkdir -p "target/tab" + cp "target/thumbv7em-none-eabi/release/examples/crypto_bench" "$elf_file_name" + + elf2tab \ + "${elf2tab_package_param}" "crypto_bench" \ + -o "${tab_folder}/crypto_bench.tab" \ + "$elf_file_name" \ + --stack 16384 \ + --app-heap 90000 \ + --kernel-heap 1024 \ + --protected-region-size=64 +} + +deploy_tock () { + if [ "x$board" = "x" ]; + then + echo "$error_text You must set the board in order to install Tock OS (example: \`board=nrf52840dk $0 os\`)" + display_supported_boards + return 1 + fi + if [ "${supported_boards[$board]+x}" = "x" -a -d "third_party/tock/boards/nordic/$board" ] + then + make -C third_party/tock/boards/nordic/$board flash + else + echo "$error_text The board '$board' doesn't seem to be supported" + display_supported_boards + return 1 + fi +} + +clear_apps=Y +install_os=N +install_app=none + +force_generate=N +has_errors=N + +if [ "$#" -eq "0" ] +then + print_usage + exit 1 +fi + +while [ "$#" -ge "1" ] +do + case "$1" in + --dont-clear-apps) + clear_apps=N + ;; + + --no-u2f) + unset enabled_features["with_ctap1"] + ;; + + --regen-keys) + force_generate=Y + ;; + + --panic-console) + enabled_features["panic_console"]="Y" + ;; + + os) + install_os=Y + ;; + + app) + install_app=ctap2 + ;; + + app_debug) + install_app=ctap2 + enabled_features["debug_ctap"]="Y" + ;; + + crypto_bench) + install_app=crypto_bench + ;; + + *) + echo "$error_text Unsupported option: '"$1"'" + has_errors=Y + ;; + esac + shift 1 +done + +if [ "$has_errors" = "Y" ] +then + echo "" + print_usage + exit 1 +fi + +# Test if we need to update Rust toolchain +# rustc --version outputs a version line such as: +# rustc 1.40.0-nightly (0e8a4b441 2019-10-16) +# The sed regexp turns it into: +# nightly-2019-10-16 +current_toolchain=$(rustc --version | sed -e 's/^rustc [0-9]*\.[0-9]*\.[0-9]*-\(nightly\) ([0-9a-f]* \([0-9]*-[0-9]*-[0-9]*\))$/\1-\2/') +target_toolchain=$(head -n 1 rust-toolchain) + +if [ "x${current_toolchain}" != "x${target_toolchain}" ] +then + rustup install "${target_toolchain}" + rustup target add thumbv7em-none-eabi +fi + +if [ "$install_os" = "Y" ] +then + deploy_tock +fi + +# Don't try to uninstall app if we don't plan to install after. +if [ "$install_app" != "none" -a "$clear_apps" = "Y" ] +then + # Uninstall can fail if there's no app already installed. + # This is fine and we don't want that to stop the script + tockloader uninstall "${tockloader_flags[@]}" -a 0x40000 || true +fi + +if [ "$install_app" = "ctap2" ] +then + generate_crypto_materials "${force_generate}" + build_app "${!enabled_features[@]}" +fi + +if [ "$install_app" = "crypto_bench" ] +then + build_crypto_bench +fi + +if [ "$install_app" != "none" ] +then + build_app_padding + tockloader flash "${tockloader_flags[@]}" -a 0x30000 "${tab_folder}/padding.bin" + tockloader install "${tockloader_flags[@]}" -a 0x40000 "${tab_folder}/${install_app}.tab" +fi diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..654a071 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google/conduct/). diff --git a/docs/img/OpenSK.svg b/docs/img/OpenSK.svg new file mode 100644 index 0000000..df1692d --- /dev/null +++ b/docs/img/OpenSK.svg @@ -0,0 +1 @@ +Mono \ No newline at end of file diff --git a/docs/img/devkit_annotated.jpg b/docs/img/devkit_annotated.jpg new file mode 100644 index 0000000..1fff7d3 Binary files /dev/null and b/docs/img/devkit_annotated.jpg differ diff --git a/docs/img/dongle_clip.jpg b/docs/img/dongle_clip.jpg new file mode 100644 index 0000000..636e1a5 Binary files /dev/null and b/docs/img/dongle_clip.jpg differ diff --git a/docs/img/dongle_front.jpg b/docs/img/dongle_front.jpg new file mode 100644 index 0000000..7d090d7 Binary files /dev/null and b/docs/img/dongle_front.jpg differ diff --git a/docs/img/dongle_pads.jpg b/docs/img/dongle_pads.jpg new file mode 100644 index 0000000..32348a7 Binary files /dev/null and b/docs/img/dongle_pads.jpg differ diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 0000000..502cf02 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,262 @@ +OpenSK logo + +# Installation guide + +This document describes in details how to turn a Nordic nRF52840 board into a +working FIDO2 security key. + +## Pre-requisite + +### Hardware + +You will need one the following supported boards: + +* [Nordic nRF52840-DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) + development kit. This board is more convenient for development and debug + scenarios as the JTAG probe is already on the board. +* [Nordic nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle) + to have a more practical form factor. + +In the case of the Nordic USB dongle, you will also need the following extra +hardware: + +* a [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) JTAG + probe. +* a + [TC2050 Tag-Connect programming cable](http://www.tag-connect.com/TC2050-IDC-NL-050-ALL). +* a [Tag-Connect TC2050 ARM2010](http://www.tag-connect.com/TC2050-ARM2010) + adaptor +* optionally a + [Tag-Connect TC2050 retainer clip](http://www.tag-connect.com/TC2050-CLIP) + to keep the spring loaded connector pressed to the PCB. + +Although [OpenOCD](http://openocd.org/) should be supported we encountered some +issues while trying to flash a firmware with it. Therefore we suggest at the +moment to use a +[Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) probe +instead. + +This guide **does not** cover how to setup the JTAG probe on your system. + +### Software + +In order to compile and flash a working OpenSK firmware, you will need the +following: + +* a working [Rust](https://rustup.rs/) toolchain installed on your system +* python3 and pip +* the OpenSSL command line tool + +The scripts provided in this project have been tested under Linux and OS X. We +haven't tested them on Windows and other platforms. + +## Compiling the firmware + +### Initial setup + +If you just cloned this repository, you need to run the following script +(_output may differ_): + +```shell +$ ./setup.sh +[-] Applying patch "01-persistent-storage.patch"... DONE. +[-] Applying patch "02-usb.patch"... DONE. +[-] Applying patch "03-app-memory.patch"... DONE. +[-] Applying patch "04-rtt.patch"... DONE. +[-] Applying patch "01-panic_console.patch"... DONE. +[-] Applying patch "02-timer.patch"... DONE. +[-] Applying patch "03-public_syscalls.patch"... DONE. +[-] Applying patch "04-bigger_heap.patch"... DONE. +Signature ok +subject=CN = Google OpenSK CA +Getting Private key +Signature ok +subject=CN = Google OpenSK Hacker Edition +Getting CA Private Key +info: syncing channel updates for 'nightly-2020-01-16-x86_64-unknown-linux-gnu' + + nightly-2020-01-16-x86_64-unknown-linux-gnu unchanged - rustc 1.42.0-nightly (3291ae339 2020-01-15) + +Requirement already up-to-date: tockloader in /usr/lib/python3/dist-packages/tockloader-1.4.0.dev0-py3.7.egg (1.4.0.dev0) +Requirement already satisfied, skipping upgrade: argcomplete>=1.8.2 in /usr/lib/python3/dist-packages (from tockloader) (1.10.0) +Requirement already satisfied, skipping upgrade: colorama>=0.3.7 in /usr/lib/python3/dist-packages (from tockloader) (0.3.7) +Requirement already satisfied, skipping upgrade: crcmod>=1.7 in /usr/lib/python3/dist-packages (from tockloader) (1.7) +Requirement already satisfied, skipping upgrade: pyserial>=3.0.1 in /usr/lib/python3/dist-packages (from tockloader) (3.4) +Requirement already satisfied, skipping upgrade: pytoml>=0.1.11 in /usr/lib/python3/dist-packages (from tockloader) (0.1.21) +info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date + Updating crates.io index + Ignored package `elf2tab v0.4.0` is already installed, use --force to override +``` + +The script performs the following steps: + +1. Make sure that the git submodules are checked out + +1. Apply our patches that haven't yet been merged upstream to both + [Tock](https://github.com/tock/tock) and + [libtock-rs](https://github.com/tock/libtock-rs) + +1. Generate a self-signed certificate authority as well as a private key and a + corresponding certificate for your OpenSK key signed by this CA. You will be + able to replace them with your own certificate and private key. + +1. Ensure that your Rust toolchain is using the same version that we tested + OpenSK with. + +1. Install [tockloader](https://github.com/tock/tockloader). + +1. Ensure that the Rust toolchain can compile code for ARM devices. + +### Replacing the certificates + +All the generated certificates and private keys are stored in the directory +`crypto_data/`. + +This is the expected content after running our `setup.sh` script: + +File | Purpose +----------------- | -------------------------------------------------------- +`opensk_ca.csr` | Certificate sign request for the Root CA +`opensk_ca.key` | ECC secp256r1 private key used for the Root CA +`opensk_ca.pem` | PEM encoded certificate of the Root CA +`opensk_ca.srl` | File generated by OpenSSL +`opensk_cert.csr` | Certificate sign request for the attestation certificate +`opensk_cert.pem` | PEM encoded certificate used for the authenticator +`opensk.key` | ECC secp256r1 private key used for the autenticator + +If you want to use your own attestation certificate and private key, simply +replace `opensk_cert.pem` and `opensk.key` files. + +Our build script is responsible for converting `opensk_cert.pem` and +`opensk.key` files into the following Rust file: `src/ctap/key_material.rs`. + +### Flashing a firmware + +#### Nordic nRF52840-DK board + +![Nordic development kit](img/devkit_annotated.jpg) + +1. Connect a micro USB cable to the JTAG USB port. + +1. Run our script for compiling/flashing your device (_output may differ_): + + ```shell + $ board=nrf52840dk ./deploy.sh app os + make: Entering directory './third_party/tock/boards/nordic/nrf52840dk' + Compiling kernel v0.1.0 (./third_party/tock/kernel) + Compiling cortexm v0.1.0 (./third_party/tock/arch/cortex-m) + Compiling nrf5x v0.1.0 (./third_party/tock/chips/nrf5x) + Compiling capsules v0.1.0 (./third_party/tock/capsules) + Compiling cortexm4 v0.1.0 (./third_party/tock/arch/cortex-m4) + Compiling nrf52 v0.1.0 (./third_party/tock/chips/nrf52) + Compiling nrf52840 v0.1.0 (./third_party/tock/chips/nrf52840) + Compiling components v0.1.0 (./third_party/tock/boards/components) + Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base) + Compiling nrf52840dk v0.1.0 (./third_party/tock/boards/nordic/nrf52840dk) + Finished release [optimized + debuginfo] target(s) in 11.28s + text data bss dec hex filename + 114688 1760 260384 376832 5c000 target/thumbv7em-none-eabi/release/nrf52840dk + tockloader flash --address 0x00000 --jlink --board nrf52dk target/thumbv7em-none-eabi/release/nrf52840dk.bin + [STATUS ] Flashing binar(y|ies) to board... + [INFO ] Using known arch and jtag-device for known board nrf52dk + [INFO ] Finished in 0.324 seconds + + make: Leaving directory './third_party/tock/boards/nordic/nrf52840dk' + [STATUS ] Preparing to uninstall apps... + [INFO ] Using known arch and jtag-device for known board nrf52dk + [ERROR ] No apps are installed on the board + + Compiling libtock v0.1.0 (./third_party/libtock-rs) + Compiling crypto v0.1.0 (./libraries/crypto) + Compiling ctap2 v0.1.0 (.) + Finished release [optimized] target(s) in 7.60s + [STATUS ] Flashing binar(y|ies) to board... + [INFO ] Using known arch and jtag-device for known board nrf52dk + [INFO ] Finished in 0.305 seconds + + [STATUS ] Installing app on the board... + [INFO ] Using known arch and jtag-device for known board nrf52dk + [INFO ] Finished in 0.975 seconds + ``` + +1. Connect a micro USB cable to the device USB port. + +**Note**: Due to current limitations of our implementation and Tock, you may +have to press the `BOOT/RESET` button, located next to the device USB port on +the board in order to see your OpenSK device on your system. + +#### Nordic nRF52840 Dongle + +![Nordic dongle](img/dongle_front.jpg) + +1. The JTAG probe used for programming won't provide power to the board. + Therefore you will need to use a USB-A extension cable to power the dongle + through its USB port. + +1. Connect the TC2050 cable to the pads below the PCB: + + ![Nordic dongle pads](img/dongle_pads.jpg) + +1. You can use the retainer clip if you have one to avoid maintaining pressure + between the board and the cable: + + ![Nordic dongle retainer clip](img/dongle_clip.jpg) + +1. Run our script for compiling/flashing your device (_output may differ_): + + ```shell + $ board=nrf52840_dongle ./deploy.sh app os + make: Entering directory './third_party/tock/boards/nordic/nrf52840_dongle' + Compiling kernel v0.1.0 (./third_party/tock/kernel) + Compiling cortexm v0.1.0 (./third_party/tock/arch/cortex-m) + Compiling nrf5x v0.1.0 (./third_party/tock/chips/nrf5x) + Compiling capsules v0.1.0 (./third_party/tock/capsules) + Compiling cortexm4 v0.1.0 (./third_party/tock/arch/cortex-m4) + Compiling nrf52 v0.1.0 (./third_party/tock/chips/nrf52) + Compiling nrf52840 v0.1.0 (./third_party/tock/chips/nrf52840) + Compiling components v0.1.0 (./third_party/tock/boards/components) + Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base) + Compiling nrf52840_dongle v0.1.0 (./third_party/tock/boards/nordic/nrf52840_dongle) + Finished release [optimized + debuginfo] target(s) in 10.47s + text data bss dec hex filename + 110592 1688 252264 364544 59000 target/thumbv7em-none-eabi/release/nrf52840_dongle + tockloader flash --address 0x00000 --jlink --board nrf52dk target/thumbv7em-none-eabi/release/nrf52840_dongle.bin + [STATUS ] Flashing binar(y|ies) to board... + [INFO ] Using known arch and jtag-device for known board nrf52dk + [INFO ] Finished in 0.296 seconds + + make: Leaving directory './third_party/tock/boards/nordic/nrf52840_dongle' + [STATUS ] Preparing to uninstall apps... + [INFO ] Using known arch and jtag-device for known board nrf52dk + [ERROR ] No apps are installed on the board + + Compiling libtock v0.1.0 (./third_party/libtock-rs) + Compiling crypto v0.1.0 (./libraries/crypto) + Compiling ctap2 v0.1.0 (.) + Finished release [optimized] target(s) in 7.60s + [STATUS ] Flashing binar(y|ies) to board... + [INFO ] Using known arch and jtag-device for known board nrf52dk + [INFO ] Finished in 0.317 seconds + + [STATUS ] Installing app on the board... + [INFO ] Using known arch and jtag-device for known board nrf52dk + [INFO ] Finished in 0.902 seconds + ``` + +1. Remove the programming cable and the USB-A extension cable. + +### Installing the udev rule (Linux only) + +By default on Linux, a USB device will require root privilege in order interact +with it. As it is not recommended to run your web browser with such a high +privileged account, we made a `udev` rule file to allow regular users to +interact with OpenSK authenticators. + +To install it, you need to execute the following commands: + +```shell +sudo cp rules.d/55-opensk.rules /etc/udev/rules.d/ +sudo udevadm control --reload +``` + +Then, you will need to unplug and replug the key for the rule to trigger. diff --git a/examples/crypto_bench.rs b/examples/crypto_bench.rs new file mode 100644 index 0000000..95a82d2 --- /dev/null +++ b/examples/crypto_bench.rs @@ -0,0 +1,180 @@ +// Copyright 2019 Google LLC +// +// 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. + +#![no_std] + +#[macro_use] +extern crate alloc; +extern crate crypto; +extern crate libtock; + +use alloc::vec::Vec; +use core::fmt::Write; +use crypto::{ + aes256, cbc, ecdsa, rng256, sha256, Decrypt16BytesBlock, Encrypt16BytesBlock, Hash256, +}; +use libtock::console::Console; +use libtock::timer; +use libtock::timer::Timer; +use libtock::timer::Timestamp; + +fn main() { + let mut console = Console::new(); + // Setup the timer with a dummy callback (we only care about reading the current time, but the + // API forces us to set an alarm callback too). + let mut with_callback = timer::with_callback(|_, _| {}); + let timer = with_callback.init().unwrap(); + + let mut rng = rng256::TockRng256 {}; + + writeln!(console, "****************************************").unwrap(); + writeln!( + console, + "Clock frequency: {} Hz", + timer.clock_frequency().hz() + ) + .unwrap(); + + // AES + bench(&mut console, &timer, "aes256::EncryptionKey::new", || { + aes256::EncryptionKey::new(&[0; 32]); + }); + let ek = aes256::EncryptionKey::new(&[0; 32]); + bench(&mut console, &timer, "aes256::DecryptionKey::new", || { + aes256::DecryptionKey::new(&ek); + }); + let dk = aes256::DecryptionKey::new(&ek); + + bench( + &mut console, + &timer, + "aes256::EncryptionKey::encrypt_block", + || { + ek.encrypt_block(&mut [0; 16]); + }, + ); + bench( + &mut console, + &timer, + "aes256::DecryptionKey::decrypt_block", + || { + dk.decrypt_block(&mut [0; 16]); + }, + ); + + // CBC + let mut blocks = Vec::new(); + for i in 0..8 { + blocks.resize(1 << i, [0; 16]); + bench( + &mut console, + &timer, + &format!("cbc::cbc_encrypt({} bytes)", blocks.len() * 16), + || { + cbc::cbc_encrypt(&ek, [0; 16], &mut blocks); + }, + ); + } + drop(blocks); + + let mut blocks = Vec::new(); + for i in 0..8 { + blocks.resize(1 << i, [0; 16]); + bench( + &mut console, + &timer, + &format!("cbc::cbc_decrypt({} bytes)", blocks.len() * 16), + || { + cbc::cbc_decrypt(&dk, [0; 16], &mut blocks); + }, + ); + } + drop(blocks); + + // SHA-256 + let mut contents = Vec::new(); + for i in 0..8 { + contents.resize(16 << i, 0); + bench( + &mut console, + &timer, + &format!("sha256::Sha256::update({} bytes)", contents.len()), + || { + let mut sha = sha256::Sha256::new(); + sha.update(&contents); + sha.finalize(); + }, + ); + } + drop(contents); + + // ECDSA + bench(&mut console, &timer, "ecdsa::SecKey::gensk", || { + ecdsa::SecKey::gensk(&mut rng); + }); + let k = ecdsa::SecKey::gensk(&mut rng); + bench(&mut console, &timer, "ecdsa::SecKey::genpk", || { + k.genpk(); + }); + bench( + &mut console, + &timer, + "ecdsa::SecKey::sign_rng::", + || { + k.sign_rng::(&[], &mut rng); + }, + ); + bench( + &mut console, + &timer, + "ecdsa::SecKey::sign_rfc6979::", + || { + k.sign_rfc6979::(&[]); + }, + ); + + writeln!(console, "****************************************").unwrap(); + writeln!(console, "All the benchmarks are done.\nHave a nice day!").unwrap(); + writeln!(console, "****************************************").unwrap(); +} + +fn bench(console: &mut Console, timer: &Timer, title: &str, mut f: F) +where + F: FnMut() -> (), +{ + writeln!(console, "****************************************").unwrap(); + writeln!(console, "Benchmarking: {}", title).unwrap(); + writeln!(console, "----------------------------------------").unwrap(); + let mut count = 1; + for _ in 0..30 { + let start = Timestamp::::from_clock_value(timer.get_current_clock()); + for _ in 0..count { + f(); + } + let end = Timestamp::::from_clock_value(timer.get_current_clock()); + let elapsed = (end - start).ms(); + writeln!( + console, + "{} ms elapsed for {} iterations ({} ms/iter)", + elapsed, + count, + elapsed / (count as f64) + ) + .unwrap(); + if elapsed > 1000.0 { + break; + } + count <<= 1; + } +} diff --git a/layout.ld b/layout.ld new file mode 100644 index 0000000..5bc9d0c --- /dev/null +++ b/layout.ld @@ -0,0 +1,170 @@ +/* Userland Generic Layout + * + * Currently, due to incomplete ROPI-RWPI support in rustc (see + * https://github.com/tock/libtock-rs/issues/28), this layout implements static + * linking. An application init script must define the FLASH and SRAM address + * ranges as well as MPU_MIN_ALIGN before including this layout file. + * + * Here is a an example application linker script to get started: + * MEMORY { + * /* FLASH memory region must start immediately *after* the Tock + * * Binary Format headers, which means you need to offset the + * * beginning of FLASH memory region relative to where the + * * application is loaded. + * FLASH (rx) : ORIGIN = 0x10030, LENGTH = 0x0FFD0 + * SRAM (RWX) : ORIGIN = 0x20000, LENGTH = 0x10000 + * } + * STACK_SIZE = 2048; + * MPU_MIN_ALIGN = 8K; + * INCLUDE ../libtock-rs/layout.ld + */ + +ENTRY(_start) + +SECTIONS { + /* Section for just the app crt0 header. + * This must be first so that the app can find it. + */ + .crt0_header : + { + _beginning = .; /* Start of the app in flash. */ + /** + * Populate the header expected by `crt0`: + * + * struct hdr { + * uint32_t got_sym_start; + * uint32_t got_start; + * uint32_t got_size; + * uint32_t data_sym_start; + * uint32_t data_start; + * uint32_t data_size; + * uint32_t bss_start; + * uint32_t bss_size; + * uint32_t reldata_start; + * uint32_t stack_size; + * }; + */ + /* Offset of GOT symbols in flash */ + LONG(LOADADDR(.got) - _beginning); + /* Offset of GOT section in memory */ + LONG(_got); + /* Size of GOT section */ + LONG(SIZEOF(.got)); + /* Offset of data symbols in flash */ + LONG(LOADADDR(.data) - _beginning); + /* Offset of data section in memory */ + LONG(_data); + /* Size of data section */ + LONG(SIZEOF(.data)); + /* Offset of BSS section in memory */ + LONG(_bss); + /* Size of BSS section */ + LONG(SIZEOF(.bss)); + /* First address offset after program flash, where elf2tab places + * .rel.data section */ + LONG(LOADADDR(.endflash) - _beginning); + /* The size of the stack requested by this application */ + LONG(STACK_SIZE); + /* Pad the header out to a multiple of 32 bytes so there is not a gap + * between the header and subsequent .data section. It's unclear why, + * but LLD is aligning sections to a multiple of 32 bytes. */ + . = ALIGN(32); + } > FLASH =0xFF + + /* App state section. Used for persistent app data. + * We put this first because this is what libtock-c does. They provide the + * following explanation: if the app code changes but the persistent data + * doesn't, the app_state can be preserved. + */ + .wfr.app_state : + { + . = ALIGN(4K); + KEEP (*(.app_state)) + . = ALIGN(4K); + } > FLASH =0xFFFFFFFF + + /* Text section, Code! */ + .text : + { + . = ALIGN(4); + _text = .; + KEEP (*(.start)) + *(.text*) + *(.rodata*) + KEEP (*(.syscalls)) + *(.ARM.extab*) + . = ALIGN(4); /* Make sure we're word-aligned here */ + _etext = .; + } > FLASH =0xFF + + /* Application stack */ + .stack : + { + . = . + STACK_SIZE; + + _stack_top_unaligned = .; + . = ALIGN(8); + _stack_top_aligned = .; + } > SRAM + + /* Data section, static initialized variables + * Note: This is placed in Flash after the text section, but needs to be + * moved to SRAM at runtime + */ + .data : AT (_etext) + { + . = ALIGN(4); /* Make sure we're word-aligned here */ + _data = .; + KEEP(*(.data*)) + . = ALIGN(4); /* Make sure we're word-aligned at the end of flash */ + } > SRAM + + /* Global Offset Table */ + .got : + { + . = ALIGN(4); /* Make sure we're word-aligned here */ + _got = .; + *(.got*) + *(.got.plt*) + . = ALIGN(4); + } > SRAM + + /* BSS section, static uninitialized variables */ + .bss : + { + . = ALIGN(4); /* Make sure we're word-aligned here */ + _bss = .; + KEEP(*(.bss*)) + *(COMMON) + . = ALIGN(4); + } > SRAM + + /* End of flash. */ + .endflash : + { + } > FLASH + + /* ARM Exception support + * + * This contains compiler-generated support for unwinding the stack, + * consisting of key-value pairs of function addresses and information on + * how to unwind stack frames. + * https://wiki.linaro.org/KenWerner/Sandbox/libunwind?action=AttachFile&do=get&target=libunwind-LDS.pdf + * + * .ARM.exidx is sorted, so has to go in its own output section. + * + * __NOTE__: It's at the end because we currently don't actually serialize + * it to the binary in elf2tbf. If it was before the RAM sections, it would + * through off our calculations of the header. + */ + PROVIDE_HIDDEN (__exidx_start = .); + .ARM.exidx : + { + /* (C++) Index entries for section unwinding */ + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > FLASH + PROVIDE_HIDDEN (__exidx_end = .); +} + +ASSERT((_stack_top_aligned - _stack_top_unaligned) == 0, " +STACK_SIZE must be 8 byte multiple") diff --git a/libraries/cbor/Cargo.toml b/libraries/cbor/Cargo.toml new file mode 100644 index 0000000..bcae013 --- /dev/null +++ b/libraries/cbor/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cbor" +version = "0.1.0" +authors = [ + "Fabian Kaczmarczyck ", + "Guillaume Endignoux ", + "Jean-Michel Picod ", +] +license = "Apache-2.0" +edition = "2018" + +[dependencies] + +[features] +std = [] diff --git a/libraries/cbor/src/lib.rs b/libraries/cbor/src/lib.rs new file mode 100644 index 0000000..00a9c55 --- /dev/null +++ b/libraries/cbor/src/lib.rs @@ -0,0 +1,29 @@ +// Copyright 2019 Google LLC +// +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +#[cfg(feature = "std")] +extern crate core; + +#[macro_use] +pub mod macros; +pub mod reader; +pub mod values; +pub mod writer; + +pub use self::reader::read; +pub use self::values::{KeyType, SimpleValue, Value}; +pub use self::writer::write; diff --git a/libraries/cbor/src/macros.rs b/libraries/cbor/src/macros.rs new file mode 100644 index 0000000..5a3d8f5 --- /dev/null +++ b/libraries/cbor/src/macros.rs @@ -0,0 +1,500 @@ +// Copyright 2019 Google LLC +// +// 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. + +#[macro_export] +macro_rules! cbor_map { + // trailing comma case + ( $( $key:expr => $value:expr, )+ ) => { + cbor_map! ( $($key => $value),+ ) + }; + + ( $( $key:expr => $value:expr ),* ) => { + { + // The import is unused if the list is empty. + #[allow(unused_imports)] + use $crate::values::{IntoCborKey, IntoCborValue}; + let mut _map = ::alloc::collections::BTreeMap::new(); + $( + _map.insert($key.into_cbor_key(), $value.into_cbor_value()); + )* + $crate::values::Value::Map(_map) + } + }; +} + +#[macro_export] +macro_rules! cbor_map_options { + // trailing comma case + ( $( $key:expr => $value:expr, )+ ) => { + cbor_map_options! ( $($key => $value),+ ) + }; + + ( $( $key:expr => $value:expr ),* ) => { + { + // The import is unused if the list is empty. + #[allow(unused_imports)] + use $crate::values::{IntoCborKey, IntoCborValueOption}; + let mut _map = ::alloc::collections::BTreeMap::<_, $crate::values::Value>::new(); + $( + { + let opt: Option<$crate::values::Value> = $value.into_cbor_value_option(); + if let Some(val) = opt { + _map.insert($key.into_cbor_key(), val); + } + } + )* + $crate::values::Value::Map(_map) + } + }; +} + +#[macro_export] +macro_rules! cbor_map_btree { + ( $tree:expr ) => { + $crate::values::Value::Map($tree) + }; +} + +#[macro_export] +macro_rules! cbor_array { + // trailing comma case + ( $( $value:expr, )+ ) => { + cbor_array! ( $($value),+ ) + }; + + ( $( $value:expr ),* ) => { + { + // The import is unused if the list is empty. + #[allow(unused_imports)] + use $crate::values::IntoCborValue; + $crate::values::Value::Array(vec![ $( $value.into_cbor_value(), )* ]) + } + }; +} + +#[macro_export] +macro_rules! cbor_array_vec { + ( $vec:expr ) => {{ + use $crate::values::IntoCborValue; + $crate::values::Value::Array($vec.into_iter().map(|x| x.into_cbor_value()).collect()) + }}; +} + +#[cfg(test)] +macro_rules! cbor_true { + ( ) => { + $crate::values::Value::Simple($crate::values::SimpleValue::TrueValue) + }; +} + +#[macro_export] +macro_rules! cbor_false { + ( ) => { + $crate::values::Value::Simple($crate::values::SimpleValue::FalseValue) + }; +} + +#[macro_export] +macro_rules! cbor_null { + ( ) => { + $crate::values::Value::Simple($crate::values::SimpleValue::NullValue) + }; +} + +#[cfg(test)] +macro_rules! cbor_undefined { + ( ) => { + $crate::values::Value::Simple($crate::values::SimpleValue::Undefined) + }; +} + +#[macro_export] +macro_rules! cbor_bool { + ( $x:expr ) => { + $crate::values::Value::bool_value($x) + }; +} + +// For key types, we construct a KeyType and call .into(), which will automatically convert it to a +// KeyType or a Value depending on the context. +#[macro_export] +macro_rules! cbor_unsigned { + ( $x:expr ) => { + cbor_key_unsigned!($x).into() + }; +} + +#[macro_export] +macro_rules! cbor_int { + ( $x:expr ) => { + cbor_key_int!($x).into() + }; +} + +#[macro_export] +macro_rules! cbor_text { + ( $x:expr ) => { + cbor_key_text!($x).into() + }; +} + +#[macro_export] +macro_rules! cbor_bytes { + ( $x:expr ) => { + cbor_key_bytes!($x).into() + }; +} + +// Macro to use with a literal, e.g. cbor_bytes_lit!(b"foo") +#[macro_export] +macro_rules! cbor_bytes_lit { + ( $x:expr ) => { + cbor_bytes!(($x as &[u8]).to_vec()) + }; +} + +// Some explicit macros are also available for contexts where the type is not explicit. +#[macro_export] +macro_rules! cbor_key_unsigned { + ( $x:expr ) => { + $crate::values::KeyType::Unsigned($x) + }; +} + +#[macro_export] +macro_rules! cbor_key_int { + ( $x:expr ) => { + $crate::values::KeyType::integer($x) + }; +} + +#[macro_export] +macro_rules! cbor_key_text { + ( $x:expr ) => { + $crate::values::KeyType::TextString($x.into()) + }; +} + +#[macro_export] +macro_rules! cbor_key_bytes { + ( $x:expr ) => { + $crate::values::KeyType::ByteString($x) + }; +} + +#[cfg(test)] +mod test { + use super::super::values::{KeyType, SimpleValue, Value}; + use alloc::collections::BTreeMap; + + #[test] + fn test_cbor_simple_values() { + assert_eq!(cbor_true!(), Value::Simple(SimpleValue::TrueValue)); + assert_eq!(cbor_false!(), Value::Simple(SimpleValue::FalseValue)); + assert_eq!(cbor_null!(), Value::Simple(SimpleValue::NullValue)); + assert_eq!(cbor_undefined!(), Value::Simple(SimpleValue::Undefined)); + } + + #[test] + fn test_cbor_bool() { + assert_eq!(cbor_bool!(true), Value::Simple(SimpleValue::TrueValue)); + assert_eq!(cbor_bool!(false), Value::Simple(SimpleValue::FalseValue)); + } + + #[test] + fn test_cbor_int_unsigned() { + assert_eq!(cbor_key_int!(0), KeyType::Unsigned(0)); + assert_eq!(cbor_key_int!(1), KeyType::Unsigned(1)); + assert_eq!(cbor_key_int!(123456), KeyType::Unsigned(123456)); + assert_eq!( + cbor_key_int!(std::i64::MAX), + KeyType::Unsigned(std::i64::MAX as u64) + ); + } + + #[test] + fn test_cbor_int_negative() { + assert_eq!(cbor_key_int!(-1), KeyType::Negative(-1)); + assert_eq!(cbor_key_int!(-123456), KeyType::Negative(-123456)); + assert_eq!( + cbor_key_int!(std::i64::MIN), + KeyType::Negative(std::i64::MIN) + ); + } + + #[test] + fn test_cbor_int_literals() { + let a = cbor_array![ + std::i64::MIN, + std::i32::MIN, + -123456, + -1, + 0, + 1, + 123456, + std::i32::MAX, + std::i64::MAX, + std::u64::MAX, + ]; + let b = Value::Array(vec![ + Value::KeyValue(KeyType::Negative(std::i64::MIN)), + Value::KeyValue(KeyType::Negative(std::i32::MIN as i64)), + Value::KeyValue(KeyType::Negative(-123456)), + Value::KeyValue(KeyType::Negative(-1)), + Value::KeyValue(KeyType::Unsigned(0)), + Value::KeyValue(KeyType::Unsigned(1)), + Value::KeyValue(KeyType::Unsigned(123456)), + Value::KeyValue(KeyType::Unsigned(std::i32::MAX as u64)), + Value::KeyValue(KeyType::Unsigned(std::i64::MAX as u64)), + Value::KeyValue(KeyType::Unsigned(std::u64::MAX)), + ]); + assert_eq!(a, b); + } + + #[test] + fn test_cbor_array() { + let a = cbor_array![ + -123, + 456, + true, + cbor_null!(), + "foo", + b"bar", + cbor_array![], + cbor_array![0, 1], + cbor_map! {}, + cbor_map! {2 => 3}, + ]; + let b = Value::Array(vec![ + Value::KeyValue(KeyType::Negative(-123)), + Value::KeyValue(KeyType::Unsigned(456)), + Value::Simple(SimpleValue::TrueValue), + Value::Simple(SimpleValue::NullValue), + Value::KeyValue(KeyType::TextString(String::from("foo"))), + Value::KeyValue(KeyType::ByteString(b"bar".to_vec())), + Value::Array(Vec::new()), + Value::Array(vec![ + Value::KeyValue(KeyType::Unsigned(0)), + Value::KeyValue(KeyType::Unsigned(1)), + ]), + Value::Map(BTreeMap::new()), + Value::Map( + [(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))] + .iter() + .cloned() + .collect(), + ), + ]); + assert_eq!(a, b); + } + + #[test] + fn test_cbor_array_vec_empty() { + let a = cbor_array_vec!(Vec::::new()); + let b = Value::Array(Vec::new()); + assert_eq!(a, b); + } + + #[test] + fn test_cbor_array_vec_int() { + let a = cbor_array_vec!(vec![1, 2, 3, 4]); + let b = Value::Array(vec![ + Value::KeyValue(KeyType::Unsigned(1)), + Value::KeyValue(KeyType::Unsigned(2)), + Value::KeyValue(KeyType::Unsigned(3)), + Value::KeyValue(KeyType::Unsigned(4)), + ]); + assert_eq!(a, b); + } + + #[test] + fn test_cbor_array_vec_text() { + let a = cbor_array_vec!(vec!["a", "b", "c"]); + let b = Value::Array(vec![ + Value::KeyValue(KeyType::TextString(String::from("a"))), + Value::KeyValue(KeyType::TextString(String::from("b"))), + Value::KeyValue(KeyType::TextString(String::from("c"))), + ]); + assert_eq!(a, b); + } + + #[test] + fn test_cbor_array_vec_bytes() { + let a = cbor_array_vec!(vec![b"a", b"b", b"c"]); + let b = Value::Array(vec![ + Value::KeyValue(KeyType::ByteString(b"a".to_vec())), + Value::KeyValue(KeyType::ByteString(b"b".to_vec())), + Value::KeyValue(KeyType::ByteString(b"c".to_vec())), + ]); + assert_eq!(a, b); + } + + #[test] + fn test_cbor_map() { + let a = cbor_map! { + -1 => -23, + 4 => 56, + "foo" => true, + b"bar" => cbor_null!(), + 5 => "foo", + 6 => b"bar", + 7 => cbor_array![], + 8 => cbor_array![0, 1], + 9 => cbor_map!{}, + 10 => cbor_map!{2 => 3}, + }; + let b = Value::Map( + [ + ( + KeyType::Negative(-1), + Value::KeyValue(KeyType::Negative(-23)), + ), + (KeyType::Unsigned(4), Value::KeyValue(KeyType::Unsigned(56))), + ( + KeyType::TextString(String::from("foo")), + Value::Simple(SimpleValue::TrueValue), + ), + ( + KeyType::ByteString(b"bar".to_vec()), + Value::Simple(SimpleValue::NullValue), + ), + ( + KeyType::Unsigned(5), + Value::KeyValue(KeyType::TextString(String::from("foo"))), + ), + ( + KeyType::Unsigned(6), + Value::KeyValue(KeyType::ByteString(b"bar".to_vec())), + ), + (KeyType::Unsigned(7), Value::Array(Vec::new())), + ( + KeyType::Unsigned(8), + Value::Array(vec![ + Value::KeyValue(KeyType::Unsigned(0)), + Value::KeyValue(KeyType::Unsigned(1)), + ]), + ), + (KeyType::Unsigned(9), Value::Map(BTreeMap::new())), + ( + KeyType::Unsigned(10), + Value::Map( + [(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))] + .iter() + .cloned() + .collect(), + ), + ), + ] + .iter() + .cloned() + .collect(), + ); + assert_eq!(a, b); + } + + #[test] + fn test_cbor_map_options() { + let a = cbor_map_options! { + -1 => -23, + 4 => Some(56), + 11 => None::, + "foo" => true, + 12 => None::<&str>, + b"bar" => Some(cbor_null!()), + 13 => None::>, + 5 => "foo", + 14 => None::<&[u8]>, + 6 => Some(b"bar" as &[u8]), + 15 => None::, + 7 => cbor_array![], + 16 => None::, + 8 => Some(cbor_array![0, 1]), + 17 => None::, + 9 => cbor_map!{}, + 18 => None::, + 10 => Some(cbor_map!{2 => 3}), + }; + let b = Value::Map( + [ + ( + KeyType::Negative(-1), + Value::KeyValue(KeyType::Negative(-23)), + ), + (KeyType::Unsigned(4), Value::KeyValue(KeyType::Unsigned(56))), + ( + KeyType::TextString(String::from("foo")), + Value::Simple(SimpleValue::TrueValue), + ), + ( + KeyType::ByteString(b"bar".to_vec()), + Value::Simple(SimpleValue::NullValue), + ), + ( + KeyType::Unsigned(5), + Value::KeyValue(KeyType::TextString(String::from("foo"))), + ), + ( + KeyType::Unsigned(6), + Value::KeyValue(KeyType::ByteString(b"bar".to_vec())), + ), + (KeyType::Unsigned(7), Value::Array(Vec::new())), + ( + KeyType::Unsigned(8), + Value::Array(vec![ + Value::KeyValue(KeyType::Unsigned(0)), + Value::KeyValue(KeyType::Unsigned(1)), + ]), + ), + (KeyType::Unsigned(9), Value::Map(BTreeMap::new())), + ( + KeyType::Unsigned(10), + Value::Map( + [(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))] + .iter() + .cloned() + .collect(), + ), + ), + ] + .iter() + .cloned() + .collect(), + ); + assert_eq!(a, b); + } + + #[test] + fn test_cbor_map_btree_empty() { + let a = cbor_map_btree!(BTreeMap::new()); + let b = Value::Map(BTreeMap::new()); + assert_eq!(a, b); + } + + #[test] + fn test_cbor_map_btree_foo() { + let a = cbor_map_btree!( + [(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))] + .iter() + .cloned() + .collect() + ); + let b = Value::Map( + [(KeyType::Unsigned(2), Value::KeyValue(KeyType::Unsigned(3)))] + .iter() + .cloned() + .collect(), + ); + assert_eq!(a, b); + } +} diff --git a/libraries/cbor/src/reader.rs b/libraries/cbor/src/reader.rs new file mode 100644 index 0000000..0ba6a6c --- /dev/null +++ b/libraries/cbor/src/reader.rs @@ -0,0 +1,811 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::values::{Constants, KeyType, SimpleValue, Value}; +use alloc::collections::BTreeMap; +use alloc::str; +use alloc::vec::Vec; + +#[derive(Debug, PartialEq)] +pub enum DecoderError { + UnsupportedMajorType, + UnknownAdditionalInfo, + IncompleteCborData, + IncorrectMapKeyType, + TooMuchNesting, + InvalidUtf8, + ExtranousData, + OutOfOrderKey, + NonMinimalCborEncoding, + UnsupportedSimpleValue, + UnsupportedFloatingPointValue, + OutOfRangeIntegerValue, +} + +pub fn read(encoded_cbor: &[u8]) -> Result { + let mut reader = Reader::new(encoded_cbor); + let value = reader.decode_complete_data_item(Reader::MAX_NESTING_DEPTH)?; + if !reader.remaining_cbor.is_empty() { + return Err(DecoderError::ExtranousData); + } + Ok(value) +} + +struct Reader<'a> { + remaining_cbor: &'a [u8], +} + +impl<'a> Reader<'a> { + const MAX_NESTING_DEPTH: i8 = 4; + + pub fn new(cbor: &'a [u8]) -> Reader<'a> { + Reader { + remaining_cbor: cbor, + } + } + + pub fn decode_complete_data_item( + &mut self, + remaining_depth: i8, + ) -> Result { + if remaining_depth < 0 { + return Err(DecoderError::TooMuchNesting); + } + + match self.read_bytes(1) { + Some([first_byte]) => { + // Unsigned byte means logical shift, so only zeros get shifted in. + let major_type_value = first_byte >> Constants::MAJOR_TYPE_BIT_SHIFT; + let additional_info = first_byte & Constants::ADDITIONAL_INFORMATION_MASK; + let size_result = self.read_variadic_length_integer(additional_info); + match size_result { + Ok(size_value) => match major_type_value { + 0 => self.decode_value_to_unsigned(size_value), + 1 => self.decode_value_to_negative(size_value), + 2 => self.read_byte_string_content(size_value), + 3 => self.read_text_string_content(size_value), + 4 => self.read_array_content(size_value, remaining_depth), + 5 => self.read_map_content(size_value, remaining_depth), + 7 => self.decode_to_simple_value(size_value, additional_info), + _ => Err(DecoderError::UnsupportedMajorType), + }, + Err(decode_error) => Err(decode_error), + } + } + _ => Err(DecoderError::IncompleteCborData), + } + } + + fn read_bytes(&mut self, num_bytes: usize) -> Option<&[u8]> { + if num_bytes > self.remaining_cbor.len() { + None + } else { + let (left, right) = self.remaining_cbor.split_at(num_bytes); + self.remaining_cbor = right; + Some(left) + } + } + + fn read_variadic_length_integer(&mut self, additional_info: u8) -> Result { + let additional_bytes_num = match additional_info { + 0..=Constants::ADDITIONAL_INFORMATION_MAX_INT => return Ok(additional_info as u64), + Constants::ADDITIONAL_INFORMATION_1_BYTE => 1, + Constants::ADDITIONAL_INFORMATION_2_BYTES => 2, + Constants::ADDITIONAL_INFORMATION_4_BYTES => 4, + Constants::ADDITIONAL_INFORMATION_8_BYTES => 8, + _ => return Err(DecoderError::UnknownAdditionalInfo), + }; + match self.read_bytes(additional_bytes_num) { + Some(bytes) => { + let mut size_value = 0u64; + for byte in bytes { + size_value <<= 8; + size_value += *byte as u64; + } + if (additional_bytes_num == 1 && size_value < 24) + || size_value < (1u64 << (8 * (additional_bytes_num >> 1))) + { + Err(DecoderError::NonMinimalCborEncoding) + } else { + Ok(size_value) + } + } + None => Err(DecoderError::IncompleteCborData), + } + } + + fn decode_value_to_unsigned(&self, size_value: u64) -> Result { + Ok(cbor_unsigned!(size_value)) + } + + fn decode_value_to_negative(&self, size_value: u64) -> Result { + let signed_size = size_value as i64; + if signed_size < 0 { + Err(DecoderError::OutOfRangeIntegerValue) + } else { + Ok(Value::KeyValue(KeyType::Negative(-(size_value as i64) - 1))) + } + } + + fn read_byte_string_content(&mut self, size_value: u64) -> Result { + match self.read_bytes(size_value as usize) { + Some(bytes) => Ok(cbor_bytes_lit!(bytes)), + None => Err(DecoderError::IncompleteCborData), + } + } + + fn read_text_string_content(&mut self, size_value: u64) -> Result { + match self.read_bytes(size_value as usize) { + Some(bytes) => match str::from_utf8(bytes) { + Ok(s) => Ok(cbor_text!(s)), + Err(_) => Err(DecoderError::InvalidUtf8), + }, + None => Err(DecoderError::IncompleteCborData), + } + } + + fn read_array_content( + &mut self, + size_value: u64, + remaining_depth: i8, + ) -> Result { + // Don't set the capacity already, it is an unsanitized input. + let mut value_array = Vec::new(); + for _ in 0..size_value { + value_array.push(self.decode_complete_data_item(remaining_depth - 1)?); + } + Ok(cbor_array_vec!(value_array)) + } + + fn read_map_content( + &mut self, + size_value: u64, + remaining_depth: i8, + ) -> Result { + let mut value_map = BTreeMap::new(); + let mut last_key_option = None; + for _ in 0..size_value { + let key_value = self.decode_complete_data_item(remaining_depth - 1)?; + if let Value::KeyValue(key) = key_value { + if let Some(last_key) = last_key_option { + if last_key >= key { + return Err(DecoderError::OutOfOrderKey); + } + } + last_key_option = Some(key.clone()); + value_map.insert(key, self.decode_complete_data_item(remaining_depth - 1)?); + } else { + return Err(DecoderError::IncorrectMapKeyType); + } + } + Ok(cbor_map_btree!(value_map)) + } + + fn decode_to_simple_value( + &self, + size_value: u64, + additional_info: u8, + ) -> Result { + if additional_info > Constants::ADDITIONAL_INFORMATION_MAX_INT + && additional_info != Constants::ADDITIONAL_INFORMATION_1_BYTE + { + // TODO(kaczmarczyck) the chromium C++ reference allows equality to 24 here, why? + // Also, why not just disallow ANY additional_info != size_value? + return Err(DecoderError::UnsupportedFloatingPointValue); + } + match SimpleValue::from_integer(size_value) { + Some(simple_value) => Ok(Value::Simple(simple_value)), + None => Err(DecoderError::UnsupportedSimpleValue), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_read_unsigned() { + let cases = vec![ + (0, vec![0x00]), + (1, vec![0x01]), + (10, vec![0x0A]), + (23, vec![0x17]), + (24, vec![0x18, 0x18]), + (255, vec![0x18, 0xFF]), + (256, vec![0x19, 0x01, 0x00]), + (65535, vec![0x19, 0xFF, 0xFF]), + (65536, vec![0x1A, 0x00, 0x01, 0x00, 0x00]), + (0xFFFFFFFF, vec![0x1A, 0xFF, 0xFF, 0xFF, 0xFF]), + ( + 0x100000000, + vec![0x1B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], + ), + ( + std::i64::MAX, + vec![0x1B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + ), + ]; + for (unsigned, mut cbor) in cases { + assert_eq!(read(&cbor), Ok(cbor_int!(unsigned))); + cbor.push(0x01); + assert_eq!(read(&cbor), Err(DecoderError::ExtranousData)); + } + } + + #[test] + fn test_read_unsigned_non_minimum_byte_length() { + let encodings = vec![ + // Uint 23 encoded with 1 byte. + vec![0x18, 0x17], + // Uint 255 encoded with 2 bytes. + vec![0x19, 0x00, 0xff], + // Uint 65535 encoded with 4 bytes. + vec![0x1a, 0x00, 0x00, 0xff, 0xff], + // Uint 4294967295 encoded with 8 bytes. + vec![0x1b, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff], + // When decoding byte has more than one syntax error, the first syntax + // error encountered during deserialization is returned as the error code. + vec![ + 0xa2, // map with non-minimally encoded key + 0x17, // key 24 + 0x61, 0x42, // value :"B" + 0x18, 0x17, // key 23 encoded with extra byte + 0x61, 0x45, // value "E" + ], + vec![ + 0xa2, // map with out of order and non-minimally encoded key + 0x18, 0x17, // key 23 encoded with extra byte + 0x61, 0x45, // value "E" + 0x17, // key 23 + 0x61, 0x42, // value :"B" + ], + vec![ + 0xa2, // map with duplicate non-minimally encoded key + 0x18, 0x17, // key 23 encoded with extra byte + 0x61, 0x45, // value "E" + 0x18, 0x17, // key 23 encoded with extra byte + 0x61, 0x42, // value :"B" + ], + ]; + for encoding in encodings { + assert_eq!(read(&encoding), Err(DecoderError::NonMinimalCborEncoding)); + } + } + + #[test] + fn test_read_negative() { + let cases = vec![ + (-1, vec![0x20]), + (-24, vec![0x37]), + (-25, vec![0x38, 0x18]), + (-256, vec![0x38, 0xFF]), + (-1000, vec![0x39, 0x03, 0xE7]), + (-1000000, vec![0x3A, 0x00, 0x0F, 0x42, 0x3F]), + (-4294967296, vec![0x3A, 0xFF, 0xFF, 0xFF, 0xFF]), + ( + std::i64::MIN, + vec![0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + ), + ]; + for (negative, mut cbor) in cases { + assert_eq!(read(&cbor), Ok(cbor_int!(negative))); + cbor.push(0x01); + assert_eq!(read(&cbor), Err(DecoderError::ExtranousData)); + } + } + + #[test] + fn test_read_byte_string() { + let cases = vec![ + (Vec::new(), vec![0x40]), + ( + vec![0x01, 0x02, 0x03, 0x04], + vec![0x44, 0x01, 0x02, 0x03, 0x04], + ), + ]; + for (byte_string, mut cbor) in cases { + assert_eq!(read(&cbor), Ok(cbor_bytes!(byte_string))); + cbor.push(0x01); + assert_eq!(read(&cbor), Err(DecoderError::ExtranousData)); + } + } + + #[test] + fn test_read_text_string() { + let unicode_3byte = vec![0xE6, 0xB0, 0xB4]; + let cases = vec![ + ("", vec![0x60]), + ("a", vec![0x61, 0x61]), + ("IETF", vec![0x64, 0x49, 0x45, 0x54, 0x46]), + ("\"\\", vec![0x62, 0x22, 0x5C]), + ("ü", vec![0x62, 0xC3, 0xBC]), + ( + std::str::from_utf8(&unicode_3byte).unwrap(), + vec![0x63, 0xE6, 0xB0, 0xB4], + ), + ("𐅑", vec![0x64, 0xF0, 0x90, 0x85, 0x91]), + ]; + for (text_string, mut cbor) in cases { + assert_eq!(read(&cbor), Ok(cbor_text!(text_string))); + cbor.push(0x01); + assert_eq!(read(&cbor), Err(DecoderError::ExtranousData)); + } + } + + #[test] + fn test_read_text_string_with_nul() { + let cases = vec![ + ( + "string_without_nul", + vec![ + 0x72, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x5F, 0x77, 0x69, 0x74, 0x68, 0x6F, + 0x75, 0x74, 0x5F, 0x6E, 0x75, 0x6C, + ], + ), + ( + "nul_terminated_string\0", + vec![ + 0x76, 0x6E, 0x75, 0x6C, 0x5F, 0x74, 0x65, 0x72, 0x6D, 0x69, 0x6E, 0x61, 0x74, + 0x65, 0x64, 0x5F, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x00, + ], + ), + ( + "embedded\0nul", + vec![ + 0x6C, 0x65, 0x6D, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x00, 0x6E, 0x75, 0x6C, + ], + ), + ( + "trailing_nuls\0\0", + vec![ + 0x6F, 0x74, 0x72, 0x61, 0x69, 0x6C, 0x69, 0x6E, 0x67, 0x5F, 0x6E, 0x75, 0x6C, + 0x73, 0x00, 0x00, + ], + ), + ]; + for (text_string, mut cbor) in cases { + assert_eq!(read(&cbor), Ok(cbor_text!(text_string))); + cbor.push(0x01); + assert_eq!(read(&cbor), Err(DecoderError::ExtranousData)); + } + } + + #[test] + fn test_read_text_string_with_invalid_byte_sequence_after_nul() { + assert_eq!( + read(&vec![0x63, 0x00, 0x00, 0xA6]), + Err(DecoderError::InvalidUtf8) + ); + } + + #[test] + fn test_read_array() { + let value_vec: Vec<_> = (1..26).collect(); + let mut test_cbor = vec![ + 0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, + 0x19, + ]; + assert_eq!(read(&test_cbor.clone()), Ok(cbor_array_vec!(value_vec))); + test_cbor.push(0x01); + assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData)); + } + + #[test] + fn test_read_map() { + let value_map = cbor_map! { + 24 => "abc", + "" => ".", + "b" => "B", + "aa" => "AA", + }; + let mut test_cbor = vec![ + 0xa4, // map with 4 key value pairs: + 0x18, 0x18, // 24 + 0x63, 0x61, 0x62, 0x63, // "abc" + 0x60, // "" + 0x61, 0x2e, // "." + 0x61, 0x62, // "b" + 0x61, 0x42, // "B" + 0x62, 0x61, 0x61, // "aa" + 0x62, 0x41, 0x41, // "AA" + ]; + assert_eq!(read(&test_cbor), Ok(value_map)); + test_cbor.push(0x01); + assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData)); + } + + #[test] + fn test_read_map_with_unsigned_keys() { + let value_map = cbor_map! { + 1 => "a", + 9 => "b", + 999 => "c", + 1111 => "d", + }; + let mut test_cbor = vec![ + 0xa4, // map with 4 key value pairs: + 0x01, // key : 1 + 0x61, 0x61, // value : "a" + 0x09, // key : 9 + 0x61, 0x62, // value : "b" + 0x19, 0x03, 0xE7, // key : 999 + 0x61, 0x63, // value "c" + 0x19, 0x04, 0x57, // key : 1111 + 0x61, 0x64, // value : "d" + ]; + assert_eq!(read(&test_cbor), Ok(value_map)); + test_cbor.push(0x01); + assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData)); + } + + #[test] + fn test_read_map_with_negative_keys() { + let value_map = cbor_map! { + -1 => 1, + -2 => 2, + -100 => 3, + }; + let mut test_cbor = vec![ + 0xA3, // map with 3 key value pairs + 0x20, // key : -1 + 0x01, // value : 1 + 0x21, // key : -2 + 0x02, // value : 2 + 0x38, 0x63, // key : -100 + 0x03, // value : 3 + ]; + assert_eq!(read(&test_cbor), Ok(value_map)); + test_cbor.push(0x01); + assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData)); + } + + #[test] + fn test_read_map_with_array() { + let value_map = cbor_map! { + "a" => 1, + "b" => cbor_array![2, 3], + }; + let mut test_cbor = vec![ + 0xa2, // map of 2 pairs + 0x61, 0x61, // "a" + 0x01, 0x61, 0x62, // "b" + 0x82, // array with 2 elements + 0x02, 0x03, + ]; + assert_eq!(read(&test_cbor), Ok(value_map)); + test_cbor.push(0x01); + assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData)); + } + + #[test] + fn test_read_map_with_text_string_keys() { + let value_map = cbor_map! { + "k" => "v", + "foo" => "bar", + }; + let mut test_cbor = vec![ + 0xa2, // map of 2 pairs + 0x61, b'k', // text string "k" + 0x61, b'v', 0x63, b'f', b'o', b'o', // text string "foo" + 0x63, b'b', b'a', b'r', + ]; + assert_eq!(read(&test_cbor), Ok(value_map)); + test_cbor.push(0x01); + assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData)); + } + + #[test] + fn test_read_map_with_byte_string_keys() { + let value_map = cbor_map! { + b"k" => b"v", + b"foo" => b"bar", + }; + let mut test_cbor = vec![ + 0xa2, // map of 2 pairs + 0x41, b'k', // text string "k" + 0x41, b'v', 0x43, b'f', b'o', b'o', // text string "foo" + 0x43, b'b', b'a', b'r', + ]; + assert_eq!(read(&test_cbor), Ok(value_map)); + test_cbor.push(0x01); + assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData)); + } + + #[test] + fn test_read_nested_map() { + let value_map = cbor_map! { + "a" => 1, + "b" => cbor_map! { + "c" => 2, + "d" => 3, + }, + }; + let mut test_cbor = vec![ + 0xa2, // map of 2 pairs + 0x61, 0x61, 0x01, // "a" + 0x61, 0x62, // "b" + 0xa2, // map of 2 pairs + 0x61, 0x63, 0x02, // "c" + 0x61, 0x64, 0x03, // "d" + ]; + assert_eq!(read(&test_cbor), Ok(value_map)); + test_cbor.push(0x01); + assert_eq!(read(&test_cbor), Err(DecoderError::ExtranousData)); + } + + #[test] + fn test_read_integer_out_of_range() { + let cases = vec![ + // The positive case is impossible since we support u64. + // vec![0x1B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + vec![0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + ]; + for cbor in cases { + assert_eq!(read(&cbor), Err(DecoderError::OutOfRangeIntegerValue)); + } + } + + #[test] + fn test_read_simple_value() { + let cases = vec![ + (cbor_false!(), vec![0xF4]), + (cbor_true!(), vec![0xF5]), + (cbor_null!(), vec![0xF6]), + (cbor_undefined!(), vec![0xF7]), + ]; + for (simple, mut cbor) in cases { + assert_eq!(read(&cbor.clone()), Ok(simple)); + cbor.push(0x01); + assert_eq!(read(&cbor), Err(DecoderError::ExtranousData)); + } + } + + #[test] + fn test_read_unsupported_floating_point_numbers() { + let cases = vec![ + vec![0xF9, 0x10, 0x00], + vec![0xFA, 0x10, 0x00, 0x00, 0x00], + vec![0xFB, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + ]; + for cbor in cases { + assert_eq!( + read(&cbor), + Err(DecoderError::UnsupportedFloatingPointValue) + ); + } + } + + #[test] + fn test_read_incomplete_cbor_data_error() { + let cases = vec![ + vec![0x19, 0x03], + vec![0x44, 0x01, 0x02, 0x03], + vec![0x65, 0x49, 0x45, 0x54, 0x46], + vec![0x82, 0x02], + vec![0xA2, 0x61, 0x61, 0x01], + vec![0x18], + vec![0x99], + vec![0xBA], + vec![0x5B], + vec![0x3B], + vec![0x99, 0x01], + vec![0xBA, 0x01, 0x02, 0x03], + vec![0x3B, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], + ]; + for cbor in cases { + assert_eq!(read(&cbor), Err(DecoderError::IncompleteCborData)); + } + } + + #[test] + fn test_read_unsupported_map_key_format_error() { + // While CBOR can handle all types as map keys, we only support a subset. + let bad_map_cbor = vec![ + 0xa2, // map of 2 pairs + 0x82, 0x01, 0x02, // invalid key : [1, 2] + 0x02, // value : 2 + 0x61, 0x64, // key : "d" + 0x03, // value : 3 + ]; + assert_eq!(read(&bad_map_cbor), Err(DecoderError::IncorrectMapKeyType)); + } + + #[test] + fn test_read_unknown_additional_info_error() { + let cases = vec![ + vec![0x7C, 0x49, 0x45, 0x54, 0x46], + vec![0x7D, 0x22, 0x5C], + vec![0x7E, 0xC3, 0xBC], + vec![0x7F, 0xE6, 0xB0, 0xB4], + vec![0xFC], + vec![0xFD], + vec![0xFE], + vec![0xFF], + ]; + for cbor in cases { + assert_eq!(read(&cbor), Err(DecoderError::UnknownAdditionalInfo)); + } + } + + #[test] + fn test_read_too_much_nesting_error() { + let cases = vec![ + vec![0x18, 0x64], + vec![0x44, 0x01, 0x02, 0x03, 0x04], + vec![0x64, 0x49, 0x45, 0x54, 0x46], + vec![0x80], + vec![0xA0], + ]; + for cbor in cases { + let mut reader = Reader::new(&cbor); + assert!(reader.decode_complete_data_item(0).is_ok()); + } + let map_cbor = vec![ + 0xa2, // map of 2 pairs + 0x61, 0x61, // "a" + 0x01, 0x61, 0x62, // "b" + 0x82, // array with 2 elements + 0x02, 0x03, + ]; + let mut reader = Reader::new(&map_cbor); + assert_eq!( + reader.decode_complete_data_item(1), + Err(DecoderError::TooMuchNesting) + ); + reader = Reader::new(&map_cbor); + assert!(reader.decode_complete_data_item(2).is_ok()); + } + + #[test] + fn test_read_out_of_order_key_error() { + let cases = vec![ + vec![ + 0xa2, // map with 2 keys with same major type and length + 0x61, 0x62, // key "b" + 0x61, 0x42, // value "B" + 0x61, 0x61, // key "a" (out of order byte-wise lexically) + 0x61, 0x45, // value "E" + ], + vec![ + 0xa2, // map with 2 keys with different major type + 0x61, 0x62, // key "b" + 0x02, // value 2 + // key 1000 (out of order since lower major type sorts first) + 0x19, 0x03, 0xe8, 0x61, 0x61, // value a + ], + vec![ + 0xa2, // map with 2 keys with same major type + 0x19, 0x03, 0xe8, // key 1000 (out of order due to longer length) + 0x61, 0x61, //value "a" + 0x0a, // key 10 + 0x61, 0x62, // value "b" + ], + vec![ + 0xa2, // map with 2 text string keys + 0x62, b'a', b'a', // key text string "aa" + // (out of order due to longer length) + 0x02, 0x61, b'b', // key "b" + 0x01, + ], + vec![ + 0xa2, // map with 2 byte string keys + 0x42, b'x', b'x', // key byte string "xx" + // (out of order due to longer length) + 0x02, 0x41, b'y', // key byte string "y" + 0x01, + ], + ]; + for cbor in cases { + assert_eq!(read(&cbor), Err(DecoderError::OutOfOrderKey)); + } + } + + #[test] + fn test_read_duplicate_key_error() { + let map_with_duplicate_key = vec![ + 0xa6, // map of 6 pairs: + 0x60, // "" + 0x61, 0x2e, // "." + 0x61, 0x62, // "b" + 0x61, 0x42, // "B" + 0x61, 0x62, // "b" (Duplicate key) + 0x61, 0x43, // "C" + 0x61, 0x64, // "d" + 0x61, 0x44, // "D" + 0x61, 0x65, // "e" + 0x61, 0x44, // "D" + 0x62, 0x61, 0x61, // "aa" + 0x62, 0x41, 0x41, // "AA" + ]; + assert_eq!( + read(&map_with_duplicate_key), + Err(DecoderError::OutOfOrderKey) + ); + } + + #[test] + fn test_read_incorrect_string_encoding_error() { + let cases = vec![ + vec![0x63, 0xED, 0x9F, 0xBF], + vec![0x63, 0xEE, 0x80, 0x80], + vec![0x63, 0xEF, 0xBF, 0xBD], + ]; + for cbor in cases { + assert!(read(&cbor).is_ok()); + } + let impossible_utf_byte = vec![0x64, 0xFE, 0xFE, 0xFF, 0xFF]; + assert_eq!(read(&impossible_utf_byte), Err(DecoderError::InvalidUtf8)); + } + + #[test] + fn test_read_extranous_cbor_data_error() { + let cases = vec![ + vec![0x19, 0x03, 0x05, 0x00], + vec![0x44, 0x01, 0x02, 0x03, 0x04, 0x00], + vec![0x64, 0x49, 0x45, 0x54, 0x46, 0x00], + vec![0x82, 0x01, 0x02, 0x00], + vec![0xa1, 0x61, 0x63, 0x02, 0x61, 0x64, 0x03], + ]; + for cbor in cases { + assert_eq!(read(&cbor), Err(DecoderError::ExtranousData)); + } + } + + #[test] + fn test_read_unsupported_simple_type() { + let cases = vec![ + vec![0xE0], + vec![0xF3], + vec![0xF8, 0x18], + vec![0xF8, 0x1C], + vec![0xF8, 0x1D], + vec![0xF8, 0x1E], + vec![0xF8, 0x1F], + vec![0xF8, 0x20], + vec![0xF8, 0xFF], + ]; + for cbor in cases { + assert_eq!(read(&cbor), Err(DecoderError::UnsupportedSimpleValue)); + } + } + + #[test] + fn test_read_super_long_content_dont_crash() { + let cases = vec![ + vec![0x9B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + vec![0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + ]; + for cbor in cases { + assert_eq!(read(&cbor), Err(DecoderError::IncompleteCborData)); + } + } + + #[test] + fn test_read_unsupported_major_type() { + let cases = vec![ + vec![0xC0], + vec![0xD8, 0xFF], + // multi-dimensional array example using tags + vec![ + 0x82, 0x82, 0x02, 0x03, 0xd8, 0x41, 0x4a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x05, + ], + ]; + for cbor in cases { + assert_eq!(read(&cbor), Err(DecoderError::UnsupportedMajorType)); + } + } +} diff --git a/libraries/cbor/src/values.rs b/libraries/cbor/src/values.rs new file mode 100644 index 0000000..d402730 --- /dev/null +++ b/libraries/cbor/src/values.rs @@ -0,0 +1,268 @@ +// Copyright 2019 Google LLC +// +// 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. + +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::cmp::Ordering; + +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + KeyValue(KeyType), + Array(Vec), + Map(BTreeMap), + // TAG is omitted + Simple(SimpleValue), +} + +// The specification recommends to limit the available keys. +// Currently supported are both integer and string types. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum KeyType { + Unsigned(u64), + // We only use 63 bits of information here. + Negative(i64), + ByteString(Vec), + TextString(String), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum SimpleValue { + FalseValue = 20, + TrueValue = 21, + NullValue = 22, + Undefined = 23, +} + +pub struct Constants {} + +impl Constants { + pub const MAJOR_TYPE_BIT_SHIFT: u8 = 5; + pub const ADDITIONAL_INFORMATION_MASK: u8 = 0x1F; + pub const ADDITIONAL_INFORMATION_MAX_INT: u8 = 23; + pub const ADDITIONAL_INFORMATION_1_BYTE: u8 = 24; + pub const ADDITIONAL_INFORMATION_2_BYTES: u8 = 25; + pub const ADDITIONAL_INFORMATION_4_BYTES: u8 = 26; + pub const ADDITIONAL_INFORMATION_8_BYTES: u8 = 27; +} + +impl Value { + pub fn bool_value(b: bool) -> Value { + if b { + Value::Simple(SimpleValue::TrueValue) + } else { + Value::Simple(SimpleValue::FalseValue) + } + } + + pub fn type_label(&self) -> u8 { + match self { + Value::KeyValue(key) => key.type_label(), + Value::Array(_) => 4, + Value::Map(_) => 5, + Value::Simple(_) => 7, + } + } +} + +impl KeyType { + // For simplicity, this only takes i64. Construct directly for the last bit. + pub fn integer(int: i64) -> KeyType { + if int >= 0 { + KeyType::Unsigned(int as u64) + } else { + KeyType::Negative(int) + } + } + + pub fn type_label(&self) -> u8 { + match self { + KeyType::Unsigned(_) => 0, + KeyType::Negative(_) => 1, + KeyType::ByteString(_) => 2, + KeyType::TextString(_) => 3, + } + } +} + +impl Ord for KeyType { + fn cmp(&self, other: &KeyType) -> Ordering { + use super::values::KeyType::{ByteString, Negative, TextString, Unsigned}; + let self_type_value = self.type_label(); + let other_type_value = other.type_label(); + if self_type_value != other_type_value { + return self_type_value.cmp(&other_type_value); + } + match (self, other) { + (Unsigned(u1), Unsigned(u2)) => u1.cmp(u2), + (Negative(n1), Negative(n2)) => n1.cmp(n2).reverse(), + (ByteString(b1), ByteString(b2)) => b1.len().cmp(&b2.len()).then(b1.cmp(b2)), + (TextString(t1), TextString(t2)) => t1.len().cmp(&t2.len()).then(t1.cmp(t2)), + _ => unreachable!(), + } + } +} + +impl PartialOrd for KeyType { + fn partial_cmp(&self, other: &KeyType) -> Option { + Some(self.cmp(other)) + } +} + +impl SimpleValue { + pub fn from_integer(int: u64) -> Option { + match int { + 20 => Some(SimpleValue::FalseValue), + 21 => Some(SimpleValue::TrueValue), + 22 => Some(SimpleValue::NullValue), + 23 => Some(SimpleValue::Undefined), + _ => None, + } + } +} + +impl From for KeyType { + fn from(unsigned: u64) -> Self { + KeyType::Unsigned(unsigned) + } +} + +impl From for KeyType { + fn from(i: i64) -> Self { + KeyType::integer(i) + } +} + +impl From for KeyType { + fn from(i: i32) -> Self { + KeyType::integer(i as i64) + } +} + +impl From> for KeyType { + fn from(bytes: Vec) -> Self { + KeyType::ByteString(bytes) + } +} + +impl From<&[u8]> for KeyType { + fn from(bytes: &[u8]) -> Self { + KeyType::ByteString(bytes.to_vec()) + } +} + +impl From for KeyType { + fn from(text: String) -> Self { + KeyType::TextString(text) + } +} + +impl From<&str> for KeyType { + fn from(text: &str) -> Self { + KeyType::TextString(text.to_string()) + } +} + +impl From for Value +where + KeyType: From, +{ + fn from(t: T) -> Self { + Value::KeyValue(KeyType::from(t)) + } +} + +impl From for Value { + fn from(b: bool) -> Self { + Value::bool_value(b) + } +} + +pub trait IntoCborKey { + fn into_cbor_key(self) -> KeyType; +} + +impl IntoCborKey for T +where + KeyType: From, +{ + fn into_cbor_key(self) -> KeyType { + KeyType::from(self) + } +} + +pub trait IntoCborValue { + fn into_cbor_value(self) -> Value; +} + +impl IntoCborValue for T +where + Value: From, +{ + fn into_cbor_value(self) -> Value { + Value::from(self) + } +} + +pub trait IntoCborValueOption { + fn into_cbor_value_option(self) -> Option; +} + +impl IntoCborValueOption for T +where + Value: From, +{ + fn into_cbor_value_option(self) -> Option { + Some(Value::from(self)) + } +} + +impl IntoCborValueOption for Option +where + Value: From, +{ + fn into_cbor_value_option(self) -> Option { + self.map(Value::from) + } +} + +#[cfg(test)] +mod test { + #[test] + fn test_key_type_ordering() { + assert!(cbor_key_int!(0) < cbor_key_int!(23)); + assert!(cbor_key_int!(23) < cbor_key_int!(24)); + assert!(cbor_key_int!(24) < cbor_key_int!(1000)); + assert!(cbor_key_int!(1000) < cbor_key_int!(1000000)); + assert!(cbor_key_int!(1000000) < cbor_key_int!(std::i64::MAX)); + assert!(cbor_key_int!(std::i64::MAX) < cbor_key_int!(-1)); + assert!(cbor_key_int!(-1) < cbor_key_int!(-23)); + assert!(cbor_key_int!(-23) < cbor_key_int!(-24)); + assert!(cbor_key_int!(-24) < cbor_key_int!(-1000)); + assert!(cbor_key_int!(-1000) < cbor_key_int!(-1000000)); + assert!(cbor_key_int!(-1000000) < cbor_key_int!(std::i64::MIN)); + assert!(cbor_key_int!(std::i64::MIN) < cbor_key_bytes!(vec![])); + assert!(cbor_key_bytes!(vec![]) < cbor_key_bytes!(vec![0x00])); + assert!(cbor_key_bytes!(vec![0x00]) < cbor_key_bytes!(vec![0x01])); + assert!(cbor_key_bytes!(vec![0x01]) < cbor_key_bytes!(vec![0xFF])); + assert!(cbor_key_bytes!(vec![0xFF]) < cbor_key_bytes!(vec![0x00, 0x00])); + assert!(cbor_key_bytes!(vec![0x00, 0x00]) < cbor_key_text!("")); + assert!(cbor_key_text!("") < cbor_key_text!("a")); + assert!(cbor_key_text!("a") < cbor_key_text!("b")); + assert!(cbor_key_text!("b") < cbor_key_text!("aa")); + assert!(cbor_key_int!(1) < cbor_key_bytes!(vec![0x00])); + assert!(cbor_key_int!(1) < cbor_key_text!("s")); + assert!(cbor_key_int!(-1) < cbor_key_text!("s")); + } +} diff --git a/libraries/cbor/src/writer.rs b/libraries/cbor/src/writer.rs new file mode 100644 index 0000000..1273160 --- /dev/null +++ b/libraries/cbor/src/writer.rs @@ -0,0 +1,429 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::values::{Constants, KeyType, Value}; +use alloc::vec::Vec; + +pub fn write(value: Value, encoded_cbor: &mut Vec) -> bool { + let mut writer = Writer::new(encoded_cbor); + writer.encode_cbor(value, Writer::MAX_NESTING_DEPTH) +} + +struct Writer<'a> { + encoded_cbor: &'a mut Vec, +} + +impl<'a> Writer<'a> { + const MAX_NESTING_DEPTH: i8 = 4; + + pub fn new(encoded_cbor: &mut Vec) -> Writer { + Writer { encoded_cbor } + } + + fn encode_cbor(&mut self, value: Value, remaining_depth: i8) -> bool { + if remaining_depth < 0 { + return false; + } + match value { + Value::KeyValue(KeyType::Unsigned(unsigned)) => self.start_item(0, unsigned as u64), + Value::KeyValue(KeyType::Negative(negative)) => { + self.start_item(1, -(negative + 1) as u64) + } + Value::KeyValue(KeyType::ByteString(byte_string)) => { + self.start_item(2, byte_string.len() as u64); + self.encoded_cbor.extend(byte_string); + } + Value::KeyValue(KeyType::TextString(text_string)) => { + self.start_item(3, text_string.len() as u64); + self.encoded_cbor.extend(text_string.into_bytes()); + } + Value::Array(array) => { + self.start_item(4, array.len() as u64); + for el in array { + if !self.encode_cbor(el, remaining_depth - 1) { + return false; + } + } + } + Value::Map(map) => { + self.start_item(5, map.len() as u64); + for (k, v) in map { + if !self.encode_cbor(Value::KeyValue(k), remaining_depth - 1) { + return false; + } + if !self.encode_cbor(v, remaining_depth - 1) { + return false; + } + } + } + Value::Simple(simple_value) => self.start_item(7, simple_value as u64), + } + true + } + + fn start_item(&mut self, type_label: u8, size: u64) { + let (mut first_byte, shift) = match size { + 0..=23 => (size as u8, 0), + 24..=0xFF => (Constants::ADDITIONAL_INFORMATION_1_BYTE, 1), + 0x100..=0xFFFF => (Constants::ADDITIONAL_INFORMATION_2_BYTES, 2), + 0x10000..=0xFFFF_FFFF => (Constants::ADDITIONAL_INFORMATION_4_BYTES, 4), + _ => (Constants::ADDITIONAL_INFORMATION_8_BYTES, 8), + }; + first_byte |= type_label << Constants::MAJOR_TYPE_BIT_SHIFT; + self.encoded_cbor.push(first_byte); + + for i in (0..shift).rev() { + self.encoded_cbor.push((size >> (i * 8)) as u8); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn write_return(value: Value) -> Option> { + let mut encoded_cbor = Vec::new(); + if write(value, &mut encoded_cbor) { + Some(encoded_cbor) + } else { + None + } + } + + #[test] + fn test_write_unsigned() { + let cases = vec![ + (0, vec![0x00]), + (1, vec![0x01]), + (10, vec![0x0A]), + (23, vec![0x17]), + (24, vec![0x18, 0x18]), + (25, vec![0x18, 0x19]), + (100, vec![0x18, 0x64]), + (1000, vec![0x19, 0x03, 0xE8]), + (1000000, vec![0x1A, 0x00, 0x0F, 0x42, 0x40]), + (0xFFFFFFFF, vec![0x1A, 0xFF, 0xFF, 0xFF, 0xFF]), + ( + 0x100000000, + vec![0x1B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], + ), + ( + std::i64::MAX, + vec![0x1B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + ), + ]; + for (unsigned, correct_cbor) in cases { + assert_eq!(write_return(cbor_int!(unsigned)), Some(correct_cbor)); + } + } + + #[test] + fn test_write_negative() { + let cases = vec![ + (-1, vec![0x20]), + (-10, vec![0x29]), + (-23, vec![0x36]), + (-24, vec![0x37]), + (-25, vec![0x38, 0x18]), + (-100, vec![0x38, 0x63]), + (-1000, vec![0x39, 0x03, 0xE7]), + (-4294967296, vec![0x3A, 0xFF, 0xFF, 0xFF, 0xFF]), + ( + -4294967297, + vec![0x3B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00], + ), + ( + std::i64::MIN, + vec![0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + ), + ]; + for (negative, correct_cbor) in cases { + assert_eq!(write_return(cbor_int!(negative)), Some(correct_cbor)); + } + } + + #[test] + fn test_write_byte_string() { + let cases = vec![ + (vec![], vec![0x40]), + ( + vec![0x01, 0x02, 0x03, 0x04], + vec![0x44, 0x01, 0x02, 0x03, 0x04], + ), + ]; + for (byte_string, correct_cbor) in cases { + assert_eq!(write_return(cbor_bytes!(byte_string)), Some(correct_cbor)); + } + } + + #[test] + fn test_write_text_string() { + let unicode_3byte = vec![0xE6, 0xB0, 0xB4]; + let cases = vec![ + ("", vec![0x60]), + ("a", vec![0x61, 0x61]), + ("IETF", vec![0x64, 0x49, 0x45, 0x54, 0x46]), + ("\"\\", vec![0x62, 0x22, 0x5C]), + ("ü", vec![0x62, 0xC3, 0xBC]), + ( + std::str::from_utf8(&unicode_3byte).unwrap(), + vec![0x63, 0xE6, 0xB0, 0xB4], + ), + ("𐅑", vec![0x64, 0xF0, 0x90, 0x85, 0x91]), + ]; + for (text_string, correct_cbor) in cases { + assert_eq!(write_return(cbor_text!(text_string)), Some(correct_cbor)); + } + } + + #[test] + fn test_write_array() { + let value_vec: Vec<_> = (1..26).collect(); + let expected_cbor = vec![ + 0x98, 0x19, // array of 25 elements + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19, + ]; + assert_eq!( + write_return(cbor_array_vec!(value_vec)), + Some(expected_cbor) + ); + } + + #[test] + fn test_write_map() { + let value_map = cbor_map! { + "aa" => "AA", + "e" => "E", + "" => ".", + -1 => "k", + -24 => "l", + -25 => "m", + -256 => "n", + -257 => "o", + -65537 => "p", + -4294967296_i64 => "q", + -4294967297_i64 => "r", + std::i64::MIN => "s", + b"a" => 2, + b"bar" => 3, + b"foo" => 4, + 0 => "a", + 23 => "b", + 24 => "c", + std::u8::MAX as i64 => "d", + 256 => "e", + std::u16::MAX as i64 => "f", + 65536 => "g", + std::u32::MAX as i64 => "h", + 4294967296_i64 => "i", + std::i64::MAX => "j", + }; + let expected_cbor = vec![ + 0xb8, 0x19, // map of 25 pairs: + 0x00, // key 0 + 0x61, 0x61, // value "a" + 0x17, // key 23 + 0x61, 0x62, // value "b" + 0x18, 0x18, // key 24 + 0x61, 0x63, // value "c" + 0x18, 0xFF, // key 255 + 0x61, 0x64, // value "d" + 0x19, 0x01, 0x00, // key 256 + 0x61, 0x65, // value "e" + 0x19, 0xFF, 0xFF, // key 65535 + 0x61, 0x66, // value "f" + 0x1A, 0x00, 0x01, 0x00, 0x00, // key 65536 + 0x61, 0x67, // value "g" + 0x1A, 0xFF, 0xFF, 0xFF, 0xFF, // key 4294967295 + 0x61, 0x68, // value "h" + // key 4294967296 + 0x1B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x69, // value "i" + // key INT64_MAX + 0x1b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x61, 0x6a, // value "j" + 0x20, // key -1 + 0x61, 0x6b, // value "k" + 0x37, // key -24 + 0x61, 0x6c, // value "l" + 0x38, 0x18, // key -25 + 0x61, 0x6d, // value "m" + 0x38, 0xFF, // key -256 + 0x61, 0x6e, // value "n" + 0x39, 0x01, 0x00, // key -257 + 0x61, 0x6f, // value "o" + 0x3A, 0x00, 0x01, 0x00, 0x00, // key -65537 + 0x61, 0x70, // value "p" + 0x3A, 0xFF, 0xFF, 0xFF, 0xFF, // key -4294967296 + 0x61, 0x71, // value "q" + // key -4294967297 + 0x3B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x61, 0x72, // value "r" + // key INT64_MIN + 0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x61, 0x73, // value "s" + 0x41, b'a', // byte string "a" + 0x02, 0x43, b'b', b'a', b'r', // byte string "bar" + 0x03, 0x43, b'f', b'o', b'o', // byte string "foo" + 0x04, 0x60, // key "" + 0x61, 0x2e, // value "." + 0x61, 0x65, // key "e" + 0x61, 0x45, // value "E" + 0x62, 0x61, 0x61, // key "aa" + 0x62, 0x41, 0x41, // value "AA" + ]; + assert_eq!(write_return(value_map), Some(expected_cbor)); + } + + #[test] + fn test_write_map_with_array() { + let value_map = cbor_map! { + "a" => 1, + "b" => cbor_array![2, 3], + }; + let expected_cbor = vec![ + 0xa2, // map of 2 pairs + 0x61, 0x61, // "a" + 0x01, 0x61, 0x62, // "b" + 0x82, // array with 2 elements + 0x02, 0x03, + ]; + assert_eq!(write_return(value_map), Some(expected_cbor)); + } + + #[test] + fn test_write_nested_map() { + let value_map = cbor_map! { + "a" => 1, + "b" => cbor_map! { + "c" => 2, + "d" => 3, + }, + }; + let expected_cbor = vec![ + 0xa2, // map of 2 pairs + 0x61, 0x61, // "a" + 0x01, 0x61, 0x62, // "b" + 0xa2, // map of 2 pairs + 0x61, 0x63, // "c" + 0x02, 0x61, 0x64, // "d" + 0x03, + ]; + assert_eq!(write_return(value_map), Some(expected_cbor)); + } + + #[test] + fn test_write_simple() { + let cases = vec![ + (cbor_false!(), vec![0xF4]), + (cbor_true!(), vec![0xF5]), + (cbor_null!(), vec![0xF6]), + (cbor_undefined!(), vec![0xF7]), + ]; + for (value, correct_cbor) in cases { + assert_eq!(write_return(value), Some(correct_cbor)); + } + } + + #[test] + fn test_write_single_levels() { + let simple_array: Value = cbor_array![2]; + let simple_map: Value = cbor_map! {"b" => 3}; + let positive_cases = vec![ + (cbor_int!(1), 0), + (cbor_bytes!(vec![0x01, 0x02, 0x03, 0x04]), 0), + (cbor_text!("a"), 0), + (cbor_array![], 0), + (cbor_map! {}, 0), + (simple_array.clone(), 1), + (simple_map.clone(), 1), + ]; + let negative_cases = vec![(simple_array.clone(), 0), (simple_map.clone(), 0)]; + for (value, level) in positive_cases { + let mut buf = Vec::new(); + let mut writer = Writer::new(&mut buf); + assert!(writer.encode_cbor(value, level)); + } + for (value, level) in negative_cases { + let mut buf = Vec::new(); + let mut writer = Writer::new(&mut buf); + assert!(!writer.encode_cbor(value, level)); + } + } + + #[test] + fn test_write_nested_map_levels() { + let cbor_map: Value = cbor_map! { + "a" => 1, + "b" => cbor_map! { + "c" => 2, + "d" => 3, + }, + }; + + let mut buf = Vec::new(); + let mut writer = Writer::new(&mut buf); + assert!(writer.encode_cbor(cbor_map.clone(), 2)); + writer = Writer::new(&mut buf); + assert!(!writer.encode_cbor(cbor_map, 1)); + } + + #[test] + fn test_write_unbalanced_nested_containers() { + let cbor_array: Value = cbor_array![ + 1, + 2, + 3, + cbor_map! { + "a" => 1, + "b" => cbor_map! { + "c" => 2, + "d" => 3, + }, + }, + ]; + + let mut buf = Vec::new(); + let mut writer = Writer::new(&mut buf); + assert!(writer.encode_cbor(cbor_array.clone(), 3)); + writer = Writer::new(&mut buf); + assert!(!writer.encode_cbor(cbor_array, 2)); + } + + #[test] + fn test_write_overly_nested() { + let cbor_map: Value = cbor_map! { + "a" => 1, + "b" => cbor_map! { + "c" => 2, + "d" => 3, + "h" => cbor_map! { + "e" => 4, + "f" => 5, + "g" => cbor_array![ + 6, + 7, + cbor_array![ + 8 + ] + ], + }, + }, + }; + + let mut buf = Vec::new(); + let mut writer = Writer::new(&mut buf); + assert!(writer.encode_cbor(cbor_map.clone(), 5)); + writer = Writer::new(&mut buf); + assert!(!writer.encode_cbor(cbor_map, 4)); + } +} diff --git a/libraries/crypto/Cargo.toml b/libraries/crypto/Cargo.toml new file mode 100644 index 0000000..d18c5ad --- /dev/null +++ b/libraries/crypto/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "crypto" +version = "0.1.0" +authors = [ + "Fabian Kaczmarczyck ", + "Guillaume Endignoux ", + "Jean-Michel Picod ", +] +license = "Apache-2.0" +edition = "2018" + +[dependencies] +libtock = { path = "../../third_party/libtock-rs" } +cbor = { path = "../cbor" } +arrayref = "0.3.5" +subtle = { version = "2.2", default-features = false, features = ["nightly"] } +byteorder = { version = "1", default-features = false } +hex = { version = "0.3.2", default-features = false, optional = true } +ring = { version = "0.14.6", optional = true } +untrusted = { version = "0.6.2", optional = true } +rand = { version = "0.6.5", optional = true } +serde = { version = "1.0", optional = true, features = ["derive"] } +serde_json = { version = "1.0", optional = true } +regex = { version = "1", optional = true } + +[features] +std = ["cbor/std", "hex", "rand", "ring", "untrusted", "serde", "serde_json", "regex"] +derive_debug = [] +with_ctap1 = [] diff --git a/libraries/crypto/src/aes256.rs b/libraries/crypto/src/aes256.rs new file mode 100644 index 0000000..cdd5905 --- /dev/null +++ b/libraries/crypto/src/aes256.rs @@ -0,0 +1,541 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::util::{xor_block_16, Block16}; +use super::{Decrypt16BytesBlock, Encrypt16BytesBlock}; + +/** A portable and naive textbook implementation of AES-256 **/ +type Word = [u8; 4]; + +/** This structure caches the round keys, to avoid re-computing the key schedule for each block. **/ +pub struct EncryptionKey { + enc_round_keys: [Block16; 15], +} + +pub struct DecryptionKey { + dec_round_keys: [Block16; 15], +} + +impl EncryptionKey { + // Computes the round keys. + pub fn new(key: &[u8; 32]) -> EncryptionKey { + let mut enc_round_keys = [Default::default(); 15]; + + enc_round_keys[0] = *array_ref![key, 0, 16]; + enc_round_keys[1] = *array_ref![key, 16, 16]; + + let mut word: Word = *array_ref![enc_round_keys[1], 12, 4]; + for i in 2..15 { + if i & 1 == 0 { + rotword(&mut word); + subword(&mut word); + word[0] ^= RCON[(i >> 1) - 1]; + } else { + subword(&mut word); + } + + for j in 0..4 { + xorword(&mut word, *array_ref![enc_round_keys[i - 2], 4 * j, 4]); + *array_mut_ref![enc_round_keys[i], 4 * j, 4] = word; + } + } + + EncryptionKey { enc_round_keys } + } +} + +impl Encrypt16BytesBlock for EncryptionKey { + // Encrypt an AES block in place. + fn encrypt_block(&self, block: &mut Block16) { + add_round_key(block, &self.enc_round_keys[0]); + for i in 1..14 { + aes_enc(block, &self.enc_round_keys[i]); + } + aes_enc_last(block, &self.enc_round_keys[14]); + } +} + +impl DecryptionKey { + // Computes the round keys. + pub fn new(key: &EncryptionKey) -> DecryptionKey { + let mut dec_round_keys = [Default::default(); 15]; + dec_round_keys[0] = key.enc_round_keys[14]; + #[allow(clippy::needless_range_loop)] + for i in 1..14 { + let rk = &mut dec_round_keys[i]; + *rk = key.enc_round_keys[14 - i]; + inv_mix_columns(rk); + } + dec_round_keys[14] = key.enc_round_keys[0]; + + DecryptionKey { dec_round_keys } + } +} + +impl Decrypt16BytesBlock for DecryptionKey { + // Decrypt an AES block in place. + fn decrypt_block(&self, block: &mut Block16) { + add_round_key(block, &self.dec_round_keys[0]); + for i in 1..14 { + aes_dec(block, &self.dec_round_keys[i]); + } + aes_dec_last(block, &self.dec_round_keys[14]); + } +} + +/** Helper functions for the key schedule **/ +fn rotword(word: &mut Word) { + let tmp = word[0]; + word[0] = word[1]; + word[1] = word[2]; + word[2] = word[3]; + word[3] = tmp; +} + +fn subword(word: &mut Word) { + for byte in word.iter_mut() { + *byte = SBOX[*byte as usize]; + } +} + +fn xorword(word: &mut Word, src: Word) { + for i in 0..4 { + word[i] ^= src[i]; + } +} + +/** Helper functions for the encryption **/ +fn aes_enc(block: &mut Block16, rkey: &Block16) { + sub_bytes(block); + shift_rows(block); + mix_columns(block); + add_round_key(block, rkey); +} + +fn aes_dec(block: &mut Block16, rkey: &Block16) { + inv_shift_rows(block); + inv_sub_bytes(block); + inv_mix_columns(block); + add_round_key(block, rkey); +} + +fn aes_enc_last(block: &mut Block16, rkey: &Block16) { + sub_bytes(block); + shift_rows(block); + add_round_key(block, rkey); +} + +fn aes_dec_last(block: &mut Block16, rkey: &Block16) { + inv_shift_rows(block); + inv_sub_bytes(block); + add_round_key(block, rkey); +} + +#[inline(always)] +fn add_round_key(block: &mut Block16, rkey: &Block16) { + xor_block_16(block, rkey); +} + +fn sub_bytes(block: &mut Block16) { + for byte in block.iter_mut() { + *byte = SBOX[*byte as usize]; + } +} + +fn inv_sub_bytes(block: &mut Block16) { + for byte in block.iter_mut() { + *byte = SBOX_INV[*byte as usize]; + } +} + +fn shift_rows(block: &mut Block16) { + let tmp = block[1]; + block[1] = block[5]; + block[5] = block[9]; + block[9] = block[13]; + block[13] = tmp; + + block.swap(2, 10); + block.swap(6, 14); + + let tmp = block[3]; + block[3] = block[15]; + block[15] = block[11]; + block[11] = block[7]; + block[7] = tmp; +} + +fn inv_shift_rows(block: &mut Block16) { + let tmp = block[7]; + block[7] = block[11]; + block[11] = block[15]; + block[15] = block[3]; + block[3] = tmp; + + block.swap(2, 10); + block.swap(6, 14); + + let tmp = block[13]; + block[13] = block[9]; + block[9] = block[5]; + block[5] = block[1]; + block[1] = tmp; +} + +// multiplication by 2 in GF(2^256) +fn mul2(x: u8) -> u8 { + (x << 1) ^ (((x >> 7) & 1) * 0x1b) +} + +// multiplication by 3 in GF(2^256) +fn mul3(x: u8) -> u8 { + mul2(x) ^ x +} + +fn mix_columns(block: &mut Block16) { + for i in 0..4 { + let x0 = block[4 * i]; + let x1 = block[4 * i + 1]; + let x2 = block[4 * i + 2]; + let x3 = block[4 * i + 3]; + block[4 * i] = mul2(x0) ^ mul3(x1) ^ x2 ^ x3; + block[4 * i + 1] = x0 ^ mul2(x1) ^ mul3(x2) ^ x3; + block[4 * i + 2] = x0 ^ x1 ^ mul2(x2) ^ mul3(x3); + block[4 * i + 3] = mul3(x0) ^ x1 ^ x2 ^ mul2(x3); + } +} + +// multiplication by 9 in GF(2^256) +fn mul9(x: u8) -> u8 { + mul2(mul2(mul2(x))) ^ x +} + +// multiplication by 11 in GF(2^256) +fn mul11(x: u8) -> u8 { + mul2(mul2(mul2(x)) ^ x) ^ x +} + +// multiplication by 13 in GF(2^256) +fn mul13(x: u8) -> u8 { + mul2(mul2(mul2(x) ^ x)) ^ x +} + +// multiplication by 14 in GF(2^256) +fn mul14(x: u8) -> u8 { + mul2(mul2(mul2(x) ^ x) ^ x) +} + +fn inv_mix_columns(block: &mut Block16) { + for i in 0..4 { + let x0 = block[4 * i]; + let x1 = block[4 * i + 1]; + let x2 = block[4 * i + 2]; + let x3 = block[4 * i + 3]; + block[4 * i] = mul14(x0) ^ mul11(x1) ^ mul13(x2) ^ mul9(x3); + block[4 * i + 1] = mul9(x0) ^ mul14(x1) ^ mul11(x2) ^ mul13(x3); + block[4 * i + 2] = mul13(x0) ^ mul9(x1) ^ mul14(x2) ^ mul11(x3); + block[4 * i + 3] = mul11(x0) ^ mul13(x1) ^ mul9(x2) ^ mul14(x3); + } +} + +/** Constants **/ +// Constants used in the key schedule. +const RCON: [u8; 7] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40]; + +// AES substitution box. +const SBOX: [u8; 256] = [ + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, +]; + +const SBOX_INV: [u8; 256] = [ + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, +]; + +#[cfg(test)] +mod test { + use super::*; + + // Test vector from the NIST obtained at: + // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_ECB.pdf + #[test] + fn test_nist_aes256_ecb_encrypt() { + let src = b"\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96\ + \xe9\x3d\x7e\x11\x73\x93\x17\x2a"; + let key = b"\x60\x3d\xeb\x10\x15\xca\x71\xbe\ + \x2b\x73\xae\xf0\x85\x7d\x77\x81\ + \x1f\x35\x2c\x07\x3b\x61\x08\xd7\ + \x2d\x98\x10\xa3\x09\x14\xdf\xf4"; + let expected = b"\xf3\xee\xd1\xbd\xb5\xd2\xa0\x3c\ + \x06\x4b\x5a\x7e\x3d\xb1\x81\xf8"; + + let mut dst: Block16 = Default::default(); + dst.copy_from_slice(src); + EncryptionKey::new(key).encrypt_block(&mut dst); + assert_eq!(&dst, expected); + } + + #[test] + fn test_nist_aes256_ecb_decrypt() { + let src = b"\xf3\xee\xd1\xbd\xb5\xd2\xa0\x3c\ + \x06\x4b\x5a\x7e\x3d\xb1\x81\xf8"; + let key = b"\x60\x3d\xeb\x10\x15\xca\x71\xbe\ + \x2b\x73\xae\xf0\x85\x7d\x77\x81\ + \x1f\x35\x2c\x07\x3b\x61\x08\xd7\ + \x2d\x98\x10\xa3\x09\x14\xdf\xf4"; + let expected = b"\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96\ + \xe9\x3d\x7e\x11\x73\x93\x17\x2a"; + + let mut dst: Block16 = Default::default(); + dst.copy_from_slice(src); + DecryptionKey::new(&EncryptionKey::new(key)).decrypt_block(&mut dst); + assert_eq!(&dst, expected); + } + + #[test] + fn test_encrypt_decrypt() { + // Test that decrypt_block is the inverse of encrypt_block for a bunch of block values. + let key_bytes = b"\x60\x3d\xeb\x10\x15\xca\x71\xbe\ + \x2b\x73\xae\xf0\x85\x7d\x77\x81\ + \x1f\x35\x2c\x07\x3b\x61\x08\xd7\ + \x2d\x98\x10\xa3\x09\x14\xdf\xf4"; + let enc_key = EncryptionKey::new(key_bytes); + let dec_key = DecryptionKey::new(&enc_key); + let mut block: Block16 = [0; 16]; + for i in 0..=255 { + for j in 0..16 { + block[j] = (i + j) as u8; + } + let expected = block; + enc_key.encrypt_block(&mut block); + dec_key.decrypt_block(&mut block); + assert_eq!(block, expected); + } + } + + #[test] + fn test_sbox_is_permutation() { + let mut image = [false; 256]; + for &sboxed in SBOX.iter() { + assert_eq!(image[sboxed as usize], false); + image[sboxed as usize] = true; + } + } + + #[test] + fn test_sbox_inv_is_permutation() { + let mut image = [false; 256]; + for &sboxed in SBOX_INV.iter() { + assert_eq!(image[sboxed as usize], false); + image[sboxed as usize] = true; + } + } + + #[test] + fn test_sbox_inverse() { + for i in 0..=255 { + assert_eq!(SBOX_INV[SBOX[i as usize] as usize], i); + } + } + + #[test] + fn test_subbytes() { + let mut block = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let expected = [ + 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, + ]; + sub_bytes(&mut block); + assert_eq!(block, expected); + } + + #[test] + fn test_subbytes_inv() { + // Test that inv_sub_bytes is the inverse of sub_bytes for a bunch of block values. + let mut block: Block16 = [0; 16]; + for i in 0..=255 { + for j in 0..16 { + block[j] = (i + j) as u8; + } + let expected = block; + sub_bytes(&mut block); + inv_sub_bytes(&mut block); + assert_eq!(block, expected); + } + } + + #[test] + fn test_shift_rows() { + let mut block = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let expected = [0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11]; + shift_rows(&mut block); + assert_eq!(block, expected); + } + + #[test] + fn test_shift_rows_inv() { + // Test that inv_shift_rows is the inverse of shift_rows for a bunch of block values. + let mut block: Block16 = [0; 16]; + for i in 0..=255 { + for j in 0..16 { + block[j] = (i + j) as u8; + } + let expected = block; + shift_rows(&mut block); + inv_shift_rows(&mut block); + assert_eq!(block, expected); + } + } + + #[test] + fn test_mix_columns_inv() { + // Test that inv_mix_columns is the inverse of mix_columns for a bunch of block values. + let mut block: Block16 = [0; 16]; + for i in 0..=255 { + for j in 0..16 { + block[j] = (i + j) as u8; + } + let expected = block; + mix_columns(&mut block); + inv_mix_columns(&mut block); + assert_eq!(block, expected); + } + } + + /** Comparison with AES-NI instructions for CPUs that support them **/ + #[cfg(all(target_arch = "x86_64", target_feature = "aes"))] + mod aesni { + use super::super::*; + + fn aes_enc_ni(block: &mut Block16, rkey: &Block16) { + use core::arch::x86_64::{__m128i, _mm_aesenc_si128}; + + unsafe { + let block_mm: __m128i = core::mem::transmute(*block); + let rkey_mm: __m128i = core::mem::transmute(*rkey); + let encrypted_mm: __m128i = _mm_aesenc_si128(block_mm, rkey_mm); + *block = core::mem::transmute(encrypted_mm) + } + } + + fn aes_enc_last_ni(block: &mut Block16, rkey: &Block16) { + use core::arch::x86_64::{__m128i, _mm_aesenclast_si128}; + + unsafe { + let block_mm: __m128i = core::mem::transmute(*block); + let rkey_mm: __m128i = core::mem::transmute(*rkey); + let encrypted_mm: __m128i = _mm_aesenclast_si128(block_mm, rkey_mm); + *block = core::mem::transmute(encrypted_mm) + } + } + + fn aes_dec_ni(block: &mut Block16, rkey: &Block16) { + use core::arch::x86_64::{__m128i, _mm_aesdec_si128}; + + unsafe { + let block_mm: __m128i = core::mem::transmute(*block); + let rkey_mm: __m128i = core::mem::transmute(*rkey); + let decrypted_mm: __m128i = _mm_aesdec_si128(block_mm, rkey_mm); + *block = core::mem::transmute(decrypted_mm) + } + } + + fn aes_dec_last_ni(block: &mut Block16, rkey: &Block16) { + use core::arch::x86_64::{__m128i, _mm_aesdeclast_si128}; + + unsafe { + let block_mm: __m128i = core::mem::transmute(*block); + let rkey_mm: __m128i = core::mem::transmute(*rkey); + let decrypted_mm: __m128i = _mm_aesdeclast_si128(block_mm, rkey_mm); + *block = core::mem::transmute(decrypted_mm) + } + } + + #[test] + fn test_aes_enc_ni() { + let mut block = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let mut block_ni = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let rkey = [ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + ]; + aes_enc(&mut block, &rkey); + aes_enc_ni(&mut block_ni, &rkey); + assert_eq!(block, block_ni); + } + + #[test] + fn test_aes_enc_last_ni() { + let mut block = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let mut block_ni = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let rkey = [ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + ]; + aes_enc_last(&mut block, &rkey); + aes_enc_last_ni(&mut block_ni, &rkey); + assert_eq!(block, block_ni); + } + + #[test] + fn test_aes_dec_ni() { + let mut block = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let mut block_ni = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let rkey = [ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + ]; + aes_dec(&mut block, &rkey); + aes_dec_ni(&mut block_ni, &rkey); + assert_eq!(block, block_ni); + } + + #[test] + fn test_aes_dec_last_ni() { + let mut block = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let mut block_ni = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let rkey = [ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + ]; + aes_dec_last(&mut block, &rkey); + aes_dec_last_ni(&mut block_ni, &rkey); + assert_eq!(block, block_ni); + } + } +} diff --git a/libraries/crypto/src/cbc.rs b/libraries/crypto/src/cbc.rs new file mode 100644 index 0000000..fd7dcc2 --- /dev/null +++ b/libraries/crypto/src/cbc.rs @@ -0,0 +1,258 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::util::{xor_block_16, Block16}; +use super::{Decrypt16BytesBlock, Encrypt16BytesBlock}; + +pub fn cbc_encrypt(key: &K, mut iv: Block16, blocks: &mut [Block16]) +where + K: Encrypt16BytesBlock, +{ + for block in blocks { + xor_block_16(block, &iv); + key.encrypt_block(block); + iv = *block; + } +} + +pub fn cbc_decrypt(key: &K, mut iv: Block16, blocks: &mut [Block16]) +where + K: Decrypt16BytesBlock, +{ + for block in blocks { + let tmp = *block; + key.decrypt_block(block); + xor_block_16(block, &iv); + iv = tmp; + } +} + +#[cfg(test)] +mod test { + use super::super::aes256; + use super::*; + + #[test] + fn test_cbc_encrypt_decrypt() { + // Test that cbc_decrypt is the inverse of cbc_encrypt for a bunch of block values. + let enc_key = aes256::EncryptionKey::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ]); + let dec_key = aes256::DecryptionKey::new(&enc_key); + + for len in 0..16 { + let mut blocks: Vec = vec![Default::default(); len]; + for i in 0..len { + for j in 0..16 { + blocks[i][j] = ((len + i) * 16 + j) as u8; + } + } + let iv = [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]; + let expected = blocks.clone(); + + cbc_encrypt(&enc_key, iv, &mut blocks); + cbc_decrypt(&dec_key, iv, &mut blocks); + assert_eq!(blocks, expected); + } + } + + #[test] + fn test_cbc_encrypt_1block_zero_iv() { + let key = aes256::EncryptionKey::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ]); + + let mut blocks = [[ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]]; + let iv = [0; 16]; + cbc_encrypt(&key, iv, &mut blocks); + + let mut expected = [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]; + key.encrypt_block(&mut expected); + + assert_eq!(blocks, [expected]); + } + + #[test] + fn test_cbc_decrypt_1block_zero_iv() { + let key = aes256::DecryptionKey::new(&aes256::EncryptionKey::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ])); + + let mut blocks = [[ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]]; + let iv = [0; 16]; + cbc_decrypt(&key, iv, &mut blocks); + + let mut expected = [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]; + key.decrypt_block(&mut expected); + + assert_eq!(blocks, [expected]); + } + + #[test] + fn test_cbc_encrypt_1block() { + let key = aes256::EncryptionKey::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ]); + + let mut blocks = [[ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]]; + let iv = [ + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, + ]; + cbc_encrypt(&key, iv, &mut blocks); + + let mut expected = [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]; + xor_block_16(&mut expected, &iv); + key.encrypt_block(&mut expected); + + assert_eq!(blocks, [expected]); + } + + #[test] + fn test_cbc_decrypt_1block() { + let key = aes256::DecryptionKey::new(&aes256::EncryptionKey::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ])); + + let mut blocks = [[ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]]; + let iv = [ + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, + ]; + cbc_decrypt(&key, iv, &mut blocks); + + let mut expected = [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]; + key.decrypt_block(&mut expected); + xor_block_16(&mut expected, &iv); + + assert_eq!(blocks, [expected]); + } + + #[test] + fn test_cbc_encrypt_2blocks() { + let key = aes256::EncryptionKey::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ]); + + let mut blocks = [ + [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ], + [ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, + ], + ]; + let iv = [ + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, + ]; + cbc_encrypt(&key, iv, &mut blocks); + + let mut expected0 = [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]; + let mut expected1 = [ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, + ]; + xor_block_16(&mut expected0, &iv); + key.encrypt_block(&mut expected0); + xor_block_16(&mut expected1, &expected0); + key.encrypt_block(&mut expected1); + + assert_eq!(blocks, [expected0, expected1]); + } + + #[test] + fn test_cbc_decrypt_2blocks() { + let key = aes256::DecryptionKey::new(&aes256::EncryptionKey::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ])); + + let mut blocks = [ + [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ], + [ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, + ], + ]; + let iv = [ + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, + ]; + cbc_decrypt(&key, iv, &mut blocks); + + let mut expected0 = [ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, + ]; + let mut expected1 = [ + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, + ]; + key.decrypt_block(&mut expected1); + xor_block_16(&mut expected1, &expected0); + key.decrypt_block(&mut expected0); + xor_block_16(&mut expected0, &iv); + + assert_eq!(blocks, [expected0, expected1]); + } +} diff --git a/libraries/crypto/src/ec/exponent256.rs b/libraries/crypto/src/ec/exponent256.rs new file mode 100644 index 0000000..8638eaa --- /dev/null +++ b/libraries/crypto/src/ec/exponent256.rs @@ -0,0 +1,346 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::super::rng256::Rng256; +use super::int256::{Digit, Int256}; +use core::ops::Mul; +use subtle::{self, Choice, ConditionallySelectable, CtOption}; + +// An exponent on the elliptic curve, that is an element modulo the curve order N. +#[derive(Clone, Copy, PartialEq, Eq)] +// TODO: remove this Default once https://github.com/dalek-cryptography/subtle/issues/63 is +// resolved. +#[derive(Default)] +#[cfg_attr(feature = "derive_debug", derive(Debug))] +pub struct ExponentP256 { + int: Int256, +} + +impl ConditionallySelectable for ExponentP256 { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + int: Int256::conditional_select(&a.int, &b.int, choice), + } + } +} + +impl ExponentP256 { + /** Constructors **/ + pub fn from_int_checked(int: Int256) -> CtOption { + CtOption::new(ExponentP256 { int }, int.ct_lt(&Int256::N)) + } + + #[cfg(test)] + // Normally the ExponentP256 type guarantees that its values stay in [0, N[ because N is the + // curve order and therefore exponents >= N are equivalent to their reduction modulo N. + // This unsafe function is only used in tests to check that N is indeed the curve order. + pub unsafe fn from_int_unchecked(int: Int256) -> ExponentP256 { + ExponentP256 { int } + } + + pub fn modn(int: Int256) -> ExponentP256 { + ExponentP256 { + int: int.modd(&Int256::N), + } + } + + /** Helpful getters **/ + pub fn bit(&self, i: usize) -> Digit { + self.int.bit(i) + } + + pub fn to_int(self) -> Int256 { + self.int + } + + pub fn is_zero(&self) -> subtle::Choice { + self.int.is_zero() + } + + pub fn non_zero(self) -> CtOption { + CtOption::new(NonZeroExponentP256 { e: self }, !self.is_zero()) + } + + /** Arithmetic **/ + pub fn mul_top(&self, other: &Int256, other_top: Digit) -> ExponentP256 { + ExponentP256 { + int: Int256::modmul_top(&self.int, other, other_top, &Int256::N), + } + } +} + +/** Arithmetic operators **/ +impl Mul for &ExponentP256 { + type Output = ExponentP256; + + fn mul(self, other: &ExponentP256) -> ExponentP256 { + ExponentP256 { + int: Int256::modmul(&self.int, &other.int, &Int256::N), + } + } +} + +// A non-zero exponent on the elliptic curve. +#[derive(Clone, Copy, PartialEq, Eq)] +// TODO: remove this Default once https://github.com/dalek-cryptography/subtle/issues/63 is +// resolved. +#[derive(Default)] +#[cfg_attr(feature = "derive_debug", derive(Debug))] +pub struct NonZeroExponentP256 { + e: ExponentP256, +} + +impl ConditionallySelectable for NonZeroExponentP256 { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + e: ExponentP256::conditional_select(&a.e, &b.e, choice), + } + } +} + +impl NonZeroExponentP256 { + /** RNG **/ + // Generates a uniformly distributed element 0 < k < N + pub fn gen_uniform(r: &mut R) -> NonZeroExponentP256 + where + R: Rng256, + { + loop { + let x = Int256::gen_uniform_256(r); + if bool::from(Int256::N_MIN_2.ct_lt(&x)) { + continue; + } + // At this point, x <= n - 2. + // We add 1 so that 0 < result < n. + return NonZeroExponentP256 { + e: ExponentP256 { int: (&x + 1).0 }, + }; + } + } + + /** Constructors **/ + pub fn from_int_checked(int: Int256) -> CtOption { + ExponentP256::from_int_checked(int) + .and_then(|e| CtOption::new(NonZeroExponentP256 { e }, !e.is_zero())) + } + + /** Helpful getters **/ + pub fn to_int(self) -> Int256 { + self.e.to_int() + } + + pub fn as_exponent(&self) -> &ExponentP256 { + &self.e + } + + /** Arithmetic **/ + // Compute the inverse modulo N. This uses Fermat's little theorem for constant-timeness. + pub fn inv(&self) -> NonZeroExponentP256 { + NonZeroExponentP256 { + e: ExponentP256 { + int: self.e.int.modpow(&Int256::N_MIN_2, &Int256::N), + }, + } + } + + #[cfg(test)] + fn inv_vartime(&self) -> NonZeroExponentP256 { + NonZeroExponentP256 { + e: ExponentP256 { + int: self.e.int.modinv_vartime(&Int256::N), + }, + } + } +} + +/** Arithmetic operators **/ +impl Mul for &NonZeroExponentP256 { + type Output = NonZeroExponentP256; + + // The product of two non-zero elements is also non-zero, because the curve order N is prime. + fn mul(self, other: &NonZeroExponentP256) -> NonZeroExponentP256 { + NonZeroExponentP256 { + e: &self.e * &other.e, + } + } +} + +#[cfg(test)] +pub mod test { + use super::super::montgomery::Montgomery; + use super::*; + use crate::util::ToOption; + + const ZERO: ExponentP256 = ExponentP256 { int: Int256::ZERO }; + const ONE: NonZeroExponentP256 = NonZeroExponentP256 { + e: ExponentP256 { int: Int256::ONE }, + }; + const N_MIN_1_INT: Int256 = Int256::new([ + 0xfc632550, 0xf3b9cac2, 0xa7179e84, 0xbce6faad, 0xffffffff, 0xffffffff, 0x00000000, + 0xffffffff, + ]); + const N_MIN_1: NonZeroExponentP256 = NonZeroExponentP256 { + e: ExponentP256 { int: N_MIN_1_INT }, + }; + + fn get_nonzero_test_values() -> Vec { + let mut values: Vec = Montgomery::PRECOMPUTED + .iter() + .flatten() + .flatten() + .map(|x| { + ExponentP256::modn(x.montgomery_to_field().to_int()) + .non_zero() + .unwrap() + }) + .collect(); + values.extend( + super::super::int256::test::get_nonzero_test_values() + .iter() + .filter_map(|&x| { + let y = ExponentP256::modn(x).non_zero(); + if bool::from(y.is_some()) { + Some(y.unwrap()) + } else { + None + } + }), + ); + values.push(ONE); + values + } + + pub fn get_test_values() -> Vec { + let mut values: Vec = get_nonzero_test_values() + .iter() + .map(|x| *x.as_exponent()) + .collect(); + values.push(ZERO); + values + } + + /** Constructors **/ + #[test] + fn test_from_int_checked() { + assert_eq!( + ExponentP256::from_int_checked(Int256::ZERO).to_option(), + Some(ExponentP256 { int: Int256::ZERO }) + ); + assert_eq!( + ExponentP256::from_int_checked(Int256::ONE).to_option(), + Some(ExponentP256 { int: Int256::ONE }) + ); + assert_eq!( + ExponentP256::from_int_checked(N_MIN_1_INT).to_option(), + Some(ExponentP256 { int: N_MIN_1_INT }) + ); + assert_eq!(ExponentP256::from_int_checked(Int256::N).to_option(), None); + } + + #[test] + fn test_modn() { + assert_eq!( + ExponentP256::modn(Int256::ZERO), + ExponentP256 { int: Int256::ZERO } + ); + assert_eq!( + ExponentP256::modn(Int256::ONE), + ExponentP256 { int: Int256::ONE } + ); + assert_eq!( + ExponentP256::modn(N_MIN_1_INT), + ExponentP256 { int: N_MIN_1_INT } + ); + assert_eq!( + ExponentP256::modn(Int256::N), + ExponentP256 { int: Int256::ZERO } + ); + } + + /** Arithmetic operations: inverse **/ + #[test] + fn test_inv_is_inv_vartime() { + for x in &get_nonzero_test_values() { + assert_eq!(x.inv(), x.inv_vartime()); + } + } + + #[test] + fn test_self_times_inv_is_one() { + for x in &get_nonzero_test_values() { + assert_eq!(x * &x.inv(), ONE); + } + } + + #[test] + fn test_inv_inv() { + for x in get_nonzero_test_values() { + assert_eq!(x.inv().inv(), x); + } + } + + #[test] + fn test_well_known_inverses() { + assert_eq!(ONE.inv(), ONE); + assert_eq!(N_MIN_1.inv(), N_MIN_1); + } + + /** RNG **/ + // Mock rng that samples through a list of values, then panics. + struct StressTestingRng { + values: Vec, + index: usize, + } + + impl StressTestingRng { + pub fn new(values: Vec) -> StressTestingRng { + StressTestingRng { values, index: 0 } + } + } + + impl Rng256 for StressTestingRng { + // This function is unused, as we redefine gen_uniform_u32x8. + fn gen_uniform_u8x32(&mut self) -> [u8; 32] { + unreachable!() + } + + fn gen_uniform_u32x8(&mut self) -> [u32; 8] { + let result = self.values[self.index].digits(); + self.index += 1; + result + } + } + + #[test] + fn test_uniform_non_zero_is_below_n() { + let mut rng = StressTestingRng::new(vec![ + Int256::new([ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, + ]), + Int256::N, + N_MIN_1.to_int(), + Int256::N_MIN_2, + ]); + + assert_eq!(NonZeroExponentP256::gen_uniform(&mut rng), N_MIN_1); + } + + #[test] + fn test_uniform_n_is_above_zero() { + let mut rng = StressTestingRng::new(vec![Int256::ZERO]); + + assert_eq!(NonZeroExponentP256::gen_uniform(&mut rng), ONE); + } +} diff --git a/libraries/crypto/src/ec/gfp256.rs b/libraries/crypto/src/ec/gfp256.rs new file mode 100644 index 0000000..bb3232c --- /dev/null +++ b/libraries/crypto/src/ec/gfp256.rs @@ -0,0 +1,260 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::int256::{Digit, Int256}; +use core::ops::Mul; +use subtle::Choice; + +// A field element on the elliptic curve, that is an element modulo the prime P. +// This is the format used to serialize coordinates of points on the curve. +// This implements enough methods to validate points and to convert them to/from the Montgomery +// form, which is more convenient to operate on. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct GFP256 { + int: Int256, +} + +impl GFP256 { + pub const ZERO: GFP256 = GFP256 { int: Int256::ZERO }; + pub const ONE: GFP256 = GFP256 { int: Int256::ONE }; + pub const B: GFP256 = GFP256 { int: Int256::B }; + pub const R: GFP256 = GFP256 { int: Int256::R }; + pub const R_INV: GFP256 = GFP256 { int: Int256::R_INV }; + + /** Constructors **/ + pub fn from_int_checked(int: Int256) -> Option { + if bool::from(int.ct_lt(&Int256::P)) { + Some(GFP256 { int }) + } else { + None + } + } + + /** Helpful getters **/ + pub fn to_int(self) -> Int256 { + self.int + } + + fn is_zero(&self) -> Choice { + self.int.is_zero() + } + + /** Arithmetic **/ + pub fn mul_top(&self, other: &Int256, other_top: Digit) -> GFP256 { + GFP256 { + int: Int256::modmul_top(&self.int, other, other_top, &Int256::P), + } + } + + /** Point validation **/ + // Verify that all of the following are true: + // * y^2 == x^3 - 3x + b mod p + // * 0 < x < p + // * 0 < y < p + // + // Not constant time. + pub fn is_valid_point_vartime(x: &GFP256, y: &GFP256) -> bool { + if bool::from(x.is_zero()) || bool::from(y.is_zero()) { + return false; + } + + // y^2 + let y2 = y * y; + + // x^3 + let x2 = x * x; + let x3 = &x2 * x; + + // x^3 - 3x + b + let mut xx = x3; + xx = xx.sub_vartime(x); + xx = xx.sub_vartime(x); + xx = xx.sub_vartime(x); + xx = xx.add_vartime(&GFP256::B); + + xx == y2 + } + + /** Arithmetic operators **/ + fn add_vartime(self, other: &GFP256) -> GFP256 { + GFP256 { + int: Int256::modadd_vartime(&self.int, &other.int, &Int256::P), + } + } + + fn sub_vartime(self, other: &GFP256) -> GFP256 { + GFP256 { + int: Int256::modsub_vartime(&self.int, &other.int, &Int256::P), + } + } +} + +/** Arithmetic operators **/ +impl Mul for &GFP256 { + type Output = GFP256; + + fn mul(self, other: &GFP256) -> GFP256 { + GFP256 { + int: Int256::modmul(&self.int, &other.int, &Int256::P), + } + } +} + +#[cfg(feature = "derive_debug")] +impl core::fmt::Debug for GFP256 { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "GFP256::{:?}", self.int) + } +} + +#[cfg(test)] +mod test { + use super::super::montgomery::Montgomery; + use super::*; + use core::ops::{Add, Sub}; + + const P_MIN_1_INT: Int256 = Int256::new([ + 0xfffffffe, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0xffffffff, + ]); + + fn get_test_values() -> Vec { + let mut values: Vec = Montgomery::PRECOMPUTED + .iter() + .flatten() + .flatten() + .map(|x| x.montgomery_to_field()) + .collect(); + values.extend( + super::super::int256::test::get_1bit_one_test_values() + .iter() + .filter_map(|&x| GFP256::from_int_checked(x)), + ); + values.extend( + super::super::int256::test::get_1bit_zero_test_values() + .iter() + .filter_map(|&x| GFP256::from_int_checked(x)), + ); + values.push(GFP256::ZERO); + values.push(GFP256::ONE); + values.push(GFP256::B); + values + } + + /** Arithmetic operators, only for tests as these are not constant time **/ + impl Add for &GFP256 { + type Output = GFP256; + + fn add(self, other: &GFP256) -> GFP256 { + self.add_vartime(other) + } + } + + impl Sub for &GFP256 { + type Output = GFP256; + + fn sub(self, other: &GFP256) -> GFP256 { + self.sub_vartime(other) + } + } + + /** Constructors **/ + #[test] + fn test_from_int_checked() { + assert_eq!( + GFP256::from_int_checked(Int256::ZERO), + Some(GFP256 { int: Int256::ZERO }) + ); + assert_eq!( + GFP256::from_int_checked(Int256::ONE), + Some(GFP256 { int: Int256::ONE }) + ); + assert_eq!( + GFP256::from_int_checked(P_MIN_1_INT), + Some(GFP256 { int: P_MIN_1_INT }) + ); + assert_eq!(GFP256::from_int_checked(Int256::P), None); + } + + /** Point validation **/ + // See point.rs + + /** Arithmetic operators **/ + // Due to the 3 nested loops, this test is super slow with debug assertions enabled. + #[cfg(not(debug_assertions))] + #[test] + fn test_add_is_associative() { + for x in &get_test_values() { + for y in &get_test_values() { + for z in &get_test_values() { + assert_eq!(&(x + y) + z, x + &(y + z)); + } + } + } + } + + #[test] + fn test_add_is_commutative() { + for x in &get_test_values() { + for y in &get_test_values() { + assert_eq!(x + y, y + x); + } + } + } + + #[test] + fn test_add_sub() { + for x in &get_test_values() { + for y in &get_test_values() { + assert_eq!(&(x - y) + y, *x); + assert_eq!(&(x + y) - y, *x); + } + } + } + + // Due to the 3 nested loops, this test is super slow with debug assertions enabled. + #[cfg(not(debug_assertions))] + #[test] + fn test_mul_is_associative() { + for x in &get_test_values() { + for y in &get_test_values() { + for z in &get_test_values() { + assert_eq!(&(x * y) * z, x * &(y * z)); + } + } + } + } + + #[test] + fn test_mul_is_commutative() { + for x in &get_test_values() { + for y in &get_test_values() { + assert_eq!(x * y, y * x); + } + } + } + + // Due to the 3 nested loops, this test is super slow with debug assertions enabled. + #[cfg(not(debug_assertions))] + #[test] + fn test_mul_is_distributive() { + for x in &get_test_values() { + for y in &get_test_values() { + for z in &get_test_values() { + assert_eq!(&(x + y) * z, &(x * z) + &(y * z)); + } + } + } + } +} diff --git a/libraries/crypto/src/ec/int256.rs b/libraries/crypto/src/ec/int256.rs new file mode 100644 index 0000000..2f3a1da --- /dev/null +++ b/libraries/crypto/src/ec/int256.rs @@ -0,0 +1,1181 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::super::rng256::Rng256; +use alloc::vec::Vec; +use byteorder::{BigEndian, ByteOrder}; +use core::ops::{Add, AddAssign, Sub, SubAssign}; +use subtle::{self, Choice, ConditionallySelectable, ConstantTimeEq}; + +const BITS_PER_DIGIT: usize = 32; +const BYTES_PER_DIGIT: usize = BITS_PER_DIGIT >> 3; +const NDIGITS: usize = 8; +pub const NBYTES: usize = NDIGITS * BYTES_PER_DIGIT; + +pub type Digit = u32; +type DoubleDigit = u64; +type SignedDoubleDigit = i64; + +#[derive(Clone, Copy, PartialEq, Eq)] +// TODO: remove this Default once https://github.com/dalek-cryptography/subtle/issues/63 is +// resolved. +#[derive(Default)] +pub struct Int256 { + digits: [Digit; NDIGITS], +} + +impl ConditionallySelectable for Int256 { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + let mut digits = [0; NDIGITS]; + for (i, digit) in digits.iter_mut().enumerate() { + *digit = Digit::conditional_select(&a.digits[i], &b.digits[i], choice); + } + Self { digits } + } +} + +/** Arithmetic operations on the secp256r1 field, where elements are represented as 8 digits of + * 32 bits. **/ +#[allow(clippy::unreadable_literal)] +impl Int256 { + /** Constants for the secp256r1 curve. **/ + // Curve order (prime) + pub const N: Int256 = Int256 { + digits: [ + 0xfc632551, 0xf3b9cac2, 0xa7179e84, 0xbce6faad, 0xffffffff, 0xffffffff, 0x00000000, + 0xffffffff, + ], + }; + // Curve order - 2 + pub const N_MIN_2: Int256 = Int256 { + digits: [ + 0xfc63254f, 0xf3b9cac2, 0xa7179e84, 0xbce6faad, 0xffffffff, 0xffffffff, 0x00000000, + 0xffffffff, + ], + }; + // Curve field size + pub const P: Int256 = Int256 { + digits: [ + 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0xffffffff, + ], + }; + // Curve b + pub const B: Int256 = Int256 { + digits: [ + 0x27d2604b, 0x3bce3c3e, 0xcc53b0f6, 0x651d06b0, 0x769886bc, 0xb3ebbd55, 0xaa3a93e7, + 0x5ac635d8, + ], + }; + // 2^257 mod P + pub const R: Int256 = Int256 { + digits: [ + 0x00000002, 0x00000000, 0x00000000, 0xfffffffe, 0xffffffff, 0xffffffff, 0xfffffffd, + 0x00000001, + ], + }; + // 1 / 2^257 mod P + pub const R_INV: Int256 = Int256 { + digits: [ + 0x80000000, 0x00000001, 0xffffffff, 0x00000000, 0x80000001, 0xfffffffe, 0x00000001, + 0x7fffffff, + ], + }; + + pub const ZERO: Int256 = Int256 { digits: [0; 8] }; + pub const ONE: Int256 = Int256 { + digits: [1, 0, 0, 0, 0, 0, 0, 0], + }; + + #[cfg(test)] + pub const fn new(digits: [Digit; NDIGITS]) -> Int256 { + Int256 { digits } + } + + #[cfg(test)] + pub fn digits(self) -> [Digit; NDIGITS] { + self.digits + } + + #[cfg(test)] + fn hamming_weight(&self) -> u32 { + self.digits.iter().map(|d| d.count_ones()).sum() + } + + /** RNG **/ + // Generates a uniformly distributed integer 0 <= x < 2^256 + pub fn gen_uniform_256(r: &mut R) -> Int256 + where + R: Rng256, + { + Int256 { + digits: r.gen_uniform_u32x8(), + } + } + + /** Serialization **/ + pub fn from_bin(src: &[u8; NBYTES]) -> Int256 { + let mut digits = [0; NDIGITS]; + for i in 0..NDIGITS { + digits[NDIGITS - 1 - i] = BigEndian::read_u32(array_ref![src, 4 * i, 4]); + } + Int256 { digits } + } + + pub fn to_bin(&self, dst: &mut [u8; NBYTES]) { + for i in 0..NDIGITS { + BigEndian::write_u32(array_mut_ref![dst, 4 * i, 4], self.digits[NDIGITS - 1 - i]); + } + } + + pub fn to_minimal_encoding(self) -> Vec { + let mut bytes_buffer = [0; NBYTES]; + self.to_bin(&mut bytes_buffer); + match bytes_buffer.iter().position(|x| *x != 0) { + Some(pos) => { + let mut encoding = vec![]; + if bytes_buffer[pos] & 0x80 == 0x80 { + encoding.push(0x00); + } + encoding.extend_from_slice(&bytes_buffer[pos..]); + encoding + } + None => vec![0x00], + } + } + + /** Useful getters **/ + #[inline(always)] + pub fn digit(&self, i: usize) -> Digit { + self.digits[i] + } + + pub fn bit(&self, i: usize) -> Digit { + let digit = i / BITS_PER_DIGIT; + let bit = i & (BITS_PER_DIGIT - 1); + (self.digits[digit] >> bit) & 1 + } + + pub fn is_zero(&self) -> subtle::Choice { + // Best effort constant-time comparison, assuming the compiler doesn't optimize that. + Choice::from( + self.digits + .iter() + .fold(1u8, |acc, x| acc & x.ct_eq(&0).unwrap_u8()), + ) + } + + // Helper function to implement variable-time modular inverse. + #[cfg(test)] + fn is_even(&self) -> bool { + self.digits[0] & 1 == 0 + } + + #[cfg(test)] + fn count_ones(&self) -> u32 { + self.digits.iter().map(|x| x.count_ones()).sum() + } + + /** Arithmetic operations: bit shifts **/ + // Shift left by n bits, and return the result as well as the top digit that was shifted out. + // This is valid only for 0 < n < BITS_PER_DIGIT + pub fn shl(&self, n: usize) -> (Int256, Digit) { + let mut digits = [0; NDIGITS]; + digits[0] = self.digits[0] << n; + #[allow(clippy::needless_range_loop)] + for i in 1..NDIGITS { + digits[i] = (self.digits[i] << n) | (self.digits[i - 1] >> (BITS_PER_DIGIT - n)); + } + + ( + Int256 { digits }, + self.digits[NDIGITS - 1] >> (BITS_PER_DIGIT - n), + ) + } + + // Shift right by n bits. + // This is valid only for 0 < n < BITS_PER_DIGIT + pub fn shr(&self, n: usize) -> Int256 { + let mut digits = [0; NDIGITS]; + #[allow(clippy::needless_range_loop)] + for i in 0..(NDIGITS - 1) { + digits[i] = (self.digits[i] >> n) | (self.digits[i + 1] << (BITS_PER_DIGIT - n)); + } + digits[NDIGITS - 1] = self.digits[NDIGITS - 1] >> n; + + Int256 { digits } + } + + // Helper function to implement variable-time modular inverse. + // Shift right by 1 bit, pushing highbit at the top. + #[cfg(test)] + fn shr1(&self, highbit: Digit) -> Int256 { + let mut digits = [0; NDIGITS]; + for i in 0..(NDIGITS - 1) { + digits[i] = (self.digits[i] >> 1) | (self.digits[i + 1] << (BITS_PER_DIGIT - 1)); + } + digits[NDIGITS - 1] = (self.digits[NDIGITS - 1] >> 1) | (highbit << (BITS_PER_DIGIT - 1)); + + Int256 { digits } + } + + /** Arithmetic operations: addition/substraction **/ + // Reduction modulo modd. + pub fn modd(&self, modd: &Int256) -> Int256 { + let mut digits = self.digits; + let choice = Int256::sub_conditional(&mut digits, modd, 0, Choice::from(1u8)); + Int256::add_conditional(&mut digits, modd, 0, choice); + Int256 { digits } + } + + // Computes: dst[], top += if choice { mod[] } else { 0 } + // Returns: new top digit + fn add_conditional( + dst: &mut [Digit; NDIGITS], + modd: &Int256, + top: Digit, + choice: Choice, + ) -> Digit { + let mut carry: DoubleDigit = 0; + + for (i, digit) in dst.iter_mut().enumerate() { + carry += *digit as DoubleDigit; + carry += u32::conditional_select(&0, &modd.digits[i], choice) as DoubleDigit; + *digit = carry as Digit; + carry >>= BITS_PER_DIGIT; + } + + (carry as Digit) + top + } + + // Computes: dst[], top -= if choice { mod[] } else { 0 } + // Returns: new top digit + fn sub_conditional( + dst: &mut [Digit; NDIGITS], + modd: &Int256, + top: Digit, + choice: Choice, + ) -> Choice { + let mut borrow: SignedDoubleDigit = 0; + + for (i, digit) in dst.iter_mut().enumerate() { + borrow += *digit as SignedDoubleDigit; + borrow -= u32::conditional_select(&0, &modd.digits[i], choice) as SignedDoubleDigit; + *digit = borrow as Digit; + borrow >>= BITS_PER_DIGIT; + } + + ((borrow + (top as SignedDoubleDigit)) as Digit).ct_eq(&!0) + } + + /** Modular arithmetic operations **/ + // Modular addition. + pub fn modadd_vartime(&self, other: &Int256, modd: &Int256) -> Int256 { + let (sum, carry) = (self as &Int256) + other; + let tmp = if carry != 0 { (&sum - modd).0 } else { sum }; + + // At this point, the sum can be >= modd, even without carry. + // We substract modd to handle this case. + tmp.modsub_vartime(modd, modd) + } + + // Modular substraction. + pub fn modsub_vartime(&self, other: &Int256, modd: &Int256) -> Int256 { + let (diff, borrow) = (self as &Int256) - other; + if borrow != 0 { + (&diff + modd).0 + } else { + diff + } + } + + // Requires: the most-significant word of the modulus is 0xffffffff. + // Computes: a * b modulo modd. + pub fn modmul(a: &Int256, b: &Int256, modd: &Int256) -> Int256 { + Int256::modmul_top(a, b, 0, modd) + } + + // Requires: the most-significant word of the modulus is 0xffffffff. + // Computes: a * (b, top_b) modulo modd. + pub fn modmul_top(a: &Int256, b: &Int256, top_b: Digit, modd: &Int256) -> Int256 { + let mut tmp = [0; NDIGITS * 2 + 1]; + let mut top = 0; + + // Multiply/add into tmp. + for i in 0..NDIGITS { + if i != 0 { + tmp[i + NDIGITS - 1] = top; + } + top = Int256::mul_add(array_mut_ref![tmp, i, NDIGITS], a, b.digits[i]); + } + + tmp[2 * NDIGITS - 1] = top; + top = Int256::mul_add(array_mut_ref![tmp, NDIGITS, NDIGITS], a, top_b); + + // Reduce tmp, digit by digit. + for j in 0..=NDIGITS { + let i = NDIGITS - j; + + // Estimate the reducer as top * modd, because the most significant word of modd is + // 0xffffffff. + let mut reducer = Int256::ZERO; + let top_reducer = Int256::mul_add(&mut reducer.digits, modd, top); + top = Int256::sub_top(array_mut_ref![tmp, i, NDIGITS], &reducer, top, top_reducer); + + #[cfg(test)] + assert!(top <= 1); + + let _top = + Int256::sub_conditional(array_mut_ref![tmp, i, NDIGITS], modd, top, top.ct_eq(&1)); + + #[cfg(test)] + assert_eq!(bool::from(_top), false); + + top = tmp[i + NDIGITS - 1]; + } + + let choice = + Int256::sub_conditional(array_mut_ref![tmp, 0, NDIGITS], modd, 0, Choice::from(1u8)); + Int256::add_conditional(array_mut_ref![tmp, 0, NDIGITS], modd, 0, choice); + + Int256 { + digits: *array_ref![tmp, 0, NDIGITS], + } + } + + // Helper function to implement modular multiplication. + // Computes: dst[] += src[] * factor + // Returns: carry digit + fn mul_add(dst: &mut [Digit; NDIGITS], src: &Int256, factor: Digit) -> Digit { + let mut carry: DoubleDigit = 0; + + for (i, digit) in dst.iter_mut().enumerate() { + carry += *digit as DoubleDigit; + carry += (src.digits[i] as DoubleDigit) * (factor as DoubleDigit); + *digit = carry as Digit; + carry >>= BITS_PER_DIGIT; + } + + carry as Digit + } + + // Helper function to implement modular multiplication. + // Computes: dst[], top -= src[], src_top + // Returns: borrow digit (new top) + fn sub_top(dst: &mut [Digit; NDIGITS], src: &Int256, top: Digit, src_top: Digit) -> Digit { + let mut borrow: SignedDoubleDigit = 0; + + for (i, digit) in dst.iter_mut().enumerate() { + borrow += *digit as SignedDoubleDigit; + borrow -= src.digits[i] as SignedDoubleDigit; + *digit = borrow as Digit; + borrow >>= BITS_PER_DIGIT; + } + + borrow += top as SignedDoubleDigit; + borrow -= src_top as SignedDoubleDigit; + + #[cfg(test)] + assert_eq!(borrow >> BITS_PER_DIGIT, 0); + + borrow as Digit + } + + /** Constant-time helpers **/ + // Helper function to implement constant-time modular inverse. + // Best-effort constant time function that computes: + // if idx == 0 { + // *tbl0 = Int256::ONE + // } else { + // *tbl0 = tbl[idx - 1] + // } + fn set_zero_to_idx(tbl0: &mut Int256, tbl: &[Int256; 15], idx: u32) { + *tbl0 = Int256::ONE; + for i in 1u32..16 { + tbl0.conditional_assign(&tbl[(i - 1) as usize], i.ct_eq(&idx)); + } + } + + /** Arithmetic operations: modular exponentiation **/ + pub fn modpow(&self, power: &Int256, modd: &Int256) -> Int256 { + let mut tbl0 = Int256::ZERO; + let mut tbl = [Int256::ZERO; 15]; + // tbl[i-1] = self^i + tbl[0] = *self; + for i in 1..15 { + tbl[i] = Int256::modmul(&tbl[i - 1], self, modd); + } + + let mut result = Int256::ONE; + for j in (0..256).step_by(4) { + let i = 256 - j; + result = Int256::modmul(&result, &result, modd); + result = Int256::modmul(&result, &result, modd); + result = Int256::modmul(&result, &result, modd); + result = Int256::modmul(&result, &result, modd); + + let idx = power.bit(i - 1) << 3 + | power.bit(i - 2) << 2 + | power.bit(i - 3) << 1 + | power.bit(i - 4); + + Int256::set_zero_to_idx(&mut tbl0, &tbl, idx); // tbl0 = tbl[idx-1]; + tbl0 = Int256::modmul(&tbl0, &result, modd); + result.conditional_assign(&tbl0, !idx.ct_eq(&0)); + } + + result + } + + /** Arithmetic operations: modular inverse **/ + // Variable time function to compute modular inverse. This uses Euclid's theorem. + #[cfg(test)] + #[allow(clippy::many_single_char_names)] + pub fn modinv_vartime(&self, modd: &Int256) -> Int256 { + let mut r = Int256::ZERO; + let mut s = Int256::ONE; + let mut u = *modd; + let mut v = *self; + + loop { + if u.is_even() { + u = u.shr1(0); + if r.is_even() { + r = r.shr1(0); + } else { + let (rr, highbit) = &r + modd; + r = rr.shr1(highbit); + } + } else if v.is_even() { + v = v.shr1(0); + if s.is_even() { + s = s.shr1(0); + } else { + let (ss, highbit) = &s + modd; + s = ss.shr1(highbit); + } + } else { + let (w, borrow) = &v - &u; + if borrow == 0 { + v = w; + let (ss, borrow) = &s - &r; + s = if borrow != 0 { (&ss + modd).0 } else { ss }; + if bool::from(v.is_zero()) { + break; + } + } else { + u = (&u - &v).0; + let (rr, borrow) = &r - &s; + r = if borrow != 0 { (&rr + modd).0 } else { rr }; + } + } + } + + r.modd(modd) + } + + /** Comparison between field elements. **/ + // Best-effort constant-time less-than operation. + // FIXME: This code is currently required because subtle only supports constant-time equality + // comparisons. This should be removed once + // https://github.com/dalek-cryptography/subtle/issues/61 is fixed + pub fn ct_lt(&self, other: &Int256) -> Choice { + let mut borrow: SignedDoubleDigit = 0; + + for i in 0..NDIGITS { + // The following statement updates the borrow according to this table. + // +-------------------------------------+----------------+------------------+ + // | self.digits[i].cmp(other.digits[i]) | borrow += ? | resulting borrow | + // +-------------------------------------+----------------+------------------+ + // | Less | ffffffff_xx... | ffffffff_yy... | + // | Equal | 0 | unchanged | + // | Greater | 00000000_xx... | 00000000_yy... | + // +-------------------------------------+----------------+------------------+ + borrow += + (self.digits[i] as SignedDoubleDigit) - (other.digits[i] as SignedDoubleDigit); + // This is a signed shift. After this operation, the borrow can take two values: + // - 00...00 (so far, self >= other) + // - ff...ff (so far, self < other) + borrow >>= BITS_PER_DIGIT; + } + + Choice::from((borrow & 1) as u8) + } + + // Best-effort constant time comparison. + // * 0 = equal + // * 1 = self > other + // * -1 = self < other + #[cfg(test)] + pub fn compare(&self, other: &Int256) -> u32 { + let mut borrow: SignedDoubleDigit = 0; + let mut notzero: Digit = 0; + + for i in 0..NDIGITS { + borrow += + (self.digits[i] as SignedDoubleDigit) - (other.digits[i] as SignedDoubleDigit); + notzero |= (borrow as Digit != 0) as Digit; + borrow >>= BITS_PER_DIGIT; + } + + (borrow as Digit) | notzero + } + + #[cfg(test)] + fn compare_vartime(&self, other: &Int256) -> u32 { + use core::cmp::Ordering; + + for i in 0..NDIGITS { + match self.digits[NDIGITS - i - 1].cmp(&other.digits[NDIGITS - i - 1]) { + Ordering::Equal => continue, + Ordering::Greater => return 1, + Ordering::Less => return 0xffffffff, + } + } + 0 + } +} + +/** Addition with carry **/ +impl Add for &Int256 { + type Output = (Int256, Digit); + + // Returns sum and carry (0 or 1). + fn add(self, other: &Int256) -> (Int256, Digit) { + let mut digits = [0; NDIGITS]; + let mut carry: DoubleDigit = 0; + + for (i, digit) in digits.iter_mut().enumerate() { + carry += (self.digits[i] as DoubleDigit) + (other.digits[i] as DoubleDigit); + *digit = carry as Digit; + carry >>= BITS_PER_DIGIT; + } + + (Int256 { digits }, carry as Digit) + } +} + +impl AddAssign<&Int256> for Int256 { + // Adds to self, ignoring carry. + fn add_assign(&mut self, other: &Int256) { + let mut carry: DoubleDigit = 0; + for i in 0..NDIGITS { + carry += (self.digits[i] as DoubleDigit) + (other.digits[i] as DoubleDigit); + self.digits[i] = carry as Digit; + carry >>= BITS_PER_DIGIT; + } + } +} + +impl Add for &Int256 { + type Output = (Int256, Digit); + + // Returns sum and carry (0 or 1). + fn add(self, digit: Digit) -> (Int256, Digit) { + let mut digits = [0; NDIGITS]; + let mut carry = digit as DoubleDigit; + + for (i, digit) in digits.iter_mut().enumerate() { + carry += self.digits[i] as DoubleDigit; + *digit = carry as Digit; + carry >>= BITS_PER_DIGIT; + } + + (Int256 { digits }, carry as Digit) + } +} + +/** Substraction with borrow **/ +impl Sub for &Int256 { + type Output = (Int256, Digit); + + // Returns difference and borrow (0 or -1). + fn sub(self, other: &Int256) -> (Int256, Digit) { + let mut digits = [0; NDIGITS]; + let mut borrow: SignedDoubleDigit = 0; + + for (i, digit) in digits.iter_mut().enumerate() { + borrow += + (self.digits[i] as SignedDoubleDigit) - (other.digits[i] as SignedDoubleDigit); + *digit = borrow as Digit; + borrow >>= BITS_PER_DIGIT; + } + + (Int256 { digits }, borrow as Digit) + } +} + +impl SubAssign<&Int256> for Int256 { + // Substract from self, ignoring carry. + fn sub_assign(&mut self, other: &Int256) { + let mut borrow: SignedDoubleDigit = 0; + for i in 0..NDIGITS { + borrow += + (self.digits[i] as SignedDoubleDigit) - (other.digits[i] as SignedDoubleDigit); + self.digits[i] = borrow as Digit; + borrow >>= BITS_PER_DIGIT; + } + } +} + +#[cfg(feature = "derive_debug")] +impl core::fmt::Debug for Int256 { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "Int256 {{ digits: {:08x?} }}", self.digits) + } +} + +#[cfg(test)] +pub mod test { + use super::super::montgomery::Montgomery; + use super::*; + + /** Extra constants for tests **/ + const TWO: Int256 = Int256 { + digits: [2, 0, 0, 0, 0, 0, 0, 0], + }; + const P_MIN_1: Int256 = Int256 { + digits: [ + 0xfffffffe, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0xffffffff, + ], + }; + const P_MIN_2: Int256 = Int256 { + digits: [ + 0xfffffffd, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000001, + 0xffffffff, + ], + }; + + // Generate all 256-bit integers that have exactly one bit set to 1. + pub fn get_1bit_one_test_values() -> Vec { + let mut values = Vec::new(); + for &byte in &[0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80] { + for &int in &[byte, byte << 8, byte << 16, byte << 24] { + values.push(Int256 { + digits: [int, 0, 0, 0, 0, 0, 0, 0], + }); + values.push(Int256 { + digits: [0, int, 0, 0, 0, 0, 0, 0], + }); + values.push(Int256 { + digits: [0, 0, int, 0, 0, 0, 0, 0], + }); + values.push(Int256 { + digits: [0, 0, 0, int, 0, 0, 0, 0], + }); + values.push(Int256 { + digits: [0, 0, 0, 0, int, 0, 0, 0], + }); + values.push(Int256 { + digits: [0, 0, 0, 0, 0, int, 0, 0], + }); + values.push(Int256 { + digits: [0, 0, 0, 0, 0, 0, int, 0], + }); + values.push(Int256 { + digits: [0, 0, 0, 0, 0, 0, 0, int], + }); + } + } + values + } + + // Generate all 256-bit integers that have exactly one bit set to 0. + pub fn get_1bit_zero_test_values() -> Vec { + let values: Vec = get_1bit_one_test_values() + .iter() + .map(|x| { + let mut digits = [Default::default(); NDIGITS]; + for i in 0..NDIGITS { + digits[i] = !x.digits[i]; + } + Int256 { digits } + }) + .collect(); + values + } + + pub fn get_nonzero_test_values() -> Vec { + let mut values: Vec = Montgomery::PRECOMPUTED + .iter() + .flatten() + .flatten() + .map(|x| x.montgomery_to_field().to_int()) + .collect(); + values.append(&mut get_1bit_one_test_values()); + values.append(&mut get_1bit_zero_test_values()); + values.push(Int256::B); + values.push(P_MIN_1); + values.push(P_MIN_2); + values + } + + fn get_test_values() -> Vec { + let mut values = get_nonzero_test_values(); + values.push(Int256::ZERO); + values + } + + #[test] + fn test_1bit_one() { + let values = get_1bit_one_test_values(); + assert_eq!(values.len(), 256); + for x in &values { + assert_eq!(x.hamming_weight(), 1); + } + } + + #[test] + fn test_1bit_zero() { + let values = get_1bit_zero_test_values(); + assert_eq!(values.len(), 256); + for x in &values { + assert_eq!(x.hamming_weight(), 255); + } + } + + /** Serialization **/ + #[test] + fn test_to_bin_from_bin() { + for &x in &get_test_values() { + let mut buf = [Default::default(); NBYTES]; + x.to_bin(&mut buf); + assert_eq!(Int256::from_bin(&buf), x); + } + } + + #[test] + fn test_minimal_encoding_zero() { + let test_int = Int256::ZERO; + let expected_encoding = vec![0x00]; + + assert_eq!(test_int.to_minimal_encoding(), expected_encoding); + } + + #[test] + fn test_minimal_encoding_one() { + let test_int = Int256::ONE; + let expected_encoding = vec![0x01]; + + assert_eq!(test_int.to_minimal_encoding(), expected_encoding); + } + + #[test] + fn test_minimal_encoding_one_full_byte() { + let bytes = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, + ]; + let test_int = Int256::from_bin(&bytes); + let expected_encoding = vec![0x00, 0xFF]; + + assert_eq!(test_int.to_minimal_encoding(), expected_encoding); + } + + #[test] + fn test_minimal_encoding_most_bytes_full() { + let bytes = [ + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ]; + let test_int = Int256::from_bin(&bytes); + let expected_encoding = bytes.to_vec(); + + assert_eq!(test_int.to_minimal_encoding(), expected_encoding); + } + + #[test] + fn test_minimal_encoding_no_leading_byte() { + let bytes = [ + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ]; + let test_int = Int256::from_bin(&bytes); + let expected_encoding = bytes.to_vec(); + + assert_eq!(test_int.to_minimal_encoding(), expected_encoding); + } + + #[test] + fn test_minimal_encoding_with_leading_byte() { + let bytes = [ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + ]; + let test_int = Int256::from_bin(&bytes); + let mut expected_encoding = vec![0x00]; + expected_encoding.extend(&bytes); + + assert_eq!(test_int.to_minimal_encoding(), expected_encoding); + } + + #[test] + fn test_from_bin_is_big_endian_bits_with_little_endian_words() { + let buf = b"\x01\x23\x45\x67\x89\xab\xcd\xef\ + \x12\x34\x56\x78\x9a\xbc\xde\xf0\ + \x23\x45\x67\x89\xab\xcd\xef\x01\ + \x34\x56\x78\x9a\xbc\xde\xf0\x12"; + assert_eq!( + Int256::from_bin(&buf), + Int256 { + digits: [ + 0xbcdef012, 0x3456789a, 0xabcdef01, 0x23456789, 0x9abcdef0, 0x12345678, + 0x89abcdef, 0x01234567, + ] + } + ); + } + + /** Useful getters **/ + #[test] + fn test_is_zero() { + assert!(bool::from(Int256::ZERO.is_zero())); + for x in get_nonzero_test_values() { + assert!(!bool::from(x.is_zero())); + } + } + + #[test] + fn test_is_even() { + assert!(Int256::ZERO.is_even()); + assert!(!Int256::ONE.is_even()); + assert!(TWO.is_even()); + assert!(!Int256::N.is_even()); + assert!(!Int256::P.is_even()); + assert!(!Int256::B.is_even()); + } + + /** Arithmetic operations: bit shifts **/ + #[test] + fn test_shift_zero() { + for i in 1..BITS_PER_DIGIT { + assert_eq!(Int256::ZERO.shl(i), (Int256::ZERO, 0)); + } + for i in 1..BITS_PER_DIGIT { + assert_eq!(Int256::ZERO.shr(i), Int256::ZERO); + } + } + + #[test] + fn test_shifts() { + let mut a = Int256::ONE; + + // Shift left. + for i in 0..255 { + assert_eq!(a.bit(i), 1); + assert!(!bool::from(a.is_zero())); + let (shifted, carry) = a.shl(1); + assert_eq!(carry, 0); + a = shifted; + assert_eq!(a.bit(i), 0); + assert_eq!(a.count_ones(), 1); + } + + assert_eq!(a.bit(255), 1); + assert!(!bool::from(a.is_zero())); + let (shifted, carry) = a.shl(1); + assert_eq!(carry, 1); + assert_eq!(shifted.bit(255), 0); + assert!(bool::from(shifted.is_zero())); + + // Shift right. + for i in (1..256).rev() { + assert_eq!(a.bit(i), 1); + assert!(!bool::from(a.is_zero())); + a = a.shr(1); + assert_eq!(a.bit(i), 0); + assert_eq!(a.count_ones(), 1); + } + + assert_eq!(a.bit(0), 1); + assert!(!bool::from(a.is_zero())); + a = a.shr(1); + assert_eq!(a.bit(0), 0); + assert!(bool::from(a.is_zero())); + } + + #[test] + fn test_shl_shr1() { + for x in &get_test_values() { + let (shifted, carry) = x.shl(1); + assert_eq!(&shifted.shr1(carry), x); + } + } + + #[test] + fn test_shr1_is_shr_one() { + for x in &get_test_values() { + assert_eq!(x.shr(1), x.shr1(0)); + } + for x in &get_test_values() { + let mut y = *x; + for i in 1..BITS_PER_DIGIT { + y = y.shr1(0); + assert_eq!(x.shr(i), y); + } + } + } + + /** Constant-time helpers **/ + #[test] + fn test_set_zero_to_idx() { + let mut tbl = [Int256::ZERO; 15]; + for (i, x) in tbl.iter_mut().enumerate() { + *x = Int256 { + digits: [i as u32; NDIGITS], + }; + } + + for i in 0..16 { + let mut tbl0 = Int256::ZERO; + Int256::set_zero_to_idx(&mut tbl0, &tbl, i as u32); + if i == 0 { + assert_eq!(tbl0, Int256::ONE); + } else { + assert_eq!(tbl0, tbl[i - 1]); + } + } + } + + /** Arithmetic: constant-time conditional addition/substraction **/ + #[test] + fn test_add_conditional() { + for x in &get_test_values() { + for y in &get_test_values() { + let mut z = *x; + let carry = Int256::add_conditional(&mut z.digits, y, 0, Choice::from(0u8)); + assert_eq!(carry, 0); + assert_eq!(z, *x); + let carry = Int256::add_conditional(&mut z.digits, y, 0, Choice::from(1u8)); + assert_eq!((z, carry), x + y); + } + } + } + + #[test] + fn test_sub_conditional() { + for x in &get_test_values() { + for y in &get_test_values() { + let mut z = *x; + let borrow = Int256::sub_conditional(&mut z.digits, y, 0, Choice::from(0u8)); + assert_eq!(bool::from(borrow), false); + assert_eq!(z, *x); + let borrow = Int256::sub_conditional(&mut z.digits, y, 0, Choice::from(1u8)); + assert_eq!((z, Digit::conditional_select(&0, &!0, borrow)), x - y); + } + } + } + + /** Arithmetic operators **/ + #[test] + fn test_add_sub() { + for x in &get_test_values() { + for y in &get_test_values() { + let (sum, carry) = x + y; + let (diff, borrow) = &sum - y; + assert_eq!(diff, *x); + assert_eq!(carry.wrapping_add(borrow), 0); + } + } + } + + #[test] + fn test_sub_add() { + for x in &get_test_values() { + for y in &get_test_values() { + let (diff, borrow) = x - y; + let (sum, carry) = &diff + y; + assert_eq!(sum, *x); + assert_eq!(carry.wrapping_add(borrow), 0); + } + } + } + + /** Arithmetic: modular exponentiation **/ + #[test] + fn test_modpow() { + const MODULUS: Int256 = Int256::P; + for x in &get_test_values() { + let mut result = Int256::ONE; + let mut power = Int256::ZERO; + + // This test is super slow with debug assertions enabled. + #[cfg(not(debug_assertions))] + const ITERATIONS: u32 = 100; + #[cfg(debug_assertions)] + const ITERATIONS: u32 = 5; + + for _ in 0..ITERATIONS { + assert_eq!(x.modpow(&power, &MODULUS), result); + result = Int256::modmul(&result, x, &MODULUS); + power += &Int256::ONE; + } + } + } + + #[test] + fn test_self_times_modinv_is_one() { + const MODULUS: Int256 = Int256::P; + for x in &get_nonzero_test_values() { + let inv = x.modinv_vartime(&MODULUS); + let product = Int256::modmul(&x, &inv, &MODULUS); + assert_eq!(product, Int256::ONE); + } + } + + #[test] + fn test_modinv_modinv() { + const MODULUS: Int256 = Int256::P; + for &x in &get_nonzero_test_values() { + // By construction, this test only works if x is less than the modulus. + if x.compare(&MODULUS) != 0xffffffff { + continue; + } + assert_eq!(x.modinv_vartime(&MODULUS).modinv_vartime(&MODULUS), x); + } + } + + /** Other arithmetic **/ + #[test] + fn test_add_digit() { + for x in &get_test_values() { + for y in &get_test_values() { + for &digit in &y.digits { + assert_eq!( + x + digit, + x + &Int256 { + digits: [digit, 0, 0, 0, 0, 0, 0, 0] + } + ); + } + } + } + } + + #[test] + fn test_add_assign() { + for x in &get_test_values() { + for y in &get_test_values() { + let mut z = *x; + z += y; + assert_eq!(z, (x + y).0); + } + } + } + + #[test] + fn test_sub_assign() { + for x in &get_test_values() { + for y in &get_test_values() { + let mut z = *x; + z -= y; + assert_eq!(z, (x - y).0); + } + } + } + + #[test] + fn test_mul_add() { + for x in &get_test_values() { + for y in &get_test_values() { + let mut result = *x; + let mut carries = 0; + + // This test is super slow with debug assertions enabled. + #[cfg(not(debug_assertions))] + const ITERATIONS: u32 = 1000; + #[cfg(debug_assertions)] + const ITERATIONS: u32 = 5; + + for factor in 0..ITERATIONS { + let mut z = *x; + let ma_carry = Int256::mul_add(&mut z.digits, y, factor); + assert_eq!(ma_carry, carries); + assert_eq!(z, result); + + let (sum, carry) = &result + y; + result = sum; + carries += carry; + } + } + } + } + + /** Comparison between field elements. **/ + #[test] + fn test_compare() { + for x in &get_test_values() { + for y in &get_test_values() { + let cmp = x.compare(y); + assert!(cmp == 0 || cmp == 1 || cmp == 0xffffffff); + assert_eq!(cmp, x.compare_vartime(y)); + } + } + } + + #[test] + fn test_compare_is_reflexive() { + for x in &get_test_values() { + assert_eq!(x.compare(x), 0); + } + } + + #[test] + fn test_compare_is_antisymetric() { + for x in &get_test_values() { + for y in &get_test_values() { + let a = x.compare(y); + let b = y.compare(x); + assert_eq!(a.wrapping_add(b), 0); + } + } + } + + #[test] + fn test_lt() { + for x in &get_test_values() { + for y in &get_test_values() { + let ct_lt = bool::from(x.ct_lt(y)); + let lt = x.compare_vartime(y) == 0xffffffff; + assert_eq!(ct_lt, lt); + } + } + } + + #[test] + fn test_lt_is_antireflexive() { + for x in &get_test_values() { + assert!(!bool::from(x.ct_lt(x))); + } + } + + #[test] + fn test_lt_is_antisymetric() { + for x in &get_test_values() { + for y in &get_test_values() { + let a = x.ct_lt(y).unwrap_u8(); + let b = y.ct_lt(x).unwrap_u8(); + let c = (x == y) as u8; + assert_eq!(a + b + c, 1); + } + } + } + + // TODO: more tests +} diff --git a/libraries/crypto/src/ec/mod.rs b/libraries/crypto/src/ec/mod.rs new file mode 100644 index 0000000..20c469f --- /dev/null +++ b/libraries/crypto/src/ec/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2019 Google LLC +// +// 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. + +pub mod exponent256; +mod gfp256; +pub mod int256; +mod montgomery; +pub mod point; +mod precomputed; diff --git a/libraries/crypto/src/ec/montgomery.rs b/libraries/crypto/src/ec/montgomery.rs new file mode 100644 index 0000000..c197b52 --- /dev/null +++ b/libraries/crypto/src/ec/montgomery.rs @@ -0,0 +1,1109 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::gfp256::GFP256; +use super::int256::Int256; +use super::precomputed; +use core::ops::{Add, Mul, Sub}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; + +pub const NLIMBS: usize = 9; +pub const BOTTOM_28_BITS: u32 = 0x0fff_ffff; +pub const BOTTOM_29_BITS: u32 = 0x1fff_ffff; + +/** Field element on the secp256r1 curve, represented in Montgomery form **/ +#[derive(Clone, Copy)] +pub struct Montgomery { + // The 9 limbs use 28 or 29 bits, alternatively: even limbs use 29 bits, odd limbs use 28 bits. + // The Montgomery form stores a field element x as (x * 2^257) mod P. + pub limbs: [u32; NLIMBS], +} + +impl ConditionallySelectable for Montgomery { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + let mut limbs = [0; NLIMBS]; + for (i, limb) in limbs.iter_mut().enumerate() { + *limb = u32::conditional_select(&a.limbs[i], &b.limbs[i], choice); + } + Self { limbs } + } +} + +#[allow(clippy::unreadable_literal)] +impl Montgomery { + /** Constants for the secp256r1 field **/ + pub const ZERO: Montgomery = Montgomery { + limbs: [ + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, + ], + }; + pub const ONE: Montgomery = Montgomery { + limbs: [ + 0x00000002, 0x00000000, 0x00000000, 0x0ffff800, 0x1fffffff, 0x0fffffff, 0x1fbfffff, + 0x01ffffff, 0x00000000, + ], + }; + pub const A: Montgomery = Montgomery { + limbs: [ + 0x1ffffff8, 0x0fffffff, 0x1fffffff, 0x10001fff, 0x1fffffff, 0x0fffffff, 0x20ffffff, + 0x07ffffff, 0x1fffffff, + ], + }; + pub const B: Montgomery = Montgomery { + limbs: [ + 0x13897bbf, 0x09cdf622, 0x043090d8, 0x002e67c4, 0x176b5678, 0x02afdc84, 0x0d196888, + 0x0b090e90, 0x0b8600c3, + ], + }; + pub const THREE_B: Montgomery = Montgomery { + limbs: [ + 0x1a9c733f, 0x0d69e267, 0x0c91b289, 0x108b2f4c, 0x26420367, 0x180f958d, 0x270c3997, + 0x031b2bb0, 0x0292024b, + ], + }; + const P: Montgomery = Montgomery { + limbs: [ + 0x1fffffff, 0x0fffffff, 0x1fffffff, 0x000003ff, 0x00000000, 0x00000000, 0x00200000, + 0x0f000000, 0x0fffffff, + ], + }; + const TWO_P: Montgomery = Montgomery { + limbs: [ + 0x1ffffffe, 0x0fffffff, 0x1fffffff, 0x000007ff, 0x00000000, 0x00000000, 0x00400000, + 0x0e000000, 0x1fffffff, + ], + }; + // A constant equal to 0 mod p. This is used to implement substraction arithmetic. + const ZERO31: Montgomery = Montgomery { + limbs: [ + (1 << 31) - (1 << 3), + (1 << 30) - (1 << 2), + (1 << 31) - (1 << 2), + (1 << 30) + (1 << 13) - (1 << 2), + (1 << 31) - (1 << 2), + (1 << 30) - (1 << 2), + (1 << 31) + (1 << 24) - (1 << 2), + (1 << 30) - (1 << 27) - (1 << 2), + (1 << 31) - (1 << 2), + ], + }; + + /** Precomputed multiples of the base point of the elliptic curve **/ + pub const fn new(limbs: [u32; NLIMBS]) -> Montgomery { + Montgomery { limbs } + } + + // This contains two tables of 15 points, each represented by its x and y coordinates in + // Montgomery form. + pub const PRECOMPUTED: [[[Montgomery; 2]; 15]; 2] = precomputed::PRECOMPUTED; + + /** Conversion to/from Montgomery form **/ + pub fn field_to_montgomery(gf: &GFP256) -> Montgomery { + let mut limbs = [0; NLIMBS]; + + let mut shifted = (gf * &GFP256::R).to_int(); + for (i, limb) in limbs.iter_mut().enumerate() { + if i & 1 == 0 { + *limb = shifted.digit(0) & BOTTOM_29_BITS; + shifted = shifted.shr(29); + } else { + *limb = shifted.digit(0) & BOTTOM_28_BITS; + shifted = shifted.shr(28); + } + } + + Montgomery { limbs } + } + + pub fn montgomery_to_field(&self) -> GFP256 { + let (mut result, _) = Int256::ZERO.add(self.limbs[NLIMBS - 1]); + let mut top = 0; + + for j in 0..=(NLIMBS - 2) { + let i = NLIMBS - 2 - j; + + let shift = if i & 1 == 0 { 29 } else { 28 }; + let (tmp, top1) = result.shl(shift); + + let (r, top2) = tmp.add(self.limbs[i]); + result = r; + top = top1 | top2; + } + + GFP256::R_INV.mul_top(&result, top) + } + + /** Useful getters **/ + #[inline(always)] + fn get64(&self, i: usize) -> u64 { + self.limbs[i] as u64 + } + + /** Advanced arithmetic **/ + // Squaring. + pub fn square(&self) -> Montgomery { + let mut big_limbs: [u64; 17] = [0; 17]; + + big_limbs[0] = self.get64(0) * self.get64(0); + big_limbs[1] = self.get64(0) * (self.get64(1) << 1); + big_limbs[2] = self.get64(0) * (self.get64(2) << 1) + self.get64(1) * (self.get64(1) << 1); + big_limbs[3] = self.get64(0) * (self.get64(3) << 1) + self.get64(1) * (self.get64(2) << 1); + big_limbs[4] = self.get64(0) * (self.get64(4) << 1) + + self.get64(1) * (self.get64(3) << 2) + + self.get64(2) * self.get64(2); + big_limbs[5] = self.get64(0) * (self.get64(5) << 1) + + self.get64(1) * (self.get64(4) << 1) + + self.get64(2) * (self.get64(3) << 1); + big_limbs[6] = self.get64(0) * (self.get64(6) << 1) + + self.get64(1) * (self.get64(5) << 2) + + self.get64(2) * (self.get64(4) << 1) + + self.get64(3) * (self.get64(3) << 1); + big_limbs[7] = self.get64(0) * (self.get64(7) << 1) + + self.get64(1) * (self.get64(6) << 1) + + self.get64(2) * (self.get64(5) << 1) + + self.get64(3) * (self.get64(4) << 1); + big_limbs[8] = self.get64(0) * (self.get64(8) << 1) + + self.get64(1) * (self.get64(7) << 2) + + self.get64(2) * (self.get64(6) << 1) + + self.get64(3) * (self.get64(5) << 2) + + self.get64(4) * self.get64(4); + big_limbs[9] = self.get64(1) * (self.get64(8) << 1) + + self.get64(2) * (self.get64(7) << 1) + + self.get64(3) * (self.get64(6) << 1) + + self.get64(4) * (self.get64(5) << 1); + big_limbs[10] = self.get64(2) * (self.get64(8) << 1) + + self.get64(3) * (self.get64(7) << 2) + + self.get64(4) * (self.get64(6) << 1) + + self.get64(5) * (self.get64(5) << 1); + big_limbs[11] = self.get64(3) * (self.get64(8) << 1) + + self.get64(4) * (self.get64(7) << 1) + + self.get64(5) * (self.get64(6) << 1); + big_limbs[12] = self.get64(4) * (self.get64(8) << 1) + + self.get64(5) * (self.get64(7) << 2) + + self.get64(6) * self.get64(6); + big_limbs[13] = self.get64(5) * (self.get64(8) << 1) + self.get64(6) * (self.get64(7) << 1); + big_limbs[14] = self.get64(6) * (self.get64(8) << 1) + self.get64(7) * (self.get64(7) << 1); + big_limbs[15] = self.get64(7) * (self.get64(8) << 1); + big_limbs[16] = self.get64(8) * self.get64(8); + + Montgomery::reduce_degree(&big_limbs) + } + + // Modular inverse. + pub fn inv(&self) -> Montgomery { + let mut tmp = self.square(); + tmp = &tmp * self; + let e2 = tmp; // 2^2 - 2^0 + + tmp = tmp.square(); + tmp = tmp.square(); + tmp = &tmp * &e2; + let e4 = tmp; // 2^4 - 2^0 + + for _ in 0..4 { + tmp = tmp.square(); + } + tmp = &tmp * &e4; + let e8 = tmp; // 2^8 - 2^0 + + for _ in 0..8 { + tmp = tmp.square(); + } + tmp = &tmp * &e8; + let e16 = tmp; // 2^16 - 2^0 + + for _ in 0..16 { + tmp = tmp.square(); + } + tmp = &tmp * &e16; + let e32 = tmp; // 2^32 - 2^0 + + for _ in 0..32 { + tmp = tmp.square(); + } + let e64 = tmp; // 2^64 - 2^32 + tmp = &tmp * self; + + for _ in 0..192 { + tmp = tmp.square(); + } // 2^256 - 2^224 + 2^192 + + // 2^64 - 2^0 + let mut tmp2 = &e64 * &e32; + for _ in 0..16 { + tmp2 = tmp2.square(); + } + // 2^80 - 2^0 + tmp2 = &tmp2 * &e16; + for _ in 0..8 { + tmp2 = tmp2.square(); + } + // 2^88 - 2^0 + tmp2 = &tmp2 * &e8; + for _ in 0..4 { + tmp2 = tmp2.square(); + } + // 2^92 - 2^0 + tmp2 = &tmp2 * &e4; + tmp2 = tmp2.square(); + tmp2 = tmp2.square(); + // 2^94 - 2^0 + tmp2 = &tmp2 * &e2; + tmp2 = tmp2.square(); + tmp2 = tmp2.square(); + // 2^96 - 3 + tmp2 = &tmp2 * self; + + // 2^256 - 2^224 + 2^192 + 2^96 - 3 + &tmp2 * &tmp + } + + // Multiplication by 2. + pub fn mul_scalar2(&mut self) { + let mut carry = 0; + + let mut i = 0; + loop { + let next_carry = self.limbs[i] >> 28; + self.limbs[i] <<= 1; + self.limbs[i] &= BOTTOM_29_BITS; + self.limbs[i] += carry; + carry = next_carry + (self.limbs[i] >> 29); + self.limbs[i] &= BOTTOM_29_BITS; + + i += 1; + if i == NLIMBS { + break; + } + + let next_carry = self.limbs[i] >> 27; + self.limbs[i] <<= 1; + self.limbs[i] &= BOTTOM_28_BITS; + self.limbs[i] += carry; + carry = next_carry + (self.limbs[i] >> 28); + self.limbs[i] &= BOTTOM_28_BITS; + + i += 1; + } + + self.reduce_carry(carry); + } + + // Multiplication by 3. + pub fn mul_scalar3(&mut self) { + let mut carry = 0; + + let mut i = 0; + loop { + self.limbs[i] *= 3; + self.limbs[i] += carry; + carry = self.limbs[i] >> 29; + self.limbs[i] &= BOTTOM_29_BITS; + + i += 1; + if i == NLIMBS { + break; + } + + self.limbs[i] *= 3; + self.limbs[i] += carry; + carry = self.limbs[i] >> 28; + self.limbs[i] &= BOTTOM_28_BITS; + + i += 1; + } + + self.reduce_carry(carry); + } + + // Multiplication by 4. + pub fn mul_scalar4(&mut self) { + let mut carry = 0; + + let mut i = 0; + loop { + let next_carry = self.limbs[i] >> 27; + self.limbs[i] <<= 2; + self.limbs[i] &= BOTTOM_29_BITS; + self.limbs[i] += carry; + carry = next_carry + (self.limbs[i] >> 29); + self.limbs[i] &= BOTTOM_29_BITS; + + i += 1; + if i == NLIMBS { + break; + } + + let next_carry = self.limbs[i] >> 26; + self.limbs[i] <<= 2; + self.limbs[i] &= BOTTOM_28_BITS; + self.limbs[i] += carry; + carry = next_carry + (self.limbs[i] >> 28); + self.limbs[i] &= BOTTOM_28_BITS; + + i += 1; + } + + self.reduce_carry(carry); + } + + // Multiplication by 8. + pub fn mul_scalar8(&mut self) { + let mut carry = 0; + + let mut i = 0; + loop { + let next_carry = self.limbs[i] >> 26; + self.limbs[i] <<= 3; + self.limbs[i] &= BOTTOM_29_BITS; + self.limbs[i] += carry; + carry = next_carry + (self.limbs[i] >> 29); + self.limbs[i] &= BOTTOM_29_BITS; + + i += 1; + if i == NLIMBS { + break; + } + + let next_carry = self.limbs[i] >> 25; + self.limbs[i] <<= 3; + self.limbs[i] &= BOTTOM_28_BITS; + self.limbs[i] += carry; + carry = next_carry + (self.limbs[i] >> 28); + self.limbs[i] &= BOTTOM_28_BITS; + + i += 1; + } + + self.reduce_carry(carry); + } + + /** Comparison **/ + pub fn is_zero_vartime(&self) -> bool { + // Reduce to a minimal form. + let tmp = self.reduced_vartime(); + + tmp.limbs == Montgomery::ZERO.limbs + || tmp.limbs == Montgomery::P.limbs + || tmp.limbs == Montgomery::TWO_P.limbs + } + + fn reduced_vartime(&self) -> Montgomery { + let mut reduced = *self; + + // Reduce to a minimal form. + loop { + let mut carry = 0; + let mut i = 0; + loop { + reduced.limbs[i] += carry; + carry = reduced.limbs[i] >> 29; + reduced.limbs[i] &= BOTTOM_29_BITS; + + i += 1; + if i == NLIMBS { + break; + } + + reduced.limbs[i] += carry; + carry = reduced.limbs[i] >> 28; + reduced.limbs[i] &= BOTTOM_28_BITS; + + i += 1; + } + + if carry == 0 { + break; + } + reduced.reduce_carry(carry); + } + + reduced + } + + /** Reduction of saturated limbs **/ + // Adds a multiple of p in order to cancel |carry|, which is a term at 2**257. + // On entry: carry < 2**3, self[0,2,...] < 2**29, self[1,3,...] < 2**28. + // On exit: self[0,2,..] < 2**30, self[1,3,...] < 2**29. + fn reduce_carry(&mut self, carry: u32) { + let carry_choice = carry.ct_eq(&0); + self.limbs[0] += carry << 1; + self.limbs[3] += u32::conditional_select(&0x10000000, &0, carry_choice); + self.limbs[3] -= carry << 11; + self.limbs[4] += u32::conditional_select(&(0x20000000 - 1), &0, carry_choice); + self.limbs[5] += u32::conditional_select(&(0x10000000 - 1), &0, carry_choice); + self.limbs[6] += u32::conditional_select(&(0x20000000 - 1), &0, carry_choice); + self.limbs[6] -= carry << 22; + self.limbs[7] += carry << 25; + self.limbs[7] -= u32::conditional_select(&1, &0, carry_choice); + } + + // Reduce the output of a multiplication or squaring. + fn reduce_degree(big_limbs: &[u64; 17]) -> Montgomery { + let mut limbs: [u32; 18] = Montgomery::propagate_carry(big_limbs); + Montgomery::eliminate_terms(&mut limbs); + Montgomery::compact_limbs(limbs) + } + + // Helper function for reduce_degree(). + // Converts 17 saturated 64-bit limbs to 18 unsaturated limbs of 28 or 29 bits. + fn propagate_carry(big_limbs: &[u64; 17]) -> [u32; 18] { + let mut limbs: [u32; 18] = [0; 18]; + + limbs[0] = (big_limbs[0] as u32) & BOTTOM_29_BITS; + limbs[1] = (big_limbs[0] as u32) >> 29; + limbs[1] |= (((big_limbs[0] >> 32) as u32) << 3) & BOTTOM_28_BITS; + limbs[1] += (big_limbs[1] as u32) & BOTTOM_28_BITS; + let mut carry = limbs[1] >> 28; + limbs[1] &= BOTTOM_28_BITS; + + let mut i = 2; + loop { + limbs[i] = ((big_limbs[i - 2] >> 32) as u32) >> 25; + limbs[i] += (big_limbs[i - 1] as u32) >> 28; + limbs[i] += (((big_limbs[i - 1] >> 32) as u32) << 4) & BOTTOM_29_BITS; + limbs[i] += (big_limbs[i] as u32) & BOTTOM_29_BITS; + limbs[i] += carry; + carry = limbs[i] >> 29; + limbs[i] &= BOTTOM_29_BITS; + + i += 1; + if i == 17 { + break; + } + + limbs[i] = ((big_limbs[i - 2] >> 32) as u32) >> 25; + limbs[i] += (big_limbs[i - 1] as u32) >> 29; + limbs[i] += (((big_limbs[i - 1] >> 32) as u32) << 3) & BOTTOM_28_BITS; + limbs[i] += (big_limbs[i] as u32) & BOTTOM_28_BITS; + limbs[i] += carry; + carry = limbs[i] >> 28; + limbs[i] &= BOTTOM_28_BITS; + + i += 1; + } + limbs[17] = ((big_limbs[15] >> 32) as u32) >> 25; + limbs[17] += (big_limbs[16] as u32) >> 29; + limbs[17] += ((big_limbs[16] >> 32) as u32) << 3; + limbs[17] += carry; + + limbs + } + + // Helper function for reduce_degree(). + // Montgomery elimination of terms. + fn eliminate_terms(limbs: &mut [u32; 18]) { + let mut i = 0; + loop { + limbs[i + 1] += limbs[i] >> 29; + let x = limbs[i] & BOTTOM_29_BITS; + let choice = x.ct_eq(&0); + limbs[i] = 0; + + limbs[i + 3] += (x << 10) & BOTTOM_28_BITS; + limbs[i + 4] += x >> 18; + + limbs[i + 6] += (x << 21) & BOTTOM_29_BITS; + limbs[i + 7] += x >> 8; + + limbs[i + 7] += u32::conditional_select(&0x10000000, &0, choice); + limbs[i + 8] += u32::conditional_select(&x.wrapping_sub(1), &0, choice); + limbs[i + 7] -= (x << 24) & BOTTOM_28_BITS; + limbs[i + 8] -= x >> 4; + + limbs[i + 8] += u32::conditional_select(&0x20000000, &0, choice); + limbs[i + 8] -= x; + limbs[i + 8] += (x << 28) & BOTTOM_29_BITS; + limbs[i + 9] = limbs[i + 9].wrapping_add(u32::conditional_select( + &(x >> 1).wrapping_sub(1), + &0, + choice, + )); + + if i + 1 == NLIMBS { + break; + } + + limbs[i + 2] += limbs[i + 1] >> 28; + let x = limbs[i + 1] & BOTTOM_28_BITS; + let choice = x.ct_eq(&0); + limbs[i + 1] = 0; + + limbs[i + 4] += (x << 11) & BOTTOM_29_BITS; + limbs[i + 5] += x >> 18; + + limbs[i + 7] += (x << 21) & BOTTOM_28_BITS; + limbs[i + 8] += x >> 7; + + limbs[i + 8] += u32::conditional_select(&0x20000000, &0, choice); + limbs[i + 9] += u32::conditional_select(&x.wrapping_sub(1), &0, choice); + limbs[i + 8] -= (x << 25) & BOTTOM_29_BITS; + limbs[i + 9] -= x >> 4; + + limbs[i + 9] += u32::conditional_select(&0x10000000, &0, choice); + limbs[i + 9] -= x; + limbs[i + 10] += u32::conditional_select(&x.wrapping_sub(1), &0, choice); + + i += 2; + } + } + + // Helper function for reduce_degree(). + // Extract the final limbs from Montgomery-eliminated terms. + fn compact_limbs(tmp: [u32; 18]) -> Montgomery { + let mut limbs = [0; NLIMBS]; + let mut carry = 0; + let mut i = 0; + loop { + limbs[i] = tmp[i + 9]; + limbs[i] += carry; + limbs[i] += (tmp[i + 10] << 28) & BOTTOM_29_BITS; + carry = limbs[i] >> 29; + limbs[i] &= BOTTOM_29_BITS; + + i += 1; + + limbs[i] = tmp[i + 9] >> 1; + limbs[i] += carry; + carry = limbs[i] >> 28; + limbs[i] &= BOTTOM_28_BITS; + + i += 1; + if i == 8 { + break; + } + } + + limbs[8] = tmp[17]; + limbs[8] = limbs[8].wrapping_add(carry); + carry = limbs[8] >> 29; + limbs[8] &= BOTTOM_29_BITS; + + let mut result = Montgomery { limbs }; + result.reduce_carry(carry); + result + } +} + +/** Arithmetic operators **/ +// Clippy warns when it sees a subtraction being done in Add implementation +// which here is completely fine. +#[allow(clippy::suspicious_arithmetic_impl)] +impl Add for &Montgomery { + type Output = Montgomery; + + fn add(self, other: &Montgomery) -> Montgomery { + let mut carry = 0; + let mut limbs = [0; NLIMBS]; + + let mut i = 0; + loop { + limbs[i] = self.limbs[i] + other.limbs[i]; + limbs[i] += carry; + carry = limbs[i] >> 29; + limbs[i] &= BOTTOM_29_BITS; + + i += 1; + if i == NLIMBS { + break; + } + + limbs[i] = self.limbs[i] + other.limbs[i]; + limbs[i] += carry; + carry = limbs[i] >> 28; + limbs[i] &= BOTTOM_28_BITS; + + i += 1; + } + + let mut result = Montgomery { limbs }; + result.reduce_carry(carry); + result + } +} + +#[allow(clippy::suspicious_arithmetic_impl)] +// Clippy warns when it sees an addition being done in Sub implementation +// which here is completely fine. +impl Sub for &Montgomery { + type Output = Montgomery; + + fn sub(self, other: &Montgomery) -> Montgomery { + let mut carry = 0; + let mut limbs = [0; NLIMBS]; + + let mut i = 0; + loop { + limbs[i] = self.limbs[i] + (Montgomery::ZERO31.limbs[i] - other.limbs[i]); + limbs[i] += carry; + carry = limbs[i] >> 29; + limbs[i] &= BOTTOM_29_BITS; + + i += 1; + if i == NLIMBS { + break; + } + + limbs[i] = self.limbs[i] + (Montgomery::ZERO31.limbs[i] - other.limbs[i]); + limbs[i] += carry; + carry = limbs[i] >> 28; + limbs[i] &= BOTTOM_28_BITS; + + i += 1; + } + + let mut result = Montgomery { limbs }; + result.reduce_carry(carry); + result + } +} + +impl Mul for &Montgomery { + type Output = Montgomery; + + fn mul(self, other: &Montgomery) -> Montgomery { + let mut big_limbs: [u64; 17] = [0; 17]; + + big_limbs[0] = self.get64(0) * other.get64(0); + big_limbs[1] = self.get64(0) * other.get64(1) + self.get64(1) * other.get64(0); + big_limbs[2] = self.get64(0) * other.get64(2) + + self.get64(1) * (other.get64(1) << 1) + + self.get64(2) * other.get64(0); + big_limbs[3] = self.get64(0) * other.get64(3) + + self.get64(1) * other.get64(2) + + self.get64(2) * other.get64(1) + + self.get64(3) * other.get64(0); + big_limbs[4] = self.get64(0) * other.get64(4) + + self.get64(1) * (other.get64(3) << 1) + + self.get64(2) * other.get64(2) + + self.get64(3) * (other.get64(1) << 1) + + self.get64(4) * other.get64(0); + big_limbs[5] = self.get64(0) * other.get64(5) + + self.get64(1) * other.get64(4) + + self.get64(2) * other.get64(3) + + self.get64(3) * other.get64(2) + + self.get64(4) * other.get64(1) + + self.get64(5) * other.get64(0); + big_limbs[6] = self.get64(0) * other.get64(6) + + self.get64(1) * (other.get64(5) << 1) + + self.get64(2) * other.get64(4) + + self.get64(3) * (other.get64(3) << 1) + + self.get64(4) * other.get64(2) + + self.get64(5) * (other.get64(1) << 1) + + self.get64(6) * other.get64(0); + big_limbs[7] = self.get64(0) * other.get64(7) + + self.get64(1) * other.get64(6) + + self.get64(2) * other.get64(5) + + self.get64(3) * other.get64(4) + + self.get64(4) * other.get64(3) + + self.get64(5) * other.get64(2) + + self.get64(6) * other.get64(1) + + self.get64(7) * other.get64(0); + big_limbs[8] = self.get64(0) * other.get64(8) + + self.get64(1) * (other.get64(7) << 1) + + self.get64(2) * other.get64(6) + + self.get64(3) * (other.get64(5) << 1) + + self.get64(4) * other.get64(4) + + self.get64(5) * (other.get64(3) << 1) + + self.get64(6) * other.get64(2) + + self.get64(7) * (other.get64(1) << 1) + + self.get64(8) * other.get64(0); + big_limbs[9] = self.get64(1) * other.get64(8) + + self.get64(2) * other.get64(7) + + self.get64(3) * other.get64(6) + + self.get64(4) * other.get64(5) + + self.get64(5) * other.get64(4) + + self.get64(6) * other.get64(3) + + self.get64(7) * other.get64(2) + + self.get64(8) * other.get64(1); + big_limbs[10] = self.get64(2) * other.get64(8) + + self.get64(3) * (other.get64(7) << 1) + + self.get64(4) * other.get64(6) + + self.get64(5) * (other.get64(5) << 1) + + self.get64(6) * other.get64(4) + + self.get64(7) * (other.get64(3) << 1) + + self.get64(8) * other.get64(2); + big_limbs[11] = self.get64(3) * other.get64(8) + + self.get64(4) * other.get64(7) + + self.get64(5) * other.get64(6) + + self.get64(6) * other.get64(5) + + self.get64(7) * other.get64(4) + + self.get64(8) * other.get64(3); + big_limbs[12] = self.get64(4) * other.get64(8) + + self.get64(5) * (other.get64(7) << 1) + + self.get64(6) * other.get64(6) + + self.get64(7) * (other.get64(5) << 1) + + self.get64(8) * other.get64(4); + big_limbs[13] = self.get64(5) * other.get64(8) + + self.get64(6) * other.get64(7) + + self.get64(7) * other.get64(6) + + self.get64(8) * other.get64(5); + big_limbs[14] = self.get64(6) * other.get64(8) + + self.get64(7) * (other.get64(7) << 1) + + self.get64(8) * other.get64(6); + big_limbs[15] = self.get64(7) * other.get64(8) + self.get64(8) * other.get64(7); + big_limbs[16] = self.get64(8) * other.get64(8); + + Montgomery::reduce_degree(&big_limbs) + } +} + +#[cfg(test)] +pub mod test { + use super::*; + + impl PartialEq for Montgomery { + fn eq(&self, other: &Montgomery) -> bool { + (self - other).is_zero_vartime() + } + } + + impl core::fmt::Debug for Montgomery { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "Montgomery {{ limbs: {:08x?} }}", self.limbs) + } + } + + pub fn get_nonzero_test_values() -> Vec { + let mut values: Vec = Montgomery::PRECOMPUTED + .iter() + .flatten() + .flatten() + .cloned() + .collect(); + values.push(Montgomery::ONE); + values.push(Montgomery::A); + values.push(Montgomery::B); + values.push(Montgomery::THREE_B); + // TODO: Add more test values. + values + } + + fn get_test_values() -> Vec { + let mut values = get_nonzero_test_values(); + values.push(Montgomery::ZERO); + values + } + + /** Constants for the secp256r1 field **/ + #[test] + fn test_zero31_is_zero_mod_p() { + assert!(Montgomery::ZERO31.is_zero_vartime()); + } + + #[test] + fn test_2p() { + assert_eq!( + Montgomery::TWO_P.limbs, + (&Montgomery::P + &Montgomery::P).limbs + ); + } + + #[test] + fn test_a() { + // a == -3 + let mut a = GFP256::ZERO; + a = &a - &GFP256::ONE; + a = &a - &GFP256::ONE; + a = &a - &GFP256::ONE; + assert_eq!(Montgomery::A, Montgomery::field_to_montgomery(&a)); + } + + #[test] + fn test_b() { + assert_eq!(Montgomery::B, Montgomery::field_to_montgomery(&GFP256::B)); + } + + #[test] + fn test_3b() { + let mut b3 = GFP256::B; + b3 = &b3 + &GFP256::B; + b3 = &b3 + &GFP256::B; + assert_eq!(Montgomery::THREE_B, Montgomery::field_to_montgomery(&b3)); + } + + /** Conversion to/from Montgomery form **/ + #[test] + fn test_conversion_round_trip() { + for x in get_test_values() { + assert_eq!(x, Montgomery::field_to_montgomery(&x.montgomery_to_field())); + } + } + + #[test] + fn test_conversion_for_constants() { + assert_eq!( + Montgomery::ZERO.limbs, + Montgomery::field_to_montgomery(&GFP256::ZERO).limbs + ); + assert_eq!( + Montgomery::ONE.limbs, + Montgomery::field_to_montgomery(&GFP256::ONE).limbs + ); + } + + /** Constant-time helpers **/ + + /** Arithmetic operators **/ + #[test] + fn test_add_is_associative() { + for x in &get_test_values() { + for y in &get_test_values() { + for z in &get_test_values() { + assert_eq!(&(x + y) + z, x + &(y + z)); + } + } + } + } + + #[test] + fn test_add_is_commutative() { + for x in &get_test_values() { + for y in &get_test_values() { + assert_eq!(x + y, y + x); + } + } + } + + #[test] + fn test_add_sub() { + for x in &get_test_values() { + for y in &get_test_values() { + assert_eq!(&(x - y) + y, *x); + assert_eq!(&(x + y) - y, *x); + } + } + } + + #[test] + fn test_mul_is_associative() { + for x in &get_test_values() { + for y in &get_test_values() { + for z in &get_test_values() { + assert_eq!(&(x * y) * z, x * &(y * z)); + } + } + } + } + + #[test] + fn test_mul_is_commutative() { + for x in &get_test_values() { + for y in &get_test_values() { + assert_eq!(x * y, y * x); + } + } + } + + #[test] + fn test_mul_is_distributive() { + for x in &get_test_values() { + for y in &get_test_values() { + for z in &get_test_values() { + assert_eq!(&(x + y) * z, &(x * z) + &(y * z)); + } + } + } + } + + /** Advanced arithmetic **/ + #[test] + fn test_square_is_mul_self() { + for x in &get_test_values() { + let multiplied = x * x; + let squared = x.square(); + assert_eq!(multiplied, squared); + } + } + + #[test] + fn test_self_times_inv_is_one() { + for x in &get_nonzero_test_values() { + let inv = x.inv(); + let product = x * &inv; + assert_eq!(product, Montgomery::ONE); + } + } + + #[test] + fn test_inv_inv() { + for x in get_nonzero_test_values() { + assert_eq!(x.inv().inv(), x); + } + } + + #[test] + fn test_well_known_inverses() { + assert_eq!(Montgomery::ONE.inv(), Montgomery::ONE); + let p_min_1 = &Montgomery::P - &Montgomery::ONE; + assert_eq!(p_min_1.inv(), p_min_1); + } + + #[test] + fn test_mul_scalar2_from_add() { + for x in get_test_values() { + let mut multiplied = x; + multiplied.mul_scalar2(); + + let added = &x + &x; + + assert_eq!(multiplied, added); + } + } + + #[test] + fn test_mul_scalar2_from_mul() { + let two = &Montgomery::ONE + &Montgomery::ONE; + + for x in get_test_values() { + let mut multiplied = x; + multiplied.mul_scalar2(); + + assert_eq!(multiplied, &x * &two); + } + } + + #[test] + fn test_mul_scalar3_from_add() { + for x in get_test_values() { + let mut multiplied = x; + multiplied.mul_scalar3(); + + let mut added = x; + for _ in 0..2 { + added = &added + &x; + } + + assert_eq!(multiplied, added); + } + } + + #[test] + fn test_mul_scalar3_from_mul() { + let mut three = Montgomery::ONE; + for _ in 0..2 { + three = &three + &Montgomery::ONE; + } + + for x in get_test_values() { + let mut multiplied = x; + multiplied.mul_scalar3(); + + assert_eq!(multiplied, &x * &three); + } + } + + #[test] + fn test_mul_scalar4_from_add() { + for x in get_test_values() { + let mut multiplied = x; + multiplied.mul_scalar4(); + + let mut added = x; + for _ in 0..3 { + added = &added + &x; + } + + assert_eq!(multiplied, added); + } + } + + #[test] + fn test_mul_scalar4_from_mul() { + let mut four = Montgomery::ONE; + for _ in 0..3 { + four = &four + &Montgomery::ONE; + } + + for x in get_test_values() { + let mut multiplied = x; + multiplied.mul_scalar4(); + + assert_eq!(multiplied, &x * &four); + } + } + + #[test] + fn test_mul_scalar8_from_add() { + for x in get_test_values() { + let mut multiplied = x; + multiplied.mul_scalar8(); + + let mut added = x; + for _ in 0..7 { + added = &added + &x; + } + + assert_eq!(multiplied, added); + } + } + + #[test] + fn test_mul_scalar8_from_mul() { + let mut eight = Montgomery::ONE; + for _ in 0..7 { + eight = &eight + &Montgomery::ONE; + } + + for x in get_test_values() { + let mut multiplied = x; + multiplied.mul_scalar8(); + + assert_eq!(multiplied, &x * &eight); + } + } + + /** Comparison **/ + #[test] + fn test_is_zero() { + assert!(Montgomery::ZERO.is_zero_vartime()); + for x in get_nonzero_test_values() { + assert!(!x.is_zero_vartime()); + } + } + + /** Reduction of saturated limbs **/ + #[test] + fn test_reduced_carry_is_one() { + let mut x = Montgomery::ZERO; + x.reduce_carry(1); + assert_eq!(x.limbs, Montgomery::ONE.limbs); + } + + #[test] + fn test_reduce_carry_works_until_8() { + let mut reduced = Montgomery::ZERO; + for i in 0..8 { + let mut x = Montgomery::ZERO; + x.reduce_carry(i); + assert_eq!(x.limbs, reduced.limbs); + reduced = &reduced + &Montgomery::ONE; + } + } + + #[test] + fn test_reduce_no_carry_is_noop() { + for x in get_test_values() { + let mut y = x; + y.reduce_carry(0); + assert_eq!(y.limbs, x.limbs); + } + } + + #[test] + fn test_reduce_degree() { + // TODO: Add a meaningful test for this. + } +} diff --git a/libraries/crypto/src/ec/point.rs b/libraries/crypto/src/ec/point.rs new file mode 100644 index 0000000..d9a6a76 --- /dev/null +++ b/libraries/crypto/src/ec/point.rs @@ -0,0 +1,1034 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::exponent256::ExponentP256; +use super::gfp256::GFP256; +use super::int256::Int256; +use super::montgomery::Montgomery; +use core::ops::Add; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; + +// A point on the elliptic curve is represented by two field elements. +// The "direct" representation with GFP256 (integer modulo p) is used for serialization of public +// keys. +#[derive(Clone, Copy)] +pub struct PointP256 { + x: GFP256, + y: GFP256, +} + +impl PointP256 { + // The point at infinity. + // Although this point is not "valid" on the curve (as it doesn't have an order of N), it is + // useful for tests. + #[cfg(test)] + const INFINITY: PointP256 = PointP256 { + x: GFP256::ZERO, + y: GFP256::ZERO, + }; + + /** Serialization **/ + // This uses uncompressed point format from "SEC 1: Elliptic Curve Cryptography" ("Standards for + // Efficient Cryptography"). + #[cfg(feature = "std")] + pub fn from_bytes_uncompressed_vartime(bytes: &[u8]) -> Option { + if bytes.len() != 65 || bytes[0] != 0x04 { + None + } else { + PointP256::new_checked_vartime( + Int256::from_bin(array_ref![bytes, 1, 32]), + Int256::from_bin(array_ref![bytes, 33, 32]), + ) + } + } + + #[cfg(test)] + pub fn to_bytes_uncompressed(&self, bytes: &mut [u8; 65]) { + bytes[0] = 0x04; + self.x.to_int().to_bin(array_mut_ref![bytes, 1, 32]); + self.y.to_int().to_bin(array_mut_ref![bytes, 33, 32]); + } + + /** Constructors **/ + pub fn new_checked_vartime(x: Int256, y: Int256) -> Option { + let gfx = GFP256::from_int_checked(x)?; + let gfy = GFP256::from_int_checked(y)?; + if GFP256::is_valid_point_vartime(&gfx, &gfy) { + Some(PointP256 { x: gfx, y: gfy }) + } else { + None + } + } + + fn from_projective(point: &PointProjective) -> PointP256 { + PointP256::from_affine(&point.to_affine()) + } + + fn from_affine(affine: &PointAffine) -> PointP256 { + PointP256 { + x: affine.x.montgomery_to_field(), + y: affine.y.montgomery_to_field(), + } + } + + fn to_affine(&self) -> PointAffine { + PointAffine { + x: Montgomery::field_to_montgomery(&self.x), + y: Montgomery::field_to_montgomery(&self.y), + } + } + + /** Useful getters **/ + #[cfg(test)] + pub fn is_valid_vartime(&self) -> bool { + GFP256::is_valid_point_vartime(&self.x, &self.y) + } + + pub fn getx(self) -> GFP256 { + self.x + } + + pub fn gety(self) -> GFP256 { + self.y + } + + /** Arithmetic **/ + pub fn base_point_mul(n: &ExponentP256) -> PointP256 { + let point = PointProjective::scalar_base_mul(n); + PointP256::from_projective(&point) + } + + pub fn mul(&self, n: &ExponentP256) -> PointP256 { + let p = self.to_affine(); + let point = p.scalar_mul(n); + PointP256::from_projective(&point) + } + + // Computes n1*G + n2*self + #[cfg(feature = "std")] + pub fn points_mul(&self, n1: &ExponentP256, n2: &ExponentP256) -> PointP256 { + let p = self.to_affine(); + let p1 = PointProjective::scalar_base_mul(n1); + let p2 = p.scalar_mul(n2); + + let point = &p1 + &p2; + PointP256::from_projective(&point) + } +} + +// A point on the elliptic curve in projective form. +// This uses Montgomery representation for field elements. +// This is in projective coordinates, i.e. it represents the point { x: x / z, y: y / z }. +// This representation is more convenient to implement complete formulas for elliptic curve +// arithmetic. +#[derive(Clone, Copy)] +pub struct PointProjective { + x: Montgomery, + y: Montgomery, + z: Montgomery, +} + +impl ConditionallySelectable for PointProjective { + #[allow(clippy::many_single_char_names)] + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + let x = Montgomery::conditional_select(&a.x, &b.x, choice); + let y = Montgomery::conditional_select(&a.y, &b.y, choice); + let z = Montgomery::conditional_select(&a.z, &b.z, choice); + Self { x, y, z } + } +} + +// Equivalent to PointProjective { x, y, z: 1 } +#[derive(Clone, Copy)] +pub struct PointAffine { + x: Montgomery, + y: Montgomery, +} + +impl PointProjective { + #[cfg(test)] + pub const INFINITY: PointProjective = PointProjective { + x: Montgomery::ZERO, + y: Montgomery::ONE, + z: Montgomery::ZERO, + }; + + /** Constructors **/ + pub fn from_affine(point: &PointAffine) -> PointProjective { + PointProjective { + x: point.x, + y: point.y, + z: Montgomery::ONE, + } + } + + #[cfg(test)] + // Construct a point in projective coordinates, with a given z value. + // This point is equivalent to { x, y, z: 1 } + pub fn from_affine_shuffled(point: &PointAffine, z: Montgomery) -> PointProjective { + PointProjective { + x: &point.x * &z, + y: &point.y * &z, + z, + } + } + + fn to_affine(&self) -> PointAffine { + let zinv = self.z.inv(); + let x = &self.x * &zinv; + let y = &self.y * &zinv; + PointAffine { x, y } + } + + /** Constant-time helpers **/ + fn select_point(table: &[PointProjective; 15], index: u32) -> PointProjective { + let mut point = PointProjective { + x: Montgomery::ZERO, + y: Montgomery::ZERO, + z: Montgomery::ZERO, + }; + + for i in 0..15 { + let choice = (i + 1).ct_eq(&index); + point.conditional_assign(&table[i as usize], choice); + } + + point + } + + /** Arithmetic **/ + // Complete formula from https://eprint.iacr.org/2015/1060.pdf, Algorithm 5. + fn add_mixed(&self, other: &PointAffine) -> PointProjective { + // Steps 1-2 (same as add). + let mut t0 = &self.x * &other.x; + let t1 = &self.y * &other.y; + let mut t2 = self.z; + + // Steps 3-7 (same as add). + let t3 = &self.x + &self.y; + let t4 = &other.x + &other.y; + let t3 = &t3 * &t4; + let t4 = &t0 + &t1; + let t3 = &t3 - &t4; + + // Steps 8-11 (add_mixed optimization). + let t4 = &other.y * &self.z; + let t4 = &t4 + &self.y; + let y = &other.x * &self.z; + let y = &y + &self.x; + + // Steps 12-17 (same as add). + let z = &Montgomery::B * &t2; + let mut x = &y - &z; + x.mul_scalar3(); // 14-15 + let z = &t1 - &x; + let x = &t1 + &x; + + // Steps 18-22 (same as add). + let y = &Montgomery::B * &y; + t2.mul_scalar3(); // 19-20 + let y = &y - &t2; + let mut y = &y - &t0; + + // Steps 23-27 (same as add). + y.mul_scalar3(); // 23-24 + t0.mul_scalar3(); // 25-26 + let t0 = &t0 - &t2; + + // Steps 28-36 (same as add). + let t1 = &t4 * &y; + let t2 = &t0 * &y; + let y = &x * &z; + let y = &y + &t2; + let x = &t3 * &x; + let x = &x - &t1; + let z = &t4 * &z; + let t1 = &t3 * &t0; + let z = &z + &t1; + + PointProjective { x, y, z } + } + + // Complete formula from https://eprint.iacr.org/2015/1060.pdf, Algorithm 6. + fn double(&self) -> PointProjective { + // Steps 1-3 (same as add). + let mut t0 = self.x.square(); + let t1 = self.y.square(); + let mut t2 = self.z.square(); + + // Steps 4-7. + let mut t3 = &self.x * &self.y; + t3.mul_scalar2(); + let mut z = &self.x * &self.z; + z.mul_scalar2(); + + // Steps 8-13 (same as add). + let y = &Montgomery::B * &t2; + let mut y = &y - &z; + y.mul_scalar3(); // 10-11 + let x = &t1 - &y; + let y = &t1 + &y; + + // Steps 14-15. + let y = &x * &y; + let x = &x * &t3; + + // Steps 16-20 (same as add). + t2.mul_scalar3(); // 16-17 + let z = &Montgomery::B * &z; + let z = &z - &t2; + let mut z = &z - &t0; + + // Steps 21-26 (same as add). + z.mul_scalar3(); // 21-22 + t0.mul_scalar3(); // 23-24 + let t0 = &t0 - &t2; + + // Steps 27-34. + let t0 = &t0 * &z; + let y = &y + &t0; + let mut t0 = &self.y * &self.z; + t0.mul_scalar2(); + let z = &t0 * &z; + let x = &x - &z; + let mut z = &t0 * &t1; + z.mul_scalar4(); // 33-34 + + PointProjective { x, y, z } + } + + // Compute scalar*G + fn scalar_base_mul(scalar: &ExponentP256) -> PointProjective { + let mut n = PointProjective { + x: Montgomery::ZERO, + y: Montgomery::ZERO, + z: Montgomery::ZERO, + }; + let mut choice_n_is_inf = Choice::from(1u8); + + for i in 0..32 { + if i != 0 { + n = n.double(); + } + + for table_offset in 0..2 { + let j = 32 * table_offset; + let bit0 = scalar.bit(31 - i + j); + let bit1 = scalar.bit(95 - i + j); + let bit2 = scalar.bit(159 - i + j); + let bit3 = scalar.bit(223 - i + j); + let index = bit0 | (bit1 << 1) | (bit2 << 2) | (bit3 << 3); + + let p = PointAffine::select_point(&Montgomery::PRECOMPUTED[table_offset], index); + let t = n.add_mixed(&p); + + n.conditional_assign(&PointProjective::from_affine(&p), choice_n_is_inf); + + let choice_p_is_inf = index.ct_eq(&0); + n.conditional_assign(&t, !(choice_p_is_inf | choice_n_is_inf)); + + choice_n_is_inf &= choice_p_is_inf; + } + } + + n + } + + // Complete formula from https://eprint.iacr.org/2015/1060.pdf, Algorithm 1. + #[cfg(test)] + fn add_complete_general(self, other: &PointProjective) -> PointProjective { + // Steps 1-3. + let t0 = &self.x * &other.x; + let t1 = &self.y * &other.y; + let t2 = &self.z * &other.z; + + // Steps 4-8. + let t3 = &self.x + &self.y; + let t4 = &other.x + &other.y; + let t3 = &t3 * &t4; + let t4 = &t0 + &t1; + let t3 = &t3 - &t4; + + // Steps 9-13. + let t4 = &self.x + &self.z; + let t5 = &other.x + &other.z; + let t4 = &t4 * &t5; + let t5 = &t0 + &t2; + let t4 = &t4 - &t5; + + // Steps 14-18. + let t5 = &self.y + &self.z; + let x = &other.y + &other.z; + let t5 = &t5 * &x; + let x = &t1 + &t2; + let t5 = &t5 - &x; + + // Steps 19-24. + let z = &Montgomery::A * &t4; + let x = &Montgomery::THREE_B * &t2; + let z = &x + &z; + let x = &t1 - &z; + let z = &t1 + &z; + let y = &x * &z; + + // Steps 25-34. + let t1 = &t0 + &t0; + let t1 = &t1 + &t0; + let t2 = &Montgomery::A * &t2; + let t4 = &Montgomery::THREE_B * &t4; + let t1 = &t1 + &t2; + let t2 = &t0 - &t2; + let t2 = &Montgomery::A * &t2; + let t4 = &t4 + &t2; + let t0 = &t1 * &t4; + let y = &y + &t0; + + // Steps 35-37. + let t0 = &t5 * &t4; + let x = &t3 * &x; + let x = &x - &t0; + + // Steps 38-40. + let t0 = &t3 * &t1; + let z = &t5 * &z; + let z = &z + &t0; + + PointProjective { x, y, z } + } +} + +impl PointAffine { + /** Constant-time helpers **/ + fn select_point(table: &[[Montgomery; 2]; 15], index: u32) -> PointAffine { + let mut x = Montgomery::ZERO; + let mut y = Montgomery::ZERO; + + for i in 0..15 { + let choice = (i + 1).ct_eq(&index); + x.conditional_assign(&table[i as usize][0], choice); + y.conditional_assign(&table[i as usize][1], choice); + } + + PointAffine { x, y } + } + + /** Arithmetic **/ + fn scalar_mul(&self, scalar: &ExponentP256) -> PointProjective { + let mut precomp = [PointProjective { + x: Montgomery::ZERO, + y: Montgomery::ZERO, + z: Montgomery::ZERO, + }; 15]; + + precomp[0] = PointProjective::from_affine(self); + + for i in (1..15).step_by(2) { + precomp[i] = precomp[i >> 1].double(); + precomp[i + 1] = precomp[i].add_mixed(self); + } + + let mut n = PointProjective { + x: Montgomery::ZERO, + y: Montgomery::ZERO, + z: Montgomery::ZERO, + }; + let mut choice_n_is_inf = Choice::from(1u8); + + for i in (0..256).step_by(4) { + if i != 0 { + n = n.double(); + n = n.double(); + n = n.double(); + n = n.double(); + } + let index = scalar.bit(255 - i) << 3 + | scalar.bit(255 - i - 1) << 2 + | scalar.bit(255 - i - 2) << 1 + | scalar.bit(255 - i - 3); + + let p = PointProjective::select_point(&precomp, index); + let t = n.add(&p); + + n.conditional_assign(&p, choice_n_is_inf); + + let choice_p_is_inf = index.ct_eq(&0); + n.conditional_assign(&t, !(choice_p_is_inf | choice_n_is_inf)); + + choice_n_is_inf &= choice_p_is_inf; + } + + n + } +} + +/** Arithmetic operators **/ +#[allow(clippy::suspicious_arithmetic_impl)] +impl Add for &PointProjective { + type Output = PointProjective; + + // Complete formula from https://eprint.iacr.org/2015/1060.pdf, Algorithm 4. + fn add(self, other: &PointProjective) -> PointProjective { + // Steps 1-3. + let mut t0 = &self.x * &other.x; + let t1 = &self.y * &other.y; + let mut t2 = &self.z * &other.z; + + // Steps 4-8. + let t3 = &self.x + &self.y; + let t4 = &other.x + &other.y; + let t3 = &t3 * &t4; + let t4 = &t0 + &t1; + let t3 = &t3 - &t4; + + // Steps 9-13. + let t4 = &self.y + &self.z; + let x = &other.y + &other.z; + let t4 = &t4 * &x; + let x = &t1 + &t2; + let t4 = &t4 - &x; + + // Steps 14-18. + let x = &self.x + &self.z; + let y = &other.x + &other.z; + let x = &x * &y; + let y = &t0 + &t2; + let y = &x - &y; + + // Steps 19-24. + let z = &Montgomery::B * &t2; + let mut x = &y - &z; + x.mul_scalar3(); // 21-22 + let z = &t1 - &x; + let x = &t1 + &x; + + // Steps 25-29. + let y = &Montgomery::B * &y; + t2.mul_scalar3(); // 26-27 + let y = &y - &t2; + let mut y = &y - &t0; + + // Steps 30-34. + y.mul_scalar3(); // 30-31 + t0.mul_scalar3(); // 32-33 + let t0 = &t0 - &t2; + + // Steps 35-43. + let t1 = &t4 * &y; + let t2 = &t0 * &y; + let y = &x * &z; + let y = &y + &t2; + let x = &t3 * &x; + let x = &x - &t1; + let z = &t4 * &z; + let t1 = &t3 * &t0; + let z = &z + &t1; + + PointProjective { x, y, z } + } +} + +#[cfg(feature = "derive_debug")] +impl core::fmt::Debug for PointP256 { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("PointP256") + .field("x", &self.x) + .field("y", &self.y) + .finish() + } +} + +#[cfg(feature = "derive_debug")] +impl PartialEq for PointP256 { + fn eq(&self, other: &PointP256) -> bool { + self.x == other.x && self.y == other.y + } +} + +#[cfg(test)] +pub mod test { + use super::*; + + impl PartialEq for PointAffine { + fn eq(&self, other: &PointAffine) -> bool { + self.x == other.x && self.y == other.y + } + } + + impl core::fmt::Debug for PointAffine { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("PointAffine") + .field("x", &self.x) + .field("y", &self.y) + .finish() + } + } + + impl PartialEq for PointProjective { + fn eq(&self, other: &PointProjective) -> bool { + self.to_affine() == other.to_affine() + } + } + + impl core::fmt::Debug for PointProjective { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("PointProjective") + .field("x", &self.x) + .field("y", &self.y) + .field("z", &self.z) + .finish() + } + } + + pub fn precomputed(i: usize, j: usize) -> PointAffine { + PointAffine { + x: Montgomery::PRECOMPUTED[i][j][0], + y: Montgomery::PRECOMPUTED[i][j][1], + } + } + + fn get_test_values_affine() -> Vec { + let mut values = Vec::new(); + for table in 0..2 { + for index in 0..15 { + values.push(precomputed(table, index)); + } + } + values + } + + fn get_test_values_projective() -> Vec { + let mut values: Vec<_> = get_test_values_affine() + .iter() + .map(|p| PointProjective::from_affine(p)) + .collect(); + values.push(PointProjective::INFINITY); + values + } + + fn get_test_values() -> Vec { + get_test_values_affine() + .iter() + .map(|p| PointP256::from_affine(p)) + .collect() + } + + /** Serialization **/ + #[test] + fn test_to_bytes_from_bytes() { + for &x in &get_test_values() { + let mut buf = [Default::default(); 65]; + x.to_bytes_uncompressed(&mut buf); + assert_eq!(PointP256::from_bytes_uncompressed_vartime(&buf), Some(x)); + } + } + + #[test] + fn test_from_bytes_infinity_is_invalid() { + let mut buf = [0; 65]; + buf[0] = 0x04; + assert_eq!(PointP256::from_bytes_uncompressed_vartime(&buf), None); + } + + /** Conversion between point types **/ + #[test] + fn test_convert_p256_affine() { + for x in &get_test_values_affine() { + assert_eq!(PointP256::from_affine(x).to_affine(), *x); + } + } + + #[test] + fn test_convert_projective_affine() { + for x in &get_test_values_affine() { + assert_eq!(PointProjective::from_affine(x).to_affine(), *x); + } + } + + #[test] + fn test_projective_shuffle() { + for x in &get_test_values_affine() { + for &shuffle in &super::super::montgomery::test::get_nonzero_test_values() { + assert_eq!( + PointProjective::from_affine_shuffled(x, shuffle).to_affine(), + *x + ); + } + } + } + + /** Point validation **/ + // Edge cases generated with the following Sage script. + // + // ``` + // k = GF(2^256 - 2^224 + 2^192 + 2^96 - 1, 't'); + // R = PolynomialRing(k, 'u'); + // u = R.gen() + // b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b + // + // def print_point(x, y): + // print "x = 0x%x" % x + // print "y = 0x%x" % y + // print "x^3 - 3*x + b = 0x%x" % (x^3 - 3*x + b) + // print "y^2 = 0x%x" % y^2 + // + // def find_point_at_x(x): + // f = x^3 - 3*x + b - u^2 + // r = f.roots() + // if len(r) > 0: + // y = r[0][0] + // print_point(x, y) + // + // def find_point_at_y(y): + // f = u^3 - 3*u + b - y^2 + // r = f.roots() + // if len(r) > 0: + // x = r[0][0] + // print_point(x, y) + // + // ITERATIONS = 16 + // + // print "*" * 40 + // print "Small x" + // print "*" * 40 + // for i in range(ITERATIONS): + // x = k(i) + // find_point_at_x(x) + // + // print "*" * 40 + // print "Small y" + // print "*" * 40 + // for i in range(ITERATIONS): + // y = k(i) + // find_point_at_y(y) + // + // print "*" * 40 + // print "High-weight x" + // print "*" * 40 + // for i in range(ITERATIONS): + // x = k(2^255 - 1 - 2^i) + // find_point_at_x(x) + // + // print "*" * 40 + // print "High-weight y" + // print "*" * 40 + // for i in range(ITERATIONS): + // y = k(2^255 - 1 - 2^i) + // find_point_at_y(y) + // ``` + #[rustfmt::skip] + const POINTS_SMALL_X: &[[[u32; 8]; 2]] = &[ +[ + [0x00000005, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], + [0xcdb70433, 0xccbea3f7, 0x9b265c3a, 0xee35afc4, 0x667e8521, 0x016ec431, 0x55a7e7fa, 0xba6dbc45], +], +[ + [0x00000006, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], + [0x7d7ddc34, 0xddf2aed7, 0xf0183f16, 0x232efd48, 0xcc8dffb8, 0xb9967a1a, 0xabdaf53e, 0xc94db3d2], +], +[ + [0x00000008, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], + [0x636041b3, 0x2242d085, 0xd631ff69, 0x27249b16, 0x3f37f6a6, 0xd624d1d2, 0xca290db0, 0xb706288a], +], +[ + [0x00000009, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], + [0x8fe7b297, 0xdb1812e4, 0x3c63a432, 0xfbc52276, 0xd33315cb, 0xcaaa94f7, 0x2caf2e23, 0x8e14e843], +], +[ + [0x0000000c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], + [0xb2b74387, 0x6abbc3b4, 0x9de5be82, 0xc726c883, 0x6c4b6500, 0xc653e363, 0xcb0680c1, 0x93fbd29a], +], + ]; + #[rustfmt::skip] + const POINTS_SMALL_Y: &[[[u32; 8]; 2]] = &[ +[ + [0x0069d2c7, 0x875d877f, 0x7b70f611, 0x6375e8a9, 0x95dbac0d, 0x10db6dd0, 0xab9c6e9e, 0x8d0177eb], + [0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], +], +[ + [0x7607ce12, 0xc9fe1bd7, 0x8b283fbb, 0x53eb03e0, 0xddcaac96, 0xa62f56d3, 0xc6825c8a, 0xcfe9c22c], + [0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], +], +[ + [0xde8de1d7, 0xb176f692, 0x841022ca, 0x4cffaf35, 0xeb345f84, 0x0a92738c, 0x46cd60d8, 0xd7325d76], + [0x00000005, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], +], +[ + [0x87914a78, 0x077d5a71, 0x8e3dc5b2, 0x979131e2, 0x97d7ab3f, 0x731dbdaf, 0x1da31d68, 0x9b21c2de], + [0x00000006, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], +], +[ + [0xd56ac453, 0xd7acceae, 0xc6693e4f, 0xcffa296d, 0xe4df51fc, 0x564d94b7, 0xbc9f7da8, 0xb2ed6eac], + [0x00000007, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000], +], + ]; + #[rustfmt::skip] + const POINTS_HIGH_WEIGHT: &[[[u32; 8]; 2]] = &[ +// High-weight x coordinate. +[ + [0xfffffffe, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xefffffff], + [0xf0e94c90, 0x722ff8d6, 0x66ebf289, 0x9b17896c, 0x334f0e43, 0xc4e1c5d1, 0x1ea63e81, 0xa120f8da], +], +[ + [0xfffffffd, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xefffffff], + [0x5ad8aa4b, 0x717d6de4, 0x2af77820, 0x04ce8429, 0xefb80898, 0xd004a68e, 0xe4b30001, 0x887ce5d3], +], +// High-weight y coordinate. +[ + [0x98619b11, 0xae2e447c, 0x02bcee26, 0x1fcb1b9b, 0xed3ee2d9, 0xefb6ff97, 0x77ee5948, 0xe063d049], + [0xfffffffd, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xefffffff], +], +[ + [0xcaf4e99c, 0x494eac75, 0x3237de43, 0x695ba4d4, 0x68339d6f, 0xbca064f3, 0x4910c02a, 0xaefca662], + [0xfffffffb, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xefffffff], +], + ]; + + #[test] + fn test_zero_x_point() { + // Even though this point verifies the equation y^2 = x^3 - 3x + b, none of the (x, y) + // coordinates is allowed to be zero. + #[rustfmt::skip] + let x = Int256::new( + [0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000] + ); + #[rustfmt::skip] + let y = Int256::new( + [0xe8b06c0b, 0xd7407a95, 0xe25178e8, 0xabe3d50d, 0x7b5f9449, 0xdbcc42a2, 0xf1d07c29, 0x99b7a386] + ); + assert!(PointP256::new_checked_vartime(x, y).is_none()); + } + + #[test] + fn test_small_x_points() { + for p in POINTS_SMALL_X { + let x = Int256::new(p[0]); + let y = Int256::new(p[1]); + // These points are valid. + assert!(PointP256::new_checked_vartime(x, y).is_some()); + // Adding p to the x coordinate doesn't invalidate the equation y^2 = x^3 - 3x + b (as + // we work in GF(p)), however the coordinates must be serialized in a canonical form + // modulo p. + assert!(PointP256::new_checked_vartime((&x + &Int256::P).0, y).is_none()); + } + } + + #[test] + fn test_small_y_points() { + for p in POINTS_SMALL_Y { + let x = Int256::new(p[0]); + let y = Int256::new(p[1]); + // These points are valid. + assert!(PointP256::new_checked_vartime(x, y).is_some()); + // Adding p to the y coordinate doesn't invalidate the equation y^2 = x^3 - 3x + b (as + // we work in GF(p)), however the coordinates must be serialized in a canonical form + // modulo p. + assert!(PointP256::new_checked_vartime(x, (&y + &Int256::P).0).is_none()); + } + } + + #[test] + fn test_high_weight_points() { + // These points are all valid, with one coordinate of high Hamming weight. This is a sanity + // check that arithmetic works on such high weight values. + for p in POINTS_HIGH_WEIGHT { + let x = Int256::new(p[0]); + let y = Int256::new(p[1]); + assert!(PointP256::new_checked_vartime(x, y).is_some()); + } + } + + #[test] + fn test_infinity_is_invalid() { + assert!(PointP256::new_checked_vartime( + PointP256::INFINITY.x.to_int(), + PointP256::INFINITY.y.to_int() + ) + .is_none()); + } + + /** Constant-time helpers **/ + #[test] + fn test_select_point_projective() { + let mut table = Vec::new(); + for i in 0..15 { + table.push(PointProjective::from_affine(&precomputed(0, i))); + } + + assert_eq!( + PointProjective::select_point(array_ref![table, 0, 15], 0), + PointProjective { + x: Montgomery::ZERO, + y: Montgomery::ZERO, + z: Montgomery::ZERO, + } + ); + for index in 1..16 { + assert_eq!( + PointProjective::select_point(array_ref![table, 0, 15], index as u32), + table[index - 1] + ); + } + } + + #[test] + fn test_select_point_affine() { + let table = &Montgomery::PRECOMPUTED[0]; + + assert_eq!( + PointAffine::select_point(table, 0), + PointAffine { + x: Montgomery::ZERO, + y: Montgomery::ZERO, + } + ); + for index in 1..16 { + assert_eq!( + PointAffine::select_point(table, index as u32), + precomputed(0, index - 1) + ); + } + } + + /** Arithmetic operators **/ + #[test] + fn test_add_is_add_complete_general() { + for x in &get_test_values_projective() { + for y in &get_test_values_projective() { + let left = x.add_complete_general(y); + let right = x + y; + assert_eq!(left, right); + } + } + } + + #[test] + fn test_add_is_associative() { + for x in &get_test_values_projective() { + for y in &get_test_values_projective() { + for z in &get_test_values_projective() { + // (x + y) + z + let left = &(x + y) + z; + // x + (y + z) + let right = x + &(y + z); + assert_eq!(left, right); + } + } + } + } + + #[test] + fn test_add_is_commutative() { + for x in &get_test_values_projective() { + for y in &get_test_values_projective() { + assert_eq!(x + y, y + x); + } + } + } + + #[test] + fn test_add_mixed() { + for x in &get_test_values_projective() { + for y in &get_test_values_affine() { + assert_eq!(x.add_mixed(y), x + &PointProjective::from_affine(y)); + } + } + } + + #[test] + fn test_double() { + for x in &get_test_values_projective() { + println!("doubling {:?}", x); + assert_eq!(x.double(), x + x); + } + } + + #[test] + fn test_add_infinity() { + for &x in &get_test_values_projective() { + assert_eq!(&x + &PointProjective::INFINITY, x); + } + } + + #[test] + fn test_add_mixed_infinity() { + for x in &get_test_values_affine() { + assert_eq!( + PointProjective::INFINITY.add_mixed(x), + PointProjective::from_affine(x) + ); + } + } + + #[test] + fn test_double_infinity() { + assert_eq!( + PointProjective::INFINITY.double(), + PointProjective::INFINITY + ); + } + + #[test] + fn test_generator_is_valid_point() { + let gen = precomputed(0, 0); + assert!(PointP256::from_affine(&gen).is_valid_vartime()); + } + + #[test] + fn test_generator_has_correct_order() { + let gen = precomputed(0, 0); + // Normally the ExponentP256 type guarantees that its values stay in [0, N[ because N is + // the curve order and therefore exponents >= N are equivalent to their reduction modulo N. + // In this test we check that N is indeed the curve order and therefore we need an unsafe + // block to construct an exponent of N. + let order = unsafe { ExponentP256::from_int_unchecked(Int256::N) }; + assert_eq!( + PointP256::from_projective(&gen.scalar_mul(&order)), + PointP256::INFINITY + ); + } + + #[test] + fn test_scalar_base_mul_is_scalar_mul_generator() { + let gen = precomputed(0, 0); + // TODO: more scalars + for scalar in &super::super::exponent256::test::get_test_values() { + assert_eq!( + PointProjective::scalar_base_mul(scalar), + gen.scalar_mul(scalar) + ); + } + } + + #[test] + fn test_base_point_mul_is_mul_generator() { + let gen = precomputed(0, 0); + // TODO: more scalars + for scalar in &super::super::exponent256::test::get_test_values() { + assert_eq!( + PointP256::base_point_mul(scalar), + PointP256::from_affine(&gen).mul(scalar) + ); + } + } + + // Helper function to compute the point 2^power * p. + pub fn power_of_two(mut p: PointProjective, power: usize) -> PointProjective { + for _ in 0..power { + p = p.double(); + } + p + } + + // TODO: more tests +} diff --git a/libraries/crypto/src/ec/precomputed.rs b/libraries/crypto/src/ec/precomputed.rs new file mode 100644 index 0000000..809fad6 --- /dev/null +++ b/libraries/crypto/src/ec/precomputed.rs @@ -0,0 +1,324 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::montgomery::{Montgomery, NLIMBS}; + +pub const PRECOMPUTED: [[[Montgomery; 2]; 15]; 2] = [ + [ + [ + Montgomery::new(PRECOMPUTED_LIMBS[0]), + Montgomery::new(PRECOMPUTED_LIMBS[1]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[2]), + Montgomery::new(PRECOMPUTED_LIMBS[3]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[4]), + Montgomery::new(PRECOMPUTED_LIMBS[5]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[6]), + Montgomery::new(PRECOMPUTED_LIMBS[7]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[8]), + Montgomery::new(PRECOMPUTED_LIMBS[9]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[10]), + Montgomery::new(PRECOMPUTED_LIMBS[11]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[12]), + Montgomery::new(PRECOMPUTED_LIMBS[13]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[14]), + Montgomery::new(PRECOMPUTED_LIMBS[15]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[16]), + Montgomery::new(PRECOMPUTED_LIMBS[17]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[18]), + Montgomery::new(PRECOMPUTED_LIMBS[19]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[20]), + Montgomery::new(PRECOMPUTED_LIMBS[21]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[22]), + Montgomery::new(PRECOMPUTED_LIMBS[23]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[24]), + Montgomery::new(PRECOMPUTED_LIMBS[25]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[26]), + Montgomery::new(PRECOMPUTED_LIMBS[27]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[28]), + Montgomery::new(PRECOMPUTED_LIMBS[29]), + ], + ], + [ + [ + Montgomery::new(PRECOMPUTED_LIMBS[30]), + Montgomery::new(PRECOMPUTED_LIMBS[31]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[32]), + Montgomery::new(PRECOMPUTED_LIMBS[33]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[34]), + Montgomery::new(PRECOMPUTED_LIMBS[35]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[36]), + Montgomery::new(PRECOMPUTED_LIMBS[37]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[38]), + Montgomery::new(PRECOMPUTED_LIMBS[39]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[40]), + Montgomery::new(PRECOMPUTED_LIMBS[41]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[42]), + Montgomery::new(PRECOMPUTED_LIMBS[43]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[44]), + Montgomery::new(PRECOMPUTED_LIMBS[45]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[46]), + Montgomery::new(PRECOMPUTED_LIMBS[47]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[48]), + Montgomery::new(PRECOMPUTED_LIMBS[49]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[50]), + Montgomery::new(PRECOMPUTED_LIMBS[51]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[52]), + Montgomery::new(PRECOMPUTED_LIMBS[53]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[54]), + Montgomery::new(PRECOMPUTED_LIMBS[55]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[56]), + Montgomery::new(PRECOMPUTED_LIMBS[57]), + ], + [ + Montgomery::new(PRECOMPUTED_LIMBS[58]), + Montgomery::new(PRECOMPUTED_LIMBS[59]), + ], + ], +]; + +#[rustfmt::skip] +#[allow(clippy::unreadable_literal)] +const PRECOMPUTED_LIMBS: [[u32; NLIMBS]; 60] = [ +[0x11522878, 0x0e730d41, 0x0db60179, 0x04afe2ff, 0x12883add, 0x0caddd88, 0x119e7edc, 0x0d4a6eab, 0x03120bee], +[0x1d2aac15, 0x0f25357c, 0x19e45cdd, 0x05c721d0, 0x1992c5a5, 0x0a237487, 0x0154ba21, 0x014b10bb, 0x00ae3fe3], +[0x0d41a576, 0x0922fc51, 0x0234994f, 0x060b60d3, 0x164586ae, 0x0ce95f18, 0x1fe49073, 0x03fa36cc, 0x05ebcd2c], +[0x0b402f2f, 0x015c70bf, 0x1561925c, 0x05a26704, 0x0da91e90, 0x0cdc1c7f, 0x1ea12446, 0x0e1ade1e, 0x0ec91f22], +[0x026f7778, 0x0566847e, 0x0a0bec9e, 0x0234f453, 0x1a31f21a, 0x0d85e75c, 0x056c7109, 0x0a267a00, 0x0b57c050], +[0x0098fb57, 0x0aa837cc, 0x060c0792, 0x0cfa5e19, 0x061bab9e, 0x0589e39b, 0x00a324c5, 0x07d6dee7, 0x02976e4b], +[0x1fc4124a, 0x0a8c244b, 0x1ce86762, 0x0cd61c7e, 0x1831c8e0, 0x075774e1, 0x1d96a5a9, 0x0843a649, 0x0c3ab0fa], +[0x06e2e7d5, 0x07673a2a, 0x178b65e8, 0x04003e9b, 0x1a1f11c2, 0x007816ea, 0x0f643e11, 0x058c43df, 0x0f423fc2], +[0x19633ffa, 0x0891f2b2, 0x123c231c, 0x046add8c, 0x054700dd, 0x059e2b17, 0x172db40f, 0x083e277d, 0x0b0dd609], +[0x0fd1da12, 0x035c6e52, 0x19ede20c, 0x0d19e0c0, 0x097d0f40, 0x0b015b19, 0x0449e3f5, 0x00e10c9e, 0x033ab581], +[0x056a67ab, 0x0577734d, 0x1dddc062, 0x0c57b10d, 0x0149b39d, 0x026a9e7b, 0x0c35df9f, 0x048764cd, 0x076dbcca], +[0x0ca4b366, 0x0e9303ab, 0x1a7480e7, 0x057e9e81, 0x1e13eb50, 0x0f466cf3, 0x06f16b20, 0x04ba3173, 0x0c168c33], +[0x15cb5439, 0x06a38e11, 0x073658bd, 0x0b29564f, 0x03f6dc5b, 0x0053b97e, 0x1322c4c0, 0x065dd7ff, 0x03a1e4f6], +[0x14e614aa, 0x09246317, 0x1bc83aca, 0x0ad97eed, 0x0d38ce4a, 0x0f82b006, 0x0341f077, 0x0a6add89, 0x04894acd], +[0x09f162d5, 0x0f8410ef, 0x1b266a56, 0x00d7f223, 0x03e0cb92, 0x0e39b672, 0x06a2901a, 0x069a8556, 0x0007e7c0], +[0x09b7d8d3, 0x00309a80, 0x1ad05f7f, 0x0c2fb5dd, 0x0cbfd41d, 0x09ceb638, 0x1051825c, 0x0da0cf5b, 0x0812e881], +[0x06f35669, 0x06a56f2c, 0x1df8d184, 0x00345820, 0x1477d477, 0x01645db1, 0x0be80c51, 0x0c22be3e, 0x0e35e65a], +[0x1aeb7aa0, 0x0c375315, 0x0f67bc99, 0x07fdd7b9, 0x191fc1be, 0x0061235d, 0x02c184e9, 0x01c5a839, 0x047a1e26], +[0x0b7cb456, 0x093e225d, 0x14f3c6ed, 0x0ccc1ac9, 0x17fe37f3, 0x04988989, 0x1a90c502, 0x02f32042, 0x0a17769b], +[0x0afd8c7c, 0x08191c6e, 0x1dcdb237, 0x016200c0, 0x107b32a1, 0x066c08db, 0x10d06a02, 0x0003fc93, 0x05620023], +[0x16722b27, 0x068b5c59, 0x0270fcfc, 0x0fad0ecc, 0x0e5de1c2, 0x0eab466b, 0x02fc513c, 0x0407f75c, 0x0baab133], +[0x09705fe9, 0x0b88b8e7, 0x0734c993, 0x01e1ff8f, 0x19156970, 0x0abd0f00, 0x10469ea7, 0x03293ac0, 0x0cdc98aa], +[0x01d843fd, 0x0e14bfe8, 0x15be825f, 0x008b5212, 0x0eb3fb67, 0x081cbd29, 0x0bc62f16, 0x02b6fcc7, 0x0f5a4e29], +[0x13560b66, 0x0c0b6ac2, 0x051ae690, 0x0d41e271, 0x0f3e9bd4, 0x01d70aab, 0x01029f72, 0x073e1c35, 0x0ee70fbc], +[0x0ad81baf, 0x09ecc49a, 0x086c741e, 0x0fe6be30, 0x176752e7, 0x0023d416, 0x1f83de85, 0x027de188, 0x066f70b8], +[0x181cd51f, 0x096b6e4c, 0x188f2335, 0x0a5df759, 0x17a77eb6, 0x0feb0e73, 0x154ae914, 0x02f3ec51, 0x03826b59], +[0x0b91f17d, 0x01c72949, 0x1362bf0a, 0x0e23fddf, 0x0a5614b0, 0x000f7d8f, 0x00079061, 0x0823d9d2, 0x08213f39], +[0x1128ae0b, 0x0d095d05, 0x0b85c0c2, 0x01ecb2ef, 0x024ddc84, 0x0e35e901, 0x18411a4a, 0x0f5ddc3d, 0x03786689], +[0x052260e8, 0x05ae3564, 0x0542b10d, 0x08d93a45, 0x19952aa4, 0x0996cc41, 0x1051a729, 0x04be3499, 0x052b23aa], +[0x109f307e, 0x06f5b6bb, 0x1f84e1e7, 0x077a0cfa, 0x10c4df3f, 0x025a02ea, 0x0b048035, 0x0e31de66, 0x0c6ecaa3], +[0x028ea335, 0x02886024, 0x1372f020, 0x00f55d35, 0x15e4684c, 0x0f2a9e17, 0x1a4a7529, 0x0cb7beb1, 0x0b2a78a1], +[0x1ab21f1f, 0x06361ccf, 0x06c9179d, 0x0b135627, 0x1267b974, 0x04408bad, 0x1cbff658, 0x0e3d6511, 0x00c7d76f], +[0x01cc7a69, 0x0e7ee31b, 0x054fab4f, 0x002b914f, 0x1ad27a30, 0x0cd3579e, 0x0c50124c, 0x050daa90, 0x00b13f72], +[0x0b06aa75, 0x070f5cc6, 0x1649e5aa, 0x084a5312, 0x0329043c, 0x041c4011, 0x13d32411, 0x0b04a838, 0x0d760d2d], +[0x1713b532, 0x0baa0c03, 0x084022ab, 0x06bcf5c1, 0x02f45379, 0x018ae070, 0x18c9e11e, 0x020bca9a, 0x066f496b], +[0x03eef294, 0x067500d2, 0x0d7f613c, 0x002dbbeb, 0x0b741038, 0x0e04133f, 0x1582968d, 0x0be985f7, 0x01acbc1a], +[0x1a6a939f, 0x033e50f6, 0x0d665ed4, 0x0b4b7bd6, 0x1e5a3799, 0x06b33847, 0x17fa56ff, 0x065ef930, 0x0021dc4a], +[0x02b37659, 0x0450fe17, 0x0b357b65, 0x0df5efac, 0x15397bef, 0x09d35a7f, 0x112ac15f, 0x0624e62e, 0x0a90ae2f], +[0x107eecd2, 0x01f69bbe, 0x077d6bce, 0x05741394, 0x13c684fc, 0x0950c910, 0x0725522b, 0x0dc78583, 0x040eeabb], +[0x1fde328a, 0x0bd61d96, 0x0d28c387, 0x09e77d89, 0x12550c40, 0x0759cb7d, 0x0367ef34, 0x0ae2a960, 0x091b8bdc], +[0x093462a9, 0x00f469ef, 0x0b2e9aef, 0x0d2ca771, 0x054e1f42, 0x007aaa49, 0x06316abb, 0x02413c8e, 0x05425bf9], +[0x1bed3e3a, 0x0f272274, 0x1f5e7326, 0x06416517, 0x0ea27072, 0x09cedea7, 0x006e7633, 0x07c91952, 0x0d806dce], +[0x08e2a7e1, 0x0e421e1a, 0x0418c9e1, 0x01dbc890, 0x1b395c36, 0x0a1dc175, 0x1dc4ef73, 0x08956f34, 0x0e4b5cf2], +[0x1b0d3a18, 0x03194a36, 0x06c2641f, 0x0e44124c, 0x0a2f4eaa, 0x0a8c25ba, 0x0f927ed7, 0x0627b614, 0x07371cca], +[0x0ba16694, 0x0417bc03, 0x07c0a7e3, 0x09c35c19, 0x1168a205, 0x08b6b00d, 0x10e3edc9, 0x09c19bf2, 0x05882229], +[0x1b2b4162, 0x0a5cef1a, 0x1543622b, 0x09bd433e, 0x0364e04d, 0x07480792, 0x05c9b5b3, 0x0e85ff25, 0x0408ef57], +[0x1814cfa4, 0x0121b41b, 0x0d248a0f, 0x03b05222, 0x039bb16a, 0x0c75966d, 0x0a038113, 0x0a4a1769, 0x011fbc6c], +[0x0917e50e, 0x0eec3da8, 0x169d6eac, 0x010c1699, 0x0a416153, 0x0f724912, 0x15cd60b7, 0x04acbad9, 0x05efc5fa], +[0x0f150ed7, 0x00122b51, 0x1104b40a, 0x0cb7f442, 0x0fbb28ff, 0x06ac53ca, 0x196142cc, 0x07bf0fa9, 0x00957651], +[0x04e0f215, 0x0ed439f8, 0x03f46bd5, 0x05ace82f, 0x110916b6, 0x006db078, 0x0ffd7d57, 0x0f2ecaac, 0x0ca86dec], +[0x15d6b2da, 0x0965ecc9, 0x1c92b4c2, 0x001f3811, 0x1cb080f5, 0x02d8b804, 0x19d1c12d, 0x0f20bd46, 0x01951fa7], +[0x0a3656c3, 0x0523a425, 0x0fcd0692, 0x0d44ddc8, 0x131f0f5b, 0x0af80e4a, 0x0cd9fc74, 0x099bb618, 0x02db944c], +[0x0a673090, 0x01c210e1, 0x178c8d23, 0x01474383, 0x10b8743d, 0x0985a55b, 0x02e74779, 0x00576138, 0x09587927], +[0x133130fa, 0x0be05516, 0x09f4d619, 0x0bb62570, 0x099ec591, 0x0d9468fe, 0x1d07782d, 0x0fc72e0b, 0x0701b298], +[0x1863863b, 0x085954b8, 0x121a0c36, 0x09e7fedf, 0x0f64b429, 0x09b9d71e, 0x14e2f5d8, 0x0f858d3a, 0x0942eea8], +[0x0da5b765, 0x06edafff, 0x0a9d18cc, 0x0c65e4ba, 0x1c747e86, 0x0e4ea915, 0x1981d7a1, 0x08395659, 0x052ed4e2], +[0x087d43b7, 0x037ab11b, 0x19d292ce, 0x0f8d4692, 0x18c3053f, 0x08863e13, 0x04c146c0, 0x06bdf55a, 0x04e4457d], +[0x16152289, 0x0ac78ec2, 0x1a59c5a2, 0x02028b97, 0x071c2d01, 0x0295851f, 0x0404747b, 0x0878558d, 0x07d29aa4], +[0x13d8341f, 0x08daefd7, 0x139c972d, 0x06b7ea75, 0x0d4a9dde, 0x0ff163d8, 0x081d55d7, 0x0a5bef68, 0x0b7b30d8], +[0x0be73d6f, 0x0aa88141, 0x0d976c81, 0x07e7a9cc, 0x18beb771, 0x0d773cbd, 0x13f51951, 0x09d0c177, 0x01c49a78], +]; + +#[cfg(test)] +mod test { + use super::super::montgomery::{BOTTOM_28_BITS, BOTTOM_29_BITS}; + use super::super::point::test::{power_of_two, precomputed}; + use super::super::point::PointProjective; + use super::*; + + #[test] + fn test_precomputed_bits() { + for x in PRECOMPUTED_LIMBS.iter() { + for (i, &limb) in x.iter().enumerate() { + if i & 1 == 0 { + assert_eq!(limb & BOTTOM_29_BITS, limb); + } else { + assert_eq!(limb & BOTTOM_28_BITS, limb); + } + } + } + } + + #[test] + fn test_precomputed_powers_of_g_are_correct() { + let gen = PointProjective::from_affine(&precomputed(0, 0)); + let g32 = power_of_two(gen, 32); + let g64 = power_of_two(gen, 64); + let g96 = power_of_two(gen, 96); + let g128 = power_of_two(gen, 128); + let g160 = power_of_two(gen, 160); + let g192 = power_of_two(gen, 192); + let g224 = power_of_two(gen, 224); + + assert_eq!( + PointProjective::from_affine(&precomputed(0, 0b0001 - 1)), + gen + ); + assert_eq!( + PointProjective::from_affine(&precomputed(0, 0b0010 - 1)), + g64 + ); + assert_eq!( + PointProjective::from_affine(&precomputed(0, 0b0100 - 1)), + g128 + ); + assert_eq!( + PointProjective::from_affine(&precomputed(0, 0b1000 - 1)), + g192 + ); + assert_eq!( + PointProjective::from_affine(&precomputed(1, 0b0001 - 1)), + g32 + ); + assert_eq!( + PointProjective::from_affine(&precomputed(1, 0b0010 - 1)), + g96 + ); + assert_eq!( + PointProjective::from_affine(&precomputed(1, 0b0100 - 1)), + g160 + ); + assert_eq!( + PointProjective::from_affine(&precomputed(1, 0b1000 - 1)), + g224 + ); + } + + #[test] + fn test_precomputed_table_0_is_correct() { + let gen = PointProjective::from_affine(&precomputed(0, 0)); + let g64 = power_of_two(gen, 64); + let g128 = power_of_two(gen, 128); + let g192 = power_of_two(gen, 192); + + for i in 1..16 { + let mut x = PointProjective::INFINITY; + if i & 1 != 0 { + x = &x + &gen; + } + if i & 2 != 0 { + x = &x + &g64; + } + if i & 4 != 0 { + x = &x + &g128; + } + if i & 8 != 0 { + x = &x + &g192; + } + assert_eq!(PointProjective::from_affine(&precomputed(0, i - 1)), x); + } + } + + #[test] + fn test_precomputed_table_1_is_correct() { + let gen = PointProjective::from_affine(&precomputed(0, 0)); + let g32 = power_of_two(gen, 32); + let g96 = power_of_two(gen, 96); + let g160 = power_of_two(gen, 160); + let g224 = power_of_two(gen, 224); + + for i in 1..16 { + let mut x = PointProjective::INFINITY; + if i & 1 != 0 { + x = &x + &g32; + } + if i & 2 != 0 { + x = &x + &g96; + } + if i & 4 != 0 { + x = &x + &g160; + } + if i & 8 != 0 { + x = &x + &g224; + } + assert_eq!(PointProjective::from_affine(&precomputed(1, i - 1)), x); + } + } +} diff --git a/libraries/crypto/src/ecdh.rs b/libraries/crypto/src/ecdh.rs new file mode 100644 index 0000000..7a4b250 --- /dev/null +++ b/libraries/crypto/src/ecdh.rs @@ -0,0 +1,154 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::ec::exponent256::NonZeroExponentP256; +use super::ec::int256; +use super::ec::int256::Int256; +use super::ec::point::PointP256; +use super::rng256::Rng256; +use super::sha256::Sha256; +use super::Hash256; + +pub const NBYTES: usize = int256::NBYTES; + +pub struct SecKey { + a: NonZeroExponentP256, +} + +#[cfg_attr(feature = "derive_debug", derive(Clone, PartialEq, Debug))] +pub struct PubKey { + p: PointP256, +} + +impl SecKey { + pub fn gensk(rng: &mut R) -> SecKey + where + R: Rng256, + { + SecKey { + a: NonZeroExponentP256::gen_uniform(rng), + } + } + + pub fn genpk(&self) -> PubKey { + PubKey { + p: PointP256::base_point_mul(self.a.as_exponent()), + } + } + + fn exchange_raw(&self, other: &PubKey) -> PointP256 { + // At this point, the PubKey type guarantees that other.p is a valid point on the curve. + // It's the responsibility of the caller to handle errors when converting serialized bytes + // to a PubKey. + other.p.mul(self.a.as_exponent()) + // TODO: Do we need to check that the exchanged point is not infinite, and if yes handle + // the error? The following argument should be reviewed: + // + // In principle this isn't needed on the P-256 curve, which has a prime order and a + // cofactor of 1. + // + // Some pointers on this: + // - https://www.secg.org/sec1-v2.pdf + } + + // DH key agreement method defined in the FIDO2 specification, Section 5.5.4. "Getting + // sharedSecret from Authenticator" + pub fn exchange_x_sha256(&self, other: &PubKey) -> [u8; 32] { + let p = self.exchange_raw(other); + let mut x: [u8; 32] = [Default::default(); 32]; + p.getx().to_int().to_bin(&mut x); + Sha256::hash(&x) + } +} + +impl PubKey { + #[cfg(test)] + fn from_bytes_uncompressed(bytes: &[u8]) -> Option { + PointP256::from_bytes_uncompressed_vartime(bytes).map(|p| PubKey { p }) + } + + #[cfg(test)] + fn to_bytes_uncompressed(&self, bytes: &mut [u8; 65]) { + self.p.to_bytes_uncompressed(bytes); + } + + pub fn from_coordinates(x: &[u8; NBYTES], y: &[u8; NBYTES]) -> Option { + PointP256::new_checked_vartime(Int256::from_bin(x), Int256::from_bin(y)) + .map(|p| PubKey { p }) + } + + pub fn to_coordinates(&self, x: &mut [u8; NBYTES], y: &mut [u8; NBYTES]) { + self.p.getx().to_int().to_bin(x); + self.p.gety().to_int().to_bin(y); + } +} + +#[cfg(test)] +mod test { + use super::super::rng256::ThreadRng256; + use super::*; + + // Run more test iterations in release mode, as the code should be faster. + #[cfg(not(debug_assertions))] + const ITERATIONS: u32 = 10000; + #[cfg(debug_assertions)] + const ITERATIONS: u32 = 1000; + + /** Test that key generation creates valid keys **/ + #[test] + fn test_gen_pub_is_valid_random() { + let mut rng = ThreadRng256 {}; + + for _ in 0..ITERATIONS { + let sk = SecKey::gensk(&mut rng); + let pk = sk.genpk(); + assert!(pk.p.is_valid_vartime()); + } + } + + /** Test that the exchanged key is the same on both sides **/ + #[test] + fn test_exchange_x_sha256_is_symmetric() { + let mut rng = ThreadRng256 {}; + + for _ in 0..ITERATIONS { + let sk_a = SecKey::gensk(&mut rng); + let pk_a = sk_a.genpk(); + let sk_b = SecKey::gensk(&mut rng); + let pk_b = sk_b.genpk(); + assert_eq!(sk_a.exchange_x_sha256(&pk_b), sk_b.exchange_x_sha256(&pk_a)); + } + } + + #[test] + fn test_exchange_x_sha256_bytes_is_symmetric() { + let mut rng = ThreadRng256 {}; + + for _ in 0..ITERATIONS { + let sk_a = SecKey::gensk(&mut rng); + let mut pk_bytes_a = [Default::default(); 65]; + sk_a.genpk().to_bytes_uncompressed(&mut pk_bytes_a); + + let sk_b = SecKey::gensk(&mut rng); + let mut pk_bytes_b = [Default::default(); 65]; + sk_b.genpk().to_bytes_uncompressed(&mut pk_bytes_b); + + let pk_a = PubKey::from_bytes_uncompressed(&pk_bytes_a).unwrap(); + let pk_b = PubKey::from_bytes_uncompressed(&pk_bytes_b).unwrap(); + assert_eq!(sk_a.exchange_x_sha256(&pk_b), sk_b.exchange_x_sha256(&pk_a)); + } + } + + // TODO: tests with invalid public shares. +} diff --git a/libraries/crypto/src/ecdsa.rs b/libraries/crypto/src/ecdsa.rs new file mode 100644 index 0000000..2d39311 --- /dev/null +++ b/libraries/crypto/src/ecdsa.rs @@ -0,0 +1,643 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::ec::exponent256::{ExponentP256, NonZeroExponentP256}; +use super::ec::int256; +use super::ec::int256::Int256; +use super::ec::point::PointP256; +use super::hmac::hmac_256; +use super::rng256::Rng256; +use super::{Hash256, HashBlockSize64Bytes}; +use alloc::vec::Vec; +use core::marker::PhantomData; + +#[derive(Clone, PartialEq)] +#[cfg_attr(feature = "derive_debug", derive(Debug))] +pub struct SecKey { + k: NonZeroExponentP256, +} + +pub struct Signature { + r: NonZeroExponentP256, + s: NonZeroExponentP256, +} + +pub struct PubKey { + p: PointP256, +} + +impl SecKey { + pub fn gensk(rng: &mut R) -> SecKey + where + R: Rng256, + { + SecKey { + k: NonZeroExponentP256::gen_uniform(rng), + } + } + + pub fn genpk(&self) -> PubKey { + PubKey { + p: PointP256::base_point_mul(self.k.as_exponent()), + } + } + + // ECDSA signature based on a RNG to generate a suitable randomization parameter. + // Under the hood, rejection sampling is used to make sure that the randomization parameter is + // uniformly distributed. + // The provided RNG must be cryptographically secure; otherwise this method is insecure. + pub fn sign_rng(&self, msg: &[u8], rng: &mut R) -> Signature + where + H: Hash256, + R: Rng256, + { + let m = ExponentP256::modn(Int256::from_bin(&H::hash(msg))); + + loop { + let k = NonZeroExponentP256::gen_uniform(rng); + if let Some(sign) = self.try_sign(&k, &m) { + return sign; + } + } + } + + // Deterministic ECDSA signature based on RFC 6979 to generate a suitable randomization + // parameter. + pub fn sign_rfc6979(&self, msg: &[u8]) -> Signature + where + H: Hash256 + HashBlockSize64Bytes, + { + let m = ExponentP256::modn(Int256::from_bin(&H::hash(msg))); + + let mut rfc_6979 = Rfc6979::::new(self, &msg); + loop { + let k = NonZeroExponentP256::from_int_checked(rfc_6979.next()); + // The branching here is fine. By design the algorithm of RFC 6976 has a running time + // that depends on the sequence of generated k. + if bool::from(k.is_none()) { + continue; + } + let k = k.unwrap(); + + if let Some(sign) = self.try_sign(&k, &m) { + return sign; + } + } + } + + // Try signing a curve element given a randomization parameter k. If no signature can be + // obtained from this k, None is returned and the caller should try again with another value. + fn try_sign(&self, k: &NonZeroExponentP256, msg: &ExponentP256) -> Option { + let r = ExponentP256::modn(PointP256::base_point_mul(k.as_exponent()).getx().to_int()); + // The branching here is fine because all this reveals is that k generated an unsuitable r. + let r = r.non_zero(); + if bool::from(r.is_none()) { + return None; + } + let r = r.unwrap(); + + let (s, top) = &(&r * &self.k).to_int() + &msg.to_int(); + let s = k.inv().as_exponent().mul_top(&s, top); + + // The branching here is fine because all this reveals is that k generated an unsuitable s. + let s = s.non_zero(); + if bool::from(s.is_none()) { + return None; + } + let s = s.unwrap(); + + Some(Signature { r, s }) + } + + #[cfg(test)] + pub fn get_k_rfc6979(&self, msg: &[u8]) -> NonZeroExponentP256 + where + H: Hash256 + HashBlockSize64Bytes, + { + let m = ExponentP256::modn(Int256::from_bin(&H::hash(msg))); + + let mut rfc_6979 = Rfc6979::::new(self, &msg); + loop { + let k = NonZeroExponentP256::from_int_checked(rfc_6979.next()); + if bool::from(k.is_none()) { + continue; + } + let k = k.unwrap(); + if self.try_sign(&k, &m).is_some() { + return k; + } + } + } + + pub fn from_bytes(bytes: &[u8; 32]) -> Option { + let k = NonZeroExponentP256::from_int_checked(Int256::from_bin(bytes)); + // The branching here is fine because all this reveals is whether the key was invalid. + if bool::from(k.is_none()) { + return None; + } + let k = k.unwrap(); + Some(SecKey { k }) + } + + pub fn to_bytes(&self, bytes: &mut [u8; 32]) { + self.k.to_int().to_bin(bytes); + } +} + +impl Signature { + pub fn to_asn1_der(&self) -> Vec { + const DER_INTEGER_TYPE: u8 = 0x02; + const DER_DEF_LENGTH_SEQUENCE: u8 = 0x30; + + let r_encoding = self.r.to_int().to_minimal_encoding(); + let s_encoding = self.s.to_int().to_minimal_encoding(); + // We rely on the encoding to be short enough such that + // sum of lengths + 4 still fits into 7 bits. + #[cfg(test)] + assert!(r_encoding.len() <= 33); + #[cfg(test)] + assert!(s_encoding.len() <= 33); + // The ASN1 of a signature is a two member sequence. Its length is the + // sum of the integer encoding lengths and 2 header bytes per integer. + let mut encoding = vec![ + DER_DEF_LENGTH_SEQUENCE, + (r_encoding.len() + s_encoding.len() + 4) as u8, + ]; + encoding.push(DER_INTEGER_TYPE); + encoding.push(r_encoding.len() as u8); + encoding.extend(r_encoding); + encoding.push(DER_INTEGER_TYPE); + encoding.push(s_encoding.len() as u8); + encoding.extend(s_encoding); + encoding + } + + #[cfg(feature = "std")] + pub fn from_bytes(bytes: &[u8]) -> Option { + if bytes.len() != 64 { + None + } else { + let r = + NonZeroExponentP256::from_int_checked(Int256::from_bin(array_ref![bytes, 0, 32])); + let s = + NonZeroExponentP256::from_int_checked(Int256::from_bin(array_ref![bytes, 32, 32])); + if bool::from(r.is_none()) || bool::from(s.is_none()) { + return None; + } + let r = r.unwrap(); + let s = s.unwrap(); + Some(Signature { r, s }) + } + } + + #[cfg(test)] + fn to_bytes(&self, bytes: &mut [u8; 64]) { + self.r.to_int().to_bin(array_mut_ref![bytes, 0, 32]); + self.s.to_int().to_bin(array_mut_ref![bytes, 32, 32]); + } +} + +impl PubKey { + pub const ES256_ALGORITHM: i64 = -7; + #[cfg(feature = "with_ctap1")] + const UNCOMPRESSED_LENGTH: usize = 1 + 2 * int256::NBYTES; + + #[cfg(feature = "std")] + pub fn from_bytes_uncompressed(bytes: &[u8]) -> Option { + PointP256::from_bytes_uncompressed_vartime(bytes).map(|p| PubKey { p }) + } + + #[cfg(test)] + fn to_bytes_uncompressed(&self, bytes: &mut [u8; 65]) { + self.p.to_bytes_uncompressed(bytes); + } + + #[cfg(feature = "with_ctap1")] + pub fn to_uncompressed(&self) -> [u8; PubKey::UNCOMPRESSED_LENGTH] { + // Formatting according to: + // https://tools.ietf.org/id/draft-jivsov-ecc-compact-05.html#overview + const B0_BYTE_MARKER: u8 = 0x04; + let mut representation = [0; PubKey::UNCOMPRESSED_LENGTH]; + let (marker, x, y) = + mut_array_refs![&mut representation, 1, int256::NBYTES, int256::NBYTES]; + marker[0] = B0_BYTE_MARKER; + self.p.getx().to_int().to_bin(x); + self.p.gety().to_int().to_bin(y); + representation + } + + // Encodes the key according to CBOR Object Signing and Encryption, defined in RFC 8152. + pub fn to_cose_key(&self) -> Option> { + const EC2_KEY_TYPE: i64 = 2; + const P_256_CURVE: i64 = 1; + let mut x_bytes = vec![0; int256::NBYTES]; + self.p + .getx() + .to_int() + .to_bin(array_mut_ref![x_bytes.as_mut_slice(), 0, int256::NBYTES]); + let x_byte_cbor: cbor::Value = cbor_bytes!(x_bytes); + let mut y_bytes = vec![0; int256::NBYTES]; + self.p + .gety() + .to_int() + .to_bin(array_mut_ref![y_bytes.as_mut_slice(), 0, int256::NBYTES]); + let y_byte_cbor: cbor::Value = cbor_bytes!(y_bytes); + let cbor_value = cbor_map_options! { + 1 => EC2_KEY_TYPE, + 3 => PubKey::ES256_ALGORITHM, + -1 => P_256_CURVE, + -2 => x_byte_cbor, + -3 => y_byte_cbor, + }; + let mut encoded_key = Vec::new(); + if cbor::write(cbor_value, &mut encoded_key) { + Some(encoded_key) + } else { + None + } + } + + #[cfg(feature = "std")] + pub fn verify_vartime(&self, msg: &[u8], sign: &Signature) -> bool + where + H: Hash256, + { + let m = ExponentP256::modn(Int256::from_bin(&H::hash(msg))); + + let v = sign.s.inv(); + let u = &m * v.as_exponent(); + let v = &sign.r * &v; + + let u = self.p.points_mul(&u, v.as_exponent()).getx(); + + ExponentP256::modn(u.to_int()) == *sign.r.as_exponent() + } +} + +struct Rfc6979 +where + H: Hash256 + HashBlockSize64Bytes, +{ + k: [u8; 32], + v: [u8; 32], + hash_marker: PhantomData, +} + +impl Rfc6979 +where + H: Hash256 + HashBlockSize64Bytes, +{ + pub fn new(sk: &SecKey, msg: &[u8]) -> Rfc6979 { + let h1 = H::hash(msg); + let v = [0x01; 32]; + let k = [0x00; 32]; + + let mut contents = [0; 3 * 32 + 1]; + let (contents_v, marker, contents_k, contents_h1) = + mut_array_refs![&mut contents, 32, 1, 32, 32]; + contents_v.copy_from_slice(&v); + marker[0] = 0x00; + Int256::to_bin(&sk.k.to_int(), contents_k); + Int256::to_bin(&Int256::from_bin(&h1).modd(&Int256::N), contents_h1); + + let k = hmac_256::(&k, &contents); + let v = hmac_256::(&k, &v); + + let (contents_v, marker, _) = mut_array_refs![&mut contents, 32, 1, 64]; + contents_v.copy_from_slice(&v); + marker[0] = 0x01; + + let k = hmac_256::(&k, &contents); + let v = hmac_256::(&k, &v); + + Rfc6979 { + k, + v, + hash_marker: PhantomData, + } + } + + fn next(&mut self) -> Int256 { + // Note: at this step, the logic from RFC 6979 is simplified, because the HMAC produces 256 + // bits and we need 256 bits. + let t = hmac_256::(&self.k, &self.v); + let result = Int256::from_bin(&t); + + let mut v1 = [0; 33]; + v1[..32].copy_from_slice(&self.v); + v1[32] = 0x00; + self.k = hmac_256::(&self.k, &v1); + self.v = hmac_256::(&self.k, &self.v); + + result + } +} + +#[cfg(test)] +mod test { + use super::super::rng256::ThreadRng256; + use super::super::sha256::Sha256; + use super::*; + extern crate hex; + extern crate ring; + extern crate untrusted; + + // Run more test iterations in release mode, as the code should be faster. + #[cfg(not(debug_assertions))] + const ITERATIONS: u32 = 10000; + #[cfg(debug_assertions)] + const ITERATIONS: u32 = 1000; + + /** Test that key generation creates valid keys **/ + #[test] + fn test_genpk_is_valid_random() { + let mut rng = ThreadRng256 {}; + + for _ in 0..ITERATIONS { + let sk = SecKey::gensk(&mut rng); + let pk = sk.genpk(); + assert!(pk.p.is_valid_vartime()); + } + } + + /** Serialization **/ + #[test] + fn test_seckey_to_bytes_from_bytes() { + let mut rng = ThreadRng256 {}; + + for _ in 0..ITERATIONS { + let sk = SecKey::gensk(&mut rng); + let mut bytes = [0; 32]; + sk.to_bytes(&mut bytes); + let decoded_sk = SecKey::from_bytes(&bytes); + assert_eq!(decoded_sk, Some(sk)); + } + } + + #[test] + fn test_seckey_from_bytes_zero() { + // Zero is not a valid exponent for a secret key. + let bytes = [0; 32]; + let sk = SecKey::from_bytes(&bytes); + assert!(sk.is_none()); + } + + #[test] + fn test_seckey_from_bytes_n() { + let mut bytes = [0; 32]; + Int256::N.to_bin(&mut bytes); + let sk = SecKey::from_bytes(&bytes); + assert!(sk.is_none()); + } + + #[test] + fn test_seckey_from_bytes_ge_n() { + let bytes = [0xFF; 32]; + let sk = SecKey::from_bytes(&bytes); + assert!(sk.is_none()); + } + + /** Test vectors from RFC6979 **/ + fn int256_from_hex(x: &str) -> Int256 { + let bytes = hex::decode(x).unwrap(); + assert_eq!(bytes.len(), 32); + Int256::from_bin(array_ref![bytes.as_slice(), 0, 32]) + } + + // Test vectors from RFC6979, Section A.2.5. + const RFC6979_X: &str = "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721"; + const RFC6979_UX: &str = "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6"; + const RFC6979_UY: &str = "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299"; + + #[test] + fn test_rfc6979_keypair() { + let sk = SecKey { + k: NonZeroExponentP256::from_int_checked(int256_from_hex(RFC6979_X)).unwrap(), + }; + let pk = sk.genpk(); + assert_eq!(pk.p.getx().to_int(), int256_from_hex(RFC6979_UX)); + assert_eq!(pk.p.gety().to_int(), int256_from_hex(RFC6979_UY)); + } + + fn test_rfc6979(msg: &str, k: &str, r: &str, s: &str) { + let sk = SecKey { + k: NonZeroExponentP256::from_int_checked(int256_from_hex(RFC6979_X)).unwrap(), + }; + assert_eq!( + sk.get_k_rfc6979::(msg.as_bytes()).to_int(), + int256_from_hex(k) + ); + let sign = sk.sign_rfc6979::(msg.as_bytes()); + assert_eq!(sign.r.to_int(), int256_from_hex(r)); + assert_eq!(sign.s.to_int(), int256_from_hex(s)); + } + + #[test] + fn test_rfc6979_sample() { + let msg = "sample"; + let k = "A6E3C57DD01ABE90086538398355DD4C3B17AA873382B0F24D6129493D8AAD60"; + let r = "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716"; + let s = "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8"; + test_rfc6979(msg, k, r, s); + } + + #[test] + fn test_rfc6979_test() { + let msg = "test"; + let k = "D16B6AE827F17175E040871A1C7EC3500192C4C92677336EC2537ACAEE0008E0"; + let r = "F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367"; + let s = "019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083"; + test_rfc6979(msg, k, r, s); + } + + /** Tests that sign and verify are consistent **/ + // Test that signed messages are correctly verified. + #[test] + fn test_sign_rfc6979_verify_random() { + let mut rng = ThreadRng256 {}; + + for _ in 0..ITERATIONS { + let msg = rng.gen_uniform_u8x32(); + let sk = SecKey::gensk(&mut rng); + let pk = sk.genpk(); + let sign = sk.sign_rfc6979::(&msg); + assert!(pk.verify_vartime::(&msg, &sign)); + } + } + + // Test that signed messages are correctly verified. + #[test] + fn test_sign_verify_random() { + let mut rng = ThreadRng256 {}; + + for _ in 0..ITERATIONS { + let msg = rng.gen_uniform_u8x32(); + let sk = SecKey::gensk(&mut rng); + let pk = sk.genpk(); + let sign = sk.sign_rng::(&msg, &mut rng); + assert!(pk.verify_vartime::(&msg, &sign)); + } + } + + /** Tests that this code is compatible with the ring crate **/ + // Test that the ring crate works properly. + #[test] + fn test_ring_sign_ring_verify() { + use ring::rand::SecureRandom; + use ring::signature::KeyPair; + + let ring_rng = ring::rand::SystemRandom::new(); + + for _ in 0..ITERATIONS { + let mut msg_bytes: [u8; 64] = [Default::default(); 64]; + ring_rng.fill(&mut msg_bytes).unwrap(); + let msg = untrusted::Input::from(&msg_bytes); + + let pkcs8_bytes = ring::signature::EcdsaKeyPair::generate_pkcs8( + &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + &ring_rng, + ) + .unwrap(); + let key_pair = ring::signature::EcdsaKeyPair::from_pkcs8( + &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + untrusted::Input::from(pkcs8_bytes.as_ref()), + ) + .unwrap(); + let public_key_bytes = key_pair.public_key().as_ref(); + + let sig = key_pair.sign(&ring_rng, msg).unwrap(); + let sig_bytes = sig.as_ref(); + + assert!(ring::signature::verify( + &ring::signature::ECDSA_P256_SHA256_FIXED, + untrusted::Input::from(public_key_bytes), + msg, + untrusted::Input::from(sig_bytes) + ) + .is_ok()); + } + } + + // Test that messages signed by the ring crate are correctly verified by this code. + #[test] + fn test_ring_sign_self_verify() { + use ring::rand::SecureRandom; + use ring::signature::KeyPair; + + let ring_rng = ring::rand::SystemRandom::new(); + + for _ in 0..ITERATIONS { + let mut msg_bytes: [u8; 64] = [Default::default(); 64]; + ring_rng.fill(&mut msg_bytes).unwrap(); + + let pkcs8_bytes = ring::signature::EcdsaKeyPair::generate_pkcs8( + &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + &ring_rng, + ) + .unwrap(); + let key_pair = ring::signature::EcdsaKeyPair::from_pkcs8( + &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + untrusted::Input::from(pkcs8_bytes.as_ref()), + ) + .unwrap(); + let public_key_bytes = key_pair.public_key().as_ref(); + + let sig = key_pair + .sign(&ring_rng, untrusted::Input::from(&msg_bytes)) + .unwrap(); + let sig_bytes = sig.as_ref(); + + let pk = PubKey::from_bytes_uncompressed(public_key_bytes).unwrap(); + let sign = Signature::from_bytes(sig_bytes).unwrap(); + assert!(pk.verify_vartime::(&msg_bytes, &sign)); + } + } + + // Test that messages signed by this code are correctly verified by the ring crate. + #[test] + fn test_self_sign_ring_verify() { + let mut rng = ThreadRng256 {}; + + for _ in 0..ITERATIONS { + let msg_bytes = rng.gen_uniform_u8x32(); + let sk = SecKey::gensk(&mut rng); + let pk = sk.genpk(); + let sign = sk.sign_rng::(&msg_bytes, &mut rng); + + let mut public_key_bytes: [u8; 65] = [Default::default(); 65]; + pk.to_bytes_uncompressed(&mut public_key_bytes); + let mut sig_bytes: [u8; 64] = [Default::default(); 64]; + sign.to_bytes(&mut sig_bytes); + + assert!(ring::signature::verify( + &ring::signature::ECDSA_P256_SHA256_FIXED, + untrusted::Input::from(&public_key_bytes), + untrusted::Input::from(&msg_bytes), + untrusted::Input::from(&sig_bytes) + ) + .is_ok()); + } + } + + #[test] + fn test_signature_to_asn1_der_short_encodings() { + let r_bytes = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + ]; + let r = NonZeroExponentP256::from_int_checked(Int256::from_bin(&r_bytes)).unwrap(); + let s_bytes = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, + ]; + let s = NonZeroExponentP256::from_int_checked(Int256::from_bin(&s_bytes)).unwrap(); + let signature = Signature { r, s }; + let expected_encoding = vec![0x30, 0x07, 0x02, 0x01, 0x01, 0x02, 0x02, 0x00, 0xFF]; + + assert_eq!(signature.to_asn1_der(), expected_encoding); + } + + #[test] + fn test_signature_to_asn1_der_long_encodings() { + let r_bytes = [ + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, + ]; + let r = NonZeroExponentP256::from_int_checked(Int256::from_bin(&r_bytes)).unwrap(); + let s_bytes = [ + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, + ]; + let s = NonZeroExponentP256::from_int_checked(Int256::from_bin(&s_bytes)).unwrap(); + let signature = Signature { r, s }; + let expected_encoding = vec![ + 0x30, 0x46, 0x02, 0x21, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x02, 0x21, 0x00, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, + ]; + + assert_eq!(signature.to_asn1_der(), expected_encoding); + } + + // TODO: Test edge-cases and compare the behavior with ring. + // - Invalid public key (at infinity, values not less than the prime p), but ring doesn't + // directly exposes key validation in its API. +} diff --git a/libraries/crypto/src/hmac.rs b/libraries/crypto/src/hmac.rs new file mode 100644 index 0000000..f09e2b9 --- /dev/null +++ b/libraries/crypto/src/hmac.rs @@ -0,0 +1,281 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::{Hash256, HashBlockSize64Bytes}; +use subtle::ConstantTimeEq; + +const BLOCK_SIZE: usize = 64; +const HASH_SIZE: usize = 32; + +pub fn verify_hmac_256(key: &[u8], contents: &[u8], mac: &[u8; HASH_SIZE]) -> bool +where + H: Hash256 + HashBlockSize64Bytes, +{ + let expected_mac = hmac_256::(key, contents); + bool::from(expected_mac.ct_eq(mac)) +} + +// FIDO2's PIN verification is just matching the first 16 bytes of the HMAC +// against the pin ¯\_(ツ)_/¯ +pub fn verify_hmac_256_first_128bits(key: &[u8], contents: &[u8], pin: &[u8; 16]) -> bool +where + H: Hash256 + HashBlockSize64Bytes, +{ + let expected_mac = hmac_256::(key, contents); + bool::from(array_ref![expected_mac, 0, 16].ct_eq(pin)) +} + +pub fn hmac_256(key: &[u8], contents: &[u8]) -> [u8; HASH_SIZE] +where + H: Hash256 + HashBlockSize64Bytes, +{ + let mut ipad: [u8; BLOCK_SIZE] = [0x36; BLOCK_SIZE]; + let mut opad: [u8; BLOCK_SIZE] = [0x5c; BLOCK_SIZE]; + if key.len() <= BLOCK_SIZE { + xor_pads(&mut ipad, &mut opad, key); + } else { + xor_pads(&mut ipad, &mut opad, &H::hash(key)); + } + + let mut ihasher = H::new(); + ihasher.update(&ipad); + ihasher.update(contents); + let ihash = ihasher.finalize(); + + let mut ohasher = H::new(); + ohasher.update(&opad); + ohasher.update(&ihash); + + ohasher.finalize() +} + +fn xor_pads(ipad: &mut [u8; BLOCK_SIZE], opad: &mut [u8; BLOCK_SIZE], key: &[u8]) { + for (i, k) in key.iter().enumerate() { + ipad[i] ^= k; + opad[i] ^= k; + } +} + +#[cfg(test)] +mod test { + use super::super::sha256::Sha256; + use super::*; + extern crate hex; + + #[test] + fn test_verify_hmac_valid() { + // Test for various lengths of the key and contents. + for len in 0..128 { + let key = vec![0; len]; + let contents = vec![0; len]; + let mac = hmac_256::(&key, &contents); + assert!(verify_hmac_256::(&key, &contents, &mac)); + } + } + + #[test] + fn test_verify_hmac_invalid() { + // Test for various lengths of the key and contents. + for len in 0..128 { + let key = vec![0; len]; + let contents = vec![0; len]; + let mac = hmac_256::(&key, &contents); + + // Check that invalid MACs don't verify, by changing any byte of the valid MAC. + for i in 0..HASH_SIZE { + let mut bad_mac = mac; + bad_mac[i] ^= 0x01; + assert!(!verify_hmac_256::(&key, &contents, &bad_mac)); + } + } + } + + #[test] + fn test_hmac_sha256_empty() { + let mut buf = [0; 96]; + buf[..64].copy_from_slice(&[0x5c; 64]); + buf[64..].copy_from_slice(&Sha256::hash(&[0x36; 64])); + assert_eq!(hmac_256::(&[], &[]), Sha256::hash(&buf)); + } + + #[test] + fn test_hmac_sha256_examples() { + assert_eq!( + hmac_256::(&[], &[]), + hex::decode("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad") + .unwrap() + .as_slice() + ); + assert_eq!( + hmac_256::(b"key", b"The quick brown fox jumps over the lazy dog"), + hex::decode("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8") + .unwrap() + .as_slice() + ); + } + + #[test] + fn test_hash_sha256_for_various_lengths() { + // This test makes sure that the key hashing and hash padding are implemented properly. + // + // Test vectors generated with the following Python script: + // + // import hashlib + // import hmac + // for n in range(128): + // print('b"' + hmac.new('A' * n, 'A' * n, hashlib.sha256).hexdigest() + '",') + // + let hashes: [&[u8; 64]; 128] = [ + b"b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", + b"ff333794c5afd126a4e75aef1a35595a846e9e1d4053017d78d7aa3d8373182a", + b"7c1d0c177d40e7fa03cd1fa9cfc314888985aecde8f586edfb05e71e5f109c20", + b"8f7afbaef39ad155ba09bcbb386a08257731fc06fb2c1d3cff451af5386c0e7d", + b"a4dbd2915084ba3bed9306ba674c1eb22fae1dffd971c95b62ab17f0711480cd", + b"f59d521db107b4265d995f197b189de468b984816d2a01dc8ca3fcfc24d6ff37", + b"29e0656a90f3975e41c8b7e55d225353c3bb8bc0b36328c8066a61763df70b83", + b"25a85cdd13b683f3cac732fa1f19757afffe7013ddbea6e441af883379bf4232", + b"5d0b0812ecb57fb4be7f29a8978cc4f2c35b32417cc9854e2702f7fb08f6d84d", + b"d5e12df428ebd9d991a280c714c9197b977520a7fea87200177cf03618868cb3", + b"fa311e0cd19106ab33f8ee84cafdc2f03be787679c1fe7b6e64df2b2e7466ca2", + b"9a481f04a2f40d561e8e6c4850498699b36e915993153e9f9b5a4485cb0541b1", + b"c8ae9a117e3a5c9125cc21c7f9df09de50de1a6846caf13586d1e5efdfcc1ce6", + b"5ef3a488aaffb5db9173ef59e65c2dd27f4cd1a10e1b464ef5bf82d2e35eddd5", + b"e5753e8febbb38a97c1dfee23a6103fc58416b7172bab834fe5684b00800abd5", + b"0ed714c729a3db2ffde2c4ccb9f5da498c5a73a2c7010d463a7b5da8d18127aa", + b"950918cfdb0cb20f327ba0b5095d53c0befb66b9f639d389020453fcf18e8c40", + b"699470b9775ec45e9024394e7ebb16463534ed7a617bafddca11a3a0e61a3694", + b"95e8c0520fc4dc4c85e00e3868c16ab22a82e8c71ca8ea329ae1ecef0ee3968a", + b"7964fba6a123164167cbf51dc2c53948e13b2fee67d09f532e8b7bc56447c091", + b"c7d07a5f715b3ea44c258de03f2000ba79a44dd8213feb6e6006e1c3becbedff", + b"3eb1a7bf7e4f5c2d9b8cc12667ada8d7773cb9e9424104a30063778567df9422", + b"19cc344ad67db96ea936ef15cc751b29fa0409f5c9894d2cfeb100d604653ffc", + b"91001e472df16d68ee8033cd13d76a26543ccdfe1426cc6e969759b05ee7d115", + b"65f1f4670f8537f37263dabedce049b0e7d1e71f741781413cc2d5c81ecd6ac3", + b"0cf5de0ada8b717296a34fc386a8b77f60b607a48a899bee37650891a616dae7", + b"de3d56f0c83c992163aac087c999ed8eb5a039f21b0536a1967e747868dafd56", + b"0bc3fd1d46d2dc4e2d357d1f6b437d168051d6f19975223505fc2d0abddc3faa", + b"1e30596f4144ca58f3e306548cd079cc92a51ab825a4ad3246393c22283af640", + b"c2fbc726bf3eb7f0216c7e1d5f5b475a93b033b3f901a4a4dca6bbefc65b68b2", + b"1c4ef56d530bb8b627df49ea7b31cf9520e067a2fd0c671896103e146dd4564a", + b"9aeed0f7808bd6c13ce54aa5724484f41bd7cbb5e39fd1d7730befbbf056a957", + b"e58aa85e83553b7fec841cbaf42cede943b6b7ded22661cd3b1867effe91b745", + b"790fc5689193ac57c97dd25729003f2a0d8d3e5f9d2a39e007b794282a51cd3e", + b"be067b95a45cdc0060d2c3757597d8d9b858128435e93346c28ce2e82a68e951", + b"8734f497f5cd2c8667c7d8e1c2328e7e11fa80fc26c7d933937490d37718871f", + b"e8a2d0e77dfad5b046bcff0340a975300b051a21ac403dd802348511a80ba8c8", + b"4afec38787117ab6e71c34a4be7575643d8e74fc16b9d5666157fd9aee0f6e86", + b"ac8b2831d636384d1d3ff5dd77b249860bd88d5aa9af3e1d4ee70be2a1b03db9", + b"e92e1ab3934d3ed2073973204aaba03de3bc4e864cc74677b560c42971b26ab1", + b"312effff0c19cf410b9e227654211133f29276c781baadbe00a1e8319e06e361", + b"d46158da3c64439f8f176fd5e3d5ac59dcaa6c0835715c5ef30abedf34b13c59", + b"693d18266ec508515b9a3839e5336918d41ec7891feadd684b49c03093bd2061", + b"440b6a14be7bc2a1ec475a197fe4bea720d1214883e4d1e2029cf0f75fcdba29", + b"ca8e30319508df21a851a06f543f59bf6fd3b241e5a52b36fb674b0d9d5e8f67", + b"fa4f327635ff5c7051d6968daf5ffcc827bbd0da1e1c56d59918895bc418be98", + b"0e6d0dd75bc42aa6e245a212ff027bc701a7ef61179b335365a29f545bcf45e0", + b"57a08bef7822c2111ac79062240e725611322543f10758763c1c6467021c4fe7", + b"29768d5fb640a46e0129303128669eb3c7fb357ef1c49506f6300cc8e451d5e2", + b"009a868f1065ccb4227dc28084484263495ad90378dc928fc61d361eef2d072e", + b"6808e39e343af0fd53309fc02b05da1a0e68b87d5cd434e94521c635a78d7fdb", + b"8fe1668eba03be5444e0956396ac9d81ac1c4a7beb6a01151ad679f5ca0cd206", + b"01f41c1cc6d9d260ec3d938d681fc476415aae96a318862a492ba1a56f1b0a88", + b"3100bc758eeccb959376b334f999a2b4e5fcced5b5d4956510a86b654f1f0c04", + b"6225330aedced9909d4a190dba55e807624f44e7c6e0c50cac5e4ccf8e2e0029", + b"783111276f7218e3e525c83cdf117e2d5ce251f6a04beabb65ef565f513a9301", + b"4b365ab05720ad517f46df03ff441f9e0769a2ce5279663b7d90eb7d980625ef", + b"52431871c39a881c63df82860e32ccf05c1addbc630aaa580733f2e6a2fff5a6", + b"f017486486b0e308a10862e910f22545c29670daa26bf0c6791827a7f9f625a4", + b"85efd1eface951759a4642e882d0ef8d0be58afd483e0945d03a7a35fac789ac", + b"880872df19c7ff14105ba59cebc07d9e9d7e67f4896a14acae5346c66c6ce2da", + b"85ba2aa3634c619d604a964d62ae3c97f9eba7fcb7e4db2ebfa2bd23338c2d60", + b"5aa9736d405016676878fcc63e84b286cfbc843799ea786d089b2200281d5a5c", + b"30f43d84f3d5ffac60323a126fe321c6cc1e9c440249a8d69abe172494cba7ae", + b"7dce62ce40f8f4cda666341730e7dfcde8839eed4236c58ae273e6687d229d85", + b"83d670a41779f63fad0ece766a19920e0cfdd9d02f5a5900c888de21f6ae0526", + b"bb2acf0d6d39e58b09204dedcc2fe68ad829ba471a077e6e03246d8a0b0c0858", + b"1f94db9ee970a9fcc9089865eb2aa485765345f6d4de54815507ce363bc20711", + b"b4bceedc935c574935117f7ad280a7d858da7ca5a6b0920b4975111206fcac77", + b"b9da394c337e9aa150c12e54c574978773ff953270f5aefde88a766e9874c260", + b"1769fcb5d31c8c09868e4e3dcd9db92b4cccefb5660e72bfc52159fda9da8518", + b"d8ceb568f85e30cb48cabf8e84d577369033511cbbdac6a7996cf9f397c2e203", + b"64c35795b87cbb02afcdc6a5e6bacb10d98cdd1bd810ccf12c847fbdcf4d2634", + b"76ccd4e2b71a826128b60bc9fe33613d82f0ae1f57fa192f107ed54d25a842e0", + b"095450e0f61a4201bb2197371247bb7ced09056d86e901202ecc561c3568e032", + b"0e928f8e201fd3019f11037bf164cc3d719ecf08e6eac985f429702c41f25d0a", + b"135f37174ff19409cef67f3263511ed9286901090eec2b54d9444036308a72cf", + b"51eafe6978c32396022a31230b8b9120ac7bd79ad7a3303e6b6979063275c9b8", + b"fd2fb9443156429b2bc042248bf022f18ee6ed11fefea222893c49e8fef6a17e", + b"ed9a240e63644aa55c71fb339d79e1a38de71002c30baf14af38359c513de2e0", + b"c925e74d4740558277a55fe57ba88ed05ce8f5d5c35c19e7228adc09351ecfa4", + b"cd99660a7c2aded095152bc6d5fa160077355819fbf421acb95ab39ad8c27862", + b"42cf7593a535a1e79299c2839f4e0ffba5b429cb0df6c77d3fe86b6a1535d505", + b"348c6014dcb0cd583862c09ad24aa5d93e81e2f6ca4b04f4e77e50067f34e625", + b"8b00c1d89bd10b24bda2c8f6e45c4112112baacab29d0330b377d811a7b0184e", + b"dfe3960732f8e5958085a581859f6ccf40169df965bb8ff5feb4b0229a02c6f8", + b"de0aa4189c97d17df38c0ea3bc9376287076939afd515336d5d7d851f81f2517", + b"543190454858ff5396a1de3f753f84bdd2869fd2b59e3a89f090ce06bd94d626", + b"541ddbe00a0237c5c8bd043e6cced9ce78209dd83b057272cd46c2cc2f39a88c", + b"11c82017445f7e295d4c734a40cf28793df855dd321d507d4a0f3e212293ca2b", + b"cda1ecd6ca2f3858352985b4833170589e17e1f7f464e279cc78051911b8e8d3", + b"5feb471949318b26898b3339ac7a66e464a752223cf764aa0af9292fe087c39a", + b"eb48e5de63ecd22f7b305c7e74ce8cc714ab858cf834cf485727868e017e473e", + b"03428482b5208a35032777c7d628a6c8f3b07c5c87c641ddfb3adf46ffefe449", + b"a8e0cd1dfcf6e1a29ba032d3889fca2f25e3fad1a9b1c36e4978e48945b06092", + b"6cf4280b75da3bb22ddee9a9a3fdd1d0cd04627c00f73b608e44be9ce84869db", + b"9fa7648f23c405938b6fb8eaf6a24c476dafd05625a9a8f6c52e1abb9d432a76", + b"2906f0a6276e9a1ba4fbca2f335d9f7d3c331684814ac5407145d86891ba37f7", + b"1b667ee18a909e3f828f2bd6a162e3aa1361f3801b7cbc863b1b54a1ccfbf580", + b"a75e75962b3f950f7718839ef06cc09112806dbf88b142e369cf1ef99069c226", + b"71b2e2f6d66f13b919a842843b201c25249ff4b4caa4c7ab079b2980de8a18f6", + b"c4a9e73df196f876f5a3af1c8836504ee61daac5d9e15cc043a511310c22ddd8", + b"88e3303a6a2ab03430652fea942ae7cd96618fae4addcf8e92989d542777e496", + b"9a727095299564e2f5e1598f1a4095a2a000cb9196fe4eb13447932af777be3c", + b"5643a1c72b189d462236a43afc7d0b9504cbf81b4fa9a6c9cca49bd50da299df", + b"caebc47e2204fe19e757372c2e0469a34051415a09c927e63be7747903d80af5", + b"e5c9fd4bd27e7a55958cb5f66ff88baf80ab40d9690e81a9cf03ada5bd08cd44", + b"dc606eb9a631ca3f080f6d6f610444201608f47b5b49c75c2c0ec03ba9000009", + b"bce230b32a7dc676fb98a4a3d41c4171e9e173e840b92c194fb1dc4accc079b9", + b"51542ff3b57cd2ff6b1542649f7a44dbed32194b98c3af32b6ff3eefe6b490cf", + b"703f5dea8c9e3ed75c691fc17699b9648a65a331add554fa3613ddcb7267565a", + b"d74f9f60763467ce0b35ced0630ecf58605da0ca49e058de21524b5c532b5de8", + b"e9dacaf1fefaad6d9d372affab8fb5216004fb3fa7a9b86b25ecd00e583c22a5", + b"abe2c75f1ac881b743da99c543506c4b7532f3fc445fa8cbf1df22689770d66a", + b"7be506d2fd6dc79f0b14ddfff0147c79d78d99140547efcd03d0a73819b84c5a", + b"78dbc5c946ee8aa147ccfa8b5d9231c95c4257d8c79bd219ab95d53303367309", + b"1d75ee600f2ba216d211189493d793aa610aa57ccbfc4d9a7f44194e7166a062", + b"368a4d0ae1b139d71f35ba5f917a7ad4c18e6aa51b095cb135193dcd09e299b4", + b"a9d3202db927be8f0d15d2dfb83ea09db32fec3b1fc10a6acfb91da8c3c5eaf3", + b"c2284f8efeee554cc29a8802aadd7cd88e84561d353282ee31322ed497a3336c", + b"7695648a111e2ea51013f03c9de91d81a2435a7777f303e51d027750f8381680", + b"719fc44e0f64f7da9ac5d33d9ca912fdc839bb4535c66a21f0804f2cdf800666", + b"6940c082413b0c1ced6f9cb6583588c472ff72b48b00a4fb6d2710d7ac4dad99", + b"af1f26fcf070d9f5926dd41db3c09ec6b4c3f2208775cf983330cd0ff5aa239e", + b"ae50df4404a7f46146d8112bfb1cde876e591abe5ef1640e27c4d178a84b6335", + b"50f91f48df7f0f96af954be7e1b518bac537173cea38be300d98761da1d9b10f", + b"9c9aee382b3e3417e87352bdcb48837e88335e9dd0112fc22ecf61e766a6ac43", + b"d53cee1696c613f988520cf9c923c7cb6e6933b4faf57e640867d5f45a0f2569", + ]; + + let mut input = Vec::new(); + for i in 0..128 { + assert_eq!( + hmac_256::(&input, &input), + hex::decode(hashes[i] as &[u8]).unwrap().as_slice() + ); + input.push(b'A'); + } + } + + // TODO: more tests +} diff --git a/libraries/crypto/src/lib.rs b/libraries/crypto/src/lib.rs new file mode 100644 index 0000000..bfc129e --- /dev/null +++ b/libraries/crypto/src/lib.rs @@ -0,0 +1,67 @@ +// Copyright 2019 Google LLC +// +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] +#![feature(wrapping_int_impl)] + +#[macro_use] +extern crate alloc; +extern crate subtle; +#[macro_use] +extern crate arrayref; +extern crate byteorder; +extern crate libtock; +#[macro_use] +extern crate cbor; + +pub mod aes256; +pub mod cbc; +mod ec; +pub mod ecdh; +pub mod ecdsa; +pub mod hmac; +pub mod rng256; +pub mod sha256; +pub mod util; + +// Trait for hash functions that returns a 256-bit hash. +// The type must be Sized (size known at compile time) so that we can instanciate one on the stack +// in the hash() method. +pub trait Hash256: Sized { + fn new() -> Self; + fn update(&mut self, contents: &[u8]); + fn finalize(self) -> [u8; 32]; + + fn hash(contents: &[u8]) -> [u8; 32] { + let mut h = Self::new(); + h.update(contents); + h.finalize() + } +} + +// Traits for block ciphers that operate on 16-byte blocks. +pub trait Encrypt16BytesBlock { + fn encrypt_block(&self, block: &mut [u8; 16]); +} + +pub trait Decrypt16BytesBlock { + fn decrypt_block(&self, block: &mut [u8; 16]); +} + +// Trait for hash functions that operate on 64-byte input blocks. +pub trait HashBlockSize64Bytes { + type State; + + fn hash_block(state: &mut Self::State, block: &[u8; 64]); +} diff --git a/libraries/crypto/src/rng256.rs b/libraries/crypto/src/rng256.rs new file mode 100644 index 0000000..572e21e --- /dev/null +++ b/libraries/crypto/src/rng256.rs @@ -0,0 +1,91 @@ +// Copyright 2019 Google LLC +// +// 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. + +use libtock::rng; + +// Lightweight RNG trait to generate uniformly distributed 256 bits. +pub trait Rng256 { + fn gen_uniform_u8x32(&mut self) -> [u8; 32]; + + fn gen_uniform_u32x8(&mut self) -> [u32; 8] { + bytes_to_u32(self.gen_uniform_u8x32()) + } +} + +// The TockOS rng driver fills a buffer of bytes, but we need 32-bit words for ECDSA. +// This function does the conversion in safe Rust, using the native endianness to avoid unnecessary +// instructions. +// An unsafe one-line equivalent could be implemented with mem::transmute, but let's use safe Rust +// when possible. +fn bytes_to_u32(bytes: [u8; 32]) -> [u32; 8] { + let mut result: [u32; 8] = [Default::default(); 8]; + for (i, r) in result.iter_mut().enumerate() { + *r = u32::from_ne_bytes(*array_ref![bytes, 4 * i, 4]); + } + result +} + +// RNG backed by the TockOS rng driver. +pub struct TockRng256 {} + +impl Rng256 for TockRng256 { + fn gen_uniform_u8x32(&mut self) -> [u8; 32] { + let mut buf: [u8; 32] = [Default::default(); 32]; + rng::fill_buffer(&mut buf); + buf + } +} + +// For tests on the desktop, we use the cryptographically secure thread rng as entropy source. +#[cfg(feature = "std")] +pub struct ThreadRng256 {} + +#[cfg(feature = "std")] +impl Rng256 for ThreadRng256 { + fn gen_uniform_u8x32(&mut self) -> [u8; 32] { + use rand::Rng; + + let mut rng = rand::thread_rng(); + let mut result = [Default::default(); 32]; + rng.fill(&mut result); + result + } +} + +#[cfg(test)] +pub mod test { + use super::*; + + #[test] + fn test_bytes_to_u32() { + // This tests that all bytes of the input are indeed used in the output, once each. + // Otherwise the result of gen_uniform_u32x8 wouldn't be uniformly distributed. + let bytes = b"\x00\x01\x02\x03\x04\x05\x06\x07\ + \x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\ + \x10\x11\x12\x13\x14\x15\x16\x17\ + \x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"; + #[cfg(target_endian = "big")] + let expected = [ + 0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f, 0x10111213, 0x14151617, 0x18191a1b, + 0x1c1d1e1f, + ]; + #[cfg(target_endian = "little")] + let expected = [ + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, + 0x1f1e1d1c, + ]; + + assert_eq!(bytes_to_u32(*bytes), expected); + } +} diff --git a/libraries/crypto/src/sha256.rs b/libraries/crypto/src/sha256.rs new file mode 100644 index 0000000..7ad54a8 --- /dev/null +++ b/libraries/crypto/src/sha256.rs @@ -0,0 +1,423 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::{Hash256, HashBlockSize64Bytes}; +use byteorder::{BigEndian, ByteOrder}; +use core::num::Wrapping; + +const BLOCK_SIZE: usize = 64; + +pub struct Sha256 { + state: [Wrapping; 8], + block: [u8; BLOCK_SIZE], + total_len: usize, +} + +impl Hash256 for Sha256 { + fn new() -> Self { + Sha256 { + state: Sha256::H, + block: [0; BLOCK_SIZE], + total_len: 0, + } + } + + fn update(&mut self, mut contents: &[u8]) { + let cursor_in_block = self.total_len % BLOCK_SIZE; + let left_in_block = BLOCK_SIZE - cursor_in_block; + + // Increment the total length before we mutate the contents slice. + self.total_len += contents.len(); + + if contents.len() < left_in_block { + // The contents don't fill the current block. Simply copy the bytes. + self.block[cursor_in_block..(cursor_in_block + contents.len())] + .copy_from_slice(contents); + } else { + // First, fill and process the current block. + let (this_block, rest) = contents.split_at(left_in_block); + self.block[cursor_in_block..].copy_from_slice(this_block); + Sha256::hash_block(&mut self.state, &self.block); + contents = rest; + + // Process full blocks. + while contents.len() >= 64 { + let (block, rest) = contents.split_at(64); + Sha256::hash_block(&mut self.state, array_ref![block, 0, 64]); + contents = rest; + } + + // Copy the last block for further processing. + self.block[..contents.len()].copy_from_slice(contents); + } + } + + fn finalize(mut self) -> [u8; 32] { + // Last block and padding. + let cursor_in_block = self.total_len % BLOCK_SIZE; + self.block[cursor_in_block] = 0x80; + // Clear the rest of the block. + for byte in self.block[(cursor_in_block + 1)..].iter_mut() { + *byte = 0; + } + + if cursor_in_block >= 56 { + // Padding doesn't fit in this block, so we first hash this block and then hash a + // padding block. + Sha256::hash_block(&mut self.state, &self.block); + // Clear buffer for the padding block. + for byte in self.block.iter_mut() { + *byte = 0; + } + } + + // The last 8 bytes of the last block contain the length of the contents. It must be + // expressed in bits, whereas `total_len` is in bytes. + BigEndian::write_u64(array_mut_ref![self.block, 56, 8], self.total_len as u64 * 8); + Sha256::hash_block(&mut self.state, &self.block); + + // Encode the state's 32-bit words into bytes, using big-endian. + let mut result: [u8; 32] = [0; 32]; + for i in 0..8 { + BigEndian::write_u32(array_mut_ref![result, 4 * i, 4], self.state[i].0); + } + result + } +} + +impl HashBlockSize64Bytes for Sha256 { + type State = [Wrapping; 8]; + + #[allow(clippy::many_single_char_names)] + fn hash_block(state: &mut Self::State, block: &[u8; 64]) { + let mut w: [Wrapping; 64] = [Wrapping(0); 64]; + + // Read the block as big-endian 32-bit words. + for (i, item) in w.iter_mut().take(16).enumerate() { + *item = Wrapping(BigEndian::read_u32(array_ref![block, 4 * i, 4])); + } + + for i in 16..64 { + w[i] = w[i - 16] + Sha256::ssig0(w[i - 15]) + w[i - 7] + Sha256::ssig1(w[i - 2]); + } + + let mut a = state[0]; + let mut b = state[1]; + let mut c = state[2]; + let mut d = state[3]; + let mut e = state[4]; + let mut f = state[5]; + let mut g = state[6]; + let mut h = state[7]; + + for (i, item) in w.iter().enumerate() { + let tmp1 = + h + Sha256::bsig1(e) + Sha256::choice(e, f, g) + Wrapping(Sha256::K[i]) + *item; + let tmp2 = Sha256::bsig0(a) + Sha256::majority(a, b, c); + + h = g; + g = f; + f = e; + e = d + tmp1; + d = c; + c = b; + b = a; + a = tmp1 + tmp2; + } + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + state[5] += f; + state[6] += g; + state[7] += h; + } +} + +impl Sha256 { + // SHA-256 constants. + #[allow(clippy::unreadable_literal)] + const H: [Wrapping; 8] = [ + Wrapping(0x6a09e667), + Wrapping(0xbb67ae85), + Wrapping(0x3c6ef372), + Wrapping(0xa54ff53a), + Wrapping(0x510e527f), + Wrapping(0x9b05688c), + Wrapping(0x1f83d9ab), + Wrapping(0x5be0cd19), + ]; + + #[allow(clippy::unreadable_literal)] + const K: [u32; 64] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, + 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, + 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, + 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, + 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, + 0xc67178f2, + ]; + + // SHA-256 helper functions. + #[inline(always)] + fn choice(e: Wrapping, f: Wrapping, g: Wrapping) -> Wrapping { + (e & f) ^ (!e & g) + } + + #[inline(always)] + fn majority(a: Wrapping, b: Wrapping, c: Wrapping) -> Wrapping { + (a & b) ^ (a & c) ^ (b & c) + } + + #[inline(always)] + fn bsig0(x: Wrapping) -> Wrapping { + x.rotate_right(2) ^ x.rotate_right(13) ^ x.rotate_right(22) + } + + #[inline(always)] + fn bsig1(x: Wrapping) -> Wrapping { + x.rotate_right(6) ^ x.rotate_right(11) ^ x.rotate_right(25) + } + + #[inline(always)] + fn ssig0(x: Wrapping) -> Wrapping { + x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3) + } + + #[inline(always)] + fn ssig1(x: Wrapping) -> Wrapping { + x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10) + } +} + +#[cfg(test)] +mod test { + use super::*; + extern crate hex; + + #[test] + fn test_choice() { + assert_eq!( + Sha256::choice( + Wrapping(0b00001111), + Wrapping(0b00110011), + Wrapping(0b01010101) + ), + Wrapping(0b01010011) + ); + } + + #[test] + fn test_majority() { + assert_eq!( + Sha256::majority( + Wrapping(0b00001111), + Wrapping(0b00110011), + Wrapping(0b01010101) + ), + Wrapping(0b00010111) + ); + } + + #[test] + fn test_hash_empty() { + assert_eq!( + Sha256::hash(&[]), + hex::decode("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + .unwrap() + .as_slice() + ); + } + + #[test] + fn test_update_for_various_splits() { + // Test vector generated with the following Python script: + // + // import hashlib + // print(hashlib.sha256('A' * 512).hexdigest()) + // + let input = vec![b'A'; 512]; + let hash = hex::decode("32beecb58a128af8248504600bd203dcc676adf41045300485655e6b8780a01d") + .unwrap(); + + for i in 0..512 { + for j in i..512 { + let mut h = Sha256::new(); + h.update(&input[..i]); + h.update(&input[i..j]); + h.update(&input[j..]); + assert_eq!(h.finalize(), hash.as_slice()); + } + } + } + + #[test] + fn test_hash_for_various_lengths() { + // This test makes sure that the padding is implemented properly. + // + // Test vectors generated with the following Python script: + // + // import hashlib + // for n in range(128): + // print('b"' + hashlib.sha256('A' * n).hexdigest() + '",') + // + let hashes: [&[u8; 64]; 128] = [ + b"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + b"559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd", + b"58bb119c35513a451d24dc20ef0e9031ec85b35bfc919d263e7e5d9868909cb5", + b"cb1ad2119d8fafb69566510ee712661f9f14b83385006ef92aec47f523a38358", + b"63c1dd951ffedf6f7fd968ad4efa39b8ed584f162f46e715114ee184f8de9201", + b"11770b3ea657fe68cba19675143e4715c8de9d763d3c21a85af6b7513d43997d", + b"69dc6c3210e25e62c5938ff4e841e81ce3c7d2cde583553478a77d7fcb389f30", + b"0f0cf9286f065a2f38e3c4e4886578e35af4050c108e507998a05888c98667ea", + b"c34ab6abb7b2bb595bc25c3b388c872fd1d575819a8f55cc689510285e212385", + b"e5f9176ecd90317cf2d4673926c9db65475b0b58e7f468586ddaef280a98cdbd", + b"1d65bf29403e4fb1767522a107c827b8884d16640cf0e3b18c4c1dd107e0d49d", + b"dd20088919031875b7bcca29995545dd40ca994be0558183f9b942b51b3b2249", + b"0592cedeabbf836d8d1c7456417c7653ac208f71e904d3d0ab37faf711021aff", + b"3461164897596e65b79bc0b7bee8cc7685487e37f52ecf0b34c000329675b859", + b"14f99c4b0a6493e3a3f52022cd75276b4cff9a7c8eef74793267f687b600af96", + b"6f9f84c09a5950e1ea7888f17922a69e5292dcbcb1e682ddfc977a9b4ea1a8c0", + b"991204fba2b6216d476282d375ab88d20e6108d109aecded97ef424ddd114706", + b"444074d5328d52b4e0036d37b1b6ea0a9fe3b0c96872d1157fbf01b6fdb2ce8d", + b"d273c6b6de3f5260e123348e0feb270126fa06e164bb82818df7c71b30ab0ef5", + b"234b7f9389f9b521f407805760775940d79a48188338d02a1fe654e826a83f69", + b"edfcaac579024f574adbcaa3c13e4fd2b7f1797826afe679f2144af2cb5c062d", + b"f48de1653fdfa9b637b7fb4da9c169a1f2be6a1ec001e3d2cca44c669a693ecf", + b"8a5bdb4cc15164126c6ef2668de9dd240d299ce6397a42c95a9411b93d080ed8", + b"1786ac1492c6c922c2734e4d3d8e9b030cfba291a72bc135989c49fc31171ac2", + b"1bda9f0aed80857d43c9329457f28b1ca29f736a0c539901e1ba16a909eb07b4", + b"6724431fc312ba42c98b38b8595a49749419526aa89722c77a85c6c813dfdb5a", + b"06f469c97c14e84c74853bb96aa79305eb4f6635291bf1202c4fdadb82706204", + b"568f214d529544bf4430513c2993495a5b434611533c63d1cf095b51c5e1f8af", + b"c84f7630cbe823fc4d80f605b98294592f15b14db1f78d6f18e686c1f8cb5ded", + b"a7951e0ca2e9612a985a36747309822a67a9b8c1a5abd848c03e82216c85f1b3", + b"37b9403cf88cc2639d0a118d757a43a0ff6d4871823707ab6a8bb56bc68e8e79", + b"55ee740f58335c97d42c32125218eb7c325fbe34206912f1aa7af7fd6580c9a1", + b"22a48051594c1949deed7040850c1f0f8764537f5191be56732d16a54c1d8153", + b"5d873590851b7b00b60490c8e6966b3409c385adcc9590d801f0e03e268b5ba5", + b"1e98a405718c430a4067d75125015a947a971449bc433b078418438c48bc6046", + b"015c50632207f69408c05d20e36facfad9bde74c727f933023f54cd6e8b87372", + b"a3b99d59dbb025726312e812c2821cfbe55189f515414bdabd5e3d284c8ad6f9", + b"7d24c321bfb2a5b6d2c7a3c2948855cef421d08352dc296ed95c6f645fcce441", + b"876fc5bf6bde065afea543aceb645ce17ffe3c9d8df9c6073ab31f3a562f4257", + b"b9b515854e040b8de31d85d597aba28db4467fd0a7d6eb77a31005f4a67a8fb3", + b"f0a2fb80ac0699075fb6c7b0ee2bcc204a1d909ee3149571216ec9cc1d4b9f8e", + b"b78244167af116f2b3597b4a81421bd2b28f3d8bf616025a5ae424f689fd7632", + b"d85ce644bf4e82cee032eaa5c3d9030a090276d9bae3703112bdfc6f8fdde307", + b"0f007385b6f9d4b7eeb2748605afe1a984a0a3bfa3f014d09e2a784ce9e5cd1a", + b"b06b3f20c246db70a136e3ae4787d0df96db4f693d215c21883d3c19700fb276", + b"ac752ced452069c55c7567a0717b87615824c568dc98c8626ac85fc34b234c3b", + b"91a07088de2d0fe9f31567b05d290e65feb06758d000ec463f7f5a6e82ce00a5", + b"abf6c5d1b6512e188f1da6a72e974f7b98b5bac62453f1748c8f9ab180803fdb", + b"4739dcdbab0c377161c539af55d47c5c90c87807d0728aabe91697b66e29096c", + b"ff85f0693c8e6bbeeaa1f90c32e1159b9b545d830ffe58cd80cb94d9d8140d21", + b"509ddb85fdf92f197d32570c005cdcb6dffa398f088bd1a013459f6fb1f730ef", + b"1d31616e307323bd80775ae7483fce654a3b65bced7134c22e179a2e25155009", + b"3e1ae21112ec8fad05e3676e1940da52d56771162142aac4d73743e7df70b686", + b"5f2671f97427c8873e5af72686d244e4c8126a4f618983bae880a48a834a0607", + b"2d0009d7df28cdc6b5a4c36063d97415a8fe99515317458fcb0b0e2a821dbcb9", + b"8963cc0afd622cc7574ac2011f93a3059b3d65548a77542a1559e3d202e6ab00", + b"6ea719cefa4b31862035a7fa606b7cc3602f46231117d135cc7119b3c1412314", + b"a00df74fbdadd9eb0e7742a019e5b2d77374de5417eba5b7a0730a60cce5e7bf", + b"cee244d999f8cf49f2a4ee4d89695130c9c95c33538cedf0306881ebd42714d2", + b"5b29354ee33cba5b924ded5e3c873a76e1d12527d824ace01ff9683d24e06816", + b"c5fb235befd875b915fa6c4702a7abb93cacf3d7c414b71cbeff9e1b0a9fbd41", + b"0ae45129ef1edf64309559f6cb7bb0af16eff14ad82f24d55fa029c1b4144078", + b"5a2aafcacb9828e41fb7c8f8098952638645874b3a8ca45d2523fb2d5fc7166d", + b"1b58d00f5b1fbd2a1884d666a2be33c2fa7463dff32cd60ef200c0f750a6b70f", + b"d53eda7a637c99cc7fb566d96e9fa109bf15c478410a3f5eb4d4c4e26cd081f6", + b"836203944f4c0280461ad73d31457c22ba19d1d99e232dc231000085899e00a2", + b"fd8afe9151793a84a21af054ba985d1486a705561e2a50d4a50f814664f5e806", + b"f495547fca5a5a2c40dccebefe40160efb8bc2888e8afef712b096b5f2585b44", + b"ba31b89f9486439fdf551f597fede0c10260f9b404866dba4a6555375f486359", + b"46f23cc7ccba8af67978bea568e63cd045be72aba974132b1b14cc59277329f1", + b"01d3a187638cc1a7740a74fbeb57aa2648dbdec42d497321912bf393d283ccd1", + b"96b437b3df7c62fc877a121b087899f5e36a58f6d87ba52d997e92bb016aa575", + b"6a6d691ac9ba70955046757cd685b6257773ff3ad93f43d4d4812c5b106f4b5b", + b"beb869adc22a7e8fbd5af12cbf3ad36dd92dca6ebf52ef3441ed6cd0dff24dc6", + b"0f40cb2f3661d73dfaec511e8ebea082fb1f77db45bf8c9ba7c9708da6ba6301", + b"ddd5d1ecc7af6a5b0d18e0825004d3bc9d52e2cdf14bc00c7474f16941a64acc", + b"6a10b9a8a33d7814ce73679ace5c43657aa6d63169ce215fd85177c77a94147d", + b"9d887d47c78267827dac4afb2cbdcc593d1b89c1d0c1f22c3800cae7916962cd", + b"e45ca598e970afb0f1f57bd34e87065839d2fac524421048fbec489f68e1fd0d", + b"1581baebc5f9dcfd89c658b3c3303203fc0e2f93e3f9e0b593d8b2b8112c6eda", + b"d9b1f3e2c6d528668a73f22575c44ed9f98d9c684964761b621417efd80d7a60", + b"9feacd760dfda20e5e0accf9ddeb8b5c01276a56dc3518046a26f5276fe15041", + b"6aacf5279e24979684fab16fb5495c3ac1dfcf7138b0825376af83473d07cae9", + b"cd2f0deb953014ee400eddef094602d9676e0fd2269d22818f0d5bc198d44d8d", + b"ff9265df14681e44d170fd2b10c6cdf3991f731601d6b89cafe39691d3b42559", + b"6c99e32b005a3a4956b9406ab15411e666c7f67982db170ae1fb111ec634b9c4", + b"e1659ad54063a379f77fee108a376a6a7d5ae3d0c437bf847203963bd0078dfc", + b"572d07a66fccf05d5f73c913552e12d9ffb39a15d01a8fd48cd6aaaab86f4f14", + b"ca97d312ef8551820844548f300f9528f27d53f6ad3910ed2709f2b35c9591f3", + b"97654dc78f4f7d4cec4b4870e6ee0a87abacc89337ed0629e2e511e4466df56d", + b"fde923c1ed5e5cd32c629bdf341db32c0f72ba8f1e2afd9c194e87e0e3d9da5f", + b"d6624a66f3bcc4adef8a17abf9eeb1fbf23746165b2f90f9cb3a679a58e4958e", + b"8676909e9578a790f84be31fe94f4d22488f912b754ee816ba0a5c4a392305a5", + b"57f65fd8a95ff738b95dba0f1606025535e34591f1b58b00d33958093808360c", + b"f6afcaf794fe0e04d6ec18bbde55412a60c0c5ef55e75223b817e97f208bbccc", + b"6121f27b52c1f17ddce365143ba58a720fa303707faa32a4e5e89029f34ac618", + b"69d62c062d67d8d2ce9068c1898fb9746c911839aa88ad1628d090f4c8e47f05", + b"eb9f8b69313e19e14b1043b3cac05d18d40321536ad485be8145007aefa9295d", + b"b1cfb0f511886ca07ade919740ca95e1b3d998ba7cb66ba2badd53be28f5f509", + b"d0118863549f990558685da9090ca8eae8c809c5545c4aa85f8e5eec413b2555", + b"d82c6aa133a0fc25b087f46ad7ed2a3042772e612e015571e61753ff55ba6da8", + b"aac76dab773c00c8ad4bb128147945c70798eae5a2511fef01e853c6e3051ab9", + b"ccd77d7adb6178ee3e3560ba4583044a36b296257ae4c5cfead96d46af31fccd", + b"3c4f48a886b2de7e908d6a626074e7515265cc9d1188c161cf159fd376d3d5f8", + b"7f9578a31905e95a16cc9d3e7b57dc3158a23dccf359a1f2cf09e73eb13e5cde", + b"770492ebaee89a20d19f9972c3e3d0c7d51c9baaedf06fdfe9a7b69da3394779", + b"893785aaddea396621c31dc5d465e2775cbc6b7423dc3498e80aa5da7a6a819d", + b"1b2b69fbec485ef3f347ee6dc9c87d73505e45b5c9b02599b823ac94f5d642a1", + b"724b4b3d3e8ac7588561ca00eec11693f6b85c03bb6b1302d458a7a4ce4b39e4", + b"6a30ef4094128b6fa463b70cb21d141da92711d80ea94c9b73fb8a0471cc49a9", + b"50968cf735e3f6a47834ae3745816234f72fd156aef1bec4b6a7d3a3151773bc", + b"3ed01dd816dc93ea2d445681df11aa24e9fd1441de429eb0ee7816ccc09a2b7a", + b"64bdc48c731313c7b37c1f1d13d6265ac7a2604ff630b50f591a86e610cb3005", + b"96666c386cf99a74ec9eb55a5545aa90a3e53a8bbbe74cd3334b32d4968a3214", + b"33555a41335654a29d5b7799bf180915e09095d21991dacf071583957b9e3f35", + b"ffd391d554ce0672ae818a149dd55325f4cb933c97017b8148934474355d5a88", + b"cf2050114ccaefd8a0ea6cf31d85e0232eadc8fd61277ff16496d2234b55c7d7", + b"e42142b4243d5a2c59a2977d0385d49eab288085f8d38ead3ae5d87145c562ec", + b"3bb810492422cc5c7466d86dcd8095b0d87e97634656a3fa5fe2270a2244c16b", + b"17d2f0f7197a6612e311d141781f2b9539c4aef7affd729246c401890e000dde", + b"a4f4256159ea6fb23b27eb8c5eb9cfb9083475985f355a85c78de8f2fef2b3ac", + b"a36c4cf85204c67047c00d5dcc16677978839af0f0fde7ff973c98b66e244552", + b"4a596559f450ce5e3a777d952d8d2ed8611e9f3facc8400483371f6eadc4bdb2", + b"64855e54c94d14ab53afc6109d3c0033c665fab85b57c0e7d4e8da55b3b26952", + b"a2ee2228d4798988ce3ac273c0cd8b9bbc4e3e58413eb22dfbe6395758659a2b", + b"35c28ee2e25f5ad70384f1ca9723f520c955fb5fe9f2e56b9dc809479a9ca8cc", + b"da3f6a7f55f821760330dd14495e68e7d153b05e472d38459d4728d63ad9df26", + b"026134f6117e45a37c5c2dc2f330bdd274c6dc087526b91ecec4d6dac9bb7346", + ]; + + let mut input = Vec::new(); + for i in 0..128 { + assert_eq!( + Sha256::hash(&input), + hex::decode(hashes[i] as &[u8]).unwrap().as_slice() + ); + input.push(b'A'); + } + } + + // TODO: more tests +} diff --git a/libraries/crypto/src/util.rs b/libraries/crypto/src/util.rs new file mode 100644 index 0000000..47a5ec5 --- /dev/null +++ b/libraries/crypto/src/util.rs @@ -0,0 +1,41 @@ +// Copyright 2019 Google LLC +// +// 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. + +#[cfg(test)] +use subtle::CtOption; + +pub type Block16 = [u8; 16]; + +#[inline(always)] +pub fn xor_block_16(block: &mut Block16, mask: &Block16) { + for i in 0..16 { + block[i] ^= mask[i]; + } +} + +#[cfg(test)] +pub trait ToOption { + fn to_option(self) -> Option; +} + +#[cfg(test)] +impl ToOption for CtOption { + fn to_option(self) -> Option { + if bool::from(self.is_some()) { + Some(self.unwrap()) + } else { + None + } + } +} diff --git a/libraries/crypto/tests/aesavs.rs b/libraries/crypto/tests/aesavs.rs new file mode 100644 index 0000000..e3798e4 --- /dev/null +++ b/libraries/crypto/tests/aesavs.rs @@ -0,0 +1,103 @@ +// Copyright 2019 Google LLC +// +// 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. + +/// Test vectors for AES-ECB from NIST's validation suite. +/// +/// See also https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/aes/AESAVS.pdf +#[macro_use] +extern crate arrayref; +extern crate hex; +extern crate regex; + +use crypto::{aes256, Decrypt16BytesBlock, Encrypt16BytesBlock}; +use regex::Regex; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; + +#[test] +fn aesavs() { + // These data files are taken from https://csrc.nist.gov/groups/STM/cavp/documents/aes/KAT_AES.zip. + test_aesavs_file("tests/data/ECBVarKey256.rsp"); + test_aesavs_file("tests/data/ECBVarTxt256.rsp"); +} + +fn test_aesavs_file>(path: P) { + // Implements some custom parsing for NIST's test vectors. + let re_count = Regex::new("^COUNT = ([0-9]+)$").unwrap(); + let re_key = Regex::new("^KEY = ([0-9a-f]{64})$").unwrap(); + let re_plaintext = Regex::new("^PLAINTEXT = ([0-9a-f]{32})$").unwrap(); + let re_ciphertext = Regex::new("^CIPHERTEXT = ([0-9a-f]{32})$").unwrap(); + + let file = BufReader::new(File::open(path).unwrap()); + let mut lines = file.lines(); + + loop { + let line = lines.next().unwrap().unwrap(); + if line == "[ENCRYPT]" { + break; + } + } + + for i in 0.. { + // empty line + let line = lines.next().unwrap().unwrap(); + assert_eq!(line, ""); + + let line = lines.next().unwrap().unwrap(); + if line == "[DECRYPT]" { + // Skip the decryption tests, they are the same as the encryption tests. + break; + } + // "COUNT = " + let captures = re_count.captures(&line).unwrap(); + let count = captures.get(1).unwrap().as_str().parse::().unwrap(); + assert_eq!(count, i); + + // "KEY = " + let line = lines.next().unwrap().unwrap(); + let captures = re_key.captures(&line).unwrap(); + let key = hex::decode(captures.get(1).unwrap().as_str()).unwrap(); + assert_eq!(key.len(), 32); + + // "PLAINTEXT = " + let line = lines.next().unwrap().unwrap(); + let captures = re_plaintext.captures(&line).unwrap(); + let plaintext = hex::decode(captures.get(1).unwrap().as_str()).unwrap(); + assert_eq!(plaintext.len(), 16); + + // "CIPHERTEXT = " + let line = lines.next().unwrap().unwrap(); + let captures = re_ciphertext.captures(&line).unwrap(); + let ciphertext = hex::decode(captures.get(1).unwrap().as_str()).unwrap(); + assert_eq!(ciphertext.len(), 16); + + { + let encryption_key = aes256::EncryptionKey::new(array_ref![key, 0, 32]); + let mut block: [u8; 16] = [Default::default(); 16]; + block.copy_from_slice(&plaintext); + encryption_key.encrypt_block(&mut block); + assert_eq!(&block, ciphertext.as_slice()); + } + + { + let encryption_key = aes256::EncryptionKey::new(array_ref![key, 0, 32]); + let decryption_key = aes256::DecryptionKey::new(&encryption_key); + let mut block: [u8; 16] = [Default::default(); 16]; + block.copy_from_slice(&ciphertext); + decryption_key.decrypt_block(&mut block); + assert_eq!(&block, plaintext.as_slice()); + } + } +} diff --git a/libraries/crypto/tests/asn1.rs b/libraries/crypto/tests/asn1.rs new file mode 100644 index 0000000..6056253 --- /dev/null +++ b/libraries/crypto/tests/asn1.rs @@ -0,0 +1,232 @@ +// Copyright 2019 Google LLC +// +// 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. + +/// A minimalist parser for ASN.1 encoded ECDSA signatures in DER form. +use arrayref::mut_array_refs; +use crypto::ecdsa; +use std::convert::TryFrom; +use std::io::Read; + +#[derive(Debug)] +pub enum Asn1Error { + IoError(std::io::Error), + InvalidTagClass, + InvalidLongFormEncoding, + ArithmeticOverflow, + ExpectedSequenceTag(Tag), + ExpectedIntegerTag(Tag), + InvalidSequenceLen(usize), + InvalidIntegerLen(usize), + UnexpectedTrailingBytes, + InvalidSignature, +} + +impl From for Asn1Error { + fn from(e: std::io::Error) -> Asn1Error { + Asn1Error::IoError(e) + } +} + +#[derive(PartialEq, Debug)] +pub enum TagClass { + Universal = 0, + Application = 1, + ContextSpecific = 2, + Private = 3, +} + +impl TryFrom for TagClass { + type Error = Asn1Error; + + fn try_from(x: u8) -> Result { + match x { + 0 => Ok(TagClass::Universal), + 1 => Ok(TagClass::Application), + 2 => Ok(TagClass::ContextSpecific), + 3 => Ok(TagClass::Private), + _ => Err(Asn1Error::InvalidTagClass), + } + } +} + +#[allow(dead_code)] +enum UniversalTag { + Boolean = 1, + Integer = 2, + BitString = 3, + OctetString = 4, + Null = 5, + ObjectIdentifier = 6, + Utf8String = 12, + Sequence = 16, + Set = 17, + PrintableString = 19, + UtcTime = 23, + GeneralizedTime = 24, +} + +#[derive(Debug)] +pub struct Tag { + class: TagClass, + constructed: bool, + number: u64, +} + +impl Tag { + fn is_sequence(&self) -> bool { + self.class == TagClass::Universal + && self.constructed + && self.number == UniversalTag::Sequence as u64 + } + + fn is_number(&self) -> bool { + self.class == TagClass::Universal + && !self.constructed + && self.number == UniversalTag::Integer as u64 + } + + // Parse an ASN.1 tag encoded in DER form. + fn parse(input: &mut R) -> Result { + let mut buf = [0u8; 1]; + + input.read_exact(&mut buf)?; + let mut tag = buf[0]; + let class = TagClass::try_from(tag >> 6)?; + let constructed = tag & 0x20 != 0; + tag &= 0x1F; + + if tag < 31 { + // Short tag number + let number = tag as u64; + Ok(Tag { + class, + constructed, + number, + }) + } else { + // Long tag number + let mut number: u64 = 0; + loop { + input.read_exact(&mut buf)?; + let x = buf[0]; + if number == 0 && x == 0 { + return Err(Asn1Error::InvalidLongFormEncoding); + } + if number >> ((8 * std::mem::size_of::()) - 7) != 0 { + return Err(Asn1Error::ArithmeticOverflow); + } + number = (number << 7) | (x & 0x7F) as u64; + if (x & 0x80) == 0 { + if number < 31 { + return Err(Asn1Error::InvalidLongFormEncoding); + } + return Ok(Tag { + class, + constructed, + number, + }); + } + } + } + } +} + +// Parse an ASN.1 length encoded in DER form. +fn parse_len(input: &mut R) -> Result { + let mut buf = [0u8; 1]; + + input.read_exact(&mut buf)?; + let first_byte = buf[0]; + if (first_byte & 0x80) == 0 { + // Short form + Ok(first_byte as usize) + } else { + // Long form + let nbytes = (first_byte & 0x7F) as usize; + + let mut length: usize = 0; + for _ in 0..nbytes { + input.read_exact(&mut buf)?; + let x = buf[0]; + if length == 0 && x == 0 { + return Err(Asn1Error::InvalidLongFormEncoding); + } + if length >> (8 * (std::mem::size_of::() - 1)) != 0 { + return Err(Asn1Error::ArithmeticOverflow); + } + length = (length << 8) | x as usize; + } + if length < 0x80 { + return Err(Asn1Error::InvalidLongFormEncoding); + } + Ok(length) + } +} + +fn parse_coordinate(mut input: R, bytes: &mut [u8; 32]) -> Result { + let tag = Tag::parse(&mut input)?; + if !tag.is_number() { + return Err(Asn1Error::ExpectedIntegerTag(tag)); + } + let len = parse_len(&mut input)?; + if len > 33 { + return Err(Asn1Error::InvalidIntegerLen(len)); + } + + let mut buf = vec![0; len]; + input.read_exact(&mut buf)?; + + if len == 33 { + if buf.remove(0) != 0 { + return Err(Asn1Error::InvalidIntegerLen(len)); + } + } + + bytes[(32 - buf.len())..].copy_from_slice(&buf); + Ok(len) +} + +pub fn parse_signature(mut input: R) -> Result { + let tag = Tag::parse(&mut input)?; + if !tag.is_sequence() { + return Err(Asn1Error::ExpectedSequenceTag(tag)); + } + let len = parse_len(&mut input)?; + + let mut bytes = [0; 64]; + let (xbytes, ybytes) = mut_array_refs![&mut bytes, 32, 32]; + + let xlen = parse_coordinate(&mut input, xbytes)?; + let ylen = parse_coordinate(&mut input, ybytes)?; + + // Each coordinate has, besides (x|y)len bytes of integer, one byte for the tag and one + // byte for the length (the length is at most 33 and therefore encoded on one byte). + if len != xlen + ylen + 4 { + return Err(Asn1Error::InvalidSequenceLen(len)); + } + + // Check for unexpected bytes at the end. + let is_eof = { + let mut buf = [0u8; 1]; + match input.read_exact(&mut buf) { + Ok(_) => false, + Err(e) => e.kind() == std::io::ErrorKind::UnexpectedEof, + } + }; + if !is_eof { + return Err(Asn1Error::UnexpectedTrailingBytes); + } + + ecdsa::Signature::from_bytes(&bytes).ok_or(Asn1Error::InvalidSignature) +} diff --git a/libraries/crypto/tests/data/ECBVarKey256.rsp b/libraries/crypto/tests/data/ECBVarKey256.rsp new file mode 100644 index 0000000..bfdd1a8 --- /dev/null +++ b/libraries/crypto/tests/data/ECBVarKey256.rsp @@ -0,0 +1,2571 @@ +# CAVS 11.1 +# Config info for aes_values +# AESVS VarKey test data for ECB +# State : Encrypt and Decrypt +# Key Length : 256 +# Generated on Fri Apr 22 15:11:30 2011 + +[ENCRYPT] + +COUNT = 0 +KEY = 8000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = e35a6dcb19b201a01ebcfa8aa22b5759 + +COUNT = 1 +KEY = c000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = b29169cdcf2d83e838125a12ee6aa400 + +COUNT = 2 +KEY = e000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d8f3a72fc3cdf74dfaf6c3e6b97b2fa6 + +COUNT = 3 +KEY = f000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 1c777679d50037c79491a94da76a9a35 + +COUNT = 4 +KEY = f800000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 9cf4893ecafa0a0247a898e040691559 + +COUNT = 5 +KEY = fc00000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 8fbb413703735326310a269bd3aa94b2 + +COUNT = 6 +KEY = fe00000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 60e32246bed2b0e859e55c1cc6b26502 + +COUNT = 7 +KEY = ff00000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ec52a212f80a09df6317021bc2a9819e + +COUNT = 8 +KEY = ff80000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = f23e5b600eb70dbccf6c0b1d9a68182c + +COUNT = 9 +KEY = ffc0000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a3f599d63a82a968c33fe26590745970 + +COUNT = 10 +KEY = ffe0000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d1ccb9b1337002cbac42c520b5d67722 + +COUNT = 11 +KEY = fff0000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = cc111f6c37cf40a1159d00fb59fb0488 + +COUNT = 12 +KEY = fff8000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = dc43b51ab609052372989a26e9cdd714 + +COUNT = 13 +KEY = fffc000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 4dcede8da9e2578f39703d4433dc6459 + +COUNT = 14 +KEY = fffe000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 1a4c1c263bbccfafc11782894685e3a8 + +COUNT = 15 +KEY = ffff000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 937ad84880db50613423d6d527a2823d + +COUNT = 16 +KEY = ffff800000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 610b71dfc688e150d8152c5b35ebc14d + +COUNT = 17 +KEY = ffffc00000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 27ef2495dabf323885aab39c80f18d8b + +COUNT = 18 +KEY = ffffe00000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 633cafea395bc03adae3a1e2068e4b4e + +COUNT = 19 +KEY = fffff00000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6e1b482b53761cf631819b749a6f3724 + +COUNT = 20 +KEY = fffff80000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 976e6f851ab52c771998dbb2d71c75a9 + +COUNT = 21 +KEY = fffffc0000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 85f2ba84f8c307cf525e124c3e22e6cc + +COUNT = 22 +KEY = fffffe0000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6bcca98bf6a835fa64955f72de4115fe + +COUNT = 23 +KEY = ffffff0000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2c75e2d36eebd65411f14fd0eb1d2a06 + +COUNT = 24 +KEY = ffffff8000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = bd49295006250ffca5100b6007a0eade + +COUNT = 25 +KEY = ffffffc000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a190527d0ef7c70f459cd3940df316ec + +COUNT = 26 +KEY = ffffffe000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = bbd1097a62433f79449fa97d4ee80dbf + +COUNT = 27 +KEY = fffffff000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 07058e408f5b99b0e0f061a1761b5b3b + +COUNT = 28 +KEY = fffffff800000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 5fd1f13fa0f31e37fabde328f894eac2 + +COUNT = 29 +KEY = fffffffc00000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = fc4af7c948df26e2ef3e01c1ee5b8f6f + +COUNT = 30 +KEY = fffffffe00000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 829fd7208fb92d44a074a677ee9861ac + +COUNT = 31 +KEY = ffffffff00000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ad9fc613a703251b54c64a0e76431711 + +COUNT = 32 +KEY = ffffffff80000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 33ac9eccc4cc75e2711618f80b1548e8 + +COUNT = 33 +KEY = ffffffffc0000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2025c74b8ad8f4cda17ee2049c4c902d + +COUNT = 34 +KEY = ffffffffe0000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = f85ca05fe528f1ce9b790166e8d551e7 + +COUNT = 35 +KEY = fffffffff0000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6f6238d8966048d4967154e0dad5a6c9 + +COUNT = 36 +KEY = fffffffff8000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = f2b21b4e7640a9b3346de8b82fb41e49 + +COUNT = 37 +KEY = fffffffffc000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = f836f251ad1d11d49dc344628b1884e1 + +COUNT = 38 +KEY = fffffffffe000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 077e9470ae7abea5a9769d49182628c3 + +COUNT = 39 +KEY = ffffffffff000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = e0dcc2d27fc9865633f85223cf0d611f + +COUNT = 40 +KEY = ffffffffff800000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = be66cfea2fecd6bf0ec7b4352c99bcaa + +COUNT = 41 +KEY = ffffffffffc00000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = df31144f87a2ef523facdcf21a427804 + +COUNT = 42 +KEY = ffffffffffe00000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = b5bb0f5629fb6aae5e1839a3c3625d63 + +COUNT = 43 +KEY = fffffffffff00000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3c9db3335306fe1ec612bdbfae6b6028 + +COUNT = 44 +KEY = fffffffffff80000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3dd5c34634a79d3cfcc8339760e6f5f4 + +COUNT = 45 +KEY = fffffffffffc0000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 82bda118a3ed7af314fa2ccc5c07b761 + +COUNT = 46 +KEY = fffffffffffe0000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2937a64f7d4f46fe6fea3b349ec78e38 + +COUNT = 47 +KEY = ffffffffffff0000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 225f068c28476605735ad671bb8f39f3 + +COUNT = 48 +KEY = ffffffffffff8000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ae682c5ecd71898e08942ac9aa89875c + +COUNT = 49 +KEY = ffffffffffffc000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 5e031cb9d676c3022d7f26227e85c38f + +COUNT = 50 +KEY = ffffffffffffe000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a78463fb064db5d52bb64bfef64f2dda + +COUNT = 51 +KEY = fffffffffffff000000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 8aa9b75e784593876c53a00eae5af52b + +COUNT = 52 +KEY = fffffffffffff800000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3f84566df23da48af692722fe980573a + +COUNT = 53 +KEY = fffffffffffffc00000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 31690b5ed41c7eb42a1e83270a7ff0e6 + +COUNT = 54 +KEY = fffffffffffffe00000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 77dd7702646d55f08365e477d3590eda + +COUNT = 55 +KEY = ffffffffffffff00000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 4c022ac62b3cb78d739cc67b3e20bb7e + +COUNT = 56 +KEY = ffffffffffffff80000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 092fa137ce18b5dfe7906f550bb13370 + +COUNT = 57 +KEY = ffffffffffffffc0000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3e0cdadf2e68353c0027672c97144dd3 + +COUNT = 58 +KEY = ffffffffffffffe0000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d8c4b200b383fc1f2b2ea677618a1d27 + +COUNT = 59 +KEY = fffffffffffffff0000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 11825f99b0e9bb3477c1c0713b015aac + +COUNT = 60 +KEY = fffffffffffffff8000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = f8b9fffb5c187f7ddc7ab10f4fb77576 + +COUNT = 61 +KEY = fffffffffffffffc000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ffb4e87a32b37d6f2c8328d3b5377802 + +COUNT = 62 +KEY = fffffffffffffffe000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d276c13a5d220f4da9224e74896391ce + +COUNT = 63 +KEY = ffffffffffffffff000000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 94efe7a0e2e031e2536da01df799c927 + +COUNT = 64 +KEY = ffffffffffffffff800000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 8f8fd822680a85974e53a5a8eb9d38de + +COUNT = 65 +KEY = ffffffffffffffffc00000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = e0f0a91b2e45f8cc37b7805a3042588d + +COUNT = 66 +KEY = ffffffffffffffffe00000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 597a6252255e46d6364dbeeda31e279c + +COUNT = 67 +KEY = fffffffffffffffff00000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = f51a0f694442b8f05571797fec7ee8bf + +COUNT = 68 +KEY = fffffffffffffffff80000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 9ff071b165b5198a93dddeebc54d09b5 + +COUNT = 69 +KEY = fffffffffffffffffc0000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = c20a19fd5758b0c4bc1a5df89cf73877 + +COUNT = 70 +KEY = fffffffffffffffffe0000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 97120166307119ca2280e9315668e96f + +COUNT = 71 +KEY = ffffffffffffffffff0000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 4b3b9f1e099c2a09dc091e90e4f18f0a + +COUNT = 72 +KEY = ffffffffffffffffff8000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = eb040b891d4b37f6851f7ec219cd3f6d + +COUNT = 73 +KEY = ffffffffffffffffffc000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 9f0fdec08b7fd79aa39535bea42db92a + +COUNT = 74 +KEY = ffffffffffffffffffe000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2e70f168fc74bf911df240bcd2cef236 + +COUNT = 75 +KEY = fffffffffffffffffff000000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 462ccd7f5fd1108dbc152f3cacad328b + +COUNT = 76 +KEY = fffffffffffffffffff800000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a4af534a7d0b643a01868785d86dfb95 + +COUNT = 77 +KEY = fffffffffffffffffffc00000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ab980296197e1a5022326c31da4bf6f3 + +COUNT = 78 +KEY = fffffffffffffffffffe00000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = f97d57b3333b6281b07d486db2d4e20c + +COUNT = 79 +KEY = ffffffffffffffffffff00000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = f33fa36720231afe4c759ade6bd62eb6 + +COUNT = 80 +KEY = ffffffffffffffffffff80000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = fdcfac0c02ca538343c68117e0a15938 + +COUNT = 81 +KEY = ffffffffffffffffffffc0000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ad4916f5ee5772be764fc027b8a6e539 + +COUNT = 82 +KEY = ffffffffffffffffffffe0000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2e16873e1678610d7e14c02d002ea845 + +COUNT = 83 +KEY = fffffffffffffffffffff0000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 4e6e627c1acc51340053a8236d579576 + +COUNT = 84 +KEY = fffffffffffffffffffff8000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ab0c8410aeeead92feec1eb430d652cb + +COUNT = 85 +KEY = fffffffffffffffffffffc000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = e86f7e23e835e114977f60e1a592202e + +COUNT = 86 +KEY = fffffffffffffffffffffe000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = e68ad5055a367041fade09d9a70a794b + +COUNT = 87 +KEY = ffffffffffffffffffffff000000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 0791823a3c666bb6162825e78606a7fe + +COUNT = 88 +KEY = ffffffffffffffffffffff800000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = dcca366a9bf47b7b868b77e25c18a364 + +COUNT = 89 +KEY = ffffffffffffffffffffffc00000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 684c9efc237e4a442965f84bce20247a + +COUNT = 90 +KEY = ffffffffffffffffffffffe00000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a858411ffbe63fdb9c8aa1bfaed67b52 + +COUNT = 91 +KEY = fffffffffffffffffffffff00000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 04bc3da2179c3015498b0e03910db5b8 + +COUNT = 92 +KEY = fffffffffffffffffffffff80000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 40071eeab3f935dbc25d00841460260f + +COUNT = 93 +KEY = fffffffffffffffffffffffc0000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 0ebd7c30ed2016e08ba806ddb008bcc8 + +COUNT = 94 +KEY = fffffffffffffffffffffffe0000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 15c6becf0f4cec7129cbd22d1a79b1b8 + +COUNT = 95 +KEY = ffffffffffffffffffffffff0000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 0aeede5b91f721700e9e62edbf60b781 + +COUNT = 96 +KEY = ffffffffffffffffffffffff8000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 266581af0dcfbed1585e0a242c64b8df + +COUNT = 97 +KEY = ffffffffffffffffffffffffc000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6693dc911662ae473216ba22189a511a + +COUNT = 98 +KEY = ffffffffffffffffffffffffe000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 7606fa36d86473e6fb3a1bb0e2c0adf5 + +COUNT = 99 +KEY = fffffffffffffffffffffffff000000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 112078e9e11fbb78e26ffb8899e96b9a + +COUNT = 100 +KEY = fffffffffffffffffffffffff800000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 40b264e921e9e4a82694589ef3798262 + +COUNT = 101 +KEY = fffffffffffffffffffffffffc00000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 8d4595cb4fa7026715f55bd68e2882f9 + +COUNT = 102 +KEY = fffffffffffffffffffffffffe00000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = b588a302bdbc09197df1edae68926ed9 + +COUNT = 103 +KEY = ffffffffffffffffffffffffff00000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 33f7502390b8a4a221cfecd0666624ba + +COUNT = 104 +KEY = ffffffffffffffffffffffffff80000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3d20253adbce3be2373767c4d822c566 + +COUNT = 105 +KEY = ffffffffffffffffffffffffffc0000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a42734a3929bf84cf0116c9856a3c18c + +COUNT = 106 +KEY = ffffffffffffffffffffffffffe0000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = e3abc4939457422bb957da3c56938c6d + +COUNT = 107 +KEY = fffffffffffffffffffffffffff0000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 972bdd2e7c525130fadc8f76fc6f4b3f + +COUNT = 108 +KEY = fffffffffffffffffffffffffff8000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 84a83d7b94c699cbcb8a7d9b61f64093 + +COUNT = 109 +KEY = fffffffffffffffffffffffffffc000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ce61d63514aded03d43e6ebfc3a9001f + +COUNT = 110 +KEY = fffffffffffffffffffffffffffe000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6c839dd58eeae6b8a36af48ed63d2dc9 + +COUNT = 111 +KEY = ffffffffffffffffffffffffffff000000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = cd5ece55b8da3bf622c4100df5de46f9 + +COUNT = 112 +KEY = ffffffffffffffffffffffffffff800000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3b6f46f40e0ac5fc0a9c1105f800f48d + +COUNT = 113 +KEY = ffffffffffffffffffffffffffffc00000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ba26d47da3aeb028de4fb5b3a854a24b + +COUNT = 114 +KEY = ffffffffffffffffffffffffffffe00000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 87f53bf620d3677268445212904389d5 + +COUNT = 115 +KEY = fffffffffffffffffffffffffffff00000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 10617d28b5e0f4605492b182a5d7f9f6 + +COUNT = 116 +KEY = fffffffffffffffffffffffffffff80000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 9aaec4fabbf6fae2a71feff02e372b39 + +COUNT = 117 +KEY = fffffffffffffffffffffffffffffc0000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3a90c62d88b5c42809abf782488ed130 + +COUNT = 118 +KEY = fffffffffffffffffffffffffffffe0000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = f1f1c5a40899e15772857ccb65c7a09a + +COUNT = 119 +KEY = ffffffffffffffffffffffffffffff0000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 190843d29b25a3897c692ce1dd81ee52 + +COUNT = 120 +KEY = ffffffffffffffffffffffffffffff8000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a866bc65b6941d86e8420a7ffb0964db + +COUNT = 121 +KEY = ffffffffffffffffffffffffffffffc000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 8193c6ff85225ced4255e92f6e078a14 + +COUNT = 122 +KEY = ffffffffffffffffffffffffffffffe000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 9661cb2424d7d4a380d547f9e7ec1cb9 + +COUNT = 123 +KEY = fffffffffffffffffffffffffffffff000000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 86f93d9ec08453a071e2e2877877a9c8 + +COUNT = 124 +KEY = fffffffffffffffffffffffffffffff800000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 27eefa80ce6a4a9d598e3fec365434d2 + +COUNT = 125 +KEY = fffffffffffffffffffffffffffffffc00000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d62068444578e3ab39ce7ec95dd045dc + +COUNT = 126 +KEY = fffffffffffffffffffffffffffffffe00000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = b5f71d4dd9a71fe5d8bc8ba7e6ea3048 + +COUNT = 127 +KEY = ffffffffffffffffffffffffffffffff00000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6825a347ac479d4f9d95c5cb8d3fd7e9 + +COUNT = 128 +KEY = ffffffffffffffffffffffffffffffff80000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = e3714e94a5778955cc0346358e94783a + +COUNT = 129 +KEY = ffffffffffffffffffffffffffffffffc0000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d836b44bb29e0c7d89fa4b2d4b677d2a + +COUNT = 130 +KEY = ffffffffffffffffffffffffffffffffe0000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 5d454b75021d76d4b84f873a8f877b92 + +COUNT = 131 +KEY = fffffffffffffffffffffffffffffffff0000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = c3498f7eced2095314fc28115885b33f + +COUNT = 132 +KEY = fffffffffffffffffffffffffffffffff8000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6e668856539ad8e405bd123fe6c88530 + +COUNT = 133 +KEY = fffffffffffffffffffffffffffffffffc000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 8680db7f3a87b8605543cfdbe6754076 + +COUNT = 134 +KEY = fffffffffffffffffffffffffffffffffe000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6c5d03b13069c3658b3179be91b0800c + +COUNT = 135 +KEY = ffffffffffffffffffffffffffffffffff000000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ef1b384ac4d93eda00c92add0995ea5f + +COUNT = 136 +KEY = ffffffffffffffffffffffffffffffffff800000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = bf8115805471741bd5ad20a03944790f + +COUNT = 137 +KEY = ffffffffffffffffffffffffffffffffffc00000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = c64c24b6894b038b3c0d09b1df068b0b + +COUNT = 138 +KEY = ffffffffffffffffffffffffffffffffffe00000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3967a10cffe27d0178545fbf6a40544b + +COUNT = 139 +KEY = fffffffffffffffffffffffffffffffffff00000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 7c85e9c95de1a9ec5a5363a8a053472d + +COUNT = 140 +KEY = fffffffffffffffffffffffffffffffffff80000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a9eec03c8abec7ba68315c2c8c2316e0 + +COUNT = 141 +KEY = fffffffffffffffffffffffffffffffffffc0000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = cac8e414c2f388227ae14986fc983524 + +COUNT = 142 +KEY = fffffffffffffffffffffffffffffffffffe0000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 5d942b7f4622ce056c3ce3ce5f1dd9d6 + +COUNT = 143 +KEY = ffffffffffffffffffffffffffffffffffff0000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d240d648ce21a3020282c3f1b528a0b6 + +COUNT = 144 +KEY = ffffffffffffffffffffffffffffffffffff8000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 45d089c36d5c5a4efc689e3b0de10dd5 + +COUNT = 145 +KEY = ffffffffffffffffffffffffffffffffffffc000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = b4da5df4becb5462e03a0ed00d295629 + +COUNT = 146 +KEY = ffffffffffffffffffffffffffffffffffffe000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = dcf4e129136c1a4b7a0f38935cc34b2b + +COUNT = 147 +KEY = fffffffffffffffffffffffffffffffffffff000000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d9a4c7618b0ce48a3d5aee1a1c0114c4 + +COUNT = 148 +KEY = fffffffffffffffffffffffffffffffffffff800000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ca352df025c65c7b0bf306fbee0f36ba + +COUNT = 149 +KEY = fffffffffffffffffffffffffffffffffffffc00000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 238aca23fd3409f38af63378ed2f5473 + +COUNT = 150 +KEY = fffffffffffffffffffffffffffffffffffffe00000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 59836a0e06a79691b36667d5380d8188 + +COUNT = 151 +KEY = ffffffffffffffffffffffffffffffffffffff00000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 33905080f7acf1cdae0a91fc3e85aee4 + +COUNT = 152 +KEY = ffffffffffffffffffffffffffffffffffffff80000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 72c9e4646dbc3d6320fc6689d93e8833 + +COUNT = 153 +KEY = ffffffffffffffffffffffffffffffffffffffc0000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ba77413dea5925b7f5417ea47ff19f59 + +COUNT = 154 +KEY = ffffffffffffffffffffffffffffffffffffffe0000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6cae8129f843d86dc786a0fb1a184970 + +COUNT = 155 +KEY = fffffffffffffffffffffffffffffffffffffff0000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = fcfefb534100796eebbd990206754e19 + +COUNT = 156 +KEY = fffffffffffffffffffffffffffffffffffffff8000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 8c791d5fdddf470da04f3e6dc4a5b5b5 + +COUNT = 157 +KEY = fffffffffffffffffffffffffffffffffffffffc000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = c93bbdc07a4611ae4bb266ea5034a387 + +COUNT = 158 +KEY = fffffffffffffffffffffffffffffffffffffffe000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = c102e38e489aa74762f3efc5bb23205a + +COUNT = 159 +KEY = ffffffffffffffffffffffffffffffffffffffff000000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 93201481665cbafc1fcc220bc545fb3d + +COUNT = 160 +KEY = ffffffffffffffffffffffffffffffffffffffff800000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 4960757ec6ce68cf195e454cfd0f32ca + +COUNT = 161 +KEY = ffffffffffffffffffffffffffffffffffffffffc00000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = feec7ce6a6cbd07c043416737f1bbb33 + +COUNT = 162 +KEY = ffffffffffffffffffffffffffffffffffffffffe00000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 11c5413904487a805d70a8edd9c35527 + +COUNT = 163 +KEY = fffffffffffffffffffffffffffffffffffffffff00000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 347846b2b2e36f1f0324c86f7f1b98e2 + +COUNT = 164 +KEY = fffffffffffffffffffffffffffffffffffffffff80000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 332eee1a0cbd19ca2d69b426894044f0 + +COUNT = 165 +KEY = fffffffffffffffffffffffffffffffffffffffffc0000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 866b5b3977ba6efa5128efbda9ff03cd + +COUNT = 166 +KEY = fffffffffffffffffffffffffffffffffffffffffe0000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = cc1445ee94c0f08cdee5c344ecd1e233 + +COUNT = 167 +KEY = ffffffffffffffffffffffffffffffffffffffffff0000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = be288319029363c2622feba4b05dfdfe + +COUNT = 168 +KEY = ffffffffffffffffffffffffffffffffffffffffff8000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = cfd1875523f3cd21c395651e6ee15e56 + +COUNT = 169 +KEY = ffffffffffffffffffffffffffffffffffffffffffc000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = cb5a408657837c53bf16f9d8465dce19 + +COUNT = 170 +KEY = ffffffffffffffffffffffffffffffffffffffffffe000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ca0bf42cb107f55ccff2fc09ee08ca15 + +COUNT = 171 +KEY = fffffffffffffffffffffffffffffffffffffffffff000000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = fdd9bbb4a7dc2e4a23536a5880a2db67 + +COUNT = 172 +KEY = fffffffffffffffffffffffffffffffffffffffffff800000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ede447b362c484993dec9442a3b46aef + +COUNT = 173 +KEY = fffffffffffffffffffffffffffffffffffffffffffc00000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 10dffb05904bff7c4781df780ad26837 + +COUNT = 174 +KEY = fffffffffffffffffffffffffffffffffffffffffffe00000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = c33bc13e8de88ac25232aa7496398783 + +COUNT = 175 +KEY = ffffffffffffffffffffffffffffffffffffffffffff00000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ca359c70803a3b2a3d542e8781dea975 + +COUNT = 176 +KEY = ffffffffffffffffffffffffffffffffffffffffffff80000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = bcc65b526f88d05b89ce8a52021fdb06 + +COUNT = 177 +KEY = ffffffffffffffffffffffffffffffffffffffffffffc0000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = db91a38855c8c4643851fbfb358b0109 + +COUNT = 178 +KEY = ffffffffffffffffffffffffffffffffffffffffffffe0000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ca6e8893a114ae8e27d5ab03a5499610 + +COUNT = 179 +KEY = fffffffffffffffffffffffffffffffffffffffffffff0000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6629d2b8df97da728cdd8b1e7f945077 + +COUNT = 180 +KEY = fffffffffffffffffffffffffffffffffffffffffffff8000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 4570a5a18cfc0dd582f1d88d5c9a1720 + +COUNT = 181 +KEY = fffffffffffffffffffffffffffffffffffffffffffffc000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 72bc65aa8e89562e3f274d45af1cd10b + +COUNT = 182 +KEY = fffffffffffffffffffffffffffffffffffffffffffffe000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 98551da1a6503276ae1c77625f9ea615 + +COUNT = 183 +KEY = ffffffffffffffffffffffffffffffffffffffffffffff000000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 0ddfe51ced7e3f4ae927daa3fe452cee + +COUNT = 184 +KEY = ffffffffffffffffffffffffffffffffffffffffffffff800000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = db826251e4ce384b80218b0e1da1dd4c + +COUNT = 185 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffc00000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2cacf728b88abbad7011ed0e64a1680c + +COUNT = 186 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 330d8ee7c5677e099ac74c9994ee4cfb + +COUNT = 187 +KEY = fffffffffffffffffffffffffffffffffffffffffffffff00000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = edf61ae362e882ddc0167474a7a77f3a + +COUNT = 188 +KEY = fffffffffffffffffffffffffffffffffffffffffffffff80000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6168b00ba7859e0970ecfd757efecf7c + +COUNT = 189 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffc0000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d1415447866230d28bb1ea18a4cdfd02 + +COUNT = 190 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffe0000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 516183392f7a8763afec68a060264141 + +COUNT = 191 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 77565c8d73cfd4130b4aa14d8911710f + +COUNT = 192 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 37232a4ed21ccc27c19c9610078cabac + +COUNT = 193 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffc000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 804f32ea71828c7d329077e712231666 + +COUNT = 194 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffe000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d64424f23cb97215e9c2c6f28d29eab7 + +COUNT = 195 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 023e82b533f68c75c238cebdb2ee89a2 + +COUNT = 196 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffff800000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 193a3d24157a51f1ee0893f6777417e7 + +COUNT = 197 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffc00000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 84ecacfcd400084d078612b1945f2ef5 + +COUNT = 198 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 1dcd8bb173259eb33a5242b0de31a455 + +COUNT = 199 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 35e9eddbc375e792c19992c19165012b + +COUNT = 200 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 8a772231c01dfdd7c98e4cfddcc0807a + +COUNT = 201 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffc0000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6eda7ff6b8319180ff0d6e65629d01c3 + +COUNT = 202 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffe0000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = c267ef0e2d01a993944dd397101413cb + +COUNT = 203 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = e9f80e9d845bcc0f62926af72eabca39 + +COUNT = 204 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffff8000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 6702990727aa0878637b45dcd3a3b074 + +COUNT = 205 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffc000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2e2e647d5360e09230a5d738ca33471e + +COUNT = 206 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffe000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 1f56413c7add6f43d1d56e4f02190330 + +COUNT = 207 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 69cd0606e15af729d6bca143016d9842 + +COUNT = 208 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffff800000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a085d7c1a500873a20099c4caa3c3f5b + +COUNT = 209 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffc00000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 4fc0d230f8891415b87b83f95f2e09d1 + +COUNT = 210 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 4327d08c523d8eba697a4336507d1f42 + +COUNT = 211 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 7a15aab82701efa5ae36ab1d6b76290f + +COUNT = 212 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffff80000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 5bf0051893a18bb30e139a58fed0fa54 + +COUNT = 213 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffc0000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 97e8adf65638fd9cdf3bc22c17fe4dbd + +COUNT = 214 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffe0000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 1ee6ee326583a0586491c96418d1a35d + +COUNT = 215 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 26b549c2ec756f82ecc48008e529956b + +COUNT = 216 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffff8000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 70377b6da669b072129e057cc28e9ca5 + +COUNT = 217 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffc000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 9c94b8b0cb8bcc919072262b3fa05ad9 + +COUNT = 218 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2fbb83dfd0d7abcb05cd28cad2dfb523 + +COUNT = 219 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 96877803de77744bb970d0a91f4debae + +COUNT = 220 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffff800000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 7379f3370cf6e5ce12ae5969c8eea312 + +COUNT = 221 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 02dc99fa3d4f98ce80985e7233889313 + +COUNT = 222 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 1e38e759075ba5cab6457da51844295a + +COUNT = 223 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 70bed8dbf615868a1f9d9b05d3e7a267 + +COUNT = 224 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 234b148b8cb1d8c32b287e896903d150 + +COUNT = 225 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 294b033df4da853f4be3e243f7e513f4 + +COUNT = 226 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3f58c950f0367160adec45f2441e7411 + +COUNT = 227 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 37f655536a704e5ace182d742a820cf4 + +COUNT = 228 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ea7bd6bb63418731aeac790fe42d61e8 + +COUNT = 229 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffc000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = e74a4c999b4c064e48bb1e413f51e5ea + +COUNT = 230 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = ba9ebefdb4ccf30f296cecb3bc1943e8 + +COUNT = 231 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3194367a4898c502c13bb7478640a72d + +COUNT = 232 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff800000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = da797713263d6f33a5478a65ef60d412 + +COUNT = 233 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d1ac39bb1ef86b9c1344f214679aa376 + +COUNT = 234 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2fdea9e650532be5bc0e7325337fd363 + +COUNT = 235 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d3a204dbd9c2af158b6ca67a5156ce4a + +COUNT = 236 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 3a0a0e75a8da36735aee6684d965a778 + +COUNT = 237 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 52fc3e620492ea99641ea168da5b6d52 + +COUNT = 238 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d2e0c7f15b4772467d2cfc873000b2ca + +COUNT = 239 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 563531135e0c4d70a38f8bdb190ba04e + +COUNT = 240 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = a8a39a0f5663f4c0fe5f2d3cafff421a + +COUNT = 241 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = d94b5e90db354c1e42f61fabe167b2c0 + +COUNT = 242 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 50e6d3c9b6698a7cd276f96b1473f35a + +COUNT = 243 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 9338f08e0ebee96905d8f2e825208f43 + +COUNT = 244 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff800 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 8b378c86672aa54a3a266ba19d2580ca + +COUNT = 245 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = cca7c3086f5f9511b31233da7cab9160 + +COUNT = 246 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 5b40ff4ec9be536ba23035fa4f06064c + +COUNT = 247 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 60eb5af8416b257149372194e8b88749 + +COUNT = 248 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 2f005a8aed8a361c92e440c15520cbd1 + +COUNT = 249 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 7b03627611678a997717578807a800e2 + +COUNT = 250 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = cf78618f74f6f3696e0a4779b90b5a77 + +COUNT = 251 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 03720371a04962eaea0a852e69972858 + +COUNT = 252 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8 +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 1f8a8133aa8ccf70e2bd3285831ca6b7 + +COUNT = 253 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 27936bd27fb1468fc8b48bc483321725 + +COUNT = 254 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = b07d4f3e2cd2ef2eb545980754dfea0f + +COUNT = 255 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +PLAINTEXT = 00000000000000000000000000000000 +CIPHERTEXT = 4bf85f1b5d54adbc307b0a048389adcb + +[DECRYPT] + +COUNT = 0 +KEY = 8000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = e35a6dcb19b201a01ebcfa8aa22b5759 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 1 +KEY = c000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = b29169cdcf2d83e838125a12ee6aa400 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 2 +KEY = e000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d8f3a72fc3cdf74dfaf6c3e6b97b2fa6 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 3 +KEY = f000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 1c777679d50037c79491a94da76a9a35 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 4 +KEY = f800000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 9cf4893ecafa0a0247a898e040691559 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 5 +KEY = fc00000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 8fbb413703735326310a269bd3aa94b2 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 6 +KEY = fe00000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 60e32246bed2b0e859e55c1cc6b26502 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 7 +KEY = ff00000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ec52a212f80a09df6317021bc2a9819e +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 8 +KEY = ff80000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = f23e5b600eb70dbccf6c0b1d9a68182c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 9 +KEY = ffc0000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = a3f599d63a82a968c33fe26590745970 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 10 +KEY = ffe0000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d1ccb9b1337002cbac42c520b5d67722 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 11 +KEY = fff0000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = cc111f6c37cf40a1159d00fb59fb0488 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 12 +KEY = fff8000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = dc43b51ab609052372989a26e9cdd714 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 13 +KEY = fffc000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 4dcede8da9e2578f39703d4433dc6459 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 14 +KEY = fffe000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 1a4c1c263bbccfafc11782894685e3a8 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 15 +KEY = ffff000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 937ad84880db50613423d6d527a2823d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 16 +KEY = ffff800000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 610b71dfc688e150d8152c5b35ebc14d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 17 +KEY = ffffc00000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 27ef2495dabf323885aab39c80f18d8b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 18 +KEY = ffffe00000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 633cafea395bc03adae3a1e2068e4b4e +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 19 +KEY = fffff00000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 6e1b482b53761cf631819b749a6f3724 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 20 +KEY = fffff80000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 976e6f851ab52c771998dbb2d71c75a9 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 21 +KEY = fffffc0000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 85f2ba84f8c307cf525e124c3e22e6cc +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 22 +KEY = fffffe0000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 6bcca98bf6a835fa64955f72de4115fe +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 23 +KEY = ffffff0000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 2c75e2d36eebd65411f14fd0eb1d2a06 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 24 +KEY = ffffff8000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = bd49295006250ffca5100b6007a0eade +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 25 +KEY = ffffffc000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = a190527d0ef7c70f459cd3940df316ec +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 26 +KEY = ffffffe000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = bbd1097a62433f79449fa97d4ee80dbf +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 27 +KEY = fffffff000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 07058e408f5b99b0e0f061a1761b5b3b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 28 +KEY = fffffff800000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 5fd1f13fa0f31e37fabde328f894eac2 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 29 +KEY = fffffffc00000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = fc4af7c948df26e2ef3e01c1ee5b8f6f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 30 +KEY = fffffffe00000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 829fd7208fb92d44a074a677ee9861ac +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 31 +KEY = ffffffff00000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ad9fc613a703251b54c64a0e76431711 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 32 +KEY = ffffffff80000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 33ac9eccc4cc75e2711618f80b1548e8 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 33 +KEY = ffffffffc0000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 2025c74b8ad8f4cda17ee2049c4c902d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 34 +KEY = ffffffffe0000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = f85ca05fe528f1ce9b790166e8d551e7 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 35 +KEY = fffffffff0000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 6f6238d8966048d4967154e0dad5a6c9 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 36 +KEY = fffffffff8000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = f2b21b4e7640a9b3346de8b82fb41e49 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 37 +KEY = fffffffffc000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = f836f251ad1d11d49dc344628b1884e1 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 38 +KEY = fffffffffe000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 077e9470ae7abea5a9769d49182628c3 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 39 +KEY = ffffffffff000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = e0dcc2d27fc9865633f85223cf0d611f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 40 +KEY = ffffffffff800000000000000000000000000000000000000000000000000000 +CIPHERTEXT = be66cfea2fecd6bf0ec7b4352c99bcaa +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 41 +KEY = ffffffffffc00000000000000000000000000000000000000000000000000000 +CIPHERTEXT = df31144f87a2ef523facdcf21a427804 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 42 +KEY = ffffffffffe00000000000000000000000000000000000000000000000000000 +CIPHERTEXT = b5bb0f5629fb6aae5e1839a3c3625d63 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 43 +KEY = fffffffffff00000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 3c9db3335306fe1ec612bdbfae6b6028 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 44 +KEY = fffffffffff80000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 3dd5c34634a79d3cfcc8339760e6f5f4 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 45 +KEY = fffffffffffc0000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 82bda118a3ed7af314fa2ccc5c07b761 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 46 +KEY = fffffffffffe0000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 2937a64f7d4f46fe6fea3b349ec78e38 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 47 +KEY = ffffffffffff0000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 225f068c28476605735ad671bb8f39f3 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 48 +KEY = ffffffffffff8000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ae682c5ecd71898e08942ac9aa89875c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 49 +KEY = ffffffffffffc000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 5e031cb9d676c3022d7f26227e85c38f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 50 +KEY = ffffffffffffe000000000000000000000000000000000000000000000000000 +CIPHERTEXT = a78463fb064db5d52bb64bfef64f2dda +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 51 +KEY = fffffffffffff000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 8aa9b75e784593876c53a00eae5af52b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 52 +KEY = fffffffffffff800000000000000000000000000000000000000000000000000 +CIPHERTEXT = 3f84566df23da48af692722fe980573a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 53 +KEY = fffffffffffffc00000000000000000000000000000000000000000000000000 +CIPHERTEXT = 31690b5ed41c7eb42a1e83270a7ff0e6 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 54 +KEY = fffffffffffffe00000000000000000000000000000000000000000000000000 +CIPHERTEXT = 77dd7702646d55f08365e477d3590eda +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 55 +KEY = ffffffffffffff00000000000000000000000000000000000000000000000000 +CIPHERTEXT = 4c022ac62b3cb78d739cc67b3e20bb7e +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 56 +KEY = ffffffffffffff80000000000000000000000000000000000000000000000000 +CIPHERTEXT = 092fa137ce18b5dfe7906f550bb13370 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 57 +KEY = ffffffffffffffc0000000000000000000000000000000000000000000000000 +CIPHERTEXT = 3e0cdadf2e68353c0027672c97144dd3 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 58 +KEY = ffffffffffffffe0000000000000000000000000000000000000000000000000 +CIPHERTEXT = d8c4b200b383fc1f2b2ea677618a1d27 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 59 +KEY = fffffffffffffff0000000000000000000000000000000000000000000000000 +CIPHERTEXT = 11825f99b0e9bb3477c1c0713b015aac +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 60 +KEY = fffffffffffffff8000000000000000000000000000000000000000000000000 +CIPHERTEXT = f8b9fffb5c187f7ddc7ab10f4fb77576 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 61 +KEY = fffffffffffffffc000000000000000000000000000000000000000000000000 +CIPHERTEXT = ffb4e87a32b37d6f2c8328d3b5377802 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 62 +KEY = fffffffffffffffe000000000000000000000000000000000000000000000000 +CIPHERTEXT = d276c13a5d220f4da9224e74896391ce +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 63 +KEY = ffffffffffffffff000000000000000000000000000000000000000000000000 +CIPHERTEXT = 94efe7a0e2e031e2536da01df799c927 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 64 +KEY = ffffffffffffffff800000000000000000000000000000000000000000000000 +CIPHERTEXT = 8f8fd822680a85974e53a5a8eb9d38de +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 65 +KEY = ffffffffffffffffc00000000000000000000000000000000000000000000000 +CIPHERTEXT = e0f0a91b2e45f8cc37b7805a3042588d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 66 +KEY = ffffffffffffffffe00000000000000000000000000000000000000000000000 +CIPHERTEXT = 597a6252255e46d6364dbeeda31e279c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 67 +KEY = fffffffffffffffff00000000000000000000000000000000000000000000000 +CIPHERTEXT = f51a0f694442b8f05571797fec7ee8bf +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 68 +KEY = fffffffffffffffff80000000000000000000000000000000000000000000000 +CIPHERTEXT = 9ff071b165b5198a93dddeebc54d09b5 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 69 +KEY = fffffffffffffffffc0000000000000000000000000000000000000000000000 +CIPHERTEXT = c20a19fd5758b0c4bc1a5df89cf73877 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 70 +KEY = fffffffffffffffffe0000000000000000000000000000000000000000000000 +CIPHERTEXT = 97120166307119ca2280e9315668e96f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 71 +KEY = ffffffffffffffffff0000000000000000000000000000000000000000000000 +CIPHERTEXT = 4b3b9f1e099c2a09dc091e90e4f18f0a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 72 +KEY = ffffffffffffffffff8000000000000000000000000000000000000000000000 +CIPHERTEXT = eb040b891d4b37f6851f7ec219cd3f6d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 73 +KEY = ffffffffffffffffffc000000000000000000000000000000000000000000000 +CIPHERTEXT = 9f0fdec08b7fd79aa39535bea42db92a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 74 +KEY = ffffffffffffffffffe000000000000000000000000000000000000000000000 +CIPHERTEXT = 2e70f168fc74bf911df240bcd2cef236 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 75 +KEY = fffffffffffffffffff000000000000000000000000000000000000000000000 +CIPHERTEXT = 462ccd7f5fd1108dbc152f3cacad328b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 76 +KEY = fffffffffffffffffff800000000000000000000000000000000000000000000 +CIPHERTEXT = a4af534a7d0b643a01868785d86dfb95 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 77 +KEY = fffffffffffffffffffc00000000000000000000000000000000000000000000 +CIPHERTEXT = ab980296197e1a5022326c31da4bf6f3 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 78 +KEY = fffffffffffffffffffe00000000000000000000000000000000000000000000 +CIPHERTEXT = f97d57b3333b6281b07d486db2d4e20c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 79 +KEY = ffffffffffffffffffff00000000000000000000000000000000000000000000 +CIPHERTEXT = f33fa36720231afe4c759ade6bd62eb6 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 80 +KEY = ffffffffffffffffffff80000000000000000000000000000000000000000000 +CIPHERTEXT = fdcfac0c02ca538343c68117e0a15938 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 81 +KEY = ffffffffffffffffffffc0000000000000000000000000000000000000000000 +CIPHERTEXT = ad4916f5ee5772be764fc027b8a6e539 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 82 +KEY = ffffffffffffffffffffe0000000000000000000000000000000000000000000 +CIPHERTEXT = 2e16873e1678610d7e14c02d002ea845 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 83 +KEY = fffffffffffffffffffff0000000000000000000000000000000000000000000 +CIPHERTEXT = 4e6e627c1acc51340053a8236d579576 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 84 +KEY = fffffffffffffffffffff8000000000000000000000000000000000000000000 +CIPHERTEXT = ab0c8410aeeead92feec1eb430d652cb +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 85 +KEY = fffffffffffffffffffffc000000000000000000000000000000000000000000 +CIPHERTEXT = e86f7e23e835e114977f60e1a592202e +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 86 +KEY = fffffffffffffffffffffe000000000000000000000000000000000000000000 +CIPHERTEXT = e68ad5055a367041fade09d9a70a794b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 87 +KEY = ffffffffffffffffffffff000000000000000000000000000000000000000000 +CIPHERTEXT = 0791823a3c666bb6162825e78606a7fe +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 88 +KEY = ffffffffffffffffffffff800000000000000000000000000000000000000000 +CIPHERTEXT = dcca366a9bf47b7b868b77e25c18a364 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 89 +KEY = ffffffffffffffffffffffc00000000000000000000000000000000000000000 +CIPHERTEXT = 684c9efc237e4a442965f84bce20247a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 90 +KEY = ffffffffffffffffffffffe00000000000000000000000000000000000000000 +CIPHERTEXT = a858411ffbe63fdb9c8aa1bfaed67b52 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 91 +KEY = fffffffffffffffffffffff00000000000000000000000000000000000000000 +CIPHERTEXT = 04bc3da2179c3015498b0e03910db5b8 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 92 +KEY = fffffffffffffffffffffff80000000000000000000000000000000000000000 +CIPHERTEXT = 40071eeab3f935dbc25d00841460260f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 93 +KEY = fffffffffffffffffffffffc0000000000000000000000000000000000000000 +CIPHERTEXT = 0ebd7c30ed2016e08ba806ddb008bcc8 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 94 +KEY = fffffffffffffffffffffffe0000000000000000000000000000000000000000 +CIPHERTEXT = 15c6becf0f4cec7129cbd22d1a79b1b8 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 95 +KEY = ffffffffffffffffffffffff0000000000000000000000000000000000000000 +CIPHERTEXT = 0aeede5b91f721700e9e62edbf60b781 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 96 +KEY = ffffffffffffffffffffffff8000000000000000000000000000000000000000 +CIPHERTEXT = 266581af0dcfbed1585e0a242c64b8df +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 97 +KEY = ffffffffffffffffffffffffc000000000000000000000000000000000000000 +CIPHERTEXT = 6693dc911662ae473216ba22189a511a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 98 +KEY = ffffffffffffffffffffffffe000000000000000000000000000000000000000 +CIPHERTEXT = 7606fa36d86473e6fb3a1bb0e2c0adf5 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 99 +KEY = fffffffffffffffffffffffff000000000000000000000000000000000000000 +CIPHERTEXT = 112078e9e11fbb78e26ffb8899e96b9a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 100 +KEY = fffffffffffffffffffffffff800000000000000000000000000000000000000 +CIPHERTEXT = 40b264e921e9e4a82694589ef3798262 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 101 +KEY = fffffffffffffffffffffffffc00000000000000000000000000000000000000 +CIPHERTEXT = 8d4595cb4fa7026715f55bd68e2882f9 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 102 +KEY = fffffffffffffffffffffffffe00000000000000000000000000000000000000 +CIPHERTEXT = b588a302bdbc09197df1edae68926ed9 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 103 +KEY = ffffffffffffffffffffffffff00000000000000000000000000000000000000 +CIPHERTEXT = 33f7502390b8a4a221cfecd0666624ba +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 104 +KEY = ffffffffffffffffffffffffff80000000000000000000000000000000000000 +CIPHERTEXT = 3d20253adbce3be2373767c4d822c566 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 105 +KEY = ffffffffffffffffffffffffffc0000000000000000000000000000000000000 +CIPHERTEXT = a42734a3929bf84cf0116c9856a3c18c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 106 +KEY = ffffffffffffffffffffffffffe0000000000000000000000000000000000000 +CIPHERTEXT = e3abc4939457422bb957da3c56938c6d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 107 +KEY = fffffffffffffffffffffffffff0000000000000000000000000000000000000 +CIPHERTEXT = 972bdd2e7c525130fadc8f76fc6f4b3f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 108 +KEY = fffffffffffffffffffffffffff8000000000000000000000000000000000000 +CIPHERTEXT = 84a83d7b94c699cbcb8a7d9b61f64093 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 109 +KEY = fffffffffffffffffffffffffffc000000000000000000000000000000000000 +CIPHERTEXT = ce61d63514aded03d43e6ebfc3a9001f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 110 +KEY = fffffffffffffffffffffffffffe000000000000000000000000000000000000 +CIPHERTEXT = 6c839dd58eeae6b8a36af48ed63d2dc9 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 111 +KEY = ffffffffffffffffffffffffffff000000000000000000000000000000000000 +CIPHERTEXT = cd5ece55b8da3bf622c4100df5de46f9 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 112 +KEY = ffffffffffffffffffffffffffff800000000000000000000000000000000000 +CIPHERTEXT = 3b6f46f40e0ac5fc0a9c1105f800f48d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 113 +KEY = ffffffffffffffffffffffffffffc00000000000000000000000000000000000 +CIPHERTEXT = ba26d47da3aeb028de4fb5b3a854a24b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 114 +KEY = ffffffffffffffffffffffffffffe00000000000000000000000000000000000 +CIPHERTEXT = 87f53bf620d3677268445212904389d5 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 115 +KEY = fffffffffffffffffffffffffffff00000000000000000000000000000000000 +CIPHERTEXT = 10617d28b5e0f4605492b182a5d7f9f6 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 116 +KEY = fffffffffffffffffffffffffffff80000000000000000000000000000000000 +CIPHERTEXT = 9aaec4fabbf6fae2a71feff02e372b39 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 117 +KEY = fffffffffffffffffffffffffffffc0000000000000000000000000000000000 +CIPHERTEXT = 3a90c62d88b5c42809abf782488ed130 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 118 +KEY = fffffffffffffffffffffffffffffe0000000000000000000000000000000000 +CIPHERTEXT = f1f1c5a40899e15772857ccb65c7a09a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 119 +KEY = ffffffffffffffffffffffffffffff0000000000000000000000000000000000 +CIPHERTEXT = 190843d29b25a3897c692ce1dd81ee52 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 120 +KEY = ffffffffffffffffffffffffffffff8000000000000000000000000000000000 +CIPHERTEXT = a866bc65b6941d86e8420a7ffb0964db +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 121 +KEY = ffffffffffffffffffffffffffffffc000000000000000000000000000000000 +CIPHERTEXT = 8193c6ff85225ced4255e92f6e078a14 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 122 +KEY = ffffffffffffffffffffffffffffffe000000000000000000000000000000000 +CIPHERTEXT = 9661cb2424d7d4a380d547f9e7ec1cb9 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 123 +KEY = fffffffffffffffffffffffffffffff000000000000000000000000000000000 +CIPHERTEXT = 86f93d9ec08453a071e2e2877877a9c8 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 124 +KEY = fffffffffffffffffffffffffffffff800000000000000000000000000000000 +CIPHERTEXT = 27eefa80ce6a4a9d598e3fec365434d2 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 125 +KEY = fffffffffffffffffffffffffffffffc00000000000000000000000000000000 +CIPHERTEXT = d62068444578e3ab39ce7ec95dd045dc +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 126 +KEY = fffffffffffffffffffffffffffffffe00000000000000000000000000000000 +CIPHERTEXT = b5f71d4dd9a71fe5d8bc8ba7e6ea3048 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 127 +KEY = ffffffffffffffffffffffffffffffff00000000000000000000000000000000 +CIPHERTEXT = 6825a347ac479d4f9d95c5cb8d3fd7e9 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 128 +KEY = ffffffffffffffffffffffffffffffff80000000000000000000000000000000 +CIPHERTEXT = e3714e94a5778955cc0346358e94783a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 129 +KEY = ffffffffffffffffffffffffffffffffc0000000000000000000000000000000 +CIPHERTEXT = d836b44bb29e0c7d89fa4b2d4b677d2a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 130 +KEY = ffffffffffffffffffffffffffffffffe0000000000000000000000000000000 +CIPHERTEXT = 5d454b75021d76d4b84f873a8f877b92 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 131 +KEY = fffffffffffffffffffffffffffffffff0000000000000000000000000000000 +CIPHERTEXT = c3498f7eced2095314fc28115885b33f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 132 +KEY = fffffffffffffffffffffffffffffffff8000000000000000000000000000000 +CIPHERTEXT = 6e668856539ad8e405bd123fe6c88530 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 133 +KEY = fffffffffffffffffffffffffffffffffc000000000000000000000000000000 +CIPHERTEXT = 8680db7f3a87b8605543cfdbe6754076 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 134 +KEY = fffffffffffffffffffffffffffffffffe000000000000000000000000000000 +CIPHERTEXT = 6c5d03b13069c3658b3179be91b0800c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 135 +KEY = ffffffffffffffffffffffffffffffffff000000000000000000000000000000 +CIPHERTEXT = ef1b384ac4d93eda00c92add0995ea5f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 136 +KEY = ffffffffffffffffffffffffffffffffff800000000000000000000000000000 +CIPHERTEXT = bf8115805471741bd5ad20a03944790f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 137 +KEY = ffffffffffffffffffffffffffffffffffc00000000000000000000000000000 +CIPHERTEXT = c64c24b6894b038b3c0d09b1df068b0b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 138 +KEY = ffffffffffffffffffffffffffffffffffe00000000000000000000000000000 +CIPHERTEXT = 3967a10cffe27d0178545fbf6a40544b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 139 +KEY = fffffffffffffffffffffffffffffffffff00000000000000000000000000000 +CIPHERTEXT = 7c85e9c95de1a9ec5a5363a8a053472d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 140 +KEY = fffffffffffffffffffffffffffffffffff80000000000000000000000000000 +CIPHERTEXT = a9eec03c8abec7ba68315c2c8c2316e0 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 141 +KEY = fffffffffffffffffffffffffffffffffffc0000000000000000000000000000 +CIPHERTEXT = cac8e414c2f388227ae14986fc983524 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 142 +KEY = fffffffffffffffffffffffffffffffffffe0000000000000000000000000000 +CIPHERTEXT = 5d942b7f4622ce056c3ce3ce5f1dd9d6 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 143 +KEY = ffffffffffffffffffffffffffffffffffff0000000000000000000000000000 +CIPHERTEXT = d240d648ce21a3020282c3f1b528a0b6 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 144 +KEY = ffffffffffffffffffffffffffffffffffff8000000000000000000000000000 +CIPHERTEXT = 45d089c36d5c5a4efc689e3b0de10dd5 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 145 +KEY = ffffffffffffffffffffffffffffffffffffc000000000000000000000000000 +CIPHERTEXT = b4da5df4becb5462e03a0ed00d295629 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 146 +KEY = ffffffffffffffffffffffffffffffffffffe000000000000000000000000000 +CIPHERTEXT = dcf4e129136c1a4b7a0f38935cc34b2b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 147 +KEY = fffffffffffffffffffffffffffffffffffff000000000000000000000000000 +CIPHERTEXT = d9a4c7618b0ce48a3d5aee1a1c0114c4 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 148 +KEY = fffffffffffffffffffffffffffffffffffff800000000000000000000000000 +CIPHERTEXT = ca352df025c65c7b0bf306fbee0f36ba +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 149 +KEY = fffffffffffffffffffffffffffffffffffffc00000000000000000000000000 +CIPHERTEXT = 238aca23fd3409f38af63378ed2f5473 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 150 +KEY = fffffffffffffffffffffffffffffffffffffe00000000000000000000000000 +CIPHERTEXT = 59836a0e06a79691b36667d5380d8188 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 151 +KEY = ffffffffffffffffffffffffffffffffffffff00000000000000000000000000 +CIPHERTEXT = 33905080f7acf1cdae0a91fc3e85aee4 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 152 +KEY = ffffffffffffffffffffffffffffffffffffff80000000000000000000000000 +CIPHERTEXT = 72c9e4646dbc3d6320fc6689d93e8833 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 153 +KEY = ffffffffffffffffffffffffffffffffffffffc0000000000000000000000000 +CIPHERTEXT = ba77413dea5925b7f5417ea47ff19f59 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 154 +KEY = ffffffffffffffffffffffffffffffffffffffe0000000000000000000000000 +CIPHERTEXT = 6cae8129f843d86dc786a0fb1a184970 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 155 +KEY = fffffffffffffffffffffffffffffffffffffff0000000000000000000000000 +CIPHERTEXT = fcfefb534100796eebbd990206754e19 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 156 +KEY = fffffffffffffffffffffffffffffffffffffff8000000000000000000000000 +CIPHERTEXT = 8c791d5fdddf470da04f3e6dc4a5b5b5 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 157 +KEY = fffffffffffffffffffffffffffffffffffffffc000000000000000000000000 +CIPHERTEXT = c93bbdc07a4611ae4bb266ea5034a387 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 158 +KEY = fffffffffffffffffffffffffffffffffffffffe000000000000000000000000 +CIPHERTEXT = c102e38e489aa74762f3efc5bb23205a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 159 +KEY = ffffffffffffffffffffffffffffffffffffffff000000000000000000000000 +CIPHERTEXT = 93201481665cbafc1fcc220bc545fb3d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 160 +KEY = ffffffffffffffffffffffffffffffffffffffff800000000000000000000000 +CIPHERTEXT = 4960757ec6ce68cf195e454cfd0f32ca +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 161 +KEY = ffffffffffffffffffffffffffffffffffffffffc00000000000000000000000 +CIPHERTEXT = feec7ce6a6cbd07c043416737f1bbb33 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 162 +KEY = ffffffffffffffffffffffffffffffffffffffffe00000000000000000000000 +CIPHERTEXT = 11c5413904487a805d70a8edd9c35527 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 163 +KEY = fffffffffffffffffffffffffffffffffffffffff00000000000000000000000 +CIPHERTEXT = 347846b2b2e36f1f0324c86f7f1b98e2 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 164 +KEY = fffffffffffffffffffffffffffffffffffffffff80000000000000000000000 +CIPHERTEXT = 332eee1a0cbd19ca2d69b426894044f0 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 165 +KEY = fffffffffffffffffffffffffffffffffffffffffc0000000000000000000000 +CIPHERTEXT = 866b5b3977ba6efa5128efbda9ff03cd +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 166 +KEY = fffffffffffffffffffffffffffffffffffffffffe0000000000000000000000 +CIPHERTEXT = cc1445ee94c0f08cdee5c344ecd1e233 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 167 +KEY = ffffffffffffffffffffffffffffffffffffffffff0000000000000000000000 +CIPHERTEXT = be288319029363c2622feba4b05dfdfe +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 168 +KEY = ffffffffffffffffffffffffffffffffffffffffff8000000000000000000000 +CIPHERTEXT = cfd1875523f3cd21c395651e6ee15e56 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 169 +KEY = ffffffffffffffffffffffffffffffffffffffffffc000000000000000000000 +CIPHERTEXT = cb5a408657837c53bf16f9d8465dce19 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 170 +KEY = ffffffffffffffffffffffffffffffffffffffffffe000000000000000000000 +CIPHERTEXT = ca0bf42cb107f55ccff2fc09ee08ca15 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 171 +KEY = fffffffffffffffffffffffffffffffffffffffffff000000000000000000000 +CIPHERTEXT = fdd9bbb4a7dc2e4a23536a5880a2db67 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 172 +KEY = fffffffffffffffffffffffffffffffffffffffffff800000000000000000000 +CIPHERTEXT = ede447b362c484993dec9442a3b46aef +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 173 +KEY = fffffffffffffffffffffffffffffffffffffffffffc00000000000000000000 +CIPHERTEXT = 10dffb05904bff7c4781df780ad26837 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 174 +KEY = fffffffffffffffffffffffffffffffffffffffffffe00000000000000000000 +CIPHERTEXT = c33bc13e8de88ac25232aa7496398783 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 175 +KEY = ffffffffffffffffffffffffffffffffffffffffffff00000000000000000000 +CIPHERTEXT = ca359c70803a3b2a3d542e8781dea975 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 176 +KEY = ffffffffffffffffffffffffffffffffffffffffffff80000000000000000000 +CIPHERTEXT = bcc65b526f88d05b89ce8a52021fdb06 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 177 +KEY = ffffffffffffffffffffffffffffffffffffffffffffc0000000000000000000 +CIPHERTEXT = db91a38855c8c4643851fbfb358b0109 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 178 +KEY = ffffffffffffffffffffffffffffffffffffffffffffe0000000000000000000 +CIPHERTEXT = ca6e8893a114ae8e27d5ab03a5499610 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 179 +KEY = fffffffffffffffffffffffffffffffffffffffffffff0000000000000000000 +CIPHERTEXT = 6629d2b8df97da728cdd8b1e7f945077 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 180 +KEY = fffffffffffffffffffffffffffffffffffffffffffff8000000000000000000 +CIPHERTEXT = 4570a5a18cfc0dd582f1d88d5c9a1720 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 181 +KEY = fffffffffffffffffffffffffffffffffffffffffffffc000000000000000000 +CIPHERTEXT = 72bc65aa8e89562e3f274d45af1cd10b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 182 +KEY = fffffffffffffffffffffffffffffffffffffffffffffe000000000000000000 +CIPHERTEXT = 98551da1a6503276ae1c77625f9ea615 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 183 +KEY = ffffffffffffffffffffffffffffffffffffffffffffff000000000000000000 +CIPHERTEXT = 0ddfe51ced7e3f4ae927daa3fe452cee +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 184 +KEY = ffffffffffffffffffffffffffffffffffffffffffffff800000000000000000 +CIPHERTEXT = db826251e4ce384b80218b0e1da1dd4c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 185 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffc00000000000000000 +CIPHERTEXT = 2cacf728b88abbad7011ed0e64a1680c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 186 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffe00000000000000000 +CIPHERTEXT = 330d8ee7c5677e099ac74c9994ee4cfb +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 187 +KEY = fffffffffffffffffffffffffffffffffffffffffffffff00000000000000000 +CIPHERTEXT = edf61ae362e882ddc0167474a7a77f3a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 188 +KEY = fffffffffffffffffffffffffffffffffffffffffffffff80000000000000000 +CIPHERTEXT = 6168b00ba7859e0970ecfd757efecf7c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 189 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffc0000000000000000 +CIPHERTEXT = d1415447866230d28bb1ea18a4cdfd02 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 190 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffe0000000000000000 +CIPHERTEXT = 516183392f7a8763afec68a060264141 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 191 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000 +CIPHERTEXT = 77565c8d73cfd4130b4aa14d8911710f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 192 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000 +CIPHERTEXT = 37232a4ed21ccc27c19c9610078cabac +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 193 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffc000000000000000 +CIPHERTEXT = 804f32ea71828c7d329077e712231666 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 194 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffe000000000000000 +CIPHERTEXT = d64424f23cb97215e9c2c6f28d29eab7 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 195 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000 +CIPHERTEXT = 023e82b533f68c75c238cebdb2ee89a2 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 196 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffff800000000000000 +CIPHERTEXT = 193a3d24157a51f1ee0893f6777417e7 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 197 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffc00000000000000 +CIPHERTEXT = 84ecacfcd400084d078612b1945f2ef5 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 198 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffe00000000000000 +CIPHERTEXT = 1dcd8bb173259eb33a5242b0de31a455 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 199 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000 +CIPHERTEXT = 35e9eddbc375e792c19992c19165012b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 200 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000 +CIPHERTEXT = 8a772231c01dfdd7c98e4cfddcc0807a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 201 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffc0000000000000 +CIPHERTEXT = 6eda7ff6b8319180ff0d6e65629d01c3 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 202 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffe0000000000000 +CIPHERTEXT = c267ef0e2d01a993944dd397101413cb +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 203 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000 +CIPHERTEXT = e9f80e9d845bcc0f62926af72eabca39 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 204 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffff8000000000000 +CIPHERTEXT = 6702990727aa0878637b45dcd3a3b074 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 205 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffc000000000000 +CIPHERTEXT = 2e2e647d5360e09230a5d738ca33471e +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 206 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffe000000000000 +CIPHERTEXT = 1f56413c7add6f43d1d56e4f02190330 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 207 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000 +CIPHERTEXT = 69cd0606e15af729d6bca143016d9842 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 208 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffff800000000000 +CIPHERTEXT = a085d7c1a500873a20099c4caa3c3f5b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 209 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffc00000000000 +CIPHERTEXT = 4fc0d230f8891415b87b83f95f2e09d1 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 210 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000000 +CIPHERTEXT = 4327d08c523d8eba697a4336507d1f42 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 211 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000 +CIPHERTEXT = 7a15aab82701efa5ae36ab1d6b76290f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 212 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffff80000000000 +CIPHERTEXT = 5bf0051893a18bb30e139a58fed0fa54 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 213 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffc0000000000 +CIPHERTEXT = 97e8adf65638fd9cdf3bc22c17fe4dbd +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 214 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffe0000000000 +CIPHERTEXT = 1ee6ee326583a0586491c96418d1a35d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 215 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000 +CIPHERTEXT = 26b549c2ec756f82ecc48008e529956b +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 216 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffff8000000000 +CIPHERTEXT = 70377b6da669b072129e057cc28e9ca5 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 217 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffc000000000 +CIPHERTEXT = 9c94b8b0cb8bcc919072262b3fa05ad9 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 218 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000000 +CIPHERTEXT = 2fbb83dfd0d7abcb05cd28cad2dfb523 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 219 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000 +CIPHERTEXT = 96877803de77744bb970d0a91f4debae +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 220 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffff800000000 +CIPHERTEXT = 7379f3370cf6e5ce12ae5969c8eea312 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 221 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000000 +CIPHERTEXT = 02dc99fa3d4f98ce80985e7233889313 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 222 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000000 +CIPHERTEXT = 1e38e759075ba5cab6457da51844295a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 223 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000 +CIPHERTEXT = 70bed8dbf615868a1f9d9b05d3e7a267 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 224 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000 +CIPHERTEXT = 234b148b8cb1d8c32b287e896903d150 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 225 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0000000 +CIPHERTEXT = 294b033df4da853f4be3e243f7e513f4 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 226 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0000000 +CIPHERTEXT = 3f58c950f0367160adec45f2441e7411 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 227 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000 +CIPHERTEXT = 37f655536a704e5ace182d742a820cf4 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 228 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000000 +CIPHERTEXT = ea7bd6bb63418731aeac790fe42d61e8 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 229 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffc000000 +CIPHERTEXT = e74a4c999b4c064e48bb1e413f51e5ea +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 230 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000 +CIPHERTEXT = ba9ebefdb4ccf30f296cecb3bc1943e8 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 231 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000 +CIPHERTEXT = 3194367a4898c502c13bb7478640a72d +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 232 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff800000 +CIPHERTEXT = da797713263d6f33a5478a65ef60d412 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 233 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000 +CIPHERTEXT = d1ac39bb1ef86b9c1344f214679aa376 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 234 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00000 +CIPHERTEXT = 2fdea9e650532be5bc0e7325337fd363 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 235 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000 +CIPHERTEXT = d3a204dbd9c2af158b6ca67a5156ce4a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 236 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000 +CIPHERTEXT = 3a0a0e75a8da36735aee6684d965a778 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 237 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0000 +CIPHERTEXT = 52fc3e620492ea99641ea168da5b6d52 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 238 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0000 +CIPHERTEXT = d2e0c7f15b4772467d2cfc873000b2ca +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 239 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000 +CIPHERTEXT = 563531135e0c4d70a38f8bdb190ba04e +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 240 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000 +CIPHERTEXT = a8a39a0f5663f4c0fe5f2d3cafff421a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 241 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc000 +CIPHERTEXT = d94b5e90db354c1e42f61fabe167b2c0 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 242 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000 +CIPHERTEXT = 50e6d3c9b6698a7cd276f96b1473f35a +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 243 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000 +CIPHERTEXT = 9338f08e0ebee96905d8f2e825208f43 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 244 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff800 +CIPHERTEXT = 8b378c86672aa54a3a266ba19d2580ca +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 245 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00 +CIPHERTEXT = cca7c3086f5f9511b31233da7cab9160 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 246 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00 +CIPHERTEXT = 5b40ff4ec9be536ba23035fa4f06064c +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 247 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 +CIPHERTEXT = 60eb5af8416b257149372194e8b88749 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 248 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80 +CIPHERTEXT = 2f005a8aed8a361c92e440c15520cbd1 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 249 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0 +CIPHERTEXT = 7b03627611678a997717578807a800e2 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 250 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 +CIPHERTEXT = cf78618f74f6f3696e0a4779b90b5a77 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 251 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0 +CIPHERTEXT = 03720371a04962eaea0a852e69972858 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 252 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8 +CIPHERTEXT = 1f8a8133aa8ccf70e2bd3285831ca6b7 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 253 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc +CIPHERTEXT = 27936bd27fb1468fc8b48bc483321725 +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 254 +KEY = fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe +CIPHERTEXT = b07d4f3e2cd2ef2eb545980754dfea0f +PLAINTEXT = 00000000000000000000000000000000 + +COUNT = 255 +KEY = ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +CIPHERTEXT = 4bf85f1b5d54adbc307b0a048389adcb +PLAINTEXT = 00000000000000000000000000000000 + diff --git a/libraries/crypto/tests/data/ECBVarTxt256.rsp b/libraries/crypto/tests/data/ECBVarTxt256.rsp new file mode 100644 index 0000000..6833f60 --- /dev/null +++ b/libraries/crypto/tests/data/ECBVarTxt256.rsp @@ -0,0 +1,1291 @@ +# CAVS 11.1 +# Config info for aes_values +# AESVS VarTxt test data for ECB +# State : Encrypt and Decrypt +# Key Length : 256 +# Generated on Fri Apr 22 15:11:30 2011 + +[ENCRYPT] + +COUNT = 0 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = 80000000000000000000000000000000 +CIPHERTEXT = ddc6bf790c15760d8d9aeb6f9a75fd4e + +COUNT = 1 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = c0000000000000000000000000000000 +CIPHERTEXT = 0a6bdc6d4c1e6280301fd8e97ddbe601 + +COUNT = 2 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = e0000000000000000000000000000000 +CIPHERTEXT = 9b80eefb7ebe2d2b16247aa0efc72f5d + +COUNT = 3 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = f0000000000000000000000000000000 +CIPHERTEXT = 7f2c5ece07a98d8bee13c51177395ff7 + +COUNT = 4 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = f8000000000000000000000000000000 +CIPHERTEXT = 7818d800dcf6f4be1e0e94f403d1e4c2 + +COUNT = 5 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fc000000000000000000000000000000 +CIPHERTEXT = e74cd1c92f0919c35a0324123d6177d3 + +COUNT = 6 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fe000000000000000000000000000000 +CIPHERTEXT = 8092a4dcf2da7e77e93bdd371dfed82e + +COUNT = 7 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ff000000000000000000000000000000 +CIPHERTEXT = 49af6b372135acef10132e548f217b17 + +COUNT = 8 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ff800000000000000000000000000000 +CIPHERTEXT = 8bcd40f94ebb63b9f7909676e667f1e7 + +COUNT = 9 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffc00000000000000000000000000000 +CIPHERTEXT = fe1cffb83f45dcfb38b29be438dbd3ab + +COUNT = 10 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffe00000000000000000000000000000 +CIPHERTEXT = 0dc58a8d886623705aec15cb1e70dc0e + +COUNT = 11 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fff00000000000000000000000000000 +CIPHERTEXT = c218faa16056bd0774c3e8d79c35a5e4 + +COUNT = 12 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fff80000000000000000000000000000 +CIPHERTEXT = 047bba83f7aa841731504e012208fc9e + +COUNT = 13 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffc0000000000000000000000000000 +CIPHERTEXT = dc8f0e4915fd81ba70a331310882f6da + +COUNT = 14 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffe0000000000000000000000000000 +CIPHERTEXT = 1569859ea6b7206c30bf4fd0cbfac33c + +COUNT = 15 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffff0000000000000000000000000000 +CIPHERTEXT = 300ade92f88f48fa2df730ec16ef44cd + +COUNT = 16 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffff8000000000000000000000000000 +CIPHERTEXT = 1fe6cc3c05965dc08eb0590c95ac71d0 + +COUNT = 17 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffc000000000000000000000000000 +CIPHERTEXT = 59e858eaaa97fec38111275b6cf5abc0 + +COUNT = 18 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffe000000000000000000000000000 +CIPHERTEXT = 2239455e7afe3b0616100288cc5a723b + +COUNT = 19 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffff000000000000000000000000000 +CIPHERTEXT = 3ee500c5c8d63479717163e55c5c4522 + +COUNT = 20 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffff800000000000000000000000000 +CIPHERTEXT = d5e38bf15f16d90e3e214041d774daa8 + +COUNT = 21 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffc00000000000000000000000000 +CIPHERTEXT = b1f4066e6f4f187dfe5f2ad1b17819d0 + +COUNT = 22 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffe00000000000000000000000000 +CIPHERTEXT = 6ef4cc4de49b11065d7af2909854794a + +COUNT = 23 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffff00000000000000000000000000 +CIPHERTEXT = ac86bc606b6640c309e782f232bf367f + +COUNT = 24 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffff80000000000000000000000000 +CIPHERTEXT = 36aff0ef7bf3280772cf4cac80a0d2b2 + +COUNT = 25 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffc0000000000000000000000000 +CIPHERTEXT = 1f8eedea0f62a1406d58cfc3ecea72cf + +COUNT = 26 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffe0000000000000000000000000 +CIPHERTEXT = abf4154a3375a1d3e6b1d454438f95a6 + +COUNT = 27 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffff0000000000000000000000000 +CIPHERTEXT = 96f96e9d607f6615fc192061ee648b07 + +COUNT = 28 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffff8000000000000000000000000 +CIPHERTEXT = cf37cdaaa0d2d536c71857634c792064 + +COUNT = 29 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffc000000000000000000000000 +CIPHERTEXT = fbd6640c80245c2b805373f130703127 + +COUNT = 30 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffe000000000000000000000000 +CIPHERTEXT = 8d6a8afe55a6e481badae0d146f436db + +COUNT = 31 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffff000000000000000000000000 +CIPHERTEXT = 6a4981f2915e3e68af6c22385dd06756 + +COUNT = 32 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffff800000000000000000000000 +CIPHERTEXT = 42a1136e5f8d8d21d3101998642d573b + +COUNT = 33 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffc00000000000000000000000 +CIPHERTEXT = 9b471596dc69ae1586cee6158b0b0181 + +COUNT = 34 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffe00000000000000000000000 +CIPHERTEXT = 753665c4af1eff33aa8b628bf8741cfd + +COUNT = 35 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffff00000000000000000000000 +CIPHERTEXT = 9a682acf40be01f5b2a4193c9a82404d + +COUNT = 36 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffff80000000000000000000000 +CIPHERTEXT = 54fafe26e4287f17d1935f87eb9ade01 + +COUNT = 37 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffc0000000000000000000000 +CIPHERTEXT = 49d541b2e74cfe73e6a8e8225f7bd449 + +COUNT = 38 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffe0000000000000000000000 +CIPHERTEXT = 11a45530f624ff6f76a1b3826626ff7b + +COUNT = 39 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffff0000000000000000000000 +CIPHERTEXT = f96b0c4a8bc6c86130289f60b43b8fba + +COUNT = 40 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffff8000000000000000000000 +CIPHERTEXT = 48c7d0e80834ebdc35b6735f76b46c8b + +COUNT = 41 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffc000000000000000000000 +CIPHERTEXT = 2463531ab54d66955e73edc4cb8eaa45 + +COUNT = 42 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffe000000000000000000000 +CIPHERTEXT = ac9bd8e2530469134b9d5b065d4f565b + +COUNT = 43 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffff000000000000000000000 +CIPHERTEXT = 3f5f9106d0e52f973d4890e6f37e8a00 + +COUNT = 44 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffff800000000000000000000 +CIPHERTEXT = 20ebc86f1304d272e2e207e59db639f0 + +COUNT = 45 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffc00000000000000000000 +CIPHERTEXT = e67ae6426bf9526c972cff072b52252c + +COUNT = 46 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffe00000000000000000000 +CIPHERTEXT = 1a518dddaf9efa0d002cc58d107edfc8 + +COUNT = 47 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffff00000000000000000000 +CIPHERTEXT = ead731af4d3a2fe3b34bed047942a49f + +COUNT = 48 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffff80000000000000000000 +CIPHERTEXT = b1d4efe40242f83e93b6c8d7efb5eae9 + +COUNT = 49 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffc0000000000000000000 +CIPHERTEXT = cd2b1fec11fd906c5c7630099443610a + +COUNT = 50 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffe0000000000000000000 +CIPHERTEXT = a1853fe47fe29289d153161d06387d21 + +COUNT = 51 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffff0000000000000000000 +CIPHERTEXT = 4632154179a555c17ea604d0889fab14 + +COUNT = 52 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffff8000000000000000000 +CIPHERTEXT = dd27cac6401a022e8f38f9f93e774417 + +COUNT = 53 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffc000000000000000000 +CIPHERTEXT = c090313eb98674f35f3123385fb95d4d + +COUNT = 54 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffe000000000000000000 +CIPHERTEXT = cc3526262b92f02edce548f716b9f45c + +COUNT = 55 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffff000000000000000000 +CIPHERTEXT = c0838d1a2b16a7c7f0dfcc433c399c33 + +COUNT = 56 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffff800000000000000000 +CIPHERTEXT = 0d9ac756eb297695eed4d382eb126d26 + +COUNT = 57 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffc00000000000000000 +CIPHERTEXT = 56ede9dda3f6f141bff1757fa689c3e1 + +COUNT = 58 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffe00000000000000000 +CIPHERTEXT = 768f520efe0f23e61d3ec8ad9ce91774 + +COUNT = 59 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffff00000000000000000 +CIPHERTEXT = b1144ddfa75755213390e7c596660490 + +COUNT = 60 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffff80000000000000000 +CIPHERTEXT = 1d7c0c4040b355b9d107a99325e3b050 + +COUNT = 61 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffc0000000000000000 +CIPHERTEXT = d8e2bb1ae8ee3dcf5bf7d6c38da82a1a + +COUNT = 62 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffe0000000000000000 +CIPHERTEXT = faf82d178af25a9886a47e7f789b98d7 + +COUNT = 63 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffff0000000000000000 +CIPHERTEXT = 9b58dbfd77fe5aca9cfc190cd1b82d19 + +COUNT = 64 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffff8000000000000000 +CIPHERTEXT = 77f392089042e478ac16c0c86a0b5db5 + +COUNT = 65 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffc000000000000000 +CIPHERTEXT = 19f08e3420ee69b477ca1420281c4782 + +COUNT = 66 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffe000000000000000 +CIPHERTEXT = a1b19beee4e117139f74b3c53fdcb875 + +COUNT = 67 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffff000000000000000 +CIPHERTEXT = a37a5869b218a9f3a0868d19aea0ad6a + +COUNT = 68 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffff800000000000000 +CIPHERTEXT = bc3594e865bcd0261b13202731f33580 + +COUNT = 69 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffc00000000000000 +CIPHERTEXT = 811441ce1d309eee7185e8c752c07557 + +COUNT = 70 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffe00000000000000 +CIPHERTEXT = 959971ce4134190563518e700b9874d1 + +COUNT = 71 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffff00000000000000 +CIPHERTEXT = 76b5614a042707c98e2132e2e805fe63 + +COUNT = 72 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffff80000000000000 +CIPHERTEXT = 7d9fa6a57530d0f036fec31c230b0cc6 + +COUNT = 73 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffc0000000000000 +CIPHERTEXT = 964153a83bf6989a4ba80daa91c3e081 + +COUNT = 74 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffe0000000000000 +CIPHERTEXT = a013014d4ce8054cf2591d06f6f2f176 + +COUNT = 75 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffff0000000000000 +CIPHERTEXT = d1c5f6399bf382502e385eee1474a869 + +COUNT = 76 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffff8000000000000 +CIPHERTEXT = 0007e20b8298ec354f0f5fe7470f36bd + +COUNT = 77 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffc000000000000 +CIPHERTEXT = b95ba05b332da61ef63a2b31fcad9879 + +COUNT = 78 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffe000000000000 +CIPHERTEXT = 4620a49bd967491561669ab25dce45f4 + +COUNT = 79 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffff000000000000 +CIPHERTEXT = 12e71214ae8e04f0bb63d7425c6f14d5 + +COUNT = 80 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffff800000000000 +CIPHERTEXT = 4cc42fc1407b008fe350907c092e80ac + +COUNT = 81 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffc00000000000 +CIPHERTEXT = 08b244ce7cbc8ee97fbba808cb146fda + +COUNT = 82 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffe00000000000 +CIPHERTEXT = 39b333e8694f21546ad1edd9d87ed95b + +COUNT = 83 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffff00000000000 +CIPHERTEXT = 3b271f8ab2e6e4a20ba8090f43ba78f3 + +COUNT = 84 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffff80000000000 +CIPHERTEXT = 9ad983f3bf651cd0393f0a73cccdea50 + +COUNT = 85 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffc0000000000 +CIPHERTEXT = 8f476cbff75c1f725ce18e4bbcd19b32 + +COUNT = 86 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffe0000000000 +CIPHERTEXT = 905b6267f1d6ab5320835a133f096f2a + +COUNT = 87 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffff0000000000 +CIPHERTEXT = 145b60d6d0193c23f4221848a892d61a + +COUNT = 88 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffff8000000000 +CIPHERTEXT = 55cfb3fb6d75cad0445bbc8dafa25b0f + +COUNT = 89 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffc000000000 +CIPHERTEXT = 7b8e7098e357ef71237d46d8b075b0f5 + +COUNT = 90 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffe000000000 +CIPHERTEXT = 2bf27229901eb40f2df9d8398d1505ae + +COUNT = 91 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffff000000000 +CIPHERTEXT = 83a63402a77f9ad5c1e931a931ecd706 + +COUNT = 92 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffff800000000 +CIPHERTEXT = 6f8ba6521152d31f2bada1843e26b973 + +COUNT = 93 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffc00000000 +CIPHERTEXT = e5c3b8e30fd2d8e6239b17b44bd23bbd + +COUNT = 94 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffe00000000 +CIPHERTEXT = 1ac1f7102c59933e8b2ddc3f14e94baa + +COUNT = 95 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffff00000000 +CIPHERTEXT = 21d9ba49f276b45f11af8fc71a088e3d + +COUNT = 96 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffff80000000 +CIPHERTEXT = 649f1cddc3792b4638635a392bc9bade + +COUNT = 97 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffc0000000 +CIPHERTEXT = e2775e4b59c1bc2e31a2078c11b5a08c + +COUNT = 98 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffe0000000 +CIPHERTEXT = 2be1fae5048a25582a679ca10905eb80 + +COUNT = 99 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffff0000000 +CIPHERTEXT = da86f292c6f41ea34fb2068df75ecc29 + +COUNT = 100 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffff8000000 +CIPHERTEXT = 220df19f85d69b1b562fa69a3c5beca5 + +COUNT = 101 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffc000000 +CIPHERTEXT = 1f11d5d0355e0b556ccdb6c7f5083b4d + +COUNT = 102 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffe000000 +CIPHERTEXT = 62526b78be79cb384633c91f83b4151b + +COUNT = 103 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffff000000 +CIPHERTEXT = 90ddbcb950843592dd47bbef00fdc876 + +COUNT = 104 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffff800000 +CIPHERTEXT = 2fd0e41c5b8402277354a7391d2618e2 + +COUNT = 105 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffc00000 +CIPHERTEXT = 3cdf13e72dee4c581bafec70b85f9660 + +COUNT = 106 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffe00000 +CIPHERTEXT = afa2ffc137577092e2b654fa199d2c43 + +COUNT = 107 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffff00000 +CIPHERTEXT = 8d683ee63e60d208e343ce48dbc44cac + +COUNT = 108 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffff80000 +CIPHERTEXT = 705a4ef8ba2133729c20185c3d3a4763 + +COUNT = 109 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffc0000 +CIPHERTEXT = 0861a861c3db4e94194211b77ed761b9 + +COUNT = 110 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffe0000 +CIPHERTEXT = 4b00c27e8b26da7eab9d3a88dec8b031 + +COUNT = 111 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffff0000 +CIPHERTEXT = 5f397bf03084820cc8810d52e5b666e9 + +COUNT = 112 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffff8000 +CIPHERTEXT = 63fafabb72c07bfbd3ddc9b1203104b8 + +COUNT = 113 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffffc000 +CIPHERTEXT = 683e2140585b18452dd4ffbb93c95df9 + +COUNT = 114 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffffe000 +CIPHERTEXT = 286894e48e537f8763b56707d7d155c8 + +COUNT = 115 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffff000 +CIPHERTEXT = a423deabc173dcf7e2c4c53e77d37cd1 + +COUNT = 116 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffff800 +CIPHERTEXT = eb8168313e1cfdfdb5e986d5429cf172 + +COUNT = 117 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffffc00 +CIPHERTEXT = 27127daafc9accd2fb334ec3eba52323 + +COUNT = 118 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffffe00 +CIPHERTEXT = ee0715b96f72e3f7a22a5064fc592f4c + +COUNT = 119 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffffff00 +CIPHERTEXT = 29ee526770f2a11dcfa989d1ce88830f + +COUNT = 120 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffffff80 +CIPHERTEXT = 0493370e054b09871130fe49af730a5a + +COUNT = 121 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffffffc0 +CIPHERTEXT = 9b7b940f6c509f9e44a4ee140448ee46 + +COUNT = 122 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffffffe0 +CIPHERTEXT = 2915be4a1ecfdcbe3e023811a12bb6c7 + +COUNT = 123 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffffff0 +CIPHERTEXT = 7240e524bc51d8c4d440b1be55d1062c + +COUNT = 124 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffffff8 +CIPHERTEXT = da63039d38cb4612b2dc36ba26684b93 + +COUNT = 125 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffffffc +CIPHERTEXT = 0f59cb5a4b522e2ac56c1a64f558ad9a + +COUNT = 126 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = fffffffffffffffffffffffffffffffe +CIPHERTEXT = 7bfe9d876c6d63c1d035da8fe21c409d + +COUNT = 127 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +PLAINTEXT = ffffffffffffffffffffffffffffffff +CIPHERTEXT = acdace8078a32b1a182bfa4987ca1347 + +[DECRYPT] + +COUNT = 0 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ddc6bf790c15760d8d9aeb6f9a75fd4e +PLAINTEXT = 80000000000000000000000000000000 + +COUNT = 1 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 0a6bdc6d4c1e6280301fd8e97ddbe601 +PLAINTEXT = c0000000000000000000000000000000 + +COUNT = 2 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 9b80eefb7ebe2d2b16247aa0efc72f5d +PLAINTEXT = e0000000000000000000000000000000 + +COUNT = 3 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 7f2c5ece07a98d8bee13c51177395ff7 +PLAINTEXT = f0000000000000000000000000000000 + +COUNT = 4 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 7818d800dcf6f4be1e0e94f403d1e4c2 +PLAINTEXT = f8000000000000000000000000000000 + +COUNT = 5 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = e74cd1c92f0919c35a0324123d6177d3 +PLAINTEXT = fc000000000000000000000000000000 + +COUNT = 6 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 8092a4dcf2da7e77e93bdd371dfed82e +PLAINTEXT = fe000000000000000000000000000000 + +COUNT = 7 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 49af6b372135acef10132e548f217b17 +PLAINTEXT = ff000000000000000000000000000000 + +COUNT = 8 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 8bcd40f94ebb63b9f7909676e667f1e7 +PLAINTEXT = ff800000000000000000000000000000 + +COUNT = 9 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = fe1cffb83f45dcfb38b29be438dbd3ab +PLAINTEXT = ffc00000000000000000000000000000 + +COUNT = 10 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 0dc58a8d886623705aec15cb1e70dc0e +PLAINTEXT = ffe00000000000000000000000000000 + +COUNT = 11 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = c218faa16056bd0774c3e8d79c35a5e4 +PLAINTEXT = fff00000000000000000000000000000 + +COUNT = 12 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 047bba83f7aa841731504e012208fc9e +PLAINTEXT = fff80000000000000000000000000000 + +COUNT = 13 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = dc8f0e4915fd81ba70a331310882f6da +PLAINTEXT = fffc0000000000000000000000000000 + +COUNT = 14 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 1569859ea6b7206c30bf4fd0cbfac33c +PLAINTEXT = fffe0000000000000000000000000000 + +COUNT = 15 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 300ade92f88f48fa2df730ec16ef44cd +PLAINTEXT = ffff0000000000000000000000000000 + +COUNT = 16 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 1fe6cc3c05965dc08eb0590c95ac71d0 +PLAINTEXT = ffff8000000000000000000000000000 + +COUNT = 17 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 59e858eaaa97fec38111275b6cf5abc0 +PLAINTEXT = ffffc000000000000000000000000000 + +COUNT = 18 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 2239455e7afe3b0616100288cc5a723b +PLAINTEXT = ffffe000000000000000000000000000 + +COUNT = 19 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 3ee500c5c8d63479717163e55c5c4522 +PLAINTEXT = fffff000000000000000000000000000 + +COUNT = 20 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d5e38bf15f16d90e3e214041d774daa8 +PLAINTEXT = fffff800000000000000000000000000 + +COUNT = 21 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = b1f4066e6f4f187dfe5f2ad1b17819d0 +PLAINTEXT = fffffc00000000000000000000000000 + +COUNT = 22 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 6ef4cc4de49b11065d7af2909854794a +PLAINTEXT = fffffe00000000000000000000000000 + +COUNT = 23 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ac86bc606b6640c309e782f232bf367f +PLAINTEXT = ffffff00000000000000000000000000 + +COUNT = 24 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 36aff0ef7bf3280772cf4cac80a0d2b2 +PLAINTEXT = ffffff80000000000000000000000000 + +COUNT = 25 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 1f8eedea0f62a1406d58cfc3ecea72cf +PLAINTEXT = ffffffc0000000000000000000000000 + +COUNT = 26 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = abf4154a3375a1d3e6b1d454438f95a6 +PLAINTEXT = ffffffe0000000000000000000000000 + +COUNT = 27 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 96f96e9d607f6615fc192061ee648b07 +PLAINTEXT = fffffff0000000000000000000000000 + +COUNT = 28 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = cf37cdaaa0d2d536c71857634c792064 +PLAINTEXT = fffffff8000000000000000000000000 + +COUNT = 29 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = fbd6640c80245c2b805373f130703127 +PLAINTEXT = fffffffc000000000000000000000000 + +COUNT = 30 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 8d6a8afe55a6e481badae0d146f436db +PLAINTEXT = fffffffe000000000000000000000000 + +COUNT = 31 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 6a4981f2915e3e68af6c22385dd06756 +PLAINTEXT = ffffffff000000000000000000000000 + +COUNT = 32 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 42a1136e5f8d8d21d3101998642d573b +PLAINTEXT = ffffffff800000000000000000000000 + +COUNT = 33 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 9b471596dc69ae1586cee6158b0b0181 +PLAINTEXT = ffffffffc00000000000000000000000 + +COUNT = 34 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 753665c4af1eff33aa8b628bf8741cfd +PLAINTEXT = ffffffffe00000000000000000000000 + +COUNT = 35 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 9a682acf40be01f5b2a4193c9a82404d +PLAINTEXT = fffffffff00000000000000000000000 + +COUNT = 36 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 54fafe26e4287f17d1935f87eb9ade01 +PLAINTEXT = fffffffff80000000000000000000000 + +COUNT = 37 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 49d541b2e74cfe73e6a8e8225f7bd449 +PLAINTEXT = fffffffffc0000000000000000000000 + +COUNT = 38 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 11a45530f624ff6f76a1b3826626ff7b +PLAINTEXT = fffffffffe0000000000000000000000 + +COUNT = 39 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = f96b0c4a8bc6c86130289f60b43b8fba +PLAINTEXT = ffffffffff0000000000000000000000 + +COUNT = 40 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 48c7d0e80834ebdc35b6735f76b46c8b +PLAINTEXT = ffffffffff8000000000000000000000 + +COUNT = 41 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 2463531ab54d66955e73edc4cb8eaa45 +PLAINTEXT = ffffffffffc000000000000000000000 + +COUNT = 42 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ac9bd8e2530469134b9d5b065d4f565b +PLAINTEXT = ffffffffffe000000000000000000000 + +COUNT = 43 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 3f5f9106d0e52f973d4890e6f37e8a00 +PLAINTEXT = fffffffffff000000000000000000000 + +COUNT = 44 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 20ebc86f1304d272e2e207e59db639f0 +PLAINTEXT = fffffffffff800000000000000000000 + +COUNT = 45 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = e67ae6426bf9526c972cff072b52252c +PLAINTEXT = fffffffffffc00000000000000000000 + +COUNT = 46 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 1a518dddaf9efa0d002cc58d107edfc8 +PLAINTEXT = fffffffffffe00000000000000000000 + +COUNT = 47 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ead731af4d3a2fe3b34bed047942a49f +PLAINTEXT = ffffffffffff00000000000000000000 + +COUNT = 48 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = b1d4efe40242f83e93b6c8d7efb5eae9 +PLAINTEXT = ffffffffffff80000000000000000000 + +COUNT = 49 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = cd2b1fec11fd906c5c7630099443610a +PLAINTEXT = ffffffffffffc0000000000000000000 + +COUNT = 50 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = a1853fe47fe29289d153161d06387d21 +PLAINTEXT = ffffffffffffe0000000000000000000 + +COUNT = 51 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 4632154179a555c17ea604d0889fab14 +PLAINTEXT = fffffffffffff0000000000000000000 + +COUNT = 52 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = dd27cac6401a022e8f38f9f93e774417 +PLAINTEXT = fffffffffffff8000000000000000000 + +COUNT = 53 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = c090313eb98674f35f3123385fb95d4d +PLAINTEXT = fffffffffffffc000000000000000000 + +COUNT = 54 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = cc3526262b92f02edce548f716b9f45c +PLAINTEXT = fffffffffffffe000000000000000000 + +COUNT = 55 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = c0838d1a2b16a7c7f0dfcc433c399c33 +PLAINTEXT = ffffffffffffff000000000000000000 + +COUNT = 56 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 0d9ac756eb297695eed4d382eb126d26 +PLAINTEXT = ffffffffffffff800000000000000000 + +COUNT = 57 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 56ede9dda3f6f141bff1757fa689c3e1 +PLAINTEXT = ffffffffffffffc00000000000000000 + +COUNT = 58 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 768f520efe0f23e61d3ec8ad9ce91774 +PLAINTEXT = ffffffffffffffe00000000000000000 + +COUNT = 59 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = b1144ddfa75755213390e7c596660490 +PLAINTEXT = fffffffffffffff00000000000000000 + +COUNT = 60 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 1d7c0c4040b355b9d107a99325e3b050 +PLAINTEXT = fffffffffffffff80000000000000000 + +COUNT = 61 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d8e2bb1ae8ee3dcf5bf7d6c38da82a1a +PLAINTEXT = fffffffffffffffc0000000000000000 + +COUNT = 62 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = faf82d178af25a9886a47e7f789b98d7 +PLAINTEXT = fffffffffffffffe0000000000000000 + +COUNT = 63 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 9b58dbfd77fe5aca9cfc190cd1b82d19 +PLAINTEXT = ffffffffffffffff0000000000000000 + +COUNT = 64 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 77f392089042e478ac16c0c86a0b5db5 +PLAINTEXT = ffffffffffffffff8000000000000000 + +COUNT = 65 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 19f08e3420ee69b477ca1420281c4782 +PLAINTEXT = ffffffffffffffffc000000000000000 + +COUNT = 66 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = a1b19beee4e117139f74b3c53fdcb875 +PLAINTEXT = ffffffffffffffffe000000000000000 + +COUNT = 67 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = a37a5869b218a9f3a0868d19aea0ad6a +PLAINTEXT = fffffffffffffffff000000000000000 + +COUNT = 68 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = bc3594e865bcd0261b13202731f33580 +PLAINTEXT = fffffffffffffffff800000000000000 + +COUNT = 69 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 811441ce1d309eee7185e8c752c07557 +PLAINTEXT = fffffffffffffffffc00000000000000 + +COUNT = 70 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 959971ce4134190563518e700b9874d1 +PLAINTEXT = fffffffffffffffffe00000000000000 + +COUNT = 71 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 76b5614a042707c98e2132e2e805fe63 +PLAINTEXT = ffffffffffffffffff00000000000000 + +COUNT = 72 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 7d9fa6a57530d0f036fec31c230b0cc6 +PLAINTEXT = ffffffffffffffffff80000000000000 + +COUNT = 73 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 964153a83bf6989a4ba80daa91c3e081 +PLAINTEXT = ffffffffffffffffffc0000000000000 + +COUNT = 74 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = a013014d4ce8054cf2591d06f6f2f176 +PLAINTEXT = ffffffffffffffffffe0000000000000 + +COUNT = 75 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = d1c5f6399bf382502e385eee1474a869 +PLAINTEXT = fffffffffffffffffff0000000000000 + +COUNT = 76 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 0007e20b8298ec354f0f5fe7470f36bd +PLAINTEXT = fffffffffffffffffff8000000000000 + +COUNT = 77 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = b95ba05b332da61ef63a2b31fcad9879 +PLAINTEXT = fffffffffffffffffffc000000000000 + +COUNT = 78 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 4620a49bd967491561669ab25dce45f4 +PLAINTEXT = fffffffffffffffffffe000000000000 + +COUNT = 79 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 12e71214ae8e04f0bb63d7425c6f14d5 +PLAINTEXT = ffffffffffffffffffff000000000000 + +COUNT = 80 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 4cc42fc1407b008fe350907c092e80ac +PLAINTEXT = ffffffffffffffffffff800000000000 + +COUNT = 81 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 08b244ce7cbc8ee97fbba808cb146fda +PLAINTEXT = ffffffffffffffffffffc00000000000 + +COUNT = 82 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 39b333e8694f21546ad1edd9d87ed95b +PLAINTEXT = ffffffffffffffffffffe00000000000 + +COUNT = 83 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 3b271f8ab2e6e4a20ba8090f43ba78f3 +PLAINTEXT = fffffffffffffffffffff00000000000 + +COUNT = 84 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 9ad983f3bf651cd0393f0a73cccdea50 +PLAINTEXT = fffffffffffffffffffff80000000000 + +COUNT = 85 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 8f476cbff75c1f725ce18e4bbcd19b32 +PLAINTEXT = fffffffffffffffffffffc0000000000 + +COUNT = 86 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 905b6267f1d6ab5320835a133f096f2a +PLAINTEXT = fffffffffffffffffffffe0000000000 + +COUNT = 87 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 145b60d6d0193c23f4221848a892d61a +PLAINTEXT = ffffffffffffffffffffff0000000000 + +COUNT = 88 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 55cfb3fb6d75cad0445bbc8dafa25b0f +PLAINTEXT = ffffffffffffffffffffff8000000000 + +COUNT = 89 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 7b8e7098e357ef71237d46d8b075b0f5 +PLAINTEXT = ffffffffffffffffffffffc000000000 + +COUNT = 90 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 2bf27229901eb40f2df9d8398d1505ae +PLAINTEXT = ffffffffffffffffffffffe000000000 + +COUNT = 91 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 83a63402a77f9ad5c1e931a931ecd706 +PLAINTEXT = fffffffffffffffffffffff000000000 + +COUNT = 92 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 6f8ba6521152d31f2bada1843e26b973 +PLAINTEXT = fffffffffffffffffffffff800000000 + +COUNT = 93 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = e5c3b8e30fd2d8e6239b17b44bd23bbd +PLAINTEXT = fffffffffffffffffffffffc00000000 + +COUNT = 94 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 1ac1f7102c59933e8b2ddc3f14e94baa +PLAINTEXT = fffffffffffffffffffffffe00000000 + +COUNT = 95 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 21d9ba49f276b45f11af8fc71a088e3d +PLAINTEXT = ffffffffffffffffffffffff00000000 + +COUNT = 96 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 649f1cddc3792b4638635a392bc9bade +PLAINTEXT = ffffffffffffffffffffffff80000000 + +COUNT = 97 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = e2775e4b59c1bc2e31a2078c11b5a08c +PLAINTEXT = ffffffffffffffffffffffffc0000000 + +COUNT = 98 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 2be1fae5048a25582a679ca10905eb80 +PLAINTEXT = ffffffffffffffffffffffffe0000000 + +COUNT = 99 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = da86f292c6f41ea34fb2068df75ecc29 +PLAINTEXT = fffffffffffffffffffffffff0000000 + +COUNT = 100 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 220df19f85d69b1b562fa69a3c5beca5 +PLAINTEXT = fffffffffffffffffffffffff8000000 + +COUNT = 101 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 1f11d5d0355e0b556ccdb6c7f5083b4d +PLAINTEXT = fffffffffffffffffffffffffc000000 + +COUNT = 102 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 62526b78be79cb384633c91f83b4151b +PLAINTEXT = fffffffffffffffffffffffffe000000 + +COUNT = 103 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 90ddbcb950843592dd47bbef00fdc876 +PLAINTEXT = ffffffffffffffffffffffffff000000 + +COUNT = 104 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 2fd0e41c5b8402277354a7391d2618e2 +PLAINTEXT = ffffffffffffffffffffffffff800000 + +COUNT = 105 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 3cdf13e72dee4c581bafec70b85f9660 +PLAINTEXT = ffffffffffffffffffffffffffc00000 + +COUNT = 106 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = afa2ffc137577092e2b654fa199d2c43 +PLAINTEXT = ffffffffffffffffffffffffffe00000 + +COUNT = 107 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 8d683ee63e60d208e343ce48dbc44cac +PLAINTEXT = fffffffffffffffffffffffffff00000 + +COUNT = 108 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 705a4ef8ba2133729c20185c3d3a4763 +PLAINTEXT = fffffffffffffffffffffffffff80000 + +COUNT = 109 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 0861a861c3db4e94194211b77ed761b9 +PLAINTEXT = fffffffffffffffffffffffffffc0000 + +COUNT = 110 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 4b00c27e8b26da7eab9d3a88dec8b031 +PLAINTEXT = fffffffffffffffffffffffffffe0000 + +COUNT = 111 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 5f397bf03084820cc8810d52e5b666e9 +PLAINTEXT = ffffffffffffffffffffffffffff0000 + +COUNT = 112 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 63fafabb72c07bfbd3ddc9b1203104b8 +PLAINTEXT = ffffffffffffffffffffffffffff8000 + +COUNT = 113 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 683e2140585b18452dd4ffbb93c95df9 +PLAINTEXT = ffffffffffffffffffffffffffffc000 + +COUNT = 114 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 286894e48e537f8763b56707d7d155c8 +PLAINTEXT = ffffffffffffffffffffffffffffe000 + +COUNT = 115 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = a423deabc173dcf7e2c4c53e77d37cd1 +PLAINTEXT = fffffffffffffffffffffffffffff000 + +COUNT = 116 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = eb8168313e1cfdfdb5e986d5429cf172 +PLAINTEXT = fffffffffffffffffffffffffffff800 + +COUNT = 117 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 27127daafc9accd2fb334ec3eba52323 +PLAINTEXT = fffffffffffffffffffffffffffffc00 + +COUNT = 118 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = ee0715b96f72e3f7a22a5064fc592f4c +PLAINTEXT = fffffffffffffffffffffffffffffe00 + +COUNT = 119 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 29ee526770f2a11dcfa989d1ce88830f +PLAINTEXT = ffffffffffffffffffffffffffffff00 + +COUNT = 120 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 0493370e054b09871130fe49af730a5a +PLAINTEXT = ffffffffffffffffffffffffffffff80 + +COUNT = 121 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 9b7b940f6c509f9e44a4ee140448ee46 +PLAINTEXT = ffffffffffffffffffffffffffffffc0 + +COUNT = 122 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 2915be4a1ecfdcbe3e023811a12bb6c7 +PLAINTEXT = ffffffffffffffffffffffffffffffe0 + +COUNT = 123 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 7240e524bc51d8c4d440b1be55d1062c +PLAINTEXT = fffffffffffffffffffffffffffffff0 + +COUNT = 124 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = da63039d38cb4612b2dc36ba26684b93 +PLAINTEXT = fffffffffffffffffffffffffffffff8 + +COUNT = 125 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 0f59cb5a4b522e2ac56c1a64f558ad9a +PLAINTEXT = fffffffffffffffffffffffffffffffc + +COUNT = 126 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = 7bfe9d876c6d63c1d035da8fe21c409d +PLAINTEXT = fffffffffffffffffffffffffffffffe + +COUNT = 127 +KEY = 0000000000000000000000000000000000000000000000000000000000000000 +CIPHERTEXT = acdace8078a32b1a182bfa4987ca1347 +PLAINTEXT = ffffffffffffffffffffffffffffffff + diff --git a/libraries/crypto/tests/data/ecdsa_secp256r1_sha256_test.json b/libraries/crypto/tests/data/ecdsa_secp256r1_sha256_test.json new file mode 100644 index 0000000..0b8ab9f --- /dev/null +++ b/libraries/crypto/tests/data/ecdsa_secp256r1_sha256_test.json @@ -0,0 +1,4578 @@ +{ + "algorithm" : "ECDSA", + "generatorVersion" : "0.8r12", + "numberOfTests" : 387, + "header" : [ + "Test vectors of type EcdsaVerify are meant for the verification", + "of ASN encoded ECDSA signatures." + ], + "notes" : { + "BER" : "This is a signature with correct values for (r, s) but using some alternative BER encoding instead of DER encoding. Implementations should not accept such signatures to limit signature malleability.", + "EdgeCase" : "Edge case values such as r=1 and s=0 can lead to forgeries if the ECDSA implementation does not check boundaries and computes s^(-1)==0.", + "MissingZero" : "Some implementations of ECDSA and DSA incorrectly encode r and s by not including leading zeros in the ASN encoding of integers when necessary. Hence, some implementations (e.g. jdk) allow signatures with incorrect ASN encodings assuming that the signature is otherwise valid.", + "PointDuplication" : "Some implementations of ECDSA do not handle duplication and points at infinity correctly. This is a test vector that has been specially crafted to check for such an omission." + }, + "schema" : "ecdsa_verify_schema.json", + "testGroups" : [ + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e", + "wx" : "2927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838", + "wy" : "00c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKSexBRK64+3c/kZ4KBKLrSkDJpkZ\n9whgacjE32xzKDjHeHlk6qwA5ZIfsUmKYPRgZ2az2WhQAVWNGpdOc0FRPg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 1, + "comment" : "signature malleability", + "msg" : "313233343030", + "sig" : "304402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1802204cd60b855d442f5b3c7b11eb6c4e0ae7525fe710fab9aa7c77a67f79e6fadd76", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 2, + "comment" : "Legacy:ASN encoding of s misses leading 0", + "msg" : "313233343030", + "sig" : "304402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180220b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "acceptable", + "flags" : [ + "MissingZero" + ] + }, + { + "tcId" : 3, + "comment" : "valid", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 4, + "comment" : "long form encoding of length of sequence", + "msg" : "313233343030", + "sig" : "30814502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [ + "BER" + ] + }, + { + "tcId" : 5, + "comment" : "length of sequence contains leading 0", + "msg" : "313233343030", + "sig" : "3082004502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [ + "BER" + ] + }, + { + "tcId" : 6, + "comment" : "wrong length of sequence", + "msg" : "313233343030", + "sig" : "304602202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 7, + "comment" : "wrong length of sequence", + "msg" : "313233343030", + "sig" : "304402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 8, + "comment" : "uint32 overflow in length of sequence", + "msg" : "313233343030", + "sig" : "3085010000004502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 9, + "comment" : "uint64 overflow in length of sequence", + "msg" : "313233343030", + "sig" : "308901000000000000004502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 10, + "comment" : "length of sequence = 2**31 - 1", + "msg" : "313233343030", + "sig" : "30847fffffff02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 11, + "comment" : "length of sequence = 2**32 - 1", + "msg" : "313233343030", + "sig" : "3084ffffffff02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 12, + "comment" : "length of sequence = 2**40 - 1", + "msg" : "313233343030", + "sig" : "3085ffffffffff02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 13, + "comment" : "length of sequence = 2**64 - 1", + "msg" : "313233343030", + "sig" : "3088ffffffffffffffff02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 14, + "comment" : "incorrect length of sequence", + "msg" : "313233343030", + "sig" : "30ff02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 15, + "comment" : "indefinite length without termination", + "msg" : "313233343030", + "sig" : "308002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 16, + "comment" : "indefinite length without termination", + "msg" : "313233343030", + "sig" : "304502802ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 17, + "comment" : "indefinite length without termination", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18028000b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 18, + "comment" : "removing sequence", + "msg" : "313233343030", + "sig" : "", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 19, + "comment" : "lonely sequence tag", + "msg" : "313233343030", + "sig" : "30", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 20, + "comment" : "appending 0's to sequence", + "msg" : "313233343030", + "sig" : "304702202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 21, + "comment" : "prepending 0's to sequence", + "msg" : "313233343030", + "sig" : "3047000002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 22, + "comment" : "appending unused 0's to sequence", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 23, + "comment" : "appending null value to sequence", + "msg" : "313233343030", + "sig" : "304702202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0500", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 24, + "comment" : "including garbage", + "msg" : "313233343030", + "sig" : "304a498177304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 25, + "comment" : "including garbage", + "msg" : "313233343030", + "sig" : "30492500304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 26, + "comment" : "including garbage", + "msg" : "313233343030", + "sig" : "3047304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0004deadbeef", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 27, + "comment" : "including garbage", + "msg" : "313233343030", + "sig" : "304a222549817702202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 28, + "comment" : "including garbage", + "msg" : "313233343030", + "sig" : "30492224250002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 29, + "comment" : "including garbage", + "msg" : "313233343030", + "sig" : "304d222202202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180004deadbeef022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 30, + "comment" : "including garbage", + "msg" : "313233343030", + "sig" : "304a02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e182226498177022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 31, + "comment" : "including garbage", + "msg" : "313233343030", + "sig" : "304902202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1822252500022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 32, + "comment" : "including garbage", + "msg" : "313233343030", + "sig" : "304d02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e182223022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0004deadbeef", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 33, + "comment" : "including undefined tags", + "msg" : "313233343030", + "sig" : "304daa00bb00cd00304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 34, + "comment" : "including undefined tags", + "msg" : "313233343030", + "sig" : "304baa02aabb304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 35, + "comment" : "including undefined tags", + "msg" : "313233343030", + "sig" : "304d2228aa00bb00cd0002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 36, + "comment" : "including undefined tags", + "msg" : "313233343030", + "sig" : "304b2226aa02aabb02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 37, + "comment" : "including undefined tags", + "msg" : "313233343030", + "sig" : "304d02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e182229aa00bb00cd00022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 38, + "comment" : "including undefined tags", + "msg" : "313233343030", + "sig" : "304b02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e182227aa02aabb022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 39, + "comment" : "truncated length of sequence", + "msg" : "313233343030", + "sig" : "3081", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 40, + "comment" : "using composition with indefinite length", + "msg" : "313233343030", + "sig" : "3080304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 41, + "comment" : "using composition with indefinite length", + "msg" : "313233343030", + "sig" : "3049228002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180000022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 42, + "comment" : "using composition with indefinite length", + "msg" : "313233343030", + "sig" : "304902202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e182280022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 43, + "comment" : "using composition with wrong tag", + "msg" : "313233343030", + "sig" : "3080314502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 44, + "comment" : "using composition with wrong tag", + "msg" : "313233343030", + "sig" : "3049228003202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180000022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 45, + "comment" : "using composition with wrong tag", + "msg" : "313233343030", + "sig" : "304902202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e182280032100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 46, + "comment" : "Replacing sequence with NULL", + "msg" : "313233343030", + "sig" : "0500", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 47, + "comment" : "changing tag value of sequence", + "msg" : "313233343030", + "sig" : "2e4502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 48, + "comment" : "changing tag value of sequence", + "msg" : "313233343030", + "sig" : "2f4502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 49, + "comment" : "changing tag value of sequence", + "msg" : "313233343030", + "sig" : "314502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 50, + "comment" : "changing tag value of sequence", + "msg" : "313233343030", + "sig" : "324502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 51, + "comment" : "changing tag value of sequence", + "msg" : "313233343030", + "sig" : "ff4502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 52, + "comment" : "dropping value of sequence", + "msg" : "313233343030", + "sig" : "3000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 53, + "comment" : "using composition for sequence", + "msg" : "313233343030", + "sig" : "30493001023044202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 54, + "comment" : "truncated sequence", + "msg" : "313233343030", + "sig" : "304402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 55, + "comment" : "truncated sequence", + "msg" : "313233343030", + "sig" : "3044202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 56, + "comment" : "indefinite length", + "msg" : "313233343030", + "sig" : "308002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0000", + "result" : "invalid", + "flags" : [ + "BER" + ] + }, + { + "tcId" : 57, + "comment" : "indefinite length with truncated delimiter", + "msg" : "313233343030", + "sig" : "308002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db00", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 58, + "comment" : "indefinite length with additional element", + "msg" : "313233343030", + "sig" : "308002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db05000000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 59, + "comment" : "indefinite length with truncated element", + "msg" : "313233343030", + "sig" : "308002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db060811220000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 60, + "comment" : "indefinite length with garbage", + "msg" : "313233343030", + "sig" : "308002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0000fe02beef", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 61, + "comment" : "indefinite length with nonempty EOC", + "msg" : "313233343030", + "sig" : "308002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0002beef", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 62, + "comment" : "prepend empty sequence", + "msg" : "313233343030", + "sig" : "3047300002202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 63, + "comment" : "append empty sequence", + "msg" : "313233343030", + "sig" : "304702202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db3000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 64, + "comment" : "append garbage with high tag number", + "msg" : "313233343030", + "sig" : "304802202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847dbbf7f00", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 65, + "comment" : "sequence of sequence", + "msg" : "313233343030", + "sig" : "3047304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 66, + "comment" : "truncated sequence: removed last 1 elements", + "msg" : "313233343030", + "sig" : "302202202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 67, + "comment" : "repeating element in sequence", + "msg" : "313233343030", + "sig" : "306802202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 68, + "comment" : "long form encoding of length of integer", + "msg" : "313233343030", + "sig" : "30460281202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [ + "BER" + ] + }, + { + "tcId" : 69, + "comment" : "long form encoding of length of integer", + "msg" : "313233343030", + "sig" : "304602202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1802812100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [ + "BER" + ] + }, + { + "tcId" : 70, + "comment" : "length of integer contains leading 0", + "msg" : "313233343030", + "sig" : "3047028200202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [ + "BER" + ] + }, + { + "tcId" : 71, + "comment" : "length of integer contains leading 0", + "msg" : "313233343030", + "sig" : "304702202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180282002100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [ + "BER" + ] + }, + { + "tcId" : 72, + "comment" : "wrong length of integer", + "msg" : "313233343030", + "sig" : "304502212ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 73, + "comment" : "wrong length of integer", + "msg" : "313233343030", + "sig" : "3045021f2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 74, + "comment" : "wrong length of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022200b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 75, + "comment" : "wrong length of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022000b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 76, + "comment" : "uint32 overflow in length of integer", + "msg" : "313233343030", + "sig" : "304a028501000000202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 77, + "comment" : "uint32 overflow in length of integer", + "msg" : "313233343030", + "sig" : "304a02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180285010000002100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 78, + "comment" : "uint64 overflow in length of integer", + "msg" : "313233343030", + "sig" : "304e02890100000000000000202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 79, + "comment" : "uint64 overflow in length of integer", + "msg" : "313233343030", + "sig" : "304e02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18028901000000000000002100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 80, + "comment" : "length of integer = 2**31 - 1", + "msg" : "313233343030", + "sig" : "304902847fffffff2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 81, + "comment" : "length of integer = 2**31 - 1", + "msg" : "313233343030", + "sig" : "304902202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1802847fffffff00b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 82, + "comment" : "length of integer = 2**32 - 1", + "msg" : "313233343030", + "sig" : "30490284ffffffff2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 83, + "comment" : "length of integer = 2**32 - 1", + "msg" : "313233343030", + "sig" : "304902202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180284ffffffff00b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 84, + "comment" : "length of integer = 2**40 - 1", + "msg" : "313233343030", + "sig" : "304a0285ffffffffff2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 85, + "comment" : "length of integer = 2**40 - 1", + "msg" : "313233343030", + "sig" : "304a02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180285ffffffffff00b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 86, + "comment" : "length of integer = 2**64 - 1", + "msg" : "313233343030", + "sig" : "304d0288ffffffffffffffff2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 87, + "comment" : "length of integer = 2**64 - 1", + "msg" : "313233343030", + "sig" : "304d02202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180288ffffffffffffffff00b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 88, + "comment" : "incorrect length of integer", + "msg" : "313233343030", + "sig" : "304502ff2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 89, + "comment" : "incorrect length of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1802ff00b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 90, + "comment" : "removing integer", + "msg" : "313233343030", + "sig" : "3023022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 91, + "comment" : "lonely integer tag", + "msg" : "313233343030", + "sig" : "302402022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 92, + "comment" : "lonely integer tag", + "msg" : "313233343030", + "sig" : "302302202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1802", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 93, + "comment" : "appending 0's to integer", + "msg" : "313233343030", + "sig" : "304702222ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180000022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 94, + "comment" : "appending 0's to integer", + "msg" : "313233343030", + "sig" : "304702202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022300b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 95, + "comment" : "prepending 0's to integer", + "msg" : "313233343030", + "sig" : "3047022200002ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [ + "BER" + ] + }, + { + "tcId" : 96, + "comment" : "prepending 0's to integer", + "msg" : "313233343030", + "sig" : "304702202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180223000000b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [ + "BER" + ] + }, + { + "tcId" : 97, + "comment" : "appending unused 0's to integer", + "msg" : "313233343030", + "sig" : "304702202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180000022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 98, + "comment" : "appending null value to integer", + "msg" : "313233343030", + "sig" : "304702222ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180500022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 99, + "comment" : "appending null value to integer", + "msg" : "313233343030", + "sig" : "304702202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022300b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db0500", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 100, + "comment" : "truncated length of integer", + "msg" : "313233343030", + "sig" : "30250281022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 101, + "comment" : "truncated length of integer", + "msg" : "313233343030", + "sig" : "302402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180281", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 102, + "comment" : "Replacing integer with NULL", + "msg" : "313233343030", + "sig" : "30250500022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 103, + "comment" : "Replacing integer with NULL", + "msg" : "313233343030", + "sig" : "302402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180500", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 104, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "304500202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 105, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "304501202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 106, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "304503202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 107, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "304504202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 108, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "3045ff202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 109, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18002100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 110, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18012100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 111, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18032100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 112, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18042100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 113, + "comment" : "changing tag value of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18ff2100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 114, + "comment" : "dropping value of integer", + "msg" : "313233343030", + "sig" : "30250200022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 115, + "comment" : "dropping value of integer", + "msg" : "313233343030", + "sig" : "302402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180200", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 116, + "comment" : "using composition for integer", + "msg" : "313233343030", + "sig" : "3049222402012b021fa3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 117, + "comment" : "using composition for integer", + "msg" : "313233343030", + "sig" : "304902202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1822250201000220b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 118, + "comment" : "modify first byte of integer", + "msg" : "313233343030", + "sig" : "3045022029a3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 119, + "comment" : "modify first byte of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022102b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 120, + "comment" : "modify last byte of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e98022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 121, + "comment" : "modify last byte of integer", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b491568475b", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 122, + "comment" : "truncated integer", + "msg" : "313233343030", + "sig" : "3044021f2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 123, + "comment" : "truncated integer", + "msg" : "313233343030", + "sig" : "3044021fa3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 124, + "comment" : "truncated integer", + "msg" : "313233343030", + "sig" : "304402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022000b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 125, + "comment" : "leading ff in integer", + "msg" : "313233343030", + "sig" : "30460221ff2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 126, + "comment" : "leading ff in integer", + "msg" : "313233343030", + "sig" : "304602202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180222ff00b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 127, + "comment" : "replaced integer by infinity", + "msg" : "313233343030", + "sig" : "3026090180022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 128, + "comment" : "replaced integer by infinity", + "msg" : "313233343030", + "sig" : "302502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18090180", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 129, + "comment" : "replacing integer with zero", + "msg" : "313233343030", + "sig" : "3026020100022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 130, + "comment" : "replacing integer with zero", + "msg" : "313233343030", + "sig" : "302502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18020100", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 131, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "30460221012ba3a8bd6b94d5ed80a6d9d1190a436ebccc0833490686deac8635bcb9bf5369022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 132, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "30460221ff2ba3a8bf6b94d5eb80a6d9d1190a436f42fe12d7fad749d4c512a036c0f908c7022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 133, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "30450220d45c5741946b2a137f59262ee6f5bc91001af27a5e1117a64733950642a3d1e8022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 134, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "3046022100d45c5740946b2a147f59262ee6f5bc90bd01ed280528b62b3aed5fc93f06f739022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 135, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "30460221fed45c5742946b2a127f59262ee6f5bc914333f7ccb6f979215379ca434640ac97022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 136, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "30460221012ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 137, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "3046022100d45c5741946b2a137f59262ee6f5bc91001af27a5e1117a64733950642a3d1e8022100b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 138, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022101b329f478a2bbd0a6c384ee1493b1f518276e0e4a5375928d6fcd160c11cb6d2c", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 139, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "304402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180220b329f47aa2bbd0a4c384ee1493b1f518ada018ef05465583885980861905228a", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 140, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180221ff4cd60b865d442f5a3c7b11eb6c4e0ae79578ec6353a20bf783ecb4b6ea97b825", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 141, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e180221fe4cd60b875d442f593c7b11eb6c4e0ae7d891f1b5ac8a6d729032e9f3ee3492d4", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 142, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "304502202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18022101b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 143, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "304402202ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1802204cd60b865d442f5a3c7b11eb6c4e0ae79578ec6353a20bf783ecb4b6ea97b825", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 144, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3006020100020100", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 145, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3006020100020101", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 146, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30060201000201ff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 147, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020100022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 148, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020100022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 149, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020100022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 150, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020100022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 151, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020100022100ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 152, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3008020100090380fe01", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 153, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3006020100090142", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 154, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3006020101020100", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 155, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3006020101020101", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 156, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30060201010201ff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 157, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020101022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 158, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020101022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 159, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020101022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 160, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020101022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 161, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026020101022100ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 162, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3008020101090380fe01", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 163, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3006020101090142", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 164, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30060201ff020100", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 165, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30060201ff020101", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 166, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30060201ff0201ff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 167, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30260201ff022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 168, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30260201ff022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 169, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30260201ff022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 170, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30260201ff022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 171, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30260201ff022100ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 172, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30080201ff090380fe01", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 173, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "30060201ff090142", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 174, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551020100", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 175, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551020101", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 176, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325510201ff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 177, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 178, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 179, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 180, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 181, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551022100ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 182, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3028022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551090380fe01", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 183, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551090142", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 184, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550020100", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 185, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550020101", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 186, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325500201ff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 187, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 188, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 189, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 190, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 191, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550022100ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 192, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3028022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550090380fe01", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 193, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550090142", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 194, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552020100", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 195, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552020101", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 196, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325520201ff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 197, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 198, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 199, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 200, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 201, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552022100ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 202, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3028022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552090380fe01", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 203, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552090142", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 204, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff020100", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 205, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff020101", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 206, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff0201ff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 207, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 208, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 209, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 210, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 211, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff022100ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 212, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3028022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff090380fe01", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 213, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff090142", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 214, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000001000000000000000000000001000000000000000000000000020100", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 215, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000001000000000000000000000001000000000000000000000000020101", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 216, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff000000010000000000000000000000010000000000000000000000000201ff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 217, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000001000000000000000000000000022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 218, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000001000000000000000000000000022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 219, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000001000000000000000000000000022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 220, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000001000000000000000000000000022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 221, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000001000000000000000000000000022100ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 222, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3028022100ffffffff00000001000000000000000000000001000000000000000000000000090380fe01", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 223, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000001000000000000000000000001000000000000000000000000090142", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 224, + "comment" : "Signature encoding contains wrong types.", + "msg" : "313233343030", + "sig" : "30060201010c0130", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 225, + "comment" : "Signature encoding contains wrong types.", + "msg" : "313233343030", + "sig" : "30050201010c00", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 226, + "comment" : "Signature encoding contains wrong types.", + "msg" : "313233343030", + "sig" : "30090c0225730c03732573", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 227, + "comment" : "Signature encoding contains wrong types.", + "msg" : "313233343030", + "sig" : "30080201013003020100", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 228, + "comment" : "Signature encoding contains wrong types.", + "msg" : "313233343030", + "sig" : "3003020101", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 229, + "comment" : "Signature encoding contains wrong types.", + "msg" : "313233343030", + "sig" : "3006020101010100", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 230, + "comment" : "Edge case for Shamir multiplication", + "msg" : "3639383139", + "sig" : "3044022064a1aab5000d0e804f3e2fc02bdee9be8ff312334e2ba16d11547c97711c898e02206af015971cc30be6d1a206d4e013e0997772a2f91d73286ffd683b9bb2cf4f1b", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 231, + "comment" : "special case hash", + "msg" : "343236343739373234", + "sig" : "3044022016aea964a2f6506d6f78c81c91fc7e8bded7d397738448de1e19a0ec580bf2660220252cd762130c6667cfe8b7bc47d27d78391e8e80c578d1cd38c3ff033be928e9", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 232, + "comment" : "special case hash", + "msg" : "37313338363834383931", + "sig" : "30450221009cc98be2347d469bf476dfc26b9b733df2d26d6ef524af917c665baccb23c8820220093496459effe2d8d70727b82462f61d0ec1b7847929d10ea631dacb16b56c32", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 233, + "comment" : "special case hash", + "msg" : "3130333539333331363638", + "sig" : "3044022073b3c90ecd390028058164524dde892703dce3dea0d53fa8093999f07ab8aa4302202f67b0b8e20636695bb7d8bf0a651c802ed25a395387b5f4188c0c4075c88634", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 234, + "comment" : "special case hash", + "msg" : "33393439343031323135", + "sig" : "3046022100bfab3098252847b328fadf2f89b95c851a7f0eb390763378f37e90119d5ba3dd022100bdd64e234e832b1067c2d058ccb44d978195ccebb65c2aaf1e2da9b8b4987e3b", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 235, + "comment" : "special case hash", + "msg" : "31333434323933303739", + "sig" : "30440220204a9784074b246d8bf8bf04a4ceb1c1f1c9aaab168b1596d17093c5cd21d2cd022051cce41670636783dc06a759c8847868a406c2506fe17975582fe648d1d88b52", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 236, + "comment" : "special case hash", + "msg" : "33373036323131373132", + "sig" : "3046022100ed66dc34f551ac82f63d4aa4f81fe2cb0031a91d1314f835027bca0f1ceeaa0302210099ca123aa09b13cd194a422e18d5fda167623c3f6e5d4d6abb8953d67c0c48c7", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 237, + "comment" : "special case hash", + "msg" : "333433363838373132", + "sig" : "30450220060b700bef665c68899d44f2356a578d126b062023ccc3c056bf0f60a237012b0221008d186c027832965f4fcc78a3366ca95dedbb410cbef3f26d6be5d581c11d3610", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 238, + "comment" : "special case hash", + "msg" : "31333531353330333730", + "sig" : "30460221009f6adfe8d5eb5b2c24d7aa7934b6cf29c93ea76cd313c9132bb0c8e38c96831d022100b26a9c9e40e55ee0890c944cf271756c906a33e66b5bd15e051593883b5e9902", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 239, + "comment" : "special case hash", + "msg" : "36353533323033313236", + "sig" : "3045022100a1af03ca91677b673ad2f33615e56174a1abf6da168cebfa8868f4ba273f16b7022020aa73ffe48afa6435cd258b173d0c2377d69022e7d098d75caf24c8c5e06b1c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 240, + "comment" : "special case hash", + "msg" : "31353634333436363033", + "sig" : "3045022100fdc70602766f8eed11a6c99a71c973d5659355507b843da6e327a28c11893db902203df5349688a085b137b1eacf456a9e9e0f6d15ec0078ca60a7f83f2b10d21350", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 241, + "comment" : "special case hash", + "msg" : "34343239353339313137", + "sig" : "3046022100b516a314f2fce530d6537f6a6c49966c23456f63c643cf8e0dc738f7b876e675022100d39ffd033c92b6d717dd536fbc5efdf1967c4bd80954479ba66b0120cd16fff2", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 242, + "comment" : "special case hash", + "msg" : "3130393533323631333531", + "sig" : "304402203b2cbf046eac45842ecb7984d475831582717bebb6492fd0a485c101e29ff0a802204c9b7b47a98b0f82de512bc9313aaf51701099cac5f76e68c8595fc1c1d99258", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 243, + "comment" : "special case hash", + "msg" : "35393837333530303431", + "sig" : "3044022030c87d35e636f540841f14af54e2f9edd79d0312cfa1ab656c3fb15bfde48dcf022047c15a5a82d24b75c85a692bd6ecafeb71409ede23efd08e0db9abf6340677ed", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 244, + "comment" : "special case hash", + "msg" : "33343633303036383738", + "sig" : "3044022038686ff0fda2cef6bc43b58cfe6647b9e2e8176d168dec3c68ff262113760f520220067ec3b651f422669601662167fa8717e976e2db5e6a4cf7c2ddabb3fde9d67d", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 245, + "comment" : "special case hash", + "msg" : "39383137333230323837", + "sig" : "3044022044a3e23bf314f2b344fc25c7f2de8b6af3e17d27f5ee844b225985ab6e2775cf02202d48e223205e98041ddc87be532abed584f0411f5729500493c9cc3f4dd15e86", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 246, + "comment" : "special case hash", + "msg" : "33323232303431303436", + "sig" : "304402202ded5b7ec8e90e7bf11f967a3d95110c41b99db3b5aa8d330eb9d638781688e902207d5792c53628155e1bfc46fb1a67e3088de049c328ae1f44ec69238a009808f9", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 247, + "comment" : "special case hash", + "msg" : "36363636333037313034", + "sig" : "3046022100bdae7bcb580bf335efd3bc3d31870f923eaccafcd40ec2f605976f15137d8b8f022100f6dfa12f19e525270b0106eecfe257499f373a4fb318994f24838122ce7ec3c7", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 248, + "comment" : "special case hash", + "msg" : "31303335393531383938", + "sig" : "3045022050f9c4f0cd6940e162720957ffff513799209b78596956d21ece251c2401f1c6022100d7033a0a787d338e889defaaabb106b95a4355e411a59c32aa5167dfab244726", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 249, + "comment" : "special case hash", + "msg" : "31383436353937313935", + "sig" : "3045022100f612820687604fa01906066a378d67540982e29575d019aabe90924ead5c860d02203f9367702dd7dd4f75ea98afd20e328a1a99f4857b316525328230ce294b0fef", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 250, + "comment" : "special case hash", + "msg" : "33313336303436313839", + "sig" : "30460221009505e407657d6e8bc93db5da7aa6f5081f61980c1949f56b0f2f507da5782a7a022100c60d31904e3669738ffbeccab6c3656c08e0ed5cb92b3cfa5e7f71784f9c5021", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 251, + "comment" : "special case hash", + "msg" : "32363633373834323534", + "sig" : "3046022100bbd16fbbb656b6d0d83e6a7787cd691b08735aed371732723e1c68a40404517d0221009d8e35dba96028b7787d91315be675877d2d097be5e8ee34560e3e7fd25c0f00", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 252, + "comment" : "special case hash", + "msg" : "31363532313030353234", + "sig" : "304402202ec9760122db98fd06ea76848d35a6da442d2ceef7559a30cf57c61e92df327e02207ab271da90859479701fccf86e462ee3393fb6814c27b760c4963625c0a19878", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 253, + "comment" : "special case hash", + "msg" : "35373438303831363936", + "sig" : "3044022054e76b7683b6650baa6a7fc49b1c51eed9ba9dd463221f7a4f1005a89fe00c5902202ea076886c773eb937ec1cc8374b7915cfd11b1c1ae1166152f2f7806a31c8fd", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 254, + "comment" : "special case hash", + "msg" : "36333433393133343638", + "sig" : "304402205291deaf24659ffbbce6e3c26f6021097a74abdbb69be4fb10419c0c496c9466022065d6fcf336d27cc7cdb982bb4e4ecef5827f84742f29f10abf83469270a03dc3", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 255, + "comment" : "special case hash", + "msg" : "31353431313033353938", + "sig" : "30450220207a3241812d75d947419dc58efb05e8003b33fc17eb50f9d15166a88479f107022100cdee749f2e492b213ce80b32d0574f62f1c5d70793cf55e382d5caadf7592767", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 256, + "comment" : "special case hash", + "msg" : "3130343738353830313238", + "sig" : "304502206554e49f82a855204328ac94913bf01bbe84437a355a0a37c0dee3cf81aa7728022100aea00de2507ddaf5c94e1e126980d3df16250a2eaebc8be486effe7f22b4f929", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 257, + "comment" : "special case hash", + "msg" : "3130353336323835353638", + "sig" : "3046022100a54c5062648339d2bff06f71c88216c26c6e19b4d80a8c602990ac82707efdfc022100e99bbe7fcfafae3e69fd016777517aa01056317f467ad09aff09be73c9731b0d", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 258, + "comment" : "special case hash", + "msg" : "393533393034313035", + "sig" : "3045022100975bd7157a8d363b309f1f444012b1a1d23096593133e71b4ca8b059cff37eaf02207faa7a28b1c822baa241793f2abc930bd4c69840fe090f2aacc46786bf919622", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 259, + "comment" : "special case hash", + "msg" : "393738383438303339", + "sig" : "304402205694a6f84b8f875c276afd2ebcfe4d61de9ec90305afb1357b95b3e0da43885e02200dffad9ffd0b757d8051dec02ebdf70d8ee2dc5c7870c0823b6ccc7c679cbaa4", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 260, + "comment" : "special case hash", + "msg" : "33363130363732343432", + "sig" : "3045022100a0c30e8026fdb2b4b4968a27d16a6d08f7098f1a98d21620d7454ba9790f1ba602205e470453a8a399f15baf463f9deceb53acc5ca64459149688bd2760c65424339", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 261, + "comment" : "special case hash", + "msg" : "31303534323430373035", + "sig" : "30440220614ea84acf736527dd73602cd4bb4eea1dfebebd5ad8aca52aa0228cf7b99a880220737cc85f5f2d2f60d1b8183f3ed490e4de14368e96a9482c2a4dd193195c902f", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 262, + "comment" : "special case hash", + "msg" : "35313734343438313937", + "sig" : "3045022100bead6734ebe44b810d3fb2ea00b1732945377338febfd439a8d74dfbd0f942fa02206bb18eae36616a7d3cad35919fd21a8af4bbe7a10f73b3e036a46b103ef56e2a", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 263, + "comment" : "special case hash", + "msg" : "31393637353631323531", + "sig" : "30440220499625479e161dacd4db9d9ce64854c98d922cbf212703e9654fae182df9bad2022042c177cf37b8193a0131108d97819edd9439936028864ac195b64fca76d9d693", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 264, + "comment" : "special case hash", + "msg" : "33343437323533333433", + "sig" : "3045022008f16b8093a8fb4d66a2c8065b541b3d31e3bfe694f6b89c50fb1aaa6ff6c9b20221009d6455e2d5d1779748573b611cb95d4a21f967410399b39b535ba3e5af81ca2e", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 265, + "comment" : "special case hash", + "msg" : "333638323634333138", + "sig" : "3046022100be26231b6191658a19dd72ddb99ed8f8c579b6938d19bce8eed8dc2b338cb5f8022100e1d9a32ee56cffed37f0f22b2dcb57d5c943c14f79694a03b9c5e96952575c89", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 266, + "comment" : "special case hash", + "msg" : "33323631313938363038", + "sig" : "3045022015e76880898316b16204ac920a02d58045f36a229d4aa4f812638c455abe0443022100e74d357d3fcb5c8c5337bd6aba4178b455ca10e226e13f9638196506a1939123", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 267, + "comment" : "special case hash", + "msg" : "39363738373831303934", + "sig" : "30440220352ecb53f8df2c503a45f9846fc28d1d31e6307d3ddbffc1132315cc07f16dad02201348dfa9c482c558e1d05c5242ca1c39436726ecd28258b1899792887dd0a3c6", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 268, + "comment" : "special case hash", + "msg" : "34393538383233383233", + "sig" : "304402204a40801a7e606ba78a0da9882ab23c7677b8642349ed3d652c5bfa5f2a9558fb02203a49b64848d682ef7f605f2832f7384bdc24ed2925825bf8ea77dc5981725782", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 269, + "comment" : "special case hash", + "msg" : "383234363337383337", + "sig" : "3045022100eacc5e1a8304a74d2be412b078924b3bb3511bac855c05c9e5e9e44df3d61e9602207451cd8e18d6ed1885dd827714847f96ec4bb0ed4c36ce9808db8f714204f6d1", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 270, + "comment" : "special case hash", + "msg" : "3131303230383333373736", + "sig" : "304502202f7a5e9e5771d424f30f67fdab61e8ce4f8cd1214882adb65f7de94c31577052022100ac4e69808345809b44acb0b2bd889175fb75dd050c5a449ab9528f8f78daa10c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 271, + "comment" : "special case hash", + "msg" : "313333383731363438", + "sig" : "3045022100ffcda40f792ce4d93e7e0f0e95e1a2147dddd7f6487621c30a03d710b3300219022079938b55f8a17f7ed7ba9ade8f2065a1fa77618f0b67add8d58c422c2453a49a", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 272, + "comment" : "special case hash", + "msg" : "333232313434313632", + "sig" : "304602210081f2359c4faba6b53d3e8c8c3fcc16a948350f7ab3a588b28c17603a431e39a8022100cd6f6a5cc3b55ead0ff695d06c6860b509e46d99fccefb9f7f9e101857f74300", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 273, + "comment" : "special case hash", + "msg" : "3130363836363535353436", + "sig" : "3045022100dfc8bf520445cbb8ee1596fb073ea283ea130251a6fdffa5c3f5f2aaf75ca8080220048e33efce147c9dd92823640e338e68bfd7d0dc7a4905b3a7ac711e577e90e7", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 274, + "comment" : "special case hash", + "msg" : "3632313535323436", + "sig" : "3046022100ad019f74c6941d20efda70b46c53db166503a0e393e932f688227688ba6a576202210093320eb7ca0710255346bdbb3102cdcf7964ef2e0988e712bc05efe16c199345", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 275, + "comment" : "special case hash", + "msg" : "37303330383138373734", + "sig" : "3046022100ac8096842e8add68c34e78ce11dd71e4b54316bd3ebf7fffdeb7bd5a3ebc1883022100f5ca2f4f23d674502d4caf85d187215d36e3ce9f0ce219709f21a3aac003b7a8", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 276, + "comment" : "special case hash", + "msg" : "35393234353233373434", + "sig" : "30440220677b2d3a59b18a5ff939b70ea002250889ddcd7b7b9d776854b4943693fb92f702206b4ba856ade7677bf30307b21f3ccda35d2f63aee81efd0bab6972cc0795db55", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 277, + "comment" : "special case hash", + "msg" : "31343935353836363231", + "sig" : "30450220479e1ded14bcaed0379ba8e1b73d3115d84d31d4b7c30e1f05e1fc0d5957cfb0022100918f79e35b3d89487cf634a4f05b2e0c30857ca879f97c771e877027355b2443", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 278, + "comment" : "special case hash", + "msg" : "34303035333134343036", + "sig" : "3044022043dfccd0edb9e280d9a58f01164d55c3d711e14b12ac5cf3b64840ead512a0a302201dbe33fa8ba84533cd5c4934365b3442ca1174899b78ef9a3199f49584389772", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 279, + "comment" : "special case hash", + "msg" : "33303936343537353132", + "sig" : "304402205b09ab637bd4caf0f4c7c7e4bca592fea20e9087c259d26a38bb4085f0bbff11022045b7eb467b6748af618e9d80d6fdcd6aa24964e5a13f885bca8101de08eb0d75", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 280, + "comment" : "special case hash", + "msg" : "32373834303235363230", + "sig" : "304502205e9b1c5a028070df5728c5c8af9b74e0667afa570a6cfa0114a5039ed15ee06f022100b1360907e2d9785ead362bb8d7bd661b6c29eeffd3c5037744edaeb9ad990c20", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 281, + "comment" : "special case hash", + "msg" : "32363138373837343138", + "sig" : "304502200671a0a85c2b72d54a2fb0990e34538b4890050f5a5712f6d1a7a5fb8578f32e022100db1846bab6b7361479ab9c3285ca41291808f27fd5bd4fdac720e5854713694c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 282, + "comment" : "special case hash", + "msg" : "31363432363235323632", + "sig" : "304402207673f8526748446477dbbb0590a45492c5d7d69859d301abbaedb35b2095103a02203dc70ddf9c6b524d886bed9e6af02e0e4dec0d417a414fed3807ef4422913d7c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 283, + "comment" : "special case hash", + "msg" : "36383234313839343336", + "sig" : "304402207f085441070ecd2bb21285089ebb1aa6450d1a06c36d3ff39dfd657a796d12b50220249712012029870a2459d18d47da9aa492a5e6cb4b2d8dafa9e4c5c54a2b9a8b", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 284, + "comment" : "special case hash", + "msg" : "343834323435343235", + "sig" : "3046022100914c67fb61dd1e27c867398ea7322d5ab76df04bc5aa6683a8e0f30a5d287348022100fa07474031481dda4953e3ac1959ee8cea7e66ec412b38d6c96d28f6d37304ea", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "040ad99500288d466940031d72a9f5445a4d43784640855bf0a69874d2de5fe103c5011e6ef2c42dcd50d5d3d29f99ae6eba2c80c9244f4c5422f0979ff0c3ba5e", + "wx" : "0ad99500288d466940031d72a9f5445a4d43784640855bf0a69874d2de5fe103", + "wy" : "00c5011e6ef2c42dcd50d5d3d29f99ae6eba2c80c9244f4c5422f0979ff0c3ba5e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200040ad99500288d466940031d72a9f5445a4d43784640855bf0a69874d2de5fe103c5011e6ef2c42dcd50d5d3d29f99ae6eba2c80c9244f4c5422f0979ff0c3ba5e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECtmVACiNRmlAAx1yqfVEWk1DeEZA\nhVvwpph00t5f4QPFAR5u8sQtzVDV09Kfma5uuiyAySRPTFQi8Jef8MO6Xg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 285, + "comment" : "k*G has a large x-coordinate", + "msg" : "313233343030", + "sig" : "303502104319055358e8617b0c46353d039cdaab022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 286, + "comment" : "r too large", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000001000000000000000000000000fffffffffffffffffffffffc022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04ab05fd9d0de26b9ce6f4819652d9fc69193d0aa398f0fba8013e09c58220455419235271228c786759095d12b75af0692dd4103f19f6a8c32f49435a1e9b8d45", + "wx" : "00ab05fd9d0de26b9ce6f4819652d9fc69193d0aa398f0fba8013e09c582204554", + "wy" : "19235271228c786759095d12b75af0692dd4103f19f6a8c32f49435a1e9b8d45" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004ab05fd9d0de26b9ce6f4819652d9fc69193d0aa398f0fba8013e09c58220455419235271228c786759095d12b75af0692dd4103f19f6a8c32f49435a1e9b8d45", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqwX9nQ3ia5zm9IGWUtn8aRk9CqOY\n8PuoAT4JxYIgRVQZI1JxIox4Z1kJXRK3WvBpLdQQPxn2qMMvSUNaHpuNRQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 287, + "comment" : "r,s are large", + "msg" : "313233343030", + "sig" : "3046022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0480984f39a1ff38a86a68aa4201b6be5dfbfecf876219710b07badf6fdd4c6c5611feb97390d9826e7a06dfb41871c940d74415ed3cac2089f1445019bb55ed95", + "wx" : "0080984f39a1ff38a86a68aa4201b6be5dfbfecf876219710b07badf6fdd4c6c56", + "wy" : "11feb97390d9826e7a06dfb41871c940d74415ed3cac2089f1445019bb55ed95" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000480984f39a1ff38a86a68aa4201b6be5dfbfecf876219710b07badf6fdd4c6c5611feb97390d9826e7a06dfb41871c940d74415ed3cac2089f1445019bb55ed95", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgJhPOaH/OKhqaKpCAba+Xfv+z4di\nGXELB7rfb91MbFYR/rlzkNmCbnoG37QYcclA10QV7TysIInxRFAZu1XtlQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 288, + "comment" : "r and s^-1 have a large Hamming weight", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100909135bdb6799286170f5ead2de4f6511453fe50914f3df2de54a36383df8dd4", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044201b4272944201c3294f5baa9a3232b6dd687495fcc19a70a95bc602b4f7c0595c37eba9ee8171c1bb5ac6feaf753bc36f463e3aef16629572c0c0a8fb0800e", + "wx" : "4201b4272944201c3294f5baa9a3232b6dd687495fcc19a70a95bc602b4f7c05", + "wy" : "0095c37eba9ee8171c1bb5ac6feaf753bc36f463e3aef16629572c0c0a8fb0800e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044201b4272944201c3294f5baa9a3232b6dd687495fcc19a70a95bc602b4f7c0595c37eba9ee8171c1bb5ac6feaf753bc36f463e3aef16629572c0c0a8fb0800e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQgG0JylEIBwylPW6qaMjK23Wh0lf\nzBmnCpW8YCtPfAWVw366nugXHBu1rG/q91O8NvRj467xZilXLAwKj7CADg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 289, + "comment" : "r and s^-1 have a large Hamming weight", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022027b4577ca009376f71303fd5dd227dcef5deb773ad5f5a84360644669ca249a5", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04a71af64de5126a4a4e02b7922d66ce9415ce88a4c9d25514d91082c8725ac9575d47723c8fbe580bb369fec9c2665d8e30a435b9932645482e7c9f11e872296b", + "wx" : "00a71af64de5126a4a4e02b7922d66ce9415ce88a4c9d25514d91082c8725ac957", + "wy" : "5d47723c8fbe580bb369fec9c2665d8e30a435b9932645482e7c9f11e872296b" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004a71af64de5126a4a4e02b7922d66ce9415ce88a4c9d25514d91082c8725ac9575d47723c8fbe580bb369fec9c2665d8e30a435b9932645482e7c9f11e872296b", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpxr2TeUSakpOAreSLWbOlBXOiKTJ\n0lUU2RCCyHJayVddR3I8j75YC7Np/snCZl2OMKQ1uZMmRUgufJ8R6HIpaw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 290, + "comment" : "small r and s", + "msg" : "313233343030", + "sig" : "3006020105020101", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046627cec4f0731ea23fc2931f90ebe5b7572f597d20df08fc2b31ee8ef16b15726170ed77d8d0a14fc5c9c3c4c9be7f0d3ee18f709bb275eaf2073e258fe694a5", + "wx" : "6627cec4f0731ea23fc2931f90ebe5b7572f597d20df08fc2b31ee8ef16b1572", + "wy" : "6170ed77d8d0a14fc5c9c3c4c9be7f0d3ee18f709bb275eaf2073e258fe694a5" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046627cec4f0731ea23fc2931f90ebe5b7572f597d20df08fc2b31ee8ef16b15726170ed77d8d0a14fc5c9c3c4c9be7f0d3ee18f709bb275eaf2073e258fe694a5", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZifOxPBzHqI/wpMfkOvlt1cvWX0g\n3wj8KzHujvFrFXJhcO132NChT8XJw8TJvn8NPuGPcJuyderyBz4lj+aUpQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 291, + "comment" : "small r and s", + "msg" : "313233343030", + "sig" : "3006020105020103", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045a7c8825e85691cce1f5e7544c54e73f14afc010cb731343262ca7ec5a77f5bfef6edf62a4497c1bd7b147fb6c3d22af3c39bfce95f30e13a16d3d7b2812f813", + "wx" : "5a7c8825e85691cce1f5e7544c54e73f14afc010cb731343262ca7ec5a77f5bf", + "wy" : "00ef6edf62a4497c1bd7b147fb6c3d22af3c39bfce95f30e13a16d3d7b2812f813" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045a7c8825e85691cce1f5e7544c54e73f14afc010cb731343262ca7ec5a77f5bfef6edf62a4497c1bd7b147fb6c3d22af3c39bfce95f30e13a16d3d7b2812f813", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWnyIJehWkczh9edUTFTnPxSvwBDL\ncxNDJiyn7Fp39b/vbt9ipEl8G9exR/tsPSKvPDm/zpXzDhOhbT17KBL4Ew==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 292, + "comment" : "small r and s", + "msg" : "313233343030", + "sig" : "3006020105020105", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04cbe0c29132cd738364fedd603152990c048e5e2fff996d883fa6caca7978c73770af6a8ce44cb41224b2603606f4c04d188e80bff7cc31ad5189d4ab0d70e8c1", + "wx" : "00cbe0c29132cd738364fedd603152990c048e5e2fff996d883fa6caca7978c737", + "wy" : "70af6a8ce44cb41224b2603606f4c04d188e80bff7cc31ad5189d4ab0d70e8c1" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004cbe0c29132cd738364fedd603152990c048e5e2fff996d883fa6caca7978c73770af6a8ce44cb41224b2603606f4c04d188e80bff7cc31ad5189d4ab0d70e8c1", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy+DCkTLNc4Nk/t1gMVKZDASOXi//\nmW2IP6bKynl4xzdwr2qM5Ey0EiSyYDYG9MBNGI6Av/fMMa1RidSrDXDowQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 293, + "comment" : "small r and s", + "msg" : "313233343030", + "sig" : "3006020105020106", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 294, + "comment" : "r is larger than n", + "msg" : "313233343030", + "sig" : "3026022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632556020106", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044be4178097002f0deab68f0d9a130e0ed33a6795d02a20796db83444b037e13920f13051e0eecdcfce4dacea0f50d1f247caa669f193c1b4075b51ae296d2d56", + "wx" : "4be4178097002f0deab68f0d9a130e0ed33a6795d02a20796db83444b037e139", + "wy" : "20f13051e0eecdcfce4dacea0f50d1f247caa669f193c1b4075b51ae296d2d56" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044be4178097002f0deab68f0d9a130e0ed33a6795d02a20796db83444b037e13920f13051e0eecdcfce4dacea0f50d1f247caa669f193c1b4075b51ae296d2d56", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES+QXgJcALw3qto8NmhMODtM6Z5XQ\nKiB5bbg0RLA34Tkg8TBR4O7Nz85NrOoPUNHyR8qmafGTwbQHW1GuKW0tVg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 295, + "comment" : "s is larger than n", + "msg" : "313233343030", + "sig" : "3026020105022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc75fbd8", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d0f73792203716afd4be4329faa48d269f15313ebbba379d7783c97bf3e890d9971f4a3206605bec21782bf5e275c714417e8f566549e6bc68690d2363c89cc1", + "wx" : "00d0f73792203716afd4be4329faa48d269f15313ebbba379d7783c97bf3e890d9", + "wy" : "00971f4a3206605bec21782bf5e275c714417e8f566549e6bc68690d2363c89cc1" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d0f73792203716afd4be4329faa48d269f15313ebbba379d7783c97bf3e890d9971f4a3206605bec21782bf5e275c714417e8f566549e6bc68690d2363c89cc1", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0Pc3kiA3Fq/UvkMp+qSNJp8VMT67\nujedd4PJe/PokNmXH0oyBmBb7CF4K/XidccUQX6PVmVJ5rxoaQ0jY8icwQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 296, + "comment" : "small r and s^-1", + "msg" : "313233343030", + "sig" : "3027020201000221008f1e3c7862c58b16bb76eddbb76eddbb516af4f63f2d74d76e0d28c9bb75ea88", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044838b2be35a6276a80ef9e228140f9d9b96ce83b7a254f71ccdebbb8054ce05ffa9cbc123c919b19e00238198d04069043bd660a828814051fcb8aac738a6c6b", + "wx" : "4838b2be35a6276a80ef9e228140f9d9b96ce83b7a254f71ccdebbb8054ce05f", + "wy" : "00fa9cbc123c919b19e00238198d04069043bd660a828814051fcb8aac738a6c6b" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044838b2be35a6276a80ef9e228140f9d9b96ce83b7a254f71ccdebbb8054ce05ffa9cbc123c919b19e00238198d04069043bd660a828814051fcb8aac738a6c6b", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESDiyvjWmJ2qA754igUD52bls6Dt6\nJU9xzN67uAVM4F/6nLwSPJGbGeACOBmNBAaQQ71mCoKIFAUfy4qsc4psaw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 297, + "comment" : "smallish r and s^-1", + "msg" : "313233343030", + "sig" : "302c02072d9b4d347952d6022100ef3043e7329581dbb3974497710ab11505ee1c87ff907beebadd195a0ffe6d7a", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "047393983ca30a520bbc4783dc9960746aab444ef520c0a8e771119aa4e74b0f64e9d7be1ab01a0bf626e709863e6a486dbaf32793afccf774e2c6cd27b1857526", + "wx" : "7393983ca30a520bbc4783dc9960746aab444ef520c0a8e771119aa4e74b0f64", + "wy" : "00e9d7be1ab01a0bf626e709863e6a486dbaf32793afccf774e2c6cd27b1857526" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200047393983ca30a520bbc4783dc9960746aab444ef520c0a8e771119aa4e74b0f64e9d7be1ab01a0bf626e709863e6a486dbaf32793afccf774e2c6cd27b1857526", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc5OYPKMKUgu8R4PcmWB0aqtETvUg\nwKjncRGapOdLD2Tp174asBoL9ibnCYY+akhtuvMnk6/M93Tixs0nsYV1Jg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 298, + "comment" : "100-bit r and small s^-1", + "msg" : "313233343030", + "sig" : "3032020d1033e67e37b32b445580bf4eff0221008b748b74000000008b748b748b748b7466e769ad4a16d3dcd87129b8e91d1b4d", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045ac331a1103fe966697379f356a937f350588a05477e308851b8a502d5dfcdc5fe9993df4b57939b2b8da095bf6d794265204cfe03be995a02e65d408c871c0b", + "wx" : "5ac331a1103fe966697379f356a937f350588a05477e308851b8a502d5dfcdc5", + "wy" : "00fe9993df4b57939b2b8da095bf6d794265204cfe03be995a02e65d408c871c0b" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045ac331a1103fe966697379f356a937f350588a05477e308851b8a502d5dfcdc5fe9993df4b57939b2b8da095bf6d794265204cfe03be995a02e65d408c871c0b", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWsMxoRA/6WZpc3nzVqk381BYigVH\nfjCIUbilAtXfzcX+mZPfS1eTmyuNoJW/bXlCZSBM/gO+mVoC5l1AjIccCw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 299, + "comment" : "small r and 100 bit s^-1", + "msg" : "313233343030", + "sig" : "302702020100022100ef9f6ba4d97c09d03178fa20b4aaad83be3cf9cb824a879fec3270fc4b81ef5b", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "041d209be8de2de877095a399d3904c74cc458d926e27bb8e58e5eae5767c41509dd59e04c214f7b18dce351fc2a549893a6860e80163f38cc60a4f2c9d040d8c9", + "wx" : "1d209be8de2de877095a399d3904c74cc458d926e27bb8e58e5eae5767c41509", + "wy" : "00dd59e04c214f7b18dce351fc2a549893a6860e80163f38cc60a4f2c9d040d8c9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200041d209be8de2de877095a399d3904c74cc458d926e27bb8e58e5eae5767c41509dd59e04c214f7b18dce351fc2a549893a6860e80163f38cc60a4f2c9d040d8c9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHSCb6N4t6HcJWjmdOQTHTMRY2Sbi\ne7jljl6uV2fEFQndWeBMIU97GNzjUfwqVJiTpoYOgBY/OMxgpPLJ0EDYyQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 300, + "comment" : "100-bit r and s^-1", + "msg" : "313233343030", + "sig" : "3032020d062522bbd3ecbe7c39e93e7c25022100ef9f6ba4d97c09d03178fa20b4aaad83be3cf9cb824a879fec3270fc4b81ef5b", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04083539fbee44625e3acaafa2fcb41349392cef0633a1b8fabecee0c133b10e99915c1ebe7bf00df8535196770a58047ae2a402f26326bb7d41d4d7616337911e", + "wx" : "083539fbee44625e3acaafa2fcb41349392cef0633a1b8fabecee0c133b10e99", + "wy" : "00915c1ebe7bf00df8535196770a58047ae2a402f26326bb7d41d4d7616337911e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004083539fbee44625e3acaafa2fcb41349392cef0633a1b8fabecee0c133b10e99915c1ebe7bf00df8535196770a58047ae2a402f26326bb7d41d4d7616337911e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECDU5++5EYl46yq+i/LQTSTks7wYz\nobj6vs7gwTOxDpmRXB6+e/AN+FNRlncKWAR64qQC8mMmu31B1NdhYzeRHg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 301, + "comment" : "r and s^-1 are close to n", + "msg" : "313233343030", + "sig" : "3045022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6324d50220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "048aeb368a7027a4d64abdea37390c0c1d6a26f399e2d9734de1eb3d0e1937387405bd13834715e1dbae9b875cf07bd55e1b6691c7f7536aef3b19bf7a4adf576d", + "wx" : "008aeb368a7027a4d64abdea37390c0c1d6a26f399e2d9734de1eb3d0e19373874", + "wy" : "05bd13834715e1dbae9b875cf07bd55e1b6691c7f7536aef3b19bf7a4adf576d" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200048aeb368a7027a4d64abdea37390c0c1d6a26f399e2d9734de1eb3d0e1937387405bd13834715e1dbae9b875cf07bd55e1b6691c7f7536aef3b19bf7a4adf576d", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEius2inAnpNZKveo3OQwMHWom85ni\n2XNN4es9Dhk3OHQFvRODRxXh266bh1zwe9VeG2aRx/dTau87Gb96St9XbQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 302, + "comment" : "s == 1", + "msg" : "313233343030", + "sig" : "30250220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70020101", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 303, + "comment" : "s == 0", + "msg" : "313233343030", + "sig" : "30250220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70020100", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04b533d4695dd5b8c5e07757e55e6e516f7e2c88fa0239e23f60e8ec07dd70f2871b134ee58cc583278456863f33c3a85d881f7d4a39850143e29d4eaf009afe47", + "wx" : "00b533d4695dd5b8c5e07757e55e6e516f7e2c88fa0239e23f60e8ec07dd70f287", + "wy" : "1b134ee58cc583278456863f33c3a85d881f7d4a39850143e29d4eaf009afe47" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004b533d4695dd5b8c5e07757e55e6e516f7e2c88fa0239e23f60e8ec07dd70f2871b134ee58cc583278456863f33c3a85d881f7d4a39850143e29d4eaf009afe47", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtTPUaV3VuMXgd1flXm5Rb34siPoC\nOeI/YOjsB91w8ocbE07ljMWDJ4RWhj8zw6hdiB99SjmFAUPinU6vAJr+Rw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 304, + "comment" : "point at infinity during verify", + "msg" : "313233343030", + "sig" : "304402207fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a80220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04f50d371b91bfb1d7d14e1323523bc3aa8cbf2c57f9e284de628c8b4536787b86f94ad887ac94d527247cd2e7d0c8b1291c553c9730405380b14cbb209f5fa2dd", + "wx" : "00f50d371b91bfb1d7d14e1323523bc3aa8cbf2c57f9e284de628c8b4536787b86", + "wy" : "00f94ad887ac94d527247cd2e7d0c8b1291c553c9730405380b14cbb209f5fa2dd" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004f50d371b91bfb1d7d14e1323523bc3aa8cbf2c57f9e284de628c8b4536787b86f94ad887ac94d527247cd2e7d0c8b1291c553c9730405380b14cbb209f5fa2dd", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9Q03G5G/sdfRThMjUjvDqoy/LFf5\n4oTeYoyLRTZ4e4b5StiHrJTVJyR80ufQyLEpHFU8lzBAU4CxTLsgn1+i3Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 305, + "comment" : "edge case for signature malleability", + "msg" : "313233343030", + "sig" : "304402207fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a902207fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0468ec6e298eafe16539156ce57a14b04a7047c221bafc3a582eaeb0d857c4d94697bed1af17850117fdb39b2324f220a5698ed16c426a27335bb385ac8ca6fb30", + "wx" : "68ec6e298eafe16539156ce57a14b04a7047c221bafc3a582eaeb0d857c4d946", + "wy" : "0097bed1af17850117fdb39b2324f220a5698ed16c426a27335bb385ac8ca6fb30" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000468ec6e298eafe16539156ce57a14b04a7047c221bafc3a582eaeb0d857c4d94697bed1af17850117fdb39b2324f220a5698ed16c426a27335bb385ac8ca6fb30", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaOxuKY6v4WU5FWzlehSwSnBHwiG6\n/DpYLq6w2FfE2UaXvtGvF4UBF/2zmyMk8iClaY7RbEJqJzNbs4WsjKb7MA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 306, + "comment" : "edge case for signature malleability", + "msg" : "313233343030", + "sig" : "304402207fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a902207fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a9", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0469da0364734d2e530fece94019265fefb781a0f1b08f6c8897bdf6557927c8b866d2d3c7dcd518b23d726960f069ad71a933d86ef8abbcce8b20f71e2a847002", + "wx" : "69da0364734d2e530fece94019265fefb781a0f1b08f6c8897bdf6557927c8b8", + "wy" : "66d2d3c7dcd518b23d726960f069ad71a933d86ef8abbcce8b20f71e2a847002" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000469da0364734d2e530fece94019265fefb781a0f1b08f6c8897bdf6557927c8b866d2d3c7dcd518b23d726960f069ad71a933d86ef8abbcce8b20f71e2a847002", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEadoDZHNNLlMP7OlAGSZf77eBoPGw\nj2yIl732VXknyLhm0tPH3NUYsj1yaWDwaa1xqTPYbvirvM6LIPceKoRwAg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 307, + "comment" : "u1 == 1", + "msg" : "313233343030", + "sig" : "30450220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70022100bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d8adc00023a8edc02576e2b63e3e30621a471e2b2320620187bf067a1ac1ff3233e2b50ec09807accb36131fff95ed12a09a86b4ea9690aa32861576ba2362e1", + "wx" : "00d8adc00023a8edc02576e2b63e3e30621a471e2b2320620187bf067a1ac1ff32", + "wy" : "33e2b50ec09807accb36131fff95ed12a09a86b4ea9690aa32861576ba2362e1" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d8adc00023a8edc02576e2b63e3e30621a471e2b2320620187bf067a1ac1ff3233e2b50ec09807accb36131fff95ed12a09a86b4ea9690aa32861576ba2362e1", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2K3AACOo7cAlduK2Pj4wYhpHHisj\nIGIBh78GehrB/zIz4rUOwJgHrMs2Ex//le0SoJqGtOqWkKoyhhV2uiNi4Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 308, + "comment" : "u1 == n - 1", + "msg" : "313233343030", + "sig" : "30440220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70022044a5ad0ad0636d9f12bc9e0a6bdd5e1cbcb012ea7bf091fcec15b0c43202d52e", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043623ac973ced0a56fa6d882f03a7d5c7edca02cfc7b2401fab3690dbe75ab7858db06908e64b28613da7257e737f39793da8e713ba0643b92e9bb3252be7f8fe", + "wx" : "3623ac973ced0a56fa6d882f03a7d5c7edca02cfc7b2401fab3690dbe75ab785", + "wy" : "008db06908e64b28613da7257e737f39793da8e713ba0643b92e9bb3252be7f8fe" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043623ac973ced0a56fa6d882f03a7d5c7edca02cfc7b2401fab3690dbe75ab7858db06908e64b28613da7257e737f39793da8e713ba0643b92e9bb3252be7f8fe", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENiOslzztClb6bYgvA6fVx+3KAs/H\nskAfqzaQ2+dat4WNsGkI5ksoYT2nJX5zfzl5PajnE7oGQ7kum7MlK+f4/g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 309, + "comment" : "u2 == 1", + "msg" : "313233343030", + "sig" : "30440220555555550000000055555555555555553ef7a8e48d07df81a693439654210c700220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04cf04ea77e9622523d894b93ff52dc3027b31959503b6fa3890e5e04263f922f1e8528fb7c006b3983c8b8400e57b4ed71740c2f3975438821199bedeaecab2e9", + "wx" : "00cf04ea77e9622523d894b93ff52dc3027b31959503b6fa3890e5e04263f922f1", + "wy" : "00e8528fb7c006b3983c8b8400e57b4ed71740c2f3975438821199bedeaecab2e9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004cf04ea77e9622523d894b93ff52dc3027b31959503b6fa3890e5e04263f922f1e8528fb7c006b3983c8b8400e57b4ed71740c2f3975438821199bedeaecab2e9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzwTqd+liJSPYlLk/9S3DAnsxlZUD\ntvo4kOXgQmP5IvHoUo+3wAazmDyLhADle07XF0DC85dUOIIRmb7ersqy6Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 310, + "comment" : "u2 == n - 1", + "msg" : "313233343030", + "sig" : "30450220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70022100aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04db7a2c8a1ab573e5929dc24077b508d7e683d49227996bda3e9f78dbeff773504f417f3bc9a88075c2e0aadd5a13311730cf7cc76a82f11a36eaf08a6c99a206", + "wx" : "00db7a2c8a1ab573e5929dc24077b508d7e683d49227996bda3e9f78dbeff77350", + "wy" : "4f417f3bc9a88075c2e0aadd5a13311730cf7cc76a82f11a36eaf08a6c99a206" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004db7a2c8a1ab573e5929dc24077b508d7e683d49227996bda3e9f78dbeff773504f417f3bc9a88075c2e0aadd5a13311730cf7cc76a82f11a36eaf08a6c99a206", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE23osihq1c+WSncJAd7UI1+aD1JIn\nmWvaPp942+/3c1BPQX87yaiAdcLgqt1aEzEXMM98x2qC8Ro26vCKbJmiBg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 311, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100e91e1ba60fdedb76a46bcb51dc0b8b4b7e019f0a28721885fa5d3a8196623397", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04dead11c7a5b396862f21974dc4752fadeff994efe9bbd05ab413765ea80b6e1f1de3f0640e8ac6edcf89cff53c40e265bb94078a343736df07aa0318fc7fe1ff", + "wx" : "00dead11c7a5b396862f21974dc4752fadeff994efe9bbd05ab413765ea80b6e1f", + "wy" : "1de3f0640e8ac6edcf89cff53c40e265bb94078a343736df07aa0318fc7fe1ff" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004dead11c7a5b396862f21974dc4752fadeff994efe9bbd05ab413765ea80b6e1f1de3f0640e8ac6edcf89cff53c40e265bb94078a343736df07aa0318fc7fe1ff", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3q0Rx6WzloYvIZdNxHUvre/5lO/p\nu9BatBN2XqgLbh8d4/BkDorG7c+Jz/U8QOJlu5QHijQ3Nt8HqgMY/H/h/w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 312, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100fdea5843ffeb73af94313ba4831b53fe24f799e525b1e8e8c87b59b95b430ad9", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d0bc472e0d7c81ebaed3a6ef96c18613bb1fea6f994326fbe80e00dfde67c7e9986c723ea4843d48389b946f64ad56c83ad70ff17ba85335667d1bb9fa619efd", + "wx" : "00d0bc472e0d7c81ebaed3a6ef96c18613bb1fea6f994326fbe80e00dfde67c7e9", + "wy" : "00986c723ea4843d48389b946f64ad56c83ad70ff17ba85335667d1bb9fa619efd" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d0bc472e0d7c81ebaed3a6ef96c18613bb1fea6f994326fbe80e00dfde67c7e9986c723ea4843d48389b946f64ad56c83ad70ff17ba85335667d1bb9fa619efd", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0LxHLg18geuu06bvlsGGE7sf6m+Z\nQyb76A4A395nx+mYbHI+pIQ9SDiblG9krVbIOtcP8XuoUzVmfRu5+mGe/Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 313, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022003ffcabf2f1b4d2a65190db1680d62bb994e41c5251cd73b3c3dfc5e5bafc035", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04a0a44ca947d66a2acb736008b9c08d1ab2ad03776e02640f78495d458dd51c326337fe5cf8c4604b1f1c409dc2d872d4294a4762420df43a30a2392e40426add", + "wx" : "00a0a44ca947d66a2acb736008b9c08d1ab2ad03776e02640f78495d458dd51c32", + "wy" : "6337fe5cf8c4604b1f1c409dc2d872d4294a4762420df43a30a2392e40426add" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004a0a44ca947d66a2acb736008b9c08d1ab2ad03776e02640f78495d458dd51c326337fe5cf8c4604b1f1c409dc2d872d4294a4762420df43a30a2392e40426add", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoKRMqUfWairLc2AIucCNGrKtA3du\nAmQPeEldRY3VHDJjN/5c+MRgSx8cQJ3C2HLUKUpHYkIN9DowojkuQEJq3Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 314, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd02204dfbc401f971cd304b33dfdb17d0fed0fe4c1a88ae648e0d2847f74977534989", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04c9c2115290d008b45fb65fad0f602389298c25420b775019d42b62c3ce8a96b73877d25a8080dc02d987ca730f0405c2c9dbefac46f9e601cc3f06e9713973fd", + "wx" : "00c9c2115290d008b45fb65fad0f602389298c25420b775019d42b62c3ce8a96b7", + "wy" : "3877d25a8080dc02d987ca730f0405c2c9dbefac46f9e601cc3f06e9713973fd" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004c9c2115290d008b45fb65fad0f602389298c25420b775019d42b62c3ce8a96b73877d25a8080dc02d987ca730f0405c2c9dbefac46f9e601cc3f06e9713973fd", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEycIRUpDQCLRftl+tD2AjiSmMJUIL\nd1AZ1Ctiw86Klrc4d9JagIDcAtmHynMPBAXCydvvrEb55gHMPwbpcTlz/Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 315, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100bc4024761cd2ffd43dfdb17d0fed112b988977055cd3a8e54971eba9cda5ca71", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045eca1ef4c287dddc66b8bccf1b88e8a24c0018962f3c5e7efa83bc1a5ff6033e5e79c4cb2c245b8c45abdce8a8e4da758d92a607c32cd407ecaef22f1c934a71", + "wx" : "5eca1ef4c287dddc66b8bccf1b88e8a24c0018962f3c5e7efa83bc1a5ff6033e", + "wy" : "5e79c4cb2c245b8c45abdce8a8e4da758d92a607c32cd407ecaef22f1c934a71" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045eca1ef4c287dddc66b8bccf1b88e8a24c0018962f3c5e7efa83bc1a5ff6033e5e79c4cb2c245b8c45abdce8a8e4da758d92a607c32cd407ecaef22f1c934a71", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXsoe9MKH3dxmuLzPG4jookwAGJYv\nPF5++oO8Gl/2Az5eecTLLCRbjEWr3Oio5Np1jZKmB8Ms1AfsrvIvHJNKcQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 316, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd0220788048ed39a5ffa77bfb62fa1fda2257742bf35d128fb3459f2a0c909ee86f91", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045caaa030e7fdf0e4936bc7ab5a96353e0a01e4130c3f8bf22d473e317029a47adeb6adc462f7058f2a20d371e9702254e9b201642005b3ceda926b42b178bef9", + "wx" : "5caaa030e7fdf0e4936bc7ab5a96353e0a01e4130c3f8bf22d473e317029a47a", + "wy" : "00deb6adc462f7058f2a20d371e9702254e9b201642005b3ceda926b42b178bef9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045caaa030e7fdf0e4936bc7ab5a96353e0a01e4130c3f8bf22d473e317029a47adeb6adc462f7058f2a20d371e9702254e9b201642005b3ceda926b42b178bef9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXKqgMOf98OSTa8erWpY1PgoB5BMM\nP4vyLUc+MXAppHretq3EYvcFjyog03HpcCJU6bIBZCAFs87akmtCsXi++Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 317, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd0220476d9131fd381bd917d0fed112bc9e0a5924b5ed5b11167edd8b23582b3cb15e", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04c2fd20bac06e555bb8ac0ce69eb1ea20f83a1fc3501c8a66469b1a31f619b0986237050779f52b615bd7b8d76a25fc95ca2ed32525c75f27ffc87ac397e6cbaf", + "wx" : "00c2fd20bac06e555bb8ac0ce69eb1ea20f83a1fc3501c8a66469b1a31f619b098", + "wy" : "6237050779f52b615bd7b8d76a25fc95ca2ed32525c75f27ffc87ac397e6cbaf" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004c2fd20bac06e555bb8ac0ce69eb1ea20f83a1fc3501c8a66469b1a31f619b0986237050779f52b615bd7b8d76a25fc95ca2ed32525c75f27ffc87ac397e6cbaf", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwv0gusBuVVu4rAzmnrHqIPg6H8NQ\nHIpmRpsaMfYZsJhiNwUHefUrYVvXuNdqJfyVyi7TJSXHXyf/yHrDl+bLrw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 318, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd0221008374253e3e21bd154448d0a8f640fe46fafa8b19ce78d538f6cc0a19662d3601", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043fd6a1ca7f77fb3b0bbe726c372010068426e11ea6ae78ce17bedae4bba86ced03ce5516406bf8cfaab8745eac1cd69018ad6f50b5461872ddfc56e0db3c8ff4", + "wx" : "3fd6a1ca7f77fb3b0bbe726c372010068426e11ea6ae78ce17bedae4bba86ced", + "wy" : "03ce5516406bf8cfaab8745eac1cd69018ad6f50b5461872ddfc56e0db3c8ff4" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043fd6a1ca7f77fb3b0bbe726c372010068426e11ea6ae78ce17bedae4bba86ced03ce5516406bf8cfaab8745eac1cd69018ad6f50b5461872ddfc56e0db3c8ff4", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEP9ahyn93+zsLvnJsNyAQBoQm4R6m\nrnjOF77a5LuobO0DzlUWQGv4z6q4dF6sHNaQGK1vULVGGHLd/Fbg2zyP9A==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 319, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd0220357cfd3be4d01d413c5b9ede36cba5452c11ee7fe14879e749ae6a2d897a52d6", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "049cb8e51e27a5ae3b624a60d6dc32734e4989db20e9bca3ede1edf7b086911114b4c104ab3c677e4b36d6556e8ad5f523410a19f2e277aa895fc57322b4427544", + "wx" : "009cb8e51e27a5ae3b624a60d6dc32734e4989db20e9bca3ede1edf7b086911114", + "wy" : "00b4c104ab3c677e4b36d6556e8ad5f523410a19f2e277aa895fc57322b4427544" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200049cb8e51e27a5ae3b624a60d6dc32734e4989db20e9bca3ede1edf7b086911114b4c104ab3c677e4b36d6556e8ad5f523410a19f2e277aa895fc57322b4427544", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnLjlHielrjtiSmDW3DJzTkmJ2yDp\nvKPt4e33sIaRERS0wQSrPGd+SzbWVW6K1fUjQQoZ8uJ3qolfxXMitEJ1RA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 320, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022029798c5c0ee287d4a5e8e6b799fd86b8df5225298e6ffc807cd2f2bc27a0a6d8", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04a3e52c156dcaf10502620b7955bc2b40bc78ef3d569e1223c262512d8f49602a4a2039f31c1097024ad3cc86e57321de032355463486164cf192944977df147f", + "wx" : "00a3e52c156dcaf10502620b7955bc2b40bc78ef3d569e1223c262512d8f49602a", + "wy" : "4a2039f31c1097024ad3cc86e57321de032355463486164cf192944977df147f" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004a3e52c156dcaf10502620b7955bc2b40bc78ef3d569e1223c262512d8f49602a4a2039f31c1097024ad3cc86e57321de032355463486164cf192944977df147f", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEo+UsFW3K8QUCYgt5VbwrQLx47z1W\nnhIjwmJRLY9JYCpKIDnzHBCXAkrTzIblcyHeAyNVRjSGFkzxkpRJd98Ufw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 321, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd02200b70f22c781092452dca1a5711fa3a5a1f72add1bf52c2ff7cae4820b30078dd", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04f19b78928720d5bee8e670fb90010fb15c37bf91b58a5157c3f3c059b2655e88cf701ec962fb4a11dcf273f5dc357e58468560c7cfeb942d074abd4329260509", + "wx" : "00f19b78928720d5bee8e670fb90010fb15c37bf91b58a5157c3f3c059b2655e88", + "wy" : "00cf701ec962fb4a11dcf273f5dc357e58468560c7cfeb942d074abd4329260509" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004f19b78928720d5bee8e670fb90010fb15c37bf91b58a5157c3f3c059b2655e88cf701ec962fb4a11dcf273f5dc357e58468560c7cfeb942d074abd4329260509", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8Zt4kocg1b7o5nD7kAEPsVw3v5G1\nilFXw/PAWbJlXojPcB7JYvtKEdzyc/XcNX5YRoVgx8/rlC0HSr1DKSYFCQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 322, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022016e1e458f021248a5b9434ae23f474b43ee55ba37ea585fef95c90416600f1ba", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0483a744459ecdfb01a5cf52b27a05bb7337482d242f235d7b4cb89345545c90a8c05d49337b9649813287de9ffe90355fd905df5f3c32945828121f37cc50de6e", + "wx" : "0083a744459ecdfb01a5cf52b27a05bb7337482d242f235d7b4cb89345545c90a8", + "wy" : "00c05d49337b9649813287de9ffe90355fd905df5f3c32945828121f37cc50de6e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000483a744459ecdfb01a5cf52b27a05bb7337482d242f235d7b4cb89345545c90a8c05d49337b9649813287de9ffe90355fd905df5f3c32945828121f37cc50de6e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEg6dERZ7N+wGlz1KyegW7czdILSQv\nI117TLiTRVRckKjAXUkze5ZJgTKH3p/+kDVf2QXfXzwylFgoEh83zFDebg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 323, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd02202252d6856831b6cf895e4f0535eeaf0e5e5809753df848fe760ad86219016a97", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04dd13c6b34c56982ddae124f039dfd23f4b19bbe88cee8e528ae51e5d6f3a21d7bfad4c2e6f263fe5eb59ca974d039fc0e4c3345692fb5320bdae4bd3b42a45ff", + "wx" : "00dd13c6b34c56982ddae124f039dfd23f4b19bbe88cee8e528ae51e5d6f3a21d7", + "wy" : "00bfad4c2e6f263fe5eb59ca974d039fc0e4c3345692fb5320bdae4bd3b42a45ff" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004dd13c6b34c56982ddae124f039dfd23f4b19bbe88cee8e528ae51e5d6f3a21d7bfad4c2e6f263fe5eb59ca974d039fc0e4c3345692fb5320bdae4bd3b42a45ff", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3RPGs0xWmC3a4STwOd/SP0sZu+iM\n7o5SiuUeXW86Ide/rUwubyY/5etZypdNA5/A5MM0VpL7UyC9rkvTtCpF/w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 324, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd02210081ffe55f178da695b28c86d8b406b15dab1a9e39661a3ae017fbe390ac0972c3", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0467e6f659cdde869a2f65f094e94e5b4dfad636bbf95192feeed01b0f3deb7460a37e0a51f258b7aeb51dfe592f5cfd5685bbe58712c8d9233c62886437c38ba0", + "wx" : "67e6f659cdde869a2f65f094e94e5b4dfad636bbf95192feeed01b0f3deb7460", + "wy" : "00a37e0a51f258b7aeb51dfe592f5cfd5685bbe58712c8d9233c62886437c38ba0" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000467e6f659cdde869a2f65f094e94e5b4dfad636bbf95192feeed01b0f3deb7460a37e0a51f258b7aeb51dfe592f5cfd5685bbe58712c8d9233c62886437c38ba0", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ+b2Wc3ehpovZfCU6U5bTfrWNrv5\nUZL+7tAbDz3rdGCjfgpR8li3rrUd/lkvXP1WhbvlhxLI2SM8YohkN8OLoA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 325, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd02207fffffffaaaaaaaaffffffffffffffffe9a2538f37b28a2c513dee40fecbb71a", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "042eb6412505aec05c6545f029932087e490d05511e8ec1f599617bb367f9ecaaf805f51efcc4803403f9b1ae0124890f06a43fedcddb31830f6669af292895cb0", + "wx" : "2eb6412505aec05c6545f029932087e490d05511e8ec1f599617bb367f9ecaaf", + "wy" : "00805f51efcc4803403f9b1ae0124890f06a43fedcddb31830f6669af292895cb0" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200042eb6412505aec05c6545f029932087e490d05511e8ec1f599617bb367f9ecaaf805f51efcc4803403f9b1ae0124890f06a43fedcddb31830f6669af292895cb0", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrZBJQWuwFxlRfApkyCH5JDQVRHo\n7B9Zlhe7Nn+eyq+AX1HvzEgDQD+bGuASSJDwakP+3N2zGDD2ZprykolcsA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 326, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100b62f26b5f2a2b26f6de86d42ad8a13da3ab3cccd0459b201de009e526adf21f2", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0484db645868eab35e3a9fd80e056e2e855435e3a6b68d75a50a854625fe0d7f356d2589ac655edc9a11ef3e075eddda9abf92e72171570ef7bf43a2ee39338cfe", + "wx" : "0084db645868eab35e3a9fd80e056e2e855435e3a6b68d75a50a854625fe0d7f35", + "wy" : "6d2589ac655edc9a11ef3e075eddda9abf92e72171570ef7bf43a2ee39338cfe" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000484db645868eab35e3a9fd80e056e2e855435e3a6b68d75a50a854625fe0d7f356d2589ac655edc9a11ef3e075eddda9abf92e72171570ef7bf43a2ee39338cfe", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhNtkWGjqs146n9gOBW4uhVQ146a2\njXWlCoVGJf4NfzVtJYmsZV7cmhHvPgde3dqav5LnIXFXDve/Q6LuOTOM/g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 327, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100bb1d9ac949dd748cd02bbbe749bd351cd57b38bb61403d700686aa7b4c90851e", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0491b9e47c56278662d75c0983b22ca8ea6aa5059b7a2ff7637eb2975e386ad66349aa8ff283d0f77c18d6d11dc062165fd13c3c0310679c1408302a16854ecfbd", + "wx" : "0091b9e47c56278662d75c0983b22ca8ea6aa5059b7a2ff7637eb2975e386ad663", + "wy" : "49aa8ff283d0f77c18d6d11dc062165fd13c3c0310679c1408302a16854ecfbd" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000491b9e47c56278662d75c0983b22ca8ea6aa5059b7a2ff7637eb2975e386ad66349aa8ff283d0f77c18d6d11dc062165fd13c3c0310679c1408302a16854ecfbd", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkbnkfFYnhmLXXAmDsiyo6mqlBZt6\nL/djfrKXXjhq1mNJqo/yg9D3fBjW0R3AYhZf0Tw8AxBnnBQIMCoWhU7PvQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 328, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022066755a00638cdaec1c732513ca0234ece52545dac11f816e818f725b4f60aaf2", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04f3ec2f13caf04d0192b47fb4c5311fb6d4dc6b0a9e802e5327f7ec5ee8e4834df97e3e468b7d0db867d6ecfe81e2b0f9531df87efdb47c1338ac321fefe5a432", + "wx" : "00f3ec2f13caf04d0192b47fb4c5311fb6d4dc6b0a9e802e5327f7ec5ee8e4834d", + "wy" : "00f97e3e468b7d0db867d6ecfe81e2b0f9531df87efdb47c1338ac321fefe5a432" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004f3ec2f13caf04d0192b47fb4c5311fb6d4dc6b0a9e802e5327f7ec5ee8e4834df97e3e468b7d0db867d6ecfe81e2b0f9531df87efdb47c1338ac321fefe5a432", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8+wvE8rwTQGStH+0xTEfttTcawqe\ngC5TJ/fsXujkg035fj5Gi30NuGfW7P6B4rD5Ux34fv20fBM4rDIf7+WkMg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 329, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022055a00c9fcdaebb6032513ca0234ecfffe98ebe492fdf02e48ca48e982beb3669", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d92b200aefcab6ac7dafd9acaf2fa10b3180235b8f46b4503e4693c670fccc885ef2f3aebf5b317475336256768f7c19efb7352d27e4cccadc85b6b8ab922c72", + "wx" : "00d92b200aefcab6ac7dafd9acaf2fa10b3180235b8f46b4503e4693c670fccc88", + "wy" : "5ef2f3aebf5b317475336256768f7c19efb7352d27e4cccadc85b6b8ab922c72" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d92b200aefcab6ac7dafd9acaf2fa10b3180235b8f46b4503e4693c670fccc885ef2f3aebf5b317475336256768f7c19efb7352d27e4cccadc85b6b8ab922c72", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2SsgCu/Ktqx9r9msry+hCzGAI1uP\nRrRQPkaTxnD8zIhe8vOuv1sxdHUzYlZ2j3wZ77c1LSfkzMrchba4q5Iscg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 330, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100ab40193f9b5d76c064a27940469d9fffd31d7c925fbe05c919491d3057d66cd2", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "040a88361eb92ecca2625b38e5f98bbabb96bf179b3d76fc48140a3bcd881523cde6bdf56033f84a5054035597375d90866aa2c96b86a41ccf6edebf47298ad489", + "wx" : "0a88361eb92ecca2625b38e5f98bbabb96bf179b3d76fc48140a3bcd881523cd", + "wy" : "00e6bdf56033f84a5054035597375d90866aa2c96b86a41ccf6edebf47298ad489" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200040a88361eb92ecca2625b38e5f98bbabb96bf179b3d76fc48140a3bcd881523cde6bdf56033f84a5054035597375d90866aa2c96b86a41ccf6edebf47298ad489", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECog2HrkuzKJiWzjl+Yu6u5a/F5s9\ndvxIFAo7zYgVI83mvfVgM/hKUFQDVZc3XZCGaqLJa4akHM9u3r9HKYrUiQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 331, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100ca0234ebb5fdcb13ca0234ecffffffffcb0dadbbc7f549f8a26b4408d0dc8600", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d0fb17ccd8fafe827e0c1afc5d8d80366e2b20e7f14a563a2ba50469d84375e868612569d39e2bb9f554355564646de99ac602cc6349cf8c1e236a7de7637d93", + "wx" : "00d0fb17ccd8fafe827e0c1afc5d8d80366e2b20e7f14a563a2ba50469d84375e8", + "wy" : "68612569d39e2bb9f554355564646de99ac602cc6349cf8c1e236a7de7637d93" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d0fb17ccd8fafe827e0c1afc5d8d80366e2b20e7f14a563a2ba50469d84375e868612569d39e2bb9f554355564646de99ac602cc6349cf8c1e236a7de7637d93", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0PsXzNj6/oJ+DBr8XY2ANm4rIOfx\nSlY6K6UEadhDdehoYSVp054rufVUNVVkZG3pmsYCzGNJz4weI2p952N9kw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 332, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100bfffffff3ea3677e082b9310572620ae19933a9e65b285598711c77298815ad3", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04836f33bbc1dc0d3d3abbcef0d91f11e2ac4181076c9af0a22b1e4309d3edb2769ab443ff6f901e30c773867582997c2bec2b0cb8120d760236f3a95bbe881f75", + "wx" : "00836f33bbc1dc0d3d3abbcef0d91f11e2ac4181076c9af0a22b1e4309d3edb276", + "wy" : "009ab443ff6f901e30c773867582997c2bec2b0cb8120d760236f3a95bbe881f75" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004836f33bbc1dc0d3d3abbcef0d91f11e2ac4181076c9af0a22b1e4309d3edb2769ab443ff6f901e30c773867582997c2bec2b0cb8120d760236f3a95bbe881f75", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEg28zu8HcDT06u87w2R8R4qxBgQds\nmvCiKx5DCdPtsnaatEP/b5AeMMdzhnWCmXwr7CsMuBINdgI286lbvogfdQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 333, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd0220266666663bbbbbbbe6666666666666665b37902e023fab7c8f055d86e5cc41f4", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0492f99fbe973ed4a299719baee4b432741237034dec8d72ba5103cb33e55feeb8033dd0e91134c734174889f3ebcf1b7a1ac05767289280ee7a794cebd6e69697", + "wx" : "0092f99fbe973ed4a299719baee4b432741237034dec8d72ba5103cb33e55feeb8", + "wy" : "033dd0e91134c734174889f3ebcf1b7a1ac05767289280ee7a794cebd6e69697" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000492f99fbe973ed4a299719baee4b432741237034dec8d72ba5103cb33e55feeb8033dd0e91134c734174889f3ebcf1b7a1ac05767289280ee7a794cebd6e69697", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkvmfvpc+1KKZcZuu5LQydBI3A03s\njXK6UQPLM+Vf7rgDPdDpETTHNBdIifPrzxt6GsBXZyiSgO56eUzr1uaWlw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 334, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100bfffffff36db6db7a492492492492492146c573f4c6dfc8d08a443e258970b09", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d35ba58da30197d378e618ec0fa7e2e2d12cffd73ebbb2049d130bba434af09eff83986e6875e41ea432b7585a49b3a6c77cbb3c47919f8e82874c794635c1d2", + "wx" : "00d35ba58da30197d378e618ec0fa7e2e2d12cffd73ebbb2049d130bba434af09e", + "wy" : "00ff83986e6875e41ea432b7585a49b3a6c77cbb3c47919f8e82874c794635c1d2" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d35ba58da30197d378e618ec0fa7e2e2d12cffd73ebbb2049d130bba434af09eff83986e6875e41ea432b7585a49b3a6c77cbb3c47919f8e82874c794635c1d2", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01uljaMBl9N45hjsD6fi4tEs/9c+\nu7IEnRMLukNK8J7/g5huaHXkHqQyt1haSbOmx3y7PEeRn46Ch0x5RjXB0g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 335, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304502207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd022100bfffffff2aaaaaab7fffffffffffffffc815d0e60b3e596ecb1ad3a27cfd49c4", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "048651ce490f1b46d73f3ff475149be29136697334a519d7ddab0725c8d0793224e11c65bd8ca92dc8bc9ae82911f0b52751ce21dd9003ae60900bd825f590cc28", + "wx" : "008651ce490f1b46d73f3ff475149be29136697334a519d7ddab0725c8d0793224", + "wy" : "00e11c65bd8ca92dc8bc9ae82911f0b52751ce21dd9003ae60900bd825f590cc28" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200048651ce490f1b46d73f3ff475149be29136697334a519d7ddab0725c8d0793224e11c65bd8ca92dc8bc9ae82911f0b52751ce21dd9003ae60900bd825f590cc28", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhlHOSQ8bRtc/P/R1FJvikTZpczSl\nGdfdqwclyNB5MiThHGW9jKktyLya6CkR8LUnUc4h3ZADrmCQC9gl9ZDMKA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 336, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd02207fffffff55555555ffffffffffffffffd344a71e6f651458a27bdc81fd976e37", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046d8e1b12c831a0da8795650ff95f101ed921d9e2f72b15b1cdaca9826b9cfc6def6d63e2bc5c089570394a4bc9f892d5e6c7a6a637b20469a58c106ad486bf37", + "wx" : "6d8e1b12c831a0da8795650ff95f101ed921d9e2f72b15b1cdaca9826b9cfc6d", + "wy" : "00ef6d63e2bc5c089570394a4bc9f892d5e6c7a6a637b20469a58c106ad486bf37" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046d8e1b12c831a0da8795650ff95f101ed921d9e2f72b15b1cdaca9826b9cfc6def6d63e2bc5c089570394a4bc9f892d5e6c7a6a637b20469a58c106ad486bf37", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbY4bEsgxoNqHlWUP+V8QHtkh2eL3\nKxWxzaypgmuc/G3vbWPivFwIlXA5SkvJ+JLV5sempjeyBGmljBBq1Ia/Nw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 337, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd02203fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192aa", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "040ae580bae933b4ef2997cbdbb0922328ca9a410f627a0f7dff24cb4d920e15428911e7f8cc365a8a88eb81421a361ccc2b99e309d8dcd9a98ba83c3949d893e3", + "wx" : "0ae580bae933b4ef2997cbdbb0922328ca9a410f627a0f7dff24cb4d920e1542", + "wy" : "008911e7f8cc365a8a88eb81421a361ccc2b99e309d8dcd9a98ba83c3949d893e3" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200040ae580bae933b4ef2997cbdbb0922328ca9a410f627a0f7dff24cb4d920e15428911e7f8cc365a8a88eb81421a361ccc2b99e309d8dcd9a98ba83c3949d893e3", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECuWAuukztO8pl8vbsJIjKMqaQQ9i\neg99/yTLTZIOFUKJEef4zDZaiojrgUIaNhzMK5njCdjc2amLqDw5SdiT4w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 338, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "304402207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd02205d8ecd64a4eeba466815ddf3a4de9a8e6abd9c5db0a01eb80343553da648428f", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc46963838a40f2a36092e9004e92d8d940cf5638550ce672ce8b8d4e15eba5499249e9", + "wx" : "5b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc46963", + "wy" : "00838a40f2a36092e9004e92d8d940cf5638550ce672ce8b8d4e15eba5499249e9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc46963838a40f2a36092e9004e92d8d940cf5638550ce672ce8b8d4e15eba5499249e9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW4Ev1SGq+mmDWoSczm+962mDtELS\nRE/nDhNMAn/EaWODikDyo2CS6QBOktjZQM9WOFUM5nLOi41OFeulSZJJ6Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 339, + "comment" : "point duplication during verification", + "msg" : "313233343030", + "sig" : "304502206f2347cab7dd76858fe0555ac3bc99048c4aacafdfb6bcbe05ea6c42c4934569022100bb726660235793aa9957a61e76e00c2c435109cf9a15dd624d53f4301047856b", + "result" : "valid", + "flags" : [ + "PointDuplication" + ] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc469637c75bf0c5c9f6d17ffb16d2726bf30a9c7aaf31a8d317472b1ea145ab66db616", + "wx" : "5b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc46963", + "wy" : "7c75bf0c5c9f6d17ffb16d2726bf30a9c7aaf31a8d317472b1ea145ab66db616" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc469637c75bf0c5c9f6d17ffb16d2726bf30a9c7aaf31a8d317472b1ea145ab66db616", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW4Ev1SGq+mmDWoSczm+962mDtELS\nRE/nDhNMAn/EaWN8db8MXJ9tF/+xbScmvzCpx6rzGo0xdHKx6hRatm22Fg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 340, + "comment" : "duplication bug", + "msg" : "313233343030", + "sig" : "304502206f2347cab7dd76858fe0555ac3bc99048c4aacafdfb6bcbe05ea6c42c4934569022100bb726660235793aa9957a61e76e00c2c435109cf9a15dd624d53f4301047856b", + "result" : "invalid", + "flags" : [ + "PointDuplication" + ] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046adda82b90261b0f319faa0d878665a6b6da497f09c903176222c34acfef72a647e6f50dcc40ad5d9b59f7602bb222fad71a41bf5e1f9df4959a364c62e488d9", + "wx" : "6adda82b90261b0f319faa0d878665a6b6da497f09c903176222c34acfef72a6", + "wy" : "47e6f50dcc40ad5d9b59f7602bb222fad71a41bf5e1f9df4959a364c62e488d9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046adda82b90261b0f319faa0d878665a6b6da497f09c903176222c34acfef72a647e6f50dcc40ad5d9b59f7602bb222fad71a41bf5e1f9df4959a364c62e488d9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEat2oK5AmGw8xn6oNh4ZlprbaSX8J\nyQMXYiLDSs/vcqZH5vUNzECtXZtZ92ArsiL61xpBv14fnfSVmjZMYuSI2Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 341, + "comment" : "point with x-coordinate 0", + "msg" : "313233343030", + "sig" : "30250201010220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "042fca0d0a47914de77ed56e7eccc3276a601120c6df0069c825c8f6a01c9f382065f3450a1d17c6b24989a39beb1c7decfca8384fbdc294418e5d807b3c6ed7de", + "wx" : "2fca0d0a47914de77ed56e7eccc3276a601120c6df0069c825c8f6a01c9f3820", + "wy" : "65f3450a1d17c6b24989a39beb1c7decfca8384fbdc294418e5d807b3c6ed7de" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200042fca0d0a47914de77ed56e7eccc3276a601120c6df0069c825c8f6a01c9f382065f3450a1d17c6b24989a39beb1c7decfca8384fbdc294418e5d807b3c6ed7de", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL8oNCkeRTed+1W5+zMMnamARIMbf\nAGnIJcj2oByfOCBl80UKHRfGskmJo5vrHH3s/Kg4T73ClEGOXYB7PG7X3g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 342, + "comment" : "point with x-coordinate 0", + "msg" : "313233343030", + "sig" : "3045022101000000000000000000000000000000000000000000000000000000000000000002203333333300000000333333333333333325c7cbbc549e52e763f1f55a327a3aa9", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04dd86d3b5f4a13e8511083b78002081c53ff467f11ebd98a51a633db76665d25045d5c8200c89f2fa10d849349226d21d8dfaed6ff8d5cb3e1b7e17474ebc18f7", + "wx" : "00dd86d3b5f4a13e8511083b78002081c53ff467f11ebd98a51a633db76665d250", + "wy" : "45d5c8200c89f2fa10d849349226d21d8dfaed6ff8d5cb3e1b7e17474ebc18f7" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004dd86d3b5f4a13e8511083b78002081c53ff467f11ebd98a51a633db76665d25045d5c8200c89f2fa10d849349226d21d8dfaed6ff8d5cb3e1b7e17474ebc18f7", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3YbTtfShPoURCDt4ACCBxT/0Z/Ee\nvZilGmM9t2Zl0lBF1cggDIny+hDYSTSSJtIdjfrtb/jVyz4bfhdHTrwY9w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 343, + "comment" : "comparison with point at infinity ", + "msg" : "313233343030", + "sig" : "30440220555555550000000055555555555555553ef7a8e48d07df81a693439654210c7002203333333300000000333333333333333325c7cbbc549e52e763f1f55a327a3aa9", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044fea55b32cb32aca0c12c4cd0abfb4e64b0f5a516e578c016591a93f5a0fbcc5d7d3fd10b2be668c547b212f6bb14c88f0fecd38a8a4b2c785ed3be62ce4b280", + "wx" : "4fea55b32cb32aca0c12c4cd0abfb4e64b0f5a516e578c016591a93f5a0fbcc5", + "wy" : "00d7d3fd10b2be668c547b212f6bb14c88f0fecd38a8a4b2c785ed3be62ce4b280" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044fea55b32cb32aca0c12c4cd0abfb4e64b0f5a516e578c016591a93f5a0fbcc5d7d3fd10b2be668c547b212f6bb14c88f0fecd38a8a4b2c785ed3be62ce4b280", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAET+pVsyyzKsoMEsTNCr+05ksPWlFu\nV4wBZZGpP1oPvMXX0/0Qsr5mjFR7IS9rsUyI8P7NOKiksseF7TvmLOSygA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 344, + "comment" : "extreme value for k and edgecase s", + "msg" : "313233343030", + "sig" : "304402207cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc476699780220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04c6a771527024227792170a6f8eee735bf32b7f98af669ead299802e32d7c3107bc3b4b5e65ab887bbd343572b3e5619261fe3a073e2ffd78412f726867db589e", + "wx" : "00c6a771527024227792170a6f8eee735bf32b7f98af669ead299802e32d7c3107", + "wy" : "00bc3b4b5e65ab887bbd343572b3e5619261fe3a073e2ffd78412f726867db589e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004c6a771527024227792170a6f8eee735bf32b7f98af669ead299802e32d7c3107bc3b4b5e65ab887bbd343572b3e5619261fe3a073e2ffd78412f726867db589e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExqdxUnAkIneSFwpvju5zW/Mrf5iv\nZp6tKZgC4y18MQe8O0teZauIe700NXKz5WGSYf46Bz4v/XhBL3JoZ9tYng==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 345, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "304502207cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978022100b6db6db6249249254924924924924924625bd7a09bec4ca81bcdd9f8fd6b63cc", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04851c2bbad08e54ec7a9af99f49f03644d6ec6d59b207fec98de85a7d15b956efcee9960283045075684b410be8d0f7494b91aa2379f60727319f10ddeb0fe9d6", + "wx" : "00851c2bbad08e54ec7a9af99f49f03644d6ec6d59b207fec98de85a7d15b956ef", + "wy" : "00cee9960283045075684b410be8d0f7494b91aa2379f60727319f10ddeb0fe9d6" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004851c2bbad08e54ec7a9af99f49f03644d6ec6d59b207fec98de85a7d15b956efcee9960283045075684b410be8d0f7494b91aa2379f60727319f10ddeb0fe9d6", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhRwrutCOVOx6mvmfSfA2RNbsbVmy\nB/7JjehafRW5Vu/O6ZYCgwRQdWhLQQvo0PdJS5GqI3n2BycxnxDd6w/p1g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 346, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "304502207cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978022100cccccccc00000000cccccccccccccccc971f2ef152794b9d8fc7d568c9e8eaa7", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04f6417c8a670584e388676949e53da7fc55911ff68318d1bf3061205acb19c48f8f2b743df34ad0f72674acb7505929784779cd9ac916c3669ead43026ab6d43f", + "wx" : "00f6417c8a670584e388676949e53da7fc55911ff68318d1bf3061205acb19c48f", + "wy" : "008f2b743df34ad0f72674acb7505929784779cd9ac916c3669ead43026ab6d43f" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004f6417c8a670584e388676949e53da7fc55911ff68318d1bf3061205acb19c48f8f2b743df34ad0f72674acb7505929784779cd9ac916c3669ead43026ab6d43f", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9kF8imcFhOOIZ2lJ5T2n/FWRH/aD\nGNG/MGEgWssZxI+PK3Q980rQ9yZ0rLdQWSl4R3nNmskWw2aerUMCarbUPw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 347, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "304402207cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc4766997802203333333300000000333333333333333325c7cbbc549e52e763f1f55a327a3aaa", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04501421277be45a5eefec6c639930d636032565af420cf3373f557faa7f8a06438673d6cb6076e1cfcdc7dfe7384c8e5cac08d74501f2ae6e89cad195d0aa1371", + "wx" : "501421277be45a5eefec6c639930d636032565af420cf3373f557faa7f8a0643", + "wy" : "008673d6cb6076e1cfcdc7dfe7384c8e5cac08d74501f2ae6e89cad195d0aa1371" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004501421277be45a5eefec6c639930d636032565af420cf3373f557faa7f8a06438673d6cb6076e1cfcdc7dfe7384c8e5cac08d74501f2ae6e89cad195d0aa1371", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUBQhJ3vkWl7v7GxjmTDWNgMlZa9C\nDPM3P1V/qn+KBkOGc9bLYHbhz83H3+c4TI5crAjXRQHyrm6JytGV0KoTcQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 348, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "304402207cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978022049249248db6db6dbb6db6db6db6db6db5a8b230d0b2b51dcd7ebf0c9fef7c185", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "040d935bf9ffc115a527735f729ca8a4ca23ee01a4894adf0e3415ac84e808bb343195a3762fea29ed38912bd9ea6c4fde70c3050893a4375850ce61d82eba33c5", + "wx" : "0d935bf9ffc115a527735f729ca8a4ca23ee01a4894adf0e3415ac84e808bb34", + "wy" : "3195a3762fea29ed38912bd9ea6c4fde70c3050893a4375850ce61d82eba33c5" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200040d935bf9ffc115a527735f729ca8a4ca23ee01a4894adf0e3415ac84e808bb343195a3762fea29ed38912bd9ea6c4fde70c3050893a4375850ce61d82eba33c5", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDZNb+f/BFaUnc19ynKikyiPuAaSJ\nSt8ONBWshOgIuzQxlaN2L+op7TiRK9nqbE/ecMMFCJOkN1hQzmHYLrozxQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 349, + "comment" : "extreme value for k", + "msg" : "313233343030", + "sig" : "304402207cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978022016a4502e2781e11ac82cbc9d1edd8c981584d13e18411e2f6e0478c34416e3bb", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045e59f50708646be8a589355014308e60b668fb670196206c41e748e64e4dca215de37fee5c97bcaf7144d5b459982f52eeeafbdf03aacbafef38e213624a01de", + "wx" : "5e59f50708646be8a589355014308e60b668fb670196206c41e748e64e4dca21", + "wy" : "5de37fee5c97bcaf7144d5b459982f52eeeafbdf03aacbafef38e213624a01de" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045e59f50708646be8a589355014308e60b668fb670196206c41e748e64e4dca215de37fee5c97bcaf7144d5b459982f52eeeafbdf03aacbafef38e213624a01de", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXln1Bwhka+iliTVQFDCOYLZo+2cB\nliBsQedI5k5NyiFd43/uXJe8r3FE1bRZmC9S7ur73wOqy6/vOOITYkoB3g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 350, + "comment" : "extreme value for k and edgecase s", + "msg" : "313233343030", + "sig" : "304402206b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2960220555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04169fb797325843faff2f7a5b5445da9e2fd6226f7ef90ef0bfe924104b02db8e7bbb8de662c7b9b1cf9b22f7a2e582bd46d581d68878efb2b861b131d8a1d667", + "wx" : "169fb797325843faff2f7a5b5445da9e2fd6226f7ef90ef0bfe924104b02db8e", + "wy" : "7bbb8de662c7b9b1cf9b22f7a2e582bd46d581d68878efb2b861b131d8a1d667" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004169fb797325843faff2f7a5b5445da9e2fd6226f7ef90ef0bfe924104b02db8e7bbb8de662c7b9b1cf9b22f7a2e582bd46d581d68878efb2b861b131d8a1d667", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFp+3lzJYQ/r/L3pbVEXani/WIm9+\n+Q7wv+kkEEsC2457u43mYse5sc+bIvei5YK9RtWB1oh477K4YbEx2KHWZw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 351, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "304502206b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296022100b6db6db6249249254924924924924924625bd7a09bec4ca81bcdd9f8fd6b63cc", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04271cd89c000143096b62d4e9e4ca885aef2f7023d18affdaf8b7b548981487540a1c6e954e32108435b55fa385b0f76481a609b9149ccb4b02b2ca47fe8e4da5", + "wx" : "271cd89c000143096b62d4e9e4ca885aef2f7023d18affdaf8b7b54898148754", + "wy" : "0a1c6e954e32108435b55fa385b0f76481a609b9149ccb4b02b2ca47fe8e4da5" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004271cd89c000143096b62d4e9e4ca885aef2f7023d18affdaf8b7b548981487540a1c6e954e32108435b55fa385b0f76481a609b9149ccb4b02b2ca47fe8e4da5", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJxzYnAABQwlrYtTp5MqIWu8vcCPR\niv/a+Le1SJgUh1QKHG6VTjIQhDW1X6OFsPdkgaYJuRScy0sCsspH/o5NpQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 352, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "304502206b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296022100cccccccc00000000cccccccccccccccc971f2ef152794b9d8fc7d568c9e8eaa7", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043d0bc7ed8f09d2cb7ddb46ebc1ed799ab1563a9ab84bf524587a220afe499c12e22dc3b3c103824a4f378d96adb0a408abf19ce7d68aa6244f78cb216fa3f8df", + "wx" : "3d0bc7ed8f09d2cb7ddb46ebc1ed799ab1563a9ab84bf524587a220afe499c12", + "wy" : "00e22dc3b3c103824a4f378d96adb0a408abf19ce7d68aa6244f78cb216fa3f8df" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043d0bc7ed8f09d2cb7ddb46ebc1ed799ab1563a9ab84bf524587a220afe499c12e22dc3b3c103824a4f378d96adb0a408abf19ce7d68aa6244f78cb216fa3f8df", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPQvH7Y8J0st920brwe15mrFWOpq4\nS/UkWHoiCv5JnBLiLcOzwQOCSk83jZatsKQIq/Gc59aKpiRPeMshb6P43w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 353, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "304402206b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29602203333333300000000333333333333333325c7cbbc549e52e763f1f55a327a3aaa", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04a6c885ade1a4c566f9bb010d066974abb281797fa701288c721bcbd23663a9b72e424b690957168d193a6096fc77a2b004a9c7d467e007e1f2058458f98af316", + "wx" : "00a6c885ade1a4c566f9bb010d066974abb281797fa701288c721bcbd23663a9b7", + "wy" : "2e424b690957168d193a6096fc77a2b004a9c7d467e007e1f2058458f98af316" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004a6c885ade1a4c566f9bb010d066974abb281797fa701288c721bcbd23663a9b72e424b690957168d193a6096fc77a2b004a9c7d467e007e1f2058458f98af316", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpsiFreGkxWb5uwENBml0q7KBeX+n\nASiMchvL0jZjqbcuQktpCVcWjRk6YJb8d6KwBKnH1GfgB+HyBYRY+YrzFg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 354, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "304402206b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296022049249248db6db6dbb6db6db6db6db6db5a8b230d0b2b51dcd7ebf0c9fef7c185", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "048d3c2c2c3b765ba8289e6ac3812572a25bf75df62d87ab7330c3bdbad9ebfa5c4c6845442d66935b238578d43aec54f7caa1621d1af241d4632e0b780c423f5d", + "wx" : "008d3c2c2c3b765ba8289e6ac3812572a25bf75df62d87ab7330c3bdbad9ebfa5c", + "wy" : "4c6845442d66935b238578d43aec54f7caa1621d1af241d4632e0b780c423f5d" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200048d3c2c2c3b765ba8289e6ac3812572a25bf75df62d87ab7330c3bdbad9ebfa5c4c6845442d66935b238578d43aec54f7caa1621d1af241d4632e0b780c423f5d", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjTwsLDt2W6gonmrDgSVyolv3XfYt\nh6tzMMO9utnr+lxMaEVELWaTWyOFeNQ67FT3yqFiHRryQdRjLgt4DEI/XQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 355, + "comment" : "extreme value for k", + "msg" : "313233343030", + "sig" : "304402206b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296022016a4502e2781e11ac82cbc9d1edd8c981584d13e18411e2f6e0478c34416e3bb", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", + "wx" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "wy" : "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaxfR8uEsQkf4vOblY6RA8ncDfYEt\n6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 356, + "comment" : "testing point duplication", + "msg" : "313233343030", + "sig" : "3045022100bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca6050230220249249246db6db6ddb6db6db6db6db6dad4591868595a8ee6bf5f864ff7be0c2", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 357, + "comment" : "testing point duplication", + "msg" : "313233343030", + "sig" : "3044022044a5ad0ad0636d9f12bc9e0a6bdd5e1cbcb012ea7bf091fcec15b0c43202d52e0220249249246db6db6ddb6db6db6db6db6dad4591868595a8ee6bf5f864ff7be0c2", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296b01cbd1c01e58065711814b583f061e9d431cca994cea1313449bf97c840ae0a", + "wx" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "wy" : "00b01cbd1c01e58065711814b583f061e9d431cca994cea1313449bf97c840ae0a" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296b01cbd1c01e58065711814b583f061e9d431cca994cea1313449bf97c840ae0a", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaxfR8uEsQkf4vOblY6RA8ncDfYEt\n6zOg9KE5RdiYwpawHL0cAeWAZXEYFLWD8GHp1DHMqZTOoTE0Sb+XyECuCg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 358, + "comment" : "testing point duplication", + "msg" : "313233343030", + "sig" : "3045022100bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca6050230220249249246db6db6ddb6db6db6db6db6dad4591868595a8ee6bf5f864ff7be0c2", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 359, + "comment" : "testing point duplication", + "msg" : "313233343030", + "sig" : "3044022044a5ad0ad0636d9f12bc9e0a6bdd5e1cbcb012ea7bf091fcec15b0c43202d52e0220249249246db6db6ddb6db6db6db6db6dad4591868595a8ee6bf5f864ff7be0c2", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0404aaec73635726f213fb8a9e64da3b8632e41495a944d0045b522eba7240fad587d9315798aaa3a5ba01775787ced05eaaf7b4e09fc81d6d1aa546e8365d525d", + "wx" : "04aaec73635726f213fb8a9e64da3b8632e41495a944d0045b522eba7240fad5", + "wy" : "0087d9315798aaa3a5ba01775787ced05eaaf7b4e09fc81d6d1aa546e8365d525d" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000404aaec73635726f213fb8a9e64da3b8632e41495a944d0045b522eba7240fad587d9315798aaa3a5ba01775787ced05eaaf7b4e09fc81d6d1aa546e8365d525d", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBKrsc2NXJvIT+4qeZNo7hjLkFJWp\nRNAEW1IuunJA+tWH2TFXmKqjpboBd1eHztBeqve04J/IHW0apUboNl1SXQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 360, + "comment" : "pseudorandom signature", + "msg" : "", + "sig" : "3045022100b292a619339f6e567a305c951c0dcbcc42d16e47f219f9e98e76e09d8770b34a02200177e60492c5a8242f76f07bfe3661bde59ec2a17ce5bd2dab2abebdf89a62e2", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 361, + "comment" : "pseudorandom signature", + "msg" : "4d7367", + "sig" : "30450220530bd6b0c9af2d69ba897f6b5fb59695cfbf33afe66dbadcf5b8d2a2a6538e23022100d85e489cb7a161fd55ededcedbf4cc0c0987e3e3f0f242cae934c72caa3f43e9", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 362, + "comment" : "pseudorandom signature", + "msg" : "313233343030", + "sig" : "3046022100a8ea150cb80125d7381c4c1f1da8e9de2711f9917060406a73d7904519e51388022100f3ab9fa68bd47973a73b2d40480c2ba50c22c9d76ec217257288293285449b86", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 363, + "comment" : "pseudorandom signature", + "msg" : "0000000000000000000000000000000000000000", + "sig" : "3045022100986e65933ef2ed4ee5aada139f52b70539aaf63f00a91f29c69178490d57fb7102203dafedfb8da6189d372308cbf1489bbbdabf0c0217d1c0ff0f701aaa7a694b9c", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044f337ccfd67726a805e4f1600ae2849df3807eca117380239fbd816900000000ed9dea124cc8c396416411e988c30f427eb504af43a3146cd5df7ea60666d685", + "wx" : "4f337ccfd67726a805e4f1600ae2849df3807eca117380239fbd816900000000", + "wy" : "00ed9dea124cc8c396416411e988c30f427eb504af43a3146cd5df7ea60666d685" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044f337ccfd67726a805e4f1600ae2849df3807eca117380239fbd816900000000ed9dea124cc8c396416411e988c30f427eb504af43a3146cd5df7ea60666d685", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETzN8z9Z3JqgF5PFgCuKEnfOAfsoR\nc4Ajn72BaQAAAADtneoSTMjDlkFkEemIww9CfrUEr0OjFGzV336mBmbWhQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 364, + "comment" : "x-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "3046022100d434e262a49eab7781e353a3565e482550dd0fd5defa013c7f29745eff3569f10221009b0c0a93f267fb6052fd8077be769c2b98953195d7bc10de844218305c6ba17a", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 365, + "comment" : "x-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "304402200fe774355c04d060f76d79fd7a772e421463489221bf0a33add0be9b1979110b0220500dcba1c69a8fbd43fa4f57f743ce124ca8b91a1f325f3fac6181175df55737", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 366, + "comment" : "x-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "3045022100bb40bf217bed3fb3950c7d39f03d36dc8e3b2cd79693f125bfd06595ee1135e30220541bf3532351ebb032710bdb6a1bf1bfc89a1e291ac692b3fa4780745bb55677", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f49726500493584fa174d791c72bf2ce3880a8960dd2a7c7a1338a82f85a9e59cdbde80000000", + "wx" : "3cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f497265004935", + "wy" : "0084fa174d791c72bf2ce3880a8960dd2a7c7a1338a82f85a9e59cdbde80000000" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f49726500493584fa174d791c72bf2ce3880a8960dd2a7c7a1338a82f85a9e59cdbde80000000", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPPA9YU2JOc/UmaB4c/rCgWGPBrj/\nh+gBXD9JcmUASTWE+hdNeRxyvyzjiAqJYN0qfHoTOKgvhanlnNvegAAAAA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 367, + "comment" : "y-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "30440220664eb7ee6db84a34df3c86ea31389a5405badd5ca99231ff556d3e75a233e73a022059f3c752e52eca46137642490a51560ce0badc678754b8f72e51a2901426a1bd", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 368, + "comment" : "y-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "304502204cd0429bbabd2827009d6fcd843d4ce39c3e42e2d1631fd001985a79d1fd8b430221009638bf12dd682f60be7ef1d0e0d98f08b7bca77a1a2b869ae466189d2acdabe3", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 369, + "comment" : "y-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "3046022100e56c6ea2d1b017091c44d8b6cb62b9f460e3ce9aed5e5fd41e8added97c56c04022100a308ec31f281e955be20b457e463440b4fcf2b80258078207fc1378180f89b55", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f4972650049357b05e8b186e38d41d31c77f5769f22d58385ecc857d07a561a6324217fffffff", + "wx" : "3cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f497265004935", + "wy" : "7b05e8b186e38d41d31c77f5769f22d58385ecc857d07a561a6324217fffffff" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f4972650049357b05e8b186e38d41d31c77f5769f22d58385ecc857d07a561a6324217fffffff", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPPA9YU2JOc/UmaB4c/rCgWGPBrj/\nh+gBXD9JcmUASTV7BeixhuONQdMcd/V2nyLVg4XsyFfQelYaYyQhf////w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 370, + "comment" : "y-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "304402201158a08d291500b4cabed3346d891eee57c176356a2624fb011f8fbbf34668300220228a8c486a736006e082325b85290c5bc91f378b75d487dda46798c18f285519", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 371, + "comment" : "y-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "3045022100b1db9289649f59410ea36b0c0fc8d6aa2687b29176939dd23e0dde56d309fa9d02203e1535e4280559015b0dbd987366dcf43a6d1af5c23c7d584e1c3f48a1251336", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 372, + "comment" : "y-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "3046022100b7b16e762286cb96446aa8d4e6e7578b0a341a79f2dd1a220ac6f0ca4e24ed86022100ddc60a700a139b04661c547d07bbb0721780146df799ccf55e55234ecb8f12bc", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "042829c31faa2e400e344ed94bca3fcd0545956ebcfe8ad0f6dfa5ff8effffffffa01aafaf000e52585855afa7676ade284113099052df57e7eb3bd37ebeb9222e", + "wx" : "2829c31faa2e400e344ed94bca3fcd0545956ebcfe8ad0f6dfa5ff8effffffff", + "wy" : "00a01aafaf000e52585855afa7676ade284113099052df57e7eb3bd37ebeb9222e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200042829c31faa2e400e344ed94bca3fcd0545956ebcfe8ad0f6dfa5ff8effffffffa01aafaf000e52585855afa7676ade284113099052df57e7eb3bd37ebeb9222e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKCnDH6ouQA40TtlLyj/NBUWVbrz+\nitD236X/jv////+gGq+vAA5SWFhVr6dnat4oQRMJkFLfV+frO9N+vrkiLg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 373, + "comment" : "x-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "3045022100d82a7c2717261187c8e00d8df963ff35d796edad36bc6e6bd1c91c670d9105b402203dcabddaf8fcaa61f4603e7cbac0f3c0351ecd5988efb23f680d07debd139929", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 374, + "comment" : "x-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "304402205eb9c8845de68eb13d5befe719f462d77787802baff30ce96a5cba063254af7802202c026ae9be2e2a5e7ca0ff9bbd92fb6e44972186228ee9a62b87ddbe2ef66fb5", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 375, + "comment" : "x-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "304602210096843dd03c22abd2f3b782b170239f90f277921becc117d0404a8e4e36230c28022100f2be378f526f74a543f67165976de9ed9a31214eb4d7e6db19e1ede123dd991d", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04fffffff948081e6a0458dd8f9e738f2665ff9059ad6aac0708318c4ca9a7a4f55a8abcba2dda8474311ee54149b973cae0c0fb89557ad0bf78e6529a1663bd73", + "wx" : "00fffffff948081e6a0458dd8f9e738f2665ff9059ad6aac0708318c4ca9a7a4f5", + "wy" : "5a8abcba2dda8474311ee54149b973cae0c0fb89557ad0bf78e6529a1663bd73" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004fffffff948081e6a0458dd8f9e738f2665ff9059ad6aac0708318c4ca9a7a4f55a8abcba2dda8474311ee54149b973cae0c0fb89557ad0bf78e6529a1663bd73", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE////+UgIHmoEWN2PnnOPJmX/kFmt\naqwHCDGMTKmnpPVairy6LdqEdDEe5UFJuXPK4MD7iVV60L945lKaFmO9cw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 376, + "comment" : "x-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "30440220766456dce1857c906f9996af729339464d27e9d98edc2d0e3b760297067421f60220402385ecadae0d8081dccaf5d19037ec4e55376eced699e93646bfbbf19d0b41", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 377, + "comment" : "x-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "3046022100c605c4b2edeab20419e6518a11b2dbc2b97ed8b07cced0b19c34f777de7b9fd9022100edf0f612c5f46e03c719647bc8af1b29b2cde2eda700fb1cff5e159d47326dba", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 378, + "comment" : "x-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "3046022100d48b68e6cabfe03cf6141c9ac54141f210e64485d9929ad7b732bfe3b7eb8a84022100feedae50c61bd00e19dc26f9b7e2265e4508c389109ad2f208f0772315b6c941", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0400000003fa15f963949d5f03a6f5c7f86f9e0015eeb23aebbff1173937ba748e1099872070e8e87c555fa13659cca5d7fadcfcb0023ea889548ca48af2ba7e71", + "wx" : "03fa15f963949d5f03a6f5c7f86f9e0015eeb23aebbff1173937ba748e", + "wy" : "1099872070e8e87c555fa13659cca5d7fadcfcb0023ea889548ca48af2ba7e71" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000400000003fa15f963949d5f03a6f5c7f86f9e0015eeb23aebbff1173937ba748e1099872070e8e87c555fa13659cca5d7fadcfcb0023ea889548ca48af2ba7e71", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAAAAA/oV+WOUnV8DpvXH+G+eABXu\nsjrrv/EXOTe6dI4QmYcgcOjofFVfoTZZzKXX+tz8sAI+qIlUjKSK8rp+cQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 379, + "comment" : "x-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "3046022100b7c81457d4aeb6aa65957098569f0479710ad7f6595d5874c35a93d12a5dd4c7022100b7961a0b652878c2d568069a432ca18a1a9199f2ca574dad4b9e3a05c0a1cdb3", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 380, + "comment" : "x-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "304402206b01332ddb6edfa9a30a1321d5858e1ee3cf97e263e669f8de5e9652e76ff3f702205939545fced457309a6a04ace2bd0f70139c8f7d86b02cb1cc58f9e69e96cd5a", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 381, + "comment" : "x-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "3046022100efdb884720eaeadc349f9fc356b6c0344101cd2fd8436b7d0e6a4fb93f106361022100f24bee6ad5dc05f7613975473aadf3aacba9e77de7d69b6ce48cb60d8113385d", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015000000001352bb4a0fa2ea4cceb9ab63dd684ade5a1127bcf300a698a7193bc2", + "wx" : "00bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015", + "wy" : "1352bb4a0fa2ea4cceb9ab63dd684ade5a1127bcf300a698a7193bc2" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015000000001352bb4a0fa2ea4cceb9ab63dd684ade5a1127bcf300a698a7193bc2", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvLspFMefBF6qbsu8YSgWs75dLWeW\ncH2BJen4UcGK8BUAAAAAE1K7Sg+i6kzOuatj3WhK3loRJ7zzAKaYpxk7wg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 382, + "comment" : "y-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "3044022031230428405560dcb88fb5a646836aea9b23a23dd973dcbe8014c87b8b20eb0702200f9344d6e812ce166646747694a41b0aaf97374e19f3c5fb8bd7ae3d9bd0beff", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 383, + "comment" : "y-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "3046022100caa797da65b320ab0d5c470cda0b36b294359c7db9841d679174db34c4855743022100cf543a62f23e212745391aaf7505f345123d2685ee3b941d3de6d9b36242e5a0", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 384, + "comment" : "y-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "304502207e5f0ab5d900d3d3d7867657e5d6d36519bc54084536e7d21c336ed8001859450221009450c07f201faec94b82dfb322e5ac676688294aad35aa72e727ff0b19b646aa", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015fffffffeecad44b6f05d15b33146549c2297b522a5eed8430cff596758e6c43d", + "wx" : "00bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015", + "wy" : "00fffffffeecad44b6f05d15b33146549c2297b522a5eed8430cff596758e6c43d" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015fffffffeecad44b6f05d15b33146549c2297b522a5eed8430cff596758e6c43d", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvLspFMefBF6qbsu8YSgWs75dLWeW\ncH2BJen4UcGK8BX////+7K1EtvBdFbMxRlScIpe1IqXu2EMM/1lnWObEPQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaVerify", + "tests" : [ + { + "tcId" : 385, + "comment" : "y-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "3046022100d7d70c581ae9e3f66dc6a480bf037ae23f8a1e4a2136fe4b03aa69f0ca25b35602210089c460f8a5a5c2bbba962c8a3ee833a413e85658e62a59e2af41d9127cc47224", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 386, + "comment" : "y-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "30440220341c1b9ff3c83dd5e0dfa0bf68bcdf4bb7aa20c625975e5eeee34bb396266b34022072b69f061b750fd5121b22b11366fad549c634e77765a017902a67099e0a4469", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 387, + "comment" : "y-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "3045022070bebe684cdcb5ca72a42f0d873879359bd1781a591809947628d313a3814f67022100aec03aca8f5587a4d535fa31027bbe9cc0e464b1c3577f4c2dcde6b2094798a9", + "result" : "valid", + "flags" : [] + } + ] + } + ] +} diff --git a/libraries/crypto/tests/wycheproof.rs b/libraries/crypto/tests/wycheproof.rs new file mode 100644 index 0000000..910a768 --- /dev/null +++ b/libraries/crypto/tests/wycheproof.rs @@ -0,0 +1,217 @@ +// Copyright 2019 Google LLC +// +// 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. + +use crypto::ecdsa; +use serde::Deserialize; +use std::collections::HashMap; +use std::error::Error; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; + +mod asn1; + +#[test] +fn wycheproof() { + let wycheproof = load_tests("tests/data/ecdsa_secp256r1_sha256_test.json").unwrap(); + wycheproof.type_check(); + assert!(wycheproof.run_tests()); +} + +fn load_tests>(path: P) -> Result> { + let file = File::open(path)?; + let wycheproof = serde_json::from_reader(BufReader::new(file))?; + Ok(wycheproof) +} + +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct Wycheproof { + algorithm: String, + #[allow(dead_code)] + generatorVersion: String, + #[allow(dead_code)] + numberOfTests: u32, + #[allow(dead_code)] + header: Vec, + notes: HashMap, + schema: String, + testGroups: Vec, +} + +impl Wycheproof { + fn type_check(&self) { + assert_eq!(self.algorithm, "ECDSA"); + assert_eq!(self.schema, "ecdsa_verify_schema.json"); + for group in &self.testGroups { + group.type_check(); + } + } + + fn run_tests(&self) -> bool { + let mut result = true; + for group in &self.testGroups { + result &= group.run_tests(&self.notes); + } + result + } +} + +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct TestGroup { + key: Key, + #[allow(dead_code)] + keyDer: String, + #[allow(dead_code)] + keyPem: String, + sha: String, + r#type: String, + tests: Vec, +} + +impl TestGroup { + fn type_check(&self) { + self.key.type_check(); + assert_eq!(self.sha, "SHA-256"); + assert_eq!(self.r#type, "EcdsaVerify"); + for test in &self.tests { + test.type_check(); + } + } + + fn run_tests(&self, notes: &HashMap) -> bool { + let key = self.key.get_key(); + let mut result = true; + for test in &self.tests { + result &= test.run_test(&key, notes); + } + result + } +} + +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct Key { + curve: String, + keySize: u32, + r#type: String, + uncompressed: String, + #[allow(dead_code)] + wx: String, + #[allow(dead_code)] + wy: String, +} + +impl Key { + fn type_check(&self) { + assert_eq!(self.curve, "secp256r1"); + assert_eq!(self.keySize, 256); + assert_eq!(self.r#type, "EcPublicKey"); + assert_eq!(self.uncompressed.len(), 130); + } + + fn get_key(&self) -> Option { + let bytes = hex::decode(&self.uncompressed).unwrap(); + ecdsa::PubKey::from_bytes_uncompressed(&bytes) + } +} + +#[derive(Deserialize, Debug)] +#[allow(non_camel_case_types)] +enum TestResult { + valid, + invalid, + acceptable, +} + +#[derive(Deserialize)] +#[allow(non_snake_case)] +struct TestCase { + tcId: u32, + comment: String, + msg: String, + sig: String, + result: TestResult, + flags: Vec, +} + +impl TestCase { + fn type_check(&self) { + // Nothing to do. + } + + fn print(&self, notes: &HashMap, error_msg: &str) { + println!("Test case #{} => {}", self.tcId, error_msg); + println!(" {}", self.comment); + println!(" result = {:?}", self.result); + for f in &self.flags { + println!( + " flag {} = {}", + f, + notes.get(f).map_or("unknown flag", |x| &x) + ); + } + } + + fn run_test(&self, key: &Option, notes: &HashMap) -> bool { + match key { + None => { + let pass = match self.result { + TestResult::invalid | TestResult::acceptable => true, + TestResult::valid => false, + }; + if !pass { + self.print(notes, "Invalid public key"); + } + pass + } + Some(k) => { + let msg = hex::decode(&self.msg).unwrap(); + let sig = hex::decode(&self.sig).unwrap(); + match asn1::parse_signature(sig.as_slice()) { + Err(e) => { + let pass = match self.result { + TestResult::invalid | TestResult::acceptable => true, + TestResult::valid => false, + }; + if !pass { + self.print(notes, "Invalid ASN.1 encoding for the signature"); + println!(" {:?}", e); + } + pass + } + Ok(signature) => { + let verified = k.verify_vartime::(&msg, &signature); + let pass = match self.result { + TestResult::acceptable => true, + TestResult::valid => verified, + TestResult::invalid => !verified, + }; + if !pass { + self.print( + notes, + &format!( + "Expected {:?} result, but the signature verification was {}", + self.result, verified + ), + ); + } + pass + } + } + } + } + } +} diff --git a/nrf52840dk_layout.ld b/nrf52840dk_layout.ld new file mode 100644 index 0000000..218b6bb --- /dev/null +++ b/nrf52840dk_layout.ld @@ -0,0 +1,19 @@ +/* Layout for the nRF52840-DK and nRF52840 dongle, used by the + * app in this repository. + */ + +MEMORY { + /* The application region is 64 bytes (0x40) */ + FLASH (rx) : ORIGIN = 0x00040040, LENGTH = 0x000BFFC0 + SRAM (rwx) : ORIGIN = 0x20020000, LENGTH = 128K +} + +/* + * Any change to STACK_SIZE should be accompanied by a corresponding change to + * `elf2tab`'s `--stack` option + */ +STACK_SIZE = 16384; + +MPU_MIN_ALIGN = 8K; + +INCLUDE layout.ld diff --git a/patches/libtock-rs/01-panic_console.patch b/patches/libtock-rs/01-panic_console.patch new file mode 100644 index 0000000..dd8f31c --- /dev/null +++ b/patches/libtock-rs/01-panic_console.patch @@ -0,0 +1,81 @@ +diff --git a/Cargo.toml b/Cargo.toml +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -7,6 +7,9 @@ + + [dependencies] + linked_list_allocator = "0.6.4" ++ ++[features] ++panic_console = [] + + [dev-dependencies] + corepack = { version = "0.4.0", default-features = false, features = ["alloc"] } +diff --git a/src/lang_items.rs b/src/lang_items.rs +index ab2c945..deef73b 100644 +--- a/src/lang_items.rs ++++ b/src/lang_items.rs +@@ -18,10 +18,14 @@ + //! `rustc_main`. That's covered by the `_start` function in the root of this + //! crate. + ++#[cfg(feature = "panic_console")] ++use crate::console::Console; + use crate::led; + use crate::timer; + use crate::timer::Duration; + use core::alloc::Layout; ++#[cfg(feature = "panic_console")] ++use core::fmt::Write; + use core::panic::PanicInfo; + + #[lang = "start"] +@@ -42,11 +46,7 @@ + } + } + +-#[panic_handler] +-fn panic_handler(_info: &PanicInfo) -> ! { ++fn flash_all_leds() -> ! { +- // Signal a panic using the LowLevelDebug capsule (if available). +- super::debug::low_level_status_code(1); +- + // Flash all LEDs (if available). + loop { + for led in led::all() { +@@ -60,6 +59,34 @@ + } + } + ++#[cfg(feature = "panic_console")] ++#[panic_handler] ++fn panic_handler(info: &PanicInfo) -> ! { ++ // Signal a panic using the LowLevelDebug capsule (if available). ++ super::debug::low_level_status_code(1); ++ ++ let mut console = Console::new(); ++ writeln!(console, "{}", info).ok(); ++ // Force the kernel to report the panic cause, by reading an invalid address. ++ // The memory protection unit should be setup by the Tock kernel to prevent apps from accessing ++ // address zero. ++ unsafe { ++ core::ptr::read_volatile(0 as *const usize); ++ } ++ // We still flash the LEDs in case for some reason the previous line didn't cause a crash in ++ // the kernel. ++ flash_all_leds(); ++} ++ ++#[cfg(not(feature = "panic_console"))] ++#[panic_handler] ++fn panic_handler(_info: &PanicInfo) -> ! { ++ // Signal a panic using the LowLevelDebug capsule (if available). ++ super::debug::low_level_status_code(1); ++ ++ flash_all_leds(); ++} ++ + #[alloc_error_handler] + fn cycle_leds(_: Layout) -> ! { + loop { + diff --git a/patches/libtock-rs/02-timer.patch b/patches/libtock-rs/02-timer.patch new file mode 100644 index 0000000..7b8a5d9 --- /dev/null +++ b/patches/libtock-rs/02-timer.patch @@ -0,0 +1,72 @@ +diff --git a/src/timer.rs b/src/timer.rs +index ae60b07..2e7d544 100644 +--- a/src/timer.rs ++++ b/src/timer.rs +@@ -178,7 +178,7 @@ impl<'a> Timer<'a> { + } + } + +-#[derive(Copy, Clone, Debug)] ++#[derive(Copy, Clone, Debug, PartialEq)] + pub struct ClockFrequency { + hz: usize, + } +@@ -196,21 +196,53 @@ pub struct ClockValue { + } + + impl ClockValue { ++ pub const fn new(num_ticks: isize, clock_hz: usize) -> ClockValue { ++ ClockValue { ++ num_ticks, ++ clock_frequency: ClockFrequency { hz: clock_hz }, ++ } ++ } ++ + pub fn num_ticks(&self) -> isize { + self.num_ticks + } + ++ // Computes (value * factor) / divisor, even when value * factor >= isize::MAX. ++ fn scale_int(value: isize, factor: isize, divisor: isize) -> isize { ++ // As long as isize is not i64, this should be fine. If not, this is an alternative: ++ // factor * (value / divisor) + ((value % divisor) * factor) / divisor ++ ((value as i64 * factor as i64) / divisor as i64) as isize ++ } ++ + pub fn ms(&self) -> isize { +- if self.num_ticks.abs() < isize::MAX / 1000 { +- (1000 * self.num_ticks) / self.clock_frequency.hz() as isize +- } else { +- 1000 * (self.num_ticks / self.clock_frequency.hz() as isize) +- } ++ ClockValue::scale_int(self.num_ticks, 1000, self.clock_frequency.hz() as isize) + } + + pub fn ms_f64(&self) -> f64 { + 1000.0 * (self.num_ticks as f64) / (self.clock_frequency.hz() as f64) + } ++ ++ pub fn wrapping_add(self, duration: Duration) -> ClockValue { ++ // This is a precision preserving formula for scaling an isize. ++ let duration_ticks = ++ ClockValue::scale_int(duration.ms, self.clock_frequency.hz() as isize, 1000); ++ ClockValue { ++ num_ticks: self.num_ticks.wrapping_add(duration_ticks), ++ clock_frequency: self.clock_frequency, ++ } ++ } ++ ++ pub fn wrapping_sub(self, other: ClockValue) -> Option> { ++ if self.clock_frequency == other.clock_frequency { ++ let clock_duration = ClockValue { ++ num_ticks: self.num_ticks - other.num_ticks, ++ clock_frequency: self.clock_frequency, ++ }; ++ Some(Duration::from_ms(clock_duration.ms())) ++ } else { ++ None ++ } ++ } + } + + pub struct Alarm { diff --git a/patches/libtock-rs/03-public_syscalls.patch b/patches/libtock-rs/03-public_syscalls.patch new file mode 100644 index 0000000..109b6cb --- /dev/null +++ b/patches/libtock-rs/03-public_syscalls.patch @@ -0,0 +1,13 @@ +diff --git a/src/lib.rs b/src/lib.rs +index 1d4f26b..c1483e9 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -42,7 +42,7 @@ pub mod syscalls; + + #[cfg(not(any(target_arch = "arm", target_arch = "riscv32")))] + #[path = "syscalls_mock.rs"] +-mod syscalls; ++pub mod syscalls; + + #[cfg(any(target_arch = "arm", target_arch = "riscv32"))] + #[global_allocator] diff --git a/patches/libtock-rs/04-bigger_heap.patch b/patches/libtock-rs/04-bigger_heap.patch new file mode 100644 index 0000000..098737d --- /dev/null +++ b/patches/libtock-rs/04-bigger_heap.patch @@ -0,0 +1,13 @@ +diff --git a/src/entry_point.rs b/src/entry_point.rs +index a24958c..e907e33 100644 +--- a/src/entry_point.rs ++++ b/src/entry_point.rs +@@ -348,7 +348,7 @@ pub unsafe extern "C" fn rust_start(app_start: usize, stacktop: usize, app_heap_ + // Heap size is set using `elf2tab` with `--app-heap` option, which is + // currently at 1024. If you change the `elf2tab` heap size, make sure to + // make the corresponding change here. +- const HEAP_SIZE: usize = 1024; ++ const HEAP_SIZE: usize = 90000; + + // we could have also bss_end for app_heap_start + let app_heap_start = app_heap_break; diff --git a/patches/tock/01-persistent-storage.patch b/patches/tock/01-persistent-storage.patch new file mode 100644 index 0000000..1a356a3 --- /dev/null +++ b/patches/tock/01-persistent-storage.patch @@ -0,0 +1,323 @@ +diff --git a/boards/nordic/nrf52dk_base/src/lib.rs b/boards/nordic/nrf52dk_base/src/lib.rs +index ddac9dbd..7e2a3298 100644 +--- a/boards/nordic/nrf52dk_base/src/lib.rs ++++ b/boards/nordic/nrf52dk_base/src/lib.rs +@@ -105,6 +105,7 @@ pub struct Platform { + // The nRF52dk does not have the flash chip on it, so we make this optional. + nonvolatile_storage: + Option<&'static capsules::nonvolatile_storage_driver::NonvolatileStorage<'static>>, ++ nvmc: &'static nrf52::nvmc::SyscallDriver, + } + + impl kernel::Platform for Platform { +@@ -128,6 +129,7 @@ impl kernel::Platform for Platform { + capsules::nonvolatile_storage_driver::DRIVER_NUM => { + f(self.nonvolatile_storage.map_or(None, |nv| Some(nv))) + } ++ nrf52::nvmc::DRIVER_NUM => f(Some(self.nvmc)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + _ => f(None), + } +@@ -408,6 +410,14 @@ pub unsafe fn setup_board( + None + }; + ++ let nvmc = static_init!( ++ nrf52::nvmc::SyscallDriver, ++ nrf52::nvmc::SyscallDriver::new( ++ &nrf52::nvmc::NVMC, ++ board_kernel.create_grant(&memory_allocation_capability) ++ ) ++ ); ++ + // Start all of the clocks. Low power operation will require a better + // approach than this. + nrf52::clock::CLOCK.low_stop(); +@@ -441,6 +451,7 @@ pub unsafe fn setup_board( + alarm: alarm, + nonvolatile_storage: nonvolatile_storage, + ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability), ++ nvmc: nvmc, + }; + + platform.pconsole.start(); +diff --git a/chips/nrf52/src/nvmc.rs b/chips/nrf52/src/nvmc.rs +index 5abd2d84..5a726fdb 100644 +--- a/chips/nrf52/src/nvmc.rs ++++ b/chips/nrf52/src/nvmc.rs +@@ -3,6 +3,7 @@ + //! Used in order read and write to internal flash. + + use core::cell::Cell; ++use core::convert::TryFrom; + use core::ops::{Index, IndexMut}; + use kernel::common::cells::OptionalCell; + use kernel::common::cells::TakeCell; +@@ -11,7 +12,7 @@ use kernel::common::deferred_call::DeferredCall; + use kernel::common::registers::{register_bitfields, ReadOnly, ReadWrite}; + use kernel::common::StaticRef; + use kernel::hil; +-use kernel::ReturnCode; ++use kernel::{AppId, AppSlice, Callback, Driver, Grant, ReturnCode, Shared}; + + use crate::deferred_call_tasks::DeferredCallTask; + +@@ -141,7 +142,13 @@ register_bitfields! [u32, + static DEFERRED_CALL: DeferredCall = + unsafe { DeferredCall::new(DeferredCallTask::Nvmc) }; + ++type WORD = u32; ++const WORD_SIZE: usize = core::mem::size_of::(); + const PAGE_SIZE: usize = 4096; ++const MAX_WORD_WRITES: usize = 2; ++const MAX_PAGE_ERASES: usize = 10000; ++const WORD_MASK: usize = WORD_SIZE - 1; ++const PAGE_MASK: usize = PAGE_SIZE - 1; + + /// This is a wrapper around a u8 array that is sized to a single page for the + /// nrf. Users of this module must pass an object of this type to use the +@@ -215,6 +222,11 @@ impl Nvmc { + } + } + ++ pub fn configure_readonly(&self) { ++ let regs = &*self.registers; ++ regs.config.write(Configuration::WEN::Ren); ++ } ++ + /// Configure the NVMC to allow writes to flash. + pub fn configure_writeable(&self) { + let regs = &*self.registers; +@@ -230,7 +242,7 @@ impl Nvmc { + let regs = &*self.registers; + regs.config.write(Configuration::WEN::Een); + while !self.is_ready() {} +- regs.erasepage.write(ErasePage::ERASEPAGE.val(0x10001000)); ++ regs.eraseuicr.write(EraseUicr::ERASEUICR::ERASE); + while !self.is_ready() {} + } + +@@ -314,7 +326,7 @@ impl Nvmc { + // Put the NVMC in write mode. + regs.config.write(Configuration::WEN::Wen); + +- for i in (0..data.len()).step_by(4) { ++ for i in (0..data.len()).step_by(WORD_SIZE) { + let word: u32 = (data[i + 0] as u32) << 0 + | (data[i + 1] as u32) << 8 + | (data[i + 2] as u32) << 16 +@@ -374,3 +386,178 @@ impl hil::flash::Flash for Nvmc { + self.erase_page(page_number) + } + } ++ ++/// Provides access to the writeable flash regions of the application. ++/// ++/// The purpose of this driver is to provide low-level access to the embedded flash of nRF52 boards ++/// to allow applications to implement flash-aware (like wear-leveling) data-structures. The driver ++/// only permits applications to operate on their writeable flash regions. The API is blocking since ++/// the CPU is halted during write and erase operations. ++/// ++/// Supported boards: ++/// - nRF52840 (tested) ++/// - nRF52833 ++/// - nRF52811 ++/// - nRF52810 ++/// ++/// The maximum number of writes for the nRF52832 board is not per word but per block (512 bytes) ++/// and as such doesn't exactly fit this API. However, it could be safely supported by returning ++/// either 1 for the maximum number of word writes (i.e. the flash can only be written once before ++/// being erased) or 8 for the word size (i.e. the write granularity is doubled). In both cases, ++/// only 128 writes per block are permitted while the flash supports 181. ++/// ++/// # Syscalls ++/// ++/// - COMMAND(0): Check the driver. ++/// - COMMAND(1, 0): Get the word size (always 4). ++/// - COMMAND(1, 1): Get the page size (always 4096). ++/// - COMMAND(1, 2): Get the maximum number of word writes between page erasures (always 2). ++/// - COMMAND(1, 3): Get the maximum number page erasures in the lifetime of the flash (always ++/// 10000). ++/// - COMMAND(2, ptr): Write the allow slice to the flash region starting at `ptr`. ++/// - `ptr` must be word-aligned. ++/// - The allow slice length must be word aligned. ++/// - The region starting at `ptr` of the same length as the allow slice must be in a writeable ++/// flash region. ++/// - COMMAND(3, ptr): Erase a page. ++/// - `ptr` must be page-aligned. ++/// - The page starting at `ptr` must be in a writeable flash region. ++/// - ALLOW(0): The allow slice for COMMAND(2). ++pub struct SyscallDriver { ++ nvmc: &'static Nvmc, ++ apps: Grant, ++} ++ ++pub const DRIVER_NUM: usize = 0x50003; ++ ++#[derive(Default)] ++pub struct App { ++ /// The allow slice for COMMAND(2). ++ slice: Option>, ++} ++ ++impl SyscallDriver { ++ pub fn new(nvmc: &'static Nvmc, apps: Grant) -> SyscallDriver { ++ SyscallDriver { nvmc, apps } ++ } ++} ++ ++fn is_write_needed(old: u32, new: u32) -> bool { ++ // No need to write if it would not modify the current value. ++ old & new != old ++} ++ ++impl SyscallDriver { ++ /// Writes a word-aligned slice at a word-aligned address. ++ /// ++ /// Words are written only if necessary, i.e. if writing the new value would change the current ++ /// value. This can be used to simplify recovery operations (e.g. if power is lost during a ++ /// write operation). The application doesn't need to check which prefix has already been ++ /// written and may repeat the complete write that was interrupted. ++ /// ++ /// # Safety ++ /// ++ /// The words in this range must have been written less than `MAX_WORD_WRITES` since their last ++ /// page erasure. ++ /// ++ /// # Errors ++ /// ++ /// Fails with `EINVAL` if any of the following conditions does not hold: ++ /// - `ptr` must be word-aligned. ++ /// - `slice.len()` must be word-aligned. ++ /// - The slice starting at `ptr` of length `slice.len()` must fit in a writeable flash region. ++ fn write_slice(&self, appid: AppId, ptr: usize, slice: &[u8]) -> ReturnCode { ++ if !appid.in_writeable_flash_region(ptr, slice.len()) { ++ return ReturnCode::EINVAL; ++ } ++ if ptr & WORD_MASK != 0 || slice.len() & WORD_MASK != 0 { ++ return ReturnCode::EINVAL; ++ } ++ self.nvmc.configure_writeable(); ++ for (i, chunk) in slice.chunks(WORD_SIZE).enumerate() { ++ // `unwrap` cannot fail because `slice.len()` is word-aligned (see above). ++ let val = WORD::from_ne_bytes(<[u8; WORD_SIZE]>::try_from(chunk).unwrap()); ++ let loc = unsafe { &*(ptr as *const VolatileCell).add(i) }; ++ if is_write_needed(loc.get(), val) { ++ loc.set(val); ++ } ++ } ++ while !self.nvmc.is_ready() {} ++ self.nvmc.configure_readonly(); ++ ReturnCode::SUCCESS ++ } ++ ++ /// Erases a page at a page-aligned address. ++ /// ++ /// # Errors ++ /// ++ /// Fails with `EINVAL` if any of the following conditions does not hold: ++ /// - `ptr` must be page-aligned. ++ /// - The slice starting at `ptr` of length `PAGE_SIZE` must fit in a writeable flash region. ++ fn erase_page(&self, appid: AppId, ptr: usize) -> ReturnCode { ++ if !appid.in_writeable_flash_region(ptr, PAGE_SIZE) { ++ return ReturnCode::EINVAL; ++ } ++ if ptr & PAGE_MASK != 0 { ++ return ReturnCode::EINVAL; ++ } ++ self.nvmc.erase_page_helper(ptr / PAGE_SIZE); ++ self.nvmc.configure_readonly(); ++ ReturnCode::SUCCESS ++ } ++} ++ ++impl Driver for SyscallDriver { ++ fn subscribe(&self, _: usize, _: Option, _: AppId) -> ReturnCode { ++ ReturnCode::ENOSUPPORT ++ } ++ ++ fn command(&self, cmd: usize, arg: usize, _: usize, appid: AppId) -> ReturnCode { ++ match (cmd, arg) { ++ (0, _) => ReturnCode::SUCCESS, ++ ++ (1, 0) => ReturnCode::SuccessWithValue { value: WORD_SIZE }, ++ (1, 1) => ReturnCode::SuccessWithValue { value: PAGE_SIZE }, ++ (1, 2) => ReturnCode::SuccessWithValue { ++ value: MAX_WORD_WRITES, ++ }, ++ (1, 3) => ReturnCode::SuccessWithValue { ++ value: MAX_PAGE_ERASES, ++ }, ++ (1, _) => ReturnCode::EINVAL, ++ ++ (2, ptr) => self ++ .apps ++ .enter(appid, |app, _| { ++ let slice = match app.slice.take() { ++ None => return ReturnCode::EINVAL, ++ Some(slice) => slice, ++ }; ++ self.write_slice(appid, ptr, slice.as_ref()) ++ }) ++ .unwrap_or_else(|err| err.into()), ++ ++ (3, ptr) => self.erase_page(appid, ptr), ++ ++ _ => ReturnCode::ENOSUPPORT, ++ } ++ } ++ ++ fn allow( ++ &self, ++ appid: AppId, ++ allow_num: usize, ++ slice: Option>, ++ ) -> ReturnCode { ++ match allow_num { ++ 0 => self ++ .apps ++ .enter(appid, |app, _| { ++ app.slice = slice; ++ ReturnCode::SUCCESS ++ }) ++ .unwrap_or_else(|err| err.into()), ++ _ => ReturnCode::ENOSUPPORT, ++ } ++ } ++} +diff --git a/kernel/src/callback.rs b/kernel/src/callback.rs +index 95b25cc0..a7d8bc5c 100644 +--- a/kernel/src/callback.rs ++++ b/kernel/src/callback.rs +@@ -50,6 +50,31 @@ impl AppId { + (start, end) + }) + } ++ ++ pub fn in_writeable_flash_region(&self, ptr: usize, len: usize) -> bool { ++ self.kernel.process_map_or(false, self.idx, |process| { ++ let ptr = match ptr.checked_sub(process.flash_start() as usize) { ++ None => return false, ++ Some(ptr) => ptr, ++ }; ++ (0..process.number_writeable_flash_regions()).any(|i| { ++ let (region_ptr, region_len) = process.get_writeable_flash_region(i); ++ let region_ptr = region_ptr as usize; ++ let region_len = region_len as usize; ++ // We want to check the 2 following inequalities: ++ // (1) `region_ptr <= ptr` ++ // (2) `ptr + len <= region_ptr + region_len` ++ // However, the second one may overflow written as is. We introduce a third ++ // inequality to solve this issue: ++ // (3) `len <= region_len` ++ // Using this third inequality, we can rewrite the second one as: ++ // (4) `ptr - region_ptr <= region_len - len` ++ // This fourth inequality is equivalent to the second one but doesn't overflow when ++ // the first and third inequalities hold. ++ region_ptr <= ptr && len <= region_len && ptr - region_ptr <= region_len - len ++ }) ++ }) ++ } + } + + /// Type to uniquely identify a callback subscription across all drivers. diff --git a/patches/tock/02-usb.patch b/patches/tock/02-usb.patch new file mode 100644 index 0000000..0ba33d8 --- /dev/null +++ b/patches/tock/02-usb.patch @@ -0,0 +1,1009 @@ +diff --git a/boards/nordic/nrf52840_dongle/src/main.rs b/boards/nordic/nrf52840_dongle/src/main.rs +index 5a9da538..75043d13 100644 +--- a/boards/nordic/nrf52840_dongle/src/main.rs ++++ b/boards/nordic/nrf52840_dongle/src/main.rs +@@ -140,6 +140,7 @@ pub unsafe fn reset_handler() { + FAULT_RESPONSE, + nrf52840::uicr::Regulator0Output::V3_0, + false, ++ &Some(&nrf52840::usbd::USBD), + chip, + ); + } +diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs +index 0b19ea3f..bd71dfbe 100644 +--- a/boards/nordic/nrf52840dk/src/main.rs ++++ b/boards/nordic/nrf52840dk/src/main.rs +@@ -252,6 +252,7 @@ pub unsafe fn reset_handler() { + FAULT_RESPONSE, + nrf52840::uicr::Regulator0Output::DEFAULT, + false, ++ &Some(&nrf52840::usbd::USBD), + chip, + ); + } +diff --git a/boards/nordic/nrf52dk/src/main.rs b/boards/nordic/nrf52dk/src/main.rs +index b49518ff..39f996c4 100644 +--- a/boards/nordic/nrf52dk/src/main.rs ++++ b/boards/nordic/nrf52dk/src/main.rs +@@ -209,6 +209,7 @@ pub unsafe fn reset_handler() { + FAULT_RESPONSE, + nrf52832::uicr::Regulator0Output::DEFAULT, + false, ++ &None, + chip, + ); + } +diff --git a/boards/nordic/nrf52dk_base/src/lib.rs b/boards/nordic/nrf52dk_base/src/lib.rs +index 7e2a3298..d391e455 100644 +--- a/boards/nordic/nrf52dk_base/src/lib.rs ++++ b/boards/nordic/nrf52dk_base/src/lib.rs +@@ -102,6 +102,13 @@ pub struct Platform { + 'static, + capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + >, ++ usb: Option< ++ &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver< ++ 'static, ++ 'static, ++ nrf52::usbd::Usbd<'static>, ++ >, ++ >, + // The nRF52dk does not have the flash chip on it, so we make this optional. + nonvolatile_storage: + Option<&'static capsules::nonvolatile_storage_driver::NonvolatileStorage<'static>>, +@@ -130,6 +137,9 @@ impl kernel::Platform for Platform { + f(self.nonvolatile_storage.map_or(None, |nv| Some(nv))) + } + nrf52::nvmc::DRIVER_NUM => f(Some(self.nvmc)), ++ capsules::usb::usb_ctap::DRIVER_NUM => { ++ f(self.usb.map(|ctap| ctap as &dyn kernel::Driver)) ++ } + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + _ => f(None), + } +@@ -157,6 +167,7 @@ pub unsafe fn setup_board( + app_fault_response: kernel::procs::FaultResponse, + reg_vout: Regulator0Output, + nfc_as_gpios: bool, ++ usb: &Option<&'static nrf52::usbd::Usbd<'static>>, + chip: &'static nrf52::chip::NRF52, + ) { + // Make non-volatile memory writable and activate the reset button +@@ -418,6 +429,44 @@ pub unsafe fn setup_board( + ) + ); + ++ // Configure USB controller if supported ++ let usb_driver: Option< ++ &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver< ++ 'static, ++ 'static, ++ nrf52::usbd::Usbd<'static>, ++ >, ++ > = usb.map(|driver| { ++ let usb_ctap = static_init!( ++ capsules::usb::usbc_ctap_hid::ClientCtapHID< ++ 'static, ++ 'static, ++ nrf52::usbd::Usbd<'static>, ++ >, ++ capsules::usb::usbc_ctap_hid::ClientCtapHID::new(driver) ++ ); ++ driver.set_client(usb_ctap); ++ ++ // Enable power events to be sent to USB controller ++ nrf52::power::POWER.set_usb_client(driver); ++ nrf52::power::POWER.enable_interrupts(); ++ ++ // Configure the USB userspace driver ++ let usb_driver = static_init!( ++ capsules::usb::usb_ctap::CtapUsbSyscallDriver< ++ 'static, ++ 'static, ++ nrf52::usbd::Usbd<'static>, ++ >, ++ capsules::usb::usb_ctap::CtapUsbSyscallDriver::new( ++ usb_ctap, ++ board_kernel.create_grant(&memory_allocation_capability) ++ ) ++ ); ++ usb_ctap.set_client(usb_driver); ++ usb_driver as &'static _ ++ }); ++ + // Start all of the clocks. Low power operation will require a better + // approach than this. + nrf52::clock::CLOCK.low_stop(); +@@ -449,6 +498,7 @@ pub unsafe fn setup_board( + rng: rng, + temp: temp, + alarm: alarm, ++ usb: usb_driver, + nonvolatile_storage: nonvolatile_storage, + ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability), + nvmc: nvmc, +diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs +index 9305e6a7..40466f44 100644 +--- a/capsules/src/driver.rs ++++ b/capsules/src/driver.rs +@@ -24,6 +24,7 @@ pub enum NUM { + Spi = 0x20001, + UsbUser = 0x20005, + I2cMasterSlave = 0x20006, ++ UsbCtap = 0x20009, + + // Radio + BleAdvertising = 0x30000, +diff --git a/capsules/src/usb/mod.rs b/capsules/src/usb/mod.rs +index e5c8d6ad..7af3da2e 100644 +--- a/capsules/src/usb/mod.rs ++++ b/capsules/src/usb/mod.rs +@@ -1,4 +1,6 @@ + pub mod descriptors; ++pub mod usb_ctap; + pub mod usb_user; + pub mod usbc_client; + pub mod usbc_client_ctrl; ++pub mod usbc_ctap_hid; +diff --git a/capsules/src/usb/usb_ctap.rs b/capsules/src/usb/usb_ctap.rs +new file mode 100644 +index 00000000..da3d16d8 +--- /dev/null ++++ b/capsules/src/usb/usb_ctap.rs +@@ -0,0 +1,355 @@ ++use super::usbc_ctap_hid::ClientCtapHID; ++use kernel::hil; ++use kernel::hil::usb::Client; ++use kernel::{AppId, AppSlice, Callback, Driver, Grant, ReturnCode, Shared}; ++ ++/// Syscall number ++use crate::driver; ++pub const DRIVER_NUM: usize = driver::NUM::UsbCtap as usize; ++ ++pub const CTAP_CMD_CHECK: usize = 0; ++pub const CTAP_CMD_CONNECT: usize = 1; ++pub const CTAP_CMD_TRANSMIT: usize = 2; ++pub const CTAP_CMD_RECEIVE: usize = 3; ++pub const CTAP_CMD_TRANSMIT_OR_RECEIVE: usize = 4; ++pub const CTAP_CMD_CANCEL: usize = 5; ++ ++pub const CTAP_ALLOW_TRANSMIT: usize = 1; ++pub const CTAP_ALLOW_RECEIVE: usize = 2; ++pub const CTAP_ALLOW_TRANSMIT_OR_RECEIVE: usize = 3; ++ ++pub const CTAP_SUBSCRIBE_TRANSMIT: usize = 1; ++pub const CTAP_SUBSCRIBE_RECEIVE: usize = 2; ++pub const CTAP_SUBSCRIBE_TRANSMIT_OR_RECEIVE: usize = 3; ++ ++pub const CTAP_CALLBACK_TRANSMITED: usize = 1; ++pub const CTAP_CALLBACK_RECEIVED: usize = 2; ++ ++#[derive(Clone, Copy, PartialEq, Eq)] ++enum Side { ++ Transmit, ++ Receive, ++ TransmitOrReceive, ++} ++ ++impl Side { ++ fn can_transmit(&self) -> bool { ++ match self { ++ Side::Transmit | Side::TransmitOrReceive => true, ++ Side::Receive => false, ++ } ++ } ++ ++ fn can_receive(&self) -> bool { ++ match self { ++ Side::Receive | Side::TransmitOrReceive => true, ++ Side::Transmit => false, ++ } ++ } ++} ++ ++#[derive(Default)] ++pub struct App { ++ // Only one app can be connected to this driver, to avoid needing to route packets among apps. ++ // This field tracks this status. ++ connected: bool, ++ // Currently enabled transaction side. Subscribing to a callback or allowing a buffer ++ // automatically sets the corresponding side. Clearing both the callback and the buffer resets ++ // the side to None. ++ side: Option, ++ callback: Option, ++ buffer: Option>, ++ // Whether the app is waiting for the kernel signaling a packet transfer. ++ waiting: bool, ++} ++ ++impl App { ++ fn check_side(&mut self) { ++ if self.callback.is_none() && self.buffer.is_none() && !self.waiting { ++ self.side = None; ++ } ++ } ++ ++ fn set_side(&mut self, side: Side) -> bool { ++ match self.side { ++ None => { ++ self.side = Some(side); ++ true ++ } ++ Some(app_side) => side == app_side, ++ } ++ } ++ ++ fn is_ready_for_command(&self, side: Side) -> bool { ++ self.buffer.is_some() && self.callback.is_some() && self.side == Some(side) ++ } ++} ++ ++pub trait CtapUsbClient { ++ // Whether this client is ready to receive a packet. This must be checked before calling ++ // packet_received(). ++ fn can_receive_packet(&self) -> bool; ++ ++ // Signal to the client that a packet has been received. ++ fn packet_received(&self, packet: &[u8; 64]); ++ ++ // Signal to the client that a packet has been transmitted. ++ fn packet_transmitted(&self); ++} ++ ++pub struct CtapUsbSyscallDriver<'a, 'b, C: 'a> { ++ usb_client: &'a ClientCtapHID<'a, 'b, C>, ++ apps: Grant, ++} ++ ++impl<'a, 'b, C: hil::usb::UsbController<'a>> CtapUsbSyscallDriver<'a, 'b, C> { ++ pub fn new(usb_client: &'a ClientCtapHID<'a, 'b, C>, apps: Grant) -> Self { ++ CtapUsbSyscallDriver { usb_client, apps } ++ } ++} ++ ++impl<'a, 'b, C: hil::usb::UsbController<'a>> CtapUsbClient for CtapUsbSyscallDriver<'a, 'b, C> { ++ fn can_receive_packet(&self) -> bool { ++ let mut result = false; ++ for app in self.apps.iter() { ++ app.enter(|app, _| { ++ if app.connected { ++ result = app.waiting ++ && app.side.map_or(false, |side| side.can_receive()) ++ && app.buffer.is_some(); ++ } ++ }); ++ } ++ result ++ } ++ ++ fn packet_received(&self, packet: &[u8; 64]) { ++ for app in self.apps.iter() { ++ app.enter(|app, _| { ++ if app.connected && app.waiting && app.side.map_or(false, |side| side.can_receive()) ++ { ++ if let Some(buf) = &mut app.buffer { ++ // Copy the packet to the app's allowed buffer. ++ buf.as_mut().copy_from_slice(packet); ++ app.waiting = false; ++ // Signal to the app that a packet is ready. ++ app.callback ++ .map(|mut cb| cb.schedule(CTAP_CALLBACK_RECEIVED, 0, 0)); ++ } ++ } ++ }); ++ } ++ } ++ ++ fn packet_transmitted(&self) { ++ for app in self.apps.iter() { ++ app.enter(|app, _| { ++ if app.connected ++ && app.waiting ++ && app.side.map_or(false, |side| side.can_transmit()) ++ { ++ app.waiting = false; ++ // Signal to the app that the packet was sent. ++ app.callback ++ .map(|mut cb| cb.schedule(CTAP_CALLBACK_TRANSMITED, 0, 0)); ++ } ++ }); ++ } ++ } ++} ++ ++impl<'a, 'b, C: hil::usb::UsbController<'a>> Driver for CtapUsbSyscallDriver<'a, 'b, C> { ++ fn allow( ++ &self, ++ appid: AppId, ++ allow_num: usize, ++ slice: Option>, ++ ) -> ReturnCode { ++ let side = match allow_num { ++ CTAP_ALLOW_TRANSMIT => Side::Transmit, ++ CTAP_ALLOW_RECEIVE => Side::Receive, ++ CTAP_ALLOW_TRANSMIT_OR_RECEIVE => Side::TransmitOrReceive, ++ _ => return ReturnCode::ENOSUPPORT, ++ }; ++ self.apps ++ .enter(appid, |app, _| { ++ if !app.connected { ++ ReturnCode::ERESERVE ++ } else { ++ if let Some(buf) = &slice { ++ if buf.len() != 64 { ++ return ReturnCode::EINVAL; ++ } ++ } ++ if !app.set_side(side) { ++ return ReturnCode::EALREADY; ++ } ++ app.buffer = slice; ++ app.check_side(); ++ ReturnCode::SUCCESS ++ } ++ }) ++ .unwrap_or_else(|err| err.into()) ++ } ++ ++ fn subscribe( ++ &self, ++ subscribe_num: usize, ++ callback: Option, ++ appid: AppId, ++ ) -> ReturnCode { ++ let side = match subscribe_num { ++ CTAP_SUBSCRIBE_TRANSMIT => Side::Transmit, ++ CTAP_SUBSCRIBE_RECEIVE => Side::Receive, ++ CTAP_SUBSCRIBE_TRANSMIT_OR_RECEIVE => Side::TransmitOrReceive, ++ _ => return ReturnCode::ENOSUPPORT, ++ }; ++ self.apps ++ .enter(appid, |app, _| { ++ if !app.connected { ++ ReturnCode::ERESERVE ++ } else { ++ if !app.set_side(side) { ++ return ReturnCode::EALREADY; ++ } ++ app.callback = callback; ++ app.check_side(); ++ ReturnCode::SUCCESS ++ } ++ }) ++ .unwrap_or_else(|err| err.into()) ++ } ++ ++ fn command(&self, cmd_num: usize, _arg1: usize, _arg2: usize, appid: AppId) -> ReturnCode { ++ match cmd_num { ++ CTAP_CMD_CHECK => ReturnCode::SUCCESS, ++ CTAP_CMD_CONNECT => { ++ // First, check if any app is already connected to this driver. ++ let mut busy = false; ++ for app in self.apps.iter() { ++ app.enter(|app, _| { ++ busy |= app.connected; ++ }); ++ } ++ ++ self.apps ++ .enter(appid, |app, _| { ++ if app.connected { ++ ReturnCode::EALREADY ++ } else if busy { ++ ReturnCode::EBUSY ++ } else { ++ self.usb_client.enable(); ++ self.usb_client.attach(); ++ app.connected = true; ++ ReturnCode::SUCCESS ++ } ++ }) ++ .unwrap_or_else(|err| err.into()) ++ } ++ CTAP_CMD_TRANSMIT => self ++ .apps ++ .enter(appid, |app, _| { ++ if !app.connected { ++ ReturnCode::ERESERVE ++ } else { ++ if app.is_ready_for_command(Side::Transmit) { ++ if app.waiting { ++ ReturnCode::EALREADY ++ } else if self ++ .usb_client ++ .transmit_packet(app.buffer.as_ref().unwrap().as_ref()) ++ { ++ app.waiting = true; ++ ReturnCode::SUCCESS ++ } else { ++ ReturnCode::EBUSY ++ } ++ } else { ++ ReturnCode::EINVAL ++ } ++ } ++ }) ++ .unwrap_or_else(|err| err.into()), ++ CTAP_CMD_RECEIVE => self ++ .apps ++ .enter(appid, |app, _| { ++ if !app.connected { ++ ReturnCode::ERESERVE ++ } else { ++ if app.is_ready_for_command(Side::Receive) { ++ if app.waiting { ++ ReturnCode::EALREADY ++ } else { ++ app.waiting = true; ++ self.usb_client.receive_packet(); ++ ReturnCode::SUCCESS ++ } ++ } else { ++ ReturnCode::EINVAL ++ } ++ } ++ }) ++ .unwrap_or_else(|err| err.into()), ++ CTAP_CMD_TRANSMIT_OR_RECEIVE => self ++ .apps ++ .enter(appid, |app, _| { ++ if !app.connected { ++ ReturnCode::ERESERVE ++ } else { ++ if app.is_ready_for_command(Side::TransmitOrReceive) { ++ if app.waiting { ++ ReturnCode::EALREADY ++ } else { ++ // Indicates to the driver that we can receive any pending packet. ++ app.waiting = true; ++ self.usb_client.receive_packet(); ++ ++ if !app.waiting { ++ // The call to receive_packet() collected a pending packet. ++ ReturnCode::SUCCESS ++ } else { ++ // Indicates to the driver that we have a packet to send. ++ if self ++ .usb_client ++ .transmit_packet(app.buffer.as_ref().unwrap().as_ref()) ++ { ++ ReturnCode::SUCCESS ++ } else { ++ ReturnCode::EBUSY ++ } ++ } ++ } ++ } else { ++ ReturnCode::EINVAL ++ } ++ } ++ }) ++ .unwrap_or_else(|err| err.into()), ++ CTAP_CMD_CANCEL => self ++ .apps ++ .enter(appid, |app, _| { ++ if !app.connected { ++ ReturnCode::ERESERVE ++ } else { ++ if app.waiting { ++ // FIXME: if cancellation failed, the app should still wait. But that ++ // doesn't work yet. ++ app.waiting = false; ++ if self.usb_client.cancel_transaction() { ++ ReturnCode::SUCCESS ++ } else { ++ // Cannot cancel now because the transaction is already in process. ++ // The app should wait for the callback instead. ++ ReturnCode::EBUSY ++ } ++ } else { ++ ReturnCode::EALREADY ++ } ++ } ++ }) ++ .unwrap_or_else(|err| err.into()), ++ _ => ReturnCode::ENOSUPPORT, ++ } ++ } ++} +diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs +new file mode 100644 +index 00000000..fdf7263a +--- /dev/null ++++ b/capsules/src/usb/usbc_ctap_hid.rs +@@ -0,0 +1,352 @@ ++//! A USB HID client of the USB hardware interface ++ ++use super::descriptors::Buffer64; ++use super::descriptors::ConfigurationDescriptor; ++use super::descriptors::DescriptorType; ++use super::descriptors::DeviceDescriptor; ++use super::descriptors::EndpointAddress; ++use super::descriptors::EndpointDescriptor; ++use super::descriptors::HIDCountryCode; ++use super::descriptors::HIDDescriptor; ++use super::descriptors::HIDSubordinateDescriptor; ++use super::descriptors::InterfaceDescriptor; ++use super::descriptors::ReportDescriptor; ++use super::descriptors::TransferDirection; ++use super::usb_ctap::CtapUsbClient; ++use super::usbc_client_ctrl::ClientCtrl; ++use core::cell::Cell; ++use kernel::common::cells::OptionalCell; ++use kernel::debug; ++use kernel::hil; ++use kernel::hil::usb::TransferType; ++ ++const VENDOR_ID: u16 = 0x1915; // Nordic Semiconductor ++const PRODUCT_ID: u16 = 0x521f; // nRF52840 Dongle (PCA10059) ++ ++static LANGUAGES: &'static [u16; 1] = &[ ++ 0x0409, // English (United States) ++]; ++ ++static STRINGS: &'static [&'static str] = &[ ++ // Manufacturer ++ "Nordic Semiconductor ASA", ++ // Product ++ "OpenSK", ++ // Serial number ++ "v0.1", ++]; ++ ++static ENDPOINTS: &'static [EndpointDescriptor] = &[ ++ EndpointDescriptor { ++ endpoint_address: EndpointAddress::new_const(1, TransferDirection::HostToDevice), ++ transfer_type: TransferType::Interrupt, ++ max_packet_size: 64, ++ interval: 5, ++ }, ++ EndpointDescriptor { ++ endpoint_address: EndpointAddress::new_const(1, TransferDirection::DeviceToHost), ++ transfer_type: TransferType::Interrupt, ++ max_packet_size: 64, ++ interval: 5, ++ }, ++]; ++ ++static CTAP_REPORT_DESCRIPTOR: &'static [u8] = &[ ++ 0x06, 0xD0, 0xF1, // HID_UsagePage ( FIDO_USAGE_PAGE ), ++ 0x09, 0x01, // HID_Usage ( FIDO_USAGE_CTAPHID ), ++ 0xA1, 0x01, // HID_Collection ( HID_Application ), ++ 0x09, 0x20, // HID_Usage ( FIDO_USAGE_DATA_IN ), ++ 0x15, 0x00, // HID_LogicalMin ( 0 ), ++ 0x26, 0xFF, 0x00, // HID_LogicalMaxS ( 0xff ), ++ 0x75, 0x08, // HID_ReportSize ( 8 ), ++ 0x95, 0x40, // HID_ReportCount ( HID_INPUT_REPORT_BYTES ), ++ 0x81, 0x02, // HID_Input ( HID_Data | HID_Absolute | HID_Variable ), ++ 0x09, 0x21, // HID_Usage ( FIDO_USAGE_DATA_OUT ), ++ 0x15, 0x00, // HID_LogicalMin ( 0 ), ++ 0x26, 0xFF, 0x00, // HID_LogicalMaxS ( 0xff ), ++ 0x75, 0x08, // HID_ReportSize ( 8 ), ++ 0x95, 0x40, // HID_ReportCount ( HID_OUTPUT_REPORT_BYTES ), ++ 0x91, 0x02, // HID_Output ( HID_Data | HID_Absolute | HID_Variable ), ++ 0xC0, // HID_EndCollection ++]; ++ ++static CTAP_REPORT: ReportDescriptor<'static> = ReportDescriptor { ++ desc: CTAP_REPORT_DESCRIPTOR, ++}; ++ ++static HID_SUB_DESCRIPTORS: &'static [HIDSubordinateDescriptor] = &[HIDSubordinateDescriptor { ++ typ: DescriptorType::Report, ++ len: CTAP_REPORT_DESCRIPTOR.len() as u16, ++}]; ++ ++static HID: HIDDescriptor<'static> = HIDDescriptor { ++ hid_class: 0x0110, ++ country_code: HIDCountryCode::NotSupported, ++ sub_descriptors: HID_SUB_DESCRIPTORS, ++}; ++ ++pub struct ClientCtapHID<'a, 'b, C: 'a> { ++ client_ctrl: ClientCtrl<'a, 'static, C>, ++ ++ // A 64-byte buffer for the endpoint ++ buffer: Buffer64, ++ ++ // Interaction with the client ++ client: OptionalCell<&'b dyn CtapUsbClient>, ++ tx_packet: OptionalCell<[u8; 64]>, ++ pending_in: Cell, ++ pending_out: Cell, ++ delayed_out: Cell, ++} ++ ++impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtapHID<'a, 'b, C> { ++ pub fn new(controller: &'a C) -> Self { ++ ClientCtapHID { ++ client_ctrl: ClientCtrl::new( ++ controller, ++ DeviceDescriptor { ++ // TODO: set this field at the board level. ++ max_packet_size_ep0: 64, ++ vendor_id: VENDOR_ID, ++ product_id: PRODUCT_ID, ++ manufacturer_string: 1, ++ product_string: 2, ++ serial_number_string: 3, ++ ..Default::default() ++ }, ++ ConfigurationDescriptor { ++ // Must be non-zero, otherwise dmesg prints the following error: ++ // [...] usb 2-3: config 0 descriptor?? ++ configuration_value: 1, ++ ..Default::default() ++ }, ++ // Interface declared in the FIDO2 specification, section 8.1.8.1 ++ InterfaceDescriptor { ++ interface_class: 0x03, // HID ++ interface_subclass: 0x00, ++ interface_protocol: 0x00, ++ ..Default::default() ++ }, ++ ENDPOINTS, ++ Some(&HID), ++ Some(&CTAP_REPORT), ++ LANGUAGES, ++ STRINGS, ++ ), ++ buffer: Default::default(), ++ client: OptionalCell::empty(), ++ tx_packet: OptionalCell::empty(), ++ pending_in: Cell::new(false), ++ pending_out: Cell::new(false), ++ delayed_out: Cell::new(false), ++ } ++ } ++ ++ pub fn set_client(&'a self, client: &'b dyn CtapUsbClient) { ++ self.client.set(client); ++ } ++ ++ pub fn transmit_packet(&'a self, packet: &[u8]) -> bool { ++ if self.pending_in.get() { ++ // The previous packet has not yet been transmitted, reject the new one. ++ false ++ } else { ++ self.pending_in.set(true); ++ let mut buf: [u8; 64] = [0; 64]; ++ buf.copy_from_slice(packet); ++ self.tx_packet.set(buf); ++ // Alert the controller that we now have data to send on the Interrupt IN endpoint. ++ self.controller().endpoint_resume_in(1); ++ true ++ } ++ } ++ ++ pub fn receive_packet(&'a self) -> bool { ++ if self.pending_out.get() { ++ // The previous packet has not yet been received, reject the new one. ++ false ++ } else { ++ self.pending_out.set(true); ++ // In case we reported Delay before, send the pending packet back to the client. ++ // Otherwise, there's nothing to do, the controller will send us a packet_out when a ++ // packet arrives. ++ if self.delayed_out.take() { ++ if self.send_packet_to_client() { ++ // If that succeeds, alert the controller that we can now ++ // receive data on the Interrupt OUT endpoint. ++ self.controller().endpoint_resume_out(1); ++ } ++ } ++ true ++ } ++ } ++ ++ // Send an OUT packet available in the controller back to the client. ++ // This returns false if the client is not ready to receive a packet, and true if the client ++ // successfully accepted the packet. ++ fn send_packet_to_client(&'a self) -> bool { ++ // Copy the packet into a buffer to send to the client. ++ let mut buf: [u8; 64] = [0; 64]; ++ for (i, x) in self.buffer.buf.iter().enumerate() { ++ buf[i] = x.get(); ++ } ++ ++ assert!(!self.delayed_out.get()); ++ ++ // Notify the client ++ if self ++ .client ++ .map_or(false, |client| client.can_receive_packet()) ++ { ++ assert!(self.pending_out.take()); ++ ++ // Clear any pending packet on the transmitting side. ++ // It's up to the client to handle the received packet and decide if this packet ++ // should be re-transmitted or not. ++ self.cancel_in_transaction(); ++ ++ self.client.map(|client| client.packet_received(&buf)); ++ true ++ } else { ++ // Cannot receive now, indicate a delay to the controller. ++ self.delayed_out.set(true); ++ false ++ } ++ } ++ ++ pub fn cancel_transaction(&'a self) -> bool { ++ self.cancel_in_transaction() | self.cancel_out_transaction() ++ } ++ ++ fn cancel_in_transaction(&'a self) -> bool { ++ self.tx_packet.take(); ++ let result = self.pending_in.take(); ++ if result { ++ self.controller().endpoint_cancel_in(1); ++ } ++ result ++ } ++ ++ fn cancel_out_transaction(&'a self) -> bool { ++ self.pending_out.take() ++ } ++ ++ #[inline] ++ fn controller(&'a self) -> &'a C { ++ self.client_ctrl.controller() ++ } ++} ++ ++impl<'a, 'b, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for ClientCtapHID<'a, 'b, C> { ++ fn enable(&'a self) { ++ // Set up the default control endpoint ++ self.client_ctrl.enable(); ++ ++ // Set up the interrupt in-out endpoint ++ self.controller().endpoint_set_buffer(1, &self.buffer.buf); ++ self.controller() ++ .endpoint_in_out_enable(TransferType::Interrupt, 1); ++ } ++ ++ fn attach(&'a self) { ++ self.client_ctrl.attach(); ++ } ++ ++ fn bus_reset(&'a self) { ++ // Should the client initiate reconfiguration here? ++ // For now, the hardware layer does it. ++ ++ debug!("Bus reset"); ++ } ++ ++ /// Handle a Control Setup transaction ++ fn ctrl_setup(&'a self, endpoint: usize) -> hil::usb::CtrlSetupResult { ++ self.client_ctrl.ctrl_setup(endpoint) ++ } ++ ++ /// Handle a Control In transaction ++ fn ctrl_in(&'a self, endpoint: usize) -> hil::usb::CtrlInResult { ++ self.client_ctrl.ctrl_in(endpoint) ++ } ++ ++ /// Handle a Control Out transaction ++ fn ctrl_out(&'a self, endpoint: usize, packet_bytes: u32) -> hil::usb::CtrlOutResult { ++ self.client_ctrl.ctrl_out(endpoint, packet_bytes) ++ } ++ ++ fn ctrl_status(&'a self, endpoint: usize) { ++ self.client_ctrl.ctrl_status(endpoint) ++ } ++ ++ /// Handle the completion of a Control transfer ++ fn ctrl_status_complete(&'a self, endpoint: usize) { ++ self.client_ctrl.ctrl_status_complete(endpoint) ++ } ++ ++ /// Handle a Bulk/Interrupt IN transaction ++ fn packet_in(&'a self, transfer_type: TransferType, endpoint: usize) -> hil::usb::InResult { ++ match transfer_type { ++ TransferType::Bulk => hil::usb::InResult::Error, ++ TransferType::Interrupt => { ++ if endpoint != 1 { ++ return hil::usb::InResult::Error; ++ } ++ ++ if let Some(packet) = self.tx_packet.take() { ++ let buf = &self.buffer.buf; ++ for i in 0..64 { ++ buf[i].set(packet[i]); ++ } ++ ++ hil::usb::InResult::Packet(64) ++ } else { ++ // Nothing to send ++ hil::usb::InResult::Delay ++ } ++ } ++ TransferType::Control | TransferType::Isochronous => unreachable!(), ++ } ++ } ++ ++ /// Handle a Bulk/Interrupt OUT transaction ++ fn packet_out( ++ &'a self, ++ transfer_type: TransferType, ++ endpoint: usize, ++ packet_bytes: u32, ++ ) -> hil::usb::OutResult { ++ match transfer_type { ++ TransferType::Bulk => hil::usb::OutResult::Error, ++ TransferType::Interrupt => { ++ if endpoint != 1 { ++ return hil::usb::OutResult::Error; ++ } ++ ++ if packet_bytes != 64 { ++ // Cannot process this packet ++ hil::usb::OutResult::Error ++ } else { ++ if self.send_packet_to_client() { ++ hil::usb::OutResult::Ok ++ } else { ++ hil::usb::OutResult::Delay ++ } ++ } ++ } ++ TransferType::Control | TransferType::Isochronous => unreachable!(), ++ } ++ } ++ ++ fn packet_transmitted(&'a self, endpoint: usize) { ++ if endpoint != 1 { ++ panic!("Unexpected transmission on ep {}", endpoint); ++ } ++ ++ if self.tx_packet.is_some() { ++ panic!("Unexpected tx_packet while a packet was being transmitted."); ++ } ++ self.pending_in.set(false); ++ // Notify the client ++ self.client.map(|client| client.packet_transmitted()); ++ } ++} +diff --git a/chips/nrf52/src/usbd.rs b/chips/nrf52/src/usbd.rs +index 8ddb5895..8c1992cc 100644 +--- a/chips/nrf52/src/usbd.rs ++++ b/chips/nrf52/src/usbd.rs +@@ -1499,7 +1499,23 @@ impl<'a> Usbd<'a> { + if epdatastatus.is_set(status_epin(endpoint)) { + let (transfer_type, direction, state) = + self.descriptors[endpoint].state.get().bulk_state(); +- assert_eq!(state, BulkState::InData); ++ match state { ++ BulkState::InData => { ++ // Totally expected state. Nothing to do. ++ } ++ BulkState::Init => { ++ internal_warn!( ++ "Received a stale epdata IN in an unexpected state: {:?}", ++ state ++ ); ++ } ++ BulkState::OutDelay ++ | BulkState::OutData ++ | BulkState::OutDma ++ | BulkState::InDma => { ++ internal_err!("Unexpected state: {:?}", state); ++ } ++ } + self.descriptors[endpoint].state.set(EndpointState::Bulk( + transfer_type, + direction, +@@ -1677,7 +1693,7 @@ impl<'a> Usbd<'a> { + } + + fn transmit_in(&self, endpoint: usize) { +- debug_info!("transmit_in({})", endpoint); ++ debug_events!("transmit_in({})", endpoint); + let regs = &*self.registers; + + self.client.map(|client| { +@@ -1717,7 +1733,7 @@ impl<'a> Usbd<'a> { + } + + fn transmit_out(&self, endpoint: usize) { +- debug_info!("transmit_out({})", endpoint); ++ debug_events!("transmit_out({})", endpoint); + + let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state(); + // Starting the DMA can only happen in the OutData state, i.e. after an EPDATA event. +@@ -1882,11 +1898,13 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { + } + + fn endpoint_resume_in(&self, endpoint: usize) { ++ debug_events!("endpoint_resume_in({})", endpoint); ++ + let (_, direction, _) = self.descriptors[endpoint].state.get().bulk_state(); + assert!(direction.has_in()); + + if self.dma_pending.get() { +- debug_info!("requesting resume_in[{}]", endpoint); ++ debug_events!("requesting resume_in[{}]", endpoint); + // A DMA is already pending. Schedule the resume for later. + self.descriptors[endpoint].request_transmit_in.set(true); + } else { +@@ -1896,6 +1914,8 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { + } + + fn endpoint_resume_out(&self, endpoint: usize) { ++ debug_events!("endpoint_resume_out({})", endpoint); ++ + let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state(); + assert!(direction.has_out()); + +@@ -1914,7 +1934,7 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { + // happened in the meantime. This pending transaction will now + // continue in transmit_out(). + if self.dma_pending.get() { +- debug_info!("requesting resume_out[{}]", endpoint); ++ debug_events!("requesting resume_out[{}]", endpoint); + // A DMA is already pending. Schedule the resume for later. + self.descriptors[endpoint].request_transmit_out.set(true); + } else { +@@ -1927,6 +1947,20 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> { + } + } + } ++ ++ fn endpoint_cancel_in(&self, endpoint: usize) { ++ debug_events!("endpoint_cancel_in({})", endpoint); ++ ++ let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state(); ++ assert!(direction.has_in()); ++ assert_eq!(state, BulkState::InData); ++ ++ self.descriptors[endpoint].state.set(EndpointState::Bulk( ++ transfer_type, ++ direction, ++ BulkState::Init, ++ )); ++ } + } + + fn status_epin(ep: usize) -> Field { +diff --git a/chips/nrf52840/src/lib.rs b/chips/nrf52840/src/lib.rs +index 0c281276..7e9f7d1e 100644 +--- a/chips/nrf52840/src/lib.rs ++++ b/chips/nrf52840/src/lib.rs +@@ -2,7 +2,7 @@ + + pub use nrf52::{ + adc, aes, ble_radio, clock, constants, crt1, ficr, i2c, ieee802154_radio, init, nvmc, pinmux, +- ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, ++ ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, usbd, + }; + pub mod chip; + pub mod gpio; +diff --git a/chips/sam4l/src/usbc/mod.rs b/chips/sam4l/src/usbc/mod.rs +index 35f3bb7c..28a0b9f9 100644 +--- a/chips/sam4l/src/usbc/mod.rs ++++ b/chips/sam4l/src/usbc/mod.rs +@@ -1547,6 +1547,10 @@ impl hil::usb::UsbController<'a> for Usbc<'a> { + requests.resume_out = true; + self.requests[endpoint].set(requests); + } ++ ++ fn endpoint_cancel_in(&self, _endpoint: usize) { ++ unimplemented!() ++ } + } + + /// Static state to manage the USBC +diff --git a/kernel/src/hil/usb.rs b/kernel/src/hil/usb.rs +index 846f5e93..64610fa5 100644 +--- a/kernel/src/hil/usb.rs ++++ b/kernel/src/hil/usb.rs +@@ -27,6 +27,8 @@ pub trait UsbController<'a> { + fn endpoint_resume_in(&self, endpoint: usize); + + fn endpoint_resume_out(&self, endpoint: usize); ++ ++ fn endpoint_cancel_in(&self, endpoint: usize); + } + + #[derive(Clone, Copy, Debug)] diff --git a/patches/tock/03-app-memory.patch b/patches/tock/03-app-memory.patch new file mode 100644 index 0000000..355375c --- /dev/null +++ b/patches/tock/03-app-memory.patch @@ -0,0 +1,13 @@ +diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs +index bd71dfbe..e8247905 100644 +--- a/boards/nordic/nrf52840dk/src/main.rs ++++ b/boards/nordic/nrf52840dk/src/main.rs +@@ -113,7 +113,7 @@ const FAULT_RESPONSE: kernel::procs::FaultResponse = kernel::procs::FaultRespons + const NUM_PROCS: usize = 8; + + #[link_section = ".app_memory"] +-static mut APP_MEMORY: [u8; 245760] = [0; 245760]; ++static mut APP_MEMORY: [u8; 0x3A000] = [0; 0x3A000]; + + static mut PROCESSES: [Option<&'static dyn kernel::procs::ProcessType>; NUM_PROCS] = + [None, None, None, None, None, None, None, None]; diff --git a/patches/tock/04-rtt.patch b/patches/tock/04-rtt.patch new file mode 100644 index 0000000..70bb382 --- /dev/null +++ b/patches/tock/04-rtt.patch @@ -0,0 +1,522 @@ +diff --git a/boards/acd52832/src/main.rs b/boards/acd52832/src/main.rs +index c844ccaf..d60bbc00 100644 +--- a/boards/acd52832/src/main.rs ++++ b/boards/acd52832/src/main.rs +@@ -304,23 +304,26 @@ pub unsafe fn reset_handler() { + ); + + // RTT communication channel ++ let name = b"Terminal\0"; ++ let up_buffer_name = name; ++ let down_buffer_name = name; ++ let up_buffer = static_init!([u8; 1024], [0; 1024]); ++ let down_buffer = static_init!([u8; 32], [0; 32]); ++ + let rtt_memory = static_init!( + capsules::segger_rtt::SeggerRttMemory, +- capsules::segger_rtt::SeggerRttMemory::new( +- b"Terminal\0", +- &mut capsules::segger_rtt::UP_BUFFER, +- b"Terminal\0", +- &mut capsules::segger_rtt::DOWN_BUFFER ++ capsules::segger_rtt::SeggerRttMemory::new_raw( ++ up_buffer_name, ++ up_buffer.as_ptr(), ++ up_buffer.len(), ++ down_buffer_name, ++ down_buffer.as_ptr(), ++ down_buffer.len() + ) + ); + let rtt = static_init!( + capsules::segger_rtt::SeggerRtt>, +- capsules::segger_rtt::SeggerRtt::new( +- virtual_alarm_rtt, +- rtt_memory, +- &mut capsules::segger_rtt::UP_BUFFER, +- &mut capsules::segger_rtt::DOWN_BUFFER +- ) ++ capsules::segger_rtt::SeggerRtt::new(virtual_alarm_rtt, rtt_memory, up_buffer, down_buffer) + ); + hil::time::Alarm::set_client(virtual_alarm_rtt, rtt); + +diff --git a/boards/nordic/nrf52840_dongle/src/main.rs b/boards/nordic/nrf52840_dongle/src/main.rs +index 31d0b392..5a9da538 100644 +--- a/boards/nordic/nrf52840_dongle/src/main.rs ++++ b/boards/nordic/nrf52840_dongle/src/main.rs +@@ -11,7 +11,7 @@ use kernel::component::Component; + #[allow(unused_imports)] + use kernel::{debug, debug_gpio, debug_verbose, static_init}; + use nrf52840::gpio::Pin; +-use nrf52dk_base::{SpiPins, UartPins}; ++use nrf52dk_base::{SpiPins, UartChannel, UartPins}; + + // The nRF52840 Dongle LEDs + const LED1_PIN: Pin = Pin::P0_06; +@@ -130,7 +130,7 @@ pub unsafe fn reset_handler() { + LED2_G_PIN, + LED2_B_PIN, + led, +- &UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD), ++ UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)), + &SpiPins::new(SPI_MOSI, SPI_MISO, SPI_CLK), + &None, + button, +diff --git a/boards/nordic/nrf52840dk/Cargo.toml b/boards/nordic/nrf52840dk/Cargo.toml +index 171c497f..9d16f679 100644 +--- a/boards/nordic/nrf52840dk/Cargo.toml ++++ b/boards/nordic/nrf52840dk/Cargo.toml +@@ -17,6 +17,10 @@ lto = true + opt-level = "z" + debug = true + ++[features] ++usb_debugging = [] ++default = ["usb_debugging"] ++ + [dependencies] + components = { path = "../../components" } + cortexm4 = { path = "../../../arch/cortex-m4" } +diff --git a/boards/nordic/nrf52840dk/src/io.rs b/boards/nordic/nrf52840dk/src/io.rs +index 4c4f3bf3..608fa9ca 100644 +--- a/boards/nordic/nrf52840dk/src/io.rs ++++ b/boards/nordic/nrf52840dk/src/io.rs +@@ -1,19 +1,46 @@ + use core::fmt::Write; + use core::panic::PanicInfo; + use cortexm4; ++#[cfg(feature = "usb_debugging")] ++use kernel::common::cells::TakeCell; + use kernel::debug; + use kernel::debug::IoWrite; + use kernel::hil::led; ++#[cfg(not(feature = "usb_debugging"))] + use kernel::hil::uart::{self, Configure}; + use nrf52840::gpio::Pin; + + use crate::PROCESSES; + + struct Writer { ++ #[cfg(not(feature = "usb_debugging"))] + initialized: bool, ++ #[cfg(feature = "usb_debugging")] ++ rtt_memory: TakeCell<'static, capsules::segger_rtt::SeggerRttMemory<'static>>, + } + ++#[cfg(not(feature = "usb_debugging"))] + static mut WRITER: Writer = Writer { initialized: false }; ++#[cfg(feature = "usb_debugging")] ++static mut WRITER: Writer = Writer { ++ rtt_memory: TakeCell::empty(), ++}; ++ ++#[cfg(feature = "usb_debugging")] ++fn wait() { ++ let mut x = 0; ++ for i in 0..5000 { ++ unsafe { core::ptr::write_volatile(&mut x as *mut _, i) }; ++ } ++} ++ ++/// Set the RTT memory buffer used to output panic messages. ++#[cfg(feature = "usb_debugging")] ++pub unsafe fn set_rtt_memory( ++ rtt_memory: &'static mut capsules::segger_rtt::SeggerRttMemory<'static>, ++) { ++ WRITER.rtt_memory.replace(rtt_memory); ++} + + impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { +@@ -23,6 +50,7 @@ impl Write for Writer { + } + + impl IoWrite for Writer { ++ #[cfg(not(feature = "usb_debugging"))] + fn write(&mut self, buf: &[u8]) { + let uart = unsafe { &mut nrf52840::uart::UARTE0 }; + if !self.initialized { +@@ -42,6 +70,30 @@ impl IoWrite for Writer { + while !uart.tx_ready() {} + } + } ++ ++ #[cfg(feature = "usb_debugging")] ++ fn write(&mut self, buf: &[u8]) { ++ // TODO: initialize if needed. ++ self.rtt_memory.map(|rtt_memory| { ++ let up_buffer = &mut rtt_memory.up_buffer; ++ let buffer_len = up_buffer.length.get(); ++ let buffer = unsafe { ++ core::slice::from_raw_parts_mut( ++ up_buffer.buffer.get() as *mut u8, ++ buffer_len as usize, ++ ) ++ }; ++ ++ let mut write_position = up_buffer.write_position.get(); ++ ++ for &c in buf { ++ buffer[write_position as usize] = c; ++ write_position = (write_position + 1) % buffer_len; ++ up_buffer.write_position.set(write_position); ++ wait(); ++ } ++ }); ++ } + } + + #[cfg(not(test))] +diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs +index 57c42436..0b19ea3f 100644 +--- a/boards/nordic/nrf52840dk/src/main.rs ++++ b/boards/nordic/nrf52840dk/src/main.rs +@@ -68,7 +68,9 @@ use kernel::component::Component; + #[allow(unused_imports)] + use kernel::{debug, debug_gpio, debug_verbose, static_init}; + use nrf52840::gpio::Pin; +-use nrf52dk_base::{SpiMX25R6435FPins, SpiPins, UartPins}; ++#[cfg(not(feature = "usb_debugging"))] ++use nrf52dk_base::UartPins; ++use nrf52dk_base::{SpiMX25R6435FPins, SpiPins, UartChannel}; + + // The nRF52840DK LEDs (see back of board) + const LED1_PIN: Pin = Pin::P0_13; +@@ -83,9 +85,13 @@ const BUTTON3_PIN: Pin = Pin::P0_24; + const BUTTON4_PIN: Pin = Pin::P0_25; + const BUTTON_RST_PIN: Pin = Pin::P0_18; + ++#[cfg(not(feature = "usb_debugging"))] + const UART_RTS: Pin = Pin::P0_05; ++#[cfg(not(feature = "usb_debugging"))] + const UART_TXD: Pin = Pin::P0_06; ++#[cfg(not(feature = "usb_debugging"))] + const UART_CTS: Pin = Pin::P0_07; ++#[cfg(not(feature = "usb_debugging"))] + const UART_RXD: Pin = Pin::P0_08; + + const SPI_MOSI: Pin = Pin::P0_20; +@@ -123,6 +129,37 @@ pub unsafe fn reset_handler() { + // Loads relocations and clears BSS + nrf52840::init(); + ++ // Initialize Segger RTT as early as possible so that any panic beyond this point can use the ++ // RTT memory object. ++ #[cfg(feature = "usb_debugging")] ++ let (up_buffer, down_buffer, rtt_memory) = { ++ let name = b"Terminal\0"; ++ let up_buffer_name = name; ++ let down_buffer_name = name; ++ let up_buffer = static_init!([u8; 1024], [0; 1024]); ++ let down_buffer = static_init!([u8; 32], [0; 32]); ++ ++ let rtt_memory = static_init!( ++ capsules::segger_rtt::SeggerRttMemory, ++ capsules::segger_rtt::SeggerRttMemory::new_raw( ++ up_buffer_name, ++ up_buffer.as_ptr(), ++ up_buffer.len(), ++ down_buffer_name, ++ down_buffer.as_ptr(), ++ down_buffer.len() ++ ) ++ ); ++ ++ (up_buffer, down_buffer, rtt_memory) ++ }; ++ ++ // XXX: This is inherently unsafe as it aliases the mutable reference to rtt_memory. This ++ // aliases reference is only used inside a panic handler, which should be OK, but maybe we ++ // should use a const reference to rtt_memory and leverage interior mutability instead. ++ #[cfg(feature = "usb_debugging")] ++ self::io::set_rtt_memory(&mut *(rtt_memory as *mut _)); ++ + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + let gpio = components::gpio::GpioComponent::new(board_kernel).finalize( + components::gpio_component_helper!( +@@ -198,7 +235,10 @@ pub unsafe fn reset_handler() { + LED2_PIN, + LED3_PIN, + led, +- &UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD), ++ #[cfg(feature = "usb_debugging")] ++ UartChannel::Rtt(up_buffer, down_buffer, rtt_memory), ++ #[cfg(not(feature = "usb_debugging"))] ++ UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)), + &SpiPins::new(SPI_MOSI, SPI_MISO, SPI_CLK), + &Some(SpiMX25R6435FPins::new( + SPI_MX25R6435F_CHIP_SELECT, +diff --git a/boards/nordic/nrf52dk/src/main.rs b/boards/nordic/nrf52dk/src/main.rs +index 693f1e18..b49518ff 100644 +--- a/boards/nordic/nrf52dk/src/main.rs ++++ b/boards/nordic/nrf52dk/src/main.rs +@@ -68,7 +68,7 @@ use kernel::component::Component; + #[allow(unused_imports)] + use kernel::{debug, debug_gpio, debug_verbose, static_init}; + use nrf52832::gpio::Pin; +-use nrf52dk_base::{SpiPins, UartPins}; ++use nrf52dk_base::{SpiPins, UartChannel, UartPins}; + + // The nRF52 DK LEDs (see back of board) + const LED1_PIN: Pin = Pin::P0_17; +@@ -199,7 +199,7 @@ pub unsafe fn reset_handler() { + LED2_PIN, + LED3_PIN, + led, +- &UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD), ++ UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)), + &SpiPins::new(SPI_MOSI, SPI_MISO, SPI_CLK), + &None, + button, +diff --git a/boards/nordic/nrf52dk_base/src/lib.rs b/boards/nordic/nrf52dk_base/src/lib.rs +index 17be93a4..ddac9dbd 100644 +--- a/boards/nordic/nrf52dk_base/src/lib.rs ++++ b/boards/nordic/nrf52dk_base/src/lib.rs +@@ -70,6 +70,15 @@ impl UartPins { + } + } + ++pub enum UartChannel<'a> { ++ Pins(UartPins), ++ Rtt( ++ &'a mut [u8], ++ &'a mut [u8], ++ &'a mut capsules::segger_rtt::SeggerRttMemory<'a>, ++ ), ++} ++ + /// Supported drivers by the platform + pub struct Platform { + ble_radio: &'static capsules::ble_advertising_driver::BLE< +@@ -136,7 +145,7 @@ pub unsafe fn setup_board( + debug_pin2_index: Pin, + debug_pin3_index: Pin, + led: &'static capsules::led::LED<'static>, +- uart_pins: &UartPins, ++ uart_channel: UartChannel<'static>, + spi_pins: &SpiPins, + mx25r6435f: &Option, + button: &'static capsules::button::Button<'static>, +@@ -232,6 +241,38 @@ pub unsafe fn setup_board( + let alarm = components::alarm::AlarmDriverComponent::new(board_kernel, mux_alarm) + .finalize(components::alarm_component_helper!(nrf52::rtc::Rtc)); + ++ let channel: &dyn kernel::hil::uart::Uart = match uart_channel { ++ UartChannel::Pins(uart_pins) => { ++ nrf52::uart::UARTE0.initialize( ++ nrf52::pinmux::Pinmux::new(uart_pins.txd as u32), ++ nrf52::pinmux::Pinmux::new(uart_pins.rxd as u32), ++ Some(nrf52::pinmux::Pinmux::new(uart_pins.cts as u32)), ++ Some(nrf52::pinmux::Pinmux::new(uart_pins.rts as u32)), ++ ); ++ &nrf52::uart::UARTE0 ++ } ++ UartChannel::Rtt(up_buffer, down_buffer, rtt_memory) => { ++ // Virtual alarm for the Segger RTT communication channel ++ let virtual_alarm_rtt = static_init!( ++ capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc>, ++ capsules::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) ++ ); ++ ++ // RTT communication channel ++ let rtt = static_init!( ++ capsules::segger_rtt::SeggerRtt>, ++ capsules::segger_rtt::SeggerRtt::new( ++ virtual_alarm_rtt, ++ rtt_memory, ++ up_buffer, ++ down_buffer ++ ) ++ ); ++ hil::time::Alarm::set_client(virtual_alarm_rtt, rtt); ++ rtt ++ } ++ }; ++ + let dynamic_deferred_call_clients = + static_init!([DynamicDeferredCallClientState; 2], Default::default()); + let dynamic_deferred_caller = static_init!( +@@ -241,19 +282,10 @@ pub unsafe fn setup_board( + DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + + // Create a shared UART channel for the console and for kernel debug. +- let uart_mux = components::console::UartMuxComponent::new( +- &nrf52::uart::UARTE0, +- 115200, +- dynamic_deferred_caller, +- ) +- .finalize(()); +- +- nrf52::uart::UARTE0.initialize( +- nrf52::pinmux::Pinmux::new(uart_pins.txd as u32), +- nrf52::pinmux::Pinmux::new(uart_pins.rxd as u32), +- Some(nrf52::pinmux::Pinmux::new(uart_pins.cts as u32)), +- Some(nrf52::pinmux::Pinmux::new(uart_pins.rts as u32)), +- ); ++ let uart_mux = ++ components::console::UartMuxComponent::new(channel, 115200, dynamic_deferred_caller) ++ .finalize(()); ++ + let pconsole = + components::process_console::ProcessConsoleComponent::new(board_kernel, uart_mux) + .finalize(()); +diff --git a/capsules/src/segger_rtt.rs b/capsules/src/segger_rtt.rs +index 1bf93fc1..33103c57 100644 +--- a/capsules/src/segger_rtt.rs ++++ b/capsules/src/segger_rtt.rs +@@ -91,67 +91,69 @@ + //! ``` + + use core::cell::Cell; +-use kernel::common::cells::{OptionalCell, TakeCell}; ++use core::marker::PhantomData; ++use kernel::common::cells::{OptionalCell, TakeCell, VolatileCell}; + use kernel::hil; + use kernel::hil::time::Frequency; + use kernel::hil::uart; + use kernel::ReturnCode; + +-/// Buffer for transmitting to the host. +-pub static mut UP_BUFFER: [u8; 1024] = [0; 1024]; +- +-/// Buffer for receiving messages from the host. +-pub static mut DOWN_BUFFER: [u8; 32] = [0; 32]; +- + /// This structure is defined by the segger RTT protocol. It must exist in + /// memory in exactly this form so that the segger JTAG tool can find it in the + /// chip's memory and read and write messages to the appropriate buffers. + #[repr(C)] +-pub struct SeggerRttMemory { +- id: [u8; 16], +- number_up_buffers: u32, +- number_down_buffers: u32, +- up_buffer: SeggerRttBuffer, +- down_buffer: SeggerRttBuffer, ++pub struct SeggerRttMemory<'a> { ++ id: VolatileCell<[u8; 16]>, ++ number_up_buffers: VolatileCell, ++ number_down_buffers: VolatileCell, ++ pub up_buffer: SeggerRttBuffer<'a>, ++ down_buffer: SeggerRttBuffer<'a>, + } + + #[repr(C)] +-pub struct SeggerRttBuffer { +- name: *const u8, // Pointer to the name of this channel. Must be a 4 byte thin pointer. +- buffer: *const u8, // Pointer to the buffer for this channel. +- length: u32, +- write_position: u32, +- read_position: u32, +- flags: u32, ++pub struct SeggerRttBuffer<'a> { ++ name: VolatileCell<*const u8>, // Pointer to the name of this channel. Must be a 4 byte thin pointer. ++ pub buffer: VolatileCell<*const u8>, // Pointer to the buffer for this channel. ++ pub length: VolatileCell, ++ pub write_position: VolatileCell, ++ read_position: VolatileCell, ++ flags: VolatileCell, ++ _lifetime: PhantomData<&'a [u8]>, + } + +-impl SeggerRttMemory { +- pub fn new( ++impl SeggerRttMemory<'a> { ++ pub fn new_raw( + up_buffer_name: &'a [u8], +- up_buffer: &'static mut [u8], +- down_buffer_name: &'static [u8], +- down_buffer: &'static mut [u8], +- ) -> SeggerRttMemory { ++ up_buffer_ptr: *const u8, ++ up_buffer_len: usize, ++ down_buffer_name: &'a [u8], ++ down_buffer_ptr: *const u8, ++ down_buffer_len: usize, ++ ) -> SeggerRttMemory<'a> { + SeggerRttMemory { ++ // TODO: only write this ID when the object is fully initialized, to avoid having ++ // these bytes elsewhere in (flash) memory. + // Must be "SEGGER RTT". +- id: *b"SEGGER RTT\0\0\0\0\0\0", +- number_up_buffers: 1, +- number_down_buffers: 1, ++ id: VolatileCell::new(*b"SEGGER RTT\0\0\0\0\0\0"), ++ number_up_buffers: VolatileCell::new(1), ++ number_down_buffers: VolatileCell::new(1), + up_buffer: SeggerRttBuffer { +- name: up_buffer_name.as_ptr(), +- buffer: up_buffer.as_ptr(), +- length: 1024, +- write_position: 0, +- read_position: 0, +- flags: 0, ++ name: VolatileCell::new(up_buffer_name.as_ptr()), ++ buffer: VolatileCell::new(up_buffer_ptr), ++ length: VolatileCell::new(up_buffer_len as u32), ++ write_position: VolatileCell::new(0), ++ read_position: VolatileCell::new(0), ++ flags: VolatileCell::new(0), ++ _lifetime: PhantomData, + }, + down_buffer: SeggerRttBuffer { +- name: down_buffer_name.as_ptr(), +- buffer: down_buffer.as_ptr(), +- length: 32, +- write_position: 0, +- read_position: 0, +- flags: 0, ++ name: VolatileCell::new(down_buffer_name.as_ptr()), ++ buffer: VolatileCell::new(down_buffer_ptr), ++ length: VolatileCell::new(down_buffer_len as u32), ++ write_position: VolatileCell::new(0), ++ read_position: VolatileCell::new(0), ++ flags: VolatileCell::new(0), ++ _lifetime: PhantomData, + }, + } + } +@@ -159,9 +161,9 @@ impl SeggerRttMemory { + + pub struct SeggerRtt<'a, A: hil::time::Alarm<'a>> { + alarm: &'a A, // Dummy alarm so we can get a callback. +- config: TakeCell<'a, SeggerRttMemory>, +- up_buffer: TakeCell<'static, [u8]>, +- _down_buffer: TakeCell<'static, [u8]>, ++ config: TakeCell<'a, SeggerRttMemory<'a>>, ++ up_buffer: TakeCell<'a, [u8]>, ++ _down_buffer: TakeCell<'a, [u8]>, + client: OptionalCell<&'a dyn uart::TransmitClient>, + client_buffer: TakeCell<'static, [u8]>, + tx_len: Cell, +@@ -170,9 +172,9 @@ pub struct SeggerRtt<'a, A: hil::time::Alarm<'a>> { + impl<'a, A: hil::time::Alarm<'a>> SeggerRtt<'a, A> { + pub fn new( + alarm: &'a A, +- config: &'a mut SeggerRttMemory, +- up_buffer: &'static mut [u8], +- down_buffer: &'static mut [u8], ++ config: &'a mut SeggerRttMemory<'a>, ++ up_buffer: &'a mut [u8], ++ down_buffer: &'a mut [u8], + ) -> SeggerRtt<'a, A> { + SeggerRtt { + alarm: alarm, +@@ -205,15 +207,15 @@ impl<'a, A: hil::time::Alarm<'a>> uart::Transmit<'a> for SeggerRtt<'a, A> { + // Copy the incoming data into the buffer. Once we increment + // the `write_position` the RTT listener will go ahead and read + // the message from us. +- let mut index = config.up_buffer.write_position as usize; +- let buffer_len = config.up_buffer.length as usize; ++ let mut index = config.up_buffer.write_position.get() as usize; ++ let buffer_len = config.up_buffer.length.get() as usize; + + for i in 0..tx_len { + buffer[(i + index) % buffer_len] = tx_data[i]; + } + + index = (index + tx_len) % buffer_len; +- config.up_buffer.write_position = index as u32; ++ config.up_buffer.write_position.set(index as u32); + self.tx_len.set(tx_len); + // Save the client buffer so we can pass it back with the callback. + self.client_buffer.replace(tx_data); diff --git a/reset.sh b/reset.sh new file mode 100755 index 0000000..8067c4a --- /dev/null +++ b/reset.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Copyright 2019 Google LLC +# +# 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. + +echo "$(tput bold)This script will restore the repository to a clean state$(tput sgr0)" +echo "$(tput bold)Any pending change will be lost.$(tput sgr0)" +echo "" + +accept='' +while + case "$accept" in + [Yy]) + # Start echoeing the commands to the screen + set -x + # Reset the submodules + git submodule foreach 'git reset --hard && git clean -fxd' + # Reset also the main repository + git reset --hard && git clean -fxd + + set +x + echo "DONE." + # And break the loop + false + ;; + + [Nn]) + echo "Nothing was done. Repository was left untouched." + # Don't do anything but break the while loop to exit + false + ;; + + *) + # Continue looping + true + ;; + esac +do + echo "$(tput bold)Are you sure you want to continue? [y/n]$(tput sgr0)" + read -s -n 1 accept +done diff --git a/rules.d/55-opensk.rules b/rules.d/55-opensk.rules new file mode 100644 index 0000000..b1c8c60 --- /dev/null +++ b/rules.d/55-opensk.rules @@ -0,0 +1 @@ +SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1915", ATTRS{idProduct}=="521f", ATTRS{product}=="OpenSK", MODE="0660", GROUP="logindev", TAG+="uaccess" diff --git a/run_desktop_tests.sh b/run_desktop_tests.sh new file mode 100755 index 0000000..11786e7 --- /dev/null +++ b/run_desktop_tests.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Copyright 2019 Google LLC +# +# 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. + +set -eu + +# Ensure we have the key_material.rs file before running the tests +if [ ! -f src/ctap/key_material.rs ] +then + echo "$(tput bold)ERROR:$(tput sgr0) Cannot find src/ctap/key_material.rs file." + echo "Please make sure that you have run the following script: ./setup.sh" + exit 2 +fi + +set -x + +echo "Checking formatting..." +cargo fmt --all -- --check +cd libraries/cbor +cargo fmt --all -- --check +cd ../.. +cd libraries/crypto +cargo fmt --all -- --check +cd ../.. + +echo "Checking that CTAP2 builds properly..." +cargo check --release --target=thumbv7em-none-eabi +cargo check --release --target=thumbv7em-none-eabi --features with_ctap1 +cargo check --release --target=thumbv7em-none-eabi --features debug_ctap +cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1 + +echo "Checking that examples build properly..." +cargo check --release --target=thumbv7em-none-eabi --examples + +echo "Checking that CTAP2 builds and links properly (1 set of features)..." +cargo build --release --target=thumbv7em-none-eabi --features with_ctap1 + +echo "Running unit tests on the desktop (release mode)..." +cd libraries/cbor +cargo test --release --features std +cd ../.. +cd libraries/crypto +RUSTFLAGS='-C target-feature=+aes' cargo test --release --features std,derive_debug +cd ../.. +cargo test --release --features std + +echo "Running unit tests on the desktop (debug mode)..." +cd libraries/cbor +cargo test --features std +cd ../.. +cd libraries/crypto +RUSTFLAGS='-C target-feature=+aes' cargo test --features std,derive_debug +cd ../.. +cargo test --features std + +echo "Running unit tests on the desktop (release mode + CTAP1)..." +cargo test --release --features std,with_ctap1 + +echo "Running unit tests on the desktop (debug mode + CTAP1)..." +cargo test --features std,with_ctap1 + diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..20d9cda --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly-2020-01-16 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..d3da3dd --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +use_field_init_shorthand = true +use_try_shorthand = true +edition = "2018" \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..2b45be3 --- /dev/null +++ b/setup.sh @@ -0,0 +1,49 @@ +# Copyright 2019 Google LLC +# +# 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. + +# Ensure the submodules are pulled and up-to-date +git submodule update --init + +done_text="$(tput bold)DONE.$(tput sgr0)" + +# Apply patches to kernel. Do that in a sub-shell +( + cd third_party/tock/ && \ + for p in ../../patches/tock/[0-9][0-9]-*.patch + do + echo -n '[-] Applying patch "'$(basename $p)'"... ' + git apply "$p" && echo $done_text + done +) + +# Now apply patches to libtock-rs. Do that in a sub-shell +( + cd third_party/libtock-rs/ && \ + for p in ../../patches/libtock-rs/[0-9][0-9]-*.patch + do + echo -n '[-] Applying patch "'$(basename $p)'"... ' + git apply "$p" && echo $done_text + done +) + +# Ensure we have certificates, keys, etc. so that the tests can run +source tools/gen_key_materials.sh +generate_crypto_materials N + +rustup install $(head -n 1 rust-toolchain) +pip3 install --user --upgrade tockloader +rustup target add thumbv7em-none-eabi + +# Install dependency to create applications. +cargo install elf2tab diff --git a/src/ctap/command.rs b/src/ctap/command.rs new file mode 100644 index 0000000..129c671 --- /dev/null +++ b/src/ctap/command.rs @@ -0,0 +1,483 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::data_formats::{ + ok_or_missing, read_array, read_byte_string, read_integer, read_map, read_text_string, + read_unsigned, ClientPinSubCommand, CoseKey, Extensions, GetAssertionOptions, + MakeCredentialOptions, PublicKeyCredentialDescriptor, PublicKeyCredentialRpEntity, + PublicKeyCredentialType, PublicKeyCredentialUserEntity, +}; +use super::status_code::Ctap2StatusCode; +use alloc::string::String; +use alloc::vec::Vec; +use core::convert::TryFrom; + +// CTAP specification (version 20190130) section 6.1 +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub enum Command { + AuthenticatorMakeCredential(AuthenticatorMakeCredentialParameters), + AuthenticatorGetAssertion(AuthenticatorGetAssertionParameters), + AuthenticatorGetInfo, + AuthenticatorClientPin(AuthenticatorClientPinParameters), + AuthenticatorReset, + AuthenticatorGetNextAssertion, + // TODO(kaczmarczyck) implement FIDO 2.1 commands (see below consts) +} + +impl From for Ctap2StatusCode { + fn from(_: cbor::reader::DecoderError) -> Self { + Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR + } +} + +// TODO: Remove this `allow(dead_code)` once the constants are used. +#[allow(dead_code)] +impl Command { + const AUTHENTICATOR_MAKE_CREDENTIAL: u8 = 0x01; + const AUTHENTICATOR_GET_ASSERTION: u8 = 0x02; + const AUTHENTICATOR_GET_INFO: u8 = 0x04; + const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06; + const AUTHENTICATOR_RESET: u8 = 0x07; + // TODO(kaczmarczyck) use or remove those constants + const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08; + const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09; + const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0; + const AUTHENTICATOR_SELECTION: u8 = 0xB0; + const AUTHENTICATOR_CONFIG: u8 = 0xC0; + const AUTHENTICATOR_VENDOR_FIRST: u8 = 0x40; + const AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF; + + pub fn deserialize(bytes: &[u8]) -> Result { + if bytes.is_empty() { + // The error to return is not specified, missing parameter seems to fit best. + return Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER); + } + + let command_value = bytes[0]; + match command_value { + Command::AUTHENTICATOR_MAKE_CREDENTIAL => { + let decoded_cbor = cbor::read(&bytes[1..])?; + Ok(Command::AuthenticatorMakeCredential( + AuthenticatorMakeCredentialParameters::try_from(decoded_cbor)?, + )) + } + Command::AUTHENTICATOR_GET_ASSERTION => { + let decoded_cbor = cbor::read(&bytes[1..])?; + Ok(Command::AuthenticatorGetAssertion( + AuthenticatorGetAssertionParameters::try_from(decoded_cbor)?, + )) + } + Command::AUTHENTICATOR_GET_INFO => { + // Parameters are ignored. + Ok(Command::AuthenticatorGetInfo) + } + Command::AUTHENTICATOR_CLIENT_PIN => { + let decoded_cbor = cbor::read(&bytes[1..])?; + Ok(Command::AuthenticatorClientPin( + AuthenticatorClientPinParameters::try_from(decoded_cbor)?, + )) + } + Command::AUTHENTICATOR_RESET => { + // Parameters are ignored. + Ok(Command::AuthenticatorReset) + } + Command::AUTHENTICATOR_GET_NEXT_ASSERTION => { + // Parameters are ignored. + Ok(Command::AuthenticatorGetNextAssertion) + } + _ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND), + } + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct AuthenticatorMakeCredentialParameters { + pub client_data_hash: Vec, + pub rp: PublicKeyCredentialRpEntity, + pub user: PublicKeyCredentialUserEntity, + pub pub_key_cred_params: Vec<(PublicKeyCredentialType, i64)>, + pub exclude_list: Option>, + pub extensions: Option, + // Even though options are optional, we can use the default if not present. + pub options: MakeCredentialOptions, + pub pin_uv_auth_param: Option>, + pub pin_uv_auth_protocol: Option, +} + +impl TryFrom for AuthenticatorMakeCredentialParameters { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: cbor::Value) -> Result { + let param_map = read_map(&cbor_value)?; + + let client_data_hash = read_byte_string(ok_or_missing(param_map.get(&cbor_unsigned!(1)))?)?; + + let rp = PublicKeyCredentialRpEntity::try_from(ok_or_missing( + param_map.get(&cbor_unsigned!(2)), + )?)?; + + let user = PublicKeyCredentialUserEntity::try_from(ok_or_missing( + param_map.get(&cbor_unsigned!(3)), + )?)?; + + let cred_param_vec = read_array(ok_or_missing(param_map.get(&cbor_unsigned!(4)))?)?; + let mut pub_key_cred_params = vec![]; + for cred_param_map_value in cred_param_vec { + let cred_param_map = read_map(cred_param_map_value)?; + let cred_type = PublicKeyCredentialType::try_from(ok_or_missing( + cred_param_map.get(&cbor_text!("type")), + )?)?; + let alg = read_integer(ok_or_missing(cred_param_map.get(&cbor_text!("alg")))?)?; + pub_key_cred_params.push((cred_type, alg)); + } + + let exclude_list = match param_map.get(&cbor_unsigned!(5)) { + Some(entry) => { + let exclude_list_vec = read_array(entry)?; + let mut exclude_list = vec![]; + for exclude_list_value in exclude_list_vec { + exclude_list.push(PublicKeyCredentialDescriptor::try_from(exclude_list_value)?); + } + Some(exclude_list) + } + None => None, + }; + + let extensions = param_map + .get(&cbor_unsigned!(6)) + .map(Extensions::try_from) + .transpose()?; + + let options = match param_map.get(&cbor_unsigned!(7)) { + Some(entry) => MakeCredentialOptions::try_from(entry)?, + None => MakeCredentialOptions { + rk: false, + uv: false, + }, + }; + + let pin_uv_auth_param = param_map + .get(&cbor_unsigned!(8)) + .map(read_byte_string) + .transpose()?; + + let pin_uv_auth_protocol = param_map + .get(&cbor_unsigned!(9)) + .map(read_unsigned) + .transpose()?; + + Ok(AuthenticatorMakeCredentialParameters { + client_data_hash, + rp, + user, + pub_key_cred_params, + exclude_list, + extensions, + options, + pin_uv_auth_param, + pin_uv_auth_protocol, + }) + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct AuthenticatorGetAssertionParameters { + pub rp_id: String, + pub client_data_hash: Vec, + pub allow_list: Option>, + pub extensions: Option, + // Even though options are optional, we can use the default if not present. + pub options: GetAssertionOptions, + pub pin_uv_auth_param: Option>, + pub pin_uv_auth_protocol: Option, +} + +impl TryFrom for AuthenticatorGetAssertionParameters { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: cbor::Value) -> Result { + let param_map = read_map(&cbor_value)?; + + let rp_id = read_text_string(ok_or_missing(param_map.get(&cbor_unsigned!(1)))?)?; + + let client_data_hash = read_byte_string(ok_or_missing(param_map.get(&cbor_unsigned!(2)))?)?; + + let allow_list = match param_map.get(&cbor_unsigned!(3)) { + Some(entry) => { + let allow_list_vec = read_array(entry)?; + let mut allow_list = vec![]; + for allow_list_value in allow_list_vec { + allow_list.push(PublicKeyCredentialDescriptor::try_from(allow_list_value)?); + } + Some(allow_list) + } + None => None, + }; + + let extensions = param_map + .get(&cbor_unsigned!(4)) + .map(Extensions::try_from) + .transpose()?; + + let options = match param_map.get(&cbor_unsigned!(5)) { + Some(entry) => GetAssertionOptions::try_from(entry)?, + None => GetAssertionOptions { + up: true, + uv: false, + }, + }; + + let pin_uv_auth_param = param_map + .get(&cbor_unsigned!(6)) + .map(read_byte_string) + .transpose()?; + + let pin_uv_auth_protocol = param_map + .get(&cbor_unsigned!(7)) + .map(read_unsigned) + .transpose()?; + + Ok(AuthenticatorGetAssertionParameters { + rp_id, + client_data_hash, + allow_list, + extensions, + options, + pin_uv_auth_param, + pin_uv_auth_protocol, + }) + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct AuthenticatorClientPinParameters { + pub pin_protocol: u64, + pub sub_command: ClientPinSubCommand, + pub key_agreement: Option, + pub pin_auth: Option>, + pub new_pin_enc: Option>, + pub pin_hash_enc: Option>, +} + +impl TryFrom for AuthenticatorClientPinParameters { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: cbor::Value) -> Result { + let param_map = read_map(&cbor_value)?; + + let pin_protocol = read_unsigned(ok_or_missing(param_map.get(&cbor_unsigned!(1)))?)?; + + let sub_command = + ClientPinSubCommand::try_from(ok_or_missing(param_map.get(&cbor_unsigned!(2)))?)?; + + let key_agreement = param_map + .get(&cbor_unsigned!(3)) + .map(read_map) + .transpose()? + .map(|x| CoseKey(x.clone())); + + let pin_auth = param_map + .get(&cbor_unsigned!(4)) + .map(read_byte_string) + .transpose()?; + + let new_pin_enc = param_map + .get(&cbor_unsigned!(5)) + .map(read_byte_string) + .transpose()?; + + let pin_hash_enc = param_map + .get(&cbor_unsigned!(6)) + .map(read_byte_string) + .transpose()?; + + Ok(AuthenticatorClientPinParameters { + pin_protocol, + sub_command, + key_agreement, + pin_auth, + new_pin_enc, + pin_hash_enc, + }) + } +} + +#[cfg(test)] +mod test { + use super::super::data_formats::{ + AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, + }; + use super::*; + use alloc::collections::BTreeMap; + + #[test] + fn test_from_cbor_make_credential_parameters() { + let cbor_value = cbor_map! { + 1 => vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F], + 2 => cbor_map! { + "id" => "example.com", + "name" => "Example", + "icon" => "example.com/icon.png", + }, + 3 => cbor_map! { + "id" => vec![0x1D, 0x1D, 0x1D, 0x1D], + "name" => "foo", + "displayName" => "bar", + "icon" => "example.com/foo/icon.png", + }, + 4 => cbor_array![ cbor_map! { + "type" => "public-key", + "alg" => -7 + } ], + 5 => cbor_array![], + 8 => vec![0x12, 0x34], + 9 => 1, + }; + let returned_make_credential_parameters = + AuthenticatorMakeCredentialParameters::try_from(cbor_value).unwrap(); + + let client_data_hash = vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, + ]; + let rp = PublicKeyCredentialRpEntity { + rp_id: "example.com".to_string(), + rp_name: Some("Example".to_string()), + rp_icon: Some("example.com/icon.png".to_string()), + }; + let user = PublicKeyCredentialUserEntity { + user_id: vec![0x1D, 0x1D, 0x1D, 0x1D], + user_name: Some("foo".to_string()), + user_display_name: Some("bar".to_string()), + user_icon: Some("example.com/foo/icon.png".to_string()), + }; + let pub_key_cred_param = (PublicKeyCredentialType::PublicKey, -7); + let options = MakeCredentialOptions { + rk: false, + uv: false, + }; + let expected_make_credential_parameters = AuthenticatorMakeCredentialParameters { + client_data_hash, + rp, + user, + pub_key_cred_params: vec![pub_key_cred_param], + exclude_list: Some(vec![]), + extensions: None, + options, + pin_uv_auth_param: Some(vec![0x12, 0x34]), + pin_uv_auth_protocol: Some(1), + }; + + assert_eq!( + returned_make_credential_parameters, + expected_make_credential_parameters + ); + } + + #[test] + fn test_from_cbor_get_assertion_parameters() { + let cbor_value = cbor_map! { + 1 => "example.com", + 2 => vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F], + 3 => cbor_array![ cbor_map! { + "type" => "public-key", + "id" => vec![0x2D, 0x2D, 0x2D, 0x2D], + "transports" => cbor_array!["usb"], + } ], + 6 => vec![0x12, 0x34], + 7 => 1, + }; + let returned_get_assertion_parameters = + AuthenticatorGetAssertionParameters::try_from(cbor_value).unwrap(); + + let rp_id = "example.com".to_string(); + let client_data_hash = vec![ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, + ]; + let pub_key_cred_descriptor = PublicKeyCredentialDescriptor { + key_type: PublicKeyCredentialType::PublicKey, + key_id: vec![0x2D, 0x2D, 0x2D, 0x2D], + transports: Some(vec![AuthenticatorTransport::Usb]), + }; + let options = GetAssertionOptions { + up: true, + uv: false, + }; + let expected_get_assertion_parameters = AuthenticatorGetAssertionParameters { + rp_id, + client_data_hash, + allow_list: Some(vec![pub_key_cred_descriptor]), + extensions: None, + options, + pin_uv_auth_param: Some(vec![0x12, 0x34]), + pin_uv_auth_protocol: Some(1), + }; + + assert_eq!( + returned_get_assertion_parameters, + expected_get_assertion_parameters + ); + } + + #[test] + fn test_from_cbor_client_pin_parameters() { + let cbor_value = cbor_map! { + 1 => 1, + 2 => ClientPinSubCommand::GetPinRetries, + 3 => cbor_map!{}, + 4 => vec! [0xBB], + 5 => vec! [0xCC], + 6 => vec! [0xDD], + }; + let returned_pin_protocol_parameters = + AuthenticatorClientPinParameters::try_from(cbor_value).unwrap(); + + let expected_pin_protocol_parameters = AuthenticatorClientPinParameters { + pin_protocol: 1, + sub_command: ClientPinSubCommand::GetPinRetries, + key_agreement: Some(CoseKey(BTreeMap::new())), + pin_auth: Some(vec![0xBB]), + new_pin_enc: Some(vec![0xCC]), + pin_hash_enc: Some(vec![0xDD]), + }; + + assert_eq!( + returned_pin_protocol_parameters, + expected_pin_protocol_parameters + ); + } + + #[test] + fn test_deserialize_get_info() { + let cbor_bytes = [Command::AUTHENTICATOR_GET_INFO]; + let command = Command::deserialize(&cbor_bytes); + assert_eq!(command, Ok(Command::AuthenticatorGetInfo)); + } + + #[test] + fn test_deserialize_reset() { + // Adding some random bytes to see if they are ignored. + let cbor_bytes = [Command::AUTHENTICATOR_RESET, 0xAB, 0xCD, 0xEF]; + let command = Command::deserialize(&cbor_bytes); + assert_eq!(command, Ok(Command::AuthenticatorReset)); + } + + #[test] + fn test_deserialize_get_next_assertion() { + let cbor_bytes = [Command::AUTHENTICATOR_GET_NEXT_ASSERTION]; + let command = Command::deserialize(&cbor_bytes); + assert_eq!(command, Ok(Command::AuthenticatorGetNextAssertion)); + } +} diff --git a/src/ctap/ctap1.rs b/src/ctap/ctap1.rs new file mode 100644 index 0000000..f3a9ce3 --- /dev/null +++ b/src/ctap/ctap1.rs @@ -0,0 +1,675 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::hid::ChannelID; +use super::key_material::{ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY}; +use super::status_code::Ctap2StatusCode; +use super::CtapState; +use crate::timer::ClockValue; +use alloc::vec::Vec; +use core::convert::Into; +use core::convert::TryFrom; +use crypto::rng256::Rng256; + +// The specification referenced in this file is at: +// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.pdf + +// status codes specification (version 20170411) section 3.3 +#[allow(non_camel_case_types)] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub enum Ctap1StatusCode { + SW_NO_ERROR = 0x9000, + SW_CONDITIONS_NOT_SATISFIED = 0x6985, + SW_WRONG_DATA = 0x6A80, + SW_WRONG_LENGTH = 0x6700, + SW_CLA_NOT_SUPPORTED = 0x6E00, + SW_INS_NOT_SUPPORTED = 0x6D00, + SW_VENDOR_KEY_HANDLE_TOO_LONG = 0xF000, +} + +impl TryFrom for Ctap1StatusCode { + type Error = (); + + fn try_from(value: u16) -> Result { + match value { + 0x9000 => Ok(Ctap1StatusCode::SW_NO_ERROR), + 0x6985 => Ok(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED), + 0x6A80 => Ok(Ctap1StatusCode::SW_WRONG_DATA), + 0x6700 => Ok(Ctap1StatusCode::SW_WRONG_LENGTH), + 0x6E00 => Ok(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED), + 0x6D00 => Ok(Ctap1StatusCode::SW_INS_NOT_SUPPORTED), + 0xF000 => Ok(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG), + _ => Err(()), + } + } +} + +impl Into for Ctap1StatusCode { + fn into(self) -> u16 { + self as u16 + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug))] +#[derive(PartialEq)] +pub enum Ctap1Flags { + CheckOnly = 0x07, + EnforceUpAndSign = 0x03, + DontEnforceUpAndSign = 0x08, +} + +impl TryFrom for Ctap1Flags { + type Error = Ctap1StatusCode; + + fn try_from(value: u8) -> Result { + match value { + 0x07 => Ok(Ctap1Flags::CheckOnly), + 0x03 => Ok(Ctap1Flags::EnforceUpAndSign), + 0x08 => Ok(Ctap1Flags::DontEnforceUpAndSign), + _ => Err(Ctap1StatusCode::SW_WRONG_DATA), + } + } +} + +impl Into for Ctap1Flags { + fn into(self) -> u8 { + self as u8 + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +// TODO: remove #allow when https://github.com/rust-lang/rust/issues/64362 is fixed +enum U2fCommand { + #[allow(dead_code)] + Register { + challenge: [u8; 32], + application: [u8; 32], + }, + #[allow(dead_code)] + Authenticate { + challenge: [u8; 32], + application: [u8; 32], + key_handle: Vec, + flags: Ctap1Flags, + }, + Version, + #[allow(dead_code)] + VendorSpecific { + payload: Vec, + }, +} + +impl TryFrom<&[u8]> for U2fCommand { + type Error = Ctap1StatusCode; + + fn try_from(message: &[u8]) -> Result { + if message.len() < Ctap1Command::APDU_HEADER_LEN as usize { + return Err(Ctap1StatusCode::SW_WRONG_DATA); + } + + let (apdu, payload) = message.split_at(Ctap1Command::APDU_HEADER_LEN as usize); + + // ISO7816 APDU Header format. Each cell is 1 byte. Note that the CTAP flavor always + // encodes the length on 3 bytes and doesn't use the field "Le" (Length Expected). + // We keep the 2 byte of "Le" for the packet length in mind, but always ignore its value. + // Lc is using big-endian encoding + // +-----+-----+----+----+-----+-----+-----+ + // | CLA | INS | P1 | P2 | Lc1 | Lc2 | Lc3 | + // +-----+-----+----+----+-----+-----+-----+ + if apdu[0] != Ctap1Command::CTAP1_CLA { + return Err(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED); + } + + let lc = (((apdu[4] as u32) << 16) | ((apdu[5] as u32) << 8) | (apdu[6] as u32)) as usize; + + // Since there is always request data, the expected length is either omitted or + // encoded in 2 bytes. + if lc != payload.len() && lc + 2 != payload.len() { + return Err(Ctap1StatusCode::SW_WRONG_LENGTH); + } + + match apdu[1] { + // U2F raw message format specification, Section 4.1 + // +-----------------+-------------------+ + // + Challenge (32B) | Application (32B) | + // +-----------------+-------------------+ + Ctap1Command::U2F_REGISTER => { + if lc != 64 { + return Err(Ctap1StatusCode::SW_WRONG_LENGTH); + } + Ok(Self::Register { + challenge: *array_ref!(payload, 0, 32), + application: *array_ref!(payload, 32, 32), + }) + } + + // U2F raw message format specification, Section 5.1 + // +-----------------+-------------------+---------------------+------------+ + // + Challenge (32B) | Application (32B) | key handle len (1B) | key handle | + // +-----------------+-------------------+---------------------+------------+ + Ctap1Command::U2F_AUTHENTICATE => { + if lc < 65 { + return Err(Ctap1StatusCode::SW_WRONG_LENGTH); + } + let handle_length = payload[64] as usize; + if lc != 65 + handle_length { + return Err(Ctap1StatusCode::SW_WRONG_LENGTH); + } + let flag = Ctap1Flags::try_from(apdu[2])?; + Ok(Self::Authenticate { + challenge: *array_ref!(payload, 0, 32), + application: *array_ref!(payload, 32, 32), + key_handle: payload[65..lc].to_vec(), + flags: flag, + }) + } + + // U2F raw message format specification, Section 6.1 + Ctap1Command::U2F_VERSION => { + if lc != 0 { + return Err(Ctap1StatusCode::SW_WRONG_LENGTH); + } + Ok(Self::Version) + } + + // For Vendor specific command. + Ctap1Command::VENDOR_SPECIFIC_FIRST..=Ctap1Command::VENDOR_SPECIFIC_LAST => { + Ok(Self::VendorSpecific { + payload: payload.to_vec(), + }) + } + + _ => Err(Ctap1StatusCode::SW_INS_NOT_SUPPORTED), + } + } +} + +pub struct Ctap1Command {} + +impl Ctap1Command { + const APDU_HEADER_LEN: u32 = 7; // CLA + INS + P1 + P2 + LC1-3 + + const CTAP1_CLA: u8 = 0; + // This byte is used in Register, but only serves backwards compatibility. + const LEGACY_BYTE: u8 = 0x05; + // This byte is hardcoded into the specification of Authenticate. + const USER_PRESENCE_INDICATOR_BYTE: u8 = 0x01; + + // CTAP1/U2F commands + // U2F raw message format specification 1.2 (version 20170411) + const U2F_REGISTER: u8 = 0x01; + const U2F_AUTHENTICATE: u8 = 0x02; + const U2F_VERSION: u8 = 0x03; + const VENDOR_SPECIFIC_FIRST: u8 = 0x40; + const VENDOR_SPECIFIC_LAST: u8 = 0xBF; + + pub fn process_command( + message: &[u8], + ctap_state: &mut CtapState, + clock_value: ClockValue, + ) -> Result, Ctap1StatusCode> + where + R: Rng256, + CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>, + { + let command = U2fCommand::try_from(message)?; + match command { + U2fCommand::Register { + challenge, + application, + } => { + if !ctap_state.u2f_up_state.consume_up(clock_value) { + return Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED); + } + Ctap1Command::process_register(challenge, application, ctap_state) + } + + U2fCommand::Authenticate { + challenge, + application, + key_handle, + flags, + } => { + // The order is important due to side effects of checking user presence. + if flags == Ctap1Flags::EnforceUpAndSign + && !ctap_state.u2f_up_state.consume_up(clock_value) + { + return Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED); + } + Ctap1Command::process_authenticate( + challenge, + application, + key_handle, + flags, + ctap_state, + ) + } + + // U2F raw message format specification (version 20170411) section 6.3 + U2fCommand::Version => Ok(Vec::::from(super::U2F_VERSION_STRING)), + + // TODO: should we return an error instead such as SW_INS_NOT_SUPPORTED? + U2fCommand::VendorSpecific { .. } => Err(Ctap1StatusCode::SW_NO_ERROR), + } + } + + // U2F raw message format specification (version 20170411) section 4.3 + // In case of success we need to send back the following reply + // (excluding ISO7816 success code) + // +------+--------------------+---------------------+------------+------------+------+ + // + 0x05 | User pub key (65B) | key handle len (1B) | key handle | X.509 Cert | Sign | + // +------+--------------------+---------------------+------------+------------+------+ + // + // Where Sign is an ECDSA signature over the following structure: + // +------+-------------------+-----------------+------------+--------------------+ + // + 0x00 | application (32B) | challenge (32B) | key handle | User pub key (65B) | + // +------+-------------------+-----------------+------------+--------------------+ + fn process_register( + challenge: [u8; 32], + application: [u8; 32], + ctap_state: &mut CtapState, + ) -> Result, Ctap1StatusCode> + where + R: Rng256, + CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>, + { + let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng); + let pk = sk.genpk(); + let key_handle = ctap_state.encrypt_key_handle(sk, &application); + if key_handle.len() > 0xFF { + // This is just being defensive with unreachable code. + return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG); + } + + let mut response = + Vec::with_capacity(105 + key_handle.len() + ATTESTATION_CERTIFICATE.len()); + response.push(Ctap1Command::LEGACY_BYTE); + let user_pk = pk.to_uncompressed(); + response.extend_from_slice(&user_pk); + response.push(key_handle.len() as u8); + response.extend(key_handle.clone()); + response.extend_from_slice(&ATTESTATION_CERTIFICATE); + + // The first byte is reserved. + let mut signature_data = Vec::with_capacity(66 + key_handle.len()); + signature_data.push(0x00); + signature_data.extend(&application); + signature_data.extend(&challenge); + signature_data.extend(key_handle); + signature_data.extend_from_slice(&user_pk); + + let attestation_key = crypto::ecdsa::SecKey::from_bytes(&ATTESTATION_PRIVATE_KEY).unwrap(); + let signature = attestation_key.sign_rfc6979::(&signature_data); + + response.extend(signature.to_asn1_der()); + Ok(response) + } + + // U2F raw message format specification (version 20170411) section 5.4 + // In case of success we need to send back the following reply + // (excluding ISO7816 success code) + // +---------+--------------+-----------+ + // + UP (1B) | Counter (4B) | Signature | + // +---------+--------------+-----------+ + // UP only has 2 defined values: + // - 0x00: user presence was not verified + // - 0x01: user presence was verified + // + // Where Signature is an ECDSA signature over the following structure: + // +-------------------+---------+--------------+-----------------+ + // + application (32B) | UP (1B) | Counter (4B) | challenge (32B) | + // +-------------------+---------+--------------+-----------------+ + fn process_authenticate( + challenge: [u8; 32], + application: [u8; 32], + key_handle: Vec, + flags: Ctap1Flags, + ctap_state: &mut CtapState, + ) -> Result, Ctap1StatusCode> + where + R: Rng256, + CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>, + { + let credential_source = ctap_state.decrypt_credential_source(key_handle, &application); + if let Some(credential_source) = credential_source { + if flags == Ctap1Flags::CheckOnly { + return Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED); + } + ctap_state.increment_global_signature_counter(); + let mut signature_data = ctap_state + .generate_auth_data(&application, Ctap1Command::USER_PRESENCE_INDICATOR_BYTE); + signature_data.extend(&challenge); + let signature = credential_source + .private_key + .sign_rfc6979::(&signature_data); + + let mut response = signature_data[application.len()..application.len() + 5].to_vec(); + response.extend(signature.to_asn1_der()); + Ok(response) + } else { + Err(Ctap1StatusCode::SW_WRONG_DATA) + } + } +} + +#[cfg(test)] +mod test { + use super::super::{ENCRYPTED_CREDENTIAL_ID_SIZE, USE_SIGNATURE_COUNTER}; + use super::*; + use crypto::rng256::ThreadRng256; + use crypto::Hash256; + + const CLOCK_FREQUENCY_HZ: usize = 32768; + const START_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ); + const TIMEOUT_CLOCK_VALUE: ClockValue = ClockValue::new( + (30001 * CLOCK_FREQUENCY_HZ as isize) / 1000, + CLOCK_FREQUENCY_HZ, + ); + + fn create_register_message(application: &[u8; 32]) -> Vec { + let mut message = vec![ + Ctap1Command::CTAP1_CLA, + Ctap1Command::U2F_REGISTER, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + ]; + let challenge = [0x0C; 32]; + message.extend(&challenge); + message.extend(application); + message + } + + fn create_authenticate_message( + application: &[u8; 32], + flags: Ctap1Flags, + key_handle: &Vec, + ) -> Vec { + let mut message = vec![ + Ctap1Command::CTAP1_CLA, + Ctap1Command::U2F_AUTHENTICATE, + flags.into(), + 0x00, + 0x00, + 0x00, + 65 + ENCRYPTED_CREDENTIAL_ID_SIZE as u8, + ]; + let challenge = [0x0C; 32]; + message.extend(&challenge); + message.extend(application); + message.push(ENCRYPTED_CREDENTIAL_ID_SIZE as u8); + message.extend(key_handle); + message + } + + #[test] + fn test_process_register() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let application = [0x0A; 32]; + let message = create_register_message(&application); + ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); + ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); + let response = + Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap(); + + assert_eq!(response[0], Ctap1Command::LEGACY_BYTE); + assert_eq!(response[66], ENCRYPTED_CREDENTIAL_ID_SIZE as u8); + assert!(ctap_state + .decrypt_credential_source( + response[67..67 + ENCRYPTED_CREDENTIAL_ID_SIZE].to_vec(), + &application + ) + .is_some()); + const CERT_START: usize = 67 + ENCRYPTED_CREDENTIAL_ID_SIZE; + assert_eq!( + &response[CERT_START..CERT_START + ATTESTATION_CERTIFICATE.len()], + &ATTESTATION_CERTIFICATE[..] + ); + } + + #[test] + fn test_process_register_bad_message() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let application = [0x0A; 32]; + let message = create_register_message(&application); + let response = Ctap1Command::process_command( + &message[..message.len() - 1], + &mut ctap_state, + START_CLOCK_VALUE, + ); + + assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_LENGTH)); + } + + #[test] + fn test_process_register_without_up() { + let application = [0x0A; 32]; + let message = create_register_message(&application); + + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); + ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); + let response = + Ctap1Command::process_command(&message, &mut ctap_state, TIMEOUT_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED)); + } + + #[test] + fn test_process_authenticate_check_only() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let sk = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let rp_id = "example.com"; + let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let key_handle = ctap_state.encrypt_key_handle(sk, &application); + let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); + + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED)); + } + + #[test] + fn test_process_authenticate_check_only_wrong_rp() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let sk = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let rp_id = "example.com"; + let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let key_handle = ctap_state.encrypt_key_handle(sk, &application); + let application = [0x55; 32]; + let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); + + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA)); + } + + #[test] + fn test_process_authenticate_check_only_wrong_length() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let sk = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let rp_id = "example.com"; + let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let key_handle = ctap_state.encrypt_key_handle(sk, &application); + let mut message = + create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); + + message.push(0x00); + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_LENGTH)); + + // Two extra zeros are okay, they could encode the expected response length. + message.push(0x00); + message.push(0x00); + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_LENGTH)); + } + + #[test] + fn test_process_authenticate_check_only_wrong_cla() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let sk = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let rp_id = "example.com"; + let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let key_handle = ctap_state.encrypt_key_handle(sk, &application); + let mut message = + create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); + message[0] = 0xEE; + + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED)); + } + + #[test] + fn test_process_authenticate_check_only_wrong_ins() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let sk = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let rp_id = "example.com"; + let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let key_handle = ctap_state.encrypt_key_handle(sk, &application); + let mut message = + create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); + message[1] = 0xEE; + + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_INS_NOT_SUPPORTED)); + } + + #[test] + fn test_process_authenticate_check_only_wrong_flags() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let sk = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let rp_id = "example.com"; + let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let key_handle = ctap_state.encrypt_key_handle(sk, &application); + let mut message = + create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); + message[2] = 0xEE; + + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA)); + } + + #[test] + fn test_process_authenticate_enforce() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let sk = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let rp_id = "example.com"; + let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let key_handle = ctap_state.encrypt_key_handle(sk, &application); + let message = + create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); + + ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); + ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); + let response = + Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap(); + assert_eq!(response[0], 0x01); + if USE_SIGNATURE_COUNTER { + assert_eq!(response[1..5], [0x00, 0x00, 0x00, 0x01]); + } else { + assert_eq!(response[1..5], [0x00, 0x00, 0x00, 0x00]); + } + } + + #[test] + fn test_process_authenticate_dont_enforce() { + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let sk = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + let rp_id = "example.com"; + let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let key_handle = ctap_state.encrypt_key_handle(sk, &application); + let message = create_authenticate_message( + &application, + Ctap1Flags::DontEnforceUpAndSign, + &key_handle, + ); + + let response = + Ctap1Command::process_command(&message, &mut ctap_state, TIMEOUT_CLOCK_VALUE).unwrap(); + assert_eq!(response[0], 0x01); + if USE_SIGNATURE_COUNTER { + assert_eq!(response[1..5], [0x00, 0x00, 0x00, 0x01]); + } else { + assert_eq!(response[1..5], [0x00, 0x00, 0x00, 0x00]); + } + } + + #[test] + fn test_process_authenticate_bad_key_handle() { + let application = [0x0A; 32]; + let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE]; + let message = + create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); + + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); + ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); + let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_WRONG_DATA)); + } + + #[test] + fn test_process_authenticate_without_up() { + let application = [0x0A; 32]; + let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE]; + let message = + create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); + + let mut rng = ThreadRng256 {}; + let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); + let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); + + ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); + ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); + let response = + Ctap1Command::process_command(&message, &mut ctap_state, TIMEOUT_CLOCK_VALUE); + assert_eq!(response, Err(Ctap1StatusCode::SW_CONDITIONS_NOT_SATISFIED)); + } +} diff --git a/src/ctap/data_formats.rs b/src/ctap/data_formats.rs new file mode 100644 index 0000000..eae2948 --- /dev/null +++ b/src/ctap/data_formats.rs @@ -0,0 +1,1020 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::status_code::Ctap2StatusCode; +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::convert::TryFrom; +use crypto::{ecdh, ecdsa}; + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct PublicKeyCredentialRpEntity { + pub rp_id: String, + pub rp_name: Option, + pub rp_icon: Option, +} + +impl TryFrom<&cbor::Value> for PublicKeyCredentialRpEntity { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result { + let rp_map = read_map(cbor_value)?; + let rp_id = read_text_string(ok_or_missing(rp_map.get(&cbor_text!("id")))?)?; + let rp_name = rp_map + .get(&cbor_text!("name")) + .map(read_text_string) + .transpose()?; + let rp_icon = rp_map + .get(&cbor_text!("icon")) + .map(read_text_string) + .transpose()?; + Ok(Self { + rp_id, + rp_name, + rp_icon, + }) + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct PublicKeyCredentialUserEntity { + pub user_id: Vec, + pub user_name: Option, + pub user_display_name: Option, + pub user_icon: Option, +} + +impl TryFrom<&cbor::Value> for PublicKeyCredentialUserEntity { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result { + let user_map = read_map(cbor_value)?; + let user_id = read_byte_string(ok_or_missing(user_map.get(&cbor_text!("id")))?)?; + let user_name = user_map + .get(&cbor_text!("name")) + .map(read_text_string) + .transpose()?; + let user_display_name = user_map + .get(&cbor_text!("displayName")) + .map(read_text_string) + .transpose()?; + let user_icon = user_map + .get(&cbor_text!("icon")) + .map(read_text_string) + .transpose()?; + Ok(Self { + user_id, + user_name, + user_display_name, + user_icon, + }) + } +} + +impl From for cbor::Value { + fn from(entity: PublicKeyCredentialUserEntity) -> Self { + cbor_map_options! { + "id" => entity.user_id, + "name" => entity.user_name, + "displayName" => entity.user_display_name, + "icon" => entity.user_icon, + } + } +} + +#[derive(Clone, PartialEq)] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] +pub enum PublicKeyCredentialType { + PublicKey, +} + +impl From for cbor::Value { + fn from(cred_type: PublicKeyCredentialType) -> Self { + match cred_type { + PublicKeyCredentialType::PublicKey => "public-key", + } + .into() + } +} + +impl TryFrom<&cbor::Value> for PublicKeyCredentialType { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result { + let cred_type_string = read_text_string(cbor_value)?; + match &cred_type_string[..] { + "public-key" => Ok(PublicKeyCredentialType::PublicKey), + _ => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM), + } + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub enum AuthenticatorTransport { + Usb, + Nfc, + Ble, + Internal, +} + +impl From for cbor::Value { + fn from(transport: AuthenticatorTransport) -> Self { + match transport { + AuthenticatorTransport::Usb => "usb", + AuthenticatorTransport::Nfc => "nfc", + AuthenticatorTransport::Ble => "ble", + AuthenticatorTransport::Internal => "internal", + } + .into() + } +} + +impl TryFrom<&cbor::Value> for AuthenticatorTransport { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result { + let transport_string = read_text_string(cbor_value)?; + match &transport_string[..] { + "usb" => Ok(AuthenticatorTransport::Usb), + "nfc" => Ok(AuthenticatorTransport::Nfc), + "ble" => Ok(AuthenticatorTransport::Ble), + "internal" => Ok(AuthenticatorTransport::Internal), + _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE), + } + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct PublicKeyCredentialDescriptor { + pub key_type: PublicKeyCredentialType, + pub key_id: Vec, + pub transports: Option>, +} + +impl TryFrom<&cbor::Value> for PublicKeyCredentialDescriptor { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result { + let cred_desc_map = read_map(cbor_value)?; + let key_type = PublicKeyCredentialType::try_from(ok_or_missing( + cred_desc_map.get(&cbor_text!("type")), + )?)?; + let key_id = read_byte_string(ok_or_missing(cred_desc_map.get(&cbor_text!("id")))?)?; + let transports = match cred_desc_map.get(&cbor_text!("transports")) { + Some(exclude_entry) => { + let transport_vec = read_array(exclude_entry)?; + let mut transports = vec![]; + for transport_value in transport_vec { + transports.push(AuthenticatorTransport::try_from(transport_value)?); + } + Some(transports) + } + None => None, + }; + Ok(Self { + key_type, + key_id, + transports, + }) + } +} + +impl From for cbor::Value { + fn from(desc: PublicKeyCredentialDescriptor) -> Self { + cbor_map_options! { + "type" => desc.key_type, + "id" => desc.key_id, + "transports" => desc.transports.map(|vec| cbor_array_vec!(vec)), + } + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct Extensions(BTreeMap); + +impl TryFrom<&cbor::Value> for Extensions { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result { + let mut extensions = BTreeMap::new(); + for (extension_key, extension_value) in read_map(cbor_value)? { + if let cbor::KeyType::TextString(extension_key_string) = extension_key { + extensions.insert(extension_key_string.to_string(), extension_value.clone()); + } else { + return Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE); + } + } + Ok(Extensions(extensions)) + } +} + +// Even though options are optional, we can use the default if not present. +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct MakeCredentialOptions { + pub rk: bool, + pub uv: bool, +} + +impl TryFrom<&cbor::Value> for MakeCredentialOptions { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result { + let options_map = read_map(cbor_value)?; + let rk = match options_map.get(&cbor_text!("rk")) { + Some(options_entry) => read_bool(options_entry)?, + None => false, + }; + if let Some(options_entry) = options_map.get(&cbor_text!("up")) { + if !read_bool(options_entry)? { + return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); + } + } + let uv = match options_map.get(&cbor_text!("uv")) { + Some(options_entry) => read_bool(options_entry)?, + None => false, + }; + Ok(Self { rk, uv }) + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct GetAssertionOptions { + pub up: bool, + pub uv: bool, +} + +impl TryFrom<&cbor::Value> for GetAssertionOptions { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result { + let options_map = read_map(cbor_value)?; + if let Some(options_entry) = options_map.get(&cbor_text!("rk")) { + // This is only for returning the correct status code. + read_bool(options_entry)?; + return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); + } + let up = match options_map.get(&cbor_text!("up")) { + Some(options_entry) => read_bool(options_entry)?, + None => true, + }; + let uv = match options_map.get(&cbor_text!("uv")) { + Some(options_entry) => read_bool(options_entry)?, + None => false, + }; + Ok(Self { up, uv }) + } +} + +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] +pub struct PackedAttestationStatement { + pub alg: i64, + pub sig: Vec, + pub x5c: Option>>, + pub ecdaa_key_id: Option>, +} + +impl From for cbor::Value { + fn from(att_stmt: PackedAttestationStatement) -> Self { + cbor_map_options! { + "alg" => att_stmt.alg, + "sig" => att_stmt.sig, + "x5c" => att_stmt.x5c.map(|x| cbor_array_vec!(x)), + "ecdaaKeyId" => att_stmt.ecdaa_key_id, + } + } +} + +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] +pub enum SignatureAlgorithm { + ES256 = ecdsa::PubKey::ES256_ALGORITHM as isize, +} + +#[derive(Clone)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] +pub struct PublicKeyCredentialSource { + // TODO function to convert to / from Vec + pub key_type: PublicKeyCredentialType, + pub credential_id: Vec, + pub private_key: ecdsa::SecKey, // TODO(kaczmarczyck) open for other algorithms + pub rp_id: String, + pub user_handle: Vec, // not optional, but nullable + pub other_ui: Option, +} + +impl From for cbor::Value { + fn from(credential: PublicKeyCredentialSource) -> cbor::Value { + let mut private_key = [0u8; 32]; + credential.private_key.to_bytes(&mut private_key); + let other_ui = match credential.other_ui { + None => cbor_null!(), + Some(other_ui) => cbor_text!(other_ui), + }; + cbor_array! { + credential.credential_id, + private_key, + credential.rp_id, + credential.user_handle, + other_ui, + } + } +} + +impl TryFrom for PublicKeyCredentialSource { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: cbor::Value) -> Result { + use cbor::{SimpleValue, Value}; + + let fields = read_array(&cbor_value)?; + if fields.len() != 5 { + return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR); + } + let credential_id = read_byte_string(&fields[0])?; + let private_key = read_byte_string(&fields[1])?; + if private_key.len() != 32 { + return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR); + } + let private_key = ecdsa::SecKey::from_bytes(array_ref!(private_key, 0, 32)) + .ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR)?; + let rp_id = read_text_string(&fields[2])?; + let user_handle = read_byte_string(&fields[3])?; + let other_ui = match &fields[4] { + Value::Simple(SimpleValue::NullValue) => None, + cbor_value => Some(read_text_string(cbor_value)?), + }; + Ok(PublicKeyCredentialSource { + key_type: PublicKeyCredentialType::PublicKey, + credential_id, + private_key, + rp_id, + user_handle, + other_ui, + }) + } +} + +// TODO(kaczmarczyck) we could decide to split this data type up +// It depends on the algorithm though, I think. +// So before creating a mess, this is my workaround. +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub struct CoseKey(pub BTreeMap); + +// This is the algorithm specifier that is supposed to be used in a COSE key +// map. The CTAP specification says -25 which represents ECDH-ES + HKDF-256 +// here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms +// In fact, this is just used for compatibility with older specification versions. +const ECDH_ALGORITHM: i64 = -25; +const EC2_KEY_TYPE: i64 = 2; +const P_256_CURVE: i64 = 1; + +impl From for CoseKey { + fn from(pk: ecdh::PubKey) -> Self { + let mut x_bytes = [0; ecdh::NBYTES]; + let mut y_bytes = [0; ecdh::NBYTES]; + pk.to_coordinates(&mut x_bytes, &mut y_bytes); + let x_byte_cbor: cbor::Value = cbor_bytes_lit!(&x_bytes); + let y_byte_cbor: cbor::Value = cbor_bytes_lit!(&y_bytes); + // TODO(kaczmarczyck) do not write optional parameters, spec is unclear + let cose_cbor_value = cbor_map_options! { + 1 => EC2_KEY_TYPE, + 3 => ECDH_ALGORITHM, + -1 => P_256_CURVE, + -2 => x_byte_cbor, + -3 => y_byte_cbor, + }; + if let cbor::Value::Map(cose_map) = cose_cbor_value { + CoseKey(cose_map) + } else { + unreachable!(); + } + } +} + +impl TryFrom for ecdh::PubKey { + type Error = Ctap2StatusCode; + + fn try_from(cose_key: CoseKey) -> Result { + let key_type = read_integer(ok_or_missing(cose_key.0.get(&cbor_int!(1)))?)?; + if key_type != EC2_KEY_TYPE { + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); + } + let algorithm = read_integer(ok_or_missing(cose_key.0.get(&cbor_int!(3)))?)?; + if algorithm != ECDH_ALGORITHM { + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); + } + let curve = read_integer(ok_or_missing(cose_key.0.get(&cbor_int!(-1)))?)?; + if curve != P_256_CURVE { + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); + } + let x_bytes = read_byte_string(ok_or_missing(cose_key.0.get(&cbor_int!(-2)))?)?; + if x_bytes.len() != ecdh::NBYTES { + return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); + } + let y_bytes = read_byte_string(ok_or_missing(cose_key.0.get(&cbor_int!(-3)))?)?; + if y_bytes.len() != ecdh::NBYTES { + return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); + } + let x_array_ref = array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES]; + let y_array_ref = array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES]; + ecdh::PubKey::from_coordinates(x_array_ref, y_array_ref) + .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) + } +} + +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] +pub enum ClientPinSubCommand { + GetPinRetries, + GetKeyAgreement, + SetPin, + ChangePin, + GetPinUvAuthTokenUsingPin, + GetPinUvAuthTokenUsingUv, + GetUvRetries, +} + +impl From for cbor::Value { + fn from(subcommand: ClientPinSubCommand) -> Self { + match subcommand { + ClientPinSubCommand::GetPinRetries => 0x01, + ClientPinSubCommand::GetKeyAgreement => 0x02, + ClientPinSubCommand::SetPin => 0x03, + ClientPinSubCommand::ChangePin => 0x04, + ClientPinSubCommand::GetPinUvAuthTokenUsingPin => 0x05, + ClientPinSubCommand::GetPinUvAuthTokenUsingUv => 0x06, + ClientPinSubCommand::GetUvRetries => 0x07, + } + .into() + } +} + +impl TryFrom<&cbor::Value> for ClientPinSubCommand { + type Error = Ctap2StatusCode; + + fn try_from(cbor_value: &cbor::Value) -> Result { + let subcommand_int = read_unsigned(cbor_value)?; + match subcommand_int { + 0x01 => Ok(ClientPinSubCommand::GetPinRetries), + 0x02 => Ok(ClientPinSubCommand::GetKeyAgreement), + 0x03 => Ok(ClientPinSubCommand::SetPin), + 0x04 => Ok(ClientPinSubCommand::ChangePin), + 0x05 => Ok(ClientPinSubCommand::GetPinUvAuthTokenUsingPin), + 0x06 => Ok(ClientPinSubCommand::GetPinUvAuthTokenUsingUv), + 0x07 => Ok(ClientPinSubCommand::GetUvRetries), + // TODO(kaczmarczyck) what is the correct status code for this error? + _ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER), + } + } +} + +pub(super) fn read_unsigned(cbor_value: &cbor::Value) -> Result { + match cbor_value { + cbor::Value::KeyValue(cbor::KeyType::Unsigned(unsigned)) => Ok(*unsigned), + _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE), + } +} + +pub(super) fn read_integer(cbor_value: &cbor::Value) -> Result { + match cbor_value { + cbor::Value::KeyValue(cbor::KeyType::Unsigned(unsigned)) => { + if *unsigned <= core::i64::MAX as u64 { + Ok(*unsigned as i64) + } else { + Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + } + } + cbor::Value::KeyValue(cbor::KeyType::Negative(signed)) => Ok(*signed), + _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE), + } +} + +pub fn read_byte_string(cbor_value: &cbor::Value) -> Result, Ctap2StatusCode> { + match cbor_value { + cbor::Value::KeyValue(cbor::KeyType::ByteString(byte_string)) => Ok(byte_string.to_vec()), + _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE), + } +} + +pub(super) fn read_text_string(cbor_value: &cbor::Value) -> Result { + match cbor_value { + cbor::Value::KeyValue(cbor::KeyType::TextString(text_string)) => { + Ok(text_string.to_string()) + } + _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE), + } +} + +pub(super) fn read_array(cbor_value: &cbor::Value) -> Result<&Vec, Ctap2StatusCode> { + match cbor_value { + cbor::Value::Array(array) => Ok(array), + _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE), + } +} + +pub(super) fn read_map( + cbor_value: &cbor::Value, +) -> Result<&BTreeMap, Ctap2StatusCode> { + match cbor_value { + cbor::Value::Map(map) => Ok(map), + _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE), + } +} + +pub(super) fn read_bool(cbor_value: &cbor::Value) -> Result { + match cbor_value { + cbor::Value::Simple(cbor::SimpleValue::FalseValue) => Ok(false), + cbor::Value::Simple(cbor::SimpleValue::TrueValue) => Ok(true), + _ => Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE), + } +} + +pub(super) fn ok_or_missing( + value_option: Option<&cbor::Value>, +) -> Result<&cbor::Value, Ctap2StatusCode> { + value_option.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER) +} + +#[cfg(test)] +mod test { + use self::Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE; + use super::*; + use alloc::collections::BTreeMap; + + #[test] + fn test_read_unsigned() { + assert_eq!(read_unsigned(&cbor_int!(123)), Ok(123)); + assert_eq!( + read_unsigned(&cbor_bool!(true)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_unsigned(&cbor_text!("foo")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_unsigned(&cbor_bytes_lit!(b"bar")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_unsigned(&cbor_array![]), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_unsigned(&cbor_map! {}), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + } + + #[test] + fn test_read_unsigned_limits() { + assert_eq!( + read_unsigned(&cbor_unsigned!(std::u64::MAX)), + Ok(std::u64::MAX) + ); + assert_eq!( + read_unsigned(&cbor_unsigned!((std::i64::MAX as u64) + 1)), + Ok((std::i64::MAX as u64) + 1) + ); + assert_eq!( + read_unsigned(&cbor_int!(std::i64::MAX)), + Ok(std::i64::MAX as u64) + ); + assert_eq!(read_unsigned(&cbor_int!(123)), Ok(123)); + assert_eq!(read_unsigned(&cbor_int!(1)), Ok(1)); + assert_eq!(read_unsigned(&cbor_int!(0)), Ok(0)); + assert_eq!( + read_unsigned(&cbor_int!(-1)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_unsigned(&cbor_int!(-123)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_unsigned(&cbor_int!(std::i64::MIN)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + } + + #[test] + fn test_read_integer() { + assert_eq!(read_integer(&cbor_int!(123)), Ok(123)); + assert_eq!(read_integer(&cbor_int!(-123)), Ok(-123)); + assert_eq!( + read_integer(&cbor_bool!(true)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_integer(&cbor_text!("foo")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_integer(&cbor_bytes_lit!(b"bar")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_integer(&cbor_array![]), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_integer(&cbor_map! {}), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + } + + #[test] + fn test_read_integer_limits() { + assert_eq!( + read_integer(&cbor_unsigned!(std::u64::MAX)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_integer(&cbor_unsigned!((std::i64::MAX as u64) + 1)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!(read_integer(&cbor_int!(std::i64::MAX)), Ok(std::i64::MAX)); + assert_eq!(read_integer(&cbor_int!(123)), Ok(123)); + assert_eq!(read_integer(&cbor_int!(1)), Ok(1)); + assert_eq!(read_integer(&cbor_int!(0)), Ok(0)); + assert_eq!(read_integer(&cbor_int!(-1)), Ok(-1)); + assert_eq!(read_integer(&cbor_int!(-123)), Ok(-123)); + assert_eq!(read_integer(&cbor_int!(std::i64::MIN)), Ok(std::i64::MIN)); + } + + #[test] + fn test_read_byte_string() { + assert_eq!( + read_byte_string(&cbor_int!(123)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_byte_string(&cbor_bool!(true)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_byte_string(&cbor_text!("foo")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!(read_byte_string(&cbor_bytes_lit!(b"")), Ok(Vec::new())); + assert_eq!( + read_byte_string(&cbor_bytes_lit!(b"bar")), + Ok(b"bar".to_vec()) + ); + assert_eq!( + read_byte_string(&cbor_array![]), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_byte_string(&cbor_map! {}), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + } + + #[test] + fn test_read_text_string() { + assert_eq!( + read_text_string(&cbor_int!(123)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_text_string(&cbor_bool!(true)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!(read_text_string(&cbor_text!("")), Ok(String::new())); + assert_eq!( + read_text_string(&cbor_text!("foo")), + Ok(String::from("foo")) + ); + assert_eq!( + read_text_string(&cbor_bytes_lit!(b"bar")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_text_string(&cbor_array![]), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_text_string(&cbor_map! {}), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + } + + #[test] + fn test_read_array() { + assert_eq!( + read_array(&cbor_int!(123)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_array(&cbor_bool!(true)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_array(&cbor_text!("foo")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_array(&cbor_bytes_lit!(b"bar")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!(read_array(&cbor_array![]), Ok(&Vec::new())); + assert_eq!( + read_array(&cbor_array![ + 123, + cbor_null!(), + "foo", + cbor_array![], + cbor_map! {}, + ]), + Ok(&vec![ + cbor_int!(123), + cbor_null!(), + cbor_text!("foo"), + cbor_array![], + cbor_map! {}, + ]) + ); + assert_eq!( + read_array(&cbor_map! {}), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + } + + #[test] + fn test_read_map() { + assert_eq!( + read_map(&cbor_int!(123)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_map(&cbor_bool!(true)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_map(&cbor_text!("foo")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_map(&cbor_bytes_lit!(b"bar")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_map(&cbor_array![]), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!(read_map(&cbor_map! {}), Ok(&BTreeMap::new())); + assert_eq!( + read_map(&cbor_map! { + 1 => cbor_false!(), + "foo" => b"bar", + b"bin" => -42, + }), + Ok(&[ + (cbor_unsigned!(1), cbor_false!()), + (cbor_text!("foo"), cbor_bytes_lit!(b"bar")), + (cbor_bytes_lit!(b"bin"), cbor_int!(-42)), + ] + .iter() + .cloned() + .collect::>()) + ); + } + + #[test] + fn test_read_bool() { + assert_eq!( + read_bool(&cbor_int!(123)), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!(read_bool(&cbor_bool!(true)), Ok(true)); + assert_eq!(read_bool(&cbor_bool!(false)), Ok(false)); + assert_eq!( + read_bool(&cbor_text!("foo")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_bool(&cbor_bytes_lit!(b"bar")), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_bool(&cbor_array![]), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + assert_eq!( + read_bool(&cbor_map! {}), + Err(CTAP2_ERR_CBOR_UNEXPECTED_TYPE) + ); + } + + #[test] + fn test_from_public_key_credential_rp_entity() { + let cbor_rp_entity = cbor_map! { + "id" => "example.com", + "name" => "Example", + "icon" => "example.com/icon.png", + }; + let rp_entity = PublicKeyCredentialRpEntity::try_from(&cbor_rp_entity); + let expected_rp_entity = PublicKeyCredentialRpEntity { + rp_id: "example.com".to_string(), + rp_name: Some("Example".to_string()), + rp_icon: Some("example.com/icon.png".to_string()), + }; + assert_eq!(rp_entity, Ok(expected_rp_entity)); + } + + #[test] + fn test_from_into_public_key_credential_user_entity() { + let cbor_user_entity = cbor_map! { + "id" => vec![0x1D, 0x1D, 0x1D, 0x1D], + "name" => "foo", + "displayName" => "bar", + "icon" => "example.com/foo/icon.png", + }; + let user_entity = PublicKeyCredentialUserEntity::try_from(&cbor_user_entity); + let expected_user_entity = PublicKeyCredentialUserEntity { + user_id: vec![0x1D, 0x1D, 0x1D, 0x1D], + user_name: Some("foo".to_string()), + user_display_name: Some("bar".to_string()), + user_icon: Some("example.com/foo/icon.png".to_string()), + }; + assert_eq!(user_entity, Ok(expected_user_entity)); + let created_cbor: cbor::Value = user_entity.unwrap().into(); + assert_eq!(created_cbor, cbor_user_entity); + } + + #[test] + fn test_from_into_public_key_credential_type() { + let cbor_credential_type = cbor_text!("public-key"); + let credential_type = PublicKeyCredentialType::try_from(&cbor_credential_type); + let expected_credential_type = PublicKeyCredentialType::PublicKey; + assert_eq!(credential_type, Ok(expected_credential_type)); + let created_cbor: cbor::Value = credential_type.unwrap().into(); + assert_eq!(created_cbor, cbor_credential_type); + } + + #[test] + fn test_from_into_authenticator_transport() { + let cbor_authenticator_transport = cbor_text!("usb"); + let authenticator_transport = + AuthenticatorTransport::try_from(&cbor_authenticator_transport); + let expected_authenticator_transport = AuthenticatorTransport::Usb; + assert_eq!( + authenticator_transport, + Ok(expected_authenticator_transport) + ); + let created_cbor: cbor::Value = authenticator_transport.unwrap().into(); + assert_eq!(created_cbor, cbor_authenticator_transport); + } + + #[test] + fn test_from_into_public_key_credential_descriptor() { + let cbor_credential_descriptor = cbor_map! { + "type" => "public-key", + "id" => vec![0x2D, 0x2D, 0x2D, 0x2D], + "transports" => cbor_array!["usb"], + }; + let credential_descriptor = + PublicKeyCredentialDescriptor::try_from(&cbor_credential_descriptor); + let expected_credential_descriptor = PublicKeyCredentialDescriptor { + key_type: PublicKeyCredentialType::PublicKey, + key_id: vec![0x2D, 0x2D, 0x2D, 0x2D], + transports: Some(vec![AuthenticatorTransport::Usb]), + }; + assert_eq!(credential_descriptor, Ok(expected_credential_descriptor)); + let created_cbor: cbor::Value = credential_descriptor.unwrap().into(); + assert_eq!(created_cbor, cbor_credential_descriptor); + } + + #[test] + fn test_from_extensions() { + let cbor_extensions = cbor_map! { + "the_answer" => 42, + }; + let extensions = Extensions::try_from(&cbor_extensions); + let mut expected_extensions = Extensions(BTreeMap::new()); + expected_extensions + .0 + .insert("the_answer".to_string(), cbor_int!(42)); + assert_eq!(extensions, Ok(expected_extensions)); + } + + #[test] + fn test_from_make_credential_options() { + let cbor_make_options = cbor_map! { + "rk" => true, + "uv" => false, + }; + let make_options = MakeCredentialOptions::try_from(&cbor_make_options); + let expected_make_options = MakeCredentialOptions { + rk: true, + uv: false, + }; + assert_eq!(make_options, Ok(expected_make_options)); + } + + #[test] + fn test_from_get_assertion_options() { + let cbor_get_assertion = cbor_map! { + "up" => true, + "uv" => false, + }; + let get_assertion = GetAssertionOptions::try_from(&cbor_get_assertion); + let expected_get_assertion = GetAssertionOptions { + up: true, + uv: false, + }; + assert_eq!(get_assertion, Ok(expected_get_assertion)); + } + + #[test] + fn test_into_packed_attestation_statement() { + let certificate: cbor::values::KeyType = cbor_bytes![vec![0x5C, 0x5C, 0x5C, 0x5C]]; + let cbor_packed_attestation_statement = cbor_map! { + "alg" => 1, + "sig" => vec![0x55, 0x55, 0x55, 0x55], + "x5c" => cbor_array_vec![vec![certificate]], + "ecdaaKeyId" => vec![0xEC, 0xDA, 0x1D], + }; + let packed_attestation_statement = PackedAttestationStatement { + alg: 1, + sig: vec![0x55, 0x55, 0x55, 0x55], + x5c: Some(vec![vec![0x5C, 0x5C, 0x5C, 0x5C]]), + ecdaa_key_id: Some(vec![0xEC, 0xDA, 0x1D]), + }; + let created_cbor: cbor::Value = packed_attestation_statement.into(); + assert_eq!(created_cbor, cbor_packed_attestation_statement); + } + + #[test] + fn test_from_into_cose_key() { + use crypto::rng256::ThreadRng256; + + let mut rng = ThreadRng256 {}; + let sk = crypto::ecdh::SecKey::gensk(&mut rng); + let pk = sk.genpk(); + let cose_key = CoseKey::from(pk.clone()); + let created_pk = ecdh::PubKey::try_from(cose_key); + assert_eq!(created_pk, Ok(pk)); + } + + #[test] + fn test_from_into_client_pin_sub_command() { + let cbor_sub_command = cbor_int!(0x01); + let sub_command = ClientPinSubCommand::try_from(&cbor_sub_command); + let expected_sub_command = ClientPinSubCommand::GetPinRetries; + assert_eq!(sub_command, Ok(expected_sub_command)); + let created_cbor: cbor::Value = sub_command.unwrap().into(); + assert_eq!(created_cbor, cbor_sub_command); + } + + #[test] + fn test_credential_source_cbor_round_trip() { + use crypto::rng256::{Rng256, ThreadRng256}; + + let mut rng = ThreadRng256 {}; + let credential = PublicKeyCredentialSource { + key_type: PublicKeyCredentialType::PublicKey, + credential_id: rng.gen_uniform_u8x32().to_vec(), + private_key: crypto::ecdsa::SecKey::gensk(&mut rng), + rp_id: "example.com".to_string(), + user_handle: b"foo".to_vec(), + other_ui: None, + }; + + assert_eq!( + PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())), + Ok(credential.clone()) + ); + + let credential = PublicKeyCredentialSource { + other_ui: Some("other".to_string()), + ..credential + }; + + assert_eq!( + PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())), + Ok(credential) + ); + } + + #[test] + fn test_credential_source_invalid_cbor() { + assert!(PublicKeyCredentialSource::try_from(cbor_false!()).is_err()); + assert!(PublicKeyCredentialSource::try_from(cbor_array!(false)).is_err()); + assert!(PublicKeyCredentialSource::try_from(cbor_array!(b"foo".to_vec())).is_err()); + } +} diff --git a/src/ctap/hid/mod.rs b/src/ctap/hid/mod.rs new file mode 100644 index 0000000..53e0ad6 --- /dev/null +++ b/src/ctap/hid/mod.rs @@ -0,0 +1,596 @@ +// Copyright 2019 Google LLC +// +// 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. + +mod receive; +mod send; + +use self::receive::MessageAssembler; +use self::send::HidPacketIterator; +#[cfg(feature = "with_ctap1")] +use super::ctap1; +use super::status_code::Ctap2StatusCode; +use super::timed_permission::TimedPermission; +use super::CtapState; +use crate::timer::{ClockValue, Duration, Timestamp}; +use alloc::vec::Vec; +#[cfg(feature = "debug_ctap")] +use core::fmt::Write; +use crypto::rng256::Rng256; +#[cfg(feature = "debug_ctap")] +use libtock::console::Console; + +// CTAP specification (version 20190130) section 8.1 +// TODO: Channel allocation, section 8.1.3? +// TODO: Transaction timeout, section 8.1.5.2 + +pub type HidPacket = [u8; 64]; +pub type ChannelID = [u8; 4]; + +pub enum ProcessedPacket<'a> { + InitPacket { + cmd: u8, + len: usize, + data: &'a [u8; 57], + }, + ContinuationPacket { + seq: u8, + data: &'a [u8; 59], + }, +} + +// An assembled CTAPHID command. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Message { + // Channel ID. + pub cid: ChannelID, + // Command. + pub cmd: u8, + // Bytes of the message. + pub payload: Vec, +} + +pub struct CtapHid { + assembler: MessageAssembler, + // The specification (version 20190130) only requires unique CIDs ; the allocation algorithm is + // vendor specific. + // We allocate them incrementally, that is all `cid` such that 1 <= cid <= allocated_cids are + // allocated. + // In packets, the ids are then encoded with the native endianness (with the + // u32::to/from_ne_bytes methods). + allocated_cids: usize, + pub wink_permission: TimedPermission, +} + +#[allow(dead_code)] +pub enum KeepaliveStatus { + Processing, + UpNeeded, +} + +#[allow(dead_code)] +// TODO(kaczmarczyck) disable the warning in the end +impl CtapHid { + // CTAP specification (version 20190130) section 8.1.3 + const CHANNEL_RESERVED: ChannelID = [0, 0, 0, 0]; + const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF]; + const TYPE_INIT_BIT: u8 = 0x80; + const PACKET_TYPE_MASK: u8 = 0x80; + + // CTAP specification (version 20190130) section 8.1.9 + const COMMAND_PING: u8 = 0x01; + const COMMAND_MSG: u8 = 0x03; + const COMMAND_INIT: u8 = 0x06; + const COMMAND_CBOR: u8 = 0x10; + pub const COMMAND_CANCEL: u8 = 0x11; + const COMMAND_KEEPALIVE: u8 = 0x3B; + const COMMAND_ERROR: u8 = 0x3F; + // TODO: optional lock command + const COMMAND_LOCK: u8 = 0x04; + const COMMAND_WINK: u8 = 0x08; + const COMMAND_VENDOR_FIRST: u8 = 0x40; + const COMMAND_VENDOR_LAST: u8 = 0x7F; + + // CTAP specification (version 20190130) section 8.1.9.1.6 + const ERR_INVALID_CMD: u8 = 0x01; + const ERR_INVALID_PAR: u8 = 0x02; + const ERR_INVALID_LEN: u8 = 0x03; + const ERR_INVALID_SEQ: u8 = 0x04; + const ERR_MSG_TIMEOUT: u8 = 0x05; + const ERR_CHANNEL_BUSY: u8 = 0x06; + const ERR_LOCK_REQUIRED: u8 = 0x0A; + const ERR_INVALID_CHANNEL: u8 = 0x0B; + const ERR_OTHER: u8 = 0x7F; + + // CTAP specification (version 20190130) section 8.1.9.1.3 + const PROTOCOL_VERSION: u8 = 2; + + // The device version number is vendor-defined. For now we define them to be zero. + // TODO: Update with device version? + const DEVICE_VERSION_MAJOR: u8 = 0; + const DEVICE_VERSION_MINOR: u8 = 0; + const DEVICE_VERSION_BUILD: u8 = 0; + + const CAPABILITY_WINK: u8 = 0x01; + const CAPABILITY_CBOR: u8 = 0x04; + const CAPABILITY_NMSG: u8 = 0x08; + // Capabilitites currently supported by this device. + #[cfg(feature = "with_ctap1")] + const CAPABILITIES: u8 = CtapHid::CAPABILITY_WINK | CtapHid::CAPABILITY_CBOR; + #[cfg(not(feature = "with_ctap1"))] + const CAPABILITIES: u8 = + CtapHid::CAPABILITY_WINK | CtapHid::CAPABILITY_CBOR | CtapHid::CAPABILITY_NMSG; + + // TODO: Is this timeout duration specified? + const TIMEOUT_DURATION: Duration = Duration::from_ms(100); + const WINK_TIMEOUT_DURATION: Duration = Duration::from_ms(5000); + + pub fn new() -> CtapHid { + CtapHid { + assembler: MessageAssembler::new(), + allocated_cids: 0, + wink_permission: TimedPermission::waiting(), + } + } + + // Process an incoming USB HID packet, and optionally returns a list of outgoing packets to + // send as a reply. + pub fn process_hid_packet( + &mut self, + packet: &HidPacket, + clock_value: ClockValue, + ctap_state: &mut CtapState, + ) -> HidPacketIterator + where + R: Rng256, + CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>, + { + // TODO: Send COMMAND_KEEPALIVE every 100ms? + match self + .assembler + .parse_packet(packet, Timestamp::::from_clock_value(clock_value)) + { + Ok(Some(message)) => { + #[cfg(feature = "debug_ctap")] + writeln!(&mut Console::new(), "Received message: {:02x?}", message).unwrap(); + + let cid = message.cid; + if !self.has_valid_channel(&message) { + #[cfg(feature = "debug_ctap")] + writeln!(&mut Console::new(), "Invalid channel: {:02x?}", cid).unwrap(); + return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL); + } + // If another command arrives, stop winking to prevent accidential button touches. + self.wink_permission = TimedPermission::waiting(); + + match message.cmd { + // CTAP specification (version 20190130) section 8.1.9.1.1 + CtapHid::COMMAND_MSG => { + // If we don't have CTAP1 backward compatibilty, this command in invalid. + #[cfg(not(feature = "with_ctap1"))] + return CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD); + + #[cfg(feature = "with_ctap1")] + match ctap1::Ctap1Command::process_command( + &message.payload, + ctap_state, + clock_value, + ) { + Ok(payload) => CtapHid::ctap1_success_message(cid, &payload), + Err(ctap1_status_code) => { + CtapHid::ctap1_error_message(cid, ctap1_status_code) + } + } + } + // CTAP specification (version 20190130) section 8.1.9.1.2 + CtapHid::COMMAND_CBOR => { + // CTAP specification (version 20190130) section 8.1.5.1 + // Each transaction is atomic, so we process the command directly here and + // don't handle any other packet in the meantime. + // TODO: Send keep-alive packets in the meantime. + let response = ctap_state.process_command(&message.payload, cid); + if let Some(iterator) = CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_CBOR, + payload: response, + }) { + iterator + } else { + // Handle the case of a payload > 7609 bytes. + // Although this shouldn't happen if the FIDO2 commands are implemented + // correctly, we reply with a vendor specific code instead of silently + // ignoring the error. + // + // The error payload that we send instead is 1 <= 7609 bytes, so it is + // safe to unwrap() the result. + CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_CBOR, + payload: vec![ + Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG as u8, + ], + }) + .unwrap() + } + } + // CTAP specification (version 20190130) section 8.1.9.1.3 + CtapHid::COMMAND_INIT => { + if cid == CtapHid::CHANNEL_BROADCAST { + if message.payload.len() != 8 { + return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN); + } + + // TODO: Prevent allocating 2^32 channels. + self.allocated_cids += 1; + let allocated_cid = (self.allocated_cids as u32).to_ne_bytes(); + + let mut payload = vec![0; 17]; + payload[..8].copy_from_slice(&message.payload); + payload[8..12].copy_from_slice(&allocated_cid); + payload[12] = CtapHid::PROTOCOL_VERSION; + payload[13] = CtapHid::DEVICE_VERSION_MAJOR; + payload[14] = CtapHid::DEVICE_VERSION_MINOR; + payload[15] = CtapHid::DEVICE_VERSION_BUILD; + payload[16] = CtapHid::CAPABILITIES; + + // This unwrap is safe because the payload length is 17 <= 7609 bytes. + CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_INIT, + payload, + }) + .unwrap() + } else { + // Sync the channel and discard the current transaction. + // TODO: The specification (version 20190130) wording isn't clear about + // the payload format in this case. + // + // This unwrap is safe because the payload length is 0 <= 7609 bytes. + CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_INIT, + payload: vec![], + }) + .unwrap() + } + } + // CTAP specification (version 20190130) section 8.1.9.1.4 + CtapHid::COMMAND_PING => { + // Pong the same message. + // This unwrap is safe because if we could parse the incoming message, it's + // payload length must be <= 7609 bytes. + CtapHid::split_message(message).unwrap() + } + // CTAP specification (version 20190130) section 8.1.9.1.5 + CtapHid::COMMAND_CANCEL => { + // Authenticators MUST NOT reply to this message. + // CANCEL is handled during user presence checks in main. + HidPacketIterator::none() + } + // Optional commands + // CTAP specification (version 20190130) section 8.1.9.2.1 + CtapHid::COMMAND_WINK => { + if !message.payload.is_empty() { + return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN); + } + self.wink_permission = + TimedPermission::granted(clock_value, CtapHid::WINK_TIMEOUT_DURATION); + CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_WINK, + payload: vec![], + }) + .unwrap() + } + // CTAP specification (version 20190130) section 8.1.9.2.2 + // TODO: implement LOCK + _ => { + // Unknown or unsupported command. + CtapHid::error_message(cid, CtapHid::ERR_INVALID_CMD) + } + } + } + Ok(None) => { + // Waiting for more packets to assemble the message, nothing to send for now. + HidPacketIterator::none() + } + Err((cid, error)) => { + if !self.is_allocated_channel(cid) { + CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL) + } else { + match error { + receive::Error::UnexpectedChannel => { + CtapHid::error_message(cid, CtapHid::ERR_CHANNEL_BUSY) + } + receive::Error::UnexpectedInit => { + // TODO: Should we send another error code in this case? + // Technically, we were expecting a sequence number and got another + // byte, although the command/seqnum bit has higher-level semantics + // than sequence numbers. + CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ) + } + receive::Error::UnexpectedContinuation => { + // CTAP specification (version 20190130) section 8.1.5.4 + // Spurious continuation packets will be ignored. + HidPacketIterator::none() + } + receive::Error::UnexpectedSeq => { + CtapHid::error_message(cid, CtapHid::ERR_INVALID_SEQ) + } + receive::Error::Timeout => { + CtapHid::error_message(cid, CtapHid::ERR_MSG_TIMEOUT) + } + } + } + } + } + } + + fn has_valid_channel(&self, message: &Message) -> bool { + match message.cid { + // Only INIT commands use the broadcast channel. + CtapHid::CHANNEL_BROADCAST => message.cmd == CtapHid::COMMAND_INIT, + // Check that the channel is allocated. + _ => self.is_allocated_channel(message.cid), + } + } + + fn is_allocated_channel(&self, cid: ChannelID) -> bool { + cid != CtapHid::CHANNEL_RESERVED && u32::from_ne_bytes(cid) as usize <= self.allocated_cids + } + + fn error_message(cid: ChannelID, error_code: u8) -> HidPacketIterator { + // This unwrap is safe because the payload length is 1 <= 7609 bytes. + CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_ERROR, + payload: vec![error_code], + }) + .unwrap() + } + + pub fn process_single_packet(packet: &HidPacket) -> (&ChannelID, ProcessedPacket) { + let (cid, rest) = array_refs![packet, 4, 60]; + if rest[0] & CtapHid::PACKET_TYPE_MASK != 0 { + let cmd = rest[0] & !CtapHid::PACKET_TYPE_MASK; + let len = (rest[1] as usize) << 8 | (rest[2] as usize); + ( + cid, + ProcessedPacket::InitPacket { + cmd, + len, + data: array_ref!(rest, 3, 57), + }, + ) + } else { + ( + cid, + ProcessedPacket::ContinuationPacket { + seq: rest[0], + data: array_ref!(rest, 1, 59), + }, + ) + } + } + + fn split_message(message: Message) -> Option { + #[cfg(feature = "debug_ctap")] + writeln!(&mut Console::new(), "Sending message: {:02x?}", message).unwrap(); + HidPacketIterator::new(message) + } + + pub fn keepalive(cid: ChannelID, status: KeepaliveStatus) -> HidPacketIterator { + let status_code = match status { + KeepaliveStatus::Processing => 1, + KeepaliveStatus::UpNeeded => 2, + }; + // This unwrap is safe because the payload length is 1 <= 7609 bytes. + CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_KEEPALIVE, + payload: vec![status_code], + }) + .unwrap() + } + + #[cfg(feature = "with_ctap1")] + fn ctap1_error_message( + cid: ChannelID, + error_code: ctap1::Ctap1StatusCode, + ) -> HidPacketIterator { + // This unwrap is safe because the payload length is 2 <= 7609 bytes + let code: u16 = error_code.into(); + CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_MSG, + payload: code.to_be_bytes().to_vec(), + }) + .unwrap() + } + + #[cfg(feature = "with_ctap1")] + fn ctap1_success_message(cid: ChannelID, payload: &[u8]) -> HidPacketIterator { + let mut response = payload.to_vec(); + let code: u16 = ctap1::Ctap1StatusCode::SW_NO_ERROR.into(); + response.extend_from_slice(&code.to_be_bytes()); + CtapHid::split_message(Message { + cid, + cmd: CtapHid::COMMAND_MSG, + payload: response, + }) + .unwrap() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crypto::rng256::ThreadRng256; + + const CLOCK_FREQUENCY_HZ: usize = 32768; + // Except for tests for timeouts (done in ctap1.rs), transactions are time independant. + const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ); + const DUMMY_TIMESTAMP: Timestamp = Timestamp::from_ms(0); + + fn process_messages( + ctap_hid: &mut CtapHid, + ctap_state: &mut CtapState, + request: Vec, + ) -> Option> + where + CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>, + { + let mut result = Vec::new(); + let mut assembler_reply = MessageAssembler::new(); + for msg_request in request { + for pkt_request in HidPacketIterator::new(msg_request).unwrap() { + for pkt_reply in + ctap_hid.process_hid_packet(&pkt_request, DUMMY_CLOCK_VALUE, ctap_state) + { + match assembler_reply.parse_packet(&pkt_reply, DUMMY_TIMESTAMP) { + Ok(Some(message)) => result.push(message), + Ok(None) => (), + Err(_) => return None, + } + } + } + } + Some(result) + } + + fn cid_from_init( + ctap_hid: &mut CtapHid, + ctap_state: &mut CtapState, + ) -> ChannelID + where + CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>, + { + let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]; + let reply = process_messages( + ctap_hid, + ctap_state, + vec![Message { + cid: CtapHid::CHANNEL_BROADCAST, + cmd: CtapHid::COMMAND_INIT, + payload: nonce.clone(), + }], + ); + + let mut cid_in_payload: ChannelID = Default::default(); + if let Some(messages) = reply { + assert_eq!(messages.len(), 1); + assert!(messages[0].payload.len() >= 12); + assert_eq!(nonce, &messages[0].payload[..8]); + cid_in_payload.copy_from_slice(&messages[0].payload[8..12]); + } else { + panic!("The init process was not successful to generate a valid channel ID.") + } + cid_in_payload + } + + #[test] + fn test_split_assemble() { + for payload_len in 0..7609 { + let message = Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x00, + payload: vec![0xFF; payload_len], + }; + + let mut messages = Vec::new(); + let mut assembler = MessageAssembler::new(); + for packet in HidPacketIterator::new(message.clone()).unwrap() { + match assembler.parse_packet(&packet, DUMMY_TIMESTAMP) { + Ok(Some(msg)) => messages.push(msg), + Ok(None) => (), + Err(_) => panic!("Couldn't assemble packet: {:02x?}", &packet as &[u8]), + } + } + + assert_eq!(messages, vec![message]); + } + } + + #[test] + fn test_command_init() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + let mut ctap_hid = CtapHid::new(); + + let reply = process_messages( + &mut ctap_hid, + &mut ctap_state, + vec![Message { + cid: CtapHid::CHANNEL_BROADCAST, + cmd: CtapHid::COMMAND_INIT, + payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0], + }], + ); + + assert_eq!( + reply, + Some(vec![Message { + cid: CtapHid::CHANNEL_BROADCAST, + cmd: CtapHid::COMMAND_INIT, + payload: vec![ + 0x12, // Nonce + 0x34, + 0x56, + 0x78, + 0x9A, + 0xBC, + 0xDE, + 0xF0, + 0x01, // Allocated CID + 0x00, + 0x00, + 0x00, + 0x02, // Protocol version + 0x00, // Device version + 0x00, + 0x00, + CtapHid::CAPABILITIES + ] + }]) + ); + } + + #[test] + fn test_command_ping() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + let mut ctap_hid = CtapHid::new(); + let cid = cid_from_init(&mut ctap_hid, &mut ctap_state); + + let reply = process_messages( + &mut ctap_hid, + &mut ctap_state, + vec![Message { + cid, + cmd: CtapHid::COMMAND_PING, + payload: vec![0x99, 0x99], + }], + ); + + assert_eq!( + reply, + Some(vec![Message { + cid, + cmd: CtapHid::COMMAND_PING, + payload: vec![0x99, 0x99] + }]) + ); + } +} diff --git a/src/ctap/hid/receive.rs b/src/ctap/hid/receive.rs new file mode 100644 index 0000000..ae2e707 --- /dev/null +++ b/src/ctap/hid/receive.rs @@ -0,0 +1,590 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::{ChannelID, CtapHid, HidPacket, Message, ProcessedPacket}; +use crate::timer::Timestamp; +use alloc::vec::Vec; +use core::mem::swap; + +// A structure to assemble CTAPHID commands from a series of incoming USB HID packets. +pub struct MessageAssembler { + // Whether this is waiting to receive an initialization packet. + idle: bool, + // Current channel ID. + cid: ChannelID, + // Timestamp of the last packet received on the current channel. + last_timestamp: Timestamp, + // Current command. + cmd: u8, + // Sequence number expected for the next packet. + seq: u8, + // Number of bytes left to fill the current message. + remaining_payload_len: usize, + // Buffer for the current payload. + payload: Vec, +} + +#[derive(PartialEq, Debug)] +pub enum Error { + // Expected a continuation packet on a specific channel, got a packet on another channel. + UnexpectedChannel, + // Expected a continuation packet, got an init packet. + UnexpectedInit, + // Expected an init packet, got a continuation packet. + UnexpectedContinuation, + // Expected a continuation packet with a specific sequence number, got another sequence number. + UnexpectedSeq, + // This packet arrived after a timeout. + Timeout, +} + +impl MessageAssembler { + pub fn new() -> MessageAssembler { + MessageAssembler { + idle: true, + cid: [0, 0, 0, 0], + last_timestamp: Timestamp::from_ms(0), + cmd: 0, + seq: 0, + remaining_payload_len: 0, + payload: Vec::new(), + } + } + + // Resets the message assembler to the idle state. + // The caller can reset the assembler for example due to a timeout. + pub fn reset(&mut self) { + self.idle = true; + self.cid = [0, 0, 0, 0]; + self.last_timestamp = Timestamp::from_ms(0); + self.cmd = 0; + self.seq = 0; + self.remaining_payload_len = 0; + self.payload.clear(); + } + + // Returns: + // - An Ok() result if the packet was parsed correctly. This contains either Some(Vec) if a + // full message was assembled after this packet, or None if more packets are needed to fill the + // message. + // - An Err() result if there was a parsing error. + // TODO: Implement timeouts. For example, have the caller pass us a timestamp of when this + // packet was received. + pub fn parse_packet( + &mut self, + packet: &HidPacket, + timestamp: Timestamp, + ) -> Result, (ChannelID, Error)> { + // TODO: Support non-full-speed devices (i.e. packet len != 64)? This isn't recommended by + // section 8.8.1 + let (cid, processed_packet) = CtapHid::process_single_packet(&packet); + + if !self.idle && timestamp - self.last_timestamp >= CtapHid::TIMEOUT_DURATION { + // The current channel timed out. + // Save the channel ID and reset the state. + let current_cid = self.cid; + self.reset(); + + // If the packet is from the timed-out channel, send back a timeout error. + // Otherwise, proceed with processing the packet. + if *cid == current_cid { + return Err((*cid, Error::Timeout)); + } + } + + if self.idle { + // Expecting an initialization packet. + match processed_packet { + ProcessedPacket::InitPacket { cmd, len, data } => { + Ok(self.accept_init_packet(*cid, cmd, len, data, timestamp)) + } + ProcessedPacket::ContinuationPacket { .. } => { + // CTAP specification (version 20190130) section 8.1.5.4 + // Spurious continuation packets will be ignored. + Err((*cid, Error::UnexpectedContinuation)) + } + } + } else { + // Expecting a continuation packet from the current channel. + + // CTAP specification (version 20190130) section 8.1.5.1 + // Reject packets from other channels. + if *cid != self.cid { + return Err((*cid, Error::UnexpectedChannel)); + } + + match processed_packet { + // Unexpected initialization packet. + ProcessedPacket::InitPacket { cmd, len, data } => { + self.reset(); + if cmd == CtapHid::COMMAND_INIT { + Ok(self.accept_init_packet(*cid, cmd, len, data, timestamp)) + } else { + Err((*cid, Error::UnexpectedInit)) + } + } + ProcessedPacket::ContinuationPacket { seq, data } => { + if seq != self.seq { + // Reject packets with the wrong sequence number. + self.reset(); + Err((*cid, Error::UnexpectedSeq)) + } else { + // Update the last timestamp. + self.last_timestamp = timestamp; + // Increment the sequence number for the next packet. + self.seq += 1; + Ok(self.append_payload(data)) + } + } + } + } + } + + fn accept_init_packet( + &mut self, + cid: ChannelID, + cmd: u8, + len: usize, + data: &[u8], + timestamp: Timestamp, + ) -> Option { + // TODO: Should invalid commands/payload lengths be rejected early, i.e. as soon as the + // initialization packet is received, or should we build a message and then catch the + // error? + // The specification (version 20190130) isn't clear on this point. + self.cid = cid; + self.last_timestamp = timestamp; + self.cmd = cmd; + self.seq = 0; + self.remaining_payload_len = len; + self.append_payload(data) + } + + fn append_payload(&mut self, data: &[u8]) -> Option { + if data.len() < self.remaining_payload_len { + self.payload.extend_from_slice(data); + self.idle = false; + self.remaining_payload_len -= data.len(); + None + } else { + self.payload + .extend_from_slice(&data[..self.remaining_payload_len]); + self.idle = true; + let mut payload = Vec::new(); + swap(&mut self.payload, &mut payload); + Some(Message { + cid: self.cid, + cmd: self.cmd, + payload, + }) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::timer::Duration; + + // Except for tests that exercise timeouts, all packets are synchronized at the same dummy + // timestamp. + const DUMMY_TIMESTAMP: Timestamp = Timestamp::from_ms(0); + + fn byte_extend(bytes: &[u8], padding: u8) -> HidPacket { + let len = bytes.len(); + assert!(len <= 64); + let mut result = [0; 64]; + result[..len].copy_from_slice(bytes); + for byte in result[len..].iter_mut() { + *byte = padding; + } + result + } + + fn zero_extend(bytes: &[u8]) -> HidPacket { + byte_extend(bytes, 0) + } + + #[test] + fn test_empty_payload() { + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x00, + payload: vec![] + })) + ); + } + + #[test] + fn test_one_packet() { + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10]), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x00, + payload: vec![0x00; 0x10] + })) + ); + } + + #[test] + fn test_nonzero_padding() { + // CTAP specification (version 20190130) section 8.1.4 + // It is written that "Unused bytes SHOULD be set to zero", so we test that non-zero + // padding is accepted as well. + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10], 0xFF), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x00, + payload: vec![0xFF; 0x10] + })) + ); + } + + #[test] + fn test_two_packets() { + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x01, + payload: vec![0x00; 0x40] + })) + ); + } + + #[test] + fn test_three_packets() { + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x80]), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01]), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x01, + payload: vec![0x00; 0x80] + })) + ); + } + + #[test] + fn test_max_packets() { + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x1D, 0xB9]), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + for seq in 0..0x7F { + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + } + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x01, + payload: vec![0x00; 0x1DB9] + })) + ); + } + + #[test] + fn test_multiple_messages() { + // Check that after yielding a message, the assembler is ready to process new messages. + let mut assembler = MessageAssembler::new(); + for i in 0..10 { + // Introduce some variability in the messages. + let cmd = 2 * i; + let byte = 3 * i; + + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80 | cmd, 0x00, 0x80], byte), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x00], byte), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x01], byte), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd, + payload: vec![byte; 0x80] + })) + ); + } + } + + #[test] + fn test_channel_switch() { + // Check that the assembler can process messages from multiple channels, sequentially. + let mut assembler = MessageAssembler::new(); + for i in 0..10 { + // Introduce some variability in the messages. + let cid = 0x78 + i; + let cmd = 2 * i; + let byte = 3 * i; + + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, cid, 0x80 | cmd, 0x00, 0x80], byte), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, cid, 0x00], byte), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, cid, 0x01], byte), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, cid], + cmd, + payload: vec![byte; 0x80] + })) + ); + } + } + + #[test] + fn test_unexpected_channel() { + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + + // Check that many sorts of packets on another channel are ignored. + for cmd in 0..=0xFF { + for byte in 0..=0xFF { + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, 0x9A, cmd, 0x00], byte), + DUMMY_TIMESTAMP + ), + Err(([0x12, 0x34, 0x56, 0x9A], Error::UnexpectedChannel)) + ); + } + } + + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x01, + payload: vec![0x00; 0x40] + })) + ); + } + + #[test] + fn test_spurious_continuation_packets() { + // CTAP specification (version 20190130) section 8.1.5.4 + // Spurious continuation packets appearing without a prior initialization packet will be + // ignored. + let mut assembler = MessageAssembler::new(); + for i in 0..0x80 { + // Some legit packet. + let byte = 2 * i; + assert_eq!( + assembler.parse_packet( + &byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x80, 0x00, 0x10], byte), + DUMMY_TIMESTAMP + ), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x00, + payload: vec![byte; 0x10] + })) + ); + + // Spurious continuation packet. + let seq = i; + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]), + DUMMY_TIMESTAMP + ), + Err(([0x12, 0x34, 0x56, 0x78], Error::UnexpectedContinuation)) + ); + } + } + + #[test] + fn test_unexpected_init() { + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x80]), + DUMMY_TIMESTAMP + ), + Err(([0x12, 0x34, 0x56, 0x78], Error::UnexpectedInit)) + ); + } + + #[test] + fn test_unexpected_seq() { + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x01]), + DUMMY_TIMESTAMP + ), + Err(([0x12, 0x34, 0x56, 0x78], Error::UnexpectedSeq)) + ); + } + + #[test] + fn test_timed_out_packet() { + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x00, 0x40]), + DUMMY_TIMESTAMP + ), + Ok(None) + ); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x00]), + DUMMY_TIMESTAMP + CtapHid::TIMEOUT_DURATION + ), + Err(([0x12, 0x34, 0x56, 0x78], Error::Timeout)) + ); + } + + #[test] + fn test_just_in_time_packets() { + let mut timestamp = DUMMY_TIMESTAMP; + // Delay between each packet is just below the threshold. + let delay = CtapHid::TIMEOUT_DURATION - Duration::from_ms(1); + + let mut assembler = MessageAssembler::new(); + assert_eq!( + assembler.parse_packet( + &zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x1D, 0xB9]), + timestamp + ), + Ok(None) + ); + for seq in 0..0x7F { + timestamp += delay; + assert_eq!( + assembler.parse_packet(&zero_extend(&[0x12, 0x34, 0x56, 0x78, seq]), timestamp), + Ok(None) + ); + } + timestamp += delay; + assert_eq!( + assembler.parse_packet(&zero_extend(&[0x12, 0x34, 0x56, 0x78, 0x7F]), timestamp), + Ok(Some(Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x01, + payload: vec![0x00; 0x1DB9] + })) + ); + } + + // TODO: more tests +} diff --git a/src/ctap/hid/send.rs b/src/ctap/hid/send.rs new file mode 100644 index 0000000..434d633 --- /dev/null +++ b/src/ctap/hid/send.rs @@ -0,0 +1,296 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::{CtapHid, HidPacket, Message}; + +pub struct HidPacketIterator(Option); + +impl HidPacketIterator { + pub fn new(message: Message) -> Option { + let splitter = MessageSplitter::new(message); + if splitter.is_some() { + Some(HidPacketIterator(splitter)) + } else { + None + } + } + + pub fn none() -> HidPacketIterator { + HidPacketIterator(None) + } +} + +impl Iterator for HidPacketIterator { + type Item = HidPacket; + + fn next(&mut self) -> Option { + match &mut self.0 { + Some(splitter) => splitter.next(), + None => None, + } + } +} + +pub struct MessageSplitter { + message: Message, + packet: HidPacket, + seq: Option, + i: usize, +} + +impl MessageSplitter { + // Try to split this message into an iterator of HID packets. This fails if the message is too + // long to fit into a sequence of HID packets (which is limited to 7609 bytes). + pub fn new(message: Message) -> Option { + if message.payload.len() > 7609 { + None + } else { + // Cache the CID, as it is constant for all packets in this message. + let mut packet = [0; 64]; + packet[..4].copy_from_slice(&message.cid); + + Some(MessageSplitter { + message, + packet, + seq: None, + i: 0, + }) + } + } + + // Copy as many bytes as possible from data to dst, and return how many bytes are copied. + // Contrary to copy_from_slice, this doesn't require slices of the same length. + // All unused bytes in dst are set to zero, as if the data was padded with zeros to match. + fn consume_data(dst: &mut [u8], data: &[u8]) -> usize { + let dst_len = dst.len(); + let data_len = data.len(); + + if data_len <= dst_len { + // data fits in dst, copy all the bytes. + dst[..data_len].copy_from_slice(data); + for byte in dst[data_len..].iter_mut() { + *byte = 0; + } + data_len + } else { + // Fill all of dst. + dst.copy_from_slice(&data[..dst_len]); + dst_len + } + } +} + +impl Iterator for MessageSplitter { + type Item = HidPacket; + + fn next(&mut self) -> Option { + let payload_len = self.message.payload.len(); + match self.seq { + None => { + // First, send an initialization packet. + self.packet[4] = self.message.cmd | CtapHid::TYPE_INIT_BIT; + self.packet[5] = (payload_len >> 8) as u8; + self.packet[6] = payload_len as u8; + + self.seq = Some(0); + self.i = + MessageSplitter::consume_data(&mut self.packet[7..], &self.message.payload); + Some(self.packet) + } + Some(seq) => { + // Send the next continuation packet, if any. + if self.i < payload_len { + self.packet[4] = seq; + self.seq = Some(seq + 1); + self.i += MessageSplitter::consume_data( + &mut self.packet[5..], + &self.message.payload[self.i..], + ); + Some(self.packet) + } else { + None + } + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn assert_packet_output_equality(message: Message, expected_packets: Vec) { + let packets: Vec = HidPacketIterator::new(message).unwrap().collect(); + assert_eq!(packets.len(), expected_packets.len()); + for (packet, expected_packet) in packets.iter().zip(expected_packets.iter()) { + assert_eq!(packet as &[u8], expected_packet as &[u8]); + } + } + + #[test] + fn test_hid_packet_iterator_single_packet() { + let message = Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x4C, + payload: vec![0xAA, 0xBB], + }; + let expected_packets: Vec = vec![[ + 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x02, 0xAA, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]]; + assert_packet_output_equality(message, expected_packets); + } + + #[test] + fn test_hid_packet_iterator_big_single_packet() { + let message = Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x4C, + payload: vec![0xAA; 64 - 7], + }; + let expected_packets: Vec = vec![[ + 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x39, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + ]]; + assert_packet_output_equality(message, expected_packets); + } + + #[test] + fn test_hid_packet_iterator_two_packets() { + let message = Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x4C, + payload: vec![0xAA; 64 - 7 + 1], + }; + let expected_packets: Vec = vec![ + [ + 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x3A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + ], + [ + 0x12, 0x34, 0x56, 0x78, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + ]; + assert_packet_output_equality(message, expected_packets); + } + + #[test] + fn test_hid_packet_iterator_two_full_packets() { + let mut payload = vec![0xAA; 64 - 7]; + payload.extend(vec![0xBB; 64 - 5]); + let message = Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0x4C, + payload, + }; + let expected_packets: Vec = vec![ + [ + 0x12, 0x34, 0x56, 0x78, 0xCC, 0x00, 0x74, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + ], + [ + 0x12, 0x34, 0x56, 0x78, 0x00, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + ], + ]; + assert_packet_output_equality(message, expected_packets); + } + + #[test] + fn test_hid_packet_iterator_max_packets() { + let mut payload = vec![0xFF; 64 - 7]; + for i in 0..128 { + payload.extend(vec![i + 1; 64 - 5]); + } + + // Sanity check for the length of the payload. + assert_eq!((64 - 7) + 128 * (64 - 5), 0x1db9); + assert_eq!(7609, 0x1db9); + assert_eq!(payload.len(), 0x1db9); + + let message = Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0xAB, + payload, + }; + + let mut expected_packets = Vec::new(); + expected_packets.push([ + 0x12, 0x34, 0x56, 0x78, 0xAB, 0x1D, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + ]); + for i in 0..128 { + let mut packet: HidPacket = [0; 64]; + packet[0] = 0x12; + packet[1] = 0x34; + packet[2] = 0x56; + packet[3] = 0x78; + packet[4] = i; + for byte in packet.iter_mut().skip(5) { + *byte = i + 1; + } + expected_packets.push(packet); + } + + assert_packet_output_equality(message, expected_packets); + } + + #[test] + fn test_hid_packet_iterator_payload_one_too_large() { + let payload = vec![0xFF; (64 - 7) + 128 * (64 - 5) + 1]; + assert_eq!(payload.len(), 0x1dba); + let message = Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0xAB, + payload, + }; + assert!(HidPacketIterator::new(message).is_none()); + } + + #[test] + fn test_hid_packet_iterator_payload_way_too_large() { + // Check that overflow of u16 doesn't bypass the size limit. + let payload = vec![0xFF; 0x10000]; + let message = Message { + cid: [0x12, 0x34, 0x56, 0x78], + cmd: 0xAB, + payload, + }; + assert!(HidPacketIterator::new(message).is_none()); + } + + // TODO(kaczmarczyck) implement and test limits (maximum bytes and packets) +} diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs new file mode 100644 index 0000000..26740d5 --- /dev/null +++ b/src/ctap/mod.rs @@ -0,0 +1,1297 @@ +// Copyright 2019 Google LLC +// +// 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. + +pub mod command; +#[cfg(feature = "with_ctap1")] +mod ctap1; +pub mod data_formats; +pub mod hid; +mod key_material; +pub mod response; +pub mod status_code; +mod storage; +mod timed_permission; + +use self::command::{ + AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters, + AuthenticatorMakeCredentialParameters, Command, +}; +use self::data_formats::{ + ClientPinSubCommand, CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor, + PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity, + SignatureAlgorithm, +}; +use self::hid::ChannelID; +use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY}; +use self::response::{ + AuthenticatorClientPinResponse, AuthenticatorGetAssertionResponse, + AuthenticatorGetInfoResponse, AuthenticatorMakeCredentialResponse, ResponseData, +}; +use self::status_code::Ctap2StatusCode; +use self::storage::PersistentStore; +#[cfg(feature = "with_ctap1")] +use self::timed_permission::U2fUserPresenceState; +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use byteorder::{BigEndian, ByteOrder}; +use core::convert::TryInto; +#[cfg(feature = "debug_ctap")] +use core::fmt::Write; +use crypto::cbc::{cbc_decrypt, cbc_encrypt}; +use crypto::hmac::{hmac_256, verify_hmac_256, verify_hmac_256_first_128bits}; +use crypto::rng256::Rng256; +use crypto::sha256::Sha256; +use crypto::Hash256; +#[cfg(feature = "debug_ctap")] +use libtock::console::Console; +use libtock::timer::{Duration, Timestamp}; +use subtle::ConstantTimeEq; + +// This flag enables or disables basic attestation for FIDO2. U2F is unaffected by +// this setting. The basic attestation uses the signing key from key_material.rs +// as a batch key. Turn it on if you want attestation. In this case, be aware that +// it is your responsibility to generate your own key material and keep it secret. +const USE_BATCH_ATTESTATION: bool = false; +// The signature counter is currently implemented as a global counter, if you set +// this flag to true. The spec strongly suggests to have per-credential-counters, +// but it means you can't have an infinite amount of credentials anymore. Also, +// since this is the only piece of information that needs writing often, we might +// need a flash storage friendly way to implement this feature. The implemented +// solution is a compromise to be compatible with U2F and not wasting storage. +const USE_SIGNATURE_COUNTER: bool = true; +// Those constants have to be multiples of 16, the AES block size. +const PIN_AUTH_LENGTH: usize = 16; +const PIN_TOKEN_LENGTH: usize = 32; +const PIN_PADDED_LENGTH: usize = 64; +// Our credential ID consists of +// - 16 byte initialization vector for AES-256, +// - 32 byte ECDSA private key for the credential, +// - 32 byte relying party ID hashed with SHA256, +// - 32 byte HMAC-SHA256 over everything else. +pub const ENCRYPTED_CREDENTIAL_ID_SIZE: usize = 112; +const UP_FLAG: u8 = 0x01; +const UV_FLAG: u8 = 0x04; +const AT_FLAG: u8 = 0x40; + +pub const TOUCH_TIMEOUT_MS: isize = 30000; +#[cfg(feature = "with_ctap1")] +const U2F_UP_PROMPT_TIMEOUT: Duration = Duration::from_ms(10000); +const RESET_TIMEOUT_MS: isize = 10000; + +pub const FIDO2_VERSION_STRING: &str = "FIDO_2_0"; +#[cfg(feature = "with_ctap1")] +pub const U2F_VERSION_STRING: &str = "U2F_V2"; + +fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bool { + if pin_auth.len() != PIN_AUTH_LENGTH { + return false; + } + verify_hmac_256_first_128bits::( + hmac_key, + hmac_contents, + array_ref![pin_auth, 0, PIN_AUTH_LENGTH], + ) +} + +// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110 +// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding. +// We change the return value, since we don't need the bool. +fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str { + if max >= s.len() { + s + } else { + while !s.is_char_boundary(max) { + max -= 1; + } + &s[..max] + } +} + +// This struct currently holds all state, not only the persistent memory. The persistent members are +// in the persistent store field. +pub struct CtapState<'a, R: Rng256, CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>> +{ + rng: &'a mut R, + // A function to check user presence, ultimately returning true if user presence was detected, + // false otherwise. + check_user_presence: CheckUserPresence, + persistent_store: PersistentStore, + key_agreement_key: crypto::ecdh::SecKey, + pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH], + consecutive_pin_mismatches: u64, + // This variable will be irreversibly set to false RESET_TIMEOUT_MS milliseconds after boot. + accepts_reset: bool, + #[cfg(feature = "with_ctap1")] + pub u2f_up_state: U2fUserPresenceState, +} + +impl<'a, R, CheckUserPresence> CtapState<'a, R, CheckUserPresence> +where + R: Rng256, + CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>, +{ + pub const PIN_PROTOCOL_VERSION: u64 = 1; + + pub fn new( + rng: &'a mut R, + check_user_presence: CheckUserPresence, + ) -> CtapState<'a, R, CheckUserPresence> { + let key_agreement_key = crypto::ecdh::SecKey::gensk(rng); + let pin_uv_auth_token = rng.gen_uniform_u8x32(); + let persistent_store = PersistentStore::new(rng); + CtapState { + rng, + check_user_presence, + persistent_store, + key_agreement_key, + pin_uv_auth_token, + consecutive_pin_mismatches: 0, + accepts_reset: true, + #[cfg(feature = "with_ctap1")] + u2f_up_state: U2fUserPresenceState::new( + U2F_UP_PROMPT_TIMEOUT, + Duration::from_ms(TOUCH_TIMEOUT_MS), + ), + } + } + + pub fn check_disable_reset(&mut self, timestamp: Timestamp) { + if timestamp - Timestamp::::from_ms(0) > Duration::from_ms(RESET_TIMEOUT_MS) { + self.accepts_reset = false; + } + } + + pub fn increment_global_signature_counter(&mut self) { + if USE_SIGNATURE_COUNTER { + self.persistent_store.incr_global_signature_counter(); + } + } + + // Encrypts the private key and relying party ID hash into a credential ID. Other + // information, such as a user name, are not stored, because encrypted credential IDs + // are used for credentials stored server-side. Also, we want the key handle to be + // compatible with U2F. + pub fn encrypt_key_handle( + &mut self, + private_key: crypto::ecdsa::SecKey, + application: &[u8; 32], + ) -> Vec { + let master_keys = self.persistent_store.master_keys(); + let aes_enc_key = crypto::aes256::EncryptionKey::new(master_keys.encryption); + let mut sk_bytes = [0; 32]; + private_key.to_bytes(&mut sk_bytes); + let mut iv = [0; 16]; + iv.copy_from_slice(&self.rng.gen_uniform_u8x32()[..16]); + + let mut blocks = [[0u8; 16]; 4]; + blocks[0].copy_from_slice(&sk_bytes[..16]); + blocks[1].copy_from_slice(&sk_bytes[16..]); + blocks[2].copy_from_slice(&application[..16]); + blocks[3].copy_from_slice(&application[16..]); + cbc_encrypt(&aes_enc_key, iv, &mut blocks); + + let mut encrypted_id = Vec::with_capacity(ENCRYPTED_CREDENTIAL_ID_SIZE); + encrypted_id.extend(&iv); + for b in &blocks { + encrypted_id.extend(b); + } + let id_hmac = hmac_256::(master_keys.hmac, &encrypted_id[..]); + encrypted_id.extend(&id_hmac); + encrypted_id + } + + // Decrypts a credential ID and writes the private key into a PublicKeyCredentialSource. + // None is returned if the HMAC test fails or the relying party does not match the + // decrypted relying party ID hash. + pub fn decrypt_credential_source( + &self, + credential_id: Vec, + rp_id_hash: &[u8], + ) -> Option { + if credential_id.len() != ENCRYPTED_CREDENTIAL_ID_SIZE { + return None; + } + let master_keys = self.persistent_store.master_keys(); + let payload_size = ENCRYPTED_CREDENTIAL_ID_SIZE - 32; + if !verify_hmac_256::( + master_keys.hmac, + &credential_id[..payload_size], + array_ref![credential_id, payload_size, 32], + ) { + return None; + } + let aes_enc_key = crypto::aes256::EncryptionKey::new(master_keys.encryption); + let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key); + let mut iv = [0; 16]; + iv.copy_from_slice(&credential_id[..16]); + let mut blocks = [[0u8; 16]; 4]; + for i in 0..4 { + blocks[i].copy_from_slice(&credential_id[16 * (i + 1)..16 * (i + 2)]); + } + + cbc_decrypt(&aes_dec_key, iv, &mut blocks); + let mut decrypted_sk = [0; 32]; + let mut decrypted_rp_id_hash = [0; 32]; + decrypted_sk[..16].clone_from_slice(&blocks[0]); + decrypted_sk[16..].clone_from_slice(&blocks[1]); + decrypted_rp_id_hash[..16].clone_from_slice(&blocks[2]); + decrypted_rp_id_hash[16..].clone_from_slice(&blocks[3]); + + if rp_id_hash != decrypted_rp_id_hash { + return None; + } + + let sk_option = crypto::ecdsa::SecKey::from_bytes(&decrypted_sk); + sk_option.map(|sk| PublicKeyCredentialSource { + key_type: PublicKeyCredentialType::PublicKey, + credential_id, + private_key: sk, + rp_id: String::from(""), + user_handle: vec![], + other_ui: None, + }) + } + + pub fn process_command(&mut self, command_cbor: &[u8], cid: ChannelID) -> Vec { + let cmd = Command::deserialize(command_cbor); + #[cfg(feature = "debug_ctap")] + writeln!(&mut Console::new(), "Received command: {:#?}", cmd).unwrap(); + match cmd { + Ok(command) => { + // Correct behavior between CTAP1 and CTAP2 isn't defined yet. Just a guess. + #[cfg(feature = "with_ctap1")] + { + self.u2f_up_state = U2fUserPresenceState::new( + U2F_UP_PROMPT_TIMEOUT, + Duration::from_ms(TOUCH_TIMEOUT_MS), + ); + } + let response = match command { + Command::AuthenticatorMakeCredential(params) => { + self.process_make_credential(params, cid) + } + Command::AuthenticatorGetAssertion(params) => { + self.process_get_assertion(params, cid) + } + Command::AuthenticatorGetInfo => self.process_get_info(), + Command::AuthenticatorClientPin(params) => self.process_client_pin(params), + Command::AuthenticatorReset => self.process_reset(cid), + // TODO(kaczmarczyck) implement GetNextAssertion and FIDO 2.1 commands + _ => unimplemented!(), + }; + #[cfg(feature = "debug_ctap")] + writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap(); + match response { + Ok(response_data) => { + let mut response_vec = vec![0x00]; + if let Some(value) = response_data.into() { + if !cbor::write(value, &mut response_vec) { + response_vec = vec![ + Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR + as u8, + ]; + } + } + response_vec + } + Err(error_code) => vec![error_code as u8], + } + } + Err(error_code) => vec![error_code as u8], + } + } + + fn process_make_credential( + &mut self, + make_credential_params: AuthenticatorMakeCredentialParameters, + cid: ChannelID, + ) -> Result { + let AuthenticatorMakeCredentialParameters { + client_data_hash, + rp, + user, + pub_key_cred_params, + exclude_list, + options, + pin_uv_auth_param, + pin_uv_auth_protocol, + .. + } = make_credential_params; + + if let Some(auth_param) = &pin_uv_auth_param { + // This case was added in FIDO 2.1. + if auth_param.is_empty() { + if self.persistent_store.pin_hash().is_none() { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); + } else { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); + } + } + + match pin_uv_auth_protocol { + Some(protocol) => { + if protocol != CtapState::::PIN_PROTOCOL_VERSION { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); + } + } + None => return Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER), + } + } + + let has_es_256 = pub_key_cred_params + .iter() + .any(|(credential_type, algorithm)| { + // Even though there is only one type now, checking seems safer in + // case of extension so you can't forget to update here. + *credential_type == PublicKeyCredentialType::PublicKey + && *algorithm == SignatureAlgorithm::ES256 as i64 + }); + if !has_es_256 { + return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM); + } + + let rp_id = rp.rp_id; + if let Some(exclude_list) = exclude_list { + for cred_desc in exclude_list { + if self + .persistent_store + .find_credential(&rp_id, &cred_desc.key_id) + .is_some() + { + // Perform this check, so bad actors can't brute force exclude_list + // without user interaction. Discard the user presence check's outcome. + let _ = (self.check_user_presence)(cid); + return Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED); + } + } + } + + // MakeCredential always requires user presence. + // User verification depends on the PIN auth inputs, which are checked here. + let flags = match pin_uv_auth_param { + Some(pin_auth) => { + if self.persistent_store.pin_hash().is_none() { + // Specification is unclear, could be CTAP2_ERR_INVALID_OPTION. + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); + } + if !check_pin_auth(&self.pin_uv_auth_token, &client_data_hash, &pin_auth) { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); + } + UP_FLAG | UV_FLAG | AT_FLAG + } + None => { + if self.persistent_store.pin_hash().is_some() { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_REQUIRED); + } + if options.uv { + return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); + } + UP_FLAG | AT_FLAG + } + }; + + (self.check_user_presence)(cid)?; + + let sk = crypto::ecdsa::SecKey::gensk(self.rng); + let pk = sk.genpk(); + + let rp_id_hash = Sha256::hash(rp_id.as_bytes()); + let credential_id = if options.rk { + let random_id = self.rng.gen_uniform_u8x32().to_vec(); + let credential_source = PublicKeyCredentialSource { + key_type: PublicKeyCredentialType::PublicKey, + credential_id: random_id.clone(), + private_key: sk.clone(), + rp_id, + user_handle: user.user_id, + // This input is user provided, so we crop it to 64 byte for storage. + // The UTF8 encoding is always preserved, so the string might end up shorter. + other_ui: user + .user_display_name + .map(|s| truncate_to_char_boundary(&s, 64).to_string()), + }; + self.persistent_store.store_credential(credential_source)?; + random_id + } else { + self.encrypt_key_handle(sk.clone(), &rp_id_hash) + }; + + let mut auth_data = self.generate_auth_data(&rp_id_hash, flags); + auth_data.extend(&AAGUID); + // The length is fixed to 0x20 or 0x70 and fits one byte. + if credential_id.len() > 0xFF { + return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG); + } + auth_data.extend(vec![0x00, credential_id.len() as u8]); + auth_data.extend(&credential_id); + let cose_key = match pk.to_cose_key() { + Some(cose_key) => cose_key, + None => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR), + }; + auth_data.extend(cose_key); + + let mut signature_data = auth_data.clone(); + signature_data.extend(client_data_hash); + let (signature, x5c) = if USE_BATCH_ATTESTATION { + let attestation_key = + crypto::ecdsa::SecKey::from_bytes(&ATTESTATION_PRIVATE_KEY).unwrap(); + ( + attestation_key.sign_rfc6979::(&signature_data), + Some(vec![ATTESTATION_CERTIFICATE.to_vec()]), + ) + } else { + ( + sk.sign_rfc6979::(&signature_data), + None, + ) + }; + let attestation_statement = PackedAttestationStatement { + alg: SignatureAlgorithm::ES256 as i64, + sig: signature.to_asn1_der(), + x5c, + ecdaa_key_id: None, + }; + Ok(ResponseData::AuthenticatorMakeCredential( + AuthenticatorMakeCredentialResponse { + fmt: String::from("packed"), + auth_data, + att_stmt: attestation_statement, + }, + )) + } + + fn process_get_assertion( + &mut self, + get_assertion_params: AuthenticatorGetAssertionParameters, + cid: ChannelID, + ) -> Result { + let AuthenticatorGetAssertionParameters { + rp_id, + client_data_hash, + allow_list, + options, + pin_uv_auth_param, + pin_uv_auth_protocol, + .. + } = get_assertion_params; + + if let Some(auth_param) = &pin_uv_auth_param { + // This case was added in FIDO 2.1. + if auth_param.is_empty() { + if self.persistent_store.pin_hash().is_none() { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); + } else { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); + } + } + + match pin_uv_auth_protocol { + Some(protocol) => { + if protocol != CtapState::::PIN_PROTOCOL_VERSION { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); + } + } + None => return Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER), + } + } + + // This case was added in FIDO 2.1. + if pin_uv_auth_param == Some(vec![]) { + if self.persistent_store.pin_hash().is_none() { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); + } else { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); + } + } + + if pin_uv_auth_param.is_some() { + match pin_uv_auth_protocol { + Some(protocol) => { + if protocol != CtapState::::PIN_PROTOCOL_VERSION { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); + } + } + None => return Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER), + } + } + + // The user verification bit depends on the existance of PIN auth, whereas + // user presence is requested as an option. + let mut flags = match pin_uv_auth_param { + Some(pin_auth) => { + if self.persistent_store.pin_hash().is_none() { + // Specification is unclear, could be CTAP2_ERR_UNSUPPORTED_OPTION. + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET); + } + if !check_pin_auth(&self.pin_uv_auth_token, &client_data_hash, &pin_auth) { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); + } + UV_FLAG + } + None => { + if options.uv { + // The specification (inconsistently) wants CTAP2_ERR_UNSUPPORTED_OPTION. + return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION); + } + 0x00 + } + }; + if options.up { + flags |= UP_FLAG; + } + + let rp_id_hash = Sha256::hash(rp_id.as_bytes()); + let mut decrypted_credential = None; + let credentials = if let Some(allow_list) = allow_list { + let mut found_credentials = vec![]; + for allowed_credential in allow_list { + match self + .persistent_store + .find_credential(&rp_id, &allowed_credential.key_id) + { + Some(credential) => found_credentials.push(credential), + None => { + if decrypted_credential.is_none() { + decrypted_credential = self + .decrypt_credential_source(allowed_credential.key_id, &rp_id_hash); + } + } + } + } + found_credentials + } else { + // TODO(kaczmarczyck) use GetNextAssertion + self.persistent_store.filter_credential(&rp_id) + }; + + let credential = if let Some(credential) = credentials.first() { + credential + } else { + decrypted_credential + .as_ref() + .ok_or(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS)? + }; + + if options.up { + (self.check_user_presence)(cid)?; + } + + self.increment_global_signature_counter(); + + let auth_data = self.generate_auth_data(&rp_id_hash, flags); + let mut signature_data = auth_data.clone(); + signature_data.extend(client_data_hash); + let signature = credential + .private_key + .sign_rfc6979::(&signature_data); + + let cred_desc = PublicKeyCredentialDescriptor { + key_type: PublicKeyCredentialType::PublicKey, + key_id: credential.credential_id.clone(), + transports: None, // You can set USB as a hint here. + }; + let user = if flags & UV_FLAG != 0 { + Some(PublicKeyCredentialUserEntity { + user_id: credential.user_handle.clone(), + user_name: None, + user_display_name: credential.other_ui.clone(), + user_icon: None, + }) + } else { + None + }; + Ok(ResponseData::AuthenticatorGetAssertion( + AuthenticatorGetAssertionResponse { + credential: Some(cred_desc), + auth_data, + signature: signature.to_asn1_der(), + user, + number_of_credentials: None, + }, + )) + } + + fn process_get_info(&self) -> Result { + let mut options_map = BTreeMap::new(); + // TODO(kaczmarczyck) add FIDO 2.1 options + options_map.insert(String::from("rk"), true); + options_map.insert(String::from("up"), true); + options_map.insert( + String::from("clientPin"), + self.persistent_store.pin_hash().is_some(), + ); + Ok(ResponseData::AuthenticatorGetInfo( + AuthenticatorGetInfoResponse { + versions: vec![ + #[cfg(feature = "with_ctap1")] + String::from(U2F_VERSION_STRING), + String::from(FIDO2_VERSION_STRING), + ], + extensions: Some(vec![]), + aaguid: AAGUID, + options: Some(options_map), + max_msg_size: Some(1024), + pin_protocols: Some(vec![ + CtapState::::PIN_PROTOCOL_VERSION, + ]), + }, + )) + } + + fn check_and_store_new_pin( + &mut self, + aes_dec_key: &crypto::aes256::DecryptionKey, + new_pin_enc: Vec, + ) -> bool { + if new_pin_enc.len() != PIN_PADDED_LENGTH { + return false; + } + let iv = [0; 16]; + // Assuming PIN_PADDED_LENGTH % block_size == 0 here. + let mut blocks = [[0u8; 16]; PIN_PADDED_LENGTH / 16]; + for i in 0..PIN_PADDED_LENGTH / 16 { + blocks[i].copy_from_slice(&new_pin_enc[i * 16..(i + 1) * 16]); + } + cbc_decrypt(aes_dec_key, iv, &mut blocks); + let mut pin = vec![]; + 'pin_block_loop: for block in blocks.iter().take(PIN_PADDED_LENGTH / 16) { + for cur_char in block.iter() { + if *cur_char != 0 { + pin.push(*cur_char); + } else { + break 'pin_block_loop; + } + } + } + if pin.len() < 4 || pin.len() == PIN_PADDED_LENGTH { + return false; + } + let mut pin_hash = [0; 16]; + pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]); + self.persistent_store.set_pin_hash(&pin_hash); + true + } + + fn check_pin_hash_enc( + &mut self, + aes_dec_key: &crypto::aes256::DecryptionKey, + pin_hash_enc: Vec, + ) -> Result<(), Ctap2StatusCode> { + match self.persistent_store.pin_hash() { + Some(pin_hash) => { + if self.consecutive_pin_mismatches >= 3 { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED); + } + // We need to copy the pin hash, because decrementing the pin retries below may + // invalidate the reference (if the page containing the pin hash is compacted). + let pin_hash = pin_hash.to_vec(); + self.persistent_store.decr_pin_retries(); + if pin_hash_enc.len() != PIN_AUTH_LENGTH { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); + } + + let iv = [0; 16]; + let mut blocks = [[0u8; 16]; 1]; + blocks[0].copy_from_slice(&pin_hash_enc[0..PIN_AUTH_LENGTH]); + cbc_decrypt(aes_dec_key, iv, &mut blocks); + + let pin_comparison = array_ref![pin_hash, 0, PIN_AUTH_LENGTH].ct_eq(&blocks[0]); + if !bool::from(pin_comparison) { + self.key_agreement_key = crypto::ecdh::SecKey::gensk(self.rng); + if self.persistent_store.pin_retries() == 0 { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); + } + self.consecutive_pin_mismatches += 1; + if self.consecutive_pin_mismatches >= 3 { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED); + } + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); + } + } + // This status code is not explicitly mentioned in the specification. + None => return Err(Ctap2StatusCode::CTAP2_ERR_PIN_REQUIRED), + } + self.persistent_store.reset_pin_retries(); + self.consecutive_pin_mismatches = 0; + Ok(()) + } + + fn process_get_pin_retries(&self) -> Result { + Ok(AuthenticatorClientPinResponse { + key_agreement: None, + pin_token: None, + retries: Some(self.persistent_store.pin_retries() as u64), + }) + } + + fn process_get_key_agreement(&self) -> Result { + let pk = self.key_agreement_key.genpk(); + Ok(AuthenticatorClientPinResponse { + key_agreement: Some(CoseKey::from(pk)), + pin_token: None, + retries: None, + }) + } + + fn process_set_pin( + &mut self, + key_agreement: CoseKey, + pin_auth: Vec, + new_pin_enc: Vec, + ) -> Result<(), Ctap2StatusCode> { + if self.persistent_store.pin_hash().is_some() { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); + } + let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?; + let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk); + + if !check_pin_auth(&shared_secret, &new_pin_enc, &pin_auth) { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); + } + + let aes_enc_key = crypto::aes256::EncryptionKey::new(&shared_secret); + let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key); + if !self.check_and_store_new_pin(&aes_dec_key, new_pin_enc) { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION); + } + self.persistent_store.reset_pin_retries(); + Ok(()) + } + + fn process_change_pin( + &mut self, + key_agreement: CoseKey, + pin_auth: Vec, + new_pin_enc: Vec, + pin_hash_enc: Vec, + ) -> Result<(), Ctap2StatusCode> { + if self.persistent_store.pin_retries() == 0 { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); + } + let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?; + let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk); + + let mut auth_param_data = new_pin_enc.clone(); + auth_param_data.extend(&pin_hash_enc); + if !check_pin_auth(&shared_secret, &auth_param_data, &pin_auth) { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); + } + + let aes_enc_key = crypto::aes256::EncryptionKey::new(&shared_secret); + let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key); + self.check_pin_hash_enc(&aes_dec_key, pin_hash_enc)?; + + if !self.check_and_store_new_pin(&aes_dec_key, new_pin_enc) { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION); + } + self.pin_uv_auth_token = self.rng.gen_uniform_u8x32(); + Ok(()) + } + + fn process_get_pin_uv_auth_token_using_pin( + &mut self, + key_agreement: CoseKey, + pin_hash_enc: Vec, + ) -> Result { + if self.persistent_store.pin_retries() == 0 { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); + } + let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?; + let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk); + + let aes_enc_key = crypto::aes256::EncryptionKey::new(&shared_secret); + let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key); + self.check_pin_hash_enc(&aes_dec_key, pin_hash_enc)?; + + // Assuming PIN_TOKEN_LENGTH % block_size == 0 here. + let iv = [0; 16]; + let mut blocks = [[0u8; 16]; PIN_TOKEN_LENGTH / 16]; + for (i, item) in blocks.iter_mut().take(PIN_TOKEN_LENGTH / 16).enumerate() { + item.copy_from_slice(&self.pin_uv_auth_token[i * 16..(i + 1) * 16]); + } + cbc_encrypt(&aes_enc_key, iv, &mut blocks); + let mut pin_token = vec![]; + for item in blocks.iter().take(PIN_TOKEN_LENGTH / 16) { + pin_token.extend(item); + } + + Ok(AuthenticatorClientPinResponse { + key_agreement: None, + pin_token: Some(pin_token), + retries: None, + }) + } + + fn process_get_pin_uv_auth_token_using_uv( + &self, + _: CoseKey, + ) -> Result { + Ok(AuthenticatorClientPinResponse { + // User verifications is only supported through PIN currently. + key_agreement: None, + pin_token: Some(vec![]), + retries: None, + }) + } + + fn process_get_uv_retries(&self) -> Result { + // User verifications is only supported through PIN currently. + Ok(AuthenticatorClientPinResponse { + key_agreement: None, + pin_token: None, + retries: Some(0), + }) + } + + fn process_client_pin( + &mut self, + client_pin_params: AuthenticatorClientPinParameters, + ) -> Result { + let AuthenticatorClientPinParameters { + pin_protocol, + sub_command, + key_agreement, + pin_auth, + new_pin_enc, + pin_hash_enc, + } = client_pin_params; + + if pin_protocol != 1 { + return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); + } + + let response = match sub_command { + ClientPinSubCommand::GetPinRetries => Some(self.process_get_pin_retries()?), + ClientPinSubCommand::GetKeyAgreement => Some(self.process_get_key_agreement()?), + ClientPinSubCommand::SetPin => { + self.process_set_pin( + key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + pin_auth.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + new_pin_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + )?; + None + } + ClientPinSubCommand::ChangePin => { + self.process_change_pin( + key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + pin_auth.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + new_pin_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + pin_hash_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + )?; + None + } + ClientPinSubCommand::GetPinUvAuthTokenUsingPin => { + Some(self.process_get_pin_uv_auth_token_using_pin( + key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + pin_hash_enc.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + )?) + } + ClientPinSubCommand::GetPinUvAuthTokenUsingUv => { + Some(self.process_get_pin_uv_auth_token_using_uv( + key_agreement.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?, + )?) + } + ClientPinSubCommand::GetUvRetries => Some(self.process_get_uv_retries()?), + }; + Ok(ResponseData::AuthenticatorClientPin(response)) + } + + fn process_reset(&mut self, cid: ChannelID) -> Result { + // Resets are only possible in the first 10 seconds after booting. + if !self.accepts_reset { + return Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED); + } + (self.check_user_presence)(cid)?; + + self.persistent_store.reset(self.rng); + self.key_agreement_key = crypto::ecdh::SecKey::gensk(self.rng); + self.pin_uv_auth_token = self.rng.gen_uniform_u8x32(); + self.consecutive_pin_mismatches = 0; + #[cfg(feature = "with_ctap1")] + { + self.u2f_up_state = U2fUserPresenceState::new( + U2F_UP_PROMPT_TIMEOUT, + Duration::from_ms(TOUCH_TIMEOUT_MS), + ); + } + Ok(ResponseData::AuthenticatorReset) + } + + pub fn generate_auth_data(&self, rp_id_hash: &[u8], flag_byte: u8) -> Vec { + let mut auth_data = vec![]; + auth_data.extend(rp_id_hash); + auth_data.push(flag_byte); + // The global counter is only increased if USE_SIGNATURE_COUNTER is true. + // It uses a big-endian representation. + let mut signature_counter = [0u8; 4]; + BigEndian::write_u32( + &mut signature_counter, + self.persistent_store.global_signature_counter(), + ); + auth_data.extend(&signature_counter); + auth_data + } +} + +#[cfg(test)] +mod test { + use super::data_formats::{ + GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity, + PublicKeyCredentialUserEntity, + }; + use super::*; + use crypto::rng256::ThreadRng256; + + // The keep-alive logic in the processing of some commands needs a channel ID to send + // keep-alive packets to. + // In tests where we define a dummy user-presence check that immediately returns, the channel + // ID is irrelevant, so we pass this (dummy but valid) value. + const DUMMY_CHANNEL_ID: ChannelID = [0x12, 0x34, 0x56, 0x78]; + + #[test] + fn test_get_info() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID); + + let mut expected_response = vec![0x00, 0xA6, 0x01]; + // The difference here is a longer array of supported versions. + #[cfg(not(feature = "with_ctap1"))] + expected_response.extend(&[ + 0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, 0x02, 0x80, 0x03, 0x50, + ]); + #[cfg(feature = "with_ctap1")] + expected_response.extend(&[ + 0x82, 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, + 0x32, 0x5F, 0x30, 0x02, 0x80, 0x03, 0x50, + ]); + expected_response.extend(&AAGUID); + expected_response.extend(&[ + 0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69, + 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01, + ]); + + assert_eq!(info_reponse, expected_response); + } + + fn create_minimal_make_credential_parameters() -> AuthenticatorMakeCredentialParameters { + let client_data_hash = vec![0xCD]; + let rp = PublicKeyCredentialRpEntity { + rp_id: String::from("example.com"), + rp_name: None, + rp_icon: None, + }; + let user = PublicKeyCredentialUserEntity { + user_id: vec![0xFA, 0xB1, 0xA2], + user_name: None, + user_display_name: None, + user_icon: None, + }; + let pub_key_cred_params = vec![( + PublicKeyCredentialType::PublicKey, + SignatureAlgorithm::ES256 as i64, + )]; + let options = MakeCredentialOptions { + rk: true, + uv: false, + }; + AuthenticatorMakeCredentialParameters { + client_data_hash, + rp, + user, + pub_key_cred_params, + exclude_list: None, + extensions: None, + options, + pin_uv_auth_param: None, + pin_uv_auth_protocol: None, + } + } + + #[test] + fn test_residential_process_make_credential() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + let make_credential_params = create_minimal_make_credential_parameters(); + let make_credential_response = + ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID); + + match make_credential_response.unwrap() { + ResponseData::AuthenticatorMakeCredential(make_credential_response) => { + let AuthenticatorMakeCredentialResponse { + fmt, + auth_data, + att_stmt, + } = make_credential_response; + // The expected response is split to only assert the non-random parts. + assert_eq!(fmt, "packed"); + let mut expected_auth_data = vec![ + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, + 0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, + 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00, + ]; + expected_auth_data.extend(&AAGUID); + expected_auth_data.extend(&[0x00, 0x20]); + assert_eq!( + auth_data[0..expected_auth_data.len()], + expected_auth_data[..] + ); + assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64); + } + _ => panic!("Invalid response type"), + } + } + + #[test] + fn test_non_residential_process_make_credential() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + let mut make_credential_params = create_minimal_make_credential_parameters(); + make_credential_params.options.rk = false; + let make_credential_response = + ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID); + + match make_credential_response.unwrap() { + ResponseData::AuthenticatorMakeCredential(make_credential_response) => { + let AuthenticatorMakeCredentialResponse { + fmt, + auth_data, + att_stmt, + } = make_credential_response; + // The expected response is split to only assert the non-random parts. + assert_eq!(fmt, "packed"); + let mut expected_auth_data = vec![ + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, + 0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, + 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00, + ]; + expected_auth_data.extend(&AAGUID); + expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]); + assert_eq!( + auth_data[0..expected_auth_data.len()], + expected_auth_data[..] + ); + assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64); + } + _ => panic!("Invalid response type"), + } + } + + #[test] + fn test_process_make_credential_unsupported_algorithm() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + let pub_key_cred_params = vec![( + PublicKeyCredentialType::PublicKey, + SignatureAlgorithm::ES256 as i64 + 1, // any different number works + )]; + let mut make_credential_params = create_minimal_make_credential_parameters(); + make_credential_params.pub_key_cred_params = pub_key_cred_params; + let make_credential_response = + ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID); + + assert_eq!( + make_credential_response, + Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM) + ); + } + + #[test] + fn test_process_make_credential_credential_excluded() { + let mut rng = ThreadRng256 {}; + let excluded_private_key = crypto::ecdsa::SecKey::gensk(&mut rng); + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + let excluded_credential_id = vec![0x01, 0x23, 0x45, 0x67]; + let excluded_credential_source = PublicKeyCredentialSource { + key_type: PublicKeyCredentialType::PublicKey, + credential_id: excluded_credential_id.clone(), + private_key: excluded_private_key, + rp_id: String::from("example.com"), + user_handle: vec![], + other_ui: None, + }; + assert!(ctap_state + .persistent_store + .store_credential(excluded_credential_source) + .is_ok()); + + let excluded_credential_descriptor = PublicKeyCredentialDescriptor { + key_type: PublicKeyCredentialType::PublicKey, + key_id: excluded_credential_id, + transports: None, + }; + let exclude_list = Some(vec![excluded_credential_descriptor]); + let mut make_credential_params = create_minimal_make_credential_parameters(); + make_credential_params.exclude_list = exclude_list; + let make_credential_response = + ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID); + + assert_eq!( + make_credential_response, + Err(Ctap2StatusCode::CTAP2_ERR_CREDENTIAL_EXCLUDED) + ); + } + + #[test] + fn test_process_make_credential_cancelled() { + let mut rng = ThreadRng256 {}; + let user_presence_always_cancel = |_| Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL); + let mut ctap_state = CtapState::new(&mut rng, user_presence_always_cancel); + + let make_credential_params = create_minimal_make_credential_parameters(); + let make_credential_response = + ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID); + + assert_eq!( + make_credential_response, + Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL) + ); + } + + #[test] + fn test_residential_process_get_assertion() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + let make_credential_params = create_minimal_make_credential_parameters(); + assert!(ctap_state + .process_make_credential(make_credential_params, DUMMY_CHANNEL_ID) + .is_ok()); + + let get_assertion_params = AuthenticatorGetAssertionParameters { + rp_id: String::from("example.com"), + client_data_hash: vec![0xCD], + allow_list: None, + extensions: None, + options: GetAssertionOptions { + up: false, + uv: false, + }, + pin_uv_auth_param: None, + pin_uv_auth_protocol: None, + }; + let get_assertion_response = + ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID); + + match get_assertion_response.unwrap() { + ResponseData::AuthenticatorGetAssertion(get_assertion_response) => { + let AuthenticatorGetAssertionResponse { + auth_data, + user, + number_of_credentials, + .. + } = get_assertion_response; + let expected_auth_data = vec![ + 0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80, + 0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2, + 0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x00, 0x00, 0x00, 0x00, 0x01, + ]; + assert_eq!(auth_data, expected_auth_data); + assert!(user.is_none()); + assert!(number_of_credentials.is_none()); + } + _ => panic!("Invalid response type"), + } + } + + #[test] + fn test_process_reset() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let private_key = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + let credential_id = vec![0x01, 0x23, 0x45, 0x67]; + let credential_source = PublicKeyCredentialSource { + key_type: PublicKeyCredentialType::PublicKey, + credential_id, + private_key, + rp_id: String::from("example.com"), + user_handle: vec![], + other_ui: None, + }; + assert!(ctap_state + .persistent_store + .store_credential(credential_source) + .is_ok()); + assert!(ctap_state.persistent_store.count_credentials() > 0); + + let reset_reponse = ctap_state.process_command(&[0x07], DUMMY_CHANNEL_ID); + let expected_response = vec![0x00]; + assert_eq!(reset_reponse, expected_response); + assert!(ctap_state.persistent_store.count_credentials() == 0); + } + + #[test] + fn test_process_reset_cancelled() { + let mut rng = ThreadRng256 {}; + let user_presence_always_cancel = |_| Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL); + let mut ctap_state = CtapState::new(&mut rng, user_presence_always_cancel); + + let reset_reponse = ctap_state.process_reset(DUMMY_CHANNEL_ID); + + assert_eq!( + reset_reponse, + Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL) + ); + } + + #[test] + fn test_encrypt_decrypt_credential() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let private_key = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + // Usually, the relying party ID or its hash is provided by the client. + // We are not testing the correctness of our SHA256 here, only if it is checked. + let rp_id_hash = [0x55; 32]; + let encrypted_id = ctap_state.encrypt_key_handle(private_key.clone(), &rp_id_hash); + let decrypted_source = ctap_state + .decrypt_credential_source(encrypted_id, &rp_id_hash) + .unwrap(); + + assert_eq!(private_key, decrypted_source.private_key); + } + + #[test] + fn test_encrypt_decrypt_bad_hmac() { + let mut rng = ThreadRng256 {}; + let user_immediately_present = |_| Ok(()); + let private_key = crypto::ecdsa::SecKey::gensk(&mut rng); + let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); + + // Same as above. + let rp_id_hash = [0x55; 32]; + let encrypted_id = ctap_state.encrypt_key_handle(private_key, &rp_id_hash); + for i in 0..encrypted_id.len() { + let mut modified_id = encrypted_id.clone(); + modified_id[i] ^= 0x01; + assert!(ctap_state + .decrypt_credential_source(modified_id, &rp_id_hash) + .is_none()); + } + } +} diff --git a/src/ctap/response.rs b/src/ctap/response.rs new file mode 100644 index 0000000..4b8a089 --- /dev/null +++ b/src/ctap/response.rs @@ -0,0 +1,267 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::data_formats::{ + CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor, + PublicKeyCredentialUserEntity, +}; +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; + +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] +pub enum ResponseData { + AuthenticatorMakeCredential(AuthenticatorMakeCredentialResponse), + AuthenticatorGetAssertion(AuthenticatorGetAssertionResponse), + AuthenticatorGetNextAssertion(AuthenticatorGetAssertionResponse), + AuthenticatorGetInfo(AuthenticatorGetInfoResponse), + AuthenticatorClientPin(Option), + AuthenticatorReset, +} + +impl From for Option { + fn from(response: ResponseData) -> Self { + match response { + ResponseData::AuthenticatorMakeCredential(data) => Some(data.into()), + ResponseData::AuthenticatorGetAssertion(data) => Some(data.into()), + ResponseData::AuthenticatorGetNextAssertion(data) => Some(data.into()), + ResponseData::AuthenticatorGetInfo(data) => Some(data.into()), + ResponseData::AuthenticatorClientPin(Some(data)) => Some(data.into()), + ResponseData::AuthenticatorClientPin(None) => None, + ResponseData::AuthenticatorReset => None, + } + } +} + +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] +pub struct AuthenticatorMakeCredentialResponse { + pub fmt: String, + pub auth_data: Vec, + pub att_stmt: PackedAttestationStatement, +} + +impl From for cbor::Value { + fn from(make_credential_response: AuthenticatorMakeCredentialResponse) -> Self { + let AuthenticatorMakeCredentialResponse { + fmt, + auth_data, + att_stmt, + } = make_credential_response; + + cbor_map_options! { + 1 => fmt, + 2 => auth_data, + 3 => att_stmt, + } + } +} + +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] +pub struct AuthenticatorGetAssertionResponse { + pub credential: Option, + pub auth_data: Vec, + pub signature: Vec, + pub user: Option, + pub number_of_credentials: Option, +} + +impl From for cbor::Value { + fn from(get_assertion_response: AuthenticatorGetAssertionResponse) -> Self { + let AuthenticatorGetAssertionResponse { + credential, + auth_data, + signature, + user, + number_of_credentials, + } = get_assertion_response; + + cbor_map_options! { + 1 => credential, + 2 => auth_data, + 3 => signature, + 4 => user, + 5 => number_of_credentials, + } + } +} + +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] +pub struct AuthenticatorGetInfoResponse { + // TODO(kaczmarczyck) add fields from 2.1 + pub versions: Vec, + pub extensions: Option>, + pub aaguid: [u8; 16], + pub options: Option>, + pub max_msg_size: Option, + pub pin_protocols: Option>, +} + +impl From for cbor::Value { + fn from(get_info_response: AuthenticatorGetInfoResponse) -> Self { + let AuthenticatorGetInfoResponse { + versions, + extensions, + aaguid, + options, + max_msg_size, + pin_protocols, + } = get_info_response; + + let options_cbor: Option = options.map(|options| { + let option_map: BTreeMap<_, _> = options + .into_iter() + .map(|(key, value)| (cbor_text!(key), cbor_bool!(value))) + .collect(); + cbor_map_btree!(option_map) + }); + + cbor_map_options! { + 1 => cbor_array_vec!(versions), + 2 => extensions.map(|vec| cbor_array_vec!(vec)), + 3 => &aaguid, + 4 => options_cbor, + 5 => max_msg_size, + 6 => pin_protocols.map(|vec| cbor_array_vec!(vec)), + } + } +} + +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] +pub struct AuthenticatorClientPinResponse { + pub key_agreement: Option, + pub pin_token: Option>, + pub retries: Option, +} + +impl From for cbor::Value { + fn from(client_pin_response: AuthenticatorClientPinResponse) -> Self { + let AuthenticatorClientPinResponse { + key_agreement, + pin_token, + retries, + } = client_pin_response; + + cbor_map_options! { + 1 => key_agreement.map(|cose_key| cbor_map_btree!(cose_key.0)), + 2 => pin_token, + 3 => retries, + } + } +} + +#[cfg(test)] +mod test { + use super::super::data_formats::PackedAttestationStatement; + use super::*; + + #[test] + fn test_make_credential_into_cbor() { + let certificate: cbor::values::KeyType = cbor_bytes![vec![0x5C, 0x5C, 0x5C, 0x5C]]; + let att_stmt = PackedAttestationStatement { + alg: 1, + sig: vec![0x55, 0x55, 0x55, 0x55], + x5c: Some(vec![vec![0x5C, 0x5C, 0x5C, 0x5C]]), + ecdaa_key_id: Some(vec![0xEC, 0xDA, 0x1D]), + }; + let cbor_packed_attestation_statement = cbor_map! { + "alg" => 1, + "sig" => vec![0x55, 0x55, 0x55, 0x55], + "x5c" => cbor_array_vec![vec![certificate]], + "ecdaaKeyId" => vec![0xEC, 0xDA, 0x1D], + }; + + let make_credential_response = AuthenticatorMakeCredentialResponse { + fmt: "packed".to_string(), + auth_data: vec![0xAD], + att_stmt, + }; + let response_cbor: Option = + ResponseData::AuthenticatorMakeCredential(make_credential_response).into(); + let expected_cbor = cbor_map_options! { + 1 => "packed", + 2 => vec![0xAD], + 3 => cbor_packed_attestation_statement, + }; + assert_eq!(response_cbor, Some(expected_cbor)); + } + + #[test] + fn test_get_assertion_into_cbor() { + let get_assertion_response = AuthenticatorGetAssertionResponse { + credential: None, + auth_data: vec![0xAD], + signature: vec![0x51], + user: None, + number_of_credentials: None, + }; + let response_cbor: Option = + ResponseData::AuthenticatorGetAssertion(get_assertion_response).into(); + let expected_cbor = cbor_map_options! { + 2 => vec![0xAD], + 3 => vec![0x51], + }; + assert_eq!(response_cbor, Some(expected_cbor)); + } + + #[test] + fn test_get_info_into_cbor() { + let get_info_response = AuthenticatorGetInfoResponse { + versions: vec!["FIDO_2_0".to_string()], + extensions: None, + aaguid: [0x00; 16], + options: None, + max_msg_size: None, + pin_protocols: None, + }; + let response_cbor: Option = + ResponseData::AuthenticatorGetInfo(get_info_response).into(); + let expected_cbor = cbor_map_options! { + 1 => cbor_array_vec![vec!["FIDO_2_0"]], + 3 => vec![0x00; 16], + }; + assert_eq!(response_cbor, Some(expected_cbor)); + } + + #[test] + fn test_used_client_pin_into_cbor() { + let client_pin_response = AuthenticatorClientPinResponse { + key_agreement: None, + pin_token: Some(vec![70]), + retries: None, + }; + let response_cbor: Option = + ResponseData::AuthenticatorClientPin(Some(client_pin_response)).into(); + let expected_cbor = cbor_map_options! { + 2 => vec![70], + }; + assert_eq!(response_cbor, Some(expected_cbor)); + } + + #[test] + fn test_empty_client_pin_into_cbor() { + let response_cbor: Option = ResponseData::AuthenticatorClientPin(None).into(); + assert_eq!(response_cbor, None); + } + + #[test] + fn test_reset_into_cbor() { + let response_cbor: Option = ResponseData::AuthenticatorReset.into(); + assert_eq!(response_cbor, None); + } +} diff --git a/src/ctap/status_code.rs b/src/ctap/status_code.rs new file mode 100644 index 0000000..1e0c2bf --- /dev/null +++ b/src/ctap/status_code.rs @@ -0,0 +1,71 @@ +// Copyright 2019 Google LLC +// +// 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. + +// CTAP specification (version 20190130) section 6.3 +// For now, only the CTAP2 codes are here, the CTAP1 are not included. +#[allow(non_camel_case_types)] +#[allow(dead_code)] +#[derive(Debug, PartialEq)] +pub enum Ctap2StatusCode { + CTAP2_OK = 0x00, + CTAP1_ERR_INVALID_COMMAND = 0x01, + CTAP1_ERR_INVALID_PARAMETER = 0x02, + CTAP1_ERR_INVALID_LENGTH = 0x03, + CTAP1_ERR_INVALID_SEQ = 0x04, + CTAP1_ERR_TIMEOUT = 0x05, + CTAP1_ERR_CHANNEL_BUSY = 0x06, + CTAP1_ERR_LOCK_REQUIRED = 0x0A, + CTAP1_ERR_INVALID_CHANNEL = 0x0B, + CTAP2_ERR_CBOR_UNEXPECTED_TYPE = 0x11, + CTAP2_ERR_INVALID_CBOR = 0x12, + CTAP2_ERR_MISSING_PARAMETER = 0x14, + CTAP2_ERR_LIMIT_EXCEEDED = 0x15, + CTAP2_ERR_UNSUPPORTED_EXTENSION = 0x16, + CTAP2_ERR_CREDENTIAL_EXCLUDED = 0x19, + CTAP2_ERR_PROCESSING = 0x21, + CTAP2_ERR_INVALID_CREDENTIAL = 0x22, + CTAP2_ERR_USER_ACTION_PENDING = 0x23, + CTAP2_ERR_OPERATION_PENDING = 0x24, + CTAP2_ERR_NO_OPERATIONS = 0x25, + CTAP2_ERR_UNSUPPORTED_ALGORITHM = 0x26, + CTAP2_ERR_OPERATION_DENIED = 0x27, + CTAP2_ERR_KEY_STORE_FULL = 0x28, + CTAP2_ERR_NO_OPERATION_PENDING = 0x2A, + CTAP2_ERR_UNSUPPORTED_OPTION = 0x2B, + CTAP2_ERR_INVALID_OPTION = 0x2C, + CTAP2_ERR_KEEPALIVE_CANCEL = 0x2D, + CTAP2_ERR_NO_CREDENTIALS = 0x2E, + CTAP2_ERR_USER_ACTION_TIMEOUT = 0x2F, + CTAP2_ERR_NOT_ALLOWED = 0x30, + CTAP2_ERR_PIN_INVALID = 0x31, + CTAP2_ERR_PIN_BLOCKED = 0x32, + CTAP2_ERR_PIN_AUTH_INVALID = 0x33, + CTAP2_ERR_PIN_AUTH_BLOCKED = 0x34, + CTAP2_ERR_PIN_NOT_SET = 0x35, + CTAP2_ERR_PIN_REQUIRED = 0x36, + CTAP2_ERR_PIN_POLICY_VIOLATION = 0x37, + CTAP2_ERR_PIN_TOKEN_EXPIRED = 0x38, + CTAP2_ERR_REQUEST_TOO_LARGE = 0x39, + CTAP2_ERR_ACTION_TIMEOUT = 0x3A, + CTAP2_ERR_UP_REQUIRED = 0x3B, + CTAP2_ERR_UV_BLOCKED = 0x3C, + CTAP1_ERR_OTHER = 0x7F, + CTAP2_ERR_SPEC_LAST = 0xDF, + CTAP2_ERR_EXTENSION_FIRST = 0xE0, + CTAP2_ERR_EXTENSION_LAST = 0xEF, + // CTAP2_ERR_VENDOR_FIRST = 0xF0, + CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG = 0xF0, + CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR = 0xF1, + CTAP2_ERR_VENDOR_LAST = 0xFF, +} diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs new file mode 100644 index 0000000..e693a13 --- /dev/null +++ b/src/ctap/storage.rs @@ -0,0 +1,682 @@ +// Copyright 2019 Google LLC +// +// 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. + +use crate::crypto::rng256::Rng256; +use crate::ctap::data_formats::PublicKeyCredentialSource; +use crate::ctap::status_code::Ctap2StatusCode; +use crate::ctap::PIN_AUTH_LENGTH; +use alloc::string::String; +use alloc::vec::Vec; +use core::convert::TryInto; +use ctap2::embedded_flash::{self, StoreConfig, StoreEntry, StoreError, StoreIndex}; + +#[cfg(test)] +type Storage = embedded_flash::BufferStorage; +#[cfg(not(test))] +type Storage = embedded_flash::SyscallStorage; + +// Those constants may be modified before compilation to tune the behavior of the key. +// +// The number of pages should be at least 2 and at most what the flash can hold. There should be no +// reason to put a small number here, except that the latency of flash operations depends on the +// number of pages. This will improve in the future. Currently, using 20 pages gives 65ms per +// operation. The rule of thumb is 3.5ms per additional page. +// +// Limiting the number of residential keys permits to ensure a minimum number of counter increments. +// Let: +// - P the number of pages (NUM_PAGES) +// - K the maximum number of residential keys (MAX_SUPPORTED_RESIDENTIAL_KEYS) +// - S the maximum size of a residential key (about 500) +// - C the number of erase cycles (10000) +// - I the minimum number of counter increments +// +// We have: I = ((P - 1) * 4092 - K * S) / 12 * C +// +// With P=20 and K=150, we have I > 2M which is enough for 500 increments per day for 10 years. +const NUM_PAGES: usize = 20; +const MAX_SUPPORTED_RESIDENTIAL_KEYS: usize = 150; + +// List of tags. They should all be unique. And there should be less than NUM_TAGS. +const TAG_CREDENTIAL: usize = 0; +const GLOBAL_SIGNATURE_COUNTER: usize = 1; +const MASTER_KEYS: usize = 2; +const PIN_HASH: usize = 3; +const PIN_RETRIES: usize = 4; +const NUM_TAGS: usize = 5; + +const MAX_PIN_RETRIES: u8 = 6; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +enum Key { + // TODO(cretin): Test whether this doesn't consume too much memory. Otherwise, we can use less + // keys. Either only a simple enum value for all credentials, or group by rp_id. + Credential { + rp_id: Option, + credential_id: Option>, + user_handle: Option>, + }, + GlobalSignatureCounter, + MasterKeys, + PinHash, + PinRetries, +} + +pub struct MasterKeys<'a> { + pub encryption: &'a [u8; 32], + pub hmac: &'a [u8; 32], +} + +struct Config; + +impl StoreConfig for Config { + type Key = Key; + + fn num_tags(&self) -> usize { + NUM_TAGS + } + + fn keys(&self, entry: StoreEntry, mut add: impl FnMut(Key)) { + match entry.tag { + TAG_CREDENTIAL => { + let credential = match deserialize_credential(entry.data) { + None => { + debug_assert!(false); + return; + } + Some(credential) => credential, + }; + add(Key::Credential { + rp_id: Some(credential.rp_id.clone()), + credential_id: Some(credential.credential_id), + user_handle: None, + }); + add(Key::Credential { + rp_id: Some(credential.rp_id.clone()), + credential_id: None, + user_handle: None, + }); + add(Key::Credential { + rp_id: Some(credential.rp_id), + credential_id: None, + user_handle: Some(credential.user_handle), + }); + add(Key::Credential { + rp_id: None, + credential_id: None, + user_handle: None, + }); + } + GLOBAL_SIGNATURE_COUNTER => add(Key::GlobalSignatureCounter), + MASTER_KEYS => add(Key::MasterKeys), + PIN_HASH => add(Key::PinHash), + PIN_RETRIES => add(Key::PinRetries), + _ => debug_assert!(false), + } + } +} + +pub struct PersistentStore { + store: embedded_flash::Store, +} + +const PAGE_SIZE: usize = 0x1000; +const STORE_SIZE: usize = NUM_PAGES * PAGE_SIZE; + +#[cfg(not(test))] +#[link_section = ".app_state"] +static STORE: [u8; STORE_SIZE] = [0xff; STORE_SIZE]; + +impl PersistentStore { + /// Gives access to the persistent store. + /// + /// # Safety + /// + /// This should be at most one instance of persistent store per program lifetime. + pub fn new(rng: &mut impl Rng256) -> PersistentStore { + #[cfg(not(test))] + let storage = PersistentStore::new_prod_storage(); + #[cfg(test)] + let storage = PersistentStore::new_test_storage(); + let mut store = PersistentStore { + store: embedded_flash::Store::new(storage, Config).unwrap(), + }; + store.init(rng); + store + } + + #[cfg(not(test))] + fn new_prod_storage() -> Storage { + let store = unsafe { + // Safety: The store cannot alias because this function is called only once. + core::slice::from_raw_parts_mut(STORE.as_ptr() as *mut u8, STORE_SIZE) + }; + unsafe { + // Safety: The store is in a writeable flash region. + Storage::new(store).unwrap() + } + } + + #[cfg(test)] + fn new_test_storage() -> Storage { + let store = vec![0xff; STORE_SIZE].into_boxed_slice(); + let options = embedded_flash::BufferOptions { + word_size: 4, + page_size: PAGE_SIZE, + max_word_writes: 2, + max_page_erases: 10000, + strict_write: true, + }; + Storage::new(store, options) + } + + fn init(&mut self, rng: &mut impl Rng256) { + if self.store.find_one(&Key::MasterKeys).is_none() { + let master_encryption_key = rng.gen_uniform_u8x32(); + let master_hmac_key = rng.gen_uniform_u8x32(); + let mut master_keys = Vec::with_capacity(64); + master_keys.extend_from_slice(&master_encryption_key); + master_keys.extend_from_slice(&master_hmac_key); + self.store + .insert(StoreEntry { + tag: MASTER_KEYS, + data: &master_keys, + }) + .unwrap(); + } + if self.store.find_one(&Key::PinRetries).is_none() { + self.store + .insert(StoreEntry { + tag: PIN_RETRIES, + data: &[MAX_PIN_RETRIES], + }) + .unwrap(); + } + } + + pub fn find_credential( + &self, + rp_id: &str, + credential_id: &[u8], + ) -> Option { + let key = Key::Credential { + rp_id: Some(rp_id.into()), + credential_id: Some(credential_id.into()), + user_handle: None, + }; + let (_, entry) = self.store.find_one(&key)?; + debug_assert_eq!(entry.tag, TAG_CREDENTIAL); + let result = deserialize_credential(entry.data); + debug_assert!(result.is_some()); + result + } + + pub fn store_credential( + &mut self, + credential: PublicKeyCredentialSource, + ) -> Result<(), Ctap2StatusCode> { + let key = Key::Credential { + rp_id: Some(credential.rp_id.clone()), + credential_id: None, + user_handle: Some(credential.user_handle.clone()), + }; + let old_entry = self.store.find_one(&key); + if old_entry.is_none() && self.count_credentials() >= MAX_SUPPORTED_RESIDENTIAL_KEYS { + return Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL); + } + let credential = serialize_credential(credential)?; + let new_entry = StoreEntry { + tag: TAG_CREDENTIAL, + data: &credential, + }; + match old_entry { + None => self.store.insert(new_entry)?, + Some((index, old_entry)) => { + debug_assert_eq!(old_entry.tag, TAG_CREDENTIAL); + self.store.replace(index, new_entry)? + } + }; + Ok(()) + } + + pub fn filter_credential(&self, rp_id: &str) -> Vec { + self.store + .find_all(&Key::Credential { + rp_id: Some(rp_id.into()), + credential_id: None, + user_handle: None, + }) + .filter_map(|(_, entry)| { + debug_assert_eq!(entry.tag, TAG_CREDENTIAL); + let credential = deserialize_credential(entry.data); + debug_assert!(credential.is_some()); + credential + }) + .collect() + } + + pub fn count_credentials(&self) -> usize { + self.store + .find_all(&Key::Credential { + rp_id: None, + credential_id: None, + user_handle: None, + }) + .count() + } + + pub fn global_signature_counter(&self) -> u32 { + self.store + .find_one(&Key::GlobalSignatureCounter) + .map_or(0, |(_, entry)| { + u32::from_ne_bytes(*array_ref!(entry.data, 0, 4)) + }) + } + + pub fn incr_global_signature_counter(&mut self) { + let mut buffer = [0; core::mem::size_of::()]; + match self.store.find_one(&Key::GlobalSignatureCounter) { + None => { + buffer.copy_from_slice(&1u32.to_ne_bytes()); + self.store + .insert(StoreEntry { + tag: GLOBAL_SIGNATURE_COUNTER, + data: &buffer, + }) + .unwrap(); + } + Some((index, entry)) => { + let value = u32::from_ne_bytes(*array_ref!(entry.data, 0, 4)); + // In hopes that servers handle the wrapping gracefully. + buffer.copy_from_slice(&value.wrapping_add(1).to_ne_bytes()); + self.store + .replace( + index, + StoreEntry { + tag: GLOBAL_SIGNATURE_COUNTER, + data: &buffer, + }, + ) + .unwrap(); + } + } + } + + pub fn master_keys(&self) -> MasterKeys { + // We have as invariant that there is always exactly one MasterKeys entry in the store. + let (_, entry) = self.store.find_one(&Key::MasterKeys).unwrap(); + let data = entry.data; + // And this entry is well formed: the encryption key followed by the hmac key. + let encryption = array_ref!(data, 0, 32); + let hmac = array_ref!(data, 32, 32); + MasterKeys { encryption, hmac } + } + + pub fn pin_hash(&self) -> Option<&[u8; PIN_AUTH_LENGTH]> { + self.store + .find_one(&Key::PinHash) + .map(|(_, entry)| array_ref!(entry.data, 0, PIN_AUTH_LENGTH)) + } + + pub fn set_pin_hash(&mut self, pin_hash: &[u8; PIN_AUTH_LENGTH]) { + let entry = StoreEntry { + tag: PIN_HASH, + data: pin_hash, + }; + match self.store.find_one(&Key::PinHash) { + None => self.store.insert(entry).unwrap(), + Some((index, _)) => { + self.store.replace(index, entry).unwrap(); + } + } + } + + fn pin_retries_entry(&self) -> (StoreIndex, u8) { + let (index, entry) = self.store.find_one(&Key::PinRetries).unwrap(); + let data = entry.data; + debug_assert_eq!(data.len(), 1); + (index, data[0]) + } + + pub fn pin_retries(&self) -> u8 { + self.pin_retries_entry().1 + } + + pub fn decr_pin_retries(&mut self) { + let (index, old_value) = self.pin_retries_entry(); + let new_value = old_value.saturating_sub(1); + self.store + .replace( + index, + StoreEntry { + tag: PIN_RETRIES, + data: &[new_value], + }, + ) + .unwrap(); + } + + pub fn reset_pin_retries(&mut self) { + let (index, _) = self.pin_retries_entry(); + self.store + .replace( + index, + StoreEntry { + tag: PIN_RETRIES, + data: &[MAX_PIN_RETRIES], + }, + ) + .unwrap(); + } + + pub fn reset(&mut self, rng: &mut impl Rng256) { + loop { + let index = { + let mut iter = self.store.iter(); + match iter.next() { + None => break, + Some((index, _)) => index, + } + }; + self.store.delete(index).unwrap(); + } + self.init(rng); + } +} + +impl From for Ctap2StatusCode { + fn from(error: StoreError) -> Ctap2StatusCode { + match error { + StoreError::StoreFull => Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL, + StoreError::InvalidTag => unreachable!(), + StoreError::InvalidPrecondition => unreachable!(), + } + } +} + +fn deserialize_credential(data: &[u8]) -> Option { + let cbor = cbor::read(data).ok()?; + cbor.try_into().ok() +} + +fn serialize_credential(credential: PublicKeyCredentialSource) -> Result, Ctap2StatusCode> { + let mut data = Vec::new(); + if cbor::write(credential.into(), &mut data) { + Ok(data) + } else { + Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CREDENTIAL) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::crypto; + use crate::crypto::rng256::{Rng256, ThreadRng256}; + use crate::ctap::data_formats::{PublicKeyCredentialSource, PublicKeyCredentialType}; + + fn create_credential_source( + rng: &mut ThreadRng256, + rp_id: &str, + user_handle: Vec, + ) -> PublicKeyCredentialSource { + let private_key = crypto::ecdsa::SecKey::gensk(rng); + PublicKeyCredentialSource { + key_type: PublicKeyCredentialType::PublicKey, + credential_id: rng.gen_uniform_u8x32().to_vec(), + private_key, + rp_id: String::from(rp_id), + user_handle, + other_ui: None, + } + } + + #[test] + fn format_overhead() { + // nRF52840 NVMC + const WORD_SIZE: usize = 4; + const PAGE_SIZE: usize = 0x1000; + const NUM_PAGES: usize = 100; + let store = vec![0xff; NUM_PAGES * PAGE_SIZE].into_boxed_slice(); + let options = embedded_flash::BufferOptions { + word_size: WORD_SIZE, + page_size: PAGE_SIZE, + max_word_writes: 2, + max_page_erases: 10000, + strict_write: true, + }; + let storage = Storage::new(store, options); + let store = embedded_flash::Store::new(storage, Config).unwrap(); + // We can replace 3 bytes with minimal overhead. + assert_eq!(store.replace_len(0), 2 * WORD_SIZE); + assert_eq!(store.replace_len(3), 2 * WORD_SIZE); + assert_eq!(store.replace_len(4), 3 * WORD_SIZE); + } + + #[test] + fn test_store() { + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + assert_eq!(persistent_store.count_credentials(), 0); + let credential_source = create_credential_source(&mut rng, "example.com", vec![]); + assert!(persistent_store.store_credential(credential_source).is_ok()); + assert!(persistent_store.count_credentials() > 0); + } + + #[test] + #[allow(clippy::assertions_on_constants)] + fn test_fill_store() { + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + assert_eq!(persistent_store.count_credentials(), 0); + + // To make this test work for bigger storages, implement better int -> Vec conversion. + assert!(MAX_SUPPORTED_RESIDENTIAL_KEYS < 256); + for i in 0..MAX_SUPPORTED_RESIDENTIAL_KEYS { + let credential_source = + create_credential_source(&mut rng, "example.com", vec![i as u8]); + assert!(persistent_store.store_credential(credential_source).is_ok()); + assert_eq!(persistent_store.count_credentials(), i + 1); + } + let credential_source = create_credential_source( + &mut rng, + "example.com", + vec![MAX_SUPPORTED_RESIDENTIAL_KEYS as u8], + ); + assert_eq!( + persistent_store.store_credential(credential_source), + Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL) + ); + assert_eq!( + persistent_store.count_credentials(), + MAX_SUPPORTED_RESIDENTIAL_KEYS + ); + } + + #[test] + #[allow(clippy::assertions_on_constants)] + fn test_overwrite() { + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + assert_eq!(persistent_store.count_credentials(), 0); + // These should have different IDs. + let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]); + let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x00]); + let expected_credential = credential_source1.clone(); + + assert!(persistent_store + .store_credential(credential_source0) + .is_ok()); + assert!(persistent_store + .store_credential(credential_source1) + .is_ok()); + assert_eq!(persistent_store.count_credentials(), 1); + assert_eq!( + &persistent_store.filter_credential("example.com"), + &[expected_credential] + ); + + // To make this test work for bigger storages, implement better int -> Vec conversion. + assert!(MAX_SUPPORTED_RESIDENTIAL_KEYS < 256); + for i in 0..MAX_SUPPORTED_RESIDENTIAL_KEYS { + let credential_source = + create_credential_source(&mut rng, "example.com", vec![i as u8]); + assert!(persistent_store.store_credential(credential_source).is_ok()); + assert_eq!(persistent_store.count_credentials(), i + 1); + } + let credential_source = create_credential_source( + &mut rng, + "example.com", + vec![MAX_SUPPORTED_RESIDENTIAL_KEYS as u8], + ); + assert_eq!( + persistent_store.store_credential(credential_source), + Err(Ctap2StatusCode::CTAP2_ERR_KEY_STORE_FULL) + ); + assert_eq!( + persistent_store.count_credentials(), + MAX_SUPPORTED_RESIDENTIAL_KEYS + ); + } + + #[test] + fn test_filter() { + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + assert_eq!(persistent_store.count_credentials(), 0); + let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]); + let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]); + let credential_source2 = + create_credential_source(&mut rng, "another.example.com", vec![0x02]); + let id0 = credential_source0.credential_id.clone(); + let id1 = credential_source1.credential_id.clone(); + assert!(persistent_store + .store_credential(credential_source0) + .is_ok()); + assert!(persistent_store + .store_credential(credential_source1) + .is_ok()); + assert!(persistent_store + .store_credential(credential_source2) + .is_ok()); + + let filtered_credentials = persistent_store.filter_credential("example.com"); + assert_eq!(filtered_credentials.len(), 2); + assert!( + (filtered_credentials[0].credential_id == id0 + && filtered_credentials[1].credential_id == id1) + || (filtered_credentials[1].credential_id == id0 + && filtered_credentials[0].credential_id == id1) + ); + } + + #[test] + fn test_find() { + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + assert_eq!(persistent_store.count_credentials(), 0); + let credential_source0 = create_credential_source(&mut rng, "example.com", vec![0x00]); + let credential_source1 = create_credential_source(&mut rng, "example.com", vec![0x01]); + let id0 = credential_source0.credential_id.clone(); + let key0 = credential_source0.private_key.clone(); + assert!(persistent_store + .store_credential(credential_source0) + .is_ok()); + assert!(persistent_store + .store_credential(credential_source1) + .is_ok()); + + let no_credential = persistent_store.find_credential("another.example.com", &id0); + assert_eq!(no_credential, None); + let found_credential = persistent_store.find_credential("example.com", &id0); + let expected_credential = PublicKeyCredentialSource { + key_type: PublicKeyCredentialType::PublicKey, + credential_id: id0, + private_key: key0, + rp_id: String::from("example.com"), + user_handle: vec![0x00], + other_ui: None, + }; + assert_eq!(found_credential, Some(expected_credential)); + } + + #[test] + fn test_master_keys() { + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + + // Master keys stay the same between resets. + let master_keys_1 = persistent_store.master_keys(); + let master_keys_2 = persistent_store.master_keys(); + assert_eq!(master_keys_2.encryption, master_keys_1.encryption); + assert_eq!(master_keys_2.hmac, master_keys_1.hmac); + + // Master keys change after reset. This test may fail if the random generator produces the + // same keys. + let master_encryption_key = master_keys_1.encryption.to_vec(); + let master_hmac_key = master_keys_1.hmac.to_vec(); + persistent_store.reset(&mut rng); + let master_keys_3 = persistent_store.master_keys(); + assert!(master_keys_3.encryption as &[u8] != &master_encryption_key[..]); + assert!(master_keys_3.hmac as &[u8] != &master_hmac_key[..]); + } + + #[test] + fn test_pin_hash() { + use crate::ctap::PIN_AUTH_LENGTH; + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + + // Pin hash is initially not set. + assert!(persistent_store.pin_hash().is_none()); + + // Setting the pin hash sets the pin hash. + let random_data = rng.gen_uniform_u8x32(); + assert_eq!(random_data.len(), 2 * PIN_AUTH_LENGTH); + let pin_hash_1 = array_ref!(random_data, 0, PIN_AUTH_LENGTH); + let pin_hash_2 = array_ref!(random_data, PIN_AUTH_LENGTH, PIN_AUTH_LENGTH); + persistent_store.set_pin_hash(&pin_hash_1); + assert_eq!(persistent_store.pin_hash(), Some(pin_hash_1)); + assert_eq!(persistent_store.pin_hash(), Some(pin_hash_1)); + persistent_store.set_pin_hash(&pin_hash_2); + assert_eq!(persistent_store.pin_hash(), Some(pin_hash_2)); + assert_eq!(persistent_store.pin_hash(), Some(pin_hash_2)); + + // Resetting the storage resets the pin hash. + persistent_store.reset(&mut rng); + assert!(persistent_store.pin_hash().is_none()); + } + + #[test] + fn test_pin_retries() { + let mut rng = ThreadRng256 {}; + let mut persistent_store = PersistentStore::new(&mut rng); + + // The pin retries is initially at the maximum. + assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES); + + // Decrementing the pin retries decrements the pin retries. + for pin_retries in (0..MAX_PIN_RETRIES).rev() { + persistent_store.decr_pin_retries(); + assert_eq!(persistent_store.pin_retries(), pin_retries); + } + + // Decrementing the pin retries after zero does not modify the pin retries. + persistent_store.decr_pin_retries(); + assert_eq!(persistent_store.pin_retries(), 0); + + // Resetting the pin retries resets the pin retries. + persistent_store.reset_pin_retries(); + assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES); + } +} diff --git a/src/ctap/timed_permission.rs b/src/ctap/timed_permission.rs new file mode 100644 index 0000000..d56ba09 --- /dev/null +++ b/src/ctap/timed_permission.rs @@ -0,0 +1,188 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2 (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 +// +// 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. + +use crate::timer::{ClockValue, Duration}; + +#[derive(Clone, Copy, Debug)] +pub enum TimedPermission { + Waiting, + Granted(ClockValue), +} + +impl TimedPermission { + pub fn waiting() -> TimedPermission { + TimedPermission::Waiting + } + + pub fn granted(now: ClockValue, grant_duration: Duration) -> TimedPermission { + TimedPermission::Granted(now.wrapping_add(grant_duration)) + } + + // Checks if the timeout is not reached, false for differing ClockValue frequencies. + pub fn is_granted(&self, now: ClockValue) -> bool { + if let TimedPermission::Granted(timeout) = self { + if let Some(remaining_duration) = timeout.wrapping_sub(now) { + return remaining_duration > Duration::from_ms(0); + } + } + false + } + + // Consumes the state and returns the current new permission state at time "now". + // Returns a new state for differing ClockValue frequencies. + pub fn check_expiration(self, now: ClockValue) -> TimedPermission { + if let TimedPermission::Granted(timeout) = self { + if let Some(remaining_duration) = timeout.wrapping_sub(now) { + if remaining_duration > Duration::from_ms(0) { + return TimedPermission::Granted(timeout); + } + } + } + TimedPermission::Waiting + } +} + +#[cfg(feature = "with_ctap1")] +#[derive(Debug)] +pub struct U2fUserPresenceState { + // If user presence was recently requested, its timeout is saved here. + needs_up: TimedPermission, + // Button touch timeouts, while user presence is requested, are saved here. + has_up: TimedPermission, + // This is the timeout duration of user presence requests. + request_duration: Duration, + // This is the timeout duration of button touches. + presence_duration: Duration, +} + +#[cfg(feature = "with_ctap1")] +impl U2fUserPresenceState { + pub fn new( + request_duration: Duration, + presence_duration: Duration, + ) -> U2fUserPresenceState { + U2fUserPresenceState { + needs_up: TimedPermission::Waiting, + has_up: TimedPermission::Waiting, + request_duration, + presence_duration, + } + } + + // Granting user presence is ignored if it needs activation, but waits. Also cleans up. + pub fn grant_up(&mut self, now: ClockValue) { + self.check_expiration(now); + if self.needs_up.is_granted(now) { + self.needs_up = TimedPermission::Waiting; + self.has_up = TimedPermission::granted(now, self.presence_duration); + } + } + + // This marks user presence as needed or uses it up if already granted. Also cleans up. + pub fn consume_up(&mut self, now: ClockValue) -> bool { + self.check_expiration(now); + if self.has_up.is_granted(now) { + self.has_up = TimedPermission::Waiting; + true + } else { + self.needs_up = TimedPermission::granted(now, self.request_duration); + false + } + } + + // Returns if user presence was requested. Also cleans up. + pub fn is_up_needed(&mut self, now: ClockValue) -> bool { + self.check_expiration(now); + self.needs_up.is_granted(now) + } + + // If you don't regularly call any other function, not cleaning up leads to overflow problems. + pub fn check_expiration(&mut self, now: ClockValue) { + self.needs_up = self.needs_up.check_expiration(now); + self.has_up = self.has_up.check_expiration(now); + } +} + +#[cfg(feature = "with_ctap1")] +#[cfg(test)] +mod test { + use super::*; + use core::isize; + + const CLOCK_FREQUENCY_HZ: usize = 32768; + const ZERO: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ); + const BIG_POSITIVE: ClockValue = ClockValue::new(isize::MAX / 1000 - 1, CLOCK_FREQUENCY_HZ); + const NEGATIVE: ClockValue = ClockValue::new(-1, CLOCK_FREQUENCY_HZ); + const SMALL_NEGATIVE: ClockValue = ClockValue::new(isize::MIN / 1000 + 1, CLOCK_FREQUENCY_HZ); + const REQUEST_DURATION: Duration = Duration::from_ms(1000); + const PRESENCE_DURATION: Duration = Duration::from_ms(1000); + + fn grant_up_when_needed(start_time: ClockValue) { + let mut u2f_state = U2fUserPresenceState::new(REQUEST_DURATION, PRESENCE_DURATION); + assert!(!u2f_state.consume_up(start_time)); + assert!(u2f_state.is_up_needed(start_time)); + u2f_state.grant_up(start_time); + assert!(u2f_state.consume_up(start_time)); + assert!(!u2f_state.consume_up(start_time)); + } + + fn need_up_timeout(start_time: ClockValue) { + let mut u2f_state = U2fUserPresenceState::new(REQUEST_DURATION, PRESENCE_DURATION); + assert!(!u2f_state.consume_up(start_time)); + assert!(u2f_state.is_up_needed(start_time)); + // The timeout excludes equality, so it should be over at this instant. + assert!(!u2f_state.is_up_needed(start_time.wrapping_add(REQUEST_DURATION))); + } + + fn grant_up_timeout(start_time: ClockValue) { + let mut u2f_state = U2fUserPresenceState::new(REQUEST_DURATION, PRESENCE_DURATION); + assert!(!u2f_state.consume_up(start_time)); + assert!(u2f_state.is_up_needed(start_time)); + u2f_state.grant_up(start_time); + // The timeout excludes equality, so it should be over at this instant. + assert!(!u2f_state.consume_up(start_time.wrapping_add(PRESENCE_DURATION))); + } + + #[test] + fn test_grant_up_timeout() { + grant_up_timeout(ZERO); + grant_up_timeout(BIG_POSITIVE); + grant_up_timeout(NEGATIVE); + grant_up_timeout(SMALL_NEGATIVE); + } + + #[test] + fn test_need_up_timeout() { + need_up_timeout(ZERO); + need_up_timeout(BIG_POSITIVE); + need_up_timeout(NEGATIVE); + need_up_timeout(SMALL_NEGATIVE); + } + + #[test] + fn test_grant_up_when_needed() { + grant_up_when_needed(ZERO); + grant_up_when_needed(BIG_POSITIVE); + grant_up_when_needed(NEGATIVE); + grant_up_when_needed(SMALL_NEGATIVE); + } + + #[test] + fn test_grant_up_without_need() { + let mut u2f_state = U2fUserPresenceState::new(REQUEST_DURATION, PRESENCE_DURATION); + u2f_state.grant_up(ZERO); + assert!(!u2f_state.is_up_needed(ZERO)); + assert!(!u2f_state.consume_up(ZERO)); + } +} diff --git a/src/embedded_flash/buffer.rs b/src/embedded_flash/buffer.rs new file mode 100644 index 0000000..b7cf3f1 --- /dev/null +++ b/src/embedded_flash/buffer.rs @@ -0,0 +1,455 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::{Index, Storage, StorageError, StorageResult}; + +pub struct BufferStorage { + storage: Box<[u8]>, + options: BufferOptions, + word_writes: Box<[usize]>, + page_erases: Box<[usize]>, + snapshot: Snapshot, +} + +#[derive(Copy, Clone, Debug)] +pub struct BufferOptions { + /// Size of a word in bytes. + pub word_size: usize, + + /// Size of a page in bytes. + pub page_size: usize, + + /// How many times a word can be written between page erasures + pub max_word_writes: usize, + + /// How many times a page can be erased. + pub max_page_erases: usize, + + /// Bits cannot be written from 0 to 1. + pub strict_write: bool, +} + +impl BufferStorage { + /// Creates a fake embedded flash using a buffer. + /// + /// This implementation checks that no words are written more than `max_word_writes` between + /// page erasures and than no pages are erased more than `max_page_erases`. If `strict_write` is + /// true, it also checks that no bits are written from 0 to 1. It also permits to take snapshots + /// of the storage during write and erase operations (although words would still be written or + /// erased completely). + /// + /// # Panics + /// + /// The following preconditions must hold: + /// - `options.word_size` must be a power of two. + /// - `options.page_size` must be a power of two. + /// - `options.page_size` must be word-aligned. + /// - `storage.len()` must be page-aligned. + pub fn new(storage: Box<[u8]>, options: BufferOptions) -> BufferStorage { + assert!(options.word_size.is_power_of_two()); + assert!(options.page_size.is_power_of_two()); + let num_words = storage.len() / options.word_size; + let num_pages = storage.len() / options.page_size; + let buffer = BufferStorage { + storage, + options, + word_writes: vec![0; num_words].into_boxed_slice(), + page_erases: vec![0; num_pages].into_boxed_slice(), + snapshot: Snapshot::Ready, + }; + assert!(buffer.is_word_aligned(buffer.options.page_size)); + assert!(buffer.is_page_aligned(buffer.storage.len())); + buffer + } + + /// Takes a snapshot of the storage after a given amount of word operations. + /// + /// Each time a word is written or erased, the delay is decremented if positive. Otherwise, a + /// snapshot is taken before the operation is executed. + /// + /// # Panics + /// + /// Panics if a snapshot has been armed and not examined. + pub fn arm_snapshot(&mut self, delay: usize) { + self.snapshot.arm(delay); + } + + /// Unarms and returns the snapshot or the delay remaining. + /// + /// # Panics + /// + /// Panics if a snapshot was not armed. + pub fn get_snapshot(&mut self) -> Result, usize> { + self.snapshot.get() + } + + /// Takes a snapshot of the storage. + pub fn take_snapshot(&self) -> Box<[u8]> { + self.storage.clone() + } + + /// Returns the storage. + pub fn get_storage(self) -> Box<[u8]> { + self.storage + } + + fn is_word_aligned(&self, x: usize) -> bool { + x & (self.options.word_size - 1) == 0 + } + + fn is_page_aligned(&self, x: usize) -> bool { + x & (self.options.page_size - 1) == 0 + } + + /// Writes a slice to the storage. + /// + /// The slice `value` is written to `index`. The `erase` boolean specifies whether this is an + /// erase operation or a write operation which matters for the checks and updating the shadow + /// storage. This also takes a snapshot of the storage if a snapshot was armed and the delay has + /// elapsed. + /// + /// The following preconditions should hold: + /// - `index` is word-aligned. + /// - `value.len()` is word-aligned. + /// + /// The following checks are performed: + /// - The region of length `value.len()` starting at `index` fits in a storage page. + /// - A word is not written more than `max_word_writes`. + /// - A page is not erased more than `max_page_erases`. + /// - The new word only switches 1s to 0s (only if `strict_write` is set). + fn update_storage(&mut self, index: Index, value: &[u8], erase: bool) -> StorageResult<()> { + debug_assert!(self.is_word_aligned(index.byte) && self.is_word_aligned(value.len())); + let dst = index.range(value.len(), self)?.step_by(self.word_size()); + let src = value.chunks(self.word_size()); + // Check and update page shadow. + if erase { + let page = index.page; + assert!(self.page_erases[page] < self.max_page_erases()); + self.page_erases[page] += 1; + } + for (byte, val) in dst.zip(src) { + let range = byte..byte + self.word_size(); + // The driver doesn't write identical words. + if &self.storage[range.clone()] == val { + continue; + } + // Check and update word shadow. + let word = byte / self.word_size(); + if erase { + self.word_writes[word] = 0; + } else { + assert!(self.word_writes[word] < self.max_word_writes()); + self.word_writes[word] += 1; + } + // Check strict write. + if !erase && self.options.strict_write { + for (byte, &val) in range.clone().zip(val) { + assert_eq!(self.storage[byte] & val, val); + } + } + // Take snapshot if armed and delay expired. + self.snapshot.take(&self.storage); + // Write storage + self.storage[range].copy_from_slice(val); + } + Ok(()) + } +} + +impl Storage for BufferStorage { + fn word_size(&self) -> usize { + self.options.word_size + } + + fn page_size(&self) -> usize { + self.options.page_size + } + + fn num_pages(&self) -> usize { + self.storage.len() / self.options.page_size + } + + fn max_word_writes(&self) -> usize { + self.options.max_word_writes + } + + fn max_page_erases(&self) -> usize { + self.options.max_page_erases + } + + fn read_slice(&self, index: Index, length: usize) -> StorageResult<&[u8]> { + Ok(&self.storage[index.range(length, self)?]) + } + + fn write_slice(&mut self, index: Index, value: &[u8]) -> StorageResult<()> { + if !self.is_word_aligned(index.byte) || !self.is_word_aligned(value.len()) { + return Err(StorageError::NotAligned); + } + self.update_storage(index, value, false) + } + + fn erase_page(&mut self, page: usize) -> StorageResult<()> { + let index = Index { page, byte: 0 }; + let value = vec![0xff; self.page_size()]; + self.update_storage(index, &value, true) + } +} + +// Controls when a snapshot of the storage is taken. +// +// This can be used to simulate power-offs while the device is writing to the storage or erasing a +// page in the storage. +enum Snapshot { + // Mutable word operations have normal behavior. + Ready, + // If the delay is positive, mutable word operations decrement it. If the count is zero, mutable + // word operations take a snapshot of the storage. + Armed { delay: usize }, + // Mutable word operations have normal behavior. + Taken { storage: Box<[u8]> }, +} + +impl Snapshot { + fn arm(&mut self, delay: usize) { + match self { + Snapshot::Ready => *self = Snapshot::Armed { delay }, + _ => panic!(), + } + } + + fn get(&mut self) -> Result, usize> { + let mut snapshot = Snapshot::Ready; + std::mem::swap(self, &mut snapshot); + match snapshot { + Snapshot::Armed { delay } => Err(delay), + Snapshot::Taken { storage } => Ok(storage), + _ => panic!(), + } + } + + fn take(&mut self, storage: &[u8]) { + if let Snapshot::Armed { delay } = self { + if *delay == 0 { + let storage = storage.to_vec().into_boxed_slice(); + *self = Snapshot::Taken { storage }; + } else { + *delay -= 1; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const NUM_PAGES: usize = 2; + const OPTIONS: BufferOptions = BufferOptions { + word_size: 4, + page_size: 16, + max_word_writes: 2, + max_page_erases: 3, + strict_write: true, + }; + // Those words are decreasing bit patterns. Bits are only changed from 1 to 0 and at last one + // bit is changed. + const BLANK_WORD: &[u8] = &[0xff, 0xff, 0xff, 0xff]; + const FIRST_WORD: &[u8] = &[0xee, 0xdd, 0xbb, 0x77]; + const SECOND_WORD: &[u8] = &[0xca, 0xc9, 0xa9, 0x65]; + const THIRD_WORD: &[u8] = &[0x88, 0x88, 0x88, 0x44]; + + fn new_storage() -> Box<[u8]> { + vec![0xff; NUM_PAGES * OPTIONS.page_size].into_boxed_slice() + } + + #[test] + fn words_are_decreasing() { + fn assert_is_decreasing(prev: &[u8], next: &[u8]) { + for (&prev, &next) in prev.iter().zip(next.iter()) { + assert_eq!(prev & next, next); + assert!(prev != next); + } + } + assert_is_decreasing(BLANK_WORD, FIRST_WORD); + assert_is_decreasing(FIRST_WORD, SECOND_WORD); + assert_is_decreasing(SECOND_WORD, THIRD_WORD); + } + + #[test] + fn options_ok() { + let buffer = BufferStorage::new(new_storage(), OPTIONS); + assert_eq!(buffer.word_size(), OPTIONS.word_size); + assert_eq!(buffer.page_size(), OPTIONS.page_size); + assert_eq!(buffer.num_pages(), NUM_PAGES); + assert_eq!(buffer.max_word_writes(), OPTIONS.max_word_writes); + assert_eq!(buffer.max_page_erases(), OPTIONS.max_page_erases); + } + + #[test] + fn read_write_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 0 }; + let next_index = Index { page: 0, byte: 4 }; + assert_eq!(buffer.read_slice(index, 4).unwrap(), BLANK_WORD); + buffer.write_slice(index, FIRST_WORD).unwrap(); + assert_eq!(buffer.read_slice(index, 4).unwrap(), FIRST_WORD); + assert_eq!(buffer.read_slice(next_index, 4).unwrap(), BLANK_WORD); + } + + #[test] + fn erase_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 0 }; + let other_index = Index { page: 1, byte: 0 }; + buffer.write_slice(index, FIRST_WORD).unwrap(); + buffer.write_slice(other_index, FIRST_WORD).unwrap(); + assert_eq!(buffer.read_slice(index, 4).unwrap(), FIRST_WORD); + assert_eq!(buffer.read_slice(other_index, 4).unwrap(), FIRST_WORD); + buffer.erase_page(0).unwrap(); + assert_eq!(buffer.read_slice(index, 4).unwrap(), BLANK_WORD); + assert_eq!(buffer.read_slice(other_index, 4).unwrap(), FIRST_WORD); + } + + #[test] + fn invalid_range() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 12 }; + let half_index = Index { page: 0, byte: 14 }; + let over_index = Index { page: 0, byte: 16 }; + let bad_page = Index { page: 2, byte: 0 }; + + // Reading a word in the storage is ok. + assert!(buffer.read_slice(index, 4).is_ok()); + // Reading a half-word in the storage is ok. + assert!(buffer.read_slice(half_index, 2).is_ok()); + // Reading even a single byte outside a page is not ok. + assert!(buffer.read_slice(over_index, 1).is_err()); + // But reading an empty slice just after a page is ok. + assert!(buffer.read_slice(over_index, 0).is_ok()); + // Reading even an empty slice outside the storage is not ok. + assert!(buffer.read_slice(bad_page, 0).is_err()); + + // Writing a word in the storage is ok. + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + // Writing an unaligned word is not ok. + assert!(buffer.write_slice(half_index, FIRST_WORD).is_err()); + // Writing a word outside a page is not ok. + assert!(buffer.write_slice(over_index, FIRST_WORD).is_err()); + // But writing an empty slice just after a page is ok. + assert!(buffer.write_slice(over_index, &[]).is_ok()); + // Writing even an empty slice outside the storage is not ok. + assert!(buffer.write_slice(bad_page, &[]).is_err()); + + // Only pages in the storage can be erased. + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(2).is_err()); + } + + #[test] + fn write_twice_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 4 }; + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + } + + #[test] + fn write_twice_and_once_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 0 }; + let next_index = Index { page: 0, byte: 4 }; + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + assert!(buffer.write_slice(next_index, THIRD_WORD).is_ok()); + } + + #[test] + #[should_panic] + fn write_three_times_panics() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 4 }; + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + let _ = buffer.write_slice(index, THIRD_WORD); + } + + #[test] + fn write_twice_then_once_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 0 }; + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.write_slice(index, FIRST_WORD).is_ok()); + } + + #[test] + fn erase_three_times_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + } + + #[test] + fn erase_three_times_and_once_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(1).is_ok()); + } + + #[test] + #[should_panic] + fn erase_four_times_panics() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + assert!(buffer.erase_page(0).is_ok()); + let _ = buffer.erase_page(0).is_ok(); + } + + #[test] + #[should_panic] + fn switch_zero_to_one_panics() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 0 }; + assert!(buffer.write_slice(index, SECOND_WORD).is_ok()); + let _ = buffer.write_slice(index, FIRST_WORD); + } + + #[test] + fn get_storage_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 4 }; + buffer.write_slice(index, FIRST_WORD).unwrap(); + let storage = buffer.get_storage(); + assert_eq!(&storage[..4], BLANK_WORD); + assert_eq!(&storage[4..8], FIRST_WORD); + } + + #[test] + fn snapshot_ok() { + let mut buffer = BufferStorage::new(new_storage(), OPTIONS); + let index = Index { page: 0, byte: 0 }; + let value = [FIRST_WORD, SECOND_WORD].concat(); + buffer.arm_snapshot(1); + buffer.write_slice(index, &value).unwrap(); + let storage = buffer.get_snapshot().unwrap(); + assert_eq!(&storage[..8], &[FIRST_WORD, BLANK_WORD].concat()[..]); + let storage = buffer.take_snapshot(); + assert_eq!(&storage[..8], &value[..]); + } +} diff --git a/src/embedded_flash/mod.rs b/src/embedded_flash/mod.rs new file mode 100644 index 0000000..5e54059 --- /dev/null +++ b/src/embedded_flash/mod.rs @@ -0,0 +1,25 @@ +// Copyright 2019 Google LLC +// +// 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. + +#[cfg(feature = "std")] +mod buffer; +mod storage; +mod store; +mod syscall; + +#[cfg(feature = "std")] +pub use self::buffer::{BufferOptions, BufferStorage}; +pub use self::storage::{Index, Storage, StorageError, StorageResult}; +pub use self::store::{Store, StoreConfig, StoreEntry, StoreError, StoreIndex}; +pub use self::syscall::SyscallStorage; diff --git a/src/embedded_flash/storage.rs b/src/embedded_flash/storage.rs new file mode 100644 index 0000000..fe87ac4 --- /dev/null +++ b/src/embedded_flash/storage.rs @@ -0,0 +1,107 @@ +// Copyright 2019 Google LLC +// +// 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. + +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct Index { + pub page: usize, + pub byte: usize, +} + +#[derive(Debug)] +pub enum StorageError { + BadFlash, + NotAligned, + OutOfBounds, + KernelError { code: isize }, +} + +pub type StorageResult = Result; + +/// Abstraction for embedded flash storage. +pub trait Storage { + /// Returns the size of a word in bytes. + fn word_size(&self) -> usize; + + /// Returns the size of a page in bytes. + fn page_size(&self) -> usize; + + /// Returns the number of pages in the storage. + fn num_pages(&self) -> usize; + + /// Returns how many times a word can be written between page erasures. + fn max_word_writes(&self) -> usize; + + /// Returns how many times a page can be erased in the lifetime of the flash. + fn max_page_erases(&self) -> usize; + + /// Reads a slice from the storage. + /// + /// The slice does not need to be word-aligned. + /// + /// # Errors + /// + /// The `index` must designate `length` bytes in the storage. + fn read_slice(&self, index: Index, length: usize) -> StorageResult<&[u8]>; + + /// Writes a word-aligned slice to the storage. + /// + /// The written words should not have been written too many times since last page erasure. + /// + /// # Errors + /// + /// The following preconditions must hold: + /// - `index` must be word-aligned. + /// - `value.len()` must be a multiple of the word size. + /// - `index` must designate `value.len()` bytes in the storage. + /// - `value` must be in memory until [read-only allow][tock_1274] is resolved. + /// + /// [tock_1274]: https://github.com/tock/tock/issues/1274. + fn write_slice(&mut self, index: Index, value: &[u8]) -> StorageResult<()>; + + /// Erases a page of the storage. + /// + /// # Errors + /// + /// The `page` must be in the storage. + fn erase_page(&mut self, page: usize) -> StorageResult<()>; +} + +impl Index { + /// Returns whether a slice fits in a storage page. + fn is_valid(self, length: usize, storage: &impl Storage) -> bool { + self.page < storage.num_pages() + && storage + .page_size() + .checked_sub(length) + .map(|limit| self.byte <= limit) + .unwrap_or(false) + } + + /// Returns the range of a valid slice. + /// + /// The range starts at `self` with `length` bytes. + pub fn range( + self, + length: usize, + storage: &impl Storage, + ) -> StorageResult> { + if self.is_valid(length, storage) { + let start = self.page * storage.page_size() + self.byte; + Ok(start..start + length) + } else { + Err(StorageError::OutOfBounds) + } + } +} diff --git a/src/embedded_flash/store/bitfield.rs b/src/embedded_flash/store/bitfield.rs new file mode 100644 index 0000000..60c6f86 --- /dev/null +++ b/src/embedded_flash/store/bitfield.rs @@ -0,0 +1,172 @@ +// Copyright 2019 Google LLC +// +// 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. + +/// Defines a consecutive sequence of bits. +#[derive(Copy, Clone)] +pub struct BitRange { + /// The first bit of the sequence. + pub start: usize, + + /// The length in bits of the sequence. + pub length: usize, +} + +impl BitRange { + /// Returns the first bit following a bit range. + pub fn end(self) -> usize { + self.start + self.length + } +} + +/// Defines a consecutive sequence of bytes. +/// +/// The bits in those bytes are ignored which essentially creates a gap in a sequence of bits. The +/// gap is necessarily at byte boundaries. This is used to ignore the user data in an entry +/// essentially providing a view of the entry information (header and footer). +#[derive(Copy, Clone)] +pub struct ByteGap { + pub start: usize, + pub length: usize, +} + +/// Empty gap. All bits count. +pub const NO_GAP: ByteGap = ByteGap { + start: 0, + length: 0, +}; + +impl ByteGap { + /// Translates a bit to skip the gap. + fn shift(self, bit: usize) -> usize { + if bit < 8 * self.start { + bit + } else { + bit + 8 * self.length + } + } +} + +/// Returns whether a bit is set in a sequence of bits. +/// +/// The sequence of bits is little-endian (both for bytes and bits) and defined by the bits that +/// are in `data` but not in `gap`. +pub fn is_zero(bit: usize, data: &[u8], gap: ByteGap) -> bool { + let bit = gap.shift(bit); + debug_assert!(bit < 8 * data.len()); + data[bit / 8] & (1 << (bit % 8)) == 0 +} + +/// Sets a bit to zero in a sequence of bits. +/// +/// The sequence of bits is little-endian (both for bytes and bits) and defined by the bits that +/// are in `data` but not in `gap`. +pub fn set_zero(bit: usize, data: &mut [u8], gap: ByteGap) { + let bit = gap.shift(bit); + debug_assert!(bit < 8 * data.len()); + data[bit / 8] &= !(1 << (bit % 8)); +} + +/// Returns a little-endian value in a sequence of bits. +/// +/// The sequence of bits is little-endian (both for bytes and bits) and defined by the bits that +/// are in `data` but not in `gap`. The range of bits where the value is stored in defined by +/// `range`. The value must fit in a `usize`. +pub fn get_range(range: BitRange, data: &[u8], gap: ByteGap) -> usize { + debug_assert!(range.length <= 8 * core::mem::size_of::()); + let mut result = 0; + for i in 0..range.length { + if !is_zero(range.start + i, data, gap) { + result |= 1 << i; + } + } + result +} + +/// Sets a little-endian value in a sequence of bits. +/// +/// The sequence of bits is little-endian (both for bytes and bits) and defined by the bits that +/// are in `data` but not in `gap`. The range of bits where the value is stored in defined by +/// `range`. The bits set to 1 in `value` must also be set to `1` in the sequence of bits. +pub fn set_range(range: BitRange, data: &mut [u8], gap: ByteGap, value: usize) { + debug_assert!(range.length == 8 * core::mem::size_of::() || value < 1 << range.length); + for i in 0..range.length { + if value & 1 << i == 0 { + set_zero(range.start + i, data, gap); + } + } +} + +/// Tests the `is_zero` and `set_zero` pair of functions. +#[test] +fn zero_ok() { + const GAP: ByteGap = ByteGap { + start: 2, + length: 1, + }; + for i in 0..24 { + assert!(!is_zero(i, &[0xffu8, 0xff, 0x00, 0xff] as &[u8], GAP)); + } + // Tests reading and setting a bit. The result should have all bits set to 1 except for the bit + // to test and the gap. + fn test(bit: usize, result: &[u8]) { + assert!(is_zero(bit, result, GAP)); + let mut data = vec![0xff; result.len()]; + // Set the gap bits to 0. + for i in 0..GAP.length { + data[GAP.start + i] = 0x00; + } + set_zero(bit, &mut data, GAP); + assert_eq!(data, result); + } + test(0, &[0xfe, 0xff, 0x00, 0xff]); + test(1, &[0xfd, 0xff, 0x00, 0xff]); + test(2, &[0xfb, 0xff, 0x00, 0xff]); + test(7, &[0x7f, 0xff, 0x00, 0xff]); + test(8, &[0xff, 0xfe, 0x00, 0xff]); + test(15, &[0xff, 0x7f, 0x00, 0xff]); + test(16, &[0xff, 0xff, 0x00, 0xfe]); + test(17, &[0xff, 0xff, 0x00, 0xfd]); + test(23, &[0xff, 0xff, 0x00, 0x7f]); +} + +/// Tests the `get_range` and `set_range` pair of functions. +#[test] +fn range_ok() { + // Tests reading and setting a range. The result should have all bits set to 1 except for the + // range to test and the gap. + fn test(start: usize, length: usize, value: usize, result: &[u8], gap: ByteGap) { + let range = BitRange { start, length }; + assert_eq!(get_range(range, result, gap), value); + let mut data = vec![0xff; result.len()]; + for i in 0..gap.length { + data[gap.start + i] = 0x00; + } + set_range(range, &mut data, gap, value); + assert_eq!(data, result); + } + test(0, 8, 42, &[42], NO_GAP); + test(3, 12, 0b11_0101, &[0b1010_1111, 0b1000_0001], NO_GAP); + test(0, 16, 0x1234, &[0x34, 0x12], NO_GAP); + test(4, 16, 0x1234, &[0x4f, 0x23, 0xf1], NO_GAP); + let mut gap = ByteGap { + start: 1, + length: 1, + }; + test(3, 12, 0b11_0101, &[0b1010_1111, 0x00, 0b1000_0001], gap); + gap.length = 2; + test(0, 16, 0x1234, &[0x34, 0x00, 0x00, 0x12], gap); + gap.start = 2; + gap.length = 1; + test(4, 16, 0x1234, &[0x4f, 0x23, 0x00, 0xf1], gap); +} diff --git a/src/embedded_flash/store/format.rs b/src/embedded_flash/store/format.rs new file mode 100644 index 0000000..cbef61f --- /dev/null +++ b/src/embedded_flash/store/format.rs @@ -0,0 +1,514 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::super::{Index, Storage}; +use super::{bitfield, StoreConfig, StoreEntry, StoreError}; +use alloc::vec::Vec; + +/// Whether a user entry is a replace entry. +pub enum IsReplace { + /// This is a replace entry. + Replace, + + /// This is an insert entry. + Insert, +} + +/// Helpers to parse the store format. +/// +/// See the store module-level documentation for information about the format. +pub struct Format { + pub word_size: usize, + pub page_size: usize, + pub num_pages: usize, + pub max_page_erases: usize, + pub num_tags: usize, + + /// Whether an entry is present. + /// + /// - 0 for entries (user entries or internal entries). + /// - 1 for free space until the end of the page. + present_bit: usize, + + /// Whether an entry is deleted. + /// + /// - 0 for deleted entries. + /// - 1 for alive entries. + deleted_bit: usize, + + /// Whether an entry is internal. + /// + /// - 0 for internal entries. + /// - 1 for user entries. + internal_bit: usize, + + /// Whether a user entry is a replace entry. + /// + /// - 0 for replace entries. + /// - 1 for insert entries. + replace_bit: usize, + + /// The data length of a user entry. + length_range: bitfield::BitRange, + + /// The tag of a user entry. + tag_range: bitfield::BitRange, + + /// The page index of a replace entry. + replace_page_range: bitfield::BitRange, + + /// The byte index of a replace entry. + replace_byte_range: bitfield::BitRange, + + /// The index of the page to erase. + /// + /// This is only present for internal entries. + old_page_range: bitfield::BitRange, + + /// The current erase count of the page to erase. + /// + /// This is only present for internal entries. + saved_erase_count_range: bitfield::BitRange, + + /// Whether a page is initialized. + /// + /// - 0 for initialized pages. + /// - 1 for uninitialized pages. + initialized_bit: usize, + + /// The erase count of a page. + erase_count_range: bitfield::BitRange, + + /// Whether a page is being compacted. + /// + /// - 0 for pages being compacted. + /// - 1 otherwise. + compacting_bit: usize, + + /// The page index to which a page is being compacted. + new_page_range: bitfield::BitRange, +} + +impl Format { + /// Returns a helper to parse the store format for a given storage and config. + /// + /// # Errors + /// + /// Returns `None` if any of the following conditions does not hold: + /// - The word size must be a power of two. + /// - The page size must be a power of two. + /// - There should be at least 2 pages in the storage. + /// - It should be possible to write a word at least twice. + /// - It should be possible to erase a page at least once. + /// - There should be at least 1 tag. + pub fn new(storage: &S, config: &C) -> Option { + let word_size = storage.word_size(); + let page_size = storage.page_size(); + let num_pages = storage.num_pages(); + let max_word_writes = storage.max_word_writes(); + let max_page_erases = storage.max_page_erases(); + let num_tags = config.num_tags(); + if !(word_size.is_power_of_two() + && page_size.is_power_of_two() + && num_pages > 1 + && max_word_writes >= 2 + && max_page_erases > 0 + && num_tags > 0) + { + return None; + } + // Compute how many bits we need to store the fields. + let page_bits = num_bits(num_pages); + let byte_bits = num_bits(page_size); + let tag_bits = num_bits(num_tags); + let erase_bits = num_bits(max_page_erases + 1); + // Compute the bit position of the fields. + let present_bit = 0; + let deleted_bit = present_bit + 1; + let internal_bit = deleted_bit + 1; + let replace_bit = internal_bit + 1; + let length_range = bitfield::BitRange { + start: replace_bit + 1, + length: byte_bits, + }; + let tag_range = bitfield::BitRange { + start: length_range.end(), + length: tag_bits, + }; + let replace_page_range = bitfield::BitRange { + start: tag_range.end(), + length: page_bits, + }; + let replace_byte_range = bitfield::BitRange { + start: replace_page_range.end(), + length: byte_bits, + }; + let old_page_range = bitfield::BitRange { + start: internal_bit + 1, + length: page_bits, + }; + let saved_erase_count_range = bitfield::BitRange { + start: old_page_range.end(), + length: erase_bits, + }; + let initialized_bit = 0; + let erase_count_range = bitfield::BitRange { + start: initialized_bit + 1, + length: erase_bits, + }; + let compacting_bit = erase_count_range.end(); + let new_page_range = bitfield::BitRange { + start: compacting_bit + 1, + length: page_bits, + }; + let format = Format { + word_size, + page_size, + num_pages, + max_page_erases, + num_tags, + present_bit, + deleted_bit, + internal_bit, + replace_bit, + length_range, + tag_range, + replace_page_range, + replace_byte_range, + old_page_range, + saved_erase_count_range, + initialized_bit, + erase_count_range, + compacting_bit, + new_page_range, + }; + // Make sure all the following conditions hold: + // - The page header is one word. + // - The internal entry is one word. + // - The entry header fits in one word. + if format.page_header_size() != word_size + || format.internal_entry_size() != word_size + || format.header_size() > word_size + { + return None; + } + Some(format) + } + + /// Ensures a user entry is valid. + pub fn validate_entry(&self, entry: StoreEntry) -> Result<(), StoreError> { + if entry.tag >= self.num_tags { + return Err(StoreError::InvalidTag); + } + if entry.data.len() >= self.page_size { + return Err(StoreError::StoreFull); + } + Ok(()) + } + + /// Returns the entry header length in bytes. + /// + /// This is the smallest number of bytes necessary to store all fields of the entry info up to + /// and including `length`. + pub fn header_size(&self) -> usize { + self.bits_to_bytes(self.length_range.end()) + } + + /// Returns the entry info length in bytes. + /// + /// This is the number of bytes necessary to store all fields of the entry info. This also + /// includes the internal padding to protect the `committed` bit from the `deleted` bit. + fn info_size(&self, is_replace: IsReplace) -> usize { + let suffix_bits = 2; // committed + complete + let info_bits = match is_replace { + IsReplace::Replace => self.replace_byte_range.end() + suffix_bits, + IsReplace::Insert => self.tag_range.end() + suffix_bits, + }; + let info_size = self.bits_to_bytes(info_bits); + // If the suffix bits would end up in the header, we need to add one byte for them. + if info_size == self.header_size() { + info_size + 1 + } else { + info_size + } + } + + /// Returns the length in bytes of an entry. + /// + /// This depends on the length of the user data and whether the entry replaces an old entry or + /// is an insertion. This also includes the internal padding to protect the `committed` bit from + /// the `deleted` bit. + pub fn entry_size(&self, is_replace: IsReplace, length: usize) -> usize { + let mut entry_size = length + self.info_size(is_replace); + let word_size = self.word_size; + entry_size = self.align_word(entry_size); + // The entry must be at least 2 words such that the `committed` and `deleted` bits are on + // different words. + if entry_size == word_size { + entry_size += word_size; + } + entry_size + } + + /// Returns the length in bytes of an internal entry. + pub fn internal_entry_size(&self) -> usize { + let length = self.bits_to_bytes(self.saved_erase_count_range.end()); + self.align_word(length) + } + + pub fn is_present(&self, header: &[u8]) -> bool { + bitfield::is_zero(self.present_bit, header, bitfield::NO_GAP) + } + + pub fn set_present(&self, header: &mut [u8]) { + bitfield::set_zero(self.present_bit, header, bitfield::NO_GAP) + } + + pub fn is_deleted(&self, header: &[u8]) -> bool { + bitfield::is_zero(self.deleted_bit, header, bitfield::NO_GAP) + } + + /// Returns whether an entry is present and not deleted. + pub fn is_alive(&self, header: &[u8]) -> bool { + self.is_present(header) && !self.is_deleted(header) + } + + pub fn set_deleted(&self, header: &mut [u8]) { + bitfield::set_zero(self.deleted_bit, header, bitfield::NO_GAP) + } + + pub fn is_internal(&self, header: &[u8]) -> bool { + bitfield::is_zero(self.internal_bit, header, bitfield::NO_GAP) + } + + pub fn set_internal(&self, header: &mut [u8]) { + bitfield::set_zero(self.internal_bit, header, bitfield::NO_GAP) + } + + pub fn is_replace(&self, header: &[u8]) -> IsReplace { + if bitfield::is_zero(self.replace_bit, header, bitfield::NO_GAP) { + IsReplace::Replace + } else { + IsReplace::Insert + } + } + + fn set_replace(&self, header: &mut [u8]) { + bitfield::set_zero(self.replace_bit, header, bitfield::NO_GAP) + } + + pub fn get_length(&self, header: &[u8]) -> usize { + bitfield::get_range(self.length_range, header, bitfield::NO_GAP) + } + + fn set_length(&self, header: &mut [u8], length: usize) { + bitfield::set_range(self.length_range, header, bitfield::NO_GAP, length) + } + + pub fn get_data<'a>(&self, entry: &'a [u8]) -> &'a [u8] { + &entry[self.header_size()..][..self.get_length(entry)] + } + + /// Returns the span of user data in an entry. + /// + /// The complement of this gap in the entry is exactly the entry info. The header is before the + /// gap and the footer is after the gap. + fn entry_gap(&self, entry: &[u8]) -> bitfield::ByteGap { + let start = self.header_size(); + let length = self.get_length(entry); + bitfield::ByteGap { start, length } + } + + pub fn get_tag(&self, entry: &[u8]) -> usize { + bitfield::get_range(self.tag_range, entry, self.entry_gap(entry)) + } + + fn set_tag(&self, entry: &mut [u8], tag: usize) { + bitfield::set_range(self.tag_range, entry, self.entry_gap(entry), tag) + } + + pub fn get_replace_index(&self, entry: &[u8]) -> Index { + let gap = self.entry_gap(entry); + let page = bitfield::get_range(self.replace_page_range, entry, gap); + let byte = bitfield::get_range(self.replace_byte_range, entry, gap); + Index { page, byte } + } + + fn set_replace_page(&self, entry: &mut [u8], page: usize) { + bitfield::set_range(self.replace_page_range, entry, self.entry_gap(entry), page) + } + + fn set_replace_byte(&self, entry: &mut [u8], byte: usize) { + bitfield::set_range(self.replace_byte_range, entry, self.entry_gap(entry), byte) + } + + /// Returns the bit position of the `committed` bit. + /// + /// This cannot be precomputed like other fields since it depends on the length of the entry. + fn committed_bit(&self, entry: &[u8]) -> usize { + 8 * entry.len() - 2 + } + + /// Returns the bit position of the `complete` bit. + /// + /// This cannot be precomputed like other fields since it depends on the length of the entry. + fn complete_bit(&self, entry: &[u8]) -> usize { + 8 * entry.len() - 1 + } + + pub fn is_committed(&self, entry: &[u8]) -> bool { + bitfield::is_zero(self.committed_bit(entry), entry, bitfield::NO_GAP) + } + + pub fn set_committed(&self, entry: &mut [u8]) { + bitfield::set_zero(self.committed_bit(entry), entry, bitfield::NO_GAP) + } + + pub fn is_complete(&self, entry: &[u8]) -> bool { + bitfield::is_zero(self.complete_bit(entry), entry, bitfield::NO_GAP) + } + + fn set_complete(&self, entry: &mut [u8]) { + bitfield::set_zero(self.complete_bit(entry), entry, bitfield::NO_GAP) + } + + pub fn get_old_page(&self, header: &[u8]) -> usize { + bitfield::get_range(self.old_page_range, header, bitfield::NO_GAP) + } + + pub fn set_old_page(&self, header: &mut [u8], old_page: usize) { + bitfield::set_range(self.old_page_range, header, bitfield::NO_GAP, old_page) + } + + pub fn get_saved_erase_count(&self, header: &[u8]) -> usize { + bitfield::get_range(self.saved_erase_count_range, header, bitfield::NO_GAP) + } + + pub fn set_saved_erase_count(&self, header: &mut [u8], erase_count: usize) { + bitfield::set_range( + self.saved_erase_count_range, + header, + bitfield::NO_GAP, + erase_count, + ) + } + + /// Builds an entry for replace or insert operations. + pub fn build_entry(&self, replace: Option, user_entry: StoreEntry) -> Vec { + let StoreEntry { tag, data } = user_entry; + let is_replace = match replace { + None => IsReplace::Insert, + Some(_) => IsReplace::Replace, + }; + let entry_len = self.entry_size(is_replace, data.len()); + let mut entry = Vec::with_capacity(entry_len); + // Build the header. + entry.resize(self.header_size(), 0xff); + self.set_present(&mut entry[..]); + self.set_length(&mut entry[..], data.len()); + // Add the data. + entry.extend_from_slice(data); + // Build the footer. + entry.resize(entry_len, 0xff); + self.set_tag(&mut entry[..], tag); + self.set_complete(&mut entry[..]); + match replace { + None => self.set_committed(&mut entry[..]), + Some(Index { page, byte }) => { + self.set_replace(&mut entry[..]); + self.set_replace_page(&mut entry[..], page); + self.set_replace_byte(&mut entry[..], byte); + } + } + entry + } + + /// Builds an entry for replace or insert operations. + pub fn build_erase_entry(&self, old_page: usize, saved_erase_count: usize) -> Vec { + let mut entry = vec![0xff; self.internal_entry_size()]; + self.set_present(&mut entry[..]); + self.set_internal(&mut entry[..]); + self.set_old_page(&mut entry[..], old_page); + self.set_saved_erase_count(&mut entry[..], saved_erase_count); + entry + } + + /// Returns the length in bytes of a page header entry. + /// + /// This includes the word padding. + pub fn page_header_size(&self) -> usize { + self.align_word(self.bits_to_bytes(self.erase_count_range.end())) + } + + pub fn is_initialized(&self, header: &[u8]) -> bool { + bitfield::is_zero(self.initialized_bit, header, bitfield::NO_GAP) + } + + pub fn set_initialized(&self, header: &mut [u8]) { + bitfield::set_zero(self.initialized_bit, header, bitfield::NO_GAP) + } + + pub fn get_erase_count(&self, header: &[u8]) -> usize { + bitfield::get_range(self.erase_count_range, header, bitfield::NO_GAP) + } + + pub fn set_erase_count(&self, header: &mut [u8], count: usize) { + bitfield::set_range(self.erase_count_range, header, bitfield::NO_GAP, count) + } + + pub fn is_compacting(&self, header: &[u8]) -> bool { + bitfield::is_zero(self.compacting_bit, header, bitfield::NO_GAP) + } + + pub fn set_compacting(&self, header: &mut [u8]) { + bitfield::set_zero(self.compacting_bit, header, bitfield::NO_GAP) + } + + pub fn get_new_page(&self, header: &[u8]) -> usize { + bitfield::get_range(self.new_page_range, header, bitfield::NO_GAP) + } + + pub fn set_new_page(&self, header: &mut [u8], new_page: usize) { + bitfield::set_range(self.new_page_range, header, bitfield::NO_GAP, new_page) + } + + /// Returns the smallest word boundary greater or equal to a value. + fn align_word(&self, value: usize) -> usize { + let word_size = self.word_size; + (value + word_size - 1) / word_size * word_size + } + + /// Returns the minimum number of bytes to represent a given number of bits. + fn bits_to_bytes(&self, bits: usize) -> usize { + (bits + 7) / 8 + } +} + +/// Returns the number of bits necessary to write numbers smaller than `x`. +fn num_bits(x: usize) -> usize { + x.next_power_of_two().trailing_zeros() as usize +} + +#[test] +fn num_bits_ok() { + assert_eq!(num_bits(0), 0); + assert_eq!(num_bits(1), 0); + assert_eq!(num_bits(2), 1); + assert_eq!(num_bits(3), 2); + assert_eq!(num_bits(4), 2); + assert_eq!(num_bits(5), 3); + assert_eq!(num_bits(8), 3); + assert_eq!(num_bits(9), 4); + assert_eq!(num_bits(16), 4); +} diff --git a/src/embedded_flash/store/mod.rs b/src/embedded_flash/store/mod.rs new file mode 100644 index 0000000..0431073 --- /dev/null +++ b/src/embedded_flash/store/mod.rs @@ -0,0 +1,1028 @@ +// Copyright 2019 Google LLC +// +// 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. + +//! Provides a multi-purpose data-structure. +//! +//! # Description +//! +//! The `Store` data-structure permits to iterate, find, insert, delete, and replace entries in a +//! multi-set. The mutable operations (insert, delete, and replace) are atomic, in the sense that if +//! power is lost during the operation, then the operation might either succeed or fail but the +//! store remains in a coherent state. The data-structure is flash-efficient, in the sense that it +//! tries to minimize the number of times a page is erased. +//! +//! An _entry_ is made of a _tag_, which is a number, and a _data_, which is a slice of bytes. The +//! tag is stored efficiently by using unassigned bits of the entry header and footer. For example, +//! it can be used to decide how to deserialize the data. It is not necessary to use tags since a +//! prefix of the data could be used to decide how to deserialize the rest. +//! +//! Entries can also be associated to a set of _keys_. The find operation permits to retrieve all +//! entries associated to a given key. The same key can be associated to multiple entries and the +//! same entry can be associated to multiple keys. +//! +//! # Storage +//! +//! The data-structure is parametric over its storage which must implement the `Storage` trait. +//! There are currently 2 implementations of this trait: +//! - `SyscallStorage` using the `embedded_flash` syscall API for production builds. +//! - `BufferStorage` using a heap-allocated buffer for testing. +//! +//! # Configuration +//! +//! The data-structure can be configured with the `StoreConfig` trait. By implementing this trait, +//! the number of possible tags and the association between keys and entries are defined. +//! +//! # Implementation +//! +//! The store is a page-aligned sequence of bits. It matches the following grammar: +//! +//! ```text +//! Store := Page* +//! Page := PageHeader (Entry | InternalEntry)* Padding(page) +//! PageHeader := // must fit in one word +//! initialized:1 +//! erase_count:erase_bits +//! compacting:1 +//! new_page:page_bits +//! Padding(word) +//! Entry := Header Data Footer +//! // Let X be the byte following `length` in `Info`. +//! Header := Info[..X] // must fit in one word +//! Footer := Info[X..] // must fit in one word +//! Info := +//! present=0 +//! deleted:1 +//! internal=1 +//! replace:1 +//! length:byte_bits +//! tag:tag_bits +//! [ // present if `replace` is 0 +//! replace_page:page_bits +//! replace_byte:byte_bits +//! ] +//! [Padding(bit)] // until `complete` is the last bit of a different word than `present` +//! committed:1 +//! complete=0 +//! InternalEntry := +//! present=0 +//! deleted:1 +//! internal=0 +//! old_page:page_bits +//! saved_erase_count:erase_bits +//! Padding(word) +//! Padding(X) := 1* until X-aligned +//! ``` +//! +//! For bit flags, a value of 0 means true and a value of 1 means false. So when erased, bits are +//! false. They can be set to true by writing 0. +//! +//! The `Entry` rule is for user entries and the `InternalEntry` rule is for internal entries of the +//! store. Currently, there is only one kind of internal entry: an entry to erase the page being +//! compacted. +//! +//! The `Header` and `Footer` rules are computed from the `Info` rule. An entry could simply be the +//! concatenation of internal metadata and the user data. However, to optimize the size in flash, we +//! splice the user data in the middle of the metadata. The reason is that we can only write twice +//! the same word and for replace entries we need to write the deleted bit and the committed bit +//! independently. Also, this is important for the complete bit to be the last written bit (since +//! slices are written to flash from low to high addresses). Here is the representation of a +//! specific replace entry for a specific configuration: +//! +//! ```text +//! page_bits=6 +//! byte_bits=9 +//! tag_bits=5 +//! +//! byte.bit name +//! 0.0 present +//! 0.1 deleted +//! 0.2 internal +//! 0.3 replace +//! 0.4 length (9 bits) +//! 1.5 tag (least significant 3 bits out of 5) +//! (the header ends at the first byte boundary after `length`) +//! 2.0 (2 bytes in this example) +//! (the footer starts immediately after the user data) +//! 4.0 tag (most significant 2 bits out of 5) +//! 4.2 replace_page (6 bits) +//! 5.0 replace_byte (9 bits) +//! 6.1 padding (make sure the 2 properties below hold) +//! 7.6 committed +//! 7.7 complete (on a different word than `present`) +//! 8.0 (word-aligned) +//! ``` +//! +//! The store should always contain at least one blank page, so that it is always possible to +//! compact. + +// TODO(cretin): We don't need inner padding for insert entries. The store format can be: +// InsertEntry | ReplaceEntry | InternalEntry (maybe rename to EraseEntry) +// InsertEntry padding is until `complete` is the last bit of a word. +// ReplaceEntry padding is until `complete` is the last bit of a different word than `present`. +// TODO(cretin): Add checksum (may play the same role as the completed bit) and recovery strategy? +// TODO(cretin): Add corruption (deterministic but undetermined reads) to fuzzing. +// TODO(cretin): Add more complex transactions? (this does not seem necessary yet) +// TODO(cretin): Add possibility to shred an entry (force compact page after delete)? + +mod bitfield; +mod format; + +use self::format::{Format, IsReplace}; +#[cfg(feature = "std")] +use super::BufferStorage; +use super::{Index, Storage}; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + +/// Configures a store. +pub trait StoreConfig { + /// How entries are keyed. + /// + /// To disable keys, this may be defined to `()` or even better a custom empty enum. + type Key: Ord; + + /// Number of entry tags. + /// + /// All tags must be smaller than this value. + /// + /// To disable tags, this function should return `1`. The only valid tag would then be `0`. + fn num_tags(&self) -> usize; + + /// Specifies the set of keys of an entry. + /// + /// If keys are not used, this function can immediately return. Otherwise, it should call + /// `associate_key` for each key that should be associated to `entry`. + fn keys(&self, entry: StoreEntry, associate_key: impl FnMut(Self::Key)); +} + +/// Errors returned by store operations. +#[derive(Debug, PartialEq, Eq)] +pub enum StoreError { + /// The operation could not proceed because the store is full. + StoreFull, + + /// The operation could not proceed because the provided tag is invalid. + InvalidTag, + + /// The operation could not proceed because the preconditions do not hold. + InvalidPrecondition, +} + +/// The position of an entry in the store. +#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Copy, Clone)] +pub struct StoreIndex { + /// The index of this entry in the storage. + index: Index, + + /// The generation at which this index is valid. + /// + /// See the documentation of the field with the same name in the `Store` struct. + generation: usize, +} + +/// A user entry. +#[cfg_attr(feature = "std", derive(Debug, PartialEq, Eq))] +#[derive(Copy, Clone)] +pub struct StoreEntry<'a> { + /// The tag of the entry. + /// + /// Must be smaller than the configured number of tags. + pub tag: usize, + + /// The data of the entry. + pub data: &'a [u8], +} + +/// Implements a configurable multi-set on top of any storage. +pub struct Store { + storage: S, + config: C, + format: Format, + + /// The index of the blank page reserved for compaction. + blank_page: usize, + + /// Counts the number of compactions since the store creation. + /// + /// A `StoreIndex` is valid only if they originate from the same generation. This is checked by + /// operations that take a `StoreIndex` as argument. + generation: usize, +} + +impl Store { + /// Creates a new store. + /// + /// Initializes the storage if it is fresh (filled with `0xff`). Rolls-back or completes an + /// operation if the store was powered off in the middle of that operation. In other words, + /// operations are atomic. + /// + /// # Errors + /// + /// Returns `None` if `storage` and/or `config` are not supported. + pub fn new(storage: S, config: C) -> Option> { + let format = Format::new(&storage, &config)?; + let blank_page = format.num_pages; + let mut store = Store { + storage, + config, + format, + blank_page, + generation: 0, + }; + // Finish any ongoing page compaction. + store.recover_compact_page(); + // Finish or roll-back any other entry-level operations. + store.recover_entry_operations(); + // Initialize uninitialized pages. + store.initialize_storage(); + Some(store) + } + + /// Iterates over all entries in the store. + pub fn iter(&self) -> impl Iterator { + Iter::new(self).filter_map(move |(index, entry)| { + if self.format.is_alive(entry) { + Some(( + StoreIndex { + index, + generation: self.generation, + }, + StoreEntry { + tag: self.format.get_tag(entry), + data: self.format.get_data(entry), + }, + )) + } else { + None + } + }) + } + + /// Iterates over all entries matching a key in the store. + pub fn find_all<'a>( + &'a self, + key: &'a C::Key, + ) -> impl Iterator + 'a { + self.iter().filter(move |&(_, entry)| { + let mut has_match = false; + self.config.keys(entry, |k| has_match |= key == &k); + has_match + }) + } + + /// Returns the first entry matching a key in the store. + /// + /// This is a convenience function for when at most one entry should match the key. + /// + /// # Panics + /// + /// In debug mode, panics if more than one entry matches the key. + pub fn find_one<'a>(&'a self, key: &'a C::Key) -> Option<(StoreIndex, StoreEntry<'a>)> { + let mut iter = self.find_all(key); + let first = iter.next()?; + let has_only_one_element = iter.next().is_none(); + debug_assert!(has_only_one_element); + Some(first) + } + + /// Deletes an entry from the store. + pub fn delete(&mut self, index: StoreIndex) -> Result<(), StoreError> { + if self.generation != index.generation { + return Err(StoreError::InvalidPrecondition); + } + self.delete_index(index.index); + Ok(()) + } + + /// Replaces an entry with another with the same tag in the store. + /// + /// This operation (like others) is atomic. If it returns successfully, then the old entry is + /// deleted and the new is inserted. If it fails, the old entry is not deleted and the new entry + /// is not inserted. If power is lost during the operation, during next startup, the operation + /// is either rolled-back (like in case of failure) or completed (like in case of success). + /// + /// # Errors + /// + /// Returns: + /// - `StoreFull` if the new entry does not fit in the store. + /// - `InvalidTag` if the tag of the new entry is not smaller than the configured number of + /// tags. + pub fn replace(&mut self, old: StoreIndex, new: StoreEntry) -> Result<(), StoreError> { + if self.generation != old.generation { + return Err(StoreError::InvalidPrecondition); + } + self.format.validate_entry(new)?; + let mut old_index = old.index; + // Find a slot. + let entry_len = self.replace_len(new.data.len()); + let index = self.find_slot_for_write(entry_len, Some(&mut old_index))?; + // Build a new entry replacing the old one. + let entry = self.format.build_entry(Some(old_index), new); + debug_assert_eq!(entry.len(), entry_len); + // Write the new entry. + self.write_entry(index, &entry); + // Commit the new entry, which both deletes the old entry and commits the new one. + self.commit_index(index); + Ok(()) + } + + /// Inserts an entry in the store. + /// + /// # Errors + /// + /// Returns: + /// - `StoreFull` if the new entry does not fit in the store. + /// - `InvalidTag` if the tag of the new entry is not smaller than the configured number of + /// tags. + pub fn insert(&mut self, entry: StoreEntry) -> Result<(), StoreError> { + self.format.validate_entry(entry)?; + // Build entry. + let entry = self.format.build_entry(None, entry); + // Find a slot. + let index = self.find_slot_for_write(entry.len(), None)?; + // Write entry. + self.write_entry(index, &entry); + Ok(()) + } + + /// Returns the byte cost of a replace operation. + /// + /// Computes the length in bytes that would be used in the storage if a replace operation is + /// executed provided the data of the new entry has `length` bytes. + pub fn replace_len(&self, length: usize) -> usize { + self.format.entry_size(IsReplace::Replace, length) + } + + /// Returns the byte cost of an insert operation. + /// + /// Computes the length in bytes that would be used in the storage if an insert operation is + /// executed provided the data of the inserted entry has `length` bytes. + pub fn insert_len(&self, length: usize) -> usize { + self.format.entry_size(IsReplace::Insert, length) + } + + /// Returns the erase count of all pages. + /// + /// The value at index `page` of the result is the number of times page `page` was erased. This + /// number is an underestimate in case power was lost when this page was erased. + pub fn compaction_info(&self) -> Vec { + let mut info = Vec::with_capacity(self.format.num_pages); + for page in 0..self.format.num_pages { + let (page_header, _) = self.read_page_header(page); + let erase_count = self.format.get_erase_count(page_header); + info.push(erase_count); + } + info + } + + /// Completes any ongoing page compaction. + fn recover_compact_page(&mut self) { + for page in 0..self.format.num_pages { + let (page_header, _) = self.read_page_header(page); + if self.format.is_compacting(page_header) { + let new_page = self.format.get_new_page(page_header); + self.compact_page(page, new_page); + } + } + } + + /// Rolls-back or completes any ongoing operation. + fn recover_entry_operations(&mut self) { + for page in 0..self.format.num_pages { + let (page_header, mut index) = self.read_page_header(page); + if !self.format.is_initialized(page_header) { + // Skip uninitialized pages. + continue; + } + while index.byte < self.format.page_size { + let entry_index = index; + let entry = self.read_entry(index); + index.byte += entry.len(); + if !self.format.is_alive(entry) { + // Skip deleted entries (or the page padding). + } else if self.format.is_internal(entry) { + // Finish page compaction. + self.erase_page(entry_index); + } else if !self.format.is_complete(entry) { + // Roll-back incomplete operations. + self.delete_index(entry_index); + } else if !self.format.is_committed(entry) { + // Finish complete but uncommitted operations. + self.commit_index(entry_index) + } + } + } + } + + /// Initializes uninitialized pages. + fn initialize_storage(&mut self) { + for page in 0..self.format.num_pages { + let (header, index) = self.read_page_header(page); + if self.format.is_initialized(header) { + // Update blank page. + let first_entry = self.read_entry(index); + if !self.format.is_present(first_entry) { + self.blank_page = page; + } + } else { + // We set the erase count to zero the very first time we initialize a page. + self.initialize_page(page, 0); + } + } + debug_assert!(self.blank_page != self.format.num_pages); + } + + /// Marks an entry as deleted. + /// + /// The provided index must point to the beginning of an entry. + fn delete_index(&mut self, index: Index) { + self.update_word(index, |format, word| format.set_deleted(word)); + } + + /// Finds a page with enough free space. + /// + /// Returns an index to the free space of a page which can hold an entry of `length` bytes. If + /// necessary, pages may be compacted to free space. In that case, if provided, the `old_index` + /// is updated according to compaction. + fn find_slot_for_write( + &mut self, + length: usize, + mut old_index: Option<&mut Index>, + ) -> Result { + loop { + if let Some(index) = self.choose_slot_for_write(length) { + return Ok(index); + } + match self.choose_page_for_compact() { + None => return Err(StoreError::StoreFull), + Some(page) => { + let blank_page = self.blank_page; + // Compact the chosen page and update the old index to point to the entry in the + // new page if it happened to be in the old page. This is essentially a way to + // avoid index invalidation due to compaction. + let map = self.compact_page(page, blank_page); + if let Some(old_index) = &mut old_index { + map_index(page, blank_page, &map, old_index); + } + } + } + } + } + + /// Returns whether a page has enough free space. + /// + /// Returns an index to the free space of a page with smallest free space that may hold `length` + /// bytes. + fn choose_slot_for_write(&self, length: usize) -> Option { + Iter::new(self) + .filter(|(index, entry)| { + index.page != self.blank_page + && !self.format.is_present(entry) + && length <= entry.len() + }) + .min_by_key(|(_, entry)| entry.len()) + .map(|(index, _)| index) + } + + /// Returns the page that should be compacted. + fn choose_page_for_compact(&self) -> Option { + // TODO(cretin): This could be optimized by using some cost function depending on: + // - the erase count + // - the length of the free space + // - the length of the alive entries + // We want to minimize this cost. We could also take into account the length of the entry we + // want to write to bound the number of compaction before failing with StoreFull. + // + // We should also make sure that all pages (including if they have no deleted entries and no + // free space) are eventually compacted (ideally to a heavily used page) to benefit from the + // low erase count of those pages. + (0..self.format.num_pages) + .map(|page| (page, self.page_info(page))) + .filter(|&(page, ref info)| { + page != self.blank_page + && info.erase_count < self.format.max_page_erases + && info.deleted_length > self.format.internal_entry_size() + }) + .min_by(|(_, lhs_info), (_, rhs_info)| lhs_info.compare_for_compaction(rhs_info)) + .map(|(page, _)| page) + } + + fn page_info(&self, page: usize) -> PageInfo { + let (page_header, mut index) = self.read_page_header(page); + let mut info = PageInfo { + erase_count: self.format.get_erase_count(page_header), + deleted_length: 0, + free_length: 0, + }; + while index.byte < self.format.page_size { + let entry = self.read_entry(index); + index.byte += entry.len(); + if !self.format.is_present(entry) { + debug_assert_eq!(info.free_length, 0); + info.free_length = entry.len(); + } else if self.format.is_deleted(entry) { + info.deleted_length += entry.len(); + } + } + debug_assert_eq!(index.page, page); + info + } + + fn read_slice(&self, index: Index, length: usize) -> &[u8] { + self.storage.read_slice(index, length).unwrap() + } + + /// Reads an entry (with header and footer) at a given index. + /// + /// If no entry is present, returns the free space up to the end of the page. + fn read_entry(&self, index: Index) -> &[u8] { + let first_byte = self.read_slice(index, 1); + let max_length = self.format.page_size - index.byte; + let mut length = if !self.format.is_present(first_byte) { + max_length + } else if self.format.is_internal(first_byte) { + self.format.internal_entry_size() + } else { + let header = self.read_slice(index, self.format.header_size()); + let replace = self.format.is_replace(header); + let length = self.format.get_length(header); + self.format.entry_size(replace, length) + }; + // Truncate the length to fit the page. This can only happen in case of corruption or + // partial writes. + length = core::cmp::min(length, max_length); + self.read_slice(index, length) + } + + /// Reads a page header. + /// + /// Also returns the index after the page header. + fn read_page_header(&self, page: usize) -> (&[u8], Index) { + let mut index = Index { page, byte: 0 }; + let page_header = self.read_slice(index, self.format.page_header_size()); + index.byte += page_header.len(); + (page_header, index) + } + + /// Updates a word at a given index. + /// + /// The `update` function is called with the word at `index`. The input value is the current + /// value of the word. The output value is the value that will be written. It should only change + /// bits from 1 to 0. + fn update_word(&mut self, index: Index, update: impl FnOnce(&Format, &mut [u8])) { + let word_size = self.format.word_size; + let mut word = self.read_slice(index, word_size).to_vec(); + update(&self.format, &mut word); + self.storage.write_slice(index, &word).unwrap(); + } + + fn write_entry(&mut self, index: Index, entry: &[u8]) { + self.storage.write_slice(index, entry).unwrap(); + } + + /// Initializes a page by writing the page header. + /// + /// If the page is not erased, it is first erased. + fn initialize_page(&mut self, page: usize, erase_count: usize) { + let index = Index { page, byte: 0 }; + let page = self.read_slice(index, self.format.page_size); + if !page.iter().all(|&byte| byte == 0xff) { + self.storage.erase_page(index.page).unwrap(); + } + self.update_word(index, |format, header| { + format.set_initialized(header); + format.set_erase_count(header, erase_count); + }); + self.blank_page = index.page; + } + + /// Commits a replace entry. + /// + /// Deletes the old entry and commits the new entry. + fn commit_index(&mut self, mut index: Index) { + let entry = self.read_entry(index); + index.byte += entry.len(); + let word_size = self.format.word_size; + debug_assert!(entry.len() >= 2 * word_size); + match self.format.is_replace(entry) { + IsReplace::Replace => { + let delete_index = self.format.get_replace_index(entry); + self.delete_index(delete_index); + } + IsReplace::Insert => debug_assert!(false), + }; + index.byte -= word_size; + self.update_word(index, |format, word| format.set_committed(word)); + } + + /// Compacts a page to an other. + /// + /// Returns the mapping from the alive entries in the old page to their index in the new page. + fn compact_page(&mut self, old_page: usize, new_page: usize) -> BTreeMap { + // Write the old page as being compacted to the new page. + let mut erase_count = 0; + self.update_word( + Index { + page: old_page, + byte: 0, + }, + |format, header| { + erase_count = format.get_erase_count(header); + format.set_compacting(header); + format.set_new_page(header, new_page); + }, + ); + // Copy alive entries from the old page to the new page. + let page_header_size = self.format.page_header_size(); + let mut old_index = Index { + page: old_page, + byte: page_header_size, + }; + let mut new_index = Index { + page: new_page, + byte: page_header_size, + }; + let mut map = BTreeMap::new(); + while old_index.byte < self.format.page_size { + let old_entry = self.read_entry(old_index); + let old_entry_index = old_index.byte; + old_index.byte += old_entry.len(); + if !self.format.is_alive(old_entry) { + continue; + } + let previous_mapping = map.insert(old_entry_index, new_index.byte); + debug_assert!(previous_mapping.is_none()); + // We need to copy the old entry because it is in the storage and we are going to write + // to the storage. Rust cannot tell that both entries don't overlap. + let old_entry = old_entry.to_vec(); + self.write_entry(new_index, &old_entry); + new_index.byte += old_entry.len(); + } + // Save the old page index and erase count to the new page. + let erase_index = new_index; + let erase_entry = self.format.build_erase_entry(old_page, erase_count); + self.storage.write_slice(new_index, &erase_entry).unwrap(); + // Erase the page. + self.erase_page(erase_index); + // Increase generation. + self.generation += 1; + map + } + + /// Commits an internal entry. + /// + /// The only kind of internal entry is to erase a page, which first erases the page, then + /// initializes it with the saved erase count, and finally deletes the internal entry. + fn erase_page(&mut self, erase_index: Index) { + let erase_entry = self.read_entry(erase_index); + debug_assert!(self.format.is_present(erase_entry)); + debug_assert!(!self.format.is_deleted(erase_entry)); + debug_assert!(self.format.is_internal(erase_entry)); + let old_page = self.format.get_old_page(erase_entry); + let erase_count = self.format.get_saved_erase_count(erase_entry) + 1; + // Erase the page. + self.storage.erase_page(old_page).unwrap(); + // Initialize the page. + self.initialize_page(old_page, erase_count); + // Delete the internal entry. + self.delete_index(erase_index); + } +} + +// Those functions are not meant for production. +#[cfg(feature = "std")] +impl Store { + /// Takes a snapshot of the storage after a given amount of word operations. + pub fn arm_snapshot(&mut self, delay: usize) { + self.storage.arm_snapshot(delay); + } + + /// Unarms and returns the snapshot or the delay remaining. + pub fn get_snapshot(&mut self) -> Result, usize> { + self.storage.get_snapshot() + } + + /// Takes a snapshot of the storage. + pub fn take_snapshot(&self) -> Box<[u8]> { + self.storage.take_snapshot() + } + + /// Returns the storage. + pub fn get_storage(self) -> Box<[u8]> { + self.storage.get_storage() + } + + /// Erases and initializes a page with a given erase count. + pub fn set_erase_count(&mut self, page: usize, erase_count: usize) { + self.initialize_page(page, erase_count); + } +} + +/// Maps an index from an old page to a new page if needed. +fn map_index(old_page: usize, new_page: usize, map: &BTreeMap, index: &mut Index) { + if index.page == old_page { + index.page = new_page; + index.byte = *map.get(&index.byte).unwrap(); + } +} + +/// Page information for compaction. +struct PageInfo { + /// How many times the page was erased. + erase_count: usize, + + /// Cumulative length of deleted entries (including header and footer). + deleted_length: usize, + + /// Length of the free space. + free_length: usize, +} + +impl PageInfo { + /// Returns whether a page should be compacted before another. + fn compare_for_compaction(&self, rhs: &PageInfo) -> core::cmp::Ordering { + self.erase_count + .cmp(&rhs.erase_count) + .then(rhs.deleted_length.cmp(&self.deleted_length)) + .then(self.free_length.cmp(&rhs.free_length)) + } +} + +/// Iterates over all entries (including free space) of a store. +struct Iter<'a, S: Storage, C: StoreConfig> { + store: &'a Store, + index: Index, +} + +impl<'a, S: Storage, C: StoreConfig> Iter<'a, S, C> { + fn new(store: &'a Store) -> Iter<'a, S, C> { + let index = Index { + page: 0, + byte: store.format.page_header_size(), + }; + Iter { store, index } + } +} + +impl<'a, S: Storage, C: StoreConfig> Iterator for Iter<'a, S, C> { + type Item = (Index, &'a [u8]); + + fn next(&mut self) -> Option<(Index, &'a [u8])> { + if self.index.byte == self.store.format.page_size { + self.index.page += 1; + self.index.byte = self.store.format.page_header_size(); + } + if self.index.page == self.store.format.num_pages { + return None; + } + let index = self.index; + let entry = self.store.read_entry(self.index); + self.index.byte += entry.len(); + Some((index, entry)) + } +} + +#[cfg(test)] +mod tests { + use super::super::{BufferOptions, BufferStorage}; + use super::*; + + struct Config; + + const WORD_SIZE: usize = 4; + const PAGE_SIZE: usize = 8 * WORD_SIZE; + const NUM_PAGES: usize = 3; + + impl StoreConfig for Config { + type Key = u8; + + fn num_tags(&self) -> usize { + 1 + } + + fn keys(&self, entry: StoreEntry, mut add: impl FnMut(u8)) { + assert_eq!(entry.tag, 0); + if !entry.data.is_empty() { + add(entry.data[0]); + } + } + } + + fn new_buffer(storage: Box<[u8]>) -> BufferStorage { + let options = BufferOptions { + word_size: WORD_SIZE, + page_size: PAGE_SIZE, + max_word_writes: 2, + max_page_erases: 2, + strict_write: true, + }; + BufferStorage::new(storage, options) + } + + fn new_store() -> Store { + let storage = vec![0xff; NUM_PAGES * PAGE_SIZE].into_boxed_slice(); + Store::new(new_buffer(storage), Config).unwrap() + } + + #[test] + fn insert_ok() { + let mut store = new_store(); + assert_eq!(store.iter().count(), 0); + let tag = 0; + let key = 1; + let data = &[key, 2]; + let entry = StoreEntry { tag, data }; + store.insert(entry).unwrap(); + assert_eq!(store.iter().count(), 1); + assert_eq!(store.find_one(&key).unwrap().1, entry); + } + + #[test] + fn delete_ok() { + let mut store = new_store(); + let tag = 0; + let key = 1; + let entry = StoreEntry { + tag, + data: &[key, 2], + }; + store.insert(entry).unwrap(); + assert_eq!(store.find_all(&key).count(), 1); + let (index, _) = store.find_one(&key).unwrap(); + store.delete(index).unwrap(); + assert_eq!(store.find_all(&key).count(), 0); + assert_eq!(store.iter().count(), 0); + } + + #[test] + fn insert_until_full() { + let mut store = new_store(); + let tag = 0; + let mut key = 0; + while store + .insert(StoreEntry { + tag, + data: &[key, 0], + }) + .is_ok() + { + key += 1; + } + assert!(key > 0); + } + + #[test] + fn compact_ok() { + let mut store = new_store(); + let tag = 0; + let mut key = 0; + while store + .insert(StoreEntry { + tag, + data: &[key, 0], + }) + .is_ok() + { + key += 1; + } + let (index, _) = store.find_one(&0).unwrap(); + store.delete(index).unwrap(); + store + .insert(StoreEntry { + tag: 0, + data: &[key, 0], + }) + .unwrap(); + for k in 1..=key { + assert_eq!(store.find_all(&k).count(), 1); + } + } + + #[test] + fn reboot_ok() { + let mut store = new_store(); + let tag = 0; + let key = 1; + let data = &[key, 2]; + let entry = StoreEntry { tag, data }; + store.insert(entry).unwrap(); + + // Reboot the store. + let store = store.get_storage(); + let store = Store::new(new_buffer(store), Config).unwrap(); + + assert_eq!(store.iter().count(), 1); + assert_eq!(store.find_one(&key).unwrap().1, entry); + } + + #[test] + fn replace_atomic() { + let tag = 0; + let key = 1; + let old_entry = StoreEntry { + tag, + data: &[key, 2, 3, 4, 5, 6], + }; + let new_entry = StoreEntry { + tag, + data: &[key, 7, 8, 9], + }; + let mut delay = 0; + loop { + let mut store = new_store(); + store.insert(old_entry).unwrap(); + store.arm_snapshot(delay); + let (index, _) = store.find_one(&key).unwrap(); + store.replace(index, new_entry).unwrap(); + let (complete, store) = match store.get_snapshot() { + Err(_) => (true, store.get_storage()), + Ok(store) => (false, store), + }; + let store = Store::new(new_buffer(store), Config).unwrap(); + assert_eq!(store.iter().count(), 1); + assert_eq!(store.find_all(&key).count(), 1); + let (_, cur_entry) = store.find_one(&key).unwrap(); + assert!((cur_entry == old_entry && !complete) || cur_entry == new_entry); + if complete { + break; + } + delay += 1; + } + } + + #[test] + fn compact_atomic() { + let tag = 0; + let mut delay = 0; + loop { + let mut store = new_store(); + let mut key = 0; + while store + .insert(StoreEntry { + tag, + data: &[key, 0], + }) + .is_ok() + { + key += 1; + } + let (index, _) = store.find_one(&0).unwrap(); + store.delete(index).unwrap(); + let (index, _) = store.find_one(&1).unwrap(); + store.arm_snapshot(delay); + store + .replace(index, StoreEntry { tag, data: &[1, 1] }) + .unwrap(); + let (complete, store) = match store.get_snapshot() { + Err(_) => (true, store.get_storage()), + Ok(store) => (false, store), + }; + let store = Store::new(new_buffer(store), Config).unwrap(); + assert_eq!(store.iter().count(), key as usize - 1); + for k in 2..key { + assert_eq!(store.find_all(&k).count(), 1); + assert_eq!( + store.find_one(&k).unwrap().1, + StoreEntry { tag, data: &[k, 0] } + ); + } + assert_eq!(store.find_all(&1).count(), 1); + let (_, entry) = store.find_one(&1).unwrap(); + assert_eq!(entry.tag, tag); + assert!((entry.data == [1, 0] && !complete) || entry.data == [1, 1]); + if complete { + break; + } + delay += 1; + } + } + + #[test] + fn invalid_tag() { + let mut store = new_store(); + let entry = StoreEntry { tag: 1, data: &[] }; + assert_eq!(store.insert(entry), Err(StoreError::InvalidTag)); + } + + #[test] + fn invalid_length() { + let mut store = new_store(); + let entry = StoreEntry { + tag: 0, + data: &[0; PAGE_SIZE], + }; + assert_eq!(store.insert(entry), Err(StoreError::StoreFull)); + } +} diff --git a/src/embedded_flash/syscall.rs b/src/embedded_flash/syscall.rs new file mode 100644 index 0000000..7fca17a --- /dev/null +++ b/src/embedded_flash/syscall.rs @@ -0,0 +1,195 @@ +// Copyright 2019 Google LLC +// +// 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. + +use super::{Index, Storage, StorageError, StorageResult}; +use libtock::syscalls; + +const DRIVER_NUMBER: usize = 0x50003; + +mod command_nr { + pub const GET_INFO: usize = 1; + pub mod get_info_nr { + pub const WORD_SIZE: usize = 0; + pub const PAGE_SIZE: usize = 1; + pub const MAX_WORD_WRITES: usize = 2; + pub const MAX_PAGE_ERASES: usize = 3; + } + pub const WRITE_SLICE: usize = 2; + pub const ERASE_PAGE: usize = 3; +} + +mod allow_nr { + pub const WRITE_SLICE: usize = 0; +} + +fn get_info(nr: usize) -> StorageResult { + let code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::GET_INFO, nr, 0) }; + if code < 0 { + Err(StorageError::KernelError { code }) + } else { + Ok(code as usize) + } +} + +pub struct SyscallStorage { + word_size: usize, + page_size: usize, + max_word_writes: usize, + max_page_erases: usize, + storage: &'static mut [u8], +} + +impl SyscallStorage { + /// Provides access to the embedded flash if available. + /// + /// # Safety + /// + /// The `storage` must be in a writeable flash region. + /// + /// # Errors + /// + /// Returns `BadFlash` if any of the following conditions do not hold: + /// - The word size is not a power of two. + /// - The page size is not a power of two. + /// - The page size is not a multiple of the word size. + /// + /// Returns `NotAligned` if any of the following conditions do not hold: + /// - `storage` is page-aligned. + /// - `storage.len()` is a multiple of the page size. + /// + /// # Examples + /// + /// ```rust + /// # extern crate ctap2; + /// # use ctap2::embedded_flash::SyscallStorage; + /// # use ctap2::embedded_flash::StorageResult; + /// # const NUM_PAGES: usize = 1; + /// # const PAGE_SIZE: usize = 1; + /// #[link_section = ".app_state"] + /// static mut STORAGE: [u8; NUM_PAGES * PAGE_SIZE] = [0xff; NUM_PAGES * PAGE_SIZE]; + /// # fn foo() -> StorageResult { + /// // This is safe because this is the only use of `STORAGE` in the whole program and this is + /// // called only once. + /// unsafe { SyscallStorage::new(&mut STORAGE) } + /// # } + /// ``` + pub unsafe fn new(storage: &'static mut [u8]) -> StorageResult { + let word_size = get_info(command_nr::get_info_nr::WORD_SIZE)?; + let page_size = get_info(command_nr::get_info_nr::PAGE_SIZE)?; + let max_word_writes = get_info(command_nr::get_info_nr::MAX_WORD_WRITES)?; + let max_page_erases = get_info(command_nr::get_info_nr::MAX_PAGE_ERASES)?; + if !word_size.is_power_of_two() || !page_size.is_power_of_two() { + return Err(StorageError::BadFlash); + } + let syscall = SyscallStorage { + word_size, + page_size, + max_word_writes, + max_page_erases, + storage, + }; + if !syscall.is_word_aligned(page_size) { + return Err(StorageError::BadFlash); + } + if syscall.is_page_aligned(syscall.storage.as_ptr() as usize) + && syscall.is_page_aligned(syscall.storage.len()) + { + Ok(syscall) + } else { + Err(StorageError::NotAligned) + } + } + + fn is_word_aligned(&self, x: usize) -> bool { + x & (self.word_size - 1) == 0 + } + + fn is_page_aligned(&self, x: usize) -> bool { + x & (self.page_size - 1) == 0 + } +} + +impl Storage for SyscallStorage { + fn word_size(&self) -> usize { + self.word_size + } + + fn page_size(&self) -> usize { + self.page_size + } + + fn num_pages(&self) -> usize { + self.storage.len() / self.page_size + } + + fn max_word_writes(&self) -> usize { + self.max_word_writes + } + + fn max_page_erases(&self) -> usize { + self.max_page_erases + } + + fn read_slice(&self, index: Index, length: usize) -> StorageResult<&[u8]> { + Ok(&self.storage[index.range(length, self)?]) + } + + fn write_slice(&mut self, index: Index, value: &[u8]) -> StorageResult<()> { + if !self.is_word_aligned(index.byte) || !self.is_word_aligned(value.len()) { + return Err(StorageError::NotAligned); + } + let range = index.range(value.len(), self)?; + let code = unsafe { + syscalls::allow_ptr( + DRIVER_NUMBER, + allow_nr::WRITE_SLICE, + // We rely on the driver not writing to the slice. This should use read-only allow + // when available. See https://github.com/tock/tock/issues/1274. + value.as_ptr() as *mut u8, + value.len(), + ) + }; + if code < 0 { + return Err(StorageError::KernelError { code }); + } + let code = unsafe { + syscalls::command( + DRIVER_NUMBER, + command_nr::WRITE_SLICE, + self.storage[range].as_ptr() as usize, + 0, + ) + }; + if code < 0 { + return Err(StorageError::KernelError { code }); + } + Ok(()) + } + + fn erase_page(&mut self, page: usize) -> StorageResult<()> { + let range = Index { page, byte: 0 }.range(self.page_size(), self)?; + let code = unsafe { + syscalls::command( + DRIVER_NUMBER, + command_nr::ERASE_PAGE, + self.storage[range].as_ptr() as usize, + 0, + ) + }; + if code < 0 { + return Err(StorageError::KernelError { code }); + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b12f77b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,21 @@ +// Copyright 2019 Google LLC +// +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[macro_use] +extern crate alloc; +extern crate libtock; + +pub mod embedded_flash; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..753f165 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,388 @@ +// Copyright 2019 Google LLC +// +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[macro_use] +extern crate alloc; +#[macro_use] +extern crate arrayref; +extern crate byteorder; +#[cfg(feature = "std")] +extern crate core; +extern crate ctap2; +extern crate libtock; +extern crate subtle; +#[macro_use] +extern crate cbor; +extern crate crypto; + +mod ctap; +mod usb_ctap_hid; + +use core::cell::Cell; +#[cfg(feature = "debug_ctap")] +use core::fmt::Write; +use crypto::rng256::TockRng256; +use ctap::hid::{ChannelID, CtapHid, KeepaliveStatus, ProcessedPacket}; +use ctap::status_code::Ctap2StatusCode; +use ctap::CtapState; +use libtock::buttons; +use libtock::buttons::ButtonState; +#[cfg(feature = "debug_ctap")] +use libtock::console::Console; +use libtock::led; +use libtock::result::TockValue; +use libtock::syscalls; +use libtock::timer; +#[cfg(feature = "debug_ctap")] +use libtock::timer::Timer; +use libtock::timer::{Duration, StopAlarmError, Timestamp}; + +const KEEPALIVE_DELAY_MS: isize = 100; +const KEEPALIVE_DELAY: Duration = Duration::from_ms(KEEPALIVE_DELAY_MS); +const SEND_TIMEOUT: Duration = Duration::from_ms(1000); + +fn main() { + // Setup the timer with a dummy callback (we only care about reading the current time, but the + // API forces us to set an alarm callback too). + let mut with_callback = timer::with_callback(|_, _| {}); + let timer = with_callback.init().unwrap(); + + // Setup USB driver. + if !usb_ctap_hid::setup() { + panic!("Cannot setup USB driver"); + } + + let mut rng = TockRng256 {}; + let mut ctap_state = CtapState::new(&mut rng, check_user_presence); + let mut ctap_hid = CtapHid::new(); + + let mut led_counter = 0; + let mut last_led_increment = timer.get_current_clock(); + + // Main loop. If CTAP1 is used, we register button presses for U2F while receiving and waiting. + // The way TockOS and apps currently interact, callbacks need a yield syscall to execute, + // making consistent blinking patterns and sending keepalives harder. + loop { + // Create the button callback, used for CTAP1. + #[cfg(feature = "with_ctap1")] + let button_touched = Cell::new(false); + #[cfg(feature = "with_ctap1")] + let mut buttons_callback = buttons::with_callback(|_button_num, state| { + match state { + ButtonState::Pressed => button_touched.set(true), + ButtonState::Released => (), + }; + }); + #[cfg(feature = "with_ctap1")] + let mut buttons = buttons_callback.init().unwrap(); + #[cfg(feature = "with_ctap1")] + // At the moment, all buttons are accepted. You can customize your setup here. + for mut button in &mut buttons { + button.enable().unwrap(); + } + + let mut pkt_request = [0; 64]; + let has_packet = match usb_ctap_hid::recv_with_timeout(&mut pkt_request, KEEPALIVE_DELAY) { + Some(usb_ctap_hid::SendOrRecvStatus::Received) => { + #[cfg(feature = "debug_ctap")] + print_packet_notice("Received packet", &timer); + true + } + Some(_) => panic!("Error receiving packet"), + None => false, + }; + + let now = timer.get_current_clock(); + #[cfg(feature = "with_ctap1")] + { + if button_touched.get() { + ctap_state.u2f_up_state.grant_up(now); + } + // Cleanup button callbacks. We miss button presses while processing though. + // Heavy computation mostly follows a registered touch luckily. Unregistering + // callbacks is important to not clash with those from check_user_presence. + for mut button in &mut buttons { + button.disable().unwrap(); + } + drop(buttons); + drop(buttons_callback); + } + + // These calls are making sure that even for long inactivity, wrapping clock values + // never randomly wink or grant user presence for U2F. + ctap_state.check_disable_reset(Timestamp::::from_clock_value(now)); + ctap_hid.wink_permission = ctap_hid.wink_permission.check_expiration(now); + + if has_packet { + let reply = ctap_hid.process_hid_packet(&pkt_request, now, &mut ctap_state); + // This block handles sending packets. + for mut pkt_reply in reply { + let status = usb_ctap_hid::send_or_recv_with_timeout(&mut pkt_reply, SEND_TIMEOUT); + match status { + None => { + #[cfg(feature = "debug_ctap")] + print_packet_notice("Sending packet timed out", &timer); + // TODO: reset the ctap_hid state. + // Since sending the packet timed out, we cancel this reply. + break; + } + Some(usb_ctap_hid::SendOrRecvStatus::Error) => panic!("Error sending packet"), + Some(usb_ctap_hid::SendOrRecvStatus::Sent) => { + #[cfg(feature = "debug_ctap")] + print_packet_notice("Sent packet", &timer); + } + Some(usb_ctap_hid::SendOrRecvStatus::Received) => { + #[cfg(feature = "debug_ctap")] + print_packet_notice("Received an UNEXPECTED packet", &timer); + // TODO: handle this unexpected packet. + } + } + } + } + + let now = timer.get_current_clock(); + if let Some(wait_duration) = now.wrapping_sub(last_led_increment) { + if wait_duration > KEEPALIVE_DELAY { + // Loops quickly when waiting for U2F user presence, so the next LED blink + // state is only set if enough time has elapsed. + led_counter += 1; + last_led_increment = now; + } + } else { + // This branch means the clock frequency changed. This should never happen. + led_counter += 1; + last_led_increment = now; + } + + if ctap_hid.wink_permission.is_granted(now) { + wink_leds(led_counter); + } else { + #[cfg(not(feature = "with_ctap1"))] + switch_off_leds(); + #[cfg(feature = "with_ctap1")] + { + if ctap_state.u2f_up_state.is_up_needed(now) { + // Flash the LEDs with an almost regular pattern. The inaccuracy comes from + // delay caused by processing and sending of packets. + blink_leds(led_counter); + } else { + switch_off_leds(); + } + } + } + } +} + +#[cfg(feature = "debug_ctap")] +fn print_packet_notice(notice_text: &str, timer: &Timer) { + let now_us = + (Timestamp::::from_clock_value(timer.get_current_clock()).ms() * 1000.0) as u64; + writeln!( + Console::new(), + "{} at {}.{:06} s", + notice_text, + now_us / 1_000_000, + now_us % 1_000_000 + ) + .unwrap(); +} + +// Returns whether the keepalive was sent, or false if cancelled. +fn send_keepalive_up_needed( + cid: ChannelID, + timeout: Duration, +) -> Result<(), Ctap2StatusCode> { + let keepalive_msg = CtapHid::keepalive(cid, KeepaliveStatus::UpNeeded); + for mut pkt in keepalive_msg { + let status = usb_ctap_hid::send_or_recv_with_timeout(&mut pkt, timeout); + match status { + None => { + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "Sending a KEEPALIVE packet timed out").unwrap(); + // TODO: abort user presence test? + } + Some(usb_ctap_hid::SendOrRecvStatus::Error) => panic!("Error sending KEEPALIVE packet"), + Some(usb_ctap_hid::SendOrRecvStatus::Sent) => { + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "Sent KEEPALIVE packet").unwrap(); + } + Some(usb_ctap_hid::SendOrRecvStatus::Received) => { + // We only parse one packet, because we only care about CANCEL. + let (received_cid, processed_packet) = CtapHid::process_single_packet(&pkt); + if received_cid != &cid { + #[cfg(feature = "debug_ctap")] + writeln!( + Console::new(), + "Received a packet on channel ID {:?} while sending a KEEPALIVE packet", + received_cid, + ) + .unwrap(); + return Ok(()); + } + match processed_packet { + ProcessedPacket::InitPacket { cmd, .. } => { + if cmd == CtapHid::COMMAND_CANCEL { + // We ignore the payload, we can't answer with an error code anyway. + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "User presence check cancelled").unwrap(); + return Err(Ctap2StatusCode::CTAP2_ERR_KEEPALIVE_CANCEL); + } else { + #[cfg(feature = "debug_ctap")] + writeln!( + Console::new(), + "Discarded packet with command {} received while sending a KEEPALIVE packet", + cmd, + ) + .unwrap(); + } + } + ProcessedPacket::ContinuationPacket { .. } => { + #[cfg(feature = "debug_ctap")] + writeln!( + Console::new(), + "Discarded continuation packet received while sending a KEEPALIVE packet", + ) + .unwrap(); + } + } + } + } + } + Ok(()) +} + +fn blink_leds(pattern_seed: isize) { + for l in 0..led::count() { + if (pattern_seed ^ l).count_ones() & 1 != 0 { + led::get(l).unwrap().on(); + } else { + led::get(l).unwrap().off(); + } + } +} + +fn wink_leds(pattern_seed: isize) { + // This generates a "snake" pattern circling through the LEDs. + // Fox example with 4 LEDs the sequence of lit LEDs will be the following. + // 0 1 2 3 + // * * + // * * * + // * * + // * * * + // * * + // * * * + // * * + // * * * + // * * + let count = led::count(); + let a = (pattern_seed / 2) % count; + let b = ((pattern_seed + 1) / 2) % count; + let c = ((pattern_seed + 3) / 2) % count; + + for l in 0..count { + // On nRF52840-DK, logically swap LEDs 3 and 4 so that the order of LEDs form a circle. + let k = match l { + 2 => 3, + 3 => 2, + _ => l, + }; + if k == a || k == b || k == c { + led::get(l).unwrap().on(); + } else { + led::get(l).unwrap().off(); + } + } +} + +fn switch_off_leds() { + for l in 0..led::count() { + led::get(l).unwrap().off(); + } +} + +fn check_user_presence(cid: ChannelID) -> Result<(), Ctap2StatusCode> { + // The timeout is N times the keepalive delay. + const TIMEOUT_ITERATIONS: isize = ctap::TOUCH_TIMEOUT_MS / KEEPALIVE_DELAY_MS; + + // First, send a keep-alive packet to notify that the keep-alive status has changed. + send_keepalive_up_needed(cid, KEEPALIVE_DELAY)?; + + // Listen to the button presses. + let button_touched = Cell::new(false); + let mut buttons_callback = buttons::with_callback(|_button_num, state| { + match state { + ButtonState::Pressed => button_touched.set(true), + ButtonState::Released => (), + }; + }); + let mut buttons = buttons_callback.init().unwrap(); + // At the moment, all buttons are accepted. You can customize your setup here. + for mut button in &mut buttons { + button.enable().unwrap(); + } + + let mut keepalive_response = Ok(()); + for i in 0..TIMEOUT_ITERATIONS { + blink_leds(i); + + // Setup a keep-alive callback. + let keepalive_expired = Cell::new(false); + let mut keepalive_callback = timer::with_callback(|_, _| { + keepalive_expired.set(true); + }); + let mut keepalive = keepalive_callback.init().unwrap(); + let keepalive_alarm = keepalive.set_alarm(KEEPALIVE_DELAY).unwrap(); + + // Wait for a button touch or an alarm. + syscalls::yieldk_for(|| button_touched.get() || keepalive_expired.get()); + + // Cleanup alarm callback. + match keepalive.stop_alarm(keepalive_alarm) { + Ok(()) => (), + Err(TockValue::Expected(StopAlarmError::AlreadyDisabled)) => { + assert!(keepalive_expired.get()) + } + Err(e) => panic!("Unexpected error when stopping alarm: {:?}", e), + } + + // TODO: this may take arbitrary time. The keepalive_delay should be adjusted accordingly, + // so that LEDs blink with a consistent pattern. + if keepalive_expired.get() { + // Do not return immediately, because we must clean up still. + keepalive_response = send_keepalive_up_needed(cid, KEEPALIVE_DELAY); + } + + if button_touched.get() || keepalive_response.is_err() { + break; + } + } + + switch_off_leds(); + + // Cleanup button callbacks. + for mut button in &mut buttons { + button.disable().unwrap(); + } + + // Returns whether the user was present. + if keepalive_response.is_err() { + keepalive_response + } else if button_touched.get() { + Ok(()) + } else { + Err(Ctap2StatusCode::CTAP2_ERR_USER_ACTION_TIMEOUT) + } +} diff --git a/src/usb_ctap_hid.rs b/src/usb_ctap_hid.rs new file mode 100644 index 0000000..421e70b --- /dev/null +++ b/src/usb_ctap_hid.rs @@ -0,0 +1,344 @@ +// Copyright 2019 Google LLC +// +// 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. + +use core::cell::Cell; +#[cfg(feature = "debug_ctap")] +use core::fmt::Write; +#[cfg(feature = "debug_ctap")] +use libtock::console::Console; +use libtock::result::TockValue; +use libtock::result::{EALREADY, EBUSY, SUCCESS}; +use libtock::syscalls; +use libtock::timer; +use libtock::timer::{Duration, StopAlarmError}; + +const DRIVER_NUMBER: usize = 0x20009; + +mod command_nr { + pub const CHECK: usize = 0; + pub const CONNECT: usize = 1; + pub const TRANSMIT: usize = 2; + pub const RECEIVE: usize = 3; + pub const TRANSMIT_OR_RECEIVE: usize = 4; + pub const CANCEL: usize = 5; +} + +mod subscribe_nr { + pub const TRANSMIT: usize = 1; + pub const RECEIVE: usize = 2; + pub const TRANSMIT_OR_RECEIVE: usize = 3; + pub mod callback_status { + pub const TRANSMITTED: usize = 1; + pub const RECEIVED: usize = 2; + } +} + +mod allow_nr { + pub const TRANSMIT: usize = 1; + pub const RECEIVE: usize = 2; + pub const TRANSMIT_OR_RECEIVE: usize = 3; +} + +pub fn setup() -> bool { + let result = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CHECK, 0, 0) }; + if result != 0 { + return false; + } + + let result = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CONNECT, 0, 0) }; + if result != 0 { + return false; + } + + true +} + +#[allow(dead_code)] +pub fn recv(buf: &mut [u8; 64]) -> bool { + let result = syscalls::allow(DRIVER_NUMBER, allow_nr::RECEIVE, buf); + if result.is_err() { + return false; + } + + let done = Cell::new(false); + let mut alarm = |_, _, _| done.set(true); + let subscription = syscalls::subscribe(DRIVER_NUMBER, subscribe_nr::RECEIVE, &mut alarm); + if subscription.is_err() { + return false; + } + + let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::RECEIVE, 0, 0) }; + if result_code != 0 { + return false; + } + + syscalls::yieldk_for(|| done.get()); + true +} + +#[allow(dead_code)] +pub fn send(buf: &mut [u8; 64]) -> bool { + let result = syscalls::allow(DRIVER_NUMBER, allow_nr::TRANSMIT, buf); + if result.is_err() { + return false; + } + + let done = Cell::new(false); + let mut alarm = |_, _, _| done.set(true); + let subscription = syscalls::subscribe(DRIVER_NUMBER, subscribe_nr::TRANSMIT, &mut alarm); + if subscription.is_err() { + return false; + } + + let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::TRANSMIT, 0, 0) }; + if result_code != 0 { + return false; + } + + syscalls::yieldk_for(|| done.get()); + true +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum SendOrRecvStatus { + Error, + Sent, + Received, +} + +// Either sends or receive a packet. +// Because USB transactions are initiated by the host, we don't decide whether an IN transaction +// (send for us), an OUT transaction (receive for us), or no transaction at all will happen next. +// +// - If an IN transaction happens first, the initial content of buf is sent to the host and the +// Sent status is returned. +// - If an OUT transaction happens first, the content of buf is replaced by the packet received +// from the host and Received status is returned. In that case, the original content of buf is not +// sent to the host, and it's up to the caller to retry sending or to handle the packet received +// from the host. +#[allow(dead_code)] +pub fn send_or_recv(buf: &mut [u8; 64]) -> SendOrRecvStatus { + let result = syscalls::allow(DRIVER_NUMBER, allow_nr::TRANSMIT_OR_RECEIVE, buf); + if result.is_err() { + return SendOrRecvStatus::Error; + } + + let status = Cell::new(None); + let mut alarm = |direction, _, _| { + status.set(Some(match direction { + subscribe_nr::callback_status::TRANSMITTED => SendOrRecvStatus::Sent, + subscribe_nr::callback_status::RECEIVED => SendOrRecvStatus::Received, + // Unknown direction sent by the kernel. + _ => SendOrRecvStatus::Error, + })); + }; + + let subscription = + syscalls::subscribe(DRIVER_NUMBER, subscribe_nr::TRANSMIT_OR_RECEIVE, &mut alarm); + if subscription.is_err() { + return SendOrRecvStatus::Error; + } + + let result_code = + unsafe { syscalls::command(DRIVER_NUMBER, command_nr::TRANSMIT_OR_RECEIVE, 0, 0) }; + if result_code != 0 { + return SendOrRecvStatus::Error; + } + + syscalls::yieldk_for(|| status.get().is_some()); + status.get().unwrap() +} + +// Same as recv, but with a timeout. +// If the timeout elapses, return None. +pub fn recv_with_timeout( + buf: &mut [u8; 64], + timeout_delay: Duration, +) -> Option { + let result = syscalls::allow(DRIVER_NUMBER, allow_nr::RECEIVE, buf); + if result.is_err() { + return Some(SendOrRecvStatus::Error); + } + + let status = Cell::new(None); + let mut alarm = |direction, _, _| { + status.set(Some(match direction { + subscribe_nr::callback_status::RECEIVED => SendOrRecvStatus::Received, + // Unknown direction or "transmitted" sent by the kernel. + _ => SendOrRecvStatus::Error, + })); + }; + + let subscription = syscalls::subscribe(DRIVER_NUMBER, subscribe_nr::RECEIVE, &mut alarm); + if subscription.is_err() { + return Some(SendOrRecvStatus::Error); + } + + // Setup a time-out callback. + let timeout_expired = Cell::new(false); + let mut timeout_callback = timer::with_callback(|_, _| { + timeout_expired.set(true); + }); + let mut timeout = match timeout_callback.init() { + Ok(x) => x, + Err(_) => return Some(SendOrRecvStatus::Error), + }; + let timeout_alarm = match timeout.set_alarm(timeout_delay) { + Ok(x) => x, + Err(_) => return Some(SendOrRecvStatus::Error), + }; + + // Trigger USB reception. + let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::RECEIVE, 0, 0) }; + if result_code != 0 { + return Some(SendOrRecvStatus::Error); + } + + syscalls::yieldk_for(|| status.get().is_some() || timeout_expired.get()); + + // Cleanup alarm callback. + match timeout.stop_alarm(timeout_alarm) { + Ok(()) => (), + Err(TockValue::Expected(StopAlarmError::AlreadyDisabled)) => { + if !timeout_expired.get() { + #[cfg(feature = "debug_ctap")] + writeln!( + Console::new(), + "The receive timeout already expired, but the callback wasn't executed." + ) + .unwrap(); + } + } + Err(e) => panic!("Unexpected error when stopping alarm: {:?}", e), + } + + // Cancel USB transaction if necessary. + if status.get().is_none() { + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "Cancelling USB receive due to timeout").unwrap(); + let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) }; + match result_code { + // - SUCCESS means that we successfully cancelled the transaction. + // - EALREADY means that the transaction was already completed. + SUCCESS | EALREADY => (), + // - EBUSY means that the transaction is in progress. + EBUSY => { + // The app should wait for it, but it may never happen if the remote app crashes. + // We just return to avoid a deadlock. + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "Couldn't cancel the USB receive").unwrap(); + } + _ => panic!( + "Unexpected error when cancelling USB receive: {:?}", + result_code + ), + } + } + + status.get() +} + +// Same as send_or_recv, but with a timeout. +// If the timeout elapses, return None. +pub fn send_or_recv_with_timeout( + buf: &mut [u8; 64], + timeout_delay: Duration, +) -> Option { + let result = syscalls::allow(DRIVER_NUMBER, allow_nr::TRANSMIT_OR_RECEIVE, buf); + if result.is_err() { + return Some(SendOrRecvStatus::Error); + } + + let status = Cell::new(None); + let mut alarm = |direction, _, _| { + status.set(Some(match direction { + subscribe_nr::callback_status::TRANSMITTED => SendOrRecvStatus::Sent, + subscribe_nr::callback_status::RECEIVED => SendOrRecvStatus::Received, + // Unknown direction sent by the kernel. + _ => SendOrRecvStatus::Error, + })); + }; + + let subscription = + syscalls::subscribe(DRIVER_NUMBER, subscribe_nr::TRANSMIT_OR_RECEIVE, &mut alarm); + if subscription.is_err() { + return Some(SendOrRecvStatus::Error); + } + + // Setup a time-out callback. + let timeout_expired = Cell::new(false); + let mut timeout_callback = timer::with_callback(|_, _| { + timeout_expired.set(true); + }); + let mut timeout = match timeout_callback.init() { + Ok(x) => x, + Err(_) => return Some(SendOrRecvStatus::Error), + }; + let timeout_alarm = match timeout.set_alarm(timeout_delay) { + Ok(x) => x, + Err(_) => return Some(SendOrRecvStatus::Error), + }; + + // Trigger USB transmission. + let result_code = + unsafe { syscalls::command(DRIVER_NUMBER, command_nr::TRANSMIT_OR_RECEIVE, 0, 0) }; + if result_code != 0 { + return Some(SendOrRecvStatus::Error); + } + + syscalls::yieldk_for(|| status.get().is_some() || timeout_expired.get()); + + // Cleanup alarm callback. + match timeout.stop_alarm(timeout_alarm) { + Ok(()) => (), + Err(TockValue::Expected(StopAlarmError::AlreadyDisabled)) => { + if !timeout_expired.get() { + #[cfg(feature = "debug_ctap")] + writeln!( + Console::new(), + "The send/receive timeout already expired, but the callback wasn't executed." + ) + .unwrap(); + } + } + Err(e) => panic!("Unexpected error when stopping alarm: {:?}", e), + } + + // Cancel USB transaction if necessary. + if status.get().is_none() { + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "Cancelling USB transaction due to timeout").unwrap(); + let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) }; + match result_code { + // - SUCCESS means that we successfully cancelled the transaction. + // - EALREADY means that the transaction was already completed. + SUCCESS | EALREADY => (), + // - EBUSY means that the transaction is in progress. + EBUSY => { + // The app should wait for it, but it may never happen if the remote app crashes. + // We just return to avoid a deadlock. + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "Couldn't cancel the transaction").unwrap(); + } + _ => panic!( + "Unexpected error when cancelling USB transaction: {:?}", + result_code + ), + } + #[cfg(feature = "debug_ctap")] + writeln!(Console::new(), "Cancelled USB transaction!").unwrap(); + } + + status.get() +} diff --git a/third_party/libtock-rs b/third_party/libtock-rs new file mode 160000 index 0000000..ab2c945 --- /dev/null +++ b/third_party/libtock-rs @@ -0,0 +1 @@ +Subproject commit ab2c945184b98ecae3e70ac678e9f5231deef73b diff --git a/third_party/tock b/third_party/tock new file mode 160000 index 0000000..862452b --- /dev/null +++ b/third_party/tock @@ -0,0 +1 @@ +Subproject commit 862452b77ae0fc160231a2250de385dc7c358ef7 diff --git a/tools/gen_key_materials.sh b/tools/gen_key_materials.sh new file mode 100644 index 0000000..260d8eb --- /dev/null +++ b/tools/gen_key_materials.sh @@ -0,0 +1,169 @@ +#!/bin/bash +# Copyright 2019 Google LLC +# +# 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. + +generate_crypto_materials () { + # Root CA key pair and certificate + local ca_priv_key=crypto_data/opensk_ca.key + local ca_cert_name=crypto_data/opensk_ca + + # Attestation key pair and certificate that will be embedded into the + # firmware. The certificate will be signed by the Root CA. + local opensk_key=crypto_data/opensk.key + local opensk_cert_name=crypto_data/opensk_cert + + # Rust file that we will generate will all cryptographic data. + local rust_file=src/ctap/key_material.rs + + # Allow invoker to override the command with a full path. + local openssl=${OPENSSL:-$(which openssl)} + + # We need openssl command to continue + if [ ! -x "${openssl}" ] + then + echo "Missing openssl command. Try to specify its full path using OPENSSL environment variable." + exit 1 + fi + + mkdir -p crypto_data + if [ ! -f "${ca_priv_key}" ] + then + "${openssl}" ecparam -genkey -name prime256v1 -out "${ca_priv_key}" + fi + + if [ ! -f "${ca_cert_name}.pem" ] + then + "${openssl}" req \ + -new \ + -key "${ca_priv_key}" \ + -out "${ca_cert_name}.csr" \ + -subj "/CN=Google OpenSK CA" + "${openssl}" x509 \ + -trustout \ + -req \ + -days 7305 \ + -in "${ca_cert_name}.csr" \ + -signkey "${ca_priv_key}" \ + -outform pem \ + -out "${ca_cert_name}.pem" \ + -sha256 + fi + + if [ ! -f "${opensk_key}" ] + then + "${openssl}" ecparam -genkey -name prime256v1 -out "${opensk_key}" + fi + + if [ ! -f "${opensk_cert_name}.pem" ] + then + "${openssl}" req \ + -new \ + -key "${opensk_key}" \ + -out "${opensk_cert_name}.csr" \ + -subj "/CN=Google OpenSK Hacker Edition" + "${openssl}" x509 \ + -req \ + -days 3652 \ + -in "${opensk_cert_name}.csr" \ + -CA "${ca_cert_name}.pem" \ + -CAkey "${ca_priv_key}" \ + -CAcreateserial \ + -outform pem \ + -out "${opensk_cert_name}.pem" \ + -sha256 + fi + + local cert_mtime=$(stat --printf="%Y" "${opensk_cert_name}.pem") + local rust_file_mtime=0 + # Only take into consideration the mtime of the file if it exists and if we're not forcing + # the rust file to be re-generated. + if [ -f "${rust_file}" -a "x$1" != "xY" ] + then + rust_file_mtime=$(stat --printf="%Y" "${rust_file}") + fi + if [ $cert_mtime -gt $rust_file_mtime ] + then + local cert_size=$("${openssl}" x509 \ + -in "${opensk_cert_name}.pem" \ + -outform der 2>/dev/null \ + | wc -c) + local cert_serial_hex=$("${openssl}" x509 \ + -in "${opensk_cert_name}.pem" \ + -noout \ + -serial \ + | cut -d'=' -f2) + # Pad with zeroes in case the serial is too short. We don't care if the + # serial is longer than 32 characters as we will only process the first 32 + # characters in the loop later. + cert_serial_hex="${cert_serial_hex}00000000000000000000000000000000" + + # Create header + echo "// This file had been generated by OpenSK deploy.sh script" > "${rust_file}" + echo "" >> "${rust_file}" + + echo "pub const AAGUID: [u8; 16] = [" >> "${rust_file}" + for i in `seq 0 2 30` + do + echo -n "0x${cert_serial_hex:$i:2}, " >> "${rust_file}" + done + echo "" >> "${rust_file}" + echo "];" >> "${rust_file}" + echo "" >> "${rust_file}" + + echo "pub const ATTESTATION_CERTIFICATE: [u8; ${cert_size}] = [" >> "${rust_file}" + "${openssl}" x509 \ + -in "${opensk_cert_name}.pem" \ + -outform der 2>/dev/null \ + | xxd -i >> "${rust_file}" + echo "];" >> "${rust_file}" + echo "" >> "${rust_file}" + + # Private key is tricky to extract as we want the raw value and not the DER encoding + # Example output of openssl ec -in file.key -noout -text: + # read EC key + # Private-Key: (256 bit) + # priv: + # 47:b3:58:b8:f0:09:1d:72:b1:03:34:62:9a:c7:b2: + # b2:e1:06:28:15:69:d4:82:b5:4e:21:6d:98:bf:65: + # 98:34 + # pub: + # 04:32:84:a1:3c:90:db:3f:db:d7:fb:ff:e9:00:c8: + # 8a:a1:79:2e:95:2e:7c:86:ec:19:03:97:6e:7c:d6: + # 67:eb:28:56:f1:d8:dd:cb:ae:ce:b9:cb:e4:6d:9d: + # 1d:76:96:fc:48:9b:2d:d5:80:86:04:3d:f9:fe:6c: + # f3:9a:45:bc:b1 + # ASN1 OID: prime256v1 + # NIST CURVE: P-256 + # + # The awk script starts printing lines after seeing a line starting with + # "priv:" and stops printing as soon as it reaches a line that doesn't start + # with a space. + # The sed script then converts the output into a proper hex-encode byte + # array by replacing the initial spaces on each line with "0x", replacing + # the semicolons at the end of each line by commas and replacing all + # remainging semicolons by ". 0x". + echo "pub const ATTESTATION_PRIVATE_KEY: [u8; 32] = [" >> "${rust_file}" + "${openssl}" ec \ + -in "${opensk_key}" \ + -noout \ + -text 2>/dev/null \ + | awk '/^priv:/{p=1;next}/^[^ ]/{p=0}p' \ + | sed -e 's/^ */0x/;s/:$/,/;s/:/, 0x/g' >> "${rust_file}" + echo "];" >> "${rust_file}" + echo "" >> "${rust_file}" + + # If the tool is installed, prettify the file. It will catch syntax errors earlier. + which rustfmt > /dev/null 2>&1 && rustfmt "${rust_file}" + fi +}