4 Commits

Author SHA1 Message Date
hcyang
81330e5d52 Persist/parse slot_id in/from credential (#569)
* Persist/parse slot_id in/from credential

Persist slot_id into credential_id or the resident credential record
during MakeCredential, and parse it during GetAssertion. Add related
unittests.

* Fix styles

* Move enable_pin_uv back to ctap/mod.rs
2022-11-02 18:08:25 +08:00
hcyang
31774ef316 Add storage test cases for multi-PIN (#567)
* Add storage test cases for multi-PIN

* Fixed proc-macro2 version (#550)

* fixes proc-macro2 in dependencies

* adds missing locked versions, and a verbose print for cargo check

* commits Cargo.lock files

* removes unnecessary Cargo.lock entries

* adds missing Cargo.lock

Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
2022-10-26 11:29:56 +08:00
hcyang
1c6c7a4eae Parse slot_id in use from token state (#546)
* Parse slot_id in use from token state

Support switching to multi-PIN feature and making the default slots 8.
But the command to switch to multi-PIN isn't exposed yet because the
implementation isn't ready.

Main change of this commit is to cache the slot_id in use inside token
state, and retrieve it from token state when needed.

* Fix once_cell dependency (#548)

* fixed version of once_cell

* fixes comments

* removes unnecessary fuzz dependency

* Fix styles

Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
2022-09-22 14:30:52 +08:00
hcyang
078e565ac1 Add basic setup for multi-PIN (#530)
* Add basic setup for multi-PIN

- Reserve the storage keys for maximum of 8 user slots.
- Modify the storage functions to take a slot_id parameter.
- Add the slot_count() customization.
- Assume slot_id as a parameter when needed except these places:
  - Entrance functions of command processing that directly takes the
    command parameter structure. slot_id is set as 0, and will be
    parsed from the parameters when we enable the feature.
  - MakeCredential/GetAssertion/AuthenticatorConfig will take the
    slot_id from active token state when we enable the feature,
    resulting in an `Option<usize>`. Below code will act on the option
    value correctly. When the feature isn't enabled, we're always
    referring to the only PIN slot so set slot_id as Some(0).
  - GetInfo returns verdict of whether PIN is supported and enabled, and
    whether PIN needs to be forced changed. There will be new fields to
    represent those values when the feature is enabled, and the old
    fields will not be populated. So when the feature isn't enabled, we
    can treat slot_id as 0.

Not covered in this commit:
- Unittests for other slots. The existing tests all pass and I plan to
  add unittests for multi-slot case after the codebase allows enabling
  the feature.
- Persisting and checking the slot_id in credentials. This is planned to
  come in the next commit.

* Fix storage and some other style

* Add support for concatenated values

* Switch some storage entries back to multi-entry

* Set bumpalo version for fuzzing (#532)

* maximum working bumpalo version

* explicit comment to explain version locking

* removes incorrect comment

* moves serde version lock to dev dependencies

* removes serde dependencies

* reverts serde removal in crypto library

* Make PIN_PROPERTIES use concatenated storage entry

* Fix bumpalo issue

* Use concatenated storage entry for force_pin_change too

* Fix cargofmt

Co-authored-by: Julien Cretin <cretin@google.com>
Co-authored-by: kaczmarczyck <43844792+kaczmarczyck@users.noreply.github.com>
2022-08-23 23:01:13 +08:00
78 changed files with 2745 additions and 9804 deletions

View File

@@ -94,3 +94,9 @@ jobs:
with:
command: check
args: --target thumbv7em-none-eabi --release --examples
- name: Check bootloader
uses: actions-rs/cargo@v1
with:
command: check
args: --manifest-path bootloader/Cargo.toml --target thumbv7em-none-eabi --release

View File

@@ -24,13 +24,12 @@ embedded-time = "0.12.1"
arbitrary = { version = "0.4.7", features = ["derive"], optional = true }
rand = { version = "0.8.4", optional = true }
ed25519-compact = { version = "1", default-features = false, optional = true }
dilithium = { path = "third_party/dilithium" }
[features]
debug_allocations = ["lang_items/debug_allocations"]
debug_ctap = ["libtock_drivers/debug_ctap"]
panic_console = ["lang_items/panic_console"]
std = ["crypto/std", "dilithium/std", "lang_items/std", "persistent_store/std", "rng256/std", "rand"]
std = ["crypto/std", "lang_items/std", "persistent_store/std", "rng256/std", "rand"]
verbose = ["debug_ctap", "libtock_drivers/verbose_usb"]
with_ctap1 = ["crypto/with_ctap1"]
with_nfc = ["libtock_drivers/with_nfc"]

162
README.md
View File

@@ -1,114 +1,84 @@
# <img alt="OpenSK logo" src="docs/img/OpenSK.svg" width="200px">
![markdownlint](https://github.com/google/OpenSK/workflows/markdownlint/badge.svg?branch=develop)
![pylint](https://github.com/google/OpenSK/workflows/pylint/badge.svg?branch=develop)
![Cargo check](https://github.com/google/OpenSK/workflows/Cargo%20check/badge.svg?branch=develop)
![Cargo format](https://github.com/google/OpenSK/workflows/Cargo%20format/badge.svg?branch=develop)
[![Coverage Status](https://coveralls.io/repos/github/google/OpenSK/badge.svg?branch=develop)](https://coveralls.io/github/google/OpenSK?branch=develop)
## OpenSK
This is an OpenSK fork that allows signing with a PQC Hybrid scheme. If you are looking for the original documentation, please check the
[develop branch of its GitHub page](https://github.com/google/OpenSK/tree/develop).
This repository contains a Rust implementation of a
[FIDO2](https://fidoalliance.org/fido2/) authenticator.
We developed OpenSK as a [Tock OS](https://tockos.org) application.
We intend to bring a full open source experience to security keys, from
application to operating system. You can even 3D print your own open source
enclosure!
You can see OpenSK in action in this
[video on YouTube](https://www.youtube.com/watch?v=klEozvpw0xg)!
You are viewing the branch for developers. New features are developed here
before they are stabilized. If you instead want to use the FIDO certified
firmware, please go back to the
[stable branch](https://github.com/google/OpenSK).
### FIDO2
The develop branch implements the
[CTAP2.1 specification](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html).
This branch is not FIDO certified. The implementation is backwards compatible
to CTAP2.0. Additionally, OpenSK supports U2F, and non-discoverable credentials
created with either protocol are compatible with the other.
### :warning: Disclaimer
This project is **proof-of-concept and a research platform**. It is **NOT**
meant for a daily usage. It comes with a few limitations:
* This branch is under development, and therefore less rigorously tested than the stable branch.
* The cryptography implementations are not resistent against side-channel attacks.
We're still in the process of integrating the
[ARM&reg; CryptoCell-310](https://developer.arm.com/ip-products/security-ip/cryptocell-300-family)
embedded in the
[Nordic nRF52840 chip](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fcryptocell.html)
to enable hardware-accelerated cryptography. Our placeholder implementations of required
cryptography algorithms (ECDSA, ECC secp256r1, HMAC-SHA256 and AES256) in Rust are research-quality
code. They haven't been reviewed and don't provide constant-time guarantees.
## Hardware
You will need a
[Nordic nRF52840-DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
development kit.
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.
* [Makerdiary nRF52840-MDK USB dongle](https://wiki.makerdiary.com/nrf52840-mdk/).
* [Feitian OpenSK dongle](https://feitiantech.github.io/OpenSK_USB/).
## Installation
To install OpenSK,
1. follow the [general setup steps](docs/install.md),
1. then continue with the instructions for your specific hardware:
[Nordic nRF52840-DK](docs/boards/nrf52840dk.md)
* [Nordic nRF52840-DK](docs/boards/nrf52840dk.md)
* [Nordic nRF52840 Dongle](docs/boards/nrf52840_dongle.md)
* [Makerdiary nRF52840-MDK USB dongle](docs/boards/nrf52840_mdk.md)
* [Feitian OpenSK dongle](docs/boards/nrf52840_feitian.md)
## PQC Experiments
To test whether the installation was successful, visit a
[demo website](https://webauthn.io/) and try to register and login.
Please check our [Troubleshooting and Debugging](docs/debugging.md) section if you
have problems with the installation process or during development. To find out what
else you can do with your OpenSK, see [Customization](docs/customization.md).
### Modes
## Contributing
The Dilithium mode is set at compile time. If you want to perform experiments for different modes,
you will need to recompile. The mode is a feature, defined in
`third_party/dilithium/Cargo.toml`. By default, it is set to
`default = [ "dilithium5", "optimize_stack" ]`. You can change the default mode by either changing
the number 5 to 2 or 3. Or you remove the feature for stack optimizations, e.g.
`default = [ "dilithium2" ]`.
See [Contributing.md](docs/contributing.md).
Note that some benchmarks will not run in all modes without stack optimizations. You can try to
play with the stack size in these cases. As an example, stack painting for speed mode Dilithium2
works if you apply the following changes:
* `APP_HEAP_SIZE = 16384` in `deploy.py`
* `libtock_core::stack_size! {0x1A000}` in `examples/measure_stack.rs`
* `STACK_SIZE = 106496;` in `nrf52840_layout.ld`
* Change the app break from `70 * 1024` to `104 * 1024` in `patches/tock/07-app-break-fix.patch`.
For your convenience, you can also simply try:
```
git apply increase_stack.patch
```
### Compiler flags
To trade binary size for speed, you can play with `[profile.release]` in `Cargo.toml`.
For example, try a different compiler optimization level:
```
opt-level = 3
```
### Debug output
Only the CTAP commands tests are measured end to end on the host. All other experiments are
measured on the embedded device itself and output over RTT. You can either use a client to print
results by running the following commands in different terminals:
```
JLinkExe -device nrf52 -if swd -speed 1000 -autoconnect 1
JLinkRTTClient
```
Or you directly output all messages to a file:
```
JLinkRTTLogger -device NRF52840_XXAA -if swd -speed 1000 -RTTchannel 0
```
### Perform Experiments
The paper contains the following experiments:
#### Crypto benchmarks
Deploy the `crypto_bench` example and read the debug output with one of the methods above:
```
./deploy.py --board=nrf52840dk_opensk --crypto_bench
```
#### CTAP benchmarks
To measure the speed of FIDO commands, run:
```
python benchmarks.py --runs=2000
```
Aggregate results will be printed, and the raw data is written to `make_durations.txt` and
`get_durations.txt`.
#### Stack painting
Deploy the `measure_stack` example and read the debug output with one of the methods above:
```
./deploy.py --board=nrf52840dk_opensk --measure_stack
```
#### x86 benchmarks
You don't need your embedded hardware for those, run:
```
cd third_party/dilithium/
cargo bench --features std
```
## Reporting a Vulnerability
See [SECURITY.md](SECURITY.md).

View File

@@ -1,167 +0,0 @@
#!/usr/bin/env python3
# Copyright 2022 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.
# Lint as: python3
"""Script to benchmark CTAP commands using Dilithium Hybrid signatures."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import datetime
from subprocess import DEVNULL, STDOUT, check_call
import sys
from time import sleep
from typing import Any
import uuid
import colorama
from tqdm.auto import tqdm
from fido2 import ctap
from fido2.webauthn import PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, PublicKeyCredentialParameters
from fido2 import hid
from tools.configure import fatal, info, get_opensk_devices
ES256_ALGORITHM = PublicKeyCredentialParameters("public-key", -7)
HYBRID_ALGORITHM = PublicKeyCredentialParameters("public-key", -65537)
def error(message: str):
tqdm.write(message)
def check_info(authenticator: Any):
"""Checks if the assumed upgrade info matches the authenticator's."""
try:
info("Reading info...")
if HYBRID_ALGORITHM not in authenticator.info.algorithms:
fatal("The device does not support hybrid signatures.")
except ctap.CtapError as ex:
error(f"Failed to read OpenSK info (error: {ex}")
def f_args(*params):
"""Constructs a dict from a list of arguments for sending a CBOR command.
None elements will be omitted.
:param params: Arguments, in order, to add to the command.
:return: The input parameters as a dict.
"""
return dict((i, v) for i, v in enumerate(params, 1) if v is not None)
def compute_stats(elapsed):
n = len(elapsed)
mean = sum(elapsed) / n
variance = sum((x - mean)**2 for x in elapsed) / n
std_dev = variance**0.5
return (mean, std_dev)
def get_authenticator():
devices = None
while not devices:
try:
devices = get_opensk_devices(False)
except Exception as e: # pylint: disable=broad-except
error(str(e))
check_call(["nrfjprog", "--reset", "--family", "NRF52"],
stdout=DEVNULL,
stderr=STDOUT)
sleep(0.1)
return devices[0]
def main(args):
colorama.init()
authenticator = get_authenticator()
# If the device supports it, wink to show which device we use.
if authenticator.device.capabilities & hid.CAPABILITY.WINK:
authenticator.device.wink()
aaguid = uuid.UUID(bytes=authenticator.get_info().aaguid)
check_info(authenticator)
info(f"Testing OpenSK device AAGUID {aaguid} ({authenticator.device}).")
make_durations = []
get_durations = []
for _ in tqdm(range(args.runs), file=sys.stdout):
authenticator = get_authenticator()
try:
start = datetime.datetime.now()
result = authenticator.make_credential(
client_data_hash=bytes(32),
rp=PublicKeyCredentialRpEntity(id="example.com", name="Example"),
user=PublicKeyCredentialUserEntity(id=b"diana", name="Diana"),
key_params=[HYBRID_ALGORITHM],
)
end = datetime.datetime.now()
make_delta = (end - start).total_seconds() * 1000.0
make_durations.append(make_delta)
credential_data = result.auth_data.credential_data
credential_id_length = 256 * credential_data[16] + credential_data[17]
credential_id = credential_data[18:18 + credential_id_length]
allow_list = [{"type": "public-key", "id": credential_id}]
start = datetime.datetime.now()
_ = authenticator.get_assertion(
rp_id="example.com",
client_data_hash=bytes(32),
allow_list=allow_list,
)
end = datetime.datetime.now()
get_delta = (end - start).total_seconds() * 1000.0
get_durations.append(get_delta)
with open("make_durations.txt", "a", encoding="utf-8") as file_make:
file_make.write(str(make_delta) + ",\n")
with open("get_durations.txt", "a", encoding="utf-8") as file_get:
file_get.write(str(get_delta) + ",\n")
except ctap.CtapError as ex:
message = "Failed to make a hybrid signature with OpenSK"
if ex.code.value == ctap.CtapError.ERR.INVALID_COMMAND:
error(f"{message} (unsupported command).")
elif ex.code.value == ctap.CtapError.ERR.INVALID_PARAMETER:
error(f"{message} (invalid parameter, maybe a wrong byte array size?).")
elif ex.code.value == 0xF2: # VENDOR_INTERNAL_ERROR
error(f"{message} (internal conditions not met).")
elif ex.code.value == 0xF3: # VENDOR_HARDWARE_FAILURE
error(f"{message} (internal hardware error).")
else:
error(f"{message} (unexpected error: {ex})")
except Exception as e: # pylint: disable=broad-except
error(str(e))
info(f"Successful operations: {len(make_durations)} and {len(get_durations)}")
info("\nMake Credential benchmark:")
(mean, std_dev) = compute_stats(make_durations)
info(f"Average: {mean} ms/iter (standard deviation: {std_dev} ms/iter)")
info("\nGet Assertion benchmark:")
(mean, std_dev) = compute_stats(get_durations)
info(f"Average: {mean} ms/iter (standard deviation: {std_dev} ms/iter)")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--runs",
type=int,
default=1000,
help=("How many iterations to use."),
)
main(parser.parse_args())

View File

@@ -69,12 +69,12 @@ static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 2] = [
kernel::StorageLocation {
address: 0xC0000,
size: 0x10000, // 16 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
kernel::StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
];

View File

@@ -63,12 +63,12 @@ static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 2] = [
kernel::StorageLocation {
address: 0xC0000,
size: 0x10000, // 16 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
kernel::StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
];

View File

@@ -16,12 +16,12 @@ static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 2] = [
kernel::StorageLocation {
address: 0xC0000,
size: 0x10000, // 16 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
kernel::StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
];
"

View File

@@ -16,28 +16,28 @@ static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 5] = [
kernel::StorageLocation {
address: 0xC0000,
size: 0x10000, // 16 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
kernel::StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
// Partitions can also be split to maximize MPU happiness.
kernel::StorageLocation {
address: 0x4000,
size: 0x2000,
storage_type: kernel::StorageType::Partition,
},
kernel::StorageLocation {
address: 0x60000,
size: 0x20000,
storage_type: kernel::StorageType::Partition,
storage_type: kernel::StorageType::PARTITION,
},
kernel::StorageLocation {
address: 0x80000,
size: 0x20000,
storage_type: kernel::StorageType::Partition,
storage_type: kernel::StorageType::PARTITION,
},
kernel::StorageLocation {
address: 0x5000,
size: 0x1000,
storage_type: kernel::StorageType::METADATA,
},
];
"

View File

@@ -16,28 +16,28 @@ static mut STORAGE_LOCATIONS: [kernel::StorageLocation; 5] = [
kernel::StorageLocation {
address: 0xC0000,
size: 0x10000, // 16 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
kernel::StorageLocation {
address: 0xD0000,
size: 0x4000, // 4 pages
storage_type: kernel::StorageType::Store,
storage_type: kernel::StorageType::STORE,
},
// Partitions can also be split to maximize MPU happiness.
kernel::StorageLocation {
address: 0x4000,
size: 0x2000,
storage_type: kernel::StorageType::Partition,
},
kernel::StorageLocation {
address: 0x20000,
size: 0x20000,
storage_type: kernel::StorageType::Partition,
storage_type: kernel::StorageType::PARTITION,
},
kernel::StorageLocation {
address: 0x40000,
size: 0x20000,
storage_type: kernel::StorageType::Partition,
storage_type: kernel::StorageType::PARTITION,
},
kernel::StorageLocation {
address: 0x4000,
size: 0x1000,
storage_type: kernel::StorageType::METADATA,
},
];
"

View File

@@ -34,7 +34,6 @@ use rtt_target::{rprintln, rtt_init_print};
/// Size of a flash page in bytes.
const PAGE_SIZE: usize = 0x1000;
const METADATA_SIGN_OFFSET: usize = 0x800;
/// A flash page.
type Page = [u8; PAGE_SIZE];
@@ -49,19 +48,21 @@ unsafe fn read_page(address: usize) -> Page {
/// Parsed metadata for a firmware partition.
struct Metadata {
checksum: [u8; 32],
_signature: [u8; 64],
version: u64,
timestamp: u32,
address: u32,
}
impl Metadata {
pub const DATA_LEN: usize = 40;
}
/// Reads the metadata from a flash page.
impl From<Page> for Metadata {
fn from(page: Page) -> Self {
Metadata {
checksum: page[0..32].try_into().unwrap(),
_signature: page[32..96].try_into().unwrap(),
version: LittleEndian::read_u64(&page[METADATA_SIGN_OFFSET..][..8]),
address: LittleEndian::read_u32(&page[METADATA_SIGN_OFFSET + 8..][..4]),
timestamp: LittleEndian::read_u32(&page[32..36]),
address: LittleEndian::read_u32(&page[36..Metadata::DATA_LEN]),
}
}
}
@@ -75,15 +76,15 @@ struct BootPartition {
impl BootPartition {
const FIRMWARE_LENGTH: usize = 0x00040000;
/// Reads the metadata, returns the firmware version if all checks pass.
pub fn read_version(&self) -> Result<u64, ()> {
/// Reads the metadata, returns the timestamp if all checks pass.
pub fn read_timestamp(&self) -> Result<u32, ()> {
let metadata_page = unsafe { read_page(self.metadata_address) };
let hash_value = self.compute_upgrade_hash(&metadata_page);
let metadata = Metadata::from(metadata_page);
if self.firmware_address != metadata.address as usize {
#[cfg(debug_assertions)]
rprintln!(
"Partition address mismatch: expected 0x{:08X}, metadata 0x{:08X}",
"Firmware address mismatch: expected 0x{:08X}, metadata 0x{:08X}",
self.firmware_address,
metadata.address as usize
);
@@ -94,7 +95,7 @@ impl BootPartition {
rprintln!("Hash mismatch");
return Err(());
}
Ok(metadata.version)
Ok(metadata.timestamp)
}
/// Computes the SHA256 of metadata information and partition data.
@@ -106,14 +107,11 @@ impl BootPartition {
debug_assert!(self.firmware_address % PAGE_SIZE == 0);
debug_assert!(BootPartition::FIRMWARE_LENGTH % PAGE_SIZE == 0);
let cc310 = crypto_cell::CryptoCell310::new();
cc310.update(&metadata_page[METADATA_SIGN_OFFSET..], false);
for page_offset in (0..BootPartition::FIRMWARE_LENGTH).step_by(PAGE_SIZE) {
let page = unsafe { read_page(self.firmware_address + page_offset) };
cc310.update(
&page,
page_offset + PAGE_SIZE == BootPartition::FIRMWARE_LENGTH,
);
cc310.update(&page, false);
}
cc310.update(&metadata_page[32..Metadata::DATA_LEN], true);
cc310.finalize_and_clear()
}
@@ -158,12 +156,12 @@ fn main() -> ! {
};
#[cfg(debug_assertions)]
rprintln!("Reading partition A");
let version_a = partition_a.read_version();
let timestamp_a = partition_a.read_timestamp();
#[cfg(debug_assertions)]
rprintln!("Reading partition B");
let version_b = partition_b.read_version();
let timestamp_b = partition_b.read_timestamp();
match (version_a, version_b) {
match (timestamp_a, timestamp_b) {
(Ok(t1), Ok(t2)) => {
if t1 >= t2 {
partition_a.boot()

View File

@@ -15,6 +15,7 @@
extern crate alloc;
use openssl::{bn, ec, nid};
use sk_cbor::cbor_map;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
@@ -51,7 +52,25 @@ fn main() {
.public_key()
.to_bytes(&group, conversion_form, &mut ctx)
.unwrap();
let upgrade_pubkey_path = Path::new(&out_dir).join("opensk_upgrade_pubkey.bin");
const POINT_LEN: usize = 32;
assert_eq!(raw_bytes.len(), 1 + 2 * POINT_LEN);
assert_eq!(raw_bytes[0], 0x04);
let x_bytes = &raw_bytes[1..][..POINT_LEN];
let y_bytes = &raw_bytes[1 + POINT_LEN..][..POINT_LEN];
const EC2_KEY_TYPE: i64 = 2;
const P_256_CURVE: i64 = 1;
const ES256_ALGORITHM: i64 = -7;
let pub_key_cbor = sk_cbor::cbor_map! {
1 => EC2_KEY_TYPE,
3 => ES256_ALGORITHM,
-1 => P_256_CURVE,
-2 => x_bytes,
-3 => y_bytes,
};
let mut cbor_bytes = vec![];
sk_cbor::writer::write(pub_key_cbor, &mut cbor_bytes).unwrap();
let upgrade_pubkey_path = Path::new(&out_dir).join("opensk_upgrade_pubkey_cbor.bin");
let mut upgrade_pub_bin_file = File::create(&upgrade_pubkey_path).unwrap();
upgrade_pub_bin_file.write_all(&raw_bytes).unwrap();
upgrade_pub_bin_file.write_all(&cbor_bytes).unwrap();
}

View File

@@ -38,7 +38,7 @@ from tockloader import tockloader as loader
from tockloader.exceptions import TockLoaderException
import tools.configure
from tools.deploy_partition import create_metadata, load_priv_key, pad_to
from tools.deploy_partition import create_metadata, pad_to
PROGRAMMERS = frozenset(("jlink", "openocd", "pyocd", "nordicdfu", "none"))
@@ -156,7 +156,9 @@ SUPPORTED_BOARDS = {
),
}
APP_HEAP_SIZE = 32768
# The following value must match the one used in the file
# `src/entry_point.rs`
APP_HEAP_SIZE = 90000
def get_supported_boards() -> Tuple[str]:
@@ -620,9 +622,7 @@ class OpenSKInstaller:
# The kernel is already padded when read.
firmware_image = kernel + pad_to(app, app_size)
priv_key = load_priv_key(self.args.upgrade_priv_key)
metadata = create_metadata(firmware_image, board_props.kernel_address,
self.args.version, priv_key)
metadata = create_metadata(firmware_image, board_props.kernel_address)
if self.args.verbose_build:
info(f"Metadata bytes: {metadata}")
@@ -1131,22 +1131,6 @@ if __name__ == "__main__":
help=("Don't check that patches are in sync with their submodules."),
)
main_parser.add_argument(
"--private-key",
type=str,
default="crypto_data/opensk_upgrade.key",
dest="upgrade_priv_key",
help=("PEM file for signing the firmware."),
)
main_parser.add_argument(
"--version",
type=int,
default=-1,
dest="version",
help=("Firmware version that is built."),
)
main_parser.set_defaults(features=["with_ctap1"])
# Start parsing to know if we're going to list things or not.
@@ -1175,12 +1159,6 @@ if __name__ == "__main__":
const="crypto_bench",
help=("Compiles and installs the crypto_bench example that benchmarks "
"the performance of the cryptographic algorithms on the board."))
apps_group.add_argument(
"--measure_stack",
dest="application",
action="store_const",
const="measure_stack",
help=("Measures stack usage of Dilithium."))
apps_group.add_argument(
"--store_latency",
dest="application",

View File

@@ -55,15 +55,15 @@ There are variants of the board that introduce A/B partitions for upgrading the
firmware. You can bootstrap an upgradable board using one of the two commands:
```shell
./deploy.py --board=nrf52840dk_opensk_a --opensk --version=0
./deploy.py --board=nrf52840dk_opensk_b --opensk --version=0
./deploy.py --board=nrf52840dk_opensk_a --opensk
./deploy.py --board=nrf52840dk_opensk_b --opensk
```
Afterwards, you can upgrade the other partition with
```shell
./tools/perform_upgrade.sh nrf52840dk_opensk_b --version=1
./tools/perform_upgrade.sh nrf52840dk_opensk_a --version=1
./tools/perform_upgrade.sh nrf52840dk_opensk_b
./tools/perform_upgrade.sh nrf52840dk_opensk_a
```
respectively. You can only upgrade the partition that is not currently running,
@@ -75,6 +75,6 @@ If you deploy with `--vendor-hid`, also add this flag to `perform_upgrade.sh`,
for example:
```shell
./deploy.py --board=nrf52840dk_opensk_a --opensk --version=0 --vendor-hid
./tools/perform_upgrade.sh nrf52840dk_opensk_b --version=1 --vendor-hid
./deploy.py --board=nrf52840dk_opensk_a --opensk --vendor-hid
./tools/perform_upgrade.sh nrf52840dk_opensk_b --vendor-hid
```

View File

@@ -17,42 +17,27 @@
extern crate alloc;
extern crate lang_items;
use alloc::format;
use alloc::vec::Vec;
use core::fmt::Write;
use crypto::sha256::Sha256;
use crypto::{ecdsa, hybrid};
use crypto::{aes256, cbc, ecdsa, sha256, Hash256};
use libtock_drivers::console::Console;
use libtock_drivers::result::FlexUnwrap;
use libtock_drivers::timer;
use libtock_drivers::timer::{Timer, Timestamp};
use rng256::Rng256;
// use ctap2::env::tock::{take_storage, TockStorage};
// use persistent_store::Store;
use rng256::TockRng256;
libtock_core::stack_size! {0x11800}
/*fn boot_store(mut storage: TockStorage, erase: bool) -> Store<TockStorage> {
use persistent_store::Storage;
let num_pages = storage.num_pages();
if erase {
for page in 0..num_pages {
storage.erase_page(page).unwrap();
}
}
Store::new(storage).ok().unwrap()
}*/
libtock_core::stack_size! {0x800}
fn main() {
// Fix to be faster.
//let storage = take_storage().unwrap();
//let mut _store = boot_store(storage, true);
let mut console = Console::new();
let mut rng = rng256::TockRng256 {};
// 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().flex_unwrap();
let mut rng = TockRng256 {};
writeln!(console, "****************************************").unwrap();
writeln!(
console,
@@ -61,133 +46,136 @@ fn main() {
)
.unwrap();
custom_bench(
&mut console,
&timer,
"ECDSA keygen",
1000,
|| {},
|()| {
let k = ecdsa::SecKey::gensk(&mut rng);
k.genpk();
},
);
// 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);
custom_bench(
bench(
&mut console,
&timer,
"ECDSA sign",
1000,
"aes256::EncryptionKey::encrypt_block",
|| {
let k = ecdsa::SecKey::gensk(&mut rng);
let mut m = [0; 64];
rng.fill_bytes(&mut m);
(k, m)
},
|(k, m)| {
k.sign_rfc6979::<Sha256>(&m);
ek.encrypt_block(&mut [0; 16]);
},
);
custom_bench(
bench(
&mut console,
&timer,
"dilithium::SecKey::gensk_with_pk",
1000,
|| {},
|()| {
dilithium::sign::SecKey::gensk_with_pk(&mut rng);
},
);
custom_bench(
&mut console,
&timer,
"dilithium::SecKey::sign",
1000,
"aes256::DecryptionKey::decrypt_block",
|| {
let sk = dilithium::sign::SecKey::gensk(&mut rng);
let mut m = [0; 64];
rng.fill_bytes(&mut m);
(sk, m)
},
|(sk, m)| {
sk.sign(&m);
dk.decrypt_block(&mut [0; 16]);
},
);
custom_bench(
&mut console,
&timer,
"hybrid::SecKey::gensk_with_pk",
1000,
|| {},
|()| {
hybrid::SecKey::gensk_with_pk(&mut rng);
},
);
// CBC
let mut blocks = Vec::new();
for i in 0..8 {
blocks.resize(1 << (i + 4), 0);
bench(
&mut console,
&timer,
&format!("cbc::cbc_encrypt({} bytes)", blocks.len()),
|| {
cbc::cbc_encrypt(&ek, [0; 16], &mut blocks);
},
);
}
drop(blocks);
custom_bench(
let mut blocks = Vec::new();
for i in 0..8 {
blocks.resize(1 << (i + 4), 0);
bench(
&mut console,
&timer,
&format!("cbc::cbc_decrypt({} bytes)", blocks.len()),
|| {
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,
"hybrid::SecKey::sign",
1000,
"ecdsa::SecKey::sign_rng::<sha256::Sha256, _>",
|| {
let sk = hybrid::SecKey::gensk(&mut rng);
let mut m = [0; 64];
rng.fill_bytes(&mut m);
(sk, m)
},
|(sk, m)| {
sk.sign_rfc6979::<Sha256>(&m).to_asn1_der();
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 custom_bench<I, O, F, S>(
console: &mut Console,
timer: &Timer,
title: &str,
iter_count: usize,
mut setup: S,
mut f: F,
) where
S: FnMut() -> I,
F: FnMut(I) -> O,
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 elapsed = 0.0;
for _ in 1..(iter_count + 1) {
let inputs = setup();
let mut count = 1;
for _ in 0..30 {
let start = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
f(inputs);
let end = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
let mut run_duration = (end - start).ms();
// After 512 seconds, we get a negative difference between the start
// time and the end time.
if run_duration < 0.0 {
run_duration += 512.0 * 1000.0;
for _ in 0..count {
f();
}
elapsed += run_duration;
writeln!(console, "{},", run_duration).unwrap();
let end = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
let elapsed = (end - start).ms();
writeln!(
console,
"{} ms elapsed for {} iterations ({} ms/iter)",
elapsed,
count,
elapsed / (count as f64)
)
.unwrap();
console.flush();
if elapsed > 1000.0 {
break;
}
count <<= 1;
}
writeln!(
console,
"Total: {} ms elapsed for {} iterations ({} ms/iter)",
elapsed,
iter_count,
elapsed / (iter_count as f64)
)
.unwrap();
console.flush();
}

View File

@@ -1,184 +0,0 @@
// Copyright 2022 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]
#![feature(asm)]
#![feature(llvm_asm)]
#![allow(dead_code)]
extern crate alloc;
extern crate lang_items;
use core::fmt::Write;
use core::ptr;
use crypto::sha256::Sha256;
use crypto::{ecdsa, hybrid, sha256};
use libtock_drivers::console::Console;
libtock_core::stack_size! {0x11800}
#[inline(never)]
fn read_stack_pointer() -> u32 {
let x = 1u32;
let address = &x as *const u32;
address as u32
}
#[inline(never)]
fn print_stack_pointer(console: &mut Console) {
let x = 1u32;
writeln!(console, "Stack pointer: {:?}", &x as *const u32).unwrap();
}
/// Writes a byte pattern to a memory range.
///
/// Since the stack grows to lower addresses, end < start.
/// Addresses after start must be unused, i.e. start must be at least the current stack pointer.
/// Addresses until end should be within the stack area.
unsafe fn paint_memory(start: u32, end: u32) {
for address in (end..start).step_by(4) {
let p = address as *const u32;
ptr::write(p as *mut u32, 0xCDCDCDCD);
}
}
/// Find the lowest address that does not have the 0xCD pattern.
unsafe fn find_border(start: u32, end: u32) -> u32 {
for address in (end..start).step_by(4) {
let p = address as *const u32;
if ptr::read(p) != 0xCDCDCDCD {
return address;
}
}
start
}
#[inline(never)]
pub fn black_box<T>(dummy: T) -> T {
unsafe { llvm_asm!("" : : "r"(&dummy)) }
dummy
}
#[inline(never)]
fn keygen_ecdsa(rng: &mut rng256::TockRng256) {
let sk = ecdsa::SecKey::gensk(rng);
black_box(sk);
}
#[inline(never)]
fn keygen_dilithium(rng: &mut rng256::TockRng256) {
let sk = dilithium::sign::SecKey::gensk(rng);
black_box(sk);
}
#[inline(never)]
fn keygen_hybrid(rng: &mut rng256::TockRng256) {
let sk = hybrid::SecKey::gensk_with_pk(rng);
black_box(sk);
}
#[inline(never)]
fn sign_ecdsa(rng: &mut rng256::TockRng256, sk: &ecdsa::SecKey) {
let sig = sk.sign_rng::<sha256::Sha256, _>(&[], rng);
black_box(sig);
}
fn sign_dilithium(sk: &dilithium::sign::SecKey) {
let sig = sk.sign(&[]);
black_box(sig);
}
#[inline(never)]
fn sign_hybrid(sk: &hybrid::SecKey) {
let sig = sk.sign_rfc6979::<Sha256>(&[]);
black_box(sig);
}
// Measure the stack usage of the method itself, plus a u32.
#[inline(never)]
fn dummy_test() {
let x = 1u32;
black_box(x);
}
// Tests whether input parameters are correctly ignored in the measurement.
#[inline(never)]
fn param_test(big_param: &mut [u8; 0x1000]) {
let x = 0x01;
big_param[0] = x;
black_box(x);
}
fn write_result(console: &mut Console, text: &str, size: u32) {
writeln!(console, "{} size: 0x{:08X}", text, size).unwrap();
}
fn main() {
let mut console = Console::new();
let x = 1u32;
let sp = &x as *const u32;
// Should be safe to write from here.
let start = sp as u32 - 0x100u32;
writeln!(console, "Search start address: 0x{:08X}", start).unwrap();
print_stack_pointer(&mut console);
let mut rng = rng256::TockRng256 {};
unsafe { paint_memory(start, 0x20020000) };
keygen_ecdsa(&mut rng);
let min_address1 = unsafe { find_border(start, 0x20020000) };
unsafe { paint_memory(start, 0x20020000) };
keygen_dilithium(&mut rng);
let min_address2 = unsafe { find_border(start, 0x20020000) };
unsafe { paint_memory(start, 0x20020000) };
keygen_hybrid(&mut rng);
let min_address3 = unsafe { find_border(start, 0x20020000) };
let sk = ecdsa::SecKey::gensk(&mut rng);
unsafe { paint_memory(start, 0x20020000) };
sign_ecdsa(&mut rng, &sk);
let min_address4 = unsafe { find_border(start, 0x20020000) };
let sk = dilithium::sign::SecKey::gensk(&mut rng);
unsafe { paint_memory(start, 0x20020000) };
sign_dilithium(&sk);
let min_address5 = unsafe { find_border(start, 0x20020000) };
let sk = hybrid::SecKey::gensk(&mut rng);
unsafe { paint_memory(start, 0x20020000) };
sign_hybrid(&sk);
let min_address6 = unsafe { find_border(start, 0x20020000) };
let mut param = [0; 0x1000];
unsafe { paint_memory(start, 0x20020000) };
param_test(&mut param);
let min_address7 = unsafe { find_border(start, 0x20020000) };
unsafe { paint_memory(start, 0x20020000) };
dummy_test();
let min_address8 = unsafe { find_border(start, 0x20020000) };
let main_end = read_stack_pointer();
write_result(&mut console, " keygen_ecdsa", main_end - min_address1);
write_result(&mut console, "keygen_dilithium", main_end - min_address2);
write_result(&mut console, " keygen_hybrid", main_end - min_address3);
write_result(&mut console, " sign_ecdsa", main_end - min_address4);
write_result(&mut console, " sign_dilithium", main_end - min_address5);
write_result(&mut console, " sign_hybrid", main_end - min_address6);
write_result(&mut console, " test dummy", main_end - min_address7);
write_result(&mut console, " test input", main_end - min_address8);
}

View File

@@ -132,49 +132,49 @@ mod example {
match buf[0] {
0xe0 /* RATS */=> {
let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00];
return_code = bench_transmit(&mut console, timer, "TX: ATS", &mut answer_to_select);
return_code = bench_transmit(&mut console, &timer, "TX: ATS", &mut answer_to_select);
}
0xc2 /* DESELECT */ => {
// Ignore the request
let mut command_error = [0x6A, 0x81];
return_code = bench_transmit(&mut console, timer, "TX: DESELECT", &mut command_error);
return_code = bench_transmit(&mut console, &timer, "TX: DESELECT", &mut command_error);
}
0x02 | 0x03 /* APDU Prefix */ => match buf[2] {
// If the received packet is applet selection command (FIDO 2)
0xa4 /* SELECT */ => if buf[3] == 0x04 && buf[5] == 0x08 && buf[6] == 0xa0 {
// Vesion: "FIDO_2_0"
let mut reply = [buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,];
return_code = bench_transmit(&mut console, timer, "TX: Version Str", &mut reply);
return_code = bench_transmit(&mut console, &timer, "TX: Version Str", &mut reply);
} else if (buf[6] == 0xd2 && buf[7] == 0x76) || (buf[6] == 0xe1 && (buf[7] == 0x03 || buf[7] == 0x04)){
let mut reply = [buf[0], 0x90, 0x00];
return_code = bench_transmit(&mut console, timer, "TX: 0x9000", &mut reply);
return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
} else /* Unknown file */ {
let mut reply = [buf[0], 0x6a, 0x82];
return_code = bench_transmit(&mut console, timer, "TX: 0x6A82", &mut reply);
return_code = bench_transmit(&mut console, &timer, "TX: 0x6A82", &mut reply);
}
0xb0 /* READ */ => match buf[5] {
0x02 => {
let mut reply = [buf[0], 0x12, 0x90, 0x00,];
return_code = bench_transmit(&mut console, timer, "TX: File Size", &mut reply);
return_code = bench_transmit(&mut console, &timer, "TX: File Size", &mut reply);
}
0x12 => {
let mut reply = [buf[0], 0xd1, 0x01, 0x0e, 0x55, 0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65,
0x6e, 0x73, 0x6b, 0x2e, 0x64, 0x65, 0x76, 0x90, 0x00,];
return_code = bench_transmit(&mut console, timer, "TX: NDEF", &mut reply);
return_code = bench_transmit(&mut console, &timer, "TX: NDEF", &mut reply);
}
0x0f => {
let mut reply = [buf[0], 0x00, 0x0f, 0x20, 0x00, 0x7f, 0x00, 0x7f, 0x04, 0x06, 0xe1, 0x04,
0x00, 0x7f, 0x00, 0x00, 0x90, 0x00,];
return_code = bench_transmit(&mut console, timer, "TX: CC", &mut reply);
return_code = bench_transmit(&mut console, &timer, "TX: CC", &mut reply);
}
_ => {
let mut reply = [buf[0], 0x90, 0x00];
return_code = bench_transmit(&mut console, timer, "TX: 0x9000", &mut reply);
return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
}
}
_ => {
let mut reply = [buf[0], 0x90, 0x00];
return_code = bench_transmit(&mut console, timer, "TX: 0x9000", &mut reply);
return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
}
}
0x26 | 0x52 | 0x50 /* REQA | WUPA | Halt */ => {

View File

@@ -1,52 +0,0 @@
diff --git a/deploy.py b/deploy.py
index 7f91a2b..f7b1e9a 100755
--- a/deploy.py
+++ b/deploy.py
@@ -156,7 +156,7 @@ SUPPORTED_BOARDS = {
),
}
-APP_HEAP_SIZE = 32768
+APP_HEAP_SIZE = 16384
def get_supported_boards() -> Tuple[str]:
diff --git a/examples/measure_stack.rs b/examples/measure_stack.rs
index 88f9ebc..d285a80 100644
--- a/examples/measure_stack.rs
+++ b/examples/measure_stack.rs
@@ -26,7 +26,7 @@ use crypto::{ecdsa, hybrid, sha256};
use crypto::sha256::Sha256;
use libtock_drivers::console::Console;
-libtock_core::stack_size! {0x11800}
+libtock_core::stack_size! {0x1A000}
#[inline(never)]
fn read_stack_pointer() -> u32 {
diff --git a/nrf52840_layout.ld b/nrf52840_layout.ld
index 538a2a8..c7dd5e7 100644
--- a/nrf52840_layout.ld
+++ b/nrf52840_layout.ld
@@ -14,7 +14,7 @@ MEMORY {
* Any change to STACK_SIZE should be accompanied by a corresponding change to
* `elf2tab`'s `--stack` option
*/
-STACK_SIZE = 71680;
+STACK_SIZE = 106496;
MPU_MIN_ALIGN = 8K;
diff --git a/patches/tock/07-app-break-fix.patch b/patches/tock/07-app-break-fix.patch
index fcf46fd..4048b59 100644
--- a/patches/tock/07-app-break-fix.patch
+++ b/patches/tock/07-app-break-fix.patch
@@ -7,7 +7,7 @@ index c78b1c9fb..2769d0138 100644
// The 1.x Tock kernel allocates at least 3 kB to processes, and we need
// to ensure that happens as userspace may expect it.
- 3 * 1024
-+ 70 * 1024
++ 104 * 1024
// TOCK 2.0
//

View File

@@ -13,7 +13,6 @@ edition = "2018"
rng256 = { path = "../rng256" }
arrayref = "0.3.6"
subtle = { version = "2.2.3", default-features = false, features = ["nightly"] }
dilithium = { path = "../../third_party/dilithium" }
byteorder = { version = "1", default-features = false }
hex = { version = "0.3.2", default-features = false, optional = true }
ring = { version = "0.16.11", optional = true }

View File

@@ -308,11 +308,6 @@ pub mod test {
}
impl Rng256 for StressTestingRng {
// This function is unused, as we redefine gen_uniform_u32x8.
fn fill_bytes(&mut self, _buf: &mut [u8]) {
unreachable!()
}
// This function is unused, as we redefine gen_uniform_u32x8.
fn gen_uniform_u8x32(&mut self) -> [u8; 32] {
unreachable!()

View File

@@ -16,8 +16,9 @@ use super::exponent256::ExponentP256;
use super::gfp256::GFP256;
use super::int256::Int256;
use super::montgomery::Montgomery;
#[cfg(feature = "std")]
#[cfg(test)]
use arrayref::array_mut_ref;
#[cfg(feature = "std")]
use arrayref::array_ref;
use core::ops::Add;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
@@ -25,7 +26,7 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
// A point on the elliptic curve is represented by two field elements.
// The "direct" representation with GFP256 (integer modulo p) is used for serialization of public
// keys.
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy)]
pub struct PointP256 {
x: GFP256,
y: GFP256,
@@ -44,6 +45,7 @@ impl PointP256 {
/** Serialization **/
// This uses uncompressed point format from "SEC 1: Elliptic Curve Cryptography" ("Standards for
// Efficient Cryptography").
#[cfg(feature = "std")]
pub fn from_bytes_uncompressed_vartime(bytes: &[u8]) -> Option<PointP256> {
if bytes.len() != 65 || bytes[0] != 0x04 {
None
@@ -55,7 +57,7 @@ impl PointP256 {
}
}
#[cfg(feature = "std")]
#[cfg(test)]
pub fn to_bytes_uncompressed(&self, bytes: &mut [u8; 65]) {
bytes[0] = 0x04;
self.x.to_int().to_bin(array_mut_ref![bytes, 1, 32]);
@@ -548,6 +550,12 @@ impl core::fmt::Debug for PointP256 {
}
}
impl PartialEq for PointP256 {
fn eq(&self, other: &PointP256) -> bool {
self.x == other.x && self.y == other.y
}
}
#[cfg(test)]
pub mod test {
use super::*;

View File

@@ -33,13 +33,12 @@ pub struct SecKey {
k: NonZeroExponentP256,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Signature {
r: NonZeroExponentP256,
s: NonZeroExponentP256,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone)]
pub struct PubKey {
p: PointP256,
}
@@ -232,12 +231,13 @@ impl PubKey {
.map(|p| PubKey { p })
}
#[cfg(feature = "std")]
pub fn from_bytes_uncompressed(bytes: &[u8]) -> Option<PubKey> {
PointP256::from_bytes_uncompressed_vartime(bytes).map(|p| PubKey { p })
}
#[cfg(feature = "std")]
pub fn to_bytes_uncompressed(&self, bytes: &mut [u8; 65]) {
#[cfg(test)]
fn to_bytes_uncompressed(&self, bytes: &mut [u8; 65]) {
self.p.to_bytes_uncompressed(bytes);
}

View File

@@ -1,246 +0,0 @@
// Copyright 2021-2022 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::ecdsa;
use alloc::vec::Vec;
// A label generated uniformly at random from the output space of SHA256.
const LABEL: [u8; 32] = [
43, 253, 32, 250, 19, 51, 24, 237, 138, 49, 47, 182, 4, 194, 133, 183, 177, 218, 115, 58, 92,
117, 45, 172, 156, 5, 214, 176, 248, 103, 55, 216,
];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SecKey {
dilithium_seed: [u8; dilithium::params::SEEDBYTES],
ecdsa_sk: ecdsa::SecKey,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PubKey {
pub dilithium_pk: dilithium::sign::PubKey,
pub ecdsa_pk: ecdsa::PubKey,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Signature {
pub dilithium_sign: Vec<u8>,
pub ecdsa_sign: ecdsa::Signature,
}
fn ecdsa_input(msg: &[u8]) -> Vec<u8> {
let mut input = LABEL.to_vec();
input.extend(msg);
return input;
}
fn dilithium_input(msg: &[u8], ecdsa_sign: &ecdsa::Signature) -> Vec<u8> {
let mut input = LABEL.to_vec();
input.extend(msg);
input.extend(ecdsa_sign.to_asn1_der());
return input;
}
impl SecKey {
pub const BYTES_LENGTH: usize = 32 + dilithium::params::SEEDBYTES;
pub fn gensk<R>(rng: &mut R) -> SecKey
where
R: rng256::Rng256,
{
let mut seed = [0u8; dilithium::params::SEEDBYTES];
rng.fill_bytes(&mut seed);
SecKey {
dilithium_seed: seed,
ecdsa_sk: ecdsa::SecKey::gensk(rng),
}
}
pub fn gensk_with_pk<R>(rng: &mut R) -> (SecKey, PubKey)
where
R: rng256::Rng256,
{
let mut seed = [0u8; dilithium::params::SEEDBYTES];
rng.fill_bytes(&mut seed);
let (_, dilithium_pk) = dilithium::sign::SecKey::gensk_with_pk_from_seed(&seed);
let ecdsa_sk = ecdsa::SecKey::gensk(rng);
let ecdsa_pk = ecdsa_sk.genpk();
let sk = SecKey {
dilithium_seed: seed,
ecdsa_sk,
};
let pk = PubKey {
dilithium_pk,
ecdsa_pk,
};
(sk, pk)
}
pub fn genpk(&self) -> PubKey {
let (_, dilithium_pk) =
dilithium::sign::SecKey::gensk_with_pk_from_seed(&self.dilithium_seed);
PubKey {
dilithium_pk,
ecdsa_pk: self.ecdsa_sk.genpk(),
}
}
pub fn sign_rfc6979<H>(&self, msg: &[u8]) -> Signature
where
H: super::Hash256 + super::HashBlockSize64Bytes,
{
let ecdsa_sign = self.ecdsa_sk.sign_rfc6979::<H>(&ecdsa_input(&msg));
let dilithium_sk = dilithium::sign::SecKey::gensk_from_seed(&self.dilithium_seed);
// This wastes some stack, we could revert the Dilithium API to take a &mut [u8].
let dilithium_sign = dilithium_sk
.sign(&dilithium_input(&msg, &ecdsa_sign))
.to_vec();
return Signature {
ecdsa_sign,
dilithium_sign,
};
}
pub fn from_bytes(bytes: &[u8; SecKey::BYTES_LENGTH]) -> Option<SecKey> {
let ecdsa_bytes = array_ref!(bytes, 0, 32);
let ecdsa_sk = ecdsa::SecKey::from_bytes(&ecdsa_bytes)?;
let dilithium_seed = array_ref!(bytes, 32, dilithium::params::SEEDBYTES).clone();
return Some(SecKey {
ecdsa_sk,
dilithium_seed,
});
}
pub fn to_bytes(&self, bytes: &mut [u8; SecKey::BYTES_LENGTH]) {
let mut ecdsa_bytes = array_mut_ref!(bytes, 0, 32);
self.ecdsa_sk.to_bytes(&mut ecdsa_bytes);
let dilithium_bytes = array_mut_ref!(bytes, 32, dilithium::params::SEEDBYTES);
dilithium_bytes.copy_from_slice(&self.dilithium_seed);
}
}
impl PubKey {
pub const BYTES_LENGTH: usize = 2 * ecdsa::NBYTES + dilithium::params::PK_SIZE_PACKED;
pub fn from_bytes(bytes: &[u8; PubKey::BYTES_LENGTH]) -> Option<PubKey> {
let ecdsa_x_bytes = array_ref!(bytes, 0, ecdsa::NBYTES);
let ecdsa_y_bytes = array_ref!(bytes, ecdsa::NBYTES, ecdsa::NBYTES);
let ecdsa_pk = ecdsa::PubKey::from_coordinates(&ecdsa_x_bytes, &ecdsa_y_bytes)?;
let dilithium_bytes = array_ref!(
bytes,
ecdsa::NBYTES + ecdsa::NBYTES,
dilithium::params::PK_SIZE_PACKED
)
.clone();
let dilithium_pk = dilithium::sign::PubKey::from_bytes(&dilithium_bytes);
Some(PubKey {
ecdsa_pk,
dilithium_pk,
})
}
pub fn to_bytes(&self, bytes: &mut [u8; PubKey::BYTES_LENGTH]) {
let mut ecdsa_x_bytes = [0; ecdsa::NBYTES];
let mut ecdsa_y_bytes = [0; ecdsa::NBYTES];
self.ecdsa_pk
.to_coordinates(&mut ecdsa_x_bytes, &mut ecdsa_y_bytes);
array_mut_ref!(bytes, 0, ecdsa::NBYTES).clone_from(&ecdsa_x_bytes);
array_mut_ref!(bytes, ecdsa::NBYTES, ecdsa::NBYTES).clone_from(&ecdsa_y_bytes);
let mut dilithium_bytes = array_mut_ref!(
bytes,
ecdsa::NBYTES + ecdsa::NBYTES,
dilithium::params::PK_SIZE_PACKED
);
self.dilithium_pk.to_bytes(&mut dilithium_bytes);
}
pub fn verify_vartime<H>(&self, msg: &[u8], sign: &Signature) -> bool
where
H: super::Hash256,
{
return self
.ecdsa_pk
.verify_hash_vartime(&H::hash(&ecdsa_input(&msg)), &sign.ecdsa_sign)
&& self.dilithium_pk.verify(
&dilithium_input(&msg, &sign.ecdsa_sign),
array_ref!(sign.dilithium_sign, 0, dilithium::params::SIG_SIZE_PACKED),
);
}
}
impl Signature {
pub const BYTES_LENGTH: usize = 64 + dilithium::params::SIG_SIZE_PACKED;
/// Converts a signature into the CBOR required byte array representation.
///
/// This operation consumes the signature to efficiently use memory.
pub fn to_asn1_der(self) -> Vec<u8> {
let mut bytes = self.ecdsa_sign.to_asn1_der();
bytes.reserve_exact(dilithium::params::SIG_SIZE_PACKED);
bytes.extend(self.dilithium_sign.into_iter());
bytes
}
}
#[cfg(test)]
mod test {
extern crate rng256;
use super::super::sha256::Sha256;
use super::*;
use rng256::Rng256;
pub const ITERATIONS: u32 = 500;
#[test]
fn test_hybrid_seckey_to_bytes_from_bytes() {
let mut rng = rng256::ThreadRng256 {};
for _ in 0..ITERATIONS {
let sk = SecKey::gensk(&mut rng);
let mut bytes = [0; SecKey::BYTES_LENGTH];
sk.to_bytes(&mut bytes);
let decoded_sk = SecKey::from_bytes(&bytes);
assert_eq!(decoded_sk, Some(sk));
}
}
#[test]
fn test_hybrid_pubkey_to_bytes_from_bytes() {
let mut rng = rng256::ThreadRng256 {};
for _ in 0..ITERATIONS {
let sk = SecKey::gensk(&mut rng);
let pk = sk.genpk();
let mut bytes = [0; PubKey::BYTES_LENGTH];
pk.to_bytes(&mut bytes);
let decoded_pk = PubKey::from_bytes(&bytes);
assert_eq!(decoded_pk, Some(pk));
}
}
#[test]
fn test_hybrid_sign_rfc6979_verify_vartime() {
let mut rng = rng256::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));
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2019-2022 Google LLC
// 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.
@@ -16,8 +16,6 @@
#![feature(wrapping_int_impl)]
extern crate alloc;
#[macro_use]
extern crate arrayref;
pub mod aes256;
pub mod cbc;
@@ -26,7 +24,6 @@ pub mod ecdh;
pub mod ecdsa;
pub mod hkdf;
pub mod hmac;
pub mod hybrid;
pub mod sha256;
pub mod util;

View File

@@ -21,8 +21,6 @@ use rand::Rng;
// Lightweight RNG trait to generate uniformly distributed 256 bits.
pub trait Rng256 {
fn fill_bytes(&mut self, buf: &mut [u8]);
fn gen_uniform_u8x32(&mut self) -> [u8; 32];
fn gen_uniform_u32x8(&mut self) -> [u32; 8] {
@@ -47,10 +45,6 @@ fn bytes_to_u32(bytes: [u8; 32]) -> [u32; 8] {
pub struct TockRng256 {}
impl Rng256 for TockRng256 {
fn fill_bytes(&mut self, buf: &mut [u8]) {
libtock_drivers::rng::fill_buffer(buf);
}
fn gen_uniform_u8x32(&mut self) -> [u8; 32] {
let mut buf: [u8; 32] = [Default::default(); 32];
rng::fill_buffer(&mut buf);
@@ -64,11 +58,6 @@ pub struct ThreadRng256 {}
#[cfg(feature = "std")]
impl Rng256 for ThreadRng256 {
fn fill_bytes(&mut self, buf: &mut [u8]) {
let mut rng = rand::thread_rng();
rng.fill(buf);
}
fn gen_uniform_u8x32(&mut self) -> [u8; 32] {
let mut rng = rand::thread_rng();
let mut result = [Default::default(); 32];

View File

@@ -14,7 +14,7 @@ MEMORY {
* Any change to STACK_SIZE should be accompanied by a corresponding change to
* `elf2tab`'s `--stack` option
*/
STACK_SIZE = 71680;
STACK_SIZE = 16384;
MPU_MIN_ALIGN = 8K;

View File

@@ -1,13 +0,0 @@
diff --git a/rust-toolchain b/rust-toolchain
index 1674405..2ba073e 100644
--- a/rust-toolchain
+++ b/rust-toolchain
@@ -1,7 +1,7 @@
[toolchain]
# See https://rust-lang.github.io/rustup-components-history/ for a list of
# recently nightlies and what components are available for them.
-channel = "nightly-2021-03-25"
+channel = "nightly-2021-06-25"
components = ["clippy", "miri", "rustfmt"]
targets = ["thumbv7em-none-eabi",
"riscv32imac-unknown-none-elf",

View File

@@ -31,18 +31,19 @@ index 5465c95f4..e596648f7 100644
}
}
diff --git a/kernel/src/sched.rs b/kernel/src/sched.rs
index 8844bc6c3..00c13a7c6 100644
index 8844bc6c3..692bad2d3 100644
--- a/kernel/src/sched.rs
+++ b/kernel/src/sched.rs
@@ -118,10 +118,18 @@ pub enum SchedulingDecision {
@@ -118,10 +118,19 @@ pub enum SchedulingDecision {
TrySleep,
}
+/// Represents the type of a storage slice.
+#[derive(Copy, Clone)]
+pub enum StorageType {
+ Store = 1,
+ Partition = 2,
+ STORE = 1,
+ PARTITION = 2,
+ METADATA = 3,
+}
+
/// Represents a storage location in flash.

View File

@@ -7,7 +7,7 @@ index c78b1c9fb..2769d0138 100644
// The 1.x Tock kernel allocates at least 3 kB to processes, and we need
// to ensure that happens as userspace may expect it.
- 3 * 1024
+ 70 * 1024
+ 16 * 1024
// TOCK 2.0
//

View File

@@ -156,7 +156,7 @@ index f7899d8c5..6956523c6 100644
hil::usb::CtrlSetupResult::ErrGeneric
}
diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs
index 642039120..abf224f97 100644
index 642039120..adb7fde14 100644
--- a/capsules/src/usb/usbc_ctap_hid.rs
+++ b/capsules/src/usb/usbc_ctap_hid.rs
@@ -44,21 +44,59 @@ static CTAP_REPORT_DESCRIPTOR: &'static [u8] = &[

View File

@@ -262,7 +262,7 @@ index da3d16d85..e8f1a87a4 100644
if !app.waiting {
// The call to receive_packet() collected a pending packet.
diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs
index abf224f97..d47e5f644 100644
index adb7fde14..f6762b4b9 100644
--- a/capsules/src/usb/usbc_ctap_hid.rs
+++ b/capsules/src/usb/usbc_ctap_hid.rs
@@ -11,6 +11,7 @@ use super::descriptors::HIDSubordinateDescriptor;

View File

@@ -1,13 +0,0 @@
diff --git a/boards/nordic/nrf52840dk_opensk/src/main.rs b/boards/nordic/nrf52840dk_opensk/src/main.rs
index 83fd0bbab..53d623b46 100644
--- a/boards/nordic/nrf52840dk_opensk/src/main.rs
+++ b/boards/nordic/nrf52840dk_opensk/src/main.rs
@@ -147,7 +147,7 @@ static mut CHIP: Option<&'static nrf52840::chip::NRF52<Nrf52840DefaultPeripheral
/// Dummy buffer that causes the linker to reserve enough space for the stack.
#[no_mangle]
#[link_section = ".stack_buffer"]
-pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000];
+pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000];
/// Supported drivers by the platform
pub struct Platform {

View File

@@ -108,7 +108,7 @@ index e8f1a87a4..2c91c0968 100644
} else {
// Cannot cancel now because the transaction is already in process.
diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs
index d47e5f644..76f6af73b 100644
index f6762b4b9..16b80cb10 100644
--- a/capsules/src/usb/usbc_ctap_hid.rs
+++ b/capsules/src/usb/usbc_ctap_hid.rs
@@ -18,13 +18,27 @@ use core::cell::Cell;

View File

@@ -1,68 +0,0 @@
#!/usr/bin/env python3
# Copyright 2022 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.
# Lint as: python3
"""Hacky script to read RTT output from crypto_bench and plot it."""
import matplotlib.pyplot as plt
import numpy as np
def read_file(filename):
with open(filename, "r", encoding="utf-8") as f:
lines = f.readlines()
return [float(l[:-2]) for l in lines]
def below_threshold(data, threshold=10.):
a = np.array(data)
return np.mean(a / 1000. < threshold)
def percentiles(data):
s = sorted(data)
l = len(s)
for i in range(10):
print(f"{i * 10}th percentile: {s[(i * l) // 10]}")
def show_plot(data, title):
threshold_ratio = below_threshold(data)
if threshold_ratio < 0.9999:
max_range = min(max(data), 2 * 10. * 1000)
hist_range = (0, max_range)
else:
hist_range = None
plt.hist(data, bins=50, range=hist_range, label="Timing distribution")
mean = np.mean(data)
plt.axvline(x=mean, color="g", label="Mean")
if threshold_ratio < 0.9999:
plt.axvline(x=10. * 1000, color="r", label="CTAP threshold")
plt.title(title)
plt.legend()
plt.savefig(title.replace(" ", "") + "_plot.png")
plt.show()
def run(filename, title):
data = read_file(filename)
print(title, "below 10s:", below_threshold(data))
print("Mean:", np.mean(data))
percentiles(data)
show_plot(data, title)
run("make_durations.txt", "MakeCredential")
run("get_durations.txt", "GetAssertion")

View File

@@ -26,12 +26,6 @@ pub trait Customization {
// Constants for adjusting privacy and protection levels.
// ###########################################################################
/// Removes support for PIN protocol v1.
///
/// We support PIN protocol v2, "intended to aid FIPS certification".
/// To certify, you might want to remove support for v1 using this customization.
fn allows_pin_protocol_v1(&self) -> bool;
/// Changes the default level for the credProtect extension.
///
/// You can change this value to one of the following for more privacy:
@@ -247,11 +241,25 @@ pub trait Customization {
/// With P=20 and K=150, we have I=2M which is enough for 500 increments per day
/// for 10 years.
fn max_supported_resident_keys(&self) -> usize;
/// Sets the slot count of the multi-PIN feature.
///
/// # Invariant
///
/// - The slot count may not:
/// - make the storage entries that concatenate data of each slots
/// become larger than the storage page size,
/// - go over u8, as we only reserve 1 byte for the array index for
/// concatenated entries, or
/// - exceed the number of keys we reserve for the storage entries
/// that use unique keys for each slot.
///
/// The upper bound of this is currently 8.
fn slot_count(&self) -> usize;
}
#[derive(Clone)]
pub struct CustomizationImpl {
pub allows_pin_protocol_v1: bool,
pub default_cred_protect: Option<CredentialProtectionPolicy>,
pub default_min_pin_length: u8,
pub default_min_pin_length_rp_ids: &'static [&'static str],
@@ -267,10 +275,10 @@ pub struct CustomizationImpl {
pub max_large_blob_array_size: usize,
pub max_rp_ids_length: usize,
pub max_supported_resident_keys: usize,
pub slot_count: usize,
}
pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
allows_pin_protocol_v1: true,
default_cred_protect: None,
default_min_pin_length: 4,
default_min_pin_length_rp_ids: &[],
@@ -286,13 +294,10 @@ pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
max_large_blob_array_size: 2048,
max_rp_ids_length: 8,
max_supported_resident_keys: 150,
slot_count: 8,
};
impl Customization for CustomizationImpl {
fn allows_pin_protocol_v1(&self) -> bool {
self.allows_pin_protocol_v1
}
fn default_cred_protect(&self) -> Option<CredentialProtectionPolicy> {
self.default_cred_protect
}
@@ -363,6 +368,10 @@ impl Customization for CustomizationImpl {
fn max_supported_resident_keys(&self) -> usize {
self.max_supported_resident_keys
}
fn slot_count(&self) -> usize {
self.slot_count
}
}
#[cfg(feature = "std")]
@@ -435,6 +444,11 @@ pub fn is_valid(customization: &impl Customization) -> bool {
return false;
}
// Slot count should be at most 8.
if customization.slot_count() > 8 {
return false;
}
true
}

View File

@@ -15,7 +15,6 @@
// For compiling with std outside of tests.
#![cfg_attr(feature = "std", allow(dead_code))]
use alloc::vec::Vec;
use core::iter::Iterator;
use persistent_store::{StorageError, StorageResult};
@@ -56,7 +55,6 @@ pub fn is_aligned(block_size: usize, address: usize) -> bool {
///
/// The range is treated as the interval `[start, start + length)`.
/// All objects with length of 0, regardless of the start value, are considered empty.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ModRange {
start: usize,
length: usize,
@@ -88,45 +86,25 @@ impl ModRange {
self.length == 0
}
/// Returns the disjoint union with the other range, if consecutive.
/// Returns the disjoint union with the other range, if is consecutive.
///
/// Appending empty ranges is not possible.
/// Appending to the empty range returns the other range.
///
/// Returns true if successful.
pub fn append(&mut self, other: &ModRange) -> bool {
pub fn append(&self, other: ModRange) -> Option<ModRange> {
if self.is_empty() {
self.start = other.start;
self.length = other.length;
return true;
return Some(other);
}
if other.is_empty() {
return false;
return None;
}
if self.start >= other.start {
return false;
return None;
}
if self.length != other.start - self.start {
return false;
return None;
}
if let Some(new_length) = self.length.checked_add(other.length) {
self.length = new_length;
true
} else {
false
}
}
/// Helper function to check whether a range starts within another.
fn starts_inside(&self, range: &ModRange) -> bool {
!range.is_empty() && self.start >= range.start && self.start - range.start < range.length
}
/// Returns whether the given range has intersects.
///
/// Mathematically, we calculate whether: `self ∩ range ≠ ∅`.
pub fn intersects_range(&self, range: &ModRange) -> bool {
self.starts_inside(range) || range.starts_inside(self)
let new_length = self.length.checked_add(other.length);
new_length.map(|l| ModRange::new(self.start, l))
}
/// Returns whether the given range is fully contained.
@@ -150,73 +128,6 @@ impl ModRange {
}
}
pub struct Partition {
ranges: Vec<ModRange>,
}
impl Partition {
pub fn new() -> Partition {
Partition { ranges: Vec::new() }
}
/// Total length of all ranges.
pub fn length(&self) -> usize {
self.ranges.iter().map(|r| r.length()).sum()
}
/// Appends the given range.
///
/// Ranges should be appending with ascending start addresses.
pub fn append(&mut self, range: ModRange) -> bool {
if let Some(last_range) = self.ranges.last_mut() {
if range.start() <= last_range.start()
|| range.start() - last_range.start() < last_range.length()
{
return false;
}
if !last_range.append(&range) {
self.ranges.push(range);
}
} else {
self.ranges.push(range);
}
true
}
/// Returns the start address that corresponds to the given offset.
///
/// If the offset bigger than the accumulated length or the requested slice doesn't fit a
/// connected component, return `None`.
pub fn find_address(&self, mut offset: usize, length: usize) -> Option<usize> {
for range in &self.ranges {
if offset < range.length() {
return if range.length() - offset >= length {
Some(range.start() + offset)
} else {
None
};
}
offset -= range.length()
}
None
}
pub fn ranges_from(&self, start_address: usize) -> Vec<ModRange> {
let mut result = Vec::new();
for range in &self.ranges {
match start_address.checked_sub(range.start()) {
None | Some(0) => result.push(range.clone()),
Some(offset) => {
if range.length() > offset {
result.push(ModRange::new(start_address, range.length() - offset));
}
}
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -275,17 +186,18 @@ mod tests {
#[test]
fn mod_range_append() {
let mut range = ModRange::new(200, 100);
assert!(range.append(&ModRange::new(300, 400)));
assert!(range.start() == 200);
assert!(range.length() == 500);
assert!(!range.append(&ModRange::new(499, 400)));
assert!(!range.append(&ModRange::new(501, 400)));
assert!(!range.append(&ModRange::new(300, 400)));
let mut range = ModRange::new_empty();
assert!(range.append(&ModRange::new(200, 100)));
assert!(range.start() == 200);
assert!(range.length() == 100);
let range = ModRange::new(200, 100);
let new_range = range.append(ModRange::new(300, 400)).unwrap();
assert!(new_range.start() == 200);
assert!(new_range.length() == 500);
assert!(range.append(ModRange::new(299, 400)).is_none());
assert!(range.append(ModRange::new(301, 400)).is_none());
assert!(range.append(ModRange::new(200, 400)).is_none());
let empty_append = ModRange::new_empty()
.append(ModRange::new(200, 100))
.unwrap();
assert!(empty_append.start() == 200);
assert!(empty_append.length() == 100);
}
#[test]
@@ -304,20 +216,6 @@ mod tests {
assert!(ModRange::new(usize::MAX, 2).contains_range(&ModRange::new(usize::MAX, 2)));
}
#[test]
fn mod_range_intersects_range() {
let range = ModRange::new(200, 100);
assert!(range.intersects_range(&ModRange::new(200, 1)));
assert!(range.intersects_range(&ModRange::new(299, 1)));
assert!(!range.intersects_range(&ModRange::new(199, 1)));
assert!(!range.intersects_range(&ModRange::new(300, 1)));
assert!(!ModRange::new_empty().intersects_range(&ModRange::new_empty()));
assert!(!ModRange::new_empty().intersects_range(&ModRange::new(200, 100)));
assert!(!ModRange::new(200, 100).intersects_range(&ModRange::new_empty()));
assert!(ModRange::new(usize::MAX, 1).intersects_range(&ModRange::new(usize::MAX, 1)));
assert!(ModRange::new(usize::MAX, 2).intersects_range(&ModRange::new(usize::MAX, 2)));
}
#[test]
fn mod_range_aligned_iter() {
let mut iter = ModRange::new(200, 100).aligned_iter(100);
@@ -336,49 +234,4 @@ mod tests {
assert_eq!(iter.next(), Some(0xffff_ffff_ffff_fff0));
assert_eq!(iter.next(), None);
}
#[test]
fn partition_append() {
let mut partition = Partition::new();
partition.append(ModRange::new(0x4000, 0x1000));
partition.append(ModRange::new(0x20000, 0x20000));
partition.append(ModRange::new(0x40000, 0x20000));
assert_eq!(partition.find_address(0, 1), Some(0x4000));
assert_eq!(partition.length(), 0x41000);
}
#[test]
fn partition_find_address() {
let mut partition = Partition::new();
partition.append(ModRange::new(0x4000, 0x1000));
partition.append(ModRange::new(0x20000, 0x20000));
partition.append(ModRange::new(0x40000, 0x20000));
assert_eq!(partition.find_address(0, 0x1000), Some(0x4000));
assert_eq!(partition.find_address(0x1000, 0x1000), Some(0x20000));
assert_eq!(partition.find_address(0x20000, 0x1000), Some(0x3F000));
assert_eq!(partition.find_address(0x21000, 0x1000), Some(0x40000));
assert_eq!(partition.find_address(0x40000, 0x1000), Some(0x5F000));
assert_eq!(partition.find_address(0x41000, 0x1000), None);
assert_eq!(partition.find_address(0x40000, 0x2000), None);
}
#[test]
fn partition_ranges_from() {
let mut partition = Partition::new();
partition.append(ModRange::new(0x4000, 0x1000));
partition.append(ModRange::new(0x20000, 0x20000));
partition.append(ModRange::new(0x40000, 0x20000));
let all_ranges = partition.ranges_from(0);
let from_start_ranges = partition.ranges_from(0x4000);
assert_eq!(&all_ranges, &from_start_ranges);
assert_eq!(all_ranges.len(), 2);
assert_eq!(all_ranges[0], ModRange::new(0x4000, 0x1000));
assert_eq!(all_ranges[1], ModRange::new(0x20000, 0x40000));
let second_range = partition.ranges_from(0x20000);
let same_second_range = partition.ranges_from(0x1F000);
assert_eq!(&second_range, &same_second_range);
assert_eq!(&second_range, &all_ranges[1..]);
let partial_range = partition.ranges_from(0x30000);
assert_eq!(partial_range[0], ModRange::new(0x30000, 0x30000));
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2021-2022 Google LLC
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,28 +12,45 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use alloc::vec::Vec;
use persistent_store::StorageResult;
pub(crate) mod helper;
/// Accessors to storage locations used for upgrading from a CTAP command.
pub trait UpgradeStorage {
/// Processes the given data as part of an upgrade.
/// Reads a slice of the partition, if within bounds.
///
/// The offset indicates the data location inside the bundle.
/// The offset is relative to the start of the partition.
///
/// # Errors
///
/// - Returns [`StorageError::OutOfBounds`] if the data does not fit.
/// - Returns [`StorageError::CustomError`] if any Metadata or other check fails.
fn write_bundle(&mut self, offset: usize, data: Vec<u8>) -> StorageResult<()>;
/// Returns [`StorageError::OutOfBounds`] if the requested slice is not inside the partition.
fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]>;
/// Returns an identifier for the requested bundle.
/// Writes the given data to the given offset address, if within bounds of the partition.
///
/// Use this to determine whether you are writing to A or B.
fn bundle_identifier(&self) -> u32;
/// The offset is relative to the start of the partition.
///
/// # Errors
///
/// Returns [`StorageError::OutOfBounds`] if the data does not fit the partition.
fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()>;
/// Returns the currently running firmware version.
fn running_firmware_version(&self) -> u64;
/// Returns the address of the partition.
fn partition_address(&self) -> usize;
/// Returns the length of the partition.
fn partition_length(&self) -> usize;
/// Reads the metadata location.
fn read_metadata(&self) -> StorageResult<&[u8]>;
/// Writes the given data into the metadata location.
///
/// The passed in data is appended with 0xFF bytes if shorter than the metadata storage.
///
/// # Errors
///
/// Returns [`StorageError::OutOfBounds`] if the data is too long to fit the metadata storage.
fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()>;
}

View File

@@ -25,9 +25,9 @@ use crate::api::customization::Customization;
use crate::ctap::storage;
use crate::env::Env;
use alloc::boxed::Box;
use alloc::str;
use alloc::string::String;
use alloc::vec::Vec;
use alloc::{str, vec};
use crypto::hmac::hmac_256;
use crypto::sha256::Sha256;
use crypto::Hash256;
@@ -79,6 +79,7 @@ fn decrypt_pin(
/// truncated for persistent storage.
fn check_and_store_new_pin(
env: &mut impl Env,
slot_id: usize,
shared_secret: &dyn SharedSecret,
new_pin_enc: Vec<u8>,
) -> Result<(), Ctap2StatusCode> {
@@ -91,7 +92,7 @@ fn check_and_store_new_pin(
let mut pin_hash = [0u8; PIN_AUTH_LENGTH];
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..PIN_AUTH_LENGTH]);
// The PIN length is always < PIN_PADDED_LENGTH < 256.
storage::set_pin(env, &pin_hash, pin_length as u8)?;
storage::set_pin(env, slot_id, &pin_hash, pin_length as u8)?;
Ok(())
}
@@ -109,16 +110,16 @@ pub enum PinPermission {
pub struct ClientPin {
pin_protocol_v1: PinProtocol,
pin_protocol_v2: PinProtocol,
consecutive_pin_mismatches: u8,
consecutive_pin_mismatches: Vec<u8>,
pin_uv_auth_token_state: PinUvAuthTokenState,
}
impl ClientPin {
pub fn new(rng: &mut impl Rng256) -> ClientPin {
pub fn new(env: &mut impl Env) -> ClientPin {
ClientPin {
pin_protocol_v1: PinProtocol::new(rng),
pin_protocol_v2: PinProtocol::new(rng),
consecutive_pin_mismatches: 0,
pin_protocol_v1: PinProtocol::new(env.rng()),
pin_protocol_v2: PinProtocol::new(env.rng()),
consecutive_pin_mismatches: vec![0; env.customization().slot_count()],
pin_uv_auth_token_state: PinUvAuthTokenState::new(),
}
}
@@ -160,16 +161,21 @@ impl ClientPin {
fn verify_pin_hash_enc(
&mut self,
env: &mut impl Env,
slot_id: usize,
pin_uv_auth_protocol: PinUvAuthProtocol,
shared_secret: &dyn SharedSecret,
pin_hash_enc: Vec<u8>,
) -> Result<(), Ctap2StatusCode> {
match storage::pin_hash(env)? {
// To prevent out of bounds access in code below.
if slot_id >= self.consecutive_pin_mismatches.len() {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
match storage::pin_hash(env, slot_id)? {
Some(pin_hash) => {
if self.consecutive_pin_mismatches >= 3 {
if self.consecutive_pin_mismatches[slot_id] >= 3 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
}
storage::decr_pin_retries(env)?;
storage::decr_pin_retries(env, slot_id)?;
let pin_hash_dec = shared_secret
.decrypt(&pin_hash_enc)
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)?;
@@ -177,11 +183,11 @@ impl ClientPin {
if !bool::from(pin_hash.ct_eq(&pin_hash_dec)) {
self.get_mut_pin_protocol(pin_uv_auth_protocol)
.regenerate(env.rng());
if storage::pin_retries(env)? == 0 {
if storage::pin_retries(env, slot_id)? == 0 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
}
self.consecutive_pin_mismatches += 1;
if self.consecutive_pin_mismatches >= 3 {
self.consecutive_pin_mismatches[slot_id] += 1;
if self.consecutive_pin_mismatches[slot_id] >= 3 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED);
}
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
@@ -190,20 +196,26 @@ impl ClientPin {
// This status code is not explicitly mentioned in the specification.
None => return Err(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED),
}
storage::reset_pin_retries(env)?;
self.consecutive_pin_mismatches = 0;
storage::reset_pin_retries(env, slot_id)?;
self.consecutive_pin_mismatches[slot_id] = 0;
Ok(())
}
fn process_get_pin_retries(
&self,
env: &mut impl Env,
_client_pin_params: AuthenticatorClientPinParameters,
) -> Result<AuthenticatorClientPinResponse, Ctap2StatusCode> {
// TODO: Parse slot_id from params if multi-PIN feature is enabled.
let slot_id = 0;
if slot_id >= self.consecutive_pin_mismatches.len() {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(AuthenticatorClientPinResponse {
key_agreement: None,
pin_uv_auth_token: None,
retries: Some(storage::pin_retries(env)? as u64),
power_cycle_state: Some(self.consecutive_pin_mismatches >= 3),
retries: Some(storage::pin_retries(env, slot_id)? as u64),
power_cycle_state: Some(self.consecutive_pin_mismatches[slot_id] >= 3),
})
}
@@ -235,18 +247,20 @@ impl ClientPin {
new_pin_enc,
..
} = client_pin_params;
// TODO: Parse slot_id from params if multi-PIN feature is enabled.
let slot_id = 0;
let key_agreement = ok_or_missing(key_agreement)?;
let pin_uv_auth_param = ok_or_missing(pin_uv_auth_param)?;
let new_pin_enc = ok_or_missing(new_pin_enc)?;
if storage::pin_hash(env)?.is_some() {
if storage::pin_hash(env, slot_id)?.is_some() {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
}
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
shared_secret.verify(&new_pin_enc, &pin_uv_auth_param)?;
check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?;
storage::reset_pin_retries(env)?;
check_and_store_new_pin(env, slot_id, shared_secret.as_ref(), new_pin_enc)?;
storage::reset_pin_retries(env, slot_id)?;
Ok(())
}
@@ -263,12 +277,14 @@ impl ClientPin {
pin_hash_enc,
..
} = client_pin_params;
// TODO: Parse slot_id from params if multi-PIN feature is enabled.
let slot_id = 0;
let key_agreement = ok_or_missing(key_agreement)?;
let pin_uv_auth_param = ok_or_missing(pin_uv_auth_param)?;
let new_pin_enc = ok_or_missing(new_pin_enc)?;
let pin_hash_enc = ok_or_missing(pin_hash_enc)?;
if storage::pin_retries(env)? == 0 {
if storage::pin_retries(env, slot_id)? == 0 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
}
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
@@ -277,12 +293,13 @@ impl ClientPin {
shared_secret.verify(&auth_param_data, &pin_uv_auth_param)?;
self.verify_pin_hash_enc(
env,
slot_id,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc,
)?;
check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?;
check_and_store_new_pin(env, slot_id, shared_secret.as_ref(), new_pin_enc)?;
self.pin_protocol_v1.reset_pin_uv_auth_token(env.rng());
self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng());
Ok(())
@@ -302,30 +319,33 @@ impl ClientPin {
permissions_rp_id,
..
} = client_pin_params;
// TODO: Parse slot_id from params if multi-PIN feature is enabled.
let slot_id = 0;
let key_agreement = ok_or_missing(key_agreement)?;
let pin_hash_enc = ok_or_missing(pin_hash_enc)?;
if permissions.is_some() || permissions_rp_id.is_some() {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
if storage::pin_retries(env)? == 0 {
if storage::pin_retries(env, slot_id)? == 0 {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED);
}
let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?;
self.verify_pin_hash_enc(
env,
slot_id,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc,
)?;
if storage::has_force_pin_change(env)? {
if storage::has_force_pin_change(env, slot_id)? {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID);
}
self.pin_protocol_v1.reset_pin_uv_auth_token(env.rng());
self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng());
self.pin_uv_auth_token_state
.begin_using_pin_uv_auth_token(now);
.begin_using_pin_uv_auth_token(now, slot_id);
self.pin_uv_auth_token_state.set_default_permissions();
let pin_uv_auth_token = shared_secret.encrypt(
env.rng(),
@@ -391,13 +411,10 @@ impl ClientPin {
client_pin_params: AuthenticatorClientPinParameters,
now: CtapInstant,
) -> Result<ResponseData, Ctap2StatusCode> {
if !env.customization().allows_pin_protocol_v1()
&& client_pin_params.pin_uv_auth_protocol == PinUvAuthProtocol::V1
{
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
let response = match client_pin_params.sub_command {
ClientPinSubCommand::GetPinRetries => Some(self.process_get_pin_retries(env)?),
ClientPinSubCommand::GetPinRetries => {
Some(self.process_get_pin_retries(env, client_pin_params)?)
}
ClientPinSubCommand::GetKeyAgreement => {
Some(self.process_get_key_agreement(client_pin_params)?)
}
@@ -452,7 +469,9 @@ impl ClientPin {
self.pin_protocol_v1.reset_pin_uv_auth_token(rng);
self.pin_protocol_v2.regenerate(rng);
self.pin_protocol_v2.reset_pin_uv_auth_token(rng);
self.consecutive_pin_mismatches = 0;
for v in &mut self.consecutive_pin_mismatches {
*v = 0;
}
self.pin_uv_auth_token_state.stop_using_pin_uv_auth_token();
}
@@ -561,24 +580,38 @@ impl ClientPin {
self.pin_uv_auth_token_state.has_permissions_rp_id(rp_id)
}
/// Get the slot_id_in_use of the current pin_uv_auth_token_state if multi-PIN
/// feature is enabled. Otherwise return the default slot (0).
pub fn get_slot_id_in_use_or_default(
&self,
env: &mut impl Env,
) -> Result<Option<usize>, Ctap2StatusCode> {
if storage::has_multi_pin(env)? {
Ok(self.pin_uv_auth_token_state.slot_id_in_use())
} else {
Ok(Some(0))
}
}
#[cfg(test)]
pub fn new_test(
env: &mut impl Env,
slot_id: usize,
key_agreement_key: crypto::ecdh::SecKey,
pin_uv_auth_token: [u8; PIN_TOKEN_LENGTH],
pin_uv_auth_protocol: PinUvAuthProtocol,
) -> ClientPin {
let mut env = crate::env::test::TestEnv::new();
let (key_agreement_key_v1, key_agreement_key_v2) = match pin_uv_auth_protocol {
PinUvAuthProtocol::V1 => (key_agreement_key, crypto::ecdh::SecKey::gensk(env.rng())),
PinUvAuthProtocol::V2 => (crypto::ecdh::SecKey::gensk(env.rng()), key_agreement_key),
};
let mut pin_uv_auth_token_state = PinUvAuthTokenState::new();
pin_uv_auth_token_state.set_permissions(0xFF);
pin_uv_auth_token_state.begin_using_pin_uv_auth_token(CtapInstant::new(0));
pin_uv_auth_token_state.begin_using_pin_uv_auth_token(CtapInstant::new(0), slot_id);
ClientPin {
pin_protocol_v1: PinProtocol::new_test(key_agreement_key_v1, pin_uv_auth_token),
pin_protocol_v2: PinProtocol::new_test(key_agreement_key_v2, pin_uv_auth_token),
consecutive_pin_mismatches: 0,
consecutive_pin_mismatches: vec![0; env.customization().slot_count()],
pin_uv_auth_token_state,
}
}
@@ -598,7 +631,7 @@ mod test {
pin[..4].copy_from_slice(b"1234");
let mut pin_hash = [0u8; 16];
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]);
storage::set_pin(env, &pin_hash, 4).unwrap();
storage::set_pin(env, 0, &pin_hash, 4).unwrap();
}
/// Fails on PINs bigger than 64 bytes.
@@ -617,6 +650,7 @@ mod test {
/// tests using the wrong combination of PIN protocol and shared secret
/// should fail.
fn create_client_pin_and_shared_secret(
slot_id: usize,
pin_uv_auth_protocol: PinUvAuthProtocol,
) -> (ClientPin, Box<dyn SharedSecret>) {
let mut env = TestEnv::new();
@@ -624,8 +658,13 @@ mod test {
let pk = key_agreement_key.genpk();
let key_agreement = CoseKey::from(pk);
let pin_uv_auth_token = [0x91; PIN_TOKEN_LENGTH];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let client_pin = ClientPin::new_test(
&mut env,
slot_id,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let shared_secret = client_pin
.get_pin_protocol(pin_uv_auth_protocol)
.decapsulate(key_agreement, pin_uv_auth_protocol)
@@ -641,7 +680,9 @@ mod test {
sub_command: ClientPinSubCommand,
) -> (ClientPin, AuthenticatorClientPinParameters) {
let mut env = TestEnv::new();
let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol);
// TODO: Make slot_id a passed parameter once we include it in AuthenticatorClientPinParameters.
let (client_pin, shared_secret) =
create_client_pin_and_shared_secret(0, pin_uv_auth_protocol);
let pin = b"1234";
let mut padded_pin = [0u8; 64];
@@ -683,7 +724,7 @@ mod test {
#[test]
fn test_mix_pin_protocols() {
let mut env = TestEnv::new();
let client_pin = ClientPin::new(env.rng());
let client_pin = ClientPin::new(&mut env);
let pin_protocol_v1 = client_pin.get_pin_protocol(PinUvAuthProtocol::V1);
let pin_protocol_v2 = client_pin.get_pin_protocol(PinUvAuthProtocol::V2);
let message = vec![0xAA; 16];
@@ -724,7 +765,7 @@ mod test {
fn test_helper_verify_pin_hash_enc(pin_uv_auth_protocol: PinUvAuthProtocol) {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
let pin_protocol = client_pin.get_pin_protocol(pin_uv_auth_protocol);
let shared_secret = pin_protocol
.decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol)
@@ -734,7 +775,7 @@ mod test {
0x01, 0xD9, 0x88, 0x40, 0x50, 0xBB, 0xD0, 0x7A, 0x23, 0x1A, 0xEB, 0x69, 0xD8, 0x36,
0xC4, 0x12,
];
storage::set_pin(&mut env, &pin_hash, 4).unwrap();
storage::set_pin(&mut env, 0, &pin_hash, 4).unwrap();
let pin_hash_enc = shared_secret
.as_ref()
@@ -743,6 +784,7 @@ mod test {
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
@@ -754,6 +796,7 @@ mod test {
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
@@ -765,22 +808,24 @@ mod test {
.as_ref()
.encrypt(env.rng(), &pin_hash)
.unwrap();
client_pin.consecutive_pin_mismatches = 3;
client_pin.consecutive_pin_mismatches[0] = 3;
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED)
);
client_pin.consecutive_pin_mismatches = 0;
client_pin.consecutive_pin_mismatches[0] = 0;
let pin_hash_enc = vec![0x77; PIN_AUTH_LENGTH - 1];
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
@@ -792,6 +837,7 @@ mod test {
assert_eq!(
client_pin.verify_pin_hash_enc(
&mut env,
0,
pin_uv_auth_protocol,
shared_secret.as_ref(),
pin_hash_enc
@@ -819,7 +865,7 @@ mod test {
let expected_response = Some(AuthenticatorClientPinResponse {
key_agreement: None,
pin_uv_auth_token: None,
retries: Some(storage::pin_retries(&mut env).unwrap() as u64),
retries: Some(storage::pin_retries(&mut env, 0).unwrap() as u64),
power_cycle_state: Some(false),
});
assert_eq!(
@@ -827,11 +873,11 @@ mod test {
Ok(ResponseData::AuthenticatorClientPin(expected_response))
);
client_pin.consecutive_pin_mismatches = 3;
client_pin.consecutive_pin_mismatches[0] = 3;
let expected_response = Some(AuthenticatorClientPinResponse {
key_agreement: None,
pin_uv_auth_token: None,
retries: Some(storage::pin_retries(&mut env).unwrap() as u64),
retries: Some(storage::pin_retries(&mut env, 0).unwrap() as u64),
power_cycle_state: Some(true),
});
assert_eq!(
@@ -878,20 +924,6 @@ mod test {
test_helper_process_get_key_agreement(PinUvAuthProtocol::V2);
}
#[test]
fn test_process_get_key_agreement_v1_not_allowed() {
let (mut client_pin, params) = create_client_pin_and_parameters(
PinUvAuthProtocol::V1,
ClientPinSubCommand::GetKeyAgreement,
);
let mut env = TestEnv::new();
env.customization_mut().set_allows_pin_protocol_v1(false);
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
}
fn test_helper_process_set_pin(pin_uv_auth_protocol: PinUvAuthProtocol) {
let (mut client_pin, params) =
create_client_pin_and_parameters(pin_uv_auth_protocol, ClientPinSubCommand::SetPin);
@@ -941,8 +973,8 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID)
);
while storage::pin_retries(&mut env).unwrap() > 0 {
storage::decr_pin_retries(&mut env).unwrap();
while storage::pin_retries(&mut env, 0).unwrap() > 0 {
storage::decr_pin_retries(&mut env, 0).unwrap();
}
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
@@ -1035,7 +1067,7 @@ mod test {
let mut env = TestEnv::new();
set_standard_pin(&mut env);
assert_eq!(storage::force_pin_change(&mut env), Ok(()));
assert_eq!(storage::force_pin_change(&mut env, 0), Ok(()));
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID),
@@ -1145,7 +1177,7 @@ mod test {
let mut env = TestEnv::new();
set_standard_pin(&mut env);
assert_eq!(storage::force_pin_change(&mut env), Ok(()));
assert_eq!(storage::force_pin_change(&mut env, 0), Ok(()));
assert_eq!(
client_pin.process_command(&mut env, params, CtapInstant::new(0)),
Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID)
@@ -1237,17 +1269,17 @@ mod test {
),
];
for (pin, result) in test_cases {
let old_pin_hash = storage::pin_hash(&mut env).unwrap();
let old_pin_hash = storage::pin_hash(&mut env, 0).unwrap();
let new_pin_enc = encrypt_pin(shared_secret.as_ref(), pin);
assert_eq!(
check_and_store_new_pin(&mut env, shared_secret.as_ref(), new_pin_enc),
check_and_store_new_pin(&mut env, 0, shared_secret.as_ref(), new_pin_enc),
result
);
if result.is_ok() {
assert_ne!(old_pin_hash, storage::pin_hash(&mut env).unwrap());
assert_ne!(old_pin_hash, storage::pin_hash(&mut env, 0).unwrap());
} else {
assert_eq!(old_pin_hash, storage::pin_hash(&mut env).unwrap());
assert_eq!(old_pin_hash, storage::pin_hash(&mut env, 0).unwrap());
}
}
}
@@ -1269,7 +1301,8 @@ mod test {
salt: Vec<u8>,
) -> Result<Vec<u8>, Ctap2StatusCode> {
let mut env = TestEnv::new();
let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol);
let (client_pin, shared_secret) =
create_client_pin_and_shared_secret(0, pin_uv_auth_protocol);
let salt_enc = shared_secret.as_ref().encrypt(env.rng(), &salt).unwrap();
let salt_auth = shared_secret.authenticate(&salt_enc);
@@ -1287,7 +1320,8 @@ mod test {
fn test_helper_process_hmac_secret_bad_salt_auth(pin_uv_auth_protocol: PinUvAuthProtocol) {
let mut env = TestEnv::new();
let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol);
let (client_pin, shared_secret) =
create_client_pin_and_shared_secret(0, pin_uv_auth_protocol);
let cred_random = [0xC9; 32];
let salt_enc = vec![0x01; 32];
@@ -1403,7 +1437,7 @@ mod test {
#[test]
fn test_has_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
client_pin.pin_uv_auth_token_state.set_permissions(0x7F);
for permission in PinPermission::into_enum_iter() {
assert_eq!(
@@ -1427,7 +1461,7 @@ mod test {
#[test]
fn test_has_no_rp_id_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
assert_eq!(client_pin.has_no_rp_id_permission(), Ok(()));
client_pin
.pin_uv_auth_token_state
@@ -1441,7 +1475,7 @@ mod test {
#[test]
fn test_has_no_or_rp_id_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
assert_eq!(client_pin.has_no_or_rp_id_permission("example.com"), Ok(()));
client_pin
.pin_uv_auth_token_state
@@ -1456,7 +1490,7 @@ mod test {
#[test]
fn test_has_no_or_rp_id_hash_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
let rp_id_hash = Sha256::hash(b"example.com");
assert_eq!(
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash),
@@ -1478,7 +1512,7 @@ mod test {
#[test]
fn test_ensure_rp_id_permission() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
assert_eq!(client_pin.ensure_rp_id_permission("example.com"), Ok(()));
assert_eq!(
client_pin
@@ -1496,11 +1530,11 @@ mod test {
#[test]
fn test_verify_pin_uv_auth_token() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
let message = [0xAA];
client_pin
.pin_uv_auth_token_state
.begin_using_pin_uv_auth_token(CtapInstant::new(0));
.begin_using_pin_uv_auth_token(CtapInstant::new(0), 0);
let pin_uv_auth_token_v1 = client_pin
.get_pin_protocol(PinUvAuthProtocol::V1)
@@ -1517,6 +1551,7 @@ mod test {
let pin_uv_auth_param_v2_from_v1_token =
authenticate_pin_uv_auth_token(pin_uv_auth_token_v1, &message, PinUvAuthProtocol::V2);
assert_eq!(client_pin.pin_uv_auth_token_state.slot_id_in_use(), Some(0));
assert_eq!(
client_pin.verify_pin_uv_auth_token(
&message,
@@ -1570,7 +1605,7 @@ mod test {
#[test]
fn test_verify_pin_uv_auth_token_not_in_use() {
let mut env = TestEnv::new();
let client_pin = ClientPin::new(env.rng());
let client_pin = ClientPin::new(&mut env);
let message = [0xAA];
let pin_uv_auth_token_v1 = client_pin
@@ -1592,7 +1627,7 @@ mod test {
#[test]
fn test_reset() {
let mut env = TestEnv::new();
let mut client_pin = ClientPin::new(env.rng());
let mut client_pin = ClientPin::new(&mut env);
let public_key_v1 = client_pin.pin_protocol_v1.get_public_key();
let public_key_v2 = client_pin.pin_protocol_v2.get_public_key();
let token_v1 = *client_pin.pin_protocol_v1.get_pin_uv_auth_token();

View File

@@ -15,10 +15,11 @@
use super::data_formats::{
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
CoseKey, CredentialManagementSubCommand, CredentialManagementSubCommandParameters,
GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions, MakeCredentialOptions,
PinUvAuthProtocol, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, SetMinPinLengthParams,
CoseKey, CoseSignature, CredentialManagementSubCommand,
CredentialManagementSubCommandParameters, GetAssertionExtensions, GetAssertionOptions,
MakeCredentialExtensions, MakeCredentialOptions, PinUvAuthProtocol,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity, SetMinPinLengthParams,
};
use super::status_code::Ctap2StatusCode;
use super::{cbor_read, key_material};
@@ -592,9 +593,10 @@ impl TryFrom<cbor::Value> for AuthenticatorVendorConfigureParameters {
#[derive(Debug, PartialEq, Eq)]
pub struct AuthenticatorVendorUpgradeParameters {
pub offset: usize,
pub address: Option<usize>,
pub data: Vec<u8>,
pub hash: [u8; 32],
pub hash: Vec<u8>,
pub signature: Option<CoseSignature>,
}
impl TryFrom<cbor::Value> for AuthenticatorVendorUpgradeParameters {
@@ -603,16 +605,25 @@ impl TryFrom<cbor::Value> for AuthenticatorVendorUpgradeParameters {
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
destructure_cbor_map! {
let {
0x01 => offset,
0x01 => address,
0x02 => data,
0x03 => hash,
0x04 => signature,
} = extract_map(cbor_value)?;
}
let offset = extract_unsigned(ok_or_missing(offset)?)? as usize;
let address = address
.map(extract_unsigned)
.transpose()?
.map(|u| u as usize);
let data = extract_byte_string(ok_or_missing(data)?)?;
let hash = <[u8; 32]>::try_from(extract_byte_string(ok_or_missing(hash)?)?)
.map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?;
Ok(AuthenticatorVendorUpgradeParameters { offset, data, hash })
let hash = extract_byte_string(ok_or_missing(hash)?)?;
let signature = signature.map(CoseSignature::try_from).transpose()?;
Ok(AuthenticatorVendorUpgradeParameters {
address,
data,
hash,
signature,
})
}
}
@@ -620,7 +631,7 @@ impl TryFrom<cbor::Value> for AuthenticatorVendorUpgradeParameters {
mod test {
use super::super::data_formats::{
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType,
PublicKeyCredentialUserEntity,
PublicKeyCredentialUserEntity, SignatureAlgorithm,
};
use super::super::ES256_CRED_PARAM;
use super::*;
@@ -1061,16 +1072,6 @@ mod test {
let command = Command::deserialize(&cbor_bytes);
assert_eq!(command, Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR));
// Missing offset
let cbor_value = cbor_map! {
0x02 => [0xFF; 0x100],
0x03 => [0x44; 32],
};
assert_eq!(
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
);
// Missing data
let cbor_value = cbor_map! {
0x01 => 0x1000,
@@ -1081,17 +1082,6 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
);
// Invalid hash size
let cbor_value = cbor_map! {
0x01 => 0x1000,
0x02 => [0xFF; 0x100],
0x03 => [0x44; 33],
};
assert_eq!(
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
// Missing hash
let cbor_value = cbor_map! {
0x01 => 0x1000,
@@ -1102,7 +1092,29 @@ mod test {
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
);
// Valid
// Valid without address
let cbor_value = cbor_map! {
0x02 => [0xFF; 0x100],
0x03 => [0x44; 32],
0x04 => cbor_map! {
"alg" => -7,
"signature" => [0x55; 64],
},
};
assert_eq!(
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
Ok(AuthenticatorVendorUpgradeParameters {
address: None,
data: vec![0xFF; 0x100],
hash: vec![0x44; 32],
signature: Some(CoseSignature {
algorithm: SignatureAlgorithm::Es256,
bytes: [0x55; 64],
}),
})
);
// Valid without signature
let cbor_value = cbor_map! {
0x01 => 0x1000,
0x02 => [0xFF; 0x100],
@@ -1111,9 +1123,10 @@ mod test {
assert_eq!(
AuthenticatorVendorUpgradeParameters::try_from(cbor_value),
Ok(AuthenticatorVendorUpgradeParameters {
offset: 0x1000,
address: Some(0x1000),
data: vec![0xFF; 0x100],
hash: [0x44; 32],
hash: vec![0x44; 32],
signature: None,
})
);
}

View File

@@ -55,15 +55,21 @@ fn process_set_min_pin_length(
if new_min_pin_length < store_min_pin_length {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION);
}
let mut force_change_pin = force_change_pin.unwrap_or(false);
if force_change_pin && storage::pin_hash(env)?.is_none() {
let force_change_pin = force_change_pin.unwrap_or(false);
if force_change_pin && !storage::has_pin(env)? {
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_NOT_SET);
}
if let Some(old_length) = storage::pin_code_point_length(env)? {
force_change_pin |= new_min_pin_length > old_length;
}
if force_change_pin {
storage::force_pin_change(env)?;
for slot_id in 0..env.customization().slot_count() {
if storage::pin_hash(env, slot_id)?.is_none() {
continue;
}
let mut force_change_pin = force_change_pin;
if let Some(old_length) = storage::pin_code_point_length(env, slot_id)? {
force_change_pin |= new_min_pin_length > old_length;
}
if force_change_pin {
storage::force_pin_change(env, slot_id)?;
}
}
storage::set_min_pin_length(env, new_min_pin_length)?;
if let Some(min_pin_length_rp_ids) = min_pin_length_rp_ids {
@@ -85,9 +91,13 @@ pub fn process_config(
pin_uv_auth_protocol,
} = params;
let slot_id = client_pin.get_slot_id_in_use_or_default(env)?;
let enforce_uv =
!matches!(sub_command, ConfigSubCommand::ToggleAlwaysUv) && storage::has_always_uv(env)?;
if storage::pin_hash(env)?.is_some() || enforce_uv {
// If multi-PIN feature is enabled, no PIN is in use, and the command is to turn off alwaysUv,
// the PIN check will be skipped here but an OPERATION_DENIED will still be returned later,
// which is correct behavior.
if (slot_id.is_some() && storage::pin_hash(env, slot_id.unwrap())?.is_some()) || enforce_uv {
let pin_uv_auth_param =
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let pin_uv_auth_protocol =
@@ -133,8 +143,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::EnableEnterpriseAttestation,
@@ -160,8 +175,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::ToggleAlwaysUv,
@@ -195,9 +215,14 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
let mut config_data = vec![0xFF; 32];
config_data.extend(&[0x0D, ConfigSubCommand::ToggleAlwaysUv as u8]);
@@ -265,8 +290,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
// First, increase minimum PIN length from 4 to 6 without PIN auth.
let min_pin_length = 6;
@@ -277,7 +307,7 @@ mod test {
// Second, increase minimum PIN length from 6 to 8 with PIN auth.
// The stored PIN or its length don't matter since we control the token.
storage::set_pin(&mut env, &[0x88; 16], 8).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 8).unwrap();
let min_pin_length = 8;
let mut config_params = create_min_pin_config_params(min_pin_length, None);
let pin_uv_auth_param = vec![
@@ -309,8 +339,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
// First, set RP IDs without PIN auth.
let min_pin_length = 6;
@@ -329,7 +364,7 @@ mod test {
let min_pin_length = 8;
let min_pin_length_rp_ids = vec!["another.example.com".to_string()];
// The stored PIN or its length don't matter since we control the token.
storage::set_pin(&mut env, &[0x88; 16], 8).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 8).unwrap();
let mut config_params =
create_min_pin_config_params(min_pin_length, Some(min_pin_length_rp_ids.clone()));
let pin_uv_auth_param = vec![
@@ -385,10 +420,15 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
// Increase min PIN, force PIN change.
let min_pin_length = 6;
let mut config_params = create_min_pin_config_params(min_pin_length, None);
@@ -400,7 +440,7 @@ mod test {
let config_response = process_config(&mut env, &mut client_pin, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(storage::min_pin_length(&mut env), Ok(min_pin_length));
assert_eq!(storage::has_force_pin_change(&mut env), Ok(true));
assert_eq!(storage::has_force_pin_change(&mut env, 0), Ok(true));
}
#[test]
@@ -408,10 +448,15 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
storage::set_pin(&mut env, &[0x88; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0x88; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xE3, 0x74, 0xF4, 0x27, 0xBE, 0x7D, 0x40, 0xB5, 0x71, 0xB6, 0xB4, 0x1A, 0xD2, 0xC1,
0x53, 0xD7,
@@ -431,7 +476,7 @@ mod test {
};
let config_response = process_config(&mut env, &mut client_pin, config_params);
assert_eq!(config_response, Ok(ResponseData::AuthenticatorConfig));
assert_eq!(storage::has_force_pin_change(&mut env), Ok(true));
assert_eq!(storage::has_force_pin_change(&mut env, 0), Ok(true));
}
#[test]
@@ -439,8 +484,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let config_params = AuthenticatorConfigParameters {
sub_command: ConfigSubCommand::VendorPrototype,

View File

@@ -19,7 +19,7 @@ use super::data_formats::{
use super::status_code::Ctap2StatusCode;
use super::{cbor_read, cbor_write};
use crate::api::key_store::KeyStore;
use crate::ctap::data_formats::{extract_byte_string, extract_map};
use crate::ctap::data_formats::{extract_byte_string, extract_map, extract_unsigned};
use crate::env::Env;
use alloc::string::String;
use alloc::vec::Vec;
@@ -48,6 +48,7 @@ struct CredentialSource {
rp_id_hash: [u8; 32],
cred_protect_policy: Option<CredentialProtectionPolicy>,
cred_blob: Option<Vec<u8>>,
slot_id: Option<usize>,
}
// The data fields contained in the credential ID are serialized using CBOR maps.
@@ -57,6 +58,7 @@ enum CredentialSourceField {
RpIdHash = 1,
CredProtectPolicy = 2,
CredBlob = 3,
SlotId = 4,
}
impl From<CredentialSourceField> for sk_cbor::Value {
@@ -84,6 +86,7 @@ fn decrypt_legacy_credential_id(
rp_id_hash: plaintext[32..64].try_into().unwrap(),
cred_protect_policy: None,
cred_blob: None,
slot_id: None,
}))
}
@@ -102,6 +105,7 @@ fn decrypt_cbor_credential_id(
CredentialSourceField::RpIdHash=> rp_id_hash,
CredentialSourceField::CredProtectPolicy => cred_protect_policy,
CredentialSourceField::CredBlob => cred_blob,
CredentialSourceField::SlotId => slot_id,
} = extract_map(cbor_credential_source)?;
}
Ok(match (private_key, rp_id_hash) {
@@ -115,11 +119,19 @@ fn decrypt_cbor_credential_id(
.map(CredentialProtectionPolicy::try_from)
.transpose()?;
let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
let slot_id = match slot_id.map(extract_unsigned).transpose()? {
Some(x) => Some(
usize::try_from(x)
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
),
None => None,
};
Some(CredentialSource {
private_key,
rp_id_hash: rp_id_hash.try_into().unwrap(),
cred_protect_policy,
cred_blob,
slot_id,
})
}
_ => None,
@@ -167,13 +179,15 @@ pub fn encrypt_to_credential_id(
rp_id_hash: &[u8; 32],
cred_protect_policy: Option<CredentialProtectionPolicy>,
cred_blob: Option<Vec<u8>>,
slot_id: usize,
) -> Result<Vec<u8>, Ctap2StatusCode> {
let mut payload = Vec::new();
let cbor = cbor_map_options! {
CredentialSourceField::PrivateKey => private_key,
CredentialSourceField::RpIdHash => rp_id_hash,
CredentialSourceField::RpIdHash=> rp_id_hash,
CredentialSourceField::CredProtectPolicy => cred_protect_policy,
CredentialSourceField::CredBlob => cred_blob,
CredentialSourceField::SlotId => slot_id as u64,
};
cbor_write(cbor, &mut payload)?;
add_padding(&mut payload)?;
@@ -262,6 +276,7 @@ pub fn decrypt_credential_id(
user_icon: None,
cred_blob: credential_source.cred_blob,
large_blob_key: None,
slot_id: credential_source.slot_id,
}))
}
@@ -282,7 +297,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
.unwrap()
.unwrap();
@@ -308,7 +323,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let mut encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
encrypted_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION;
// Override the HMAC to pass the check.
encrypted_id.truncate(&encrypted_id.len() - 32);
@@ -328,7 +343,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
for i in 0..encrypted_id.len() {
let mut modified_id = encrypted_id.clone();
modified_id[i] ^= 0x01;
@@ -356,7 +371,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
for length in (1..CBOR_CREDENTIAL_ID_SIZE).step_by(16) {
assert_eq!(
@@ -423,7 +438,7 @@ mod test {
let rp_id_hash = [0x55; 32];
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, 0).unwrap();
assert_eq!(encrypted_id.len(), CBOR_CREDENTIAL_ID_SIZE);
}
@@ -444,6 +459,7 @@ mod test {
&rp_id_hash,
cred_protect_policy,
cred_blob,
0,
);
assert!(encrypted_id.is_ok());
@@ -461,6 +477,7 @@ mod test {
&rp_id_hash,
Some(CredentialProtectionPolicy::UserVerificationRequired),
None,
0,
)
.unwrap();
@@ -481,9 +498,15 @@ mod test {
let rp_id_hash = [0x55; 32];
let cred_blob = Some(vec![0x55; env.customization().max_cred_blob_length()]);
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, cred_blob.clone())
.unwrap();
let encrypted_id = encrypt_to_credential_id(
&mut env,
&private_key,
&rp_id_hash,
None,
cred_blob.clone(),
0,
)
.unwrap();
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
.unwrap()
@@ -491,4 +514,22 @@ mod test {
assert_eq!(decrypted_source.private_key, private_key);
assert_eq!(decrypted_source.cred_blob, cred_blob);
}
#[test]
fn test_slot_id_persisted() {
let mut env = TestEnv::new();
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
let rp_id_hash = [0x55; 32];
let slot_id = 1;
let encrypted_id =
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None, slot_id)
.unwrap();
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
.unwrap()
.unwrap();
assert_eq!(decrypted_source.private_key, private_key);
assert_eq!(decrypted_source.slot_id, Some(slot_id));
}
}

View File

@@ -81,6 +81,7 @@ fn enumerate_credentials_response(
user_icon,
cred_blob: _,
large_blob_key,
slot_id: _,
} = credential;
let user = PublicKeyCredentialUserEntity {
user_id: user_handle,
@@ -183,12 +184,17 @@ fn process_enumerate_credentials_begin(
.rp_id_hash
.ok_or(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)?;
client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash[..])?;
// enumerateCredentials needs UV, so slot_id must not be None.
let slot_id = client_pin
.get_slot_id_in_use_or_default(env)?
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let mut iter_result = Ok(());
let iter = storage::iter_credentials(env, &mut iter_result)?;
let mut rp_credentials: Vec<usize> = iter
.filter_map(|(key, credential)| {
let cred_rp_id_hash = Sha256::hash(credential.rp_id.as_bytes());
if cred_rp_id_hash == rp_id_hash.as_slice() {
let slot_id_matches = credential.slot_id.unwrap_or(0) == slot_id;
if cred_rp_id_hash == rp_id_hash.as_slice() && slot_id_matches {
Some(key)
} else {
None
@@ -385,6 +391,7 @@ mod test {
user_icon: Some("icon".to_string()),
cred_blob: None,
large_blob_key: None,
slot_id: None,
}
}
@@ -392,14 +399,19 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let credential_source = create_credential_source(&mut env);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
ctap_state.client_pin = client_pin;
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let management_data = vec![CredentialManagementSubCommand::GetCredsMetadata as u8];
let pin_uv_auth_param = authenticate_pin_uv_auth_token(
&pin_uv_auth_token,
@@ -474,8 +486,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let credential_source1 = create_credential_source(&mut env);
let mut credential_source2 = create_credential_source(&mut env);
credential_source2.rp_id = "another.example.com".to_string();
@@ -486,7 +503,7 @@ mod test {
storage::store_credential(&mut env, credential_source1).unwrap();
storage::store_credential(&mut env, credential_source2).unwrap();
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
0xD0, 0xD1,
@@ -568,8 +585,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let credential_source = create_credential_source(&mut env);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
@@ -582,7 +604,7 @@ mod test {
storage::store_credential(&mut env, credential).unwrap();
}
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0x1A, 0xA4, 0x96, 0xDA, 0x62, 0x80, 0x28, 0x13, 0xEB, 0x32, 0xB9, 0xF1, 0xD2, 0xA9,
0xD0, 0xD1,
@@ -649,8 +671,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let credential_source1 = create_credential_source(&mut env);
let mut credential_source2 = create_credential_source(&mut env);
credential_source2.user_handle = vec![0x02];
@@ -664,7 +691,7 @@ mod test {
storage::store_credential(&mut env, credential_source1).unwrap();
storage::store_credential(&mut env, credential_source2).unwrap();
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xF8, 0xB0, 0x3C, 0xC1, 0xD5, 0x58, 0x9C, 0xB7, 0x4D, 0x42, 0xA1, 0x64, 0x14, 0x28,
0x2B, 0x68,
@@ -746,13 +773,132 @@ mod test {
);
}
#[test]
fn test_process_enumerate_credentials_multi_pin() {
let mut env = TestEnv::new();
storage::_enable_multi_pin_for_test(&mut env).unwrap();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin = ClientPin::new_test(
&mut env,
1,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
// credential_source1 has no slot_id, so should be treated as slot 0. Only credential_source 2 and 4
// should be discovered.
let credential_source1 = create_credential_source(&mut env);
let mut credential_source2 = create_credential_source(&mut env);
credential_source2.user_handle = vec![0x02];
credential_source2.slot_id = Some(1);
let mut credential_source3 = create_credential_source(&mut env);
credential_source3.user_handle = vec![0x03];
credential_source3.slot_id = Some(2);
let mut credential_source4 = create_credential_source(&mut env);
credential_source4.user_handle = vec![0x04];
credential_source4.slot_id = Some(1);
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
ctap_state.client_pin = client_pin;
storage::store_credential(&mut env, credential_source1).unwrap();
storage::store_credential(&mut env, credential_source2).unwrap();
storage::store_credential(&mut env, credential_source3).unwrap();
storage::store_credential(&mut env, credential_source4).unwrap();
storage::set_pin(&mut env, 1, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xF8, 0xB0, 0x3C, 0xC1, 0xD5, 0x58, 0x9C, 0xB7, 0x4D, 0x42, 0xA1, 0x64, 0x14, 0x28,
0x2B, 0x68,
]);
let sub_command_params = CredentialManagementSubCommandParameters {
rp_id_hash: Some(Sha256::hash(b"example.com").to_vec()),
credential_id: None,
user: None,
};
// RP ID hash:
// A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsBegin,
sub_command_params: Some(sub_command_params),
pin_uv_auth_protocol: Some(PinUvAuthProtocol::V1),
pin_uv_auth_param,
};
let cred_management_response = process_credential_management(
&mut env,
&mut ctap_state.stateful_command_permission,
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
assert!(response.user.is_some());
assert!(response.public_key.is_some());
assert_eq!(response.total_credentials, Some(2));
}
_ => panic!("Invalid response type"),
};
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential,
sub_command_params: None,
pin_uv_auth_protocol: None,
pin_uv_auth_param: None,
};
let cred_management_response = process_credential_management(
&mut env,
&mut ctap_state.stateful_command_permission,
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
match cred_management_response.unwrap() {
ResponseData::AuthenticatorCredentialManagement(Some(response)) => {
assert!(response.user.is_some());
assert!(response.public_key.is_some());
assert_eq!(response.total_credentials, None);
}
_ => panic!("Invalid response type"),
};
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::EnumerateCredentialsGetNextCredential,
sub_command_params: None,
pin_uv_auth_protocol: None,
pin_uv_auth_param: None,
};
let cred_management_response = process_credential_management(
&mut env,
&mut ctap_state.stateful_command_permission,
&mut ctap_state.client_pin,
cred_management_params,
DUMMY_CHANNEL,
CtapInstant::new(0),
);
assert_eq!(
cred_management_response,
Err(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)
);
}
#[test]
fn test_process_delete_credential() {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut credential_source = create_credential_source(&mut env);
credential_source.credential_id = vec![0x1D; 32];
@@ -761,7 +907,7 @@ mod test {
storage::store_credential(&mut env, credential_source).unwrap();
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xBD, 0xE3, 0xEF, 0x8A, 0x77, 0x01, 0xB1, 0x69, 0x19, 0xE6, 0x62, 0xB9, 0x9B, 0x89,
0x9C, 0x64,
@@ -821,8 +967,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut credential_source = create_credential_source(&mut env);
credential_source.credential_id = vec![0x1D; 32];
@@ -831,7 +982,7 @@ mod test {
storage::store_credential(&mut env, credential_source).unwrap();
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let pin_uv_auth_param = Some(vec![
0xA5, 0x55, 0x8F, 0x03, 0xC3, 0xD3, 0x73, 0x1C, 0x07, 0xDA, 0x1F, 0x8C, 0xC7, 0xBD,
0x9D, 0xB7,
@@ -889,7 +1040,7 @@ mod test {
let mut env = TestEnv::new();
let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0));
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let cred_management_params = AuthenticatorCredentialManagementParameters {
sub_command: CredentialManagementSubCommand::GetCredsMetadata,

View File

@@ -20,8 +20,8 @@ use alloc::vec;
use alloc::vec::Vec;
use core::convert::TryFrom;
use crypto::cbc::{cbc_decrypt, cbc_encrypt};
use crypto::ecdsa;
use crypto::sha256::Sha256;
use crypto::{ecdsa, hybrid};
use rng256::Rng256;
use sk_cbor as cbor;
use sk_cbor::{cbor_array, cbor_bytes, cbor_int};
@@ -82,7 +82,6 @@ pub enum PrivateKey {
Ecdsa([u8; 32]),
#[cfg(feature = "ed25519")]
Ed25519(ed25519_compact::SecretKey),
Hybrid(hybrid::SecKey),
}
impl PrivateKey {
@@ -101,34 +100,6 @@ impl PrivateKey {
let bytes = env.rng().gen_uniform_u8x32();
Self::new_ed25519_from_bytes(&bytes).unwrap()
}
SignatureAlgorithm::Hybrid => PrivateKey::Hybrid(hybrid::SecKey::gensk(env.rng())),
SignatureAlgorithm::Unknown => unreachable!(),
}
}
/// Creates a new private / public key pair for the given algorithm.
///
/// # Panics
///
/// Panics if the algorithm is [`SignatureAlgorithm::Unknown`].
pub fn new_with_pub_key(env: &mut impl Env, alg: SignatureAlgorithm) -> (Self, CoseKey) {
match alg {
SignatureAlgorithm::Es256 => {
let private_key = PrivateKey::Ecdsa(env.key_store().generate_ecdsa_seed().unwrap());
let pub_key = private_key.get_pub_key(env).unwrap();
(private_key, pub_key)
}
#[cfg(feature = "ed25519")]
SignatureAlgorithm::Eddsa => {
let bytes = env.rng().gen_uniform_u8x32();
let private_key = Self::new_ed25519_from_bytes(&bytes).unwrap();
let pub_key = private_key.get_pub_key(env).unwrap();
(private_key, pub_key)
}
SignatureAlgorithm::Hybrid => {
let (hybrid_key, pub_key) = hybrid::SecKey::gensk_with_pk(env.rng());
(PrivateKey::Hybrid(hybrid_key), CoseKey::from(pub_key))
}
SignatureAlgorithm::Unknown => unreachable!(),
}
}
@@ -166,15 +137,6 @@ impl PrivateKey {
}
}
/// Helper function that creates a private key of type Hybrid.
fn new_hybrid_from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != hybrid::SecKey::BYTES_LENGTH {
return None;
}
hybrid::SecKey::from_bytes(array_ref!(bytes, 0, hybrid::SecKey::BYTES_LENGTH))
.map(PrivateKey::from)
}
/// Returns the corresponding public key.
pub fn get_pub_key(&self, env: &mut impl Env) -> Result<CoseKey, Ctap2StatusCode> {
Ok(match self {
@@ -183,7 +145,6 @@ impl PrivateKey {
}
#[cfg(feature = "ed25519")]
PrivateKey::Ed25519(ed25519_key) => CoseKey::from(ed25519_key.public_key()),
PrivateKey::Hybrid(hybrid_key) => CoseKey::from(hybrid_key.genpk()),
})
}
@@ -199,9 +160,6 @@ impl PrivateKey {
.to_asn1_der(),
#[cfg(feature = "ed25519")]
PrivateKey::Ed25519(ed25519_key) => ed25519_key.sign(message, None).to_vec(),
PrivateKey::Hybrid(hybrid_key) => {
hybrid_key.sign_rfc6979::<Sha256>(message).to_asn1_der()
}
})
}
@@ -211,7 +169,6 @@ impl PrivateKey {
PrivateKey::Ecdsa(_) => SignatureAlgorithm::Es256,
#[cfg(feature = "ed25519")]
PrivateKey::Ed25519(_) => SignatureAlgorithm::Eddsa,
PrivateKey::Hybrid(_) => SignatureAlgorithm::Hybrid,
}
}
@@ -221,11 +178,6 @@ impl PrivateKey {
PrivateKey::Ecdsa(ecdsa_seed) => ecdsa_seed.to_vec(),
#[cfg(feature = "ed25519")]
PrivateKey::Ed25519(ed25519_key) => ed25519_key.seed().to_vec(),
PrivateKey::Hybrid(hybrid_key) => {
let mut key_bytes = vec![0u8; hybrid::SecKey::BYTES_LENGTH];
hybrid_key.to_bytes(array_mut_ref!(key_bytes, 0, hybrid::SecKey::BYTES_LENGTH));
key_bytes
}
}
}
}
@@ -262,19 +214,11 @@ impl TryFrom<cbor::Value> for PrivateKey {
#[cfg(feature = "ed25519")]
SignatureAlgorithm::Eddsa => PrivateKey::new_ed25519_from_bytes(&key_bytes)
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
SignatureAlgorithm::Hybrid => PrivateKey::new_hybrid_from_bytes(&key_bytes)
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
_ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
}
}
}
impl From<hybrid::SecKey> for PrivateKey {
fn from(hybrid_key: hybrid::SecKey) -> Self {
PrivateKey::Hybrid(hybrid_key)
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@@ -249,7 +249,7 @@ impl Ctap1Command {
.ecdsa_key(env)
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
let pk = sk.genpk();
let key_handle = encrypt_to_credential_id(env, &private_key, &application, None, None)
let key_handle = encrypt_to_credential_id(env, &private_key, &application, None, None, 0)
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
if key_handle.len() > 0xFF {
// This is just being defensive with unreachable code.
@@ -320,11 +320,12 @@ impl Ctap1Command {
return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED);
}
ctap_state
.increment_global_signature_counter(env)
.increment_global_signature_counter(env, 0)
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
let mut signature_data = ctap_state
.generate_auth_data(
env,
0,
&application,
Ctap1Command::USER_PRESENCE_INDICATOR_BYTE,
)
@@ -498,7 +499,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
let response =
@@ -516,7 +518,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let application = [0x55; 32];
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
@@ -535,7 +538,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let mut message = create_authenticate_message(
&application,
Ctap1Flags::DontEnforceUpAndSign,
@@ -573,7 +577,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[0] = 0xEE;
@@ -593,7 +598,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[1] = 0xEE;
@@ -613,7 +619,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let mut message =
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
message[2] = 0xEE;
@@ -641,7 +648,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
@@ -651,7 +659,7 @@ mod test {
Ctap1Command::process_command(&mut env, &message, &mut ctap_state, CtapInstant::new(0))
.unwrap();
assert_eq!(response[0], 0x01);
let global_signature_counter = storage::global_signature_counter(&mut env).unwrap();
let global_signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
check_signature_counter(
&mut env,
array_ref!(response, 1, 4),
@@ -669,7 +677,8 @@ mod test {
let rp_id = "example.com";
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
let key_handle =
encrypt_to_credential_id(&mut env, &sk, &application, None, None, 0).unwrap();
let message = create_authenticate_message(
&application,
Ctap1Flags::DontEnforceUpAndSign,
@@ -684,7 +693,7 @@ mod test {
)
.unwrap();
assert_eq!(response[0], 0x01);
let global_signature_counter = storage::global_signature_counter(&mut env).unwrap();
let global_signature_counter = storage::global_signature_counter(&mut env, 0).unwrap();
check_signature_counter(
&mut env,
array_ref!(response, 1, 4),

View File

@@ -15,26 +15,21 @@
use super::crypto_wrapper::PrivateKey;
use super::status_code::Ctap2StatusCode;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
#[cfg(feature = "fuzz")]
use arbitrary::Arbitrary;
use arrayref::array_ref;
use core::convert::TryFrom;
use crypto::{ecdh, ecdsa, hybrid};
use crypto::{ecdh, ecdsa};
#[cfg(test)]
use enum_iterator::IntoEnumIterator;
use sk_cbor::{cbor_array_vec, cbor_bytes, cbor_map_options, destructure_cbor_map};
use {dilithium, sk_cbor as cbor};
use sk_cbor as cbor;
use sk_cbor::{cbor_array_vec, cbor_map, cbor_map_options, destructure_cbor_map};
// Used as the identifier for ECDSA in assertion signatures and COSE.
pub const ES256_ALGORITHM: i64 = -7;
#[cfg(feature = "ed25519")]
pub const EDDSA_ALGORITHM: i64 = -8;
// Used as the identifier for Hybrid in assertion signatures.
// (numbers less than -65536 are reserved for private use)
// TODO: Update this number later.
pub const HYBRID_ALGORITHM: i64 = -65537;
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -515,7 +510,6 @@ pub enum SignatureAlgorithm {
Es256 = ES256_ALGORITHM as isize,
#[cfg(feature = "ed25519")]
Eddsa = EDDSA_ALGORITHM as isize,
Hybrid = HYBRID_ALGORITHM as isize,
// This is the default for all numbers not covered above.
// Unknown types should be ignored, instead of returning errors.
Unknown = 0,
@@ -533,7 +527,6 @@ impl From<i64> for SignatureAlgorithm {
ES256_ALGORITHM => SignatureAlgorithm::Es256,
#[cfg(feature = "ed25519")]
EDDSA_ALGORITHM => SignatureAlgorithm::Eddsa,
HYBRID_ALGORITHM => SignatureAlgorithm::Hybrid,
_ => SignatureAlgorithm::Unknown,
}
}
@@ -602,6 +595,7 @@ pub struct PublicKeyCredentialSource {
pub user_icon: Option<String>,
pub cred_blob: Option<Vec<u8>>,
pub large_blob_key: Option<Vec<u8>>,
pub slot_id: Option<usize>,
}
// We serialize credentials for the persistent storage using CBOR maps. Each field of a credential
@@ -620,6 +614,7 @@ enum PublicKeyCredentialSourceField {
CredBlob = 10,
LargeBlobKey = 11,
PrivateKey = 12,
SlotId = 13,
// When a field is removed, its tag should be reserved and not used for new fields. We document
// those reserved tags below.
// Reserved tags:
@@ -646,6 +641,7 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
PublicKeyCredentialSourceField::CredBlob => credential.cred_blob,
PublicKeyCredentialSourceField::LargeBlobKey => credential.large_blob_key,
PublicKeyCredentialSourceField::PrivateKey => credential.private_key,
PublicKeyCredentialSourceField::SlotId => credential.slot_id.map(|x| x as u64),
}
}
}
@@ -668,6 +664,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
PublicKeyCredentialSourceField::CredBlob => cred_blob,
PublicKeyCredentialSourceField::LargeBlobKey => large_blob_key,
PublicKeyCredentialSourceField::PrivateKey => private_key,
PublicKeyCredentialSourceField::SlotId => slot_id,
} = extract_map(cbor_value)?;
}
@@ -694,6 +691,12 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
(None, Some(k)) => k,
};
let slot_id = match slot_id.map(extract_unsigned).transpose()? {
Some(x) => Some(
usize::try_from(x).map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
),
None => None,
};
// We don't return whether there were unknown fields in the CBOR value. This means that
// deserialization is not injective. In particular deserialization is only an inverse of
@@ -718,6 +721,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
user_icon,
cred_blob,
large_blob_key,
slot_id,
})
}
}
@@ -740,7 +744,6 @@ pub struct CoseKey {
algorithm: i64,
key_type: i64,
curve: i64,
dilithium_bytes: Option<Vec<u8>>,
}
impl CoseKey {
@@ -752,8 +755,6 @@ impl CoseKey {
const EC2_KEY_TYPE: i64 = 2;
#[cfg(feature = "ed25519")]
const OKP_KEY_TYPE: i64 = 1;
// The key type changes for hybrid. The value is made up.
const HYBRID_KEY_TYPE: i64 = -65537;
// The parameter behind map key -1.
const P_256_CURVE: i64 = 1;
#[cfg(feature = "ed25519")]
@@ -773,7 +774,6 @@ impl TryFrom<cbor::Value> for CoseKey {
-1 => curve,
-2 => x_bytes,
-3 => y_bytes,
-4 => dilithium_bytes,
} = extract_map(cbor_value)?;
}
@@ -796,30 +796,16 @@ impl TryFrom<cbor::Value> for CoseKey {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let key_type = extract_integer(ok_or_missing(key_type)?)?;
if key_type != CoseKey::EC2_KEY_TYPE && key_type != CoseKey::HYBRID_KEY_TYPE {
if key_type != CoseKey::EC2_KEY_TYPE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let parsed_dilithium_bytes = if key_type == CoseKey::EC2_KEY_TYPE {
if dilithium_bytes.is_some() {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
None
} else {
let dilithium_bytes = extract_byte_string(ok_or_missing(dilithium_bytes)?)?;
if dilithium_bytes.len() != dilithium::params::PK_SIZE_PACKED {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
Some(dilithium_bytes)
};
Ok(CoseKey {
x_bytes: *array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES],
y_bytes: *array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES],
algorithm,
key_type,
curve,
dilithium_bytes: parsed_dilithium_bytes,
})
}
}
@@ -832,16 +818,14 @@ impl From<CoseKey> for cbor::Value {
algorithm,
key_type,
curve,
dilithium_bytes,
} = cose_key;
cbor_map_options! {
1 => Some(key_type),
3 => Some(algorithm),
-1 => Some(curve),
-2 => Some(cbor_bytes!(x_bytes.to_vec())),
-3 => Some(cbor_bytes!(y_bytes.to_vec())),
-4 => dilithium_bytes.map(|b| cbor_bytes!(b)),
cbor_map! {
1 => key_type,
3 => algorithm,
-1 => curve,
-2 => x_bytes,
-3 => y_bytes,
}
}
}
@@ -857,15 +841,14 @@ impl From<ecdh::PubKey> for CoseKey {
algorithm: CoseKey::ECDH_ALGORITHM,
key_type: CoseKey::EC2_KEY_TYPE,
curve: CoseKey::P_256_CURVE,
dilithium_bytes: None,
}
}
}
impl From<ecdsa::PubKey> for CoseKey {
fn from(pk: ecdsa::PubKey) -> Self {
let mut x_bytes = [0; ecdsa::NBYTES];
let mut y_bytes = [0; ecdsa::NBYTES];
let mut x_bytes = [0; ecdh::NBYTES];
let mut y_bytes = [0; ecdh::NBYTES];
pk.to_coordinates(&mut x_bytes, &mut y_bytes);
CoseKey {
x_bytes,
@@ -873,7 +856,6 @@ impl From<ecdsa::PubKey> for CoseKey {
algorithm: ES256_ALGORITHM,
key_type: CoseKey::EC2_KEY_TYPE,
curve: CoseKey::P_256_CURVE,
dilithium_bytes: None,
}
}
}
@@ -887,27 +869,6 @@ impl From<ed25519_compact::PublicKey> for CoseKey {
key_type: CoseKey::OKP_KEY_TYPE,
curve: CoseKey::ED25519_CURVE,
algorithm: EDDSA_ALGORITHM,
dilithium_bytes: None,
}
}
}
impl From<hybrid::PubKey> for CoseKey {
fn from(pk: hybrid::PubKey) -> Self {
let mut ecdsa_x_bytes = [0; ecdsa::NBYTES];
let mut ecdsa_y_bytes = [0; ecdsa::NBYTES];
pk.ecdsa_pk
.to_coordinates(&mut ecdsa_x_bytes, &mut ecdsa_y_bytes);
let mut dilithium_bytes = vec![0; dilithium::params::PK_SIZE_PACKED];
let bytes_ref = array_mut_ref!(dilithium_bytes, 0, dilithium::params::PK_SIZE_PACKED);
pk.dilithium_pk.to_bytes(bytes_ref);
CoseKey {
x_bytes: ecdsa_x_bytes,
y_bytes: ecdsa_y_bytes,
key_type: CoseKey::EC2_KEY_TYPE,
curve: CoseKey::P_256_CURVE,
algorithm: ES256_ALGORITHM,
dilithium_bytes: Some(dilithium_bytes),
}
}
}
@@ -922,7 +883,6 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
algorithm,
key_type,
curve,
dilithium_bytes,
} = cose_key;
// Since algorithm can be used for different COSE key types, we check
@@ -935,9 +895,6 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
if key_type != CoseKey::EC2_KEY_TYPE || curve != CoseKey::P_256_CURVE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
if dilithium_bytes.is_some() {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
ecdh::PubKey::from_coordinates(&x_bytes, &y_bytes)
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
}
@@ -953,7 +910,6 @@ impl TryFrom<CoseKey> for ecdsa::PubKey {
algorithm,
key_type,
curve,
dilithium_bytes,
} = cose_key;
if algorithm != ES256_ALGORITHM
@@ -962,14 +918,58 @@ impl TryFrom<CoseKey> for ecdsa::PubKey {
{
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
if dilithium_bytes.is_some() {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
ecdsa::PubKey::from_coordinates(&x_bytes, &y_bytes)
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
}
}
/// Data structure for receiving a signature.
///
/// See https://datatracker.ietf.org/doc/html/rfc8152#appendix-C.1.1 for reference.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CoseSignature {
pub algorithm: SignatureAlgorithm,
pub bytes: [u8; ecdsa::Signature::BYTES_LENGTH],
}
impl TryFrom<cbor::Value> for CoseSignature {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
destructure_cbor_map! {
let {
"alg" => algorithm,
"signature" => bytes,
} = extract_map(cbor_value)?;
}
let algorithm = SignatureAlgorithm::try_from(ok_or_missing(algorithm)?)?;
let bytes = extract_byte_string(ok_or_missing(bytes)?)?;
if bytes.len() != ecdsa::Signature::BYTES_LENGTH {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
}
Ok(CoseSignature {
algorithm,
bytes: *array_ref![bytes.as_slice(), 0, ecdsa::Signature::BYTES_LENGTH],
})
}
}
impl TryFrom<CoseSignature> for ecdsa::Signature {
type Error = Ctap2StatusCode;
fn try_from(cose_signature: CoseSignature) -> Result<Self, Ctap2StatusCode> {
match cose_signature.algorithm {
SignatureAlgorithm::Es256 => ecdsa::Signature::from_bytes(&cose_signature.bytes)
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
#[cfg(feature = "ed25519")]
SignatureAlgorithm::Eddsa => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
SignatureAlgorithm::Unknown => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub enum PinUvAuthProtocol {
@@ -1300,9 +1300,10 @@ mod test {
use super::*;
use crate::env::test::TestEnv;
use cbor::{
cbor_array, cbor_bool, cbor_bytes_lit, cbor_false, cbor_int, cbor_map, cbor_null,
cbor_array, cbor_bool, cbor_bytes, cbor_bytes_lit, cbor_false, cbor_int, cbor_null,
cbor_text, cbor_unsigned,
};
use crypto::sha256::Sha256;
use rng256::Rng256;
#[test]
@@ -2013,6 +2014,64 @@ mod test {
assert_eq!(cose_key.algorithm, ES256_ALGORITHM);
}
#[test]
fn test_from_into_cose_signature() {
let mut env = TestEnv::new();
let sk = crypto::ecdsa::SecKey::gensk(env.rng());
let dummy_signature = sk.sign_rfc6979::<Sha256>(&[]);
let mut bytes = [0; ecdsa::Signature::BYTES_LENGTH];
dummy_signature.to_bytes(&mut bytes);
let cbor_value = cbor_map! {
"alg" => ES256_ALGORITHM,
"signature" => bytes,
};
let cose_signature = CoseSignature::try_from(cbor_value).unwrap();
let created_signature = crypto::ecdsa::Signature::try_from(cose_signature).unwrap();
let mut created_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
created_signature.to_bytes(&mut created_bytes);
assert_eq!(bytes[..], created_bytes[..]);
}
#[test]
fn test_cose_signature_wrong_algorithm() {
let mut env = TestEnv::new();
let sk = crypto::ecdsa::SecKey::gensk(env.rng());
let dummy_signature = sk.sign_rfc6979::<Sha256>(&[]);
let mut bytes = [0; ecdsa::Signature::BYTES_LENGTH];
dummy_signature.to_bytes(&mut bytes);
let cbor_value = cbor_map! {
"alg" => -1, // unused algorithm
"signature" => bytes,
};
let cose_signature = CoseSignature::try_from(cbor_value).unwrap();
let created_signature = crypto::ecdsa::Signature::try_from(cose_signature);
// Can not compare directly, since ecdsa::Signature does not implement Debug.
assert_eq!(
created_signature.err(),
Some(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM)
);
}
#[test]
fn test_cose_signature_wrong_signature_length() {
let cbor_value = cbor_map! {
"alg" => ES256_ALGORITHM,
"signature" => [0; ecdsa::Signature::BYTES_LENGTH - 1],
};
assert_eq!(
CoseSignature::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
let cbor_value = cbor_map! {
"alg" => ES256_ALGORITHM,
"signature" => [0; ecdsa::Signature::BYTES_LENGTH + 1],
};
assert_eq!(
CoseSignature::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
}
#[test]
fn test_from_pin_uv_auth_protocol() {
let cbor_protocol: cbor::Value = cbor_int!(0x01);
@@ -2181,6 +2240,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
assert_eq!(
@@ -2243,6 +2303,16 @@ mod test {
..credential
};
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
Ok(credential.clone())
);
let credential = PublicKeyCredentialSource {
slot_id: Some(1),
..credential
};
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
Ok(credential)
@@ -2267,6 +2337,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
let source_cbor = cbor_map! {
@@ -2299,6 +2370,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
let source_cbor = cbor_map! {

View File

@@ -14,6 +14,9 @@
pub const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
pub const AAGUID_LENGTH: usize = 16;
pub const UPGRADE_PUBLIC_KEY_LENGTH: usize = 77;
pub const AAGUID: &[u8; AAGUID_LENGTH] =
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
pub const UPGRADE_PUBLIC_KEY: &[u8; UPGRADE_PUBLIC_KEY_LENGTH] =
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_upgrade_pubkey_cbor.bin"));

View File

@@ -87,7 +87,9 @@ impl LargeBlobs {
if offset != self.expected_next_offset {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_SEQ);
}
if storage::pin_hash(env)?.is_some() || storage::has_always_uv(env)? {
// We only check whether slot 0 has a PIN here because if multi-PIN is enabled,
// has_always_uv will always be true and the condition will always pass.
if storage::pin_hash(env, 0)?.is_some() || storage::has_always_uv(env)? {
let pin_uv_auth_param =
pin_uv_auth_param.ok_or(Ctap2StatusCode::CTAP2_ERR_PUAT_REQUIRED)?;
let pin_uv_auth_protocol =
@@ -147,8 +149,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
let large_blob = vec![
@@ -178,8 +185,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 200;
@@ -240,8 +252,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 200;
@@ -286,8 +303,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 200;
@@ -332,8 +354,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
let large_blobs_params = AuthenticatorLargeBlobsParameters {
@@ -355,8 +382,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, PinUvAuthProtocol::V1);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
PinUvAuthProtocol::V1,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 20;
@@ -383,8 +415,13 @@ mod test {
let mut env = TestEnv::new();
let key_agreement_key = crypto::ecdh::SecKey::gensk(env.rng());
let pin_uv_auth_token = [0x55; 32];
let mut client_pin =
ClientPin::new_test(key_agreement_key, pin_uv_auth_token, pin_uv_auth_protocol);
let mut client_pin = ClientPin::new_test(
&mut env,
0,
key_agreement_key,
pin_uv_auth_token,
pin_uv_auth_protocol,
);
let mut large_blobs = LargeBlobs::new();
const BLOB_LEN: usize = 20;
@@ -392,7 +429,7 @@ mod test {
let mut large_blob = vec![0x1B; DATA_LEN];
large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]);
storage::set_pin(&mut env, &[0u8; 16], 4).unwrap();
storage::set_pin(&mut env, 0, &[0u8; 16], 4).unwrap();
let mut large_blob_data = vec![0xFF; 32];
// Command constant and offset bytes.
large_blob_data.extend(&[0x0C, 0x00, 0x00, 0x00, 0x00, 0x00]);

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@ use alloc::vec::Vec;
use arrayref::array_ref;
use core::cmp;
use core::convert::TryInto;
use persistent_store::{fragment, StoreUpdate};
use persistent_store::{concat, fragment};
use rng256::Rng256;
use sk_cbor::cbor_array_vec;
@@ -237,9 +237,34 @@ pub fn new_creation_order(env: &mut impl Env) -> Result<u64, Ctap2StatusCode> {
Ok(max.unwrap_or(0).wrapping_add(1))
}
fn check_and_get_key_for_slot(
env: &mut impl Env,
slot_id: usize,
first_key: usize,
key_array: core::ops::Range<usize>,
) -> Result<usize, Ctap2StatusCode> {
if slot_id >= env.customization().slot_count() {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(if slot_id == 0 {
first_key
} else {
key_array.start + slot_id - 1
})
}
/// Returns the global signature counter.
pub fn global_signature_counter(env: &mut impl Env) -> Result<u32, Ctap2StatusCode> {
match env.store().find(key::GLOBAL_SIGNATURE_COUNTER)? {
pub fn global_signature_counter(
env: &mut impl Env,
slot_id: usize,
) -> Result<u32, Ctap2StatusCode> {
let key = check_and_get_key_for_slot(
env,
slot_id,
key::FIRST_GLOBAL_SIGNATURE_COUNTER,
key::GLOBAL_SIGNATURE_COUNTER,
)?;
match env.store().find(key)? {
None => Ok(INITIAL_SIGNATURE_COUNTER),
Some(value) if value.len() == 4 => Ok(u32::from_ne_bytes(*array_ref!(&value, 0, 4))),
Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
@@ -249,13 +274,19 @@ pub fn global_signature_counter(env: &mut impl Env) -> Result<u32, Ctap2StatusCo
/// Increments the global signature counter.
pub fn incr_global_signature_counter(
env: &mut impl Env,
slot_id: usize,
increment: u32,
) -> Result<(), Ctap2StatusCode> {
let old_value = global_signature_counter(env)?;
let key = check_and_get_key_for_slot(
env,
slot_id,
key::FIRST_GLOBAL_SIGNATURE_COUNTER,
key::GLOBAL_SIGNATURE_COUNTER,
)?;
let old_value = global_signature_counter(env, slot_id)?;
// In hopes that servers handle the wrapping gracefully.
let new_value = old_value.wrapping_add(increment);
env.store()
.insert(key::GLOBAL_SIGNATURE_COUNTER, &new_value.to_ne_bytes())?;
env.store().insert(key, &new_value.to_ne_bytes())?;
Ok(())
}
@@ -273,9 +304,22 @@ pub fn cred_random_secret(env: &mut impl Env, has_uv: bool) -> Result<[u8; 32],
}
/// Reads the PIN properties and wraps them into PinProperties.
fn pin_properties(env: &mut impl Env) -> Result<Option<PinProperties>, Ctap2StatusCode> {
let pin_properties = match env.store().find(key::PIN_PROPERTIES)? {
None => return Ok(None),
fn pin_properties(
env: &mut impl Env,
slot_id: usize,
) -> Result<Option<PinProperties>, Ctap2StatusCode> {
let pin_properties = match concat::read(env.store(), key::PIN_PROPERTIES, slot_id as u8)? {
None => {
// Backward compatibility: old implementation where there is only 1 PIN slot
// uses the entry with key `FIRST_PIN_PROPERTIES`.
if slot_id != 0 {
return Ok(None);
}
match env.store().find(key::FIRST_PIN_PROPERTIES)? {
None => return Ok(None),
Some(pin_properties) => pin_properties,
}
}
Some(pin_properties) => pin_properties,
};
const PROPERTIES_LENGTH: usize = PIN_AUTH_LENGTH + 1;
@@ -288,14 +332,30 @@ fn pin_properties(env: &mut impl Env) -> Result<Option<PinProperties>, Ctap2Stat
}
}
/// Returns if PIN is set for at least one slot.
pub fn has_pin(env: &mut impl Env) -> Result<bool, Ctap2StatusCode> {
for slot_id in 0..env.customization().slot_count() {
if pin_hash(env, slot_id)?.is_some() {
return Ok(true);
}
}
Ok(false)
}
/// Returns the PIN hash if defined.
pub fn pin_hash(env: &mut impl Env) -> Result<Option<[u8; PIN_AUTH_LENGTH]>, Ctap2StatusCode> {
Ok(pin_properties(env)?.map(|p| p.hash))
pub fn pin_hash(
env: &mut impl Env,
slot_id: usize,
) -> Result<Option<[u8; PIN_AUTH_LENGTH]>, Ctap2StatusCode> {
Ok(pin_properties(env, slot_id)?.map(|p| p.hash))
}
/// Returns the length of the currently set PIN if defined.
pub fn pin_code_point_length(env: &mut impl Env) -> Result<Option<u8>, Ctap2StatusCode> {
Ok(pin_properties(env)?.map(|p| p.code_point_length))
pub fn pin_code_point_length(
env: &mut impl Env,
slot_id: usize,
) -> Result<Option<u8>, Ctap2StatusCode> {
Ok(pin_properties(env, slot_id)?.map(|p| p.code_point_length))
}
/// Sets the PIN hash and length.
@@ -303,26 +363,32 @@ pub fn pin_code_point_length(env: &mut impl Env) -> Result<Option<u8>, Ctap2Stat
/// If it was already defined, it is updated.
pub fn set_pin(
env: &mut impl Env,
slot_id: usize,
pin_hash: &[u8; PIN_AUTH_LENGTH],
pin_code_point_length: u8,
) -> Result<(), Ctap2StatusCode> {
concat::delete(env.store(), key::FORCE_PIN_CHANGE, slot_id as u8)?;
if slot_id == 0 {
// Backward compatibility: data might be stored in these entries for slot 0 as well.
env.store().remove(key::FIRST_FORCE_PIN_CHANGE)?;
env.store().remove(key::FIRST_PIN_PROPERTIES)?;
}
let mut pin_properties = [0; 1 + PIN_AUTH_LENGTH];
pin_properties[0] = pin_code_point_length;
pin_properties[1..].clone_from_slice(pin_hash);
Ok(env.store().transaction(&[
StoreUpdate::Insert {
key: key::PIN_PROPERTIES,
value: &pin_properties[..],
},
StoreUpdate::Remove {
key: key::FORCE_PIN_CHANGE,
},
])?)
concat::write(
env.store(),
key::PIN_PROPERTIES,
slot_id as u8,
&pin_properties[..],
)?;
Ok(())
}
/// Returns the number of remaining PIN retries.
pub fn pin_retries(env: &mut impl Env) -> Result<u8, Ctap2StatusCode> {
match env.store().find(key::PIN_RETRIES)? {
pub fn pin_retries(env: &mut impl Env, slot_id: usize) -> Result<u8, Ctap2StatusCode> {
let key = check_and_get_key_for_slot(env, slot_id, key::FIRST_PIN_RETRIES, key::PIN_RETRIES)?;
match env.store().find(key)? {
None => Ok(env.customization().max_pin_retries()),
Some(value) if value.len() == 1 => Ok(value[0]),
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
@@ -330,18 +396,20 @@ pub fn pin_retries(env: &mut impl Env) -> Result<u8, Ctap2StatusCode> {
}
/// Decrements the number of remaining PIN retries.
pub fn decr_pin_retries(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
let old_value = pin_retries(env)?;
pub fn decr_pin_retries(env: &mut impl Env, slot_id: usize) -> Result<(), Ctap2StatusCode> {
let key = check_and_get_key_for_slot(env, slot_id, key::FIRST_PIN_RETRIES, key::PIN_RETRIES)?;
let old_value = pin_retries(env, slot_id)?;
let new_value = old_value.saturating_sub(1);
if new_value != old_value {
env.store().insert(key::PIN_RETRIES, &[new_value])?;
env.store().insert(key, &[new_value])?;
}
Ok(())
}
/// Resets the number of remaining PIN retries.
pub fn reset_pin_retries(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
Ok(env.store().remove(key::PIN_RETRIES)?)
pub fn reset_pin_retries(env: &mut impl Env, slot_id: usize) -> Result<(), Ctap2StatusCode> {
let key = check_and_get_key_for_slot(env, slot_id, key::FIRST_PIN_RETRIES, key::PIN_RETRIES)?;
Ok(env.store().remove(key)?)
}
/// Returns the minimum PIN length.
@@ -467,17 +535,34 @@ pub fn reset(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
}
/// Returns whether the PIN needs to be changed before its next usage.
pub fn has_force_pin_change(env: &mut impl Env) -> Result<bool, Ctap2StatusCode> {
match env.store().find(key::FORCE_PIN_CHANGE)? {
None => Ok(false),
pub fn has_force_pin_change(env: &mut impl Env, slot_id: usize) -> Result<bool, Ctap2StatusCode> {
match concat::read(env.store(), key::FORCE_PIN_CHANGE, slot_id as u8)? {
None => {
if slot_id != 0 {
Ok(false)
} else {
// Backward compatibility: old implementation where there is only 1 PIN slot
// uses the entry with key `FIRST_FORCE_PIN_CHANGE`.
match env.store().find(key::FIRST_FORCE_PIN_CHANGE)? {
None => Ok(false),
Some(value) if value.is_empty() => Ok(true),
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
}
}
}
Some(value) if value.is_empty() => Ok(true),
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
}
}
/// Marks the PIN as outdated with respect to the new PIN policy.
pub fn force_pin_change(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
Ok(env.store().insert(key::FORCE_PIN_CHANGE, &[])?)
pub fn force_pin_change(env: &mut impl Env, slot_id: usize) -> Result<(), Ctap2StatusCode> {
Ok(concat::write(
env.store(),
key::FORCE_PIN_CHANGE,
slot_id as u8,
&[],
)?)
}
/// Returns whether enterprise attestation is enabled.
@@ -528,6 +613,25 @@ pub fn toggle_always_uv(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
}
}
/// Returns whether multi-PIN is enabled.
pub fn has_multi_pin(env: &mut impl Env) -> Result<bool, Ctap2StatusCode> {
match env.store().find(key::MULTI_PIN)? {
None => Ok(false),
Some(value) if value.is_empty() => Ok(true),
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
}
}
// TODO: Call this in config_commands after the whole multi-PIN feature is ready.
// Before that, this function only be used for testing purpose.
/// Enables multi-PIN, when disabled.
pub fn _enable_multi_pin_for_test(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
if !has_multi_pin(env)? {
env.store().insert(key::MULTI_PIN, &[])?;
}
Ok(())
}
impl From<persistent_store::StoreError> for Ctap2StatusCode {
fn from(error: persistent_store::StoreError) -> Ctap2StatusCode {
use persistent_store::StoreError;
@@ -651,6 +755,7 @@ mod test {
CredentialProtectionPolicy, PublicKeyCredentialSource, PublicKeyCredentialType,
};
use crate::env::test::TestEnv;
use persistent_store::StoreUpdate;
use rng256::Rng256;
fn create_credential_source(
@@ -672,6 +777,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
}
}
@@ -868,6 +974,7 @@ mod test {
user_icon: None,
cred_blob: None,
large_blob_key: None,
slot_id: None,
};
assert_eq!(found_credential, Some(expected_credential));
}
@@ -899,8 +1006,8 @@ mod test {
let mut env = TestEnv::new();
// Pin hash is initially not set.
assert!(pin_hash(&mut env).unwrap().is_none());
assert!(pin_code_point_length(&mut env).unwrap().is_none());
assert!(pin_hash(&mut env, 0).unwrap().is_none());
assert!(pin_code_point_length(&mut env, 0).unwrap().is_none());
// Setting the pin sets the pin hash.
let random_data = env.rng().gen_uniform_u8x32();
@@ -909,17 +1016,131 @@ mod test {
let pin_hash_2 = *array_ref!(random_data, PIN_AUTH_LENGTH, PIN_AUTH_LENGTH);
let pin_length_1 = 4;
let pin_length_2 = 63;
set_pin(&mut env, &pin_hash_1, pin_length_1).unwrap();
assert_eq!(pin_hash(&mut env).unwrap(), Some(pin_hash_1));
assert_eq!(pin_code_point_length(&mut env).unwrap(), Some(pin_length_1));
set_pin(&mut env, &pin_hash_2, pin_length_2).unwrap();
assert_eq!(pin_hash(&mut env).unwrap(), Some(pin_hash_2));
assert_eq!(pin_code_point_length(&mut env).unwrap(), Some(pin_length_2));
set_pin(&mut env, 0, &pin_hash_1, pin_length_1).unwrap();
assert_eq!(pin_hash(&mut env, 0).unwrap(), Some(pin_hash_1));
assert_eq!(
pin_code_point_length(&mut env, 0).unwrap(),
Some(pin_length_1)
);
set_pin(&mut env, 0, &pin_hash_2, pin_length_2).unwrap();
assert_eq!(pin_hash(&mut env, 0).unwrap(), Some(pin_hash_2));
assert_eq!(
pin_code_point_length(&mut env, 0).unwrap(),
Some(pin_length_2)
);
// Resetting the storage resets the pin hash.
reset(&mut env).unwrap();
assert!(pin_hash(&mut env).unwrap().is_none());
assert!(pin_code_point_length(&mut env).unwrap().is_none());
assert!(pin_hash(&mut env, 0).unwrap().is_none());
assert!(pin_code_point_length(&mut env, 0).unwrap().is_none());
}
#[test]
fn test_pin_hash_and_length_multi_pin() {
let mut env = TestEnv::new();
for i in 0..env.customization().slot_count() {
// Pin hash is initially not set.
assert!(pin_hash(&mut env, i).unwrap().is_none());
assert!(pin_code_point_length(&mut env, i).unwrap().is_none());
}
// Setting the pin sets the pin hash.
let random_data = env.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);
let pin_length_1 = 4;
let pin_length_2 = 63;
set_pin(&mut env, 1, &pin_hash_1, pin_length_1).unwrap();
assert_eq!(pin_hash(&mut env, 1).unwrap(), Some(pin_hash_1));
assert_eq!(
pin_code_point_length(&mut env, 1).unwrap(),
Some(pin_length_1)
);
// Other slots aren't affected.
assert!(pin_hash(&mut env, 0).unwrap().is_none());
assert!(pin_code_point_length(&mut env, 0).unwrap().is_none());
set_pin(&mut env, 1, &pin_hash_2, pin_length_2).unwrap();
assert_eq!(pin_hash(&mut env, 1).unwrap(), Some(pin_hash_2));
assert_eq!(
pin_code_point_length(&mut env, 1).unwrap(),
Some(pin_length_2)
);
// Other slots aren't affected.
assert!(pin_hash(&mut env, 0).unwrap().is_none());
assert!(pin_code_point_length(&mut env, 0).unwrap().is_none());
// Set PIN for multiple slots is supported.
set_pin(&mut env, 2, &pin_hash_1, pin_length_1).unwrap();
assert_eq!(pin_hash(&mut env, 2).unwrap(), Some(pin_hash_1));
assert_eq!(
pin_code_point_length(&mut env, 2).unwrap(),
Some(pin_length_1)
);
// Resetting the storage resets the pin hash.
reset(&mut env).unwrap();
for i in 0..env.customization().slot_count() {
assert!(pin_hash(&mut env, i).unwrap().is_none());
assert!(pin_code_point_length(&mut env, i).unwrap().is_none());
}
}
fn set_pin_legacy(
env: &mut impl Env,
pin_hash: &[u8; PIN_AUTH_LENGTH],
pin_code_point_length: u8,
) -> Result<(), Ctap2StatusCode> {
let mut pin_properties = [0; 1 + PIN_AUTH_LENGTH];
pin_properties[0] = pin_code_point_length;
pin_properties[1..].clone_from_slice(pin_hash);
Ok(env.store().transaction(&[
StoreUpdate::Insert {
key: key::FIRST_PIN_PROPERTIES,
value: &pin_properties[..],
},
StoreUpdate::Remove {
key: key::FIRST_FORCE_PIN_CHANGE,
},
])?)
}
#[test]
fn test_pin_hash_and_length_backward_compat() {
let mut env = TestEnv::new();
// Setting the pin sets the pin hash.
let random_data = env.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);
let pin_length_1 = 4;
let pin_length_2 = 63;
assert!(set_pin_legacy(&mut env, &pin_hash_1, pin_length_1).is_ok());
// Should fallback to read from legacy storage location successfully.
assert_eq!(pin_hash(&mut env, 0).unwrap(), Some(pin_hash_1));
assert_eq!(
pin_code_point_length(&mut env, 0).unwrap(),
Some(pin_length_1)
);
// Fallback logic should only apply to slot 0.
assert!(pin_hash(&mut env, 1).unwrap().is_none());
assert!(pin_code_point_length(&mut env, 1).unwrap().is_none());
// Setting PIN again should use the new storage location, and erase the old one.
set_pin(&mut env, 0, &pin_hash_2, pin_length_2).unwrap();
assert_eq!(pin_hash(&mut env, 0).unwrap(), Some(pin_hash_2));
assert_eq!(
pin_code_point_length(&mut env, 0).unwrap(),
Some(pin_length_2)
);
assert_eq!(env.store().find(key::FIRST_PIN_PROPERTIES), Ok(None));
assert_eq!(env.store().find(key::FIRST_FORCE_PIN_CHANGE), Ok(None));
}
#[test]
@@ -928,24 +1149,76 @@ mod test {
// The pin retries is initially at the maximum.
assert_eq!(
pin_retries(&mut env),
pin_retries(&mut env, 0),
Ok(env.customization().max_pin_retries())
);
// Decrementing the pin retries decrements the pin retries.
for retries in (0..env.customization().max_pin_retries()).rev() {
decr_pin_retries(&mut env).unwrap();
assert_eq!(pin_retries(&mut env), Ok(retries));
decr_pin_retries(&mut env, 0).unwrap();
assert_eq!(pin_retries(&mut env, 0), Ok(retries));
}
// Decrementing the pin retries after zero does not modify the pin retries.
decr_pin_retries(&mut env).unwrap();
assert_eq!(pin_retries(&mut env), Ok(0));
decr_pin_retries(&mut env, 0).unwrap();
assert_eq!(pin_retries(&mut env, 0), Ok(0));
// Resetting the pin retries resets the pin retries.
reset_pin_retries(&mut env).unwrap();
reset_pin_retries(&mut env, 0).unwrap();
assert_eq!(
pin_retries(&mut env),
pin_retries(&mut env, 0),
Ok(env.customization().max_pin_retries())
);
}
#[test]
fn test_pin_retries_multi_pin() {
let mut env = TestEnv::new();
for i in 0..env.customization().slot_count() {
// The pin retries is initially at the maximum.
assert_eq!(
pin_retries(&mut env, i),
Ok(env.customization().max_pin_retries())
);
}
// Decrementing the pin retries decrements the pin retries.
for retries in (0..env.customization().max_pin_retries()).rev() {
decr_pin_retries(&mut env, 1).unwrap();
assert_eq!(pin_retries(&mut env, 1), Ok(retries));
// Other slots shouldn't be affected.
assert_eq!(
pin_retries(&mut env, 2),
Ok(env.customization().max_pin_retries())
);
}
// Decrementing the pin retries after zero does not modify the pin retries.
decr_pin_retries(&mut env, 1).unwrap();
assert_eq!(pin_retries(&mut env, 1), Ok(0));
// Operating on multiple slots is supported.
decr_pin_retries(&mut env, 2).unwrap();
assert_eq!(
pin_retries(&mut env, 2),
Ok(env.customization().max_pin_retries() - 1)
);
// Resetting the pin retries resets the pin retries.
reset_pin_retries(&mut env, 1).unwrap();
assert_eq!(
pin_retries(&mut env, 1),
Ok(env.customization().max_pin_retries())
);
// Other slots shouldn't be affected.
assert_eq!(
pin_retries(&mut env, 2),
Ok(env.customization().max_pin_retries() - 1)
);
assert_eq!(
pin_retries(&mut env, 3),
Ok(env.customization().max_pin_retries())
);
}
@@ -1088,11 +1361,56 @@ mod test {
let mut env = TestEnv::new();
let mut counter_value = 1;
assert_eq!(global_signature_counter(&mut env).unwrap(), counter_value);
assert_eq!(
global_signature_counter(&mut env, 0).unwrap(),
counter_value
);
for increment in 1..10 {
assert!(incr_global_signature_counter(&mut env, increment).is_ok());
assert!(incr_global_signature_counter(&mut env, 0, increment).is_ok());
counter_value += increment;
assert_eq!(global_signature_counter(&mut env).unwrap(), counter_value);
assert_eq!(
global_signature_counter(&mut env, 0).unwrap(),
counter_value
);
}
}
#[test]
fn test_global_signature_counter_multi_pin() {
let mut env = TestEnv::new();
let mut counter_value = 1;
for i in 0..env.customization().slot_count() {
assert_eq!(
global_signature_counter(&mut env, i).unwrap(),
counter_value
);
}
for increment in 1..10 {
assert!(incr_global_signature_counter(&mut env, 1, increment).is_ok());
counter_value += increment;
assert_eq!(
global_signature_counter(&mut env, 1).unwrap(),
counter_value
);
// Other slots aren't affected.
assert_eq!(global_signature_counter(&mut env, 2).unwrap(), 1);
}
let counter_value_for_slot_1 = counter_value;
counter_value = 1;
for increment in 1..10 {
assert!(incr_global_signature_counter(&mut env, 2, increment).is_ok());
counter_value += increment;
assert_eq!(
global_signature_counter(&mut env, 2).unwrap(),
counter_value
);
// Other slots aren't affected.
assert_eq!(
global_signature_counter(&mut env, 1).unwrap(),
counter_value_for_slot_1
);
}
}
@@ -1100,11 +1418,60 @@ mod test {
fn test_force_pin_change() {
let mut env = TestEnv::new();
assert!(!has_force_pin_change(&mut env).unwrap());
assert_eq!(force_pin_change(&mut env), Ok(()));
assert!(has_force_pin_change(&mut env).unwrap());
assert_eq!(set_pin(&mut env, &[0x88; 16], 8), Ok(()));
assert!(!has_force_pin_change(&mut env).unwrap());
assert!(!has_force_pin_change(&mut env, 0).unwrap());
assert_eq!(force_pin_change(&mut env, 0), Ok(()));
assert!(has_force_pin_change(&mut env, 0).unwrap());
assert_eq!(set_pin(&mut env, 0, &[0x88; 16], 8), Ok(()));
assert!(!has_force_pin_change(&mut env, 0).unwrap());
}
#[test]
fn test_force_pin_change_multi_pin() {
let mut env = TestEnv::new();
for i in 0..env.customization().slot_count() {
assert!(!has_force_pin_change(&mut env, i).unwrap());
}
assert_eq!(force_pin_change(&mut env, 1), Ok(()));
assert!(has_force_pin_change(&mut env, 1).unwrap());
// Other slots shouldn't be affected.
assert!(!has_force_pin_change(&mut env, 2).unwrap());
assert_eq!(set_pin(&mut env, 1, &[0x88; 16], 8), Ok(()));
assert!(!has_force_pin_change(&mut env, 1).unwrap());
// Other slots shouldn't be affected.
assert!(!has_force_pin_change(&mut env, 2).unwrap());
// Operating on multiple slots is supported.
assert_eq!(force_pin_change(&mut env, 2), Ok(()));
assert!(has_force_pin_change(&mut env, 2).unwrap());
assert!(!has_force_pin_change(&mut env, 1).unwrap());
}
fn force_pin_change_legacy(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
Ok(env.store().insert(key::FIRST_FORCE_PIN_CHANGE, &[])?)
}
#[test]
fn test_force_pin_change_backward_compat() {
let mut env = TestEnv::new();
assert!(!has_force_pin_change(&mut env, 0).unwrap());
assert!(!has_force_pin_change(&mut env, 1).unwrap());
assert_eq!(force_pin_change_legacy(&mut env), Ok(()));
// Should fallback to read from legacy storage location successfully.
assert!(has_force_pin_change(&mut env, 0).unwrap());
// Fallback logic should only apply to slot 0.
assert!(!has_force_pin_change(&mut env, 1).unwrap());
assert_eq!(set_pin(&mut env, 0, &[0x88; 16], 8), Ok(()));
assert!(!has_force_pin_change(&mut env, 0).unwrap());
// Old storage location should be cleared.
assert_eq!(env.store().find(key::FIRST_FORCE_PIN_CHANGE), Ok(None));
assert_eq!(force_pin_change(&mut env, 0), Ok(()));
assert!(has_force_pin_change(&mut env, 0).unwrap());
}
#[test]
@@ -1145,6 +1512,15 @@ mod test {
}
}
#[test]
fn test_multi_pin() {
let mut env = TestEnv::new();
assert!(!has_multi_pin(&mut env).unwrap());
assert_eq!(_enable_multi_pin_for_test(&mut env), Ok(()));
assert!(has_multi_pin(&mut env).unwrap());
}
#[test]
fn test_serialize_deserialize_credential() {
let mut env = TestEnv::new();
@@ -1162,6 +1538,7 @@ mod test {
user_icon: Some(String::from("icon")),
cred_blob: Some(vec![0xCB]),
large_blob_key: Some(vec![0x1B]),
slot_id: Some(1),
};
let serialized = serialize_credential(credential.clone()).unwrap();
let reconstructed = deserialize_credential(&serialized).unwrap();

View File

@@ -73,6 +73,37 @@ make_partition! {
// - When adding a (non-persistent) key below this message, make sure its value is bigger or
// equal than NUM_PERSISTENT_KEYS.
/// Whether multi-PIN is enabled.
///
/// The value must be empty. Only presence of the value matters.
MULTI_PIN = 983;
// Start of key arrays for multi-PIN feature: these fields are separated for each slots, so
// a unique key is needed for each slot. However, we reuse the existing fields and rename them
// to `FIRST_{KEY_NAME}` so the upgrade is backward compatible.
// Depending on `Customization::slot_count()`, only a prefix of those keys is used.
/// Whether the PIN needs to be changed each slot.
///
/// The PIN needs to be changed if the slot exists and its data is empty.
FORCE_PIN_CHANGE = 984;
/// The number of PIN retries for each slot, except the first.
PIN_RETRIES = 985..992;
/// The PIN hash and length for each slot.
///
/// If a slot is absent, there is no PIN set for that slot. The first byte represents
/// the length, the following are an array with the hash.
PIN_PROPERTIES = 992;
/// The global signature counters for each slot, except the first.
///
/// If the entry is absent, the counter is 0.
GLOBAL_SIGNATURE_COUNTER = 993..1000;
// End of key arrays for multi-PIN feature.
/// Reserved for future credential-related objects.
///
/// In particular, additional credentials could be added there by reducing the lower bound of
@@ -97,8 +128,10 @@ make_partition! {
/// If this entry exists and is empty, enterprise attestation is enabled.
ENTERPRISE_ATTESTATION = 2039;
/// If this entry exists and is empty, the PIN needs to be changed.
FORCE_PIN_CHANGE = 2040;
/// Whether the PIN needs to be changed for the first slot.
///
/// The PIN needs to be changed if this entry exists and is empty.
FIRST_FORCE_PIN_CHANGE = 2040;
/// The secret of the CredRandom feature.
CRED_RANDOM_SECRET = 2041;
@@ -111,24 +144,22 @@ make_partition! {
/// If the entry is absent, the minimum PIN length is `Customization::default_min_pin_length()`.
MIN_PIN_LENGTH = 2043;
/// The number of PIN retries.
/// The number of PIN retries for the first slot.
///
/// If the entry is absent, the number of PIN retries is `Customization::max_pin_retries()`.
PIN_RETRIES = 2044;
FIRST_PIN_RETRIES = 2044;
/// The PIN hash and length.
/// The PIN hash and length for the first slot.
///
/// If the entry is absent, there is no PIN set. The first byte represents
/// the length, the following are an array with the hash.
PIN_PROPERTIES = 2045;
FIRST_PIN_PROPERTIES = 2045;
/// Reserved for the key store implementation of the environment.
_RESERVED_KEY_STORE = 2046;
/// The global signature counter.
///
/// If the entry is absent, the counter is 0.
GLOBAL_SIGNATURE_COUNTER = 2047;
/// The global signature counter for the first slot.
FIRST_GLOBAL_SIGNATURE_COUNTER = 2047;
}
#[cfg(test)]

View File

@@ -41,7 +41,7 @@ pub struct PinUvAuthTokenState {
permissions_rp_id: Option<String>,
usage_timer: TimedPermission,
user_verified: bool,
in_use: bool,
slot_id_in_use: Option<usize>,
}
impl PinUvAuthTokenState {
@@ -52,13 +52,18 @@ impl PinUvAuthTokenState {
permissions_rp_id: None,
usage_timer: TimedPermission::waiting(),
user_verified: false,
in_use: false,
slot_id_in_use: None,
}
}
/// Returns whether the pinUvAuthToken is active.
pub fn is_in_use(&self) -> bool {
self.in_use
self.slot_id_in_use.is_some()
}
/// Returns the slot id in use.
pub fn slot_id_in_use(&self) -> Option<usize> {
self.slot_id_in_use
}
/// Checks if the permission is granted.
@@ -113,15 +118,15 @@ impl PinUvAuthTokenState {
}
/// Starts the timer for pinUvAuthToken usage.
pub fn begin_using_pin_uv_auth_token(&mut self, now: CtapInstant) {
pub fn begin_using_pin_uv_auth_token(&mut self, now: CtapInstant, slot_id: usize) {
self.user_verified = true;
self.usage_timer = TimedPermission::granted(now, INITIAL_USAGE_TIME_LIMIT);
self.in_use = true;
self.slot_id_in_use = Some(slot_id);
}
/// Updates the usage timer, and disables the pinUvAuthToken on timeout.
pub fn pin_uv_auth_token_usage_timer_observer(&mut self, now: CtapInstant) {
if !self.in_use {
if !self.is_in_use() {
return;
}
self.usage_timer = self.usage_timer.check_expiration(now);
@@ -132,7 +137,7 @@ impl PinUvAuthTokenState {
/// Returns whether the user is verified.
pub fn get_user_verified_flag_value(&self) -> bool {
self.in_use && self.user_verified
self.is_in_use() && self.user_verified
}
/// Consumes the user verification.
@@ -151,7 +156,7 @@ impl PinUvAuthTokenState {
self.permissions_set = 0;
self.usage_timer = TimedPermission::waiting();
self.user_verified = false;
self.in_use = false;
self.slot_id_in_use = None;
}
}
@@ -164,24 +169,28 @@ mod test {
fn test_observer() {
let mut token_state = PinUvAuthTokenState::new();
let mut now: CtapInstant = CtapInstant::new(0);
token_state.begin_using_pin_uv_auth_token(now);
token_state.begin_using_pin_uv_auth_token(now, 0);
assert!(token_state.is_in_use());
assert_eq!(token_state.slot_id_in_use(), Some(0));
now = now + Milliseconds(100_u32);
token_state.pin_uv_auth_token_usage_timer_observer(now);
assert!(token_state.is_in_use());
now = now + INITIAL_USAGE_TIME_LIMIT;
token_state.pin_uv_auth_token_usage_timer_observer(now);
assert!(!token_state.is_in_use());
assert!(token_state.slot_id_in_use().is_none());
}
#[test]
fn test_stop() {
let mut token_state = PinUvAuthTokenState::new();
let now: CtapInstant = CtapInstant::new(0);
token_state.begin_using_pin_uv_auth_token(now);
token_state.begin_using_pin_uv_auth_token(now, 0);
assert!(token_state.is_in_use());
assert_eq!(token_state.slot_id_in_use(), Some(0));
token_state.stop_using_pin_uv_auth_token();
assert!(!token_state.is_in_use());
assert!(token_state.slot_id_in_use().is_none());
}
#[test]
@@ -265,11 +274,11 @@ mod test {
let mut token_state = PinUvAuthTokenState::new();
assert!(!token_state.get_user_verified_flag_value());
let now: CtapInstant = CtapInstant::new(0);
token_state.begin_using_pin_uv_auth_token(now);
token_state.begin_using_pin_uv_auth_token(now, 0);
assert!(token_state.get_user_verified_flag_value());
token_state.clear_user_verified_flag();
assert!(!token_state.get_user_verified_flag_value());
token_state.begin_using_pin_uv_auth_token(now);
token_state.begin_using_pin_uv_auth_token(now, 0);
assert!(token_state.get_user_verified_flag_value());
token_state.stop_using_pin_uv_auth_token();
assert!(!token_state.get_user_verified_flag_value());

View File

@@ -18,7 +18,6 @@ use alloc::string::String;
use alloc::vec::Vec;
pub struct TestCustomization {
allows_pin_protocol_v1: bool,
default_cred_protect: Option<CredentialProtectionPolicy>,
default_min_pin_length: u8,
default_min_pin_length_rp_ids: Vec<String>,
@@ -34,13 +33,10 @@ pub struct TestCustomization {
max_large_blob_array_size: usize,
max_rp_ids_length: usize,
max_supported_resident_keys: usize,
slot_count: usize,
}
impl TestCustomization {
pub fn set_allows_pin_protocol_v1(&mut self, is_allowed: bool) {
self.allows_pin_protocol_v1 = is_allowed;
}
pub fn setup_enterprise_attestation(
&mut self,
mode: Option<EnterpriseAttestationMode>,
@@ -54,10 +50,6 @@ impl TestCustomization {
}
impl Customization for TestCustomization {
fn allows_pin_protocol_v1(&self) -> bool {
self.allows_pin_protocol_v1
}
fn default_cred_protect(&self) -> Option<CredentialProtectionPolicy> {
self.default_cred_protect
}
@@ -121,12 +113,15 @@ impl Customization for TestCustomization {
fn max_supported_resident_keys(&self) -> usize {
self.max_supported_resident_keys
}
fn slot_count(&self) -> usize {
self.slot_count
}
}
impl From<CustomizationImpl> for TestCustomization {
fn from(c: CustomizationImpl) -> Self {
let CustomizationImpl {
allows_pin_protocol_v1,
default_cred_protect,
default_min_pin_length,
default_min_pin_length_rp_ids,
@@ -142,6 +137,7 @@ impl From<CustomizationImpl> for TestCustomization {
max_large_blob_array_size,
max_rp_ids_length,
max_supported_resident_keys,
slot_count,
} = c;
let default_min_pin_length_rp_ids = default_min_pin_length_rp_ids
@@ -155,7 +151,6 @@ impl From<CustomizationImpl> for TestCustomization {
.collect::<Vec<_>>();
Self {
allows_pin_protocol_v1,
default_cred_protect,
default_min_pin_length,
default_min_pin_length_rp_ids,
@@ -171,6 +166,7 @@ impl From<CustomizationImpl> for TestCustomization {
max_large_blob_array_size,
max_rp_ids_length,
max_supported_resident_keys,
slot_count,
}
}
}

4
src/env/test/mod.rs vendored
View File

@@ -50,10 +50,6 @@ impl TestRng256 {
}
impl Rng256 for TestRng256 {
fn fill_bytes(&mut self, buf: &mut [u8]) {
self.rng.fill(buf)
}
fn gen_uniform_u8x32(&mut self) -> [u8; 32] {
let mut result = [Default::default(); 32];
self.rng.fill(&mut result);

View File

@@ -17,22 +17,27 @@ use crate::api::upgrade_storage::UpgradeStorage;
use alloc::boxed::Box;
use persistent_store::{StorageError, StorageResult};
const PARTITION_LENGTH: usize = 0x41000;
const PARTITION_LENGTH: usize = 0x40000;
const METADATA_LENGTH: usize = 0x1000;
pub struct BufferUpgradeStorage {
/// Content of the partition storage.
partition: Box<[u8]>,
/// Content of the metadata storage.
metadata: Box<[u8]>,
}
impl BufferUpgradeStorage {
pub fn new() -> StorageResult<BufferUpgradeStorage> {
Ok(BufferUpgradeStorage {
partition: vec![0xff; PARTITION_LENGTH].into_boxed_slice(),
metadata: vec![0xff; METADATA_LENGTH].into_boxed_slice(),
})
}
}
#[cfg(test)]
impl UpgradeStorage for BufferUpgradeStorage {
fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> {
if length == 0 {
return Err(StorageError::OutOfBounds);
@@ -44,31 +49,40 @@ impl BufferUpgradeStorage {
Err(StorageError::OutOfBounds)
}
}
}
impl UpgradeStorage for BufferUpgradeStorage {
fn write_bundle(&mut self, offset: usize, data: Vec<u8>) -> StorageResult<()> {
if offset == 0 && data.len() != METADATA_LENGTH {
return Err(StorageError::OutOfBounds);
}
fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()> {
if data.is_empty() {
return Err(StorageError::OutOfBounds);
}
let partition_range = ModRange::new(0, self.partition.len());
if partition_range.contains_range(&ModRange::new(offset, data.len())) {
self.partition[offset..][..data.len()].copy_from_slice(&data);
self.partition[offset..][..data.len()].copy_from_slice(data);
Ok(())
} else {
Err(StorageError::OutOfBounds)
}
}
fn bundle_identifier(&self) -> u32 {
fn partition_address(&self) -> usize {
0x60000
}
fn running_firmware_version(&self) -> u64 {
0
fn partition_length(&self) -> usize {
PARTITION_LENGTH
}
fn read_metadata(&self) -> StorageResult<&[u8]> {
Ok(&self.metadata[..])
}
fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()> {
if data.len() <= METADATA_LENGTH {
self.metadata.copy_from_slice(&[0xff; METADATA_LENGTH]);
self.metadata[..data.len()].copy_from_slice(data);
Ok(())
} else {
Err(StorageError::OutOfBounds)
}
}
}
@@ -77,13 +91,13 @@ mod tests {
use super::*;
#[test]
fn read_write_bundle() {
fn read_write_partition() {
let mut storage = BufferUpgradeStorage::new().unwrap();
assert_eq!(storage.read_partition(0, 2).unwrap(), &[0xFF, 0xFF]);
assert!(storage.write_bundle(1, vec![0x88, 0x88]).is_ok());
assert!(storage.write_partition(1, &[0x88, 0x88]).is_ok());
assert_eq!(storage.read_partition(0, 2).unwrap(), &[0xFF, 0x88]);
assert_eq!(
storage.write_bundle(PARTITION_LENGTH - 1, vec![0x88, 0x88],),
storage.write_partition(PARTITION_LENGTH - 1, &[0x88, 0x88]),
Err(StorageError::OutOfBounds)
);
assert_eq!(
@@ -95,11 +109,11 @@ mod tests {
Err(StorageError::OutOfBounds)
);
assert_eq!(
storage.write_bundle(4, vec![]),
storage.write_partition(4, &[]),
Err(StorageError::OutOfBounds)
);
assert_eq!(
storage.write_bundle(PARTITION_LENGTH + 4, vec![]),
storage.write_partition(PARTITION_LENGTH + 4, &[]),
Err(StorageError::OutOfBounds)
);
assert_eq!(storage.read_partition(4, 0), Err(StorageError::OutOfBounds));
@@ -112,6 +126,23 @@ mod tests {
#[test]
fn partition_slice() {
let storage = BufferUpgradeStorage::new().unwrap();
assert_eq!(storage.bundle_identifier(), 0x60000);
assert_eq!(storage.partition_address(), 0x60000);
assert_eq!(storage.partition_length(), PARTITION_LENGTH);
}
#[test]
fn read_write_metadata() {
let mut storage = BufferUpgradeStorage::new().unwrap();
assert_eq!(storage.read_metadata().unwrap(), &[0xFF; METADATA_LENGTH]);
assert!(storage.write_metadata(&[0x88, 0x88]).is_ok());
assert_eq!(
storage.write_metadata(&[0x88; METADATA_LENGTH + 1]),
Err(StorageError::OutOfBounds)
);
let new_metadata = storage.read_metadata().unwrap();
assert_eq!(&new_metadata[0..2], &[0x88, 0x88]);
assert_eq!(&new_metadata[2..], &[0xFF; METADATA_LENGTH - 2]);
assert!(storage.write_metadata(&[]).is_ok());
assert_eq!(storage.read_metadata().unwrap(), &[0xFF; METADATA_LENGTH]);
}
}

2
src/env/tock/mod.rs vendored
View File

@@ -117,7 +117,7 @@ impl UserPresence for TockEnv {
}
fn wait_with_timeout(&mut self, timeout: Milliseconds<ClockInt>) -> UserPresenceResult {
if timeout.integer() == 0 {
return Ok(());
return Err(UserPresenceError::Timeout);
}
blink_leds(self.blink_pattern);
self.blink_pattern += 1;

View File

@@ -12,23 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::api::upgrade_storage::helper::{find_slice, is_aligned, ModRange, Partition};
use crate::api::upgrade_storage::helper::{find_slice, is_aligned, ModRange};
use crate::api::upgrade_storage::UpgradeStorage;
use alloc::borrow::Cow;
use alloc::vec::Vec;
use arrayref::array_ref;
use byteorder::{ByteOrder, LittleEndian};
use core::cell::Cell;
use crypto::sha256::Sha256;
use crypto::{ecdsa, Hash256};
use libtock_core::{callback, syscalls};
use persistent_store::{Storage, StorageError, StorageIndex, StorageResult};
const DRIVER_NUMBER: usize = 0x50003;
const METADATA_SIGN_OFFSET: usize = 0x800;
const UPGRADE_PUBLIC_KEY: &[u8; 65] =
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_upgrade_pubkey.bin"));
mod subscribe_nr {
pub const DONE: usize = 0;
@@ -60,6 +52,7 @@ mod memop_nr {
mod storage_type {
pub const STORE: usize = 1;
pub const PARTITION: usize = 2;
pub const METADATA: usize = 3;
}
fn get_info(nr: usize, arg: usize) -> StorageResult<usize> {
@@ -101,10 +94,6 @@ fn block_command(driver: usize, cmd: usize, arg1: usize, arg2: usize) -> Storage
}
}
unsafe fn read_slice(address: usize, length: usize) -> &'static [u8] {
core::slice::from_raw_parts(address as *const u8, length)
}
fn write_slice(ptr: usize, value: &[u8]) -> StorageResult<()> {
let code = unsafe {
syscalls::raw::allow(
@@ -231,19 +220,11 @@ impl Storage for TockStorage {
pub struct TockUpgradeStorage {
page_size: usize,
partition: Partition,
partition: ModRange,
metadata: ModRange,
running_metadata: ModRange,
identifier: u32,
}
impl TockUpgradeStorage {
// Ideally, the kernel should tell us metadata and partitions directly.
// This code only works for one layout, refactor this into the storage driver to support more.
const METADATA_ADDRESS: usize = 0x4000;
const PARTITION_ADDRESS_A: usize = 0x20000;
const PARTITION_ADDRESS_B: usize = 0x60000;
/// Provides access to the other upgrade partition and metadata if available.
///
/// The implementation assumes that storage locations returned by the kernel through
@@ -254,26 +235,24 @@ impl TockUpgradeStorage {
/// Returns `CustomError` if any of the following conditions do not hold:
/// - The page size is a power of two.
/// - The storage slices are page-aligned.
/// - There are no partition or no metadata slices.
/// - There are not partition or metadata slices.
/// Returns a `NotAligned` error if partitions or metadata ranges are
/// - not exclusive or,
/// - not consecutive.
pub fn new() -> StorageResult<TockUpgradeStorage> {
let mut locations = TockUpgradeStorage {
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
partition: Partition::new(),
partition: ModRange::new_empty(),
metadata: ModRange::new_empty(),
running_metadata: ModRange::new_empty(),
identifier: Self::PARTITION_ADDRESS_A as u32,
};
if !locations.page_size.is_power_of_two() {
return Err(StorageError::CustomError);
}
let mut firmware_range = ModRange::new_empty();
for i in 0..memop(memop_nr::STORAGE_CNT, 0)? {
let storage_type = memop(memop_nr::STORAGE_TYPE, i)?;
if !matches!(storage_type, storage_type::PARTITION) {
continue;
match storage_type {
storage_type::PARTITION | storage_type::METADATA => (),
_ => continue,
};
let storage_ptr = memop(memop_nr::STORAGE_PTR, i)?;
let storage_len = memop(memop_nr::STORAGE_LEN, i)?;
@@ -281,292 +260,91 @@ impl TockUpgradeStorage {
return Err(StorageError::CustomError);
}
let range = ModRange::new(storage_ptr, storage_len);
match range.start() {
Self::METADATA_ADDRESS => {
// Will be swapped if we are on B.
locations.metadata = ModRange::new(range.start(), locations.page_size);
locations.running_metadata =
ModRange::new(range.start() + locations.page_size, locations.page_size);
match storage_type {
storage_type::PARTITION => {
locations.partition = locations
.partition
.append(range)
.ok_or(StorageError::NotAligned)?
}
_ => {
if !firmware_range.append(&range) {
return Err(StorageError::NotAligned);
}
storage_type::METADATA => {
locations.metadata = locations
.metadata
.append(range)
.ok_or(StorageError::NotAligned)?
}
}
_ => (),
};
}
if firmware_range.is_empty()
|| locations.metadata.is_empty()
|| locations.running_metadata.is_empty()
{
return Err(StorageError::CustomError);
if locations.partition.is_empty() || locations.metadata.is_empty() {
Err(StorageError::CustomError)
} else {
Ok(locations)
}
if firmware_range.start() == Self::PARTITION_ADDRESS_B {
core::mem::swap(&mut locations.metadata, &mut locations.running_metadata);
locations.identifier = Self::PARTITION_ADDRESS_B as u32;
}
if !locations.partition.append(locations.metadata.clone()) {
return Err(StorageError::NotAligned);
}
if !locations.partition.append(firmware_range) {
return Err(StorageError::NotAligned);
}
Ok(locations)
}
fn is_page_aligned(&self, x: usize) -> bool {
is_aligned(self.page_size, x)
}
/// Returns whether the metadata is contained in this range or not.
///
/// Assumes that metadata is written in one call per range. If the metadata is only partially
/// contained, returns an error.
fn contains_metadata(&self, checked_range: &ModRange) -> StorageResult<bool> {
if checked_range.intersects_range(&self.metadata) {
if checked_range.contains_range(&self.metadata) {
Ok(true)
} else {
Err(StorageError::NotAligned)
}
} else {
Ok(false)
}
}
/// Checks if the metadata's hash matches the partition's content.
fn check_partition_hash(&self, metadata: &[u8]) -> StorageResult<()> {
let start_address = self.metadata.start() + METADATA_SIGN_OFFSET;
let mut hasher = Sha256::new();
for range in self.partition.ranges_from(start_address) {
let partition_slice = unsafe { read_slice(range.start(), range.length()) };
// The hash implementation handles this in chunks, so no memory issues.
hasher.update(partition_slice);
}
let computed_hash = hasher.finalize();
if &computed_hash != parse_metadata_hash(metadata) {
return Err(StorageError::CustomError);
}
Ok(())
}
}
impl UpgradeStorage for TockUpgradeStorage {
fn write_bundle(&mut self, offset: usize, data: Vec<u8>) -> StorageResult<()> {
fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> {
if length == 0 {
return Err(StorageError::OutOfBounds);
}
let address = self.partition.start() + offset;
if self
.partition
.contains_range(&ModRange::new(address, length))
{
Ok(unsafe { core::slice::from_raw_parts(address as *const u8, length) })
} else {
Err(StorageError::OutOfBounds)
}
}
fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()> {
if data.is_empty() {
return Err(StorageError::OutOfBounds);
}
let address = self
.partition
.find_address(offset, data.len())
.ok_or(StorageError::OutOfBounds)?;
let address = self.partition.start() + offset;
let write_range = ModRange::new(address, data.len());
if self.contains_metadata(&write_range)? {
let new_metadata = &data[self.metadata.start() - address..][..self.metadata.length()];
check_metadata(self, UPGRADE_PUBLIC_KEY, new_metadata)?;
if self.partition.contains_range(&write_range) {
// Erases all pages that have their first byte in the write range.
// Since we expect calls in order, we don't want to erase half-written pages.
for address in write_range.aligned_iter(self.page_size) {
erase_page(address, self.page_size)?;
}
write_slice(address, data)
} else {
Err(StorageError::OutOfBounds)
}
}
// Erases all pages that have their first byte in the write range.
// Since we expect calls in order, we don't want to erase half-written pages.
for address in write_range.aligned_iter(self.page_size) {
erase_page(address, self.page_size)?;
fn partition_address(&self) -> usize {
self.partition.start()
}
fn partition_length(&self) -> usize {
self.partition.length()
}
fn read_metadata(&self) -> StorageResult<&[u8]> {
Ok(unsafe {
core::slice::from_raw_parts(self.metadata.start() as *const u8, self.metadata.length())
})
}
fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()> {
// If less data is passed in than is reserved, assume the rest is 0xFF.
if data.len() <= self.metadata.length() {
for address in self.metadata.aligned_iter(self.page_size) {
erase_page(address, self.page_size)?;
}
write_slice(self.metadata.start(), data)
} else {
Err(StorageError::OutOfBounds)
}
write_slice(address, &data)?;
let written_slice = unsafe { read_slice(address, data.len()) };
if written_slice != data {
return Err(StorageError::CustomError);
}
// Case: Last slice is written.
if data.len() == self.partition.length() - offset {
let metadata = unsafe { read_slice(self.metadata.start(), self.metadata.length()) };
self.check_partition_hash(metadata)?;
}
Ok(())
}
fn bundle_identifier(&self) -> u32 {
self.identifier
}
fn running_firmware_version(&self) -> u64 {
let running_metadata = unsafe {
read_slice(
self.running_metadata.start(),
self.running_metadata.length(),
)
};
parse_metadata_version(running_metadata)
}
}
/// Parses the metadata of an upgrade, and checks its correctness.
///
/// The metadata is a page starting with:
/// - 32 B upgrade hash (SHA256)
/// - 64 B signature,
/// that are not signed over. The second part is included in the signature with
/// - 8 B version and
/// - 4 B partition address in little endian encoding
/// written at METADATA_SIGN_OFFSET.
///
/// Checks signature correctness against the hash, and whether the partition offset matches.
/// Whether the hash matches the partition content is not tested here!
fn check_metadata(
upgrade_locations: &impl UpgradeStorage,
public_key_bytes: &[u8],
metadata: &[u8],
) -> StorageResult<()> {
const METADATA_LEN: usize = 0x1000;
if metadata.len() != METADATA_LEN {
return Err(StorageError::CustomError);
}
let version = parse_metadata_version(metadata);
if version < upgrade_locations.running_firmware_version() {
return Err(StorageError::CustomError);
}
let metadata_address = LittleEndian::read_u32(&metadata[METADATA_SIGN_OFFSET + 8..][..4]);
if metadata_address != upgrade_locations.bundle_identifier() {
return Err(StorageError::CustomError);
}
verify_signature(
array_ref!(metadata, 32, 64),
public_key_bytes,
parse_metadata_hash(metadata),
)?;
Ok(())
}
/// Parses the metadata, returns the hash.
fn parse_metadata_hash(data: &[u8]) -> &[u8; 32] {
array_ref!(data, 0, 32)
}
/// Parses the metadata, returns the firmware version.
fn parse_metadata_version(data: &[u8]) -> u64 {
LittleEndian::read_u64(&data[METADATA_SIGN_OFFSET..][..8])
}
/// Verifies the signature over the given hash.
///
/// The public key is COSE encoded, and the hash is a SHA256.
fn verify_signature(
signature_bytes: &[u8; 64],
public_key_bytes: &[u8],
signed_hash: &[u8; 32],
) -> StorageResult<()> {
let signature =
ecdsa::Signature::from_bytes(signature_bytes).ok_or(StorageError::CustomError)?;
let public_key = ecdsa::PubKey::from_bytes_uncompressed(public_key_bytes)
.ok_or(StorageError::CustomError)?;
if !public_key.verify_hash_vartime(signed_hash, &signature) {
return Err(StorageError::CustomError);
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::env::test::TestEnv;
use crate::env::Env;
#[test]
fn test_check_metadata() {
let mut env = TestEnv::new();
let private_key = crypto::ecdsa::SecKey::gensk(env.rng());
let upgrade_locations = env.upgrade_storage().unwrap();
const METADATA_LEN: usize = 0x1000;
const METADATA_SIGN_OFFSET: usize = 0x800;
let mut metadata = vec![0xFF; METADATA_LEN];
LittleEndian::write_u32(&mut metadata[METADATA_SIGN_OFFSET + 8..][..4], 0x60000);
let mut signed_over_data = metadata[METADATA_SIGN_OFFSET..].to_vec();
signed_over_data.extend(&[0xFF; 0x20000]);
let signed_hash = Sha256::hash(&signed_over_data);
metadata[..32].copy_from_slice(&signed_hash);
let signature = private_key.sign_rfc6979::<Sha256>(&signed_over_data);
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
signature.to_bytes(&mut signature_bytes);
metadata[32..96].copy_from_slice(&signature_bytes);
let public_key = private_key.genpk();
let mut public_key_bytes = [0; 65];
public_key.to_bytes_uncompressed(&mut public_key_bytes);
assert_eq!(
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
Ok(())
);
// Manipulating the partition address fails.
metadata[METADATA_SIGN_OFFSET + 8] = 0x88;
assert_eq!(
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
Err(StorageError::CustomError)
);
metadata[METADATA_SIGN_OFFSET + 8] = 0x00;
// Wrong metadata length fails.
assert_eq!(
check_metadata(
upgrade_locations,
&public_key_bytes,
&metadata[..METADATA_LEN - 1]
),
Err(StorageError::CustomError)
);
// Manipulating the hash fails.
metadata[0] ^= 0x01;
assert_eq!(
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
Err(StorageError::CustomError)
);
metadata[0] ^= 0x01;
// Manipulating the signature fails.
metadata[32] ^= 0x01;
assert_eq!(
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
Err(StorageError::CustomError)
);
}
#[test]
fn test_verify_signature() {
let mut env = TestEnv::new();
let private_key = crypto::ecdsa::SecKey::gensk(env.rng());
let message = [0x44; 64];
let signed_hash = Sha256::hash(&message);
let signature = private_key.sign_rfc6979::<Sha256>(&message);
let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH];
signature.to_bytes(&mut signature_bytes);
let public_key = private_key.genpk();
let mut public_key_bytes = [0; 65];
public_key.to_bytes_uncompressed(&mut public_key_bytes);
assert_eq!(
verify_signature(&signature_bytes, &public_key_bytes, &signed_hash),
Ok(())
);
assert_eq!(
verify_signature(&signature_bytes, &public_key_bytes, &[0x55; 32]),
Err(StorageError::CustomError)
);
public_key_bytes[0] ^= 0x01;
assert_eq!(
verify_signature(&signature_bytes, &public_key_bytes, &signed_hash),
Err(StorageError::CustomError)
);
public_key_bytes[0] ^= 0x01;
signature_bytes[0] ^= 0x01;
assert_eq!(
verify_signature(&signature_bytes, &public_key_bytes, &signed_hash),
Err(StorageError::CustomError)
);
}
}

View File

@@ -49,7 +49,7 @@ use libtock_drivers::timer::Duration;
use libtock_drivers::usb_ctap_hid;
use usb_ctap_hid::UsbEndpoint;
libtock_core::stack_size! {0x11800}
libtock_core::stack_size! {0x4000}
const SEND_TIMEOUT: Milliseconds<ClockInt> = Milliseconds(1000);
const KEEPALIVE_DELAY_TOCK: Duration<isize> = Duration::from_ms(KEEPALIVE_DELAY_MS as isize);

957
third_party/dilithium/Cargo.lock generated vendored
View File

@@ -1,957 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
dependencies = [
"arrayref",
"byte-tools",
]
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]]
name = "byte-tools"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "criterion"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
dependencies = [
"atty",
"cast",
"clap",
"criterion-plot",
"csv",
"itertools 0.10.5",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
dependencies = [
"cast",
"itertools 0.10.5",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
dependencies = [
"autocfg 1.1.0",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
dependencies = [
"cfg-if",
]
[[package]]
name = "csv"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bstr",
"csv-core",
"itoa 0.4.8",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]]
name = "digest"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
dependencies = [
"generic-array",
]
[[package]]
name = "dilithium"
version = "0.2.0-alpha.3"
dependencies = [
"arrayref",
"byteorder",
"criterion",
"digest",
"hex",
"itertools 0.7.11",
"once_cell",
"rand_core 0.6.4",
"rng256",
"sha3",
"structopt",
]
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "generic-array"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d00328cedcac5e81c683e5620ca6a30756fc23027ebf9bff405c0e8da1fbb7e"
dependencies = [
"typenum",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
[[package]]
name = "itertools"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "keccak"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]]
name = "libtock_codegen"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "libtock_core"
version = "0.1.0"
dependencies = [
"libtock_codegen",
]
[[package]]
name = "libtock_drivers"
version = "0.1.0"
dependencies = [
"libtock_core",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "plotters"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
[[package]]
name = "plotters-svg"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
dependencies = [
"plotters-backend",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.8",
"libc",
"rand_chacha",
"rand_core 0.4.2",
"rand_hc",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg",
"rand_xorshift",
"winapi",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.3.1",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.4.2",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rayon"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
dependencies = [
"autocfg 1.1.0",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "rng256"
version = "0.1.0"
dependencies = [
"arrayref",
"libtock_drivers",
"rand",
]
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
[[package]]
name = "serde_cbor"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
dependencies = [
"itoa 1.0.4",
"ryu",
"serde",
]
[[package]]
name = "sha3"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64dcef59ed4290b9fb562b53df07f564690d6539e8ecdd4728cf392477530bc"
dependencies = [
"block-buffer",
"byte-tools",
"digest",
"keccak",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
dependencies = [
"clap",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "typenum"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "unicode-segmentation"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -1,34 +0,0 @@
[package]
name = "dilithium"
version = "0.2.0-alpha.3"
authors = ["quininer <quininer@live.com>"]
description = "Digital Signatures from Module Lattices"
repository = "https://github.com/quininer/dilithium"
license = "MIT"
[dependencies]
rng256 = { path = "../../libraries/rng256" }
rand_core = { version = "0.6", default-features = false }
arrayref = {version = "0.3", default-features = false}
itertools = { version = "0.7", default-features = false }
byteorder = { version = "1", default-features = false }
sha3 = { version = "0.7.3", default-features = false }
digest = { version = "0.7", default-features = false }
[dev-dependencies]
hex = "0.3"
structopt = "0.3.25"
criterion = "0.3"
[features]
std = [ "rng256/std" ]
default = [ "dilithium5", "optimize_stack" ]
dilithium2 = []
dilithium3 = []
dilithium5 = []
optimize_stack = []
derive_debug = []
[[bench]]
name = "sign_bench"
harness = false

View File

@@ -1,8 +0,0 @@
MIT License
Copyright (c) 2017 quininer@live.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,7 +0,0 @@
Dilithium
---------
Digital Signatures from Module Lattices
* [CRYSTALS Dilithium: Digital Signatures from Module Lattices](https://eprint.iacr.org/2017/633.pdf)
* [ref dilithium implemention](https://github.com/pq-crystals/dilithium)

View File

@@ -1,70 +0,0 @@
// Benchmarks for key generation and signing with Dilithium.
// cargo criterion --features std
extern crate core;
extern crate criterion;
extern crate dilithium;
extern crate rng256;
use core::time::Duration;
use criterion::*;
use dilithium::sign::SecKey;
use rng256::Rng256;
const SAMPLE_SIZE: usize = 1000;
const MEASUREMENT_TIME: Duration = Duration::from_secs(10);
fn bench_sk(c: &mut Criterion) {
let mut rng = rng256::ThreadRng256 {};
c.bench_function("gensk", |b| {
b.iter_batched(
|| {},
|_| {
SecKey::gensk(&mut rng);
},
BatchSize::SmallInput,
)
});
}
fn bench_pk(c: &mut Criterion) {
let mut rng = rng256::ThreadRng256 {};
c.bench_function("genpk", |b| {
b.iter_batched(
|| SecKey::gensk(&mut rng),
|sk| {
sk.genpk();
},
BatchSize::SmallInput,
)
});
}
fn bench_sign(c: &mut Criterion) {
const MESSAGE_LENGTH: usize = 64;
let mut rng = rng256::ThreadRng256 {};
c.bench_function("sign", |b| {
b.iter_batched(
|| {
let sk = SecKey::gensk(&mut rng);
let mut message = [0; MESSAGE_LENGTH];
rng.fill_bytes(&mut message);
(sk, message)
},
|(sk, message)| {
sk.sign(&message);
},
BatchSize::SmallInput,
)
});
}
criterion_group! {
name = benches;
config = Criterion::default().sample_size(SAMPLE_SIZE).measurement_time(MEASUREMENT_TIME);
targets = bench_sk, bench_pk, bench_sign
}
criterion_main!(benches);

View File

@@ -1,41 +0,0 @@
// Command for changing the stack size:
// cargo run --example sign --features std -- --stack-size-kb (new value in KB)
extern crate dilithium;
extern crate rng256;
extern crate structopt;
use dilithium::sign::SecKey;
use rng256::Rng256;
use std::thread;
use structopt::StructOpt;
const DEFAULT_STACK_SIZE_KB: &str = "81";
#[derive(Debug, StructOpt)]
struct Opts {
#[structopt(long, default_value=DEFAULT_STACK_SIZE_KB)]
stack_size_kb: usize,
}
fn run() {
let mut rng = rng256::ThreadRng256 {};
let sk = SecKey::gensk(&mut rng);
let mut message = [0; 59];
rng.fill_bytes(&mut message);
sk.sign(&message);
}
fn main() {
let stack_size_kb = Opts::from_args().stack_size_kb;
// We bound the stack size for generating keys and signing in Dilithium.
let child = thread::Builder::new()
.stack_size(stack_size_kb * 1024)
.spawn(run)
.unwrap();
// Wait for thread to join
child.join().unwrap();
}

View File

@@ -1,25 +0,0 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "std")]
extern crate core;
#[macro_use]
extern crate arrayref;
extern crate byteorder;
extern crate digest;
extern crate itertools;
extern crate sha3;
#[macro_use]
mod utils;
mod ntt;
mod packing;
pub mod params;
mod poly;
mod polyvec;
mod reduce;
mod rounding;
pub mod sign;
#[cfg(test)]
mod test_mul;

View File

@@ -1,94 +0,0 @@
use itertools::Itertools;
use params::N;
use reduce::montgomery_reduce;
const ZETAS: [i32; N] = [
0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468, 1826347, 2353451, -359251,
-2091905, 3119733, -2884855, 3111497, 2680103, 2725464, 1024112, -1079900, 3585928, -549488,
-1119584, 2619752, -2108549, -2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497,
280005, 2706023, 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439, -3861115,
-3043716, 3574422, -2867647, 3539968, -300467, 2348700, -539299, -1699267, -1643818, 3505694,
-3821735, 3507263, -2140649, -1600420, 3699596, 811944, 531354, 954230, 3881043, 3900724,
-2556880, 2071892, -2797779, -3930395, -1528703, -3677745, -3041255, -1452451, 3475950,
2176455, -1585221, -1257611, 1939314, -4083598, -1000202, -3190144, -3157330, -3632928, 126922,
3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047, -671102, -1228525,
-22981, -1308169, -381987, 1349076, 1852771, -1430430, -3343383, 264944, 508951, 3097992,
44288, -1100098, 904516, 3958618, -3724342, -8578, 1653064, -3249728, 2389356, -210977, 759969,
-1316856, 189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330, 1285669,
-1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961, 2091667, 3407706, 2316500,
3817976, -3342478, 2244091, -2446433, -3562462, 266997, 2434439, -1235728, 3513181, -3520352,
-3759364, -1197226, -3193378, 900702, 1859098, 909542, 819034, 495491, -1613174, -43260,
-522500, -655327, -3122442, 2031748, 3207046, -3556995, -525098, -768622, -3595838, 342297,
286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044, 2842341, 2691481, -2590150,
1265009, 4055324, 1247620, 2486353, 1595974, -3767016, 1250494, 2635921, -3548272, -2994039,
1869119, 1903435, -1050970, -1333058, 1237275, -3318210, -1430225, -451100, 1312455, 3306115,
-1962642, -1279661, 1917081, -2546312, -1374803, 1500165, 777191, 2235880, 3406031, -542412,
-2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993, -2013608, 2432395, 2454455,
-164721, 1957272, 3369112, 185531, -1207385, -3183426, 162844, 1616392, 3014001, 810149,
1652634, -3694233, -1799107, -3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735,
472078, -426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893, -2939036,
-2235985, -420899, -2286327, 183443, -976891, 1612842, -3545687, -554416, 3919660, -48306,
-1362209, 3937738, 1400424, -846154, 1976782,
];
/// Implements forward NTT, in-place.
///
/// No modular reduction is performed after additions or substractions.
/// The output vector is in bitreversed order.
///
/// # Arguments
///
/// * `p` - a polynomial in standard representation.
pub fn ntt(p: &mut [i32; N]) {
let mut k = 1;
let mut len = 128;
while len > 0 {
for start in Itertools::step(0..N, 2 * len) {
let zeta = i64::from(ZETAS[k]);
k += 1;
for j in start..(start + len) {
let t = montgomery_reduce(zeta * i64::from(p[j + len]));
p[j + len] = p[j] - t;
p[j] += t;
}
}
len >>= 1;
}
}
/// Implements inverse NTT and multiplication by Montgomery factor 2^32.
///
/// The implementation is in-place.
/// No modular reduction is performed after additions or substractions.
/// Input coefficients must be smaller than Q in absolute value.
/// The output coefficients are smaller than Q in absolute value.
///
/// # Arguments
///
/// * `p` - a polynomial in NTT representation.
pub fn invntt_frominvmont(p: &mut [i32; N]) {
let mut k = 255;
let mut len = 1;
while len < N {
for start in Itertools::step(0..N, 2 * len) {
let zeta = (-1) * i64::from(ZETAS[k]);
k -= 1;
for j in start..(start + len) {
let t = p[j];
p[j] += p[j + len];
p[j + len] = t - p[j + len];
p[j + len] = montgomery_reduce(zeta * i64::from(p[j + len]));
}
}
len <<= 1;
}
// F = MONT^2 / 256 mod Q, where MONT = 2^32 mod Q.
const F: i64 = 41978;
for j in 0..N {
p[j] = montgomery_reduce(F * i64::from(p[j]));
}
}

View File

@@ -1,164 +0,0 @@
use params::{
K, L, N, OMEGA, PK_SIZE_PACKED, POLT1_SIZE_PACKED, POLZ_SIZE_PACKED, SEEDBYTES, SIG_SIZE_PACKED,
};
use poly::{self, Poly};
use polyvec::{PolyVecK, PolyVecL};
pub mod pk {
use super::*;
/// Decodes a public key with the shape: `pk || rho || encodings of t1`.
///
/// # Arguments
///
/// * `pk` - the encoded public key
/// * `rho` - output array for the randomness seed `rho`
/// * `t1` - output PolyVecK for the vector of polynomials `t1`
pub fn unpack(pk: &[u8; PK_SIZE_PACKED], rho: &mut [u8; SEEDBYTES], t1: &mut PolyVecK) {
let (rho_bytes, t1s_bytes) = array_refs!(pk, SEEDBYTES, POLT1_SIZE_PACKED * K);
rho.clone_from(rho_bytes);
for i in 0..K {
let t1_bytes = array_ref!(t1s_bytes, i * POLT1_SIZE_PACKED, POLT1_SIZE_PACKED);
poly::t1_unpack(&mut t1[i], t1_bytes);
}
}
}
// Encodes and Decodes a signature with the shape:
// c_seed || encodings of z || encodings of h
pub mod sign {
use super::*;
/// Encodes an array used to obtain the challenge `c`.
///
/// # Arguments
///
/// * `sig` - the output array representing the encoded signature
/// * `c_seed` - array to be encoded
pub fn pack_c(sign: &mut [u8; SIG_SIZE_PACKED], c_seed: &[u8; SEEDBYTES]) {
let c_bytes = array_mut_ref!(sign, 0, SEEDBYTES);
for i in 0..SEEDBYTES {
c_bytes[i] = c_seed[i];
}
}
/// Encodes `z[i]`, where z is a vector of `L` polynomials.
///
/// # Arguments
///
/// * `sig` - output array representing the encoded signature
/// * `z_component` - polynomial representing `z[i]`
/// * `i` - the index of the component to be encoded
pub fn pack_z_component(sign: &mut [u8; SIG_SIZE_PACKED], z_component: &Poly, i: usize) {
let z_bytes = array_mut_ref!(sign, SEEDBYTES + i * POLZ_SIZE_PACKED, POLZ_SIZE_PACKED);
poly::z_pack(z_bytes, &z_component);
}
/// Encodes `z`, where `z` is a vector of `L` polynomials.
///
/// # Arguments
///
/// * `sig` - output array representing the encoded signature
/// * `z` - vector of `L` polynomials`
#[cfg(not(feature = "optimize_stack"))]
pub fn pack_z(sign: &mut [u8; SIG_SIZE_PACKED], z: &PolyVecL) {
for i in 0..L {
pack_z_component(sign, &z[i], i);
}
}
/// Encodes `h[i]`, where `h` is a vector of `K` polynomials.
///
/// # Arguments
///
/// * `sig` - output array representing the encoded signature
/// * `h_component` - polynomial representing `h[i]`
/// * `i` - the index of the component to be encoded
/// * `non_zero_coeff_index` - the index returned when encoding
/// `h[i - 1]` (0 if `i` = 0)
pub fn pack_h_component(
sign: &mut [u8; SIG_SIZE_PACKED],
h_component: &Poly,
i: usize,
non_zero_coeff_index: &mut usize,
) {
let h_bytes = array_mut_ref!(sign, SEEDBYTES + POLZ_SIZE_PACKED * L, OMEGA + K);
for j in 0..N {
if h_component[j] != 0 {
h_bytes[*non_zero_coeff_index] = j as u8;
*non_zero_coeff_index += 1;
}
}
h_bytes[OMEGA + i] = *non_zero_coeff_index as u8;
}
/// Encodes `h`, where `h` is a vector of `K` polynomials.
///
/// # Arguments
///
/// * `sig` - output array representing the encoded signature
/// * `h` - vector of `K` polynomials.
#[cfg(not(feature = "optimize_stack"))]
pub fn pack_h(sign: &mut [u8; SIG_SIZE_PACKED], h: &PolyVecK) {
let mut non_zero_coeff_index = 0;
for i in 0..K {
pack_h_component(sign, &h[i], i, &mut non_zero_coeff_index);
}
}
/// Decodes the components of the signature.
///
/// The values are written into the output arguments `c_seed`,
/// `z`, and `h` from `sig`.
///
/// # Arguments
///
/// * `sig` - the encoded signature
/// * `c_seed` - output array for the seed used to compute the challenge
/// * `z` - output PolyVecL for the vector of polynomials `z`
/// * `h` - output PolyVecK for the vector of polynomials `h`
pub fn unpack(
sign: &[u8; SIG_SIZE_PACKED],
c_seed: &mut [u8; SEEDBYTES],
z: &mut PolyVecL,
h: &mut PolyVecK,
) -> bool {
let (c_bytes, z_bytes, h_bytes) =
array_refs!(sign, SEEDBYTES, POLZ_SIZE_PACKED * L, OMEGA + K);
for i in 0..SEEDBYTES {
c_seed[i] = c_bytes[i];
}
for i in 0..L {
let z_bytes = array_ref!(z_bytes, i * POLZ_SIZE_PACKED, POLZ_SIZE_PACKED);
poly::z_unpack(&mut z[i], z_bytes);
}
// Decode h
let mut k = 0;
for i in 0..K {
if (h_bytes[OMEGA + i] as usize) < k || (h_bytes[OMEGA + i] as usize) > OMEGA {
return false;
}
for j in k..(h_bytes[OMEGA + i] as usize) {
// Coefficients are ordered for strong unforgeability
if j > k && h_bytes[j] <= h_bytes[j - 1] {
return false;
}
h[i][h_bytes[j] as usize] = 1;
}
k = h_bytes[OMEGA + i] as usize;
}
// Extra indices are zero for strong unforgeability
if h_bytes[k..OMEGA].iter().any(|&v| v != 0) {
return false;
}
true
}
}

View File

@@ -1,78 +0,0 @@
#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))]
pub const SEEDBYTES: usize = 32;
pub const CRHBYTES: usize = 64;
pub const N: usize = 256;
pub const Q: i32 = 8380417;
pub const D: usize = 13;
pub const ROOT_OF_UNITY: usize = 1753;
#[cfg(feature = "dilithium2")]
mod mode {
use super::Q;
pub const K: usize = 4;
pub const L: usize = 4;
pub const ETA: i32 = 2;
pub const TAU: usize = 39;
pub const BETA: i32 = 78;
pub const GAMMA1: i32 = 1 << 17;
pub const GAMMA2: i32 = (Q - 1) / 88;
pub const OMEGA: usize = 80;
pub const POLZ_SIZE_PACKED: usize = 576;
pub const POLW1_SIZE_PACKED: usize = 192;
pub const POLETA_SIZE_PACKED: usize = 96;
}
#[cfg(feature = "dilithium3")]
mod mode {
use super::Q;
pub const K: usize = 6;
pub const L: usize = 5;
pub const ETA: i32 = 4;
pub const TAU: usize = 49;
pub const BETA: i32 = 196;
pub const GAMMA1: i32 = 1 << 19;
pub const GAMMA2: i32 = (Q - 1) / 32;
pub const OMEGA: usize = 55;
pub const POLZ_SIZE_PACKED: usize = 640;
pub const POLW1_SIZE_PACKED: usize = 128;
pub const POLETA_SIZE_PACKED: usize = 128;
}
#[cfg(feature = "dilithium5")]
mod mode {
use super::Q;
pub const K: usize = 8;
pub const L: usize = 7;
pub const ETA: i32 = 2;
pub const TAU: usize = 60;
pub const BETA: i32 = 120;
pub const GAMMA1: i32 = 1 << 19;
pub const GAMMA2: i32 = (Q - 1) / 32;
pub const OMEGA: usize = 75;
pub const POLZ_SIZE_PACKED: usize = 640;
pub const POLW1_SIZE_PACKED: usize = 128;
pub const POLETA_SIZE_PACKED: usize = 96;
}
pub use self::mode::*;
pub const POLT1_SIZE_PACKED: usize = 320;
pub const POLT0_SIZE_PACKED: usize = 416;
pub const PK_SIZE_PACKED: usize = SEEDBYTES + K * POLT1_SIZE_PACKED;
pub const SK_SIZE_PACKED: usize = 3 * SEEDBYTES + (L + K) * POLETA_SIZE_PACKED;
pub const SK_SIZE_PACKED_ORIGINAL: usize =
3 * SEEDBYTES + (L + K) * POLETA_SIZE_PACKED + K * POLT0_SIZE_PACKED;
pub const SIG_SIZE_PACKED: usize = L * POLZ_SIZE_PACKED + (OMEGA + K) + SEEDBYTES;
pub const PUBLICKEYBYTES: usize = PK_SIZE_PACKED;
pub const SECRETKEYBYTES: usize = SK_SIZE_PACKED;
pub const BYTES: usize = SIG_SIZE_PACKED;
/// `MONT = 2^32 mod Q`
pub const MONT: i64 = -4186625;
pub const QINV: isize = 58728449;

View File

@@ -1,775 +0,0 @@
use byteorder::{ByteOrder, LittleEndian};
pub use ntt::{invntt_frominvmont as invntt_montgomery, ntt};
use params::{
CRHBYTES, D, ETA, GAMMA1, GAMMA2, N, POLETA_SIZE_PACKED, POLT1_SIZE_PACKED, POLW1_SIZE_PACKED,
POLZ_SIZE_PACKED, Q, SEEDBYTES, TAU,
};
use reduce::{caddq as xcaddq, freeze as xfreeze, montgomery_reduce, reduce32};
use rounding;
pub type Poly = [i32; N];
/// Reduces the coefficients of the polynomial `a` to [-6283009,6283007].
///
/// # Arguments
///
/// * `a` - a polynomial
pub fn reduce(a: &mut Poly) {
for i in 0..N {
a[i] = reduce32(a[i]);
}
}
/// Adds `Q` to every negative coefficient in `a`.
///
/// # Arguments
///
/// * `a` - a polynomial
pub fn caddq(a: &mut Poly) {
for i in 0..N {
a[i] = xcaddq(a[i]);
}
}
/// For every coefficient `x` in `a`, computes `x mod Q`.
///
/// # Arguments
///
/// * `a` - a polynomial
pub fn freeze(a: &mut Poly) {
for i in 0..N {
a[i] = xfreeze(a[i]);
}
}
/// Computes `c = a + b`, where `c`, `a`, and `b` are polynomials.
///
/// # Arguments
///
/// * `a` - a polynomial
pub fn add(c: &mut Poly, a: &Poly, b: &Poly) {
for i in 0..N {
c[i] = a[i] + b[i];
}
}
/// Computes `c = c + a`, where `c`, and `a` are polynomials.
///
/// # Arguments
///
/// * `a` - a polynomial
pub fn add_assign(c: &mut Poly, a: &Poly) {
for i in 0..N {
c[i] += a[i];
}
}
/// Computes `c = a - b`, where `c`, `a` and `b` are polynomials.
///
/// # Arguments
///
/// * `a` - a polynomial
pub fn sub(c: &mut Poly, a: &Poly, b: &Poly) {
for i in 0..N {
c[i] = a[i] - b[i];
}
}
/// Multiplies the polynomial `a` by `2^D` without modular reduction.
///
/// # Arguments
///
/// * `a` - a polynomial with coefficients smaller than than 2^{31-D}
/// in absolute value
pub fn shift_left(a: &mut Poly) {
for i in 0..N {
a[i] <<= D;
}
}
/// Computes `c = a * b` in NTT domain representation.
///
/// # Arguments
///
/// * `c` - the output polynomial, in NTT domain representation
/// * `a` - a polynomial in NTT domain representation
/// * `b` - a polynomial in NTT domain representation
pub fn pointwise_invmontgomery(c: &mut Poly, a: &Poly, b: &Poly) {
for i in 0..N {
c[i] = montgomery_reduce((a[i] as i64) * (b[i] as i64));
}
}
/// Returns `c = a * b` in standard representation.
///
/// # Arguments
///
/// * `a` - a polynomial in NTT domain representation
/// * `b` - a polynomial in NTT domain representation
pub fn multiply(a: &Poly, b: &Poly) -> Poly {
let mut c = [0; N];
pointwise_invmontgomery(&mut c, a, b);
invntt_montgomery(&mut c);
reduce(&mut c);
c
}
/// Decomposes a into the quotient and remainder of its division with `2^{D-1}`.
///
/// For every coefficient `c` of the polynomial `a`, computes `c0`, `c1`
/// such that `c mod Q = c1 * 2^D + c0`, with `-2^{D-1} < c0 <= 2^{D-1}`.
///
/// # Arguments
///
/// * `a` - a polynomial in standard representation (not NTT)
/// * `a0` - output polynomial representing the remainder (coefficients `c0`)
/// * `a1` - output polynomial representing the quotient (coefficients `c1`)
pub fn power2round(a: &Poly, a0: &mut Poly, a1: &mut Poly) {
for i in 0..N {
let (x, y) = rounding::power2round(a[i]);
a0[i] = x;
a1[i] = y;
}
}
/// Obtains the remainder of dividing `a` with `2^{D-1}`.
///
/// For every coefficient `c` of the polynomial a, computes `c0`, `c1`
/// such that `c mod Q = c1 * 2^D + c0`, with `-2^{D-1} < c0 <= 2^{D-1}`.
///
/// # Arguments
///
/// * `a` - a polynomial in standard representation (not NTT)
/// * `a0` - output polynomial representing the remainder (coefficients `c0`)
pub fn power2round_remainder(a: &Poly) -> Poly {
let mut remainder = [0; N];
for i in 0..N {
let (x, _) = rounding::power2round(a[i]);
remainder[i] = x;
}
remainder
}
/// Obtains the quotient of dividing `a` with `2^{D-1}`.
///
/// For every coefficient `c` of the polynomial `a`, computes `c0, c1`
/// such that `c mod Q = c1 * 2^D + c0`, with `-2^{D-1} < c0 <= 2^{D-1}`.
///
/// # Arguments
///
/// * `a` - a polynomial in standard representation (not NTT)
/// * `a1` - output polynomial representing the quotient (coefficients `c1`)
pub fn power2round_quotient(a: &Poly) -> Poly {
let mut quotient = [0; N];
for i in 0..N {
let (_, y) = rounding::power2round(a[i]);
quotient[i] = y;
}
quotient
}
/// Obtains the high bits and the low bits of `a`.
///
/// For every coefficient `c` of the input polynomial `a`, computes its
/// high bits `c1` and low bits `c0` such that `c mod Q = c1*ALPHA + c0`,
/// where -ALPHA/2 < c0 <= ALPHA/2.
/// Exception: if `c1 = (Q-1)/ALPHA`, `c1` is set to 0 and `c0 = c mod Q - Q`.
///
/// # Arguments
///
/// * `a` - a polynomial in standard representation (not NTT)
/// * `a0` - output polynomial representing `a`'s low bits (coefficients `c0`)
/// * `a1` - output polynomial representing `a`'s high bits (coefficients `c1`)
pub fn decompose(a: &Poly, a0: &mut Poly, a1: &mut Poly) {
for i in 0..N {
let (x, y) = rounding::decompose(a[i]);
a0[i] = x; // low bits
a1[i] = y; // high bits
}
}
/// Returns a polynomial whose coefficients are the high bits of `a`.
///
/// For every coefficient `c` of the input polynomial a, computes its
/// high bits `c1` and low bits `c0` such that `c mod Q = c1*ALPHA + c0`,
/// where `-ALPHA/2 < c0 <= ALPHA/2`.
/// Exception: if `c1 = (Q-1)/ALPHA`, `c1` is set to 0 and `c0 = c mod Q - Q`.
///
/// # Arguments
///
/// * `a` - a polynomial in standard representation (not NTT)
#[cfg(feature = "optimize_stack")]
pub fn high_bits(a: &Poly) -> Poly {
let mut high_bits: Poly = [0; N];
for i in 0..N {
let (_x, y) = rounding::decompose(a[i]);
high_bits[i] = y;
}
return high_bits;
}
/// Returns a polynomial whose coefficients are the low bits of `a`.
///
/// For every coefficient `c` of the input polynomial `a`, computes its
/// high bits `c1` and low bits `c0` such that `c mod Q = c1*ALPHA + c0`,
/// where `-ALPHA/2 < c0 <= ALPHA/2`.
/// Exception: if `c1 = (Q-1)/ALPHA`, `c1` is set to 0 and `c0 = c mod Q - Q`.
///
/// # Arguments
///
/// * `a` - a polynomial in standard representation (not NTT)
#[cfg(feature = "optimize_stack")]
pub fn low_bits(a: &Poly) -> Poly {
let mut low_bits: Poly = [0; N];
for i in 0..N {
let (x, _y) = rounding::decompose(a[i]);
low_bits[i] = x;
}
return low_bits;
}
/// Makes the hint used to obtain `a` from an approximate result `b`.
///
/// Given a polynomial of low bits `a`, and a polynomial of high bits `b`,
/// computes the hint polynomial `h`. The coefficient of `h` indicate
/// whether the low bits of the corresponding coefficient of the input
/// polynomial `a` overflow into the high bits (`b`).
///
/// # Arguments
///
/// * `a` - a polynomial
/// * `b` - a polynomial
/// * `h` - the output polynomial
pub fn make_hint(a: &Poly, b: &Poly, h: &mut Poly) -> usize {
let mut s = 0;
for i in 0..N {
h[i] = rounding::make_hint(a[i], b[i]) as i32;
s += h[i] as usize;
}
s
}
/// Uses a hint polynomial `h` to correct the high bits of a polynomial `b`.
///
/// # Arguments
///
/// * `a` - the output corrected polynomial
/// * `b` - a polynomial
/// * `h` - the hint polynomial: containing values 0 or 1
pub fn use_hint(a: &mut Poly, b: &Poly, h: &Poly) {
for i in 0..N {
a[i] = rounding::use_hint(b[i], h[i] as u32);
}
}
/// Checks if the infinity norm of a polynomial `a` against a given bound `b`.
///
/// The input coefficients must be reduced by `reduce32()`.
///
/// # Arguments
///
/// * `a` - a polynomial
/// * `b` - the bound.
pub fn chknorm(a: &Poly, b: i32) -> bool {
if b > (Q - 1) / 8 {
return true;
}
// It is ok to leak which coefficient violates the bound since
// the probability for each coefficient is independent of secret
// data but we must not leak the sign of the centralized representative.
for i in 0..N {
let mut t: i32 = a[i] >> 31;
t = a[i] - (t & 2 * a[i]);
if t >= b {
return true;
}
}
return false;
}
/// Samples a polynomial with random coefficients in `[0, Q - 1]`.
///
/// The sampling is done by performing rejection sampling on the output stream
/// of `SHAKE256(seed|nonce)`.
///
/// # Arguments
///
/// * `a` - the output polynomial
/// * `seed` - an array of random bytes
/// * `nonce` - a number.
pub fn uniform(a: &mut Poly, seed: &[u8; SEEDBYTES], nonce: u16) {
use digest::{ExtendableOutput, Input, XofReader};
use sha3::Shake128;
fn rej_uniform(a: &mut [i32], i_start: usize, buf: &[u8], buf_len: usize) -> usize {
let mut ctr = 0usize;
let mut pos = 0usize;
let mut t: u32;
let len = a.len() - i_start;
while ctr < len && pos + 3 <= buf_len {
t = buf[pos] as u32;
pos += 1;
t |= (buf[pos] as u32) << 8;
pos += 1;
t |= (buf[pos] as u32) << 16;
pos += 1;
t &= 0x7FFFFF;
if t < (Q as u32) {
a[i_start + ctr] = t as i32;
ctr += 1;
}
}
ctr
}
let mut hasher = Shake128::default();
hasher.process(seed);
let nonce0 = (nonce & ((1 << 8) - 1)) as u8;
let nonce1 = (nonce >> 8) as u8;
hasher.process(&[nonce0, nonce1]);
const STREAM128_BLOCKBYTES: usize = 168;
const POLY_UNIFORM_NBLOCKS: usize = (768 + STREAM128_BLOCKBYTES - 1) / STREAM128_BLOCKBYTES;
let mut buf_len = POLY_UNIFORM_NBLOCKS * STREAM128_BLOCKBYTES;
let mut buf = [0u8; POLY_UNIFORM_NBLOCKS * STREAM128_BLOCKBYTES + 2];
let mut xof = hasher.xof_result();
xof.read(&mut buf[..buf_len]);
let mut ctr = rej_uniform(a, 0, &buf, buf_len);
while ctr < N {
let off = buf_len % 3;
for i in 0..off {
buf[i] = buf[buf_len - off + i];
}
for i in 0..STREAM128_BLOCKBYTES {
buf[off + i] = 0;
}
xof.read(&mut buf[off..off + STREAM128_BLOCKBYTES]);
buf_len = STREAM128_BLOCKBYTES + off;
ctr += rej_uniform(a, ctr, &buf, buf_len);
}
}
/// Samples a polynomial with random coefficients in `[-ETA, ETA]`.
///
/// The sampling is done by performing rejection sampling on the output stream
/// of `SHAKE256(seed|nonce)`.
///
/// # Arguments
///
/// * `a` - the output polynomial
/// * `seed` - an array of random bytes
/// * `nonce` - a number.
pub fn uniform_eta(a: &mut Poly, seed: &[u8; CRHBYTES], nonce: u16) {
use digest::{ExtendableOutput, Input, XofReader};
use sha3::Shake256;
const STREAM256_BLOCKBYTES: usize = 136;
const POLY_UNIFORM_ETA_NBLOCKS: usize = match ETA {
2 => (136 + STREAM256_BLOCKBYTES - 1) / STREAM256_BLOCKBYTES,
_ => (227 + STREAM256_BLOCKBYTES - 1) / STREAM256_BLOCKBYTES,
};
fn rej_eta(a: &mut [i32], a_start: usize, buf: &[u8], buf_len: usize) -> usize {
let mut ctr = 0;
let mut pos = 0;
while a_start + ctr < a.len() && pos < buf_len {
let mut t0 = (buf[pos] as u32) & 0x0F;
let mut t1 = (buf[pos] as u32) >> 4;
pos += 1;
if ETA == 2 {
if t0 < 15 {
t0 = t0 - (205 * t0 >> 10) * 5;
a[a_start + ctr] = 2 - (t0 as i32);
ctr += 1;
}
if t1 < 15 && a_start + ctr < a.len() {
t1 = t1 - (205 * t1 >> 10) * 5;
a[a_start + ctr] = 2 - (t1 as i32);
ctr += 1;
}
} else if ETA == 4 {
if t0 < 9 {
a[a_start + ctr] = 4 - (t0 as i32);
ctr += 1;
}
if t1 < 9 && a_start + ctr < a.len() {
a[a_start + ctr] = 4 - (t1 as i32);
ctr += 1;
}
}
}
ctr
}
let buf_len = POLY_UNIFORM_ETA_NBLOCKS * STREAM256_BLOCKBYTES;
let mut buf = [0u8; POLY_UNIFORM_ETA_NBLOCKS * STREAM256_BLOCKBYTES];
let mut hasher = Shake256::default();
hasher.process(seed);
let nonce0 = (nonce & ((1 << 8) - 1)) as u8;
let nonce1 = (nonce >> 8) as u8;
hasher.process(&[nonce0, nonce1]);
let mut xof = hasher.xof_result();
xof.read(&mut buf[..buf_len]);
let mut ctr = rej_eta(a, 0, &buf, buf_len);
while ctr < N {
xof.read(&mut buf[..STREAM256_BLOCKBYTES]);
ctr += rej_eta(a, ctr, &buf, STREAM256_BLOCKBYTES);
}
}
/// Samples a polynomial with random coefficients in `[-(GAMMA1 - 1), GAMMA1]`.
///
/// The sampling is done by unpacking the first `POLZ_SIZE_PACKED` bytes in the
/// output stream of `SHAKE256(seed|nonce)`.
///
/// # Arguments
///
/// * `a` - the output polynomial
/// * `seed` - an array of random bytes
/// * `nonce` - a number.
pub fn uniform_gamma1m1(a: &mut Poly, seed: &[u8; CRHBYTES], nonce: u16) {
use digest::{ExtendableOutput, Input, XofReader};
use sha3::Shake256;
const SHAKE256_RATE: usize = 136;
let mut outbuf = [0; 5 * SHAKE256_RATE];
let mut nonce_bytes = [0; 2];
LittleEndian::write_u16(&mut nonce_bytes, nonce);
let mut hasher = Shake256::default();
hasher.process(seed);
hasher.process(&nonce_bytes);
let mut xof = hasher.xof_result();
xof.read(&mut outbuf);
z_unpack(a, array_ref!(&outbuf, 0, POLZ_SIZE_PACKED));
}
/// Returns a polynomial with coefficients in {0, -1, 1}.
///
/// Returns a polynomial sampled with `TAU` nonzero coefficients in
/// {-1, 1} and `N - TAU` zero coefficients using the output stream
/// of `SHAKE256(seed)`.
/// More details can be found in the paper, in section 2.3.
/// <https://eprint.iacr.org/2017/633.pdf>
///
/// # Arguments
///
/// * `seed` - an array of bytes
pub fn build_challenge_from_seed(seed: &[u8; SEEDBYTES]) -> Poly {
use digest::{ExtendableOutput, Input, XofReader};
use sha3::Shake256;
const SHAKE256_RATE: usize = 136;
let mut outbuf = [0u8; SHAKE256_RATE];
let mut hasher = Shake256::default();
hasher.process(seed);
let mut xof = hasher.xof_result();
xof.read(&mut outbuf);
let mut signs: u64 = 0;
for i in 0..8 {
signs |= (outbuf[i] as u64) << 8 * i;
}
let mut pos = 8;
let mut c = [0i32; N];
for i in (N - TAU)..N {
let b = loop {
if pos >= SHAKE256_RATE {
xof.read(&mut outbuf);
pos = 0;
}
let b = outbuf[pos] as usize;
pos += 1;
if b <= i {
break b;
}
};
c[i] = c[b];
c[b] = 1i32 - (2 * (signs & 1) as i32);
signs >>= 1;
}
c
}
/// Bit-packs a polynomial with coefficients in `[-ETA, ETA]`.
///
/// # Arguments
///
/// * `r` - the output array, which will contain the polynomial's encoding
/// * `a` - the polynomial to encode
#[inline]
pub fn eta_pack(r: &mut [u8; POLETA_SIZE_PACKED], a: &Poly) {
if ETA == 2 {
let mut t = [0u8; 8];
for i in 0..(N / 8) {
for j in 0..8 {
t[j] = (ETA - a[8 * i + j]) as u8;
}
r[3 * i + 0] = (t[0] >> 0) | (t[1] << 3) | (t[2] << 6);
r[3 * i + 1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7);
r[3 * i + 2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5);
}
} else {
let mut t = [0u8; 2];
for i in 0..(N / 2) {
t[0] = (ETA - a[2 * i + 0]) as u8;
t[1] = (ETA - a[2 * i + 1]) as u8;
r[i] = t[0] | (t[1] << 4);
}
}
}
/// Unpacks a polynomial with coefficients in `[-ETA, ETA]`.
///
/// # Arguments
///
/// * `r` - the output decoded polynomial
/// * `a` - the polynomial's encoding
#[inline]
pub fn eta_unpack(r: &mut Poly, a: &[u8; POLETA_SIZE_PACKED]) {
if ETA == 2 {
for i in 0..(N / 8) {
r[8 * i + 0] = ((a[3 * i + 0] as i32) >> 0) & 7;
r[8 * i + 1] = ((a[3 * i + 0] as i32) >> 3) & 7;
r[8 * i + 2] = (((a[3 * i + 0] as i32) >> 6) | ((a[3 * i + 1] as i32) << 2)) & 7;
r[8 * i + 3] = ((a[3 * i + 1] as i32) >> 1) & 7;
r[8 * i + 4] = ((a[3 * i + 1] as i32) >> 4) & 7;
r[8 * i + 5] = (((a[3 * i + 1] as i32) >> 7) | ((a[3 * i + 2] as i32) << 1)) & 7;
r[8 * i + 6] = ((a[3 * i + 2] as i32) >> 2) & 7;
r[8 * i + 7] = ((a[3 * i + 2] as i32) >> 5) & 7;
for j in 0..8 {
r[8 * i + j] = ETA - r[8 * i + j];
}
}
} else {
for i in 0..(N / 2) {
r[2 * i + 0] = (a[i] as i32) & 0x0F;
r[2 * i + 1] = (a[i] as i32) >> 4;
r[2 * i + 0] = ETA - r[2 * i + 0];
r[2 * i + 1] = ETA - r[2 * i + 1];
}
}
}
/// Bit-packs a polynomial with coefficients fitting in 10 bits.
///
/// # Arguments
///
/// * `r` - the output array, which will contain the polynomial's encoding
/// * `a` - the polynomial to encode
#[inline]
pub fn t1_pack(r: &mut [u8; POLT1_SIZE_PACKED], a: &Poly) {
for i in 0..(N / 4) {
r[5 * i + 0] = (a[4 * i + 0] >> 0) as u8;
r[5 * i + 1] = ((a[4 * i + 0] >> 8) | (a[4 * i + 1] << 2)) as u8;
r[5 * i + 2] = ((a[4 * i + 1] >> 6) | (a[4 * i + 2] << 4)) as u8;
r[5 * i + 3] = ((a[4 * i + 2] >> 4) | (a[4 * i + 3] << 6)) as u8;
r[5 * i + 4] = (a[4 * i + 3] >> 2) as u8;
}
}
/// Unpacks a polynomial with coefficients fitting in 10 bits.
///
/// # Arguments
///
/// * `r` - the output decoded polynomial
/// * `a` - the polynomial's encoding
#[inline]
pub fn t1_unpack(r: &mut Poly, a: &[u8; POLT1_SIZE_PACKED]) {
for i in 0..(N / 4) {
r[4 * i + 0] =
((((a[5 * i + 0] >> 0) as u32) | ((a[5 * i + 1] as u32) << 8)) & 0x3FF) as i32;
r[4 * i + 1] =
((((a[5 * i + 1] >> 2) as u32) | ((a[5 * i + 2] as u32) << 6)) & 0x3FF) as i32;
r[4 * i + 2] =
((((a[5 * i + 2] >> 4) as u32) | ((a[5 * i + 3] as u32) << 4)) & 0x3FF) as i32;
r[4 * i + 3] =
((((a[5 * i + 3] >> 6) as u32) | ((a[5 * i + 4] as u32) << 2)) & 0x3FF) as i32;
}
}
/// Packs a polynomial with coefficients in `[-(GAMMA1 - 1), GAMMA1]`.
///
/// # Arguments
///
/// * `r` - the output array, which will contain the polynomial's encoding
/// * `a` - the polynomial to encode
#[inline]
pub fn z_pack(r: &mut [u8; POLZ_SIZE_PACKED], a: &Poly) {
let mut t = [0u32; 4];
if GAMMA1 == (1 << 17) {
for i in 0..(N / 4) {
for j in 0..4 {
t[j] = (GAMMA1 - a[4 * i + j]) as u32;
}
r[9 * i + 0] = t[0] as u8;
r[9 * i + 1] = (t[0] >> 8) as u8;
r[9 * i + 2] = (t[0] >> 16) as u8;
r[9 * i + 2] |= (t[1] << 2) as u8;
r[9 * i + 3] = (t[1] >> 6) as u8;
r[9 * i + 4] = (t[1] >> 14) as u8;
r[9 * i + 4] |= (t[2] << 4) as u8;
r[9 * i + 5] = (t[2] >> 4) as u8;
r[9 * i + 6] = (t[2] >> 12) as u8;
r[9 * i + 6] |= (t[3] << 6) as u8;
r[9 * i + 7] = (t[3] >> 2) as u8;
r[9 * i + 8] = (t[3] >> 10) as u8;
}
} else if GAMMA1 == (1 << 19) {
for i in 0..(N / 2) {
t[0] = (GAMMA1 - a[2 * i + 0]) as u32;
t[1] = (GAMMA1 - a[2 * i + 1]) as u32;
r[5 * i + 0] = t[0] as u8;
r[5 * i + 1] = (t[0] >> 8) as u8;
r[5 * i + 2] = (t[0] >> 16) as u8;
r[5 * i + 2] |= (t[1] << 4) as u8;
r[5 * i + 3] = (t[1] >> 4) as u8;
r[5 * i + 4] = (t[1] >> 12) as u8;
}
}
}
/// Unpacks a polynomial with coefficients in `[-(GAMMA1 - 1), GAMMA1]`.
///
/// # Arguments
///
/// * `r` - the output decoded polynomial
/// * `a` - the polynomial's encoding
#[inline]
pub fn z_unpack(r: &mut Poly, a: &[u8; POLZ_SIZE_PACKED]) {
if GAMMA1 == (1 << 17) {
for i in 0..(N / 4) {
r[4 * i + 0] = a[9 * i + 0] as i32;
r[4 * i + 0] |= (a[9 * i + 1] as i32) << 8;
r[4 * i + 0] |= (a[9 * i + 2] as i32) << 16;
r[4 * i + 0] &= 0x3FFFF;
r[4 * i + 1] = (a[9 * i + 2] >> 2) as i32;
r[4 * i + 1] |= (a[9 * i + 3] as i32) << 6;
r[4 * i + 1] |= (a[9 * i + 4] as i32) << 14;
r[4 * i + 1] &= 0x3FFFF;
r[4 * i + 2] = (a[9 * i + 4] >> 4) as i32;
r[4 * i + 2] |= (a[9 * i + 5] as i32) << 4;
r[4 * i + 2] |= (a[9 * i + 6] as i32) << 12;
r[4 * i + 2] &= 0x3FFFF;
r[4 * i + 3] = (a[9 * i + 6] >> 6) as i32;
r[4 * i + 3] |= (a[9 * i + 7] as i32) << 2;
r[4 * i + 3] |= (a[9 * i + 8] as i32) << 10;
r[4 * i + 3] &= 0x3FFFF;
for j in 0..4 {
r[4 * i + j] = GAMMA1 - r[4 * i + j];
}
}
} else if GAMMA1 == (1 << 19) {
for i in 0..(N / 2) {
r[2 * i + 0] = a[5 * i + 0] as i32;
r[2 * i + 0] |= (a[5 * i + 1] as i32) << 8;
r[2 * i + 0] |= (a[5 * i + 2] as i32) << 16;
r[2 * i + 0] &= 0xFFFFF;
r[2 * i + 1] = (a[5 * i + 2] >> 4) as i32;
r[2 * i + 1] |= (a[5 * i + 3] as i32) << 4;
r[2 * i + 1] |= (a[5 * i + 4] as i32) << 12;
r[2 * i + 0] &= 0xFFFFF;
r[2 * i + 0] = GAMMA1 - r[2 * i + 0];
r[2 * i + 1] = GAMMA1 - r[2 * i + 1];
}
}
}
/// Bit-packs a polynomial with coefficients in `[0,15]` or `[0,43]`.
///
/// # Arguments
///
/// * `r` - the output array, which will contain the polynomial's encoding
/// * `a` - the polynomial to encode
#[inline]
pub fn w1_pack(r: &mut [u8; POLW1_SIZE_PACKED], a: &Poly) {
if GAMMA2 == (Q - 1) / 88 {
for i in 0..(N / 4) {
r[3 * i + 0] = a[4 * i + 0] as u8;
r[3 * i + 0] |= (a[4 * i + 1] << 6) as u8;
r[3 * i + 1] = (a[4 * i + 1] >> 2) as u8;
r[3 * i + 1] |= (a[4 * i + 2] << 4) as u8;
r[3 * i + 2] = (a[4 * i + 2] >> 4) as u8;
r[3 * i + 2] |= (a[4 * i + 3] << 2) as u8;
}
} else if GAMMA2 == (Q - 1) / 32 {
for i in 0..(N / 2) {
r[i] = (a[2 * i + 0] | (a[2 * i + 1] << 4)) as u8;
}
}
}
/// Bit-packs a polynomial `t0` with coefficients in `[-2^{D-1}, 2^{D-1}]`.
///
/// # Arguments
///
/// * `r` - the output array, which will contain the polynomial's encoding
/// * `a` - the polynomial to encode
#[inline]
pub fn t0_pack(r: &mut [u8], a: &Poly) {
let mut t = [0u32; 8];
for i in 0..(N / 8) {
for j in 0..8 {
t[j] = ((1 << (D - 1) as u32) - a[8 * i + j]) as u32;
}
r[13 * i + 0] = (t[0]) as u8;
r[13 * i + 1] = (t[0] >> 8) as u8;
r[13 * i + 1] |= (t[1] << 5) as u8;
r[13 * i + 2] = (t[1] >> 3) as u8;
r[13 * i + 3] = (t[1] >> 11) as u8;
r[13 * i + 3] |= (t[2] << 2) as u8;
r[13 * i + 4] = (t[2] >> 6) as u8;
r[13 * i + 4] |= (t[3] << 7) as u8;
r[13 * i + 5] = (t[3] >> 1) as u8;
r[13 * i + 6] = (t[3] >> 9) as u8;
r[13 * i + 6] |= (t[4] << 4) as u8;
r[13 * i + 7] = (t[4] >> 4) as u8;
r[13 * i + 8] = (t[4] >> 12) as u8;
r[13 * i + 8] |= (t[5] << 1) as u8;
r[13 * i + 9] = (t[5] >> 7) as u8;
r[13 * i + 9] |= (t[6] << 6) as u8;
r[13 * i + 10] = (t[6] >> 2) as u8;
r[13 * i + 11] = (t[6] >> 10) as u8;
r[13 * i + 11] |= (t[7] << 3) as u8;
r[13 * i + 12] = (t[7] >> 5) as u8;
}
}

View File

@@ -1,167 +0,0 @@
#![allow(dead_code)]
use params::{K, L, N};
use poly::{self, Poly};
macro_rules! polyvec {
( $polyvec:ident, $len:expr ) => {
#[derive(Copy, Clone)]
pub struct $polyvec(pub [Poly; $len]);
impl $polyvec {
pub fn reduce(&mut self) {
self.0.iter_mut().for_each(poly::reduce)
}
pub fn caddq(&mut self) {
self.0.iter_mut().for_each(poly::caddq)
}
pub fn freeze(&mut self) {
self.0.iter_mut().for_each(poly::freeze)
}
pub fn with_add(&mut self, u: &Self, v: &Self) {
for i in 0..$len {
poly::add(&mut self[i], &u[i], &v[i]);
}
}
pub fn add_assign(&mut self, u: &Self) {
for i in 0..$len {
poly::add_assign(&mut self[i], &u[i]);
}
}
pub fn with_sub(&mut self, u: &Self, v: &Self) {
for i in 0..$len {
poly::sub(&mut self[i], &u[i], &v[i]);
}
}
pub fn shift_left(&mut self) {
self.0.iter_mut().for_each(|p| poly::shift_left(p));
}
pub fn ntt(&mut self) {
self.0.iter_mut().for_each(poly::ntt);
}
pub fn invntt_montgomery(&mut self) {
self.0.iter_mut().for_each(poly::invntt_montgomery)
}
pub fn chknorm(&self, bound: i32) -> bool {
self.0
.iter()
.map(|p| poly::chknorm(p, bound))
.fold(false, |x, y| x | y)
}
}
impl ::core::ops::Index<usize> for $polyvec {
type Output = Poly;
#[inline(always)]
fn index(&self, i: usize) -> &Self::Output {
self.0.index(i)
}
}
impl ::core::ops::IndexMut<usize> for $polyvec {
#[inline(always)]
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
self.0.index_mut(i)
}
}
impl ::core::cmp::PartialEq for $polyvec {
fn eq(&self, other: &Self) -> bool {
self.0
.iter()
.zip(&other.0)
.flat_map(|(x, y)| x.iter().zip(y.iter()))
.all(|(x, y)| x == y)
}
}
impl Eq for $polyvec {}
impl Default for $polyvec {
fn default() -> Self {
$polyvec([[0; N]; $len])
}
}
};
}
polyvec!(PolyVecL, L);
polyvec!(PolyVecK, K);
pub fn pointwise_acc_invmontgomery(w: &mut Poly, u: &PolyVecL, v: &PolyVecL) {
let mut t = [0; N];
poly::pointwise_invmontgomery(w, &u[0], &v[0]);
for i in 1..L {
poly::pointwise_invmontgomery(&mut t, &u[i], &v[i]);
poly::add_assign(w, &t);
}
}
/// Computes a partial result of the dot product `w = u * v`.
///
/// # Arguments
/// * `w` - the output polynomial, which will contain the partial result
/// * `u_component` - the polynomial `u[i]`
/// * `v_component` - the polynomial `v[i]`
/// * `i` - the index
pub fn pointwise_acc_invmontgomery_componentwise(
w: &mut Poly,
u_component: &Poly,
v_component: &Poly,
i: usize,
) {
if i == 0 {
poly::pointwise_invmontgomery(w, &u_component, &v_component);
return;
}
let mut t = [0; N];
poly::pointwise_invmontgomery(&mut t, &u_component, &v_component);
poly::add_assign(w, &t);
}
impl PolyVecK {
pub fn power2round(&self, v0: &mut Self, v1: &mut Self) {
for i in 0..K {
poly::power2round(&self[i], &mut v0[i], &mut v1[i]);
}
}
pub fn power2round_remainder(&self, v0: &mut Self) {
for i in 0..K {
v0[i] = poly::power2round_remainder(&self[i]);
}
}
pub fn decompose(&self, v0: &mut Self, v1: &mut Self) {
for i in 0..K {
poly::decompose(&self[i], &mut v0[i], &mut v1[i]);
}
}
}
pub fn make_hint(u: &PolyVecK, v: &PolyVecK, h: &mut PolyVecK) -> usize {
let mut s = 0;
for i in 0..K {
s += poly::make_hint(&u[i], &v[i], &mut h[i]);
}
s
}
pub fn use_hint(w: &mut PolyVecK, u: &PolyVecK, h: &PolyVecK) {
for i in 0..K {
poly::use_hint(&mut w[i], &u[i], &h[i]);
}
}

View File

@@ -1,52 +0,0 @@
use params::{Q, QINV};
/// Returns a value between `-Q` and `Q` that is equivalent to `a`.
///
/// For a finite field element `a` with `-2^{31}*Q <= a <= Q*2^31`,
/// computes `r` equivalent to `a*2^{-32} (mod Q)` such that `-Q < r < Q`.
///
/// # Arguments
///
/// * `a` - a number between `2^{31}*Q` and `Q*2^31`.
pub fn montgomery_reduce(a: i64) -> i32 {
let mut t: i32 = (((a as i32) as i64) * (QINV as i64)) as i32;
t = ((a - (t as i64) * (Q as i64)) >> 32) as i32;
t
}
/// Returns a value between `-6283009` and `6283007` that is equivalent to `a`.
///
/// For a finite field element `a` with `a <= 2^{31} - 2^{22} - 1`,
/// computes `r` equivalent to `a (mod Q)` such that
/// `-6283009 <= r <= 6283007`.
///
/// # Arguments
///
/// * `a` - a number between `2^{31}*Q` and `Q*2^31`.
pub fn reduce32(a: i32) -> i32 {
let mut t: i32 = (a + (1 << 22)) >> 23;
t = a - t * Q;
t
}
/// Adds `Q` if the input finite field element is negative.
///
/// # Arguments
///
/// * `a` - a number.
pub fn caddq(a: i32) -> i32 {
let mut t = a;
t += (a >> 31) & Q;
t
}
/// Computes the standard representative `r = a mod Q`.
///
/// # Arguments
///
/// * `a` - a number.
pub fn freeze(a: i32) -> i32 {
let a = reduce32(a);
let a = caddq(a);
a
}

View File

@@ -1,93 +0,0 @@
use params::{D, GAMMA2, Q};
/// Returns the remainder and the quotient of `a` divided by `2^{D-1}`.
///
/// For a finite field element `a`, computes `a0` and `a1` such that
/// `a mod Q = a1*2^D + a0` with `-2^{D-1} < a0 <= 2^{D-1}`.
///
/// # Arguments
///
/// * `a` - a number assumed to be a standard representative modulo `Q`.
pub fn power2round(a: i32) -> (i32, i32) {
let a1: i32 = (a + (1 << (D - 1)) - 1) >> D;
let a0: i32 = a - (a1 << D);
(a0, a1)
}
/// Computes the high bits and low bits of `a`.
///
/// For a finite field element `a`, computes the high and the low bits `a1`
/// and respectively `a0`, such that `a mod Q = a1*ALPHA + a0`
/// with `-ALPHA/2 < a0 <= ALPHA/2`.
/// Exception: If `a1 = (Q-1)/ALPHA`, `a0` is set to 0.
///
/// # Arguments
///
/// * `a` - a number assumed to be a standard representative modulo `Q`.
pub fn decompose(a: i32) -> (i32, i32) {
let mut a1: i32 = (a + 127) >> 7;
if GAMMA2 == (Q - 1) / 32 {
a1 = (a1 * 1025 + (1 << 21)) >> 22;
a1 &= 15;
} else if GAMMA2 == (Q - 1) / 88 {
a1 = (a1 * 11275 + (1 << 23)) >> 24;
a1 ^= ((43 - a1) >> 31) & a1;
}
let mut a0: i32 = a - a1 * 2 * GAMMA2;
a0 -= (((Q - 1) / 2 - a0) >> 31) & Q;
(a0, a1)
}
/// Computes the hint bit.
///
/// The hint bit indicates whether the low bits `a0` overflow into the
/// the high bits `a1`.
///
/// # Arguments
///
/// * `a0` - a number representing the low bits of some element `a`
/// * `a1` - a number representing the high bits of the same element `a`
pub fn make_hint(a0: i32, a1: i32) -> u32 {
if a0 > GAMMA2 || a0 < -GAMMA2 || (a0 == -GAMMA2 && a1 != 0) {
1
} else {
0
}
}
/// Uses the given hint to correct the high bits of a.
///
/// # Arguments
///
/// * `a` - the number to be corrected
/// * `hint` - a value 0 or 1
pub fn use_hint(a: i32, hint: u32) -> i32 {
let (a0, a1) = decompose(a);
if hint == 0 {
a1
} else if GAMMA2 == (Q - 1) / 32 {
if a0 > 0 {
(a1 + 1) & 15
} else {
(a1 - 1) & 15
}
} else {
if a0 > 0 {
if a1 == 43 {
0
} else {
a1 + 1
}
} else {
if a1 == 0 {
43
} else {
a1 - 1
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,64 +0,0 @@
extern crate rng256;
use super::*;
use params::{N, Q};
use poly::Poly;
const NTESTS: usize = 10000;
fn poly_naivemul(c: &mut Poly, a: &Poly, b: &Poly) {
let mut r = [0; 2 * N];
for i in 0..N {
for j in 0..N {
r[i + j] += (((a[i] as i64) * (b[j] as i64)) % (Q as i64)) as i32;
}
}
for i in N..(2 * N) {
r[i - N] = (r[i - N] - r[i]) % Q;
}
c.copy_from_slice(&r[..N]);
}
#[test]
fn test_mul() {
use self::rng256::Rng256;
let mut rndbuf = [0; 32];
let mut c = [0; N];
let (mut c1, mut c2) = ([0; N], [0; N]);
let (mut a, mut b) = ([0; N], [0; N]);
let mut rng = rng256::ThreadRng256 {};
for _ in 0..NTESTS {
rng.fill_bytes(&mut rndbuf);
poly::uniform(&mut a, &rndbuf, 0);
rng.fill_bytes(&mut rndbuf);
poly::uniform(&mut b, &rndbuf, 0);
c.copy_from_slice(&a[..N]);
poly::ntt(&mut c);
for j in 0..N {
c[j] = ((c[j] as i64) * -114592 % (Q as i64)) as i32;
}
poly::invntt_montgomery(&mut c);
for j in 0..N {
assert_eq!((c[j] - a[j]) % Q, 0);
}
poly_naivemul(&mut c1, &a, &b);
poly::ntt(&mut a);
poly::ntt(&mut b);
poly::pointwise_invmontgomery(&mut c2, &a, &b);
poly::invntt_montgomery(&mut c2);
for j in 0..N {
assert_eq!((c1[j] - c2[j]) % Q, 0);
}
}
}

View File

@@ -1,10 +0,0 @@
macro_rules! shake256 {
( $output:expr ; $( $input:expr ),* ) => {
let mut hasher = ::sha3::Shake256::default();
$(
::digest::Input::process(&mut hasher, $input);
)*
let mut reader = ::digest::ExtendableOutput::xof_result(hasher);
::digest::XofReader::read(&mut reader, $output);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,65 +0,0 @@
extern crate dilithium;
extern crate rng256;
use dilithium::sign::{PubKey, SecKey};
use rng256::Rng256;
const ITERATIONS: u32 = 500;
#[test]
fn test_sk_with_pk() {
let mut rng = rng256::ThreadRng256 {};
for _ in 0..ITERATIONS {
let (sk, pk) = SecKey::gensk_with_pk(&mut rng);
let pk_from_sk = sk.genpk();
assert_eq!(pk, pk_from_sk);
}
}
#[test]
fn test_sign() {
let mut rng = rng256::ThreadRng256 {};
for _ in 0..ITERATIONS {
let sk = SecKey::gensk(&mut rng);
let mut message = [0; 59];
rng.fill_bytes(&mut message);
let sig = sk.sign(&message);
let pk = sk.genpk();
let mut bytes = [0; dilithium::params::PK_SIZE_PACKED];
pk.to_bytes(&mut bytes);
assert!(pk.verify(&message, &sig));
message[2] ^= 42;
assert!(!pk.verify(&message, &sig));
}
}
#[test]
fn test_seckey_to_bytes_from_bytes() {
let mut rng = rng256::ThreadRng256 {};
for _ in 0..ITERATIONS {
let sk = SecKey::gensk(&mut rng);
let mut bytes = [0; dilithium::params::SK_SIZE_PACKED];
sk.to_bytes(&mut bytes);
let decoded_sk = SecKey::from_bytes(&bytes);
assert_eq!(decoded_sk, sk);
}
}
#[test]
fn test_pubkey_to_bytes_from_bytes() {
let mut rng = rng256::ThreadRng256 {};
for _ in 0..ITERATIONS {
let sk = SecKey::gensk(&mut rng);
let pk = sk.genpk();
let mut bytes = [0; dilithium::params::PK_SIZE_PACKED];
pk.to_bytes(&mut bytes);
let decoded_pk = PubKey::from_bytes(&bytes);
assert_eq!(decoded_pk, pk);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -169,7 +169,7 @@ def main(args):
("Failed to configure OpenSK (device is partially programmed but "
"the given cert/key don't match the ones currently programmed)."))
else:
error(f"Failed to configure OpenSK (unknown error: {ex})")
error(f"Failed to configure OpenSK (unknown error: {ex}")
return responses

View File

@@ -20,6 +20,7 @@ from __future__ import division
from __future__ import print_function
import argparse
import datetime
import hashlib
import os
import struct
@@ -43,7 +44,6 @@ OPENSK_VID_PID = (0x1915, 0x521F)
OPENSK_VENDOR_UPGRADE = 0x42
OPENSK_VENDOR_UPGRADE_INFO = 0x43
PAGE_SIZE = 0x1000
METADATA_SIGN_OFFSET = 0x800
KERNEL_SIZE = 0x20000
APP_SIZE = 0x20000
PARTITION_ADDRESS = {
@@ -54,15 +54,7 @@ ES256_ALGORITHM = -7
ARCH = "thumbv7em-none-eabi"
def hash_message(message: bytes) -> bytes:
"""Uses SHA256 to hash a message."""
sha256_hash = hashlib.sha256()
sha256_hash.update(message)
return sha256_hash.digest()
def create_metadata(firmware_image: bytes, partition_address: int, version: int,
priv_key: Any) -> bytes:
def create_metadata(firmware_image: bytes, partition_address: int) -> bytes:
"""Creates the matching metadata for the given firmware.
The metadata consists of a timestamp, the expected address and a hash of
@@ -73,26 +65,25 @@ def create_metadata(firmware_image: bytes, partition_address: int, version: int,
partition_address: The address to be written as a metadata property.
Returns:
A byte array of page size, consisting of
- 32 B hash,
- 64 B signature,
at the beginning and
- 8 B version and
- 4 B partition address in little endian encoding
after METADATA_SIGN_OFFSET. All other bytes are 0xFF.
A byte array consisting of 32B hash, 4B timestamp and 4B partition address
in little endian encoding.
"""
if version < 0 or version >= 2**63:
fatal("The version must fit into an unsigned integer with 63 bit.\n"
"Please pass it using --version")
version_bytes = struct.pack("<Q", version)
t = datetime.datetime.utcnow().timestamp()
timestamp = struct.pack("<I", int(t))
partition_start = struct.pack("<I", partition_address)
# Prefix sizes that are a multiple of 64 suit our bootloader's SHA.
signed_metadata = pad_to(version_bytes + partition_start,
PAGE_SIZE - METADATA_SIGN_OFFSET)
signed_data = signed_metadata + firmware_image
checksum = hash_message(signed_data)
signature = sign_firmware(signed_data, priv_key)
return pad_to(checksum + signature, METADATA_SIGN_OFFSET) + signed_metadata
sha256_hash = hashlib.sha256()
sha256_hash.update(firmware_image)
sha256_hash.update(timestamp)
sha256_hash.update(partition_start)
checksum = sha256_hash.digest()
return checksum + timestamp + partition_start
def hash_message(message: bytes) -> bytes:
"""Uses SHA256 to hash a message."""
sha256_hash = hashlib.sha256()
sha256_hash.update(message)
return sha256_hash.digest()
def check_info(partition_address: int, authenticator: Any):
@@ -104,10 +95,9 @@ def check_info(partition_address: int, authenticator: Any):
data={},
)
if result[0x01] != partition_address:
fatal(f"Identifiers do not match, received 0x{result[0x01]:0x}, "
f"expected 0x{partition_address:0x}.")
fatal("Identifiers do not match.")
except ctap.CtapError as ex:
fatal(f"Failed to read OpenSK upgrade info (error: {ex})")
error(f"Failed to read OpenSK upgrade info (error: {ex}")
def get_kernel(board: str) -> bytes:
@@ -147,21 +137,17 @@ def generate_firmware_image(board: str) -> bytes:
return pad_to(kernel, KERNEL_SIZE) + pad_to(app, APP_SIZE)
def load_priv_key(priv_key_filename: str) -> Any:
def load_priv_key(priv_key_file: argparse.FileType) -> Any:
"""Loads the ECDSA private key from the specified file."""
try:
with open(priv_key_filename, "rb") as priv_key_file:
priv_key = get_private_key(priv_key_file.read())
if not isinstance(priv_key, ec.EllipticCurvePrivateKey):
fatal("Private key must be an Elliptic Curve one.")
if not isinstance(priv_key.curve, ec.SECP256R1):
fatal("Private key must use Secp256r1 curve.")
if priv_key.key_size != 256:
fatal("Private key must be 256 bits long.")
info("Private key is valid.")
return priv_key
except IOError as e:
fatal(f"Unable to open file: {priv_key_filename}\n{e}")
priv_key = get_private_key(priv_key_file.read())
if not isinstance(priv_key, ec.EllipticCurvePrivateKey):
fatal("Private key must be an Elliptic Curve one.")
if not isinstance(priv_key.curve, ec.SECP256R1):
fatal("Private key must use Secp256r1 curve.")
if priv_key.key_size != 256:
fatal("Private key must be 256 bits long.")
info("Private key is valid.")
return priv_key
def sign_firmware(data: bytes, priv_key: Any) -> bytes:
@@ -173,15 +159,19 @@ def sign_firmware(data: bytes, priv_key: Any) -> bytes:
def main(args):
colorama.init()
if not args.priv_key:
fatal("Please pass in a private key file using --private-key.")
firmware_image = generate_firmware_image(args.board)
partition_address = PARTITION_ADDRESS[args.board]
metadata = create_metadata(firmware_image, partition_address)
if not args.priv_key:
fatal("Please pass in a private key file using --private-key.")
priv_key = load_priv_key(args.priv_key)
metadata = create_metadata(firmware_image, partition_address, args.version,
priv_key)
partition = metadata + firmware_image
signed_data = firmware_image + metadata[32:40]
signature = {
"alg": ES256_ALGORITHM,
"signature": sign_firmware(signed_data, priv_key)
}
if args.use_vendor_hid:
patcher = patch.object(hid.base, "FIDO_USAGE_PAGE", 0xFF00)
@@ -199,18 +189,11 @@ def main(args):
aaguid = uuid.UUID(bytes=authenticator.get_info().aaguid)
info(f"Upgrading OpenSK device AAGUID {aaguid} ({authenticator.device}).")
running_version = authenticator.get_info().firmware_version
if args.version < running_version:
fatal(f"Can not write version {args.version} when version "
f"{running_version} is running.")
else:
info(f"Running version: {running_version}")
try:
check_info(partition_address, authenticator)
offset = 0
for offset in range(0, len(partition), PAGE_SIZE):
page = partition[offset:][:PAGE_SIZE]
for offset in range(0, len(firmware_image), PAGE_SIZE):
page = firmware_image[offset:][:PAGE_SIZE]
info(f"Writing at offset 0x{offset:08X}...")
cbor_data = {1: offset, 2: page, 3: hash_message(page)}
authenticator.send_cbor(
@@ -218,20 +201,26 @@ def main(args):
data=cbor_data,
)
info("Writing metadata...")
cbor_data = {2: metadata, 3: hash_message(metadata), 4: signature}
authenticator.send_cbor(
OPENSK_VENDOR_UPGRADE,
data=cbor_data,
)
except ctap.CtapError as ex:
message = "Failed to upgrade OpenSK"
if ex.code.value == ctap.CtapError.ERR.INVALID_COMMAND:
error(f"{message} (unsupported command).")
elif ex.code.value == ctap.CtapError.ERR.INVALID_PARAMETER:
error(f"{message} (invalid parameter).")
elif ex.code.value == ctap.CtapError.ERR.INTEGRITY_FAILURE:
error(f"{message} (data hash does not match slice).")
error(f"{message} (invalid parameter, maybe a wrong byte array size?).")
elif ex.code.value == ctap.CtapError.ERR_INTEGRITY_FAILURE:
error(f"{message} (hashes or signature don't match).")
elif ex.code.value == 0xF2: # VENDOR_INTERNAL_ERROR
error(f"{message} (internal conditions not met).")
elif ex.code.value == 0xF3: # VENDOR_HARDWARE_FAILURE
error(f"{message} (internal hardware error).")
else:
error(f"{message} (unexpected error: {ex})")
error(f"{message} (unexpected error: {ex}")
if __name__ == "__main__":
@@ -258,7 +247,7 @@ if __name__ == "__main__":
)
parser.add_argument(
"--private-key",
type=str,
type=argparse.FileType("rb"),
default="crypto_data/opensk_upgrade.key",
dest="priv_key",
help=("PEM file for signing the firmware."),
@@ -270,10 +259,4 @@ if __name__ == "__main__":
dest="use_vendor_hid",
help=("Whether to upgrade the device using the Vendor HID interface."),
)
parser.add_argument(
"--version",
type=int,
dest="version",
help=("Firmware version that is built."),
)
main(parser.parse_args())