Initial commit

This commit is contained in:
Jean-Michel Picod
2020-01-28 15:09:10 +01:00
commit f91d2fd3db
90 changed files with 31123 additions and 0 deletions

7
.cargo/config Normal file
View File

@@ -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",
]

16
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,16 @@
## Expected Behavior
## Actual Behavior
## Steps to Reproduce the Problem
1.
1.
1.
## Specifications
- Version:
- Platform:

6
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,6 @@
Fixes #<issue_number_goes_here>
> It's a good idea to open an issue first for discussion.
- [ ] Tests pass
- [ ] Appropriate changes to README are included in PR

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
target/
Cargo.lock
# Prevent people from commiting sensitive files.
crypto_data/
src/ctap/key_material.rs

6
.gitmodules vendored Normal file
View File

@@ -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

35
.markdownlint.json Normal file
View File

@@ -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"
}
}

45
.travis.yml Normal file
View File

@@ -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

6
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"recommendations": [
"davidanson.vscode-markdownlint",
"rust-lang.rust"
]
}

7
.vscode/settings.json vendored Normal file
View File

@@ -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"
}

35
Cargo.toml Normal file
View File

@@ -0,0 +1,35 @@
[package]
name = "ctap2"
version = "0.1.0"
authors = [
"Fabian Kaczmarczyck <kaczmarczyck@google.com>",
"Guillaume Endignoux <guillaumee@google.com>",
"Jean-Michel Picod <jmichel@google.com>",
]
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

202
LICENSE Normal file
View File

@@ -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.

116
README.md Normal file
View File

