Initial commit
This commit is contained in:
7
.cargo/config
Normal file
7
.cargo/config
Normal 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
16
.github/ISSUE_TEMPLATE.md
vendored
Normal 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
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
7
.gitignore
vendored
Normal 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
6
.gitmodules
vendored
Normal 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
35
.markdownlint.json
Normal 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
45
.travis.yml
Normal 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
6
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"davidanson.vscode-markdownlint",
|
||||
"rust-lang.rust"
|
||||
]
|
||||
}
|
||||
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal 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
35
Cargo.toml
Normal 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
202
LICENSE
Normal 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
116
README.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# <img alt="OpenSK logo" src="docs/img/OpenSK.svg" width="200px">
|
||||
|
||||
[](https://travis-ci.org/google/OpenSK)
|
||||
|
||||
## OpenSK
|
||||
|
||||
This repository contains a Rust implementation of a
|
||||
[FIDO2](https://fidoalliance.org/fido2/) authenticator.
|
||||
|
||||
We developed this as a [Tock OS](https://tockos.org) application and it has been
|
||||
successfully tested on the following boards:
|
||||
|
||||
* [Nordic nRF52840-DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
|
||||
* [Nordic nRF52840-dongle](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-Dongle)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This project is proof-of-concept and a research platform. It's still under
|
||||
development and as such comes with a few limitations:
|
||||
|
||||
### FIDO2
|
||||
|
||||
Although we tested and implemented our firmware based on the published
|
||||
[CTAP2.0 specifications](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html),
|
||||
our implementation was not reviewed nor officially tested and doesn't claim to
|
||||
be FIDO Certified.
|
||||
|
||||
### Cryptography
|
||||
|
||||
We're currently still in the process on making the
|
||||
[ARM® CryptoCell-310](https://developer.arm.com/ip-products/security-ip/cryptocell-300-family)
|
||||
embedded in the
|
||||
[Nordic nRF52840 chip](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fcryptocell.html)
|
||||
work to get hardware-accelerated cryptography. In the meantime we implemented
|
||||
the required cryptography algorithms (ECDSA, ECC secp256r1, HMAC-SHA256 and
|
||||
AES256) in Rust as a placeholder. Those implementations are research-quality
|
||||
code and haven't been reviewed. They don't provide constant-time guarantees and
|
||||
are not designed to be resistant against side-channel attacks.
|
||||
|
||||
## Installation
|
||||
|
||||
For a more detailed guide, please refer to our
|
||||
[installation guide](docs/install.md).
|
||||
|
||||
1. If you just cloned this repository, run the following script (**Note**: you
|
||||
only need to do this once):
|
||||
|
||||
```shell
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
2. If you have a Nordic development board that doesn't already have Tock OS
|
||||
installed, you can install both TockOS and the OpenSK application by running
|
||||
the following command, depending on the board you have:
|
||||
|
||||
```shell
|
||||
# Nordic nRF52840-DK board
|
||||
board=nrf52840dk ./deploy.sh os app
|
||||
# Nordic nRF52840-Dongle
|
||||
board=nrf52840_dongle ./deploy.sh os app
|
||||
```
|
||||
|
||||
3. If Tock OS is already installed and you want to install/update the OpenSK
|
||||
application on your board (**Warning**: it will erase the locally stored
|
||||
credentials), run:
|
||||
|
||||
```shell
|
||||
./deploy.sh app
|
||||
```
|
||||
|
||||
4. On Linux, you may want to avoid the need for `root` privileges to interact
|
||||
with the key. For that purpose we provide a udev rule file that can be
|
||||
installed with the following command:
|
||||
|
||||
```shell
|
||||
sudo cp rules.d/55-opensk.rules /etc/udev/rules.d/ &&
|
||||
sudo udevadm control --reload
|
||||
```
|
||||
|
||||
### Customization
|
||||
|
||||
If you build your own security key, depending on the hardware you use, there are
|
||||
a few things you can personalize:
|
||||
|
||||
1. If you have multiple buttons, choose the buttons responsible for user
|
||||
presence in main.rs.
|
||||
2. Decide whether you want to use batch attestation. There is a boolean flag in
|
||||
`ctap/mod.rs`. It is mandatory for U2F, and you can create your own
|
||||
self-signed certificate. The flag is used for FIDO2 and has some privacy
|
||||
implications. Please check
|
||||
[WebAuthn](https://www.w3.org/TR/webauthn/#attestation) for more
|
||||
information.
|
||||
3. Decide whether you want to use signature counters. Currently, only global
|
||||
signature counters are implemented, as they are the default option for U2F.
|
||||
The flag in `ctap/mod.rs` only turns them off for FIDO2. The most privacy
|
||||
preserving solution is individual or no signature counters. Again, please
|
||||
check [WebAuthn](https://www.w3.org/TR/webauthn/#signature-counter) for
|
||||
documentation.
|
||||
4. Depending on your available flash storage, choose an appropriate maximum
|
||||
number of supported residential keys and number of pages in
|
||||
`ctap/storage.rs`.
|
||||
|
||||
### 3D printed enclosure
|
||||
|
||||
To protect and carry your key, we partnered with a professional designer and we
|
||||
are providing a custom enclosure that can be printed on both professional 3D
|
||||
printers and hobbyist models.
|
||||
|
||||
All the required files can be downloaded from
|
||||
[Thingiverse](https://www.thingiverse.com/thing:4132768) including the STEP
|
||||
file, allowing you to easily make the modifications you need to further
|
||||
customize it.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing.md](docs/contributing.md).
|
||||
321
deploy.sh
Executable file
321
deploy.sh
Executable 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
28
docs/contributing.md
Normal 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
1
docs/img/OpenSK.svg
Normal 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 |
BIN
docs/img/devkit_annotated.jpg
Normal file
BIN
docs/img/devkit_annotated.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 532 KiB |
BIN
docs/img/dongle_clip.jpg
Normal file
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
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
BIN
docs/img/dongle_pads.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
262
docs/install.md
Normal file
262
docs/install.md
Normal 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
|
||||
|
||||

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

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

|
||||
|
||||
1. You can use the retainer clip if you have one to avoid maintaining pressure
|
||||
between the board and the cable:
|
||||
|
||||

|
||||
|
||||
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
180
examples/crypto_bench.rs
Normal 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
170
layout.ld
Normal 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
15
libraries/cbor/Cargo.toml
Normal 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
29
libraries/cbor/src/lib.rs
Normal 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;
|
||||
500
libraries/cbor/src/macros.rs
Normal file
500
libraries/cbor/src/macros.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
811
libraries/cbor/src/reader.rs
Normal file
811
libraries/cbor/src/reader.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
268
libraries/cbor/src/values.rs
Normal file
268
libraries/cbor/src/values.rs
Normal 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"));
|
||||
}
|
||||
}
|
||||
429
libraries/cbor/src/writer.rs
Normal file
429
libraries/cbor/src/writer.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
29
libraries/crypto/Cargo.toml
Normal file
29
libraries/crypto/Cargo.toml
Normal 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 = []
|
||||
541
libraries/crypto/src/aes256.rs
Normal file
541
libraries/crypto/src/aes256.rs
Normal 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
258
libraries/crypto/src/cbc.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
346
libraries/crypto/src/ec/exponent256.rs
Normal file
346
libraries/crypto/src/ec/exponent256.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
260
libraries/crypto/src/ec/gfp256.rs
Normal file
260
libraries/crypto/src/ec/gfp256.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1181
libraries/crypto/src/ec/int256.rs
Normal file
1181
libraries/crypto/src/ec/int256.rs
Normal file
File diff suppressed because it is too large
Load Diff
20
libraries/crypto/src/ec/mod.rs
Normal file
20
libraries/crypto/src/ec/mod.rs
Normal 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;
|
||||
1109
libraries/crypto/src/ec/montgomery.rs
Normal file
1109
libraries/crypto/src/ec/montgomery.rs
Normal file
File diff suppressed because it is too large
Load Diff
1034
libraries/crypto/src/ec/point.rs
Normal file
1034
libraries/crypto/src/ec/point.rs
Normal file
File diff suppressed because it is too large
Load Diff
324
libraries/crypto/src/ec/precomputed.rs
Normal file
324
libraries/crypto/src/ec/precomputed.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
154
libraries/crypto/src/ecdh.rs
Normal file
154
libraries/crypto/src/ecdh.rs
Normal 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.
|
||||
}
|
||||
643
libraries/crypto/src/ecdsa.rs
Normal file
643
libraries/crypto/src/ecdsa.rs
Normal 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.
|
||||
}
|
||||
281
libraries/crypto/src/hmac.rs
Normal file
281
libraries/crypto/src/hmac.rs
Normal 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
|
||||
}
|
||||
67
libraries/crypto/src/lib.rs
Normal file
67
libraries/crypto/src/lib.rs
Normal 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]);
|
||||
}
|
||||
91
libraries/crypto/src/rng256.rs
Normal file
91
libraries/crypto/src/rng256.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
423
libraries/crypto/src/sha256.rs
Normal file
423
libraries/crypto/src/sha256.rs
Normal 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
|
||||
}
|
||||
41
libraries/crypto/src/util.rs
Normal file
41
libraries/crypto/src/util.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
103
libraries/crypto/tests/aesavs.rs
Normal file
103
libraries/crypto/tests/aesavs.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
232
libraries/crypto/tests/asn1.rs
Normal file
232
libraries/crypto/tests/asn1.rs
Normal 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)
|
||||
}
|
||||
2571
libraries/crypto/tests/data/ECBVarKey256.rsp
Normal file
2571
libraries/crypto/tests/data/ECBVarKey256.rsp
Normal file
File diff suppressed because it is too large
Load Diff
1291
libraries/crypto/tests/data/ECBVarTxt256.rsp
Normal file
1291
libraries/crypto/tests/data/ECBVarTxt256.rsp
Normal file
File diff suppressed because it is too large
Load Diff
4578
libraries/crypto/tests/data/ecdsa_secp256r1_sha256_test.json
Normal file
4578
libraries/crypto/tests/data/ecdsa_secp256r1_sha256_test.json
Normal file
File diff suppressed because it is too large
Load Diff
217
libraries/crypto/tests/wycheproof.rs
Normal file
217
libraries/crypto/tests/wycheproof.rs
Normal 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
19
nrf52840dk_layout.ld
Normal 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
|
||||
81
patches/libtock-rs/01-panic_console.patch
Normal file
81
patches/libtock-rs/01-panic_console.patch
Normal 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 {
|
||||
|
||||
72
patches/libtock-rs/02-timer.patch
Normal file
72
patches/libtock-rs/02-timer.patch
Normal 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 {
|
||||
13
patches/libtock-rs/03-public_syscalls.patch
Normal file
13
patches/libtock-rs/03-public_syscalls.patch
Normal 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]
|
||||
13
patches/libtock-rs/04-bigger_heap.patch
Normal file
13
patches/libtock-rs/04-bigger_heap.patch
Normal 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;
|
||||
323
patches/tock/01-persistent-storage.patch
Normal file
323
patches/tock/01-persistent-storage.patch
Normal 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
1009
patches/tock/02-usb.patch
Normal file
File diff suppressed because it is too large
Load Diff
13
patches/tock/03-app-memory.patch
Normal file
13
patches/tock/03-app-memory.patch
Normal 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
522
patches/tock/04-rtt.patch
Normal 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
51
reset.sh
Executable 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
1
rules.d/55-opensk.rules
Normal 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
72
run_desktop_tests.sh
Executable 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
1
rust-toolchain
Normal file
@@ -0,0 +1 @@
|
||||
nightly-2020-01-16
|
||||
3
rustfmt.toml
Normal file
3
rustfmt.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
edition = "2018"
|
||||
49
setup.sh
Executable file
49
setup.sh
Executable 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
483
src/ctap/command.rs
Normal 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
675
src/ctap/ctap1.rs
Normal 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
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
596
src/ctap/hid/mod.rs
Normal 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
590
src/ctap/hid/receive.rs
Normal 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
296
src/ctap/hid/send.rs
Normal 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
1297
src/ctap/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
267
src/ctap/response.rs
Normal file
267
src/ctap/response.rs
Normal 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
71
src/ctap/status_code.rs
Normal 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
682
src/ctap/storage.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
188
src/ctap/timed_permission.rs
Normal file
188
src/ctap/timed_permission.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
455
src/embedded_flash/buffer.rs
Normal file
455
src/embedded_flash/buffer.rs
Normal 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
25
src/embedded_flash/mod.rs
Normal 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;
|
||||
107
src/embedded_flash/storage.rs
Normal file
107
src/embedded_flash/storage.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
172
src/embedded_flash/store/bitfield.rs
Normal file
172
src/embedded_flash/store/bitfield.rs
Normal 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);
|
||||
}
|
||||
514
src/embedded_flash/store/format.rs
Normal file
514
src/embedded_flash/store/format.rs
Normal 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);
|
||||
}
|
||||
1028
src/embedded_flash/store/mod.rs
Normal file
1028
src/embedded_flash/store/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
195
src/embedded_flash/syscall.rs
Normal file
195
src/embedded_flash/syscall.rs
Normal 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
21
src/lib.rs
Normal 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
388
src/main.rs
Normal 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
344
src/usb_ctap_hid.rs
Normal 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
1
third_party/libtock-rs
vendored
Submodule
Submodule third_party/libtock-rs added at ab2c945184
1
third_party/tock
vendored
Submodule
1
third_party/tock
vendored
Submodule
Submodule third_party/tock added at 862452b77a
169
tools/gen_key_materials.sh
Normal file
169
tools/gen_key_materials.sh
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user