@@ -0,0 +1,116 @@
# <img alt="OpenSK logo" src="docs/img/OpenSK.svg" width="200px">
[![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&reg; 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).

321
deploy.sh Executable file
View File

@@ -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 <<EOH
Usage: $0 [options...] <actions...>
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

28
docs/contributing.md Normal file
View File

@@ -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 <https://cla.developers.google.com/> 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/).

1
docs/img/OpenSK.svg Normal file
View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 116.46 110.31"><defs><style>.cls-1{fill:#5f6368;}.cls-2{fill:#fff;}</style></defs><title>Mono</title><path class="cls-1" d="M42.39,47.19v0h0Z"/><path class="cls-1" d="M51.49,44.09a11.46,11.46,0,0,1-6.2,10.21.05.05,0,0,1,0,0,31.79,31.79,0,0,1-1.45-3c-.12-.29-.23-.57-.33-.83s-.17-.43-.25-.65c-.3-.84-.53-1.63-.8-2.53a3.48,3.48,0,0,0-1-5.83,3.5,3.5,0,0,0-1.3-.25,3.41,3.41,0,0,0-1.77.49,1.64,1.64,0,0,0-.22.14,2.08,2.08,0,0,0-.4.32,1.21,1.21,0,0,0-.19.2,1.12,1.12,0,0,0-.18.2,2.71,2.71,0,0,0-.24.35,1.74,1.74,0,0,0-.11.18,2.72,2.72,0,0,0-.11.25,1.75,1.75,0,0,0-.12.32,2,2,0,0,0-.09.33,3.36,3.36,0,0,0-.08.71,2.19,2.19,0,0,0,0,.36,1.26,1.26,0,0,0,0,.33v0a3,3,0,0,0,.09.32,1.75,1.75,0,0,0,.12.32,2.14,2.14,0,0,0,.1.22l.12.22a1.79,1.79,0,0,0,.2.28,1.71,1.71,0,0,0,.21.26,1.54,1.54,0,0,0,.26.25L33.67,55a12.09,12.09,0,0,1,4.51-22.24h0a5.35,5.35,0,0,1,.56-.07c.21,0,.41,0,.62-.05H40l.62,0,.51,0h.06l.54.07.27,0,.27,0a11.54,11.54,0,0,1,8.14,6.49A11.36,11.36,0,0,1,51.49,44.09Z"/><path class="cls-1" d="M42.39,47.19v0h0Z"/><path class="cls-2" d="M42.39,47.19v0h0Z"/><path class="cls-1" d="M35.27,84a8.77,8.77,0,0,1-.72,3.67,5.46,5.46,0,0,1-2.06,2.42,6.1,6.1,0,0,1-6.18,0,5.61,5.61,0,0,1-2.09-2.41,8.36,8.36,0,0,1-.74-3.6v-.8a8.65,8.65,0,0,1,.73-3.67,5.57,5.57,0,0,1,2.08-2.44,6.05,6.05,0,0,1,6.17,0,5.54,5.54,0,0,1,2.07,2.4,8.52,8.52,0,0,1,.74,3.65Zm-2.47-.74a6.2,6.2,0,0,0-.89-3.63,3.17,3.17,0,0,0-5,0A6.1,6.1,0,0,0,26,83.2V84a6.15,6.15,0,0,0,.91,3.62,2.92,2.92,0,0,0,2.54,1.28,2.89,2.89,0,0,0,2.52-1.25A6.28,6.28,0,0,0,32.8,84Z"/><path class="cls-1" d="M46.76,85.58a6.28,6.28,0,0,1-1.11,3.91,3.56,3.56,0,0,1-3,1.46,3.61,3.61,0,0,1-2.79-1.14v5H37.5V80.19h2.19l.1,1.08A3.46,3.46,0,0,1,42.63,80a3.59,3.59,0,0,1,3,1.44,6.47,6.47,0,0,1,1.1,4Zm-2.36-.2a4.4,4.4,0,0,0-.63-2.51A2.06,2.06,0,0,0,42,81.94a2.19,2.19,0,0,0-2.09,1.2v4.69A2.21,2.21,0,0,0,42,89.06a2.07,2.07,0,0,0,1.78-.91A4.93,4.93,0,0,0,44.4,85.38Z"/><path class="cls-1" d="M53.42,91a4.9,4.9,0,0,1-3.66-1.42,5.16,5.16,0,0,1-1.4-3.78v-.29A6.29,6.29,0,0,1,49,82.63a4.69,4.69,0,0,1,1.71-1.94A4.58,4.58,0,0,1,53.15,80a4.16,4.16,0,0,1,3.33,1.37,5.86,5.86,0,0,1,1.18,3.9v1H50.75a3.18,3.18,0,0,0,.88,2.07,2.61,2.61,0,0,0,1.93.76,3.21,3.21,0,0,0,2.65-1.32L57.49,89a4.31,4.31,0,0,1-1.69,1.47A5.3,5.3,0,0,1,53.42,91Zm-.28-9a2,2,0,0,0-1.58.68,3.59,3.59,0,0,0-.77,1.91h4.52v-.18a2.89,2.89,0,0,0-.63-1.8A2,2,0,0,0,53.14,81.9Z"/><path class="cls-1" d="M61.7,80.19l.07,1.22A3.79,3.79,0,0,1,64.84,80q3.3,0,3.36,3.78v7H65.83V83.91a2.19,2.19,0,0,0-.43-1.49A1.84,1.84,0,0,0,64,81.94a2.28,2.28,0,0,0-2.14,1.3v7.52H59.46V80.19Z"/><path class="cls-1" d="M78.28,87.1a1.72,1.72,0,0,0-.66-1.45,7.76,7.76,0,0,0-2.38-1,11.5,11.5,0,0,1-2.73-1.16,3.63,3.63,0,0,1-1.94-3.18A3.44,3.44,0,0,1,72,77.46a5.67,5.67,0,0,1,3.64-1.12,6.17,6.17,0,0,1,2.64.55,4.31,4.31,0,0,1,1.83,1.56,4,4,0,0,1,.66,2.24H78.28a2.21,2.21,0,0,0-.7-1.74,2.86,2.86,0,0,0-2-.63,3,3,0,0,0-1.88.51A1.73,1.73,0,0,0,73,80.28a1.56,1.56,0,0,0,.72,1.3,8.34,8.34,0,0,0,2.38,1,11,11,0,0,1,2.67,1.12,4.17,4.17,0,0,1,1.47,1.45,3.68,3.68,0,0,1,.47,1.91,3.37,3.37,0,0,1-1.36,2.82,5.92,5.92,0,0,1-3.7,1,7,7,0,0,1-2.83-.57,4.65,4.65,0,0,1-2-1.57,4,4,0,0,1-.72-2.35H72.6a2.3,2.3,0,0,0,.8,1.88,3.51,3.51,0,0,0,2.29.66,3,3,0,0,0,1.94-.52A1.7,1.7,0,0,0,78.28,87.1Z"/><path class="cls-1" d="M87,84.62l-1.61,1.7v4.44H82.92V76.54h2.47v6.67l1.37-1.69,4.16-5h3l-5.29,6.3,5.59,7.92H91.28Z"/><path class="cls-1" d="M59.74,19.62,41.21,25.14v5.48h.1a.85.85,0,0,1,.23,0l.48.06.43.07h.15l.09,0a13.53,13.53,0,0,1,9.55,7.61c.08.17.14.35.21.52H72.58a2.35,2.35,0,0,1,1.75,3.92s-5.14,6-6.82,6a2.82,2.82,0,0,1-1.91-.75l-2.68-2.47a1.21,1.21,0,0,0-1.52-.1l-4,2.8a2.77,2.77,0,0,1-1.62.52H52.6c-.11.28-.21.56-.33.83a13.37,13.37,0,0,1-5.79,6.22l-.16.15A31.31,31.31,0,0,0,59.74,67.74a31.51,31.51,0,0,0,18.13-24.3,48.25,48.25,0,0,0,.4-5.23V25.14Z"/><path class="cls-1" d="M42.39,47.19v0h0Z"/><path class="cls-1" d="M42.39,47.19v0h0Z"/><path class="cls-2" d="M42.39,47.19v0h0Z"/><path class="cls-1" d="M42.39,47.19v0h0Z"/><path class="cls-1" d="M42.39,47.19v0h0Z"/><path class="cls-2" d="M42.39,47.19v0h0Z"/><path class="cls-1" d="M42.39,47.19v0h0Z"/><path class="cls-1" d="M42.39,47.19v0h0Z"/><path class="cls-2" d="M42.39,47.19v0h0Z"/></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

BIN
docs/img/dongle_clip.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

BIN
docs/img/dongle_front.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

BIN
docs/img/dongle_pads.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

262
docs/install.md Normal file
View File

@@ -0,0 +1,262 @@
<img alt="OpenSK logo" src="img/OpenSK.svg" width="200px">
# 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.

180
examples/crypto_bench.rs Normal file
View File

@@ -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::<sha256::Sha256, _>",
|| {
k.sign_rng::<sha256::Sha256, _>(&[], &mut rng);
},
);
bench(
&mut console,
&timer,
"ecdsa::SecKey::sign_rfc6979::<sha256::Sha256>",
|| {
k.sign_rfc6979::<sha256::Sha256>(&[]);
},
);
writeln!(console, "****************************************").unwrap();
writeln!(console, "All the benchmarks are done.\nHave a nice day!").unwrap();
writeln!(console, "****************************************").unwrap();
}
fn bench<F>(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::<f64>::from_clock_value(timer.get_current_clock());
for _ in 0..count {
f();
}
let end = Timestamp::<f64>::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;
}
}

170
layout.ld Normal file
View File

@@ -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")

15
libraries/cbor/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "cbor"
version = "0.1.0"
authors = [
"Fabian Kaczmarczyck <kaczmarczyck@google.com>",
"Guillaume Endignoux <guillaumee@google.com>",
"Jean-Michel Picod <jmichel@google.com>",
]
license = "Apache-2.0"
edition = "2018"
[dependencies]
[features]
std = []

29
libraries/cbor/src/lib.rs Normal file
View File

@@ -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;

View File

@@ -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::<bool>::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::<String>,
"foo" => true,
12 => None::<&str>,
b"bar" => Some(cbor_null!()),
13 => None::<Vec<u8>>,
5 => "foo",
14 => None::<&[u8]>,
6 => Some(b"bar" as &[u8]),
15 => None::<bool>,
7 => cbor_array![],
16 => None::<i32>,
8 => Some(cbor_array![0, 1]),
17 => None::<i64>,
9 => cbor_map!{},
18 => None::<u64>,
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);
}
}

View File

@@ -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<Value, DecoderError> {
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<Value, DecoderError> {
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<u64, DecoderError> {
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<Value, DecoderError> {
Ok(cbor_unsigned!(size_value))
}
fn decode_value_to_negative(&self, size_value: u64) -> Result<Value, DecoderError> {
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<Value, DecoderError> {
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<Value, DecoderError> {
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<Value, DecoderError> {
// 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<Value, DecoderError> {
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<Value, DecoderError> {
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));
}
}
}

View File

@@ -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<Value>),
Map(BTreeMap<KeyType, Value>),
// 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<u8>),
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<Ordering> {
Some(self.cmp(other))
}
}
impl SimpleValue {
pub fn from_integer(int: u64) -> Option<SimpleValue> {
match int {
20 => Some(SimpleValue::FalseValue),
21 => Some(SimpleValue::TrueValue),
22 => Some(SimpleValue::NullValue),
23 => Some(SimpleValue::Undefined),
_ => None,
}
}
}
impl From<u64> for KeyType {
fn from(unsigned: u64) -> Self {
KeyType::Unsigned(unsigned)
}
}
impl From<i64> for KeyType {
fn from(i: i64) -> Self {
KeyType::integer(i)
}
}
impl From<i32> for KeyType {
fn from(i: i32) -> Self {
KeyType::integer(i as i64)
}
}
impl From<Vec<u8>> for KeyType {
fn from(bytes: Vec<u8>) -> Self {
KeyType::ByteString(bytes)
}
}
impl From<&[u8]> for KeyType {
fn from(bytes: &[u8]) -> Self {
KeyType::ByteString(bytes.to_vec())
}
}
impl From<String> 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<T> From<T> for Value
where
KeyType: From<T>,
{
fn from(t: T) -> Self {
Value::KeyValue(KeyType::from(t))
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::bool_value(b)
}
}
pub trait IntoCborKey {
fn into_cbor_key(self) -> KeyType;
}
impl<T> IntoCborKey for T
where
KeyType: From<T>,
{
fn into_cbor_key(self) -> KeyType {
KeyType::from(self)
}
}
pub trait IntoCborValue {
fn into_cbor_value(self) -> Value;
}
impl<T> IntoCborValue for T
where
Value: From<T>,
{
fn into_cbor_value(self) -> Value {
Value::from(self)
}
}
pub trait IntoCborValueOption {
fn into_cbor_value_option(self) -> Option<Value>;
}
impl<T> IntoCborValueOption for T
where
Value: From<T>,
{
fn into_cbor_value_option(self) -> Option<Value> {
Some(Value::from(self))
}
}
impl<T> IntoCborValueOption for Option<T>
where
Value: From<T>,
{
fn into_cbor_value_option(self) -> Option<Value> {
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"));
}
}

View File

@@ -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<u8>) -> bool {
let mut writer = Writer::new(encoded_cbor);
writer.encode_cbor(value, Writer::MAX_NESTING_DEPTH)
}
struct Writer<'a> {
encoded_cbor: &'a mut Vec<u8>,
}
impl<'a> Writer<'a> {
const MAX_NESTING_DEPTH: i8 = 4;
pub fn new(encoded_cbor: &mut Vec<u8>) -> 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<Vec<u8>> {
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));
}
}

View File

@@ -0,0 +1,29 @@
[package]
name = "crypto"
version = "0.1.0"
authors = [
"Fabian Kaczmarczyck <kaczmarczyck@google.com>",
"Guillaume Endignoux <guillaumee@google.com>",
"Jean-Michel Picod <jmichel@google.com>",
]
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 = []

View File

@@ -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);
}
}
}

258
libraries/crypto/src/cbc.rs Normal file
View File

@@ -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<K>(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<K>(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<Block16> = 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]);
}
}

View File

@@ -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<ExponentP256> {
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<NonZeroExponentP256> {
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>(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<NonZeroExponentP256> {
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<NonZeroExponentP256> {
let mut values: Vec<NonZeroExponentP256> = 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<ExponentP256> {
let mut values: Vec<ExponentP256> = 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<Int256>,
index: usize,
}
impl StressTestingRng {
pub fn new(values: Vec<Int256>) -> 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);
}
}

View File

@@ -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<GFP256> {
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<GFP256> {
let mut values: Vec<GFP256> = 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));
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}
}

View File

@@ -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<R>(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<PubKey> {
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<PubKey> {
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.
}

View File

@@ -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<R>(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<H, R>(&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<H>(&self, msg: &[u8]) -> Signature
where
H: Hash256 + HashBlockSize64Bytes,
{
let m = ExponentP256::modn(Int256::from_bin(&H::hash(msg)));
let mut rfc_6979 = Rfc6979::<H>::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<Signature> {
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<H>(&self, msg: &[u8]) -> NonZeroExponentP256
where
H: Hash256 + HashBlockSize64Bytes,
{
let m = ExponentP256::modn(Int256::from_bin(&H::hash(msg)));
let mut rfc_6979 = Rfc6979::<H>::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<SecKey> {
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<u8> {
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<Signature> {
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<PubKey> {
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<Vec<u8>> {
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<H>(&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<H>
where
H: Hash256 + HashBlockSize64Bytes,
{
k: [u8; 32],
v: [u8; 32],
hash_marker: PhantomData<H>,
}
impl<H> Rfc6979<H>
where
H: Hash256 + HashBlockSize64Bytes,
{
pub fn new(sk: &SecKey, msg: &[u8]) -> Rfc6979<H> {
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::<H>(&k, &contents);
let v = hmac_256::<H>(&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::<H>(&k, &contents);
let v = hmac_256::<H>(&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::<H>(&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::<H>(&self.k, &v1);
self.v = hmac_256::<H>(&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::<Sha256>(msg.as_bytes()).to_int(),
int256_from_hex(k)
);
let sign = sk.sign_rfc6979::<Sha256>(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::<Sha256>(&msg);
assert!(pk.verify_vartime::<Sha256>(&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::<Sha256, _>(&msg, &mut rng);
assert!(pk.verify_vartime::<Sha256>(&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::<Sha256>(&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::<Sha256, _>(&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.
}

View File

@@ -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<H>(key: &[u8], contents: &[u8], mac: &[u8; HASH_SIZE]) -> bool
where
H: Hash256 + HashBlockSize64Bytes,
{
let expected_mac = hmac_256::<H>(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<H>(key: &[u8], contents: &[u8], pin: &[u8; 16]) -> bool
where
H: Hash256 + HashBlockSize64Bytes,
{
let expected_mac = hmac_256::<H>(key, contents);
bool::from(array_ref![expected_mac, 0, 16].ct_eq(pin))
}
pub fn hmac_256<H>(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::<Sha256>(&key, &contents);
assert!(verify_hmac_256::<Sha256>(&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::<Sha256>(&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::<Sha256>(&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>(&[], &[]), Sha256::hash(&buf));
}
#[test]
fn test_hmac_sha256_examples() {
assert_eq!(
hmac_256::<Sha256>(&[], &[]),
hex::decode("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad")
.unwrap()
.as_slice()
);
assert_eq!(
hmac_256::<Sha256>(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::<Sha256>(&input, &input),
hex::decode(hashes[i] as &[u8]).unwrap().as_slice()
);
input.push(b'A');
}
}
// TODO: more tests
}

View File

@@ -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]);
}

View File

@@ -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);
}
}

View File

@@ -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<u32>; 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<u32>; 8];
#[allow(clippy::many_single_char_names)]
fn hash_block(state: &mut Self::State, block: &[u8; 64]) {
let mut w: [Wrapping<u32>; 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<u32>; 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<u32>, f: Wrapping<u32>, g: Wrapping<u32>) -> Wrapping<u32> {
(e & f) ^ (!e & g)
}
#[inline(always)]
fn majority(a: Wrapping<u32>, b: Wrapping<u32>, c: Wrapping<u32>) -> Wrapping<u32> {
(a & b) ^ (a & c) ^ (b & c)
}
#[inline(always)]
fn bsig0(x: Wrapping<u32>) -> Wrapping<u32> {
x.rotate_right(2) ^ x.rotate_right(13) ^ x.rotate_right(22)
}
#[inline(always)]
fn bsig1(x: Wrapping<u32>) -> Wrapping<u32> {
x.rotate_right(6) ^ x.rotate_right(11) ^ x.rotate_right(25)
}
#[inline(always)]
fn ssig0(x: Wrapping<u32>) -> Wrapping<u32> {
x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3)
}
#[inline(always)]
fn ssig1(x: Wrapping<u32>) -> Wrapping<u32> {
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
}

View File

@@ -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<T> {
fn to_option(self) -> Option<T>;
}
#[cfg(test)]
impl<T> ToOption<T> for CtOption<T> {
fn to_option(self) -> Option<T> {
if bool::from(self.is_some()) {
Some(self.unwrap())
} else {
None
}
}
}

View File

@@ -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<P: AsRef<Path>>(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::<usize>().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());
}
}
}

View File

@@ -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<std::io::Error> 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<u8> for TagClass {
type Error = Asn1Error;
fn try_from(x: u8) -> Result<TagClass, Asn1Error> {
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<R: Read>(input: &mut R) -> Result<Tag, Asn1Error> {
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::<u64>()) - 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<R: Read>(input: &mut R) -> Result<usize, Asn1Error> {
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::<usize>() - 1)) != 0 {
return Err(Asn1Error::ArithmeticOverflow);
}
length = (length << 8) | x as usize;
}
if length < 0x80 {
return Err(Asn1Error::InvalidLongFormEncoding);
}
Ok(length)
}
}
fn parse_coordinate<R: Read>(mut input: R, bytes: &mut [u8; 32]) -> Result<usize, Asn1Error> {
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<R: Read>(mut input: R) -> Result<ecdsa::Signature, Asn1Error> {
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)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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<P: AsRef<Path>>(path: P) -> Result<Wycheproof, Box<dyn Error>> {
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<String>,
notes: HashMap<String, String>,
schema: String,
testGroups: Vec<TestGroup>,
}
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<TestCase>,
}
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<String, String>) -> 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<ecdsa::PubKey> {
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<String>,
}
impl TestCase {
fn type_check(&self) {
// Nothing to do.
}
fn print(&self, notes: &HashMap<String, String>, 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<ecdsa::PubKey>, notes: &HashMap<String, String>) -> 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::<crypto::sha256::Sha256>(&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
}
}
}
}
}
}

19
nrf52840dk_layout.ld Normal file
View File

@@ -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

View File

@@ -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 {

View File

@@ -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<isize>) -> 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<Duration<isize>> {
+ 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 {

View File

@@ -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]

View File

@@ -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;

View File

@@ -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<I: nrf52::interrupt_service::InterruptService>(
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<I: nrf52::interrupt_service::InterruptService>(
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<DeferredCallTask> =
unsafe { DeferredCall::new(DeferredCallTask::Nvmc) };
+type WORD = u32;
+const WORD_SIZE: usize = core::mem::size_of::<WORD>();
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<App>,
+}
+
+pub const DRIVER_NUM: usize = 0x50003;
+
+#[derive(Default)]
+pub struct App {
+ /// The allow slice for COMMAND(2).
+ slice: Option<AppSlice<Shared, u8>>,
+}
+
+impl SyscallDriver {
+ pub fn new(nvmc: &'static Nvmc, apps: Grant<App>) -> 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<u32>).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<Callback>, _: 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<AppSlice<Shared, u8>>,
+ ) -> 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.

1009
patches/tock/02-usb.patch Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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];

522
patches/tock/04-rtt.patch Normal file
View File

@@ -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<VirtualMuxAlarm<'static, nrf52832::rtc::Rtc>>,
- 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<I: nrf52::interrupt_service::InterruptService>(
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<SpiMX25R6435FPins>,
button: &'static capsules::button::Button<'static>,
@@ -232,6 +241,38 @@ pub unsafe fn setup_board<I: nrf52::interrupt_service::InterruptService>(
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<VirtualMuxAlarm<'static, nrf52::rtc::Rtc>>,
+ 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<I: nrf52::interrupt_service::InterruptService>(
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<u32>,
+ number_down_buffers: VolatileCell<u32>,
+ 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<u32>,
+ pub write_position: VolatileCell<u32>,
+ read_position: VolatileCell<u32>,
+ flags: VolatileCell<u32>,
+ _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<usize>,
@@ -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);

51
reset.sh Executable file
View File

@@ -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

1
rules.d/55-opensk.rules Normal file
View File

@@ -0,0 +1 @@
SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1915", ATTRS{idProduct}=="521f", ATTRS{product}=="OpenSK", MODE="0660", GROUP="logindev", TAG+="uaccess"

72
run_desktop_tests.sh Executable file
View File

@@ -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

1
rust-toolchain Normal file
View File

@@ -0,0 +1 @@
nightly-2020-01-16

3
rustfmt.toml Normal file
View File

@@ -0,0 +1,3 @@
use_field_init_shorthand = true
use_try_shorthand = true
edition = "2018"

49
setup.sh Executable file
View File

@@ -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

483
src/ctap/command.rs Normal file
View File

@@ -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<cbor::reader::DecoderError> 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<Command, Ctap2StatusCode> {
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<u8>,
pub rp: PublicKeyCredentialRpEntity,
pub user: PublicKeyCredentialUserEntity,
pub pub_key_cred_params: Vec<(PublicKeyCredentialType, i64)>,
pub exclude_list: Option<Vec<PublicKeyCredentialDescriptor>>,
pub extensions: Option<Extensions>,
// Even though options are optional, we can use the default if not present.
pub options: MakeCredentialOptions,
pub pin_uv_auth_param: Option<Vec<u8>>,
pub pin_uv_auth_protocol: Option<u64>,
}
impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
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<u8>,
pub allow_list: Option<Vec<PublicKeyCredentialDescriptor>>,
pub extensions: Option<Extensions>,
// Even though options are optional, we can use the default if not present.
pub options: GetAssertionOptions,
pub pin_uv_auth_param: Option<Vec<u8>>,
pub pin_uv_auth_protocol: Option<u64>,
}
impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
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<CoseKey>,
pub pin_auth: Option<Vec<u8>>,
pub new_pin_enc: Option<Vec<u8>>,
pub pin_hash_enc: Option<Vec<u8>>,
}
impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
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));
}
}

675
src/ctap/ctap1.rs Normal file
View File

@@ -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<u16> for Ctap1StatusCode {
type Error = ();
fn try_from(value: u16) -> Result<Ctap1StatusCode, ()> {
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<u16> 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<u8> for Ctap1Flags {
type Error = Ctap1StatusCode;
fn try_from(value: u8) -> Result<Ctap1Flags, Ctap1StatusCode> {
match value {
0x07 => Ok(Ctap1Flags::CheckOnly),
0x03 => Ok(Ctap1Flags::EnforceUpAndSign),
0x08 => Ok(Ctap1Flags::DontEnforceUpAndSign),
_ => Err(Ctap1StatusCode::SW_WRONG_DATA),
}
}
}
impl Into<u8> 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<u8>,
flags: Ctap1Flags,
},
Version,
#[allow(dead_code)]
VendorSpecific {
payload: Vec<u8>,
},
}
impl TryFrom<&[u8]> for U2fCommand {
type Error = Ctap1StatusCode;
fn try_from(message: &[u8]) -> Result<Self, Ctap1StatusCode> {
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<R, CheckUserPresence>(
message: &[u8],
ctap_state: &mut CtapState<R, CheckUserPresence>,
clock_value: ClockValue,
) -> Result<Vec<u8>, 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::<u8>::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<R, CheckUserPresence>(
challenge: [u8; 32],
application: [u8; 32],
ctap_state: &mut CtapState<R, CheckUserPresence>,
) -> Result<Vec<u8>, 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::<crypto::sha256::Sha256>(&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<R, CheckUserPresence>(
challenge: [u8; 32],
application: [u8; 32],
key_handle: Vec<u8>,
flags: Ctap1Flags,
ctap_state: &mut CtapState<R, CheckUserPresence>,
) -> Result<Vec<u8>, 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::<crypto::sha256::Sha256>(&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<u8> {
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<u8>,
) -> Vec<u8> {
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));
}
}

1020
src/ctap/data_formats.rs Normal file

File diff suppressed because it is too large Load Diff

596
src/ctap/hid/mod.rs Normal file
View File

@@ -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<u8>,
}
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<isize> = Duration::from_ms(100);
const WINK_TIMEOUT_DURATION: Duration<isize> = 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<R, CheckUserPresence>(
&mut self,
packet: &HidPacket,
clock_value: ClockValue,
ctap_state: &mut CtapState<R, CheckUserPresence>,
) -> HidPacketIterator
where
R: Rng256,
CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>,
{
// TODO: Send COMMAND_KEEPALIVE every 100ms?
match self
.assembler
.parse_packet(packet, Timestamp::<isize>::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<HidPacketIterator> {
#[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<isize> = Timestamp::from_ms(0);
fn process_messages<CheckUserPresence>(
ctap_hid: &mut CtapHid,
ctap_state: &mut CtapState<ThreadRng256, CheckUserPresence>,
request: Vec<Message>,
) -> Option<Vec<Message>>
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<CheckUserPresence>(
ctap_hid: &mut CtapHid,
ctap_state: &mut CtapState<ThreadRng256, CheckUserPresence>,
) -> 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]
}])
);
}
}

590
src/ctap/hid/receive.rs Normal file
View File

@@ -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<isize>,
// 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<u8>,
}
#[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<u8>) 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<isize>,
) -> Result<Option<Message>, (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<isize>,
) -> Option<Message> {
// 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<Message> {
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<isize> = 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
}

296
src/ctap/hid/send.rs Normal file
View File

@@ -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<MessageSplitter>);
impl HidPacketIterator {
pub fn new(message: Message) -> Option<HidPacketIterator> {
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<HidPacket> {
match &mut self.0 {
Some(splitter) => splitter.next(),
None => None,
}
}
}
pub struct MessageSplitter {
message: Message,
packet: HidPacket,
seq: Option<u8>,
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<MessageSplitter> {
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<HidPacket> {
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<HidPacket>) {
let packets: Vec<HidPacket> = 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<HidPacket> = 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<HidPacket> = 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<HidPacket> = 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<HidPacket> = 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)
}

1297
src/ctap/mod.rs Normal file

File diff suppressed because it is too large Load Diff

267
src/ctap/response.rs Normal file
View File

@@ -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<AuthenticatorClientPinResponse>),
AuthenticatorReset,
}
impl From<ResponseData> for Option<cbor::Value> {
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<u8>,
pub att_stmt: PackedAttestationStatement,
}
impl From<AuthenticatorMakeCredentialResponse> 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<PublicKeyCredentialDescriptor>,
pub auth_data: Vec<u8>,
pub signature: Vec<u8>,
pub user: Option<PublicKeyCredentialUserEntity>,
pub number_of_credentials: Option<u64>,
}
impl From<AuthenticatorGetAssertionResponse> 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<String>,
pub extensions: Option<Vec<String>>,
pub aaguid: [u8; 16],
pub options: Option<BTreeMap<String, bool>>,
pub max_msg_size: Option<u64>,
pub pin_protocols: Option<Vec<u64>>,
}
impl From<AuthenticatorGetInfoResponse> 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<cbor::Value> = 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<CoseKey>,
pub pin_token: Option<Vec<u8>>,
pub retries: Option<u64>,
}
impl From<AuthenticatorClientPinResponse> 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<cbor::Value> =
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<cbor::Value> =
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<cbor::Value> =
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<cbor::Value> =
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<cbor::Value> = ResponseData::AuthenticatorClientPin(None).into();
assert_eq!(response_cbor, None);
}
#[test]
fn test_reset_into_cbor() {
let response_cbor: Option<cbor::Value> = ResponseData::AuthenticatorReset.into();
assert_eq!(response_cbor, None);
}
}

71
src/ctap/status_code.rs Normal file
View File

@@ -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,
}

682
src/ctap/storage.rs Normal file
View File

@@ -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<String>,
credential_id: Option<Vec<u8>>,
user_handle: Option<Vec<u8>>,
},
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<Storage, Config>,
}
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<PublicKeyCredentialSource> {
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<PublicKeyCredentialSource> {
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::<u32>()];
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<StoreError> 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<PublicKeyCredentialSource> {
let cbor = cbor::read(data).ok()?;
cbor.try_into().ok()
}
fn serialize_credential(credential: PublicKeyCredentialSource) -> Result<Vec<u8>, 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<u8>,
) -> 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);
}
}

View File

@@ -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<isize>) -> 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<isize>,
// This is the timeout duration of button touches.
presence_duration: Duration<isize>,
}
#[cfg(feature = "with_ctap1")]
impl U2fUserPresenceState {
pub fn new(
request_duration: Duration<isize>,
presence_duration: Duration<isize>,
) -> 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<isize> = Duration::from_ms(1000);
const PRESENCE_DURATION: Duration<isize> = 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));
}
}

View File

@@ -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<Box<[u8]>, 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<Box<[u8]>, 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[..]);
}
}

25
src/embedded_flash/mod.rs Normal file
View File

@@ -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;

View File

@@ -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<T> = Result<T, StorageError>;
/// 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<core::ops::Range<usize>> {
if self.is_valid(length, storage) {
let start = self.page * storage.page_size() + self.byte;
Ok(start..start + length)
} else {
Err(StorageError::OutOfBounds)
}
}
}

View File

@@ -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::<usize>());
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::<usize>() || 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);
}

View File

@@ -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<S: Storage, C: StoreConfig>(storage: &S, config: &C) -> Option<Format> {
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<Index>, user_entry: StoreEntry) -> Vec<u8> {
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<u8> {
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);
}

File diff suppressed because it is too large Load Diff

View File

@@ -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<usize> {
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<SyscallStorage> {
/// // 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<SyscallStorage> {
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(())
}
}

21
src/lib.rs Normal file
View File

@@ -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;

388
src/main.rs Normal file
View File

@@ -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<isize> = Duration::from_ms(KEEPALIVE_DELAY_MS);
const SEND_TIMEOUT: Duration<isize> = 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::<isize>::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::<f64>::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<isize>,
) -> 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)
}
}

344
src/usb_ctap_hid.rs Normal file
View File

@@ -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<isize>,
) -> Option<SendOrRecvStatus> {
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<isize>,
) -> Option<SendOrRecvStatus> {
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()
}

1
third_party/libtock-rs vendored Submodule

Submodule third_party/libtock-rs added at ab2c945184

1
third_party/tock vendored Submodule

Submodule third_party/tock added at 862452b77a

169
tools/gen_key_materials.sh Normal file
View File

@@ -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
}