Merge pull request #239 from jmichelp/transparency
Add vendor commands to inject crypto materials
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ Cargo.lock
|
|||||||
/reproducible/binaries.sha256sum
|
/reproducible/binaries.sha256sum
|
||||||
/reproducible/elf2tab.txt
|
/reproducible/elf2tab.txt
|
||||||
/reproducible/reproduced.tar
|
/reproducible/reproduced.tar
|
||||||
|
__pycache__
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ctap2"
|
name = "ctap2"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Fabian Kaczmarczyck <kaczmarczyck@google.com>",
|
"Fabian Kaczmarczyck <kaczmarczyck@google.com>",
|
||||||
"Guillaume Endignoux <guillaumee@google.com>",
|
"Guillaume Endignoux <guillaumee@google.com>",
|
||||||
@@ -35,7 +35,6 @@ elf2tab = "0.6.0"
|
|||||||
enum-iterator = "0.6.0"
|
enum-iterator = "0.6.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
openssl = "0.10"
|
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
43
README.md
43
README.md
@@ -1,6 +1,5 @@
|
|||||||
# <img alt="OpenSK logo" src="docs/img/OpenSK.svg" width="200px">
|
# <img alt="OpenSK logo" src="docs/img/OpenSK.svg" width="200px">
|
||||||
|
|
||||||
[](https://travis-ci.org/google/OpenSK)
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
@@ -31,7 +30,8 @@ our implementation was not reviewed nor officially tested and doesn't claim to
|
|||||||
be FIDO Certified.
|
be FIDO Certified.
|
||||||
We started adding features of the upcoming next version of the
|
We started adding features of the upcoming next version of the
|
||||||
[CTAP2.1 specifications](https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html).
|
[CTAP2.1 specifications](https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html).
|
||||||
The development is currently between 2.0 and 2.1, with updates hidden behind a feature flag.
|
The development is currently between 2.0 and 2.1, with updates hidden behind
|
||||||
|
a feature flag.
|
||||||
Please add the flag `--ctap2.1` to the deploy command to include them.
|
Please add the flag `--ctap2.1` to the deploy command to include them.
|
||||||
|
|
||||||
### Cryptography
|
### Cryptography
|
||||||
@@ -58,8 +58,8 @@ For a more detailed guide, please refer to our
|
|||||||
./setup.sh
|
./setup.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Next step is to install Tock OS as well as the OpenSK application on your
|
1. Next step is to install Tock OS as well as the OpenSK application on your
|
||||||
board (**Warning**: it will erase the locally stored credentials). Run:
|
board. Run:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Nordic nRF52840-DK board
|
# Nordic nRF52840-DK board
|
||||||
@@ -68,7 +68,17 @@ For a more detailed guide, please refer to our
|
|||||||
./deploy.py --board=nrf52840_dongle --opensk
|
./deploy.py --board=nrf52840_dongle --opensk
|
||||||
```
|
```
|
||||||
|
|
||||||
3. On Linux, you may want to avoid the need for `root` privileges to interact
|
1. Finally you need to inject the cryptographic material if you enabled
|
||||||
|
batch attestation or CTAP1/U2F compatibility (which is the case by
|
||||||
|
default):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./tools/configure.py \
|
||||||
|
--certificate=crypto_data/opensk_cert.pem \
|
||||||
|
--private-key=crypto_data/opensk.key
|
||||||
|
```
|
||||||
|
|
||||||
|
1. On Linux, you may want to avoid the need for `root` privileges to interact
|
||||||
with the key. For that purpose we provide a udev rule file that can be
|
with the key. For that purpose we provide a udev rule file that can be
|
||||||
installed with the following command:
|
installed with the following command:
|
||||||
|
|
||||||
@@ -148,7 +158,7 @@ operation.
|
|||||||
|
|
||||||
The additional output looks like the following.
|
The additional output looks like the following.
|
||||||
|
|
||||||
```
|
```text
|
||||||
# Allocation of 256 byte(s), aligned on 1 byte(s). The allocated address is
|
# Allocation of 256 byte(s), aligned on 1 byte(s). The allocated address is
|
||||||
# 0x2002401c. After this operation, 2 pointers have been allocated, totalling
|
# 0x2002401c. After this operation, 2 pointers have been allocated, totalling
|
||||||
# 384 bytes (the total heap usage may be larger, due to alignment and
|
# 384 bytes (the total heap usage may be larger, due to alignment and
|
||||||
@@ -163,12 +173,12 @@ A tool is provided to analyze such reports, in `tools/heapviz`. This tool
|
|||||||
parses the console output, identifies the lines corresponding to (de)allocation
|
parses the console output, identifies the lines corresponding to (de)allocation
|
||||||
operations, and first computes some statistics:
|
operations, and first computes some statistics:
|
||||||
|
|
||||||
- Address range used by the heap over this run of the program,
|
* Address range used by the heap over this run of the program,
|
||||||
- Peak heap usage (how many useful bytes are allocated),
|
* Peak heap usage (how many useful bytes are allocated),
|
||||||
- Peak heap consumption (how many bytes are used by the heap, including
|
* Peak heap consumption (how many bytes are used by the heap, including
|
||||||
unavailable bytes between allocated blocks, due to alignment constraints and
|
unavailable bytes between allocated blocks, due to alignment constraints and
|
||||||
memory fragmentation),
|
memory fragmentation),
|
||||||
- Fragmentation overhead (difference between heap consumption and usage).
|
* Fragmentation overhead (difference between heap consumption and usage).
|
||||||
|
|
||||||
Then, the `heapviz` tool displays an animated "movie" of the allocated bytes in
|
Then, the `heapviz` tool displays an animated "movie" of the allocated bytes in
|
||||||
heap memory. Each frame in this "movie" shows bytes that are currently
|
heap memory. Each frame in this "movie" shows bytes that are currently
|
||||||
@@ -177,10 +187,11 @@ allocated. A new frame is generated for each (de)allocation operation. This tool
|
|||||||
uses the `ncurses` library, that you may have to install beforehand.
|
uses the `ncurses` library, that you may have to install beforehand.
|
||||||
|
|
||||||
You can control the tool with the following parameters:
|
You can control the tool with the following parameters:
|
||||||
- `--logfile` (required) to provide the file which contains the console output
|
|
||||||
to parse,
|
* `--logfile` (required) to provide the file which contains the console output
|
||||||
- `--fps` (optional) to customize the number of frames per second in the movie
|
to parse,
|
||||||
animation.
|
* `--fps` (optional) to customize the number of frames per second in the movie
|
||||||
|
animation.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo run --manifest-path tools/heapviz/Cargo.toml -- --logfile console.log --fps 50
|
cargo run --manifest-path tools/heapviz/Cargo.toml -- --logfile console.log --fps 50
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ static STRINGS: &'static [&'static str] = &[
|
|||||||
// Product
|
// Product
|
||||||
"OpenSK",
|
"OpenSK",
|
||||||
// Serial number
|
// Serial number
|
||||||
"v0.1",
|
"v1.0",
|
||||||
];
|
];
|
||||||
|
|
||||||
// State for loading and holding applications.
|
// State for loading and holding applications.
|
||||||
|
|||||||
59
build.rs
59
build.rs
@@ -12,11 +12,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use openssl::asn1;
|
|
||||||
use openssl::ec;
|
|
||||||
use openssl::nid::Nid;
|
|
||||||
use openssl::pkey::PKey;
|
|
||||||
use openssl::x509;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
@@ -25,65 +20,11 @@ use std::path::Path;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-changed=crypto_data/opensk.key");
|
|
||||||
println!("cargo:rerun-if-changed=crypto_data/opensk_cert.pem");
|
|
||||||
println!("cargo:rerun-if-changed=crypto_data/aaguid.txt");
|
println!("cargo:rerun-if-changed=crypto_data/aaguid.txt");
|
||||||
|
|
||||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||||
let priv_key_bin_path = Path::new(&out_dir).join("opensk_pkey.bin");
|
|
||||||
let cert_bin_path = Path::new(&out_dir).join("opensk_cert.bin");
|
|
||||||
let aaguid_bin_path = Path::new(&out_dir).join("opensk_aaguid.bin");
|
let aaguid_bin_path = Path::new(&out_dir).join("opensk_aaguid.bin");
|
||||||
|
|
||||||
// Load the OpenSSL PEM ECC key
|
|
||||||
let ecc_data = include_bytes!("crypto_data/opensk.key");
|
|
||||||
let pkey =
|
|
||||||
ec::EcKey::private_key_from_pem(ecc_data).expect("Failed to load OpenSK private key file");
|
|
||||||
|
|
||||||
// Check key validity
|
|
||||||
pkey.check_key().unwrap();
|
|
||||||
assert_eq!(pkey.group().curve_name(), Some(Nid::X9_62_PRIME256V1));
|
|
||||||
|
|
||||||
// Private keys generated by OpenSSL have variable size but we only handle
|
|
||||||
// constant size. Serialization is done in big endian so if the size is less
|
|
||||||
// than 32 bytes, we need to prepend with null bytes.
|
|
||||||
// If the size is 33 bytes, this means the serialized BigInt is negative.
|
|
||||||
// Any other size is invalid.
|
|
||||||
let priv_key_hex = pkey.private_key().to_hex_str().unwrap();
|
|
||||||
let priv_key_vec = pkey.private_key().to_vec();
|
|
||||||
let key_len = priv_key_vec.len();
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
key_len <= 33,
|
|
||||||
"Invalid private key (too big): {} ({:#?})",
|
|
||||||
priv_key_hex,
|
|
||||||
priv_key_vec,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Copy OpenSSL generated key to our vec, starting from the end
|
|
||||||
let mut output_vec = [0u8; 32];
|
|
||||||
let min_key_len = std::cmp::min(key_len, 32);
|
|
||||||
output_vec[32 - min_key_len..].copy_from_slice(&priv_key_vec[key_len - min_key_len..]);
|
|
||||||
|
|
||||||
// Create the raw private key out of the OpenSSL data
|
|
||||||
let mut priv_key_bin_file = File::create(&priv_key_bin_path).unwrap();
|
|
||||||
priv_key_bin_file.write_all(&output_vec).unwrap();
|
|
||||||
|
|
||||||
// Convert the PEM certificate to DER and extract the serial for AAGUID
|
|
||||||
let input_pem_cert = include_bytes!("crypto_data/opensk_cert.pem");
|
|
||||||
let cert = x509::X509::from_pem(input_pem_cert).expect("Failed to load OpenSK certificate");
|
|
||||||
|
|
||||||
// Do some sanity check on the certificate
|
|
||||||
assert!(cert
|
|
||||||
.public_key()
|
|
||||||
.unwrap()
|
|
||||||
.public_eq(&PKey::from_ec_key(pkey).unwrap()));
|
|
||||||
let now = asn1::Asn1Time::days_from_now(0).unwrap();
|
|
||||||
assert!(cert.not_after() > now);
|
|
||||||
assert!(cert.not_before() <= now);
|
|
||||||
|
|
||||||
let mut cert_bin_file = File::create(&cert_bin_path).unwrap();
|
|
||||||
cert_bin_file.write_all(&cert.to_der().unwrap()).unwrap();
|
|
||||||
|
|
||||||
let mut aaguid_bin_file = File::create(&aaguid_bin_path).unwrap();
|
let mut aaguid_bin_file = File::create(&aaguid_bin_path).unwrap();
|
||||||
let mut aaguid_txt_file = File::open("crypto_data/aaguid.txt").unwrap();
|
let mut aaguid_txt_file = File::open("crypto_data/aaguid.txt").unwrap();
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
|
|||||||
43
deploy.py
43
deploy.py
@@ -710,6 +710,22 @@ class OpenSKInstaller:
|
|||||||
check=False,
|
check=False,
|
||||||
timeout=None,
|
timeout=None,
|
||||||
).returncode
|
).returncode
|
||||||
|
|
||||||
|
# Configure OpenSK through vendor specific command if needed
|
||||||
|
if any([
|
||||||
|
self.args.lock_device,
|
||||||
|
self.args.config_cert,
|
||||||
|
self.args.config_pkey,
|
||||||
|
]):
|
||||||
|
# pylint: disable=g-import-not-at-top,import-outside-toplevel
|
||||||
|
import tools.configure
|
||||||
|
tools.configure.main(
|
||||||
|
argparse.Namespace(
|
||||||
|
batch=False,
|
||||||
|
certificate=self.args.config_cert,
|
||||||
|
priv_key=self.args.config_pkey,
|
||||||
|
lock=self.args.lock_device,
|
||||||
|
))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -770,6 +786,33 @@ if __name__ == "__main__":
|
|||||||
help=("Erases the persistent storage when installing an application. "
|
help=("Erases the persistent storage when installing an application. "
|
||||||
"All stored data will be permanently lost."),
|
"All stored data will be permanently lost."),
|
||||||
)
|
)
|
||||||
|
main_parser.add_argument(
|
||||||
|
"--lock-device",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
dest="lock_device",
|
||||||
|
help=("Try to disable JTAG at the end of the operations. This "
|
||||||
|
"operation may fail if the device is already locked or if "
|
||||||
|
"the certificate/private key are not programmed."),
|
||||||
|
)
|
||||||
|
main_parser.add_argument(
|
||||||
|
"--inject-certificate",
|
||||||
|
default=None,
|
||||||
|
metavar="PEM_FILE",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
dest="config_cert",
|
||||||
|
help=("If this option is set, the corresponding certificate "
|
||||||
|
"will be programmed into the key as the last operation."),
|
||||||
|
)
|
||||||
|
main_parser.add_argument(
|
||||||
|
"--inject-private-key",
|
||||||
|
default=None,
|
||||||
|
metavar="PEM_FILE",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
dest="config_pkey",
|
||||||
|
help=("If this option is set, the corresponding private key "
|
||||||
|
"will be programmed into the key as the last operation."),
|
||||||
|
)
|
||||||
main_parser.add_argument(
|
main_parser.add_argument(
|
||||||
"--programmer",
|
"--programmer",
|
||||||
metavar="METHOD",
|
metavar="METHOD",
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ This is the expected content after running our `setup.sh` script:
|
|||||||
|
|
||||||
File | Purpose
|
File | Purpose
|
||||||
----------------- | --------------------------------------------------------
|
----------------- | --------------------------------------------------------
|
||||||
|
`aaguid.txt` | Text file containaing the AAGUID value
|
||||||
`opensk_ca.csr` | Certificate sign request for the Root CA
|
`opensk_ca.csr` | Certificate sign request for the Root CA
|
||||||
`opensk_ca.key` | ECC secp256r1 private key used for the Root CA
|
`opensk_ca.key` | ECC secp256r1 private key used for the Root CA
|
||||||
`opensk_ca.pem` | PEM encoded certificate of the Root CA
|
`opensk_ca.pem` | PEM encoded certificate of the Root CA
|
||||||
@@ -136,9 +137,11 @@ File | Purpose
|
|||||||
If you want to use your own attestation certificate and private key, simply
|
If you want to use your own attestation certificate and private key, simply
|
||||||
replace `opensk_cert.pem` and `opensk.key` files.
|
replace `opensk_cert.pem` and `opensk.key` files.
|
||||||
|
|
||||||
Our build script `build.rs` is responsible for converting `opensk_cert.pem` and
|
Our build script `build.rs` is responsible for converting the `aaguid.txt` file
|
||||||
`opensk.key` files into raw data that is then used by the Rust file:
|
into raw data that is then used by the Rust file `src/ctap/key_material.rs`.
|
||||||
`src/ctap/key_material.rs`.
|
|
||||||
|
Our configuration script `tools/configure.py` is responsible for configuring
|
||||||
|
an OpenSK device with the correct certificate and private key.
|
||||||
|
|
||||||
### Flashing a firmware
|
### Flashing a firmware
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ index d72d20482..118ea6d68 100644
|
|||||||
+ // Product
|
+ // Product
|
||||||
+ "OpenSK",
|
+ "OpenSK",
|
||||||
+ // Serial number
|
+ // Serial number
|
||||||
+ "v0.1",
|
+ "v1.0",
|
||||||
+];
|
+];
|
||||||
+
|
+
|
||||||
// State for loading and holding applications.
|
// State for loading and holding applications.
|
||||||
@@ -189,7 +189,7 @@ index 2ebb384d8..4a7bfffdd 100644
|
|||||||
+ // Product
|
+ // Product
|
||||||
+ "OpenSK",
|
+ "OpenSK",
|
||||||
+ // Serial number
|
+ // Serial number
|
||||||
+ "v0.1",
|
+ "v1.0",
|
||||||
+];
|
+];
|
||||||
+
|
+
|
||||||
// State for loading and holding applications.
|
// State for loading and holding applications.
|
||||||
|
|||||||
100
patches/tock/06-update-uicr.patch
Normal file
100
patches/tock/06-update-uicr.patch
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
diff --git a/chips/nrf52/src/uicr.rs b/chips/nrf52/src/uicr.rs
|
||||||
|
index 6bb6c86..3bb8b5a 100644
|
||||||
|
--- a/chips/nrf52/src/uicr.rs
|
||||||
|
+++ b/chips/nrf52/src/uicr.rs
|
||||||
|
@@ -1,38 +1,45 @@
|
||||||
|
//! User information configuration registers
|
||||||
|
-//!
|
||||||
|
-//! Minimal implementation to support activation of the reset button on
|
||||||
|
-//! nRF52-DK.
|
||||||
|
+
|
||||||
|
|
||||||
|
use enum_primitive::cast::FromPrimitive;
|
||||||
|
-use kernel::common::registers::{register_bitfields, ReadWrite};
|
||||||
|
+use kernel::common::registers::{register_bitfields, register_structs, ReadWrite};
|
||||||
|
use kernel::common::StaticRef;
|
||||||
|
+use kernel::hil;
|
||||||
|
+use kernel::ReturnCode;
|
||||||
|
|
||||||
|
use crate::gpio::Pin;
|
||||||
|
|
||||||
|
const UICR_BASE: StaticRef<UicrRegisters> =
|
||||||
|
- unsafe { StaticRef::new(0x10001200 as *const UicrRegisters) };
|
||||||
|
-
|
||||||
|
-#[repr(C)]
|
||||||
|
-struct UicrRegisters {
|
||||||
|
- /// Mapping of the nRESET function (see POWER chapter for details)
|
||||||
|
- /// - Address: 0x200 - 0x204
|
||||||
|
- pselreset0: ReadWrite<u32, Pselreset::Register>,
|
||||||
|
- /// Mapping of the nRESET function (see POWER chapter for details)
|
||||||
|
- /// - Address: 0x204 - 0x208
|
||||||
|
- pselreset1: ReadWrite<u32, Pselreset::Register>,
|
||||||
|
- /// Access Port protection
|
||||||
|
- /// - Address: 0x208 - 0x20c
|
||||||
|
- approtect: ReadWrite<u32, ApProtect::Register>,
|
||||||
|
- /// Setting of pins dedicated to NFC functionality: NFC antenna or GPIO
|
||||||
|
- /// - Address: 0x20c - 0x210
|
||||||
|
- nfcpins: ReadWrite<u32, NfcPins::Register>,
|
||||||
|
- _reserved1: [u32; 60],
|
||||||
|
- /// External circuitry to be supplied from VDD pin.
|
||||||
|
- /// - Address: 0x300 - 0x304
|
||||||
|
- extsupply: ReadWrite<u32, ExtSupply::Register>,
|
||||||
|
- /// GPIO reference voltage
|
||||||
|
- /// - Address: 0x304 - 0x308
|
||||||
|
- regout0: ReadWrite<u32, RegOut::Register>,
|
||||||
|
+ unsafe { StaticRef::new(0x10001000 as *const UicrRegisters) };
|
||||||
|
+
|
||||||
|
+register_structs! {
|
||||||
|
+ UicrRegisters {
|
||||||
|
+ (0x000 => _reserved1),
|
||||||
|
+ /// Reserved for Nordic firmware design
|
||||||
|
+ (0x014 => nrffw: [ReadWrite<u32>; 13]),
|
||||||
|
+ (0x048 => _reserved2),
|
||||||
|
+ /// Reserved for Nordic hardware design
|
||||||
|
+ (0x050 => nrfhw: [ReadWrite<u32>; 12]),
|
||||||
|
+ /// Reserved for customer
|
||||||
|
+ (0x080 => customer: [ReadWrite<u32>; 32]),
|
||||||
|
+ (0x100 => _reserved3),
|
||||||
|
+ /// Mapping of the nRESET function (see POWER chapter for details)
|
||||||
|
+ (0x200 => pselreset0: ReadWrite<u32, Pselreset::Register>),
|
||||||
|
+ /// Mapping of the nRESET function (see POWER chapter for details)
|
||||||
|
+ (0x204 => pselreset1: ReadWrite<u32, Pselreset::Register>),
|
||||||
|
+ /// Access Port protection
|
||||||
|
+ (0x208 => approtect: ReadWrite<u32, ApProtect::Register>),
|
||||||
|
+ /// Setting of pins dedicated to NFC functionality: NFC antenna or GPIO
|
||||||
|
+ /// - Address: 0x20c - 0x210
|
||||||
|
+ (0x20c => nfcpins: ReadWrite<u32, NfcPins::Register>),
|
||||||
|
+ (0x210 => debugctrl: ReadWrite<u32, DebugControl::Register>),
|
||||||
|
+ (0x214 => _reserved4),
|
||||||
|
+ /// External circuitry to be supplied from VDD pin.
|
||||||
|
+ (0x300 => extsupply: ReadWrite<u32, ExtSupply::Register>),
|
||||||
|
+ /// GPIO reference voltage
|
||||||
|
+ (0x304 => regout0: ReadWrite<u32, RegOut::Register>),
|
||||||
|
+ (0x308 => @END),
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
register_bitfields! [u32,
|
||||||
|
@@ -58,6 +65,21 @@ register_bitfields! [u32,
|
||||||
|
DISABLED = 0xff
|
||||||
|
]
|
||||||
|
],
|
||||||
|
+ /// Processor debug control
|
||||||
|
+ DebugControl [
|
||||||
|
+ CPUNIDEN OFFSET(0) NUMBITS(8) [
|
||||||
|
+ /// Enable
|
||||||
|
+ ENABLED = 0xff,
|
||||||
|
+ /// Disable
|
||||||
|
+ DISABLED = 0x00
|
||||||
|
+ ],
|
||||||
|
+ CPUFPBEN OFFSET(8) NUMBITS(8) [
|
||||||
|
+ /// Enable
|
||||||
|
+ ENABLED = 0xff,
|
||||||
|
+ /// Disable
|
||||||
|
+ DISABLED = 0x00
|
||||||
|
+ ]
|
||||||
|
+ ],
|
||||||
|
/// Setting of pins dedicated to NFC functionality: NFC antenna or GPIO
|
||||||
|
NfcPins [
|
||||||
|
/// Setting pins dedicated to NFC functionality
|
||||||
|
|
||||||
426
patches/tock/07-firmware-protect.patch
Normal file
426
patches/tock/07-firmware-protect.patch
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
diff --git a/boards/components/src/firmware_protection.rs b/boards/components/src/firmware_protection.rs
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..58695af
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/boards/components/src/firmware_protection.rs
|
||||||
|
@@ -0,0 +1,70 @@
|
||||||
|
+//! Component for firmware protection syscall interface.
|
||||||
|
+//!
|
||||||
|
+//! This provides one Component, `FirmwareProtectionComponent`, which implements a
|
||||||
|
+//! userspace syscall interface to enable the code readout protection.
|
||||||
|
+//!
|
||||||
|
+//! Usage
|
||||||
|
+//! -----
|
||||||
|
+//! ```rust
|
||||||
|
+//! let crp = components::firmware_protection::FirmwareProtectionComponent::new(
|
||||||
|
+//! board_kernel,
|
||||||
|
+//! nrf52840::uicr::Uicr::new()
|
||||||
|
+//! )
|
||||||
|
+//! .finalize(
|
||||||
|
+//! components::firmware_protection_component_helper!(uicr));
|
||||||
|
+//! ```
|
||||||
|
+
|
||||||
|
+use core::mem::MaybeUninit;
|
||||||
|
+
|
||||||
|
+use capsules::firmware_protection;
|
||||||
|
+use kernel::capabilities;
|
||||||
|
+use kernel::component::Component;
|
||||||
|
+use kernel::create_capability;
|
||||||
|
+use kernel::hil;
|
||||||
|
+use kernel::static_init_half;
|
||||||
|
+
|
||||||
|
+// Setup static space for the objects.
|
||||||
|
+#[macro_export]
|
||||||
|
+macro_rules! firmware_protection_component_helper {
|
||||||
|
+ ($C:ty) => {{
|
||||||
|
+ use capsules::firmware_protection;
|
||||||
|
+ use core::mem::MaybeUninit;
|
||||||
|
+ static mut BUF: MaybeUninit<firmware_protection::FirmwareProtection<$C>> =
|
||||||
|
+ MaybeUninit::uninit();
|
||||||
|
+ &mut BUF
|
||||||
|
+ };};
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub struct FirmwareProtectionComponent<C: hil::firmware_protection::FirmwareProtection> {
|
||||||
|
+ board_kernel: &'static kernel::Kernel,
|
||||||
|
+ crp: C,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<C: 'static + hil::firmware_protection::FirmwareProtection> FirmwareProtectionComponent<C> {
|
||||||
|
+ pub fn new(board_kernel: &'static kernel::Kernel, crp: C) -> FirmwareProtectionComponent<C> {
|
||||||
|
+ FirmwareProtectionComponent {
|
||||||
|
+ board_kernel: board_kernel,
|
||||||
|
+ crp: crp,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<C: 'static + hil::firmware_protection::FirmwareProtection> Component
|
||||||
|
+ for FirmwareProtectionComponent<C>
|
||||||
|
+{
|
||||||
|
+ type StaticInput = &'static mut MaybeUninit<firmware_protection::FirmwareProtection<C>>;
|
||||||
|
+ type Output = &'static firmware_protection::FirmwareProtection<C>;
|
||||||
|
+
|
||||||
|
+ unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output {
|
||||||
|
+ let grant_cap = create_capability!(capabilities::MemoryAllocationCapability);
|
||||||
|
+
|
||||||
|
+ static_init_half!(
|
||||||
|
+ static_buffer,
|
||||||
|
+ firmware_protection::FirmwareProtection<C>,
|
||||||
|
+ firmware_protection::FirmwareProtection::new(
|
||||||
|
+ self.crp,
|
||||||
|
+ self.board_kernel.create_grant(&grant_cap),
|
||||||
|
+ )
|
||||||
|
+ )
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
diff --git a/boards/components/src/lib.rs b/boards/components/src/lib.rs
|
||||||
|
index 917497a..520408f 100644
|
||||||
|
--- a/boards/components/src/lib.rs
|
||||||
|
+++ b/boards/components/src/lib.rs
|
||||||
|
@@ -9,6 +9,7 @@ pub mod console;
|
||||||
|
pub mod crc;
|
||||||
|
pub mod debug_queue;
|
||||||
|
pub mod debug_writer;
|
||||||
|
+pub mod firmware_protection;
|
||||||
|
pub mod ft6x06;
|
||||||
|
pub mod gpio;
|
||||||
|
pub mod hd44780;
|
||||||
|
diff --git a/boards/nordic/nrf52840_dongle/src/main.rs b/boards/nordic/nrf52840_dongle/src/main.rs
|
||||||
|
index 118ea6d..76436f3 100644
|
||||||
|
--- a/boards/nordic/nrf52840_dongle/src/main.rs
|
||||||
|
+++ b/boards/nordic/nrf52840_dongle/src/main.rs
|
||||||
|
@@ -112,6 +112,7 @@ pub struct Platform {
|
||||||
|
'static,
|
||||||
|
nrf52840::usbd::Usbd<'static>,
|
||||||
|
>,
|
||||||
|
+ crp: &'static capsules::firmware_protection::FirmwareProtection<nrf52840::uicr::Uicr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl kernel::Platform for Platform {
|
||||||
|
@@ -132,6 +133,7 @@ impl kernel::Platform for Platform {
|
||||||
|
capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)),
|
||||||
|
nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)),
|
||||||
|
capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)),
|
||||||
|
+ capsules::firmware_protection::DRIVER_NUM => f(Some(self.crp)),
|
||||||
|
kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
|
||||||
|
_ => f(None),
|
||||||
|
}
|
||||||
|
@@ -355,6 +357,14 @@ pub unsafe fn reset_handler() {
|
||||||
|
)
|
||||||
|
.finalize(components::usb_ctap_component_buf!(nrf52840::usbd::Usbd));
|
||||||
|
|
||||||
|
+ let crp = components::firmware_protection::FirmwareProtectionComponent::new(
|
||||||
|
+ board_kernel,
|
||||||
|
+ nrf52840::uicr::Uicr::new(),
|
||||||
|
+ )
|
||||||
|
+ .finalize(components::firmware_protection_component_helper!(
|
||||||
|
+ nrf52840::uicr::Uicr
|
||||||
|
+ ));
|
||||||
|
+
|
||||||
|
nrf52_components::NrfClockComponent::new().finalize(());
|
||||||
|
|
||||||
|
let platform = Platform {
|
||||||
|
@@ -371,6 +381,7 @@ pub unsafe fn reset_handler() {
|
||||||
|
analog_comparator,
|
||||||
|
nvmc,
|
||||||
|
usb,
|
||||||
|
+ crp,
|
||||||
|
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
|
||||||
|
};
|
||||||
|
|
||||||
|
diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs
|
||||||
|
index b1d0d3c..3cfb38d 100644
|
||||||
|
--- a/boards/nordic/nrf52840dk/src/main.rs
|
||||||
|
+++ b/boards/nordic/nrf52840dk/src/main.rs
|
||||||
|
@@ -180,6 +180,7 @@ pub struct Platform {
|
||||||
|
'static,
|
||||||
|
nrf52840::usbd::Usbd<'static>,
|
||||||
|
>,
|
||||||
|
+ crp: &'static capsules::firmware_protection::FirmwareProtection<nrf52840::uicr::Uicr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl kernel::Platform for Platform {
|
||||||
|
@@ -201,6 +202,7 @@ impl kernel::Platform for Platform {
|
||||||
|
capsules::nonvolatile_storage_driver::DRIVER_NUM => f(Some(self.nonvolatile_storage)),
|
||||||
|
nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)),
|
||||||
|
capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)),
|
||||||
|
+ capsules::firmware_protection::DRIVER_NUM => f(Some(self.crp)),
|
||||||
|
kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
|
||||||
|
_ => f(None),
|
||||||
|
}
|
||||||
|
@@ -480,6 +482,14 @@ pub unsafe fn reset_handler() {
|
||||||
|
)
|
||||||
|
.finalize(components::usb_ctap_component_buf!(nrf52840::usbd::Usbd));
|
||||||
|
|
||||||
|
+ let crp = components::firmware_protection::FirmwareProtectionComponent::new(
|
||||||
|
+ board_kernel,
|
||||||
|
+ nrf52840::uicr::Uicr::new(),
|
||||||
|
+ )
|
||||||
|
+ .finalize(components::firmware_protection_component_helper!(
|
||||||
|
+ nrf52840::uicr::Uicr
|
||||||
|
+ ));
|
||||||
|
+
|
||||||
|
nrf52_components::NrfClockComponent::new().finalize(());
|
||||||
|
|
||||||
|
let platform = Platform {
|
||||||
|
@@ -497,6 +507,7 @@ pub unsafe fn reset_handler() {
|
||||||
|
nonvolatile_storage,
|
||||||
|
nvmc,
|
||||||
|
usb,
|
||||||
|
+ crp,
|
||||||
|
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
|
||||||
|
};
|
||||||
|
|
||||||
|
diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs
|
||||||
|
index ae458b3..f536dad 100644
|
||||||
|
--- a/capsules/src/driver.rs
|
||||||
|
+++ b/capsules/src/driver.rs
|
||||||
|
@@ -16,6 +16,7 @@ pub enum NUM {
|
||||||
|
Adc = 0x00005,
|
||||||
|
Dac = 0x00006,
|
||||||
|
AnalogComparator = 0x00007,
|
||||||
|
+ FirmwareProtection = 0x00008,
|
||||||
|
|
||||||
|
// Kernel
|
||||||
|
Ipc = 0x10000,
|
||||||
|
diff --git a/capsules/src/firmware_protection.rs b/capsules/src/firmware_protection.rs
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..8cf63d6
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/capsules/src/firmware_protection.rs
|
||||||
|
@@ -0,0 +1,85 @@
|
||||||
|
+//! Provides userspace control of firmware protection on a board.
|
||||||
|
+//!
|
||||||
|
+//! This allows an application to enable firware readout protection,
|
||||||
|
+//! disabling JTAG interface and other ways to read/tamper the firmware.
|
||||||
|
+//! Of course, outside of a hardware bug, once set, the only way to enable
|
||||||
|
+//! programming/debugging is by fully erasing the flash.
|
||||||
|
+//!
|
||||||
|
+//! Usage
|
||||||
|
+//! -----
|
||||||
|
+//!
|
||||||
|
+//! ```rust
|
||||||
|
+//! # use kernel::static_init;
|
||||||
|
+//!
|
||||||
|
+//! let crp = static_init!(
|
||||||
|
+//! capsules::firmware_protection::FirmwareProtection,
|
||||||
|
+//! capsules::firmware_protection::FirmwareProtection::new(
|
||||||
|
+//! nrf52840::uicr::Uicr,
|
||||||
|
+//! board_kernel.create_grant(&grant_cap),
|
||||||
|
+//! );
|
||||||
|
+//! ```
|
||||||
|
+//!
|
||||||
|
+//! Syscall Interface
|
||||||
|
+//! -----------------
|
||||||
|
+//!
|
||||||
|
+//! - Stability: 0 - Draft
|
||||||
|
+//!
|
||||||
|
+//! ### Command
|
||||||
|
+//!
|
||||||
|
+//! Enable code readout protection on the current board.
|
||||||
|
+//!
|
||||||
|
+//! #### `command_num`
|
||||||
|
+//!
|
||||||
|
+//! - `0`: Driver check.
|
||||||
|
+//! - `1`: Get current firmware readout protection (aka CRP) state.
|
||||||
|
+//! - `2`: Set current firmware readout protection (aka CRP) state.
|
||||||
|
+//!
|
||||||
|
+
|
||||||
|
+use kernel::hil;
|
||||||
|
+use kernel::{AppId, Callback, Driver, Grant, ReturnCode};
|
||||||
|
+
|
||||||
|
+/// Syscall driver number.
|
||||||
|
+use crate::driver;
|
||||||
|
+pub const DRIVER_NUM: usize = driver::NUM::FirmwareProtection as usize;
|
||||||
|
+
|
||||||
|
+pub struct FirmwareProtection<C: hil::firmware_protection::FirmwareProtection> {
|
||||||
|
+ crp_unit: C,
|
||||||
|
+ apps: Grant<Option<Callback>>,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<C: hil::firmware_protection::FirmwareProtection> FirmwareProtection<C> {
|
||||||
|
+ pub fn new(crp_unit: C, apps: Grant<Option<Callback>>) -> Self {
|
||||||
|
+ Self { crp_unit, apps }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl<C: hil::firmware_protection::FirmwareProtection> Driver for FirmwareProtection<C> {
|
||||||
|
+ ///
|
||||||
|
+ /// ### Command numbers
|
||||||
|
+ ///
|
||||||
|
+ /// * `0`: Returns non-zero to indicate the driver is present.
|
||||||
|
+ /// * `1`: Gets firmware protection state.
|
||||||
|
+ /// * `2`: Sets firmware protection state.
|
||||||
|
+ fn command(&self, command_num: usize, data: usize, _: usize, appid: AppId) -> ReturnCode {
|
||||||
|
+ match command_num {
|
||||||
|
+ // return if driver is available
|
||||||
|
+ 0 => ReturnCode::SUCCESS,
|
||||||
|
+
|
||||||
|
+ 1 => self
|
||||||
|
+ .apps
|
||||||
|
+ .enter(appid, |_, _| ReturnCode::SuccessWithValue {
|
||||||
|
+ value: self.crp_unit.get_protection() as usize,
|
||||||
|
+ })
|
||||||
|
+ .unwrap_or_else(|err| err.into()),
|
||||||
|
+
|
||||||
|
+ // sets firmware protection
|
||||||
|
+ 2 => self
|
||||||
|
+ .apps
|
||||||
|
+ .enter(appid, |_, _| self.crp_unit.set_protection(data.into()))
|
||||||
|
+ .unwrap_or_else(|err| err.into()),
|
||||||
|
+
|
||||||
|
+ // default
|
||||||
|
+ _ => ReturnCode::ENOSUPPORT,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
diff --git a/capsules/src/lib.rs b/capsules/src/lib.rs
|
||||||
|
index e4423fe..7538aad 100644
|
||||||
|
--- a/capsules/src/lib.rs
|
||||||
|
+++ b/capsules/src/lib.rs
|
||||||
|
@@ -22,6 +22,7 @@ pub mod crc;
|
||||||
|
pub mod dac;
|
||||||
|
pub mod debug_process_restart;
|
||||||
|
pub mod driver;
|
||||||
|
+pub mod firmware_protection;
|
||||||
|
pub mod fm25cl;
|
||||||
|
pub mod ft6x06;
|
||||||
|
pub mod fxos8700cq;
|
||||||
|
diff --git a/chips/nrf52/src/uicr.rs b/chips/nrf52/src/uicr.rs
|
||||||
|
index 3bb8b5a..ea96cb2 100644
|
||||||
|
--- a/chips/nrf52/src/uicr.rs
|
||||||
|
+++ b/chips/nrf52/src/uicr.rs
|
||||||
|
@@ -1,13 +1,14 @@
|
||||||
|
//! User information configuration registers
|
||||||
|
|
||||||
|
-
|
||||||
|
use enum_primitive::cast::FromPrimitive;
|
||||||
|
+use hil::firmware_protection::ProtectionLevel;
|
||||||
|
use kernel::common::registers::{register_bitfields, register_structs, ReadWrite};
|
||||||
|
use kernel::common::StaticRef;
|
||||||
|
use kernel::hil;
|
||||||
|
use kernel::ReturnCode;
|
||||||
|
|
||||||
|
use crate::gpio::Pin;
|
||||||
|
+use crate::nvmc::NVMC;
|
||||||
|
|
||||||
|
const UICR_BASE: StaticRef<UicrRegisters> =
|
||||||
|
unsafe { StaticRef::new(0x10001000 as *const UicrRegisters) };
|
||||||
|
@@ -210,3 +211,49 @@ impl Uicr {
|
||||||
|
self.registers.approtect.write(ApProtect::PALL::ENABLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+impl hil::firmware_protection::FirmwareProtection for Uicr {
|
||||||
|
+ fn get_protection(&self) -> ProtectionLevel {
|
||||||
|
+ let ap_protect_state = self.is_ap_protect_enabled();
|
||||||
|
+ let cpu_debug_state = self
|
||||||
|
+ .registers
|
||||||
|
+ .debugctrl
|
||||||
|
+ .matches_all(DebugControl::CPUNIDEN::ENABLED + DebugControl::CPUFPBEN::ENABLED);
|
||||||
|
+ match (ap_protect_state, cpu_debug_state) {
|
||||||
|
+ (false, _) => ProtectionLevel::NoProtection,
|
||||||
|
+ (true, true) => ProtectionLevel::JtagDisabled,
|
||||||
|
+ (true, false) => ProtectionLevel::FullyLocked,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ fn set_protection(&self, level: ProtectionLevel) -> ReturnCode {
|
||||||
|
+ let current_level = self.get_protection();
|
||||||
|
+ if current_level > level || level == ProtectionLevel::Unknown {
|
||||||
|
+ return ReturnCode::EINVAL;
|
||||||
|
+ }
|
||||||
|
+ if current_level == level {
|
||||||
|
+ return ReturnCode::EALREADY;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ unsafe { NVMC.configure_writeable() };
|
||||||
|
+ if level >= ProtectionLevel::JtagDisabled {
|
||||||
|
+ self.set_ap_protect();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if level >= ProtectionLevel::FullyLocked {
|
||||||
|
+ // Prevent CPU debug and flash patching. Leaving these enabled could
|
||||||
|
+ // allow to circumvent protection.
|
||||||
|
+ self.registers
|
||||||
|
+ .debugctrl
|
||||||
|
+ .write(DebugControl::CPUNIDEN::DISABLED + DebugControl::CPUFPBEN::DISABLED);
|
||||||
|
+ // TODO(jmichel): prevent returning into bootloader if present
|
||||||
|
+ }
|
||||||
|
+ unsafe { NVMC.configure_readonly() };
|
||||||
|
+
|
||||||
|
+ if self.get_protection() == level {
|
||||||
|
+ ReturnCode::SUCCESS
|
||||||
|
+ } else {
|
||||||
|
+ ReturnCode::FAIL
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
diff --git a/kernel/src/hil/firmware_protection.rs b/kernel/src/hil/firmware_protection.rs
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..de08246
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/kernel/src/hil/firmware_protection.rs
|
||||||
|
@@ -0,0 +1,48 @@
|
||||||
|
+//! Interface for Firmware Protection, also called Code Readout Protection.
|
||||||
|
+
|
||||||
|
+use crate::returncode::ReturnCode;
|
||||||
|
+
|
||||||
|
+#[derive(PartialOrd, PartialEq)]
|
||||||
|
+pub enum ProtectionLevel {
|
||||||
|
+ /// Unsupported feature
|
||||||
|
+ Unknown = 0,
|
||||||
|
+ /// This should be the factory default for the chip.
|
||||||
|
+ NoProtection = 1,
|
||||||
|
+ /// At this level, only JTAG/SWD are disabled but other debugging
|
||||||
|
+ /// features may still be enabled.
|
||||||
|
+ JtagDisabled = 2,
|
||||||
|
+ /// This is the maximum level of protection the chip supports.
|
||||||
|
+ /// At this level, JTAG and all other features are expected to be
|
||||||
|
+ /// disabled and only a full chip erase may allow to recover from
|
||||||
|
+ /// that state.
|
||||||
|
+ FullyLocked = 0xff,
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+impl From<usize> for ProtectionLevel {
|
||||||
|
+ fn from(value: usize) -> Self {
|
||||||
|
+ match value {
|
||||||
|
+ 1 => ProtectionLevel::NoProtection,
|
||||||
|
+ 2 => ProtectionLevel::JtagDisabled,
|
||||||
|
+ 0xff => ProtectionLevel::FullyLocked,
|
||||||
|
+ _ => ProtectionLevel::Unknown,
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+pub trait FirmwareProtection {
|
||||||
|
+ /// Gets the current firmware protection level.
|
||||||
|
+ /// This doesn't fail and always returns a value.
|
||||||
|
+ fn get_protection(&self) -> ProtectionLevel;
|
||||||
|
+
|
||||||
|
+ /// Sets the firmware protection level.
|
||||||
|
+ /// There are four valid return values:
|
||||||
|
+ /// - SUCCESS: protection level has been set to `level`
|
||||||
|
+ /// - FAIL: something went wrong while setting the protection
|
||||||
|
+ /// level and the effective protection level is not the one
|
||||||
|
+ /// that was requested.
|
||||||
|
+ /// - EALREADY: the requested protection level is already the
|
||||||
|
+ /// level that is set.
|
||||||
|
+ /// - EINVAL: unsupported protection level or the requested
|
||||||
|
+ /// protection level is lower than the currently set one.
|
||||||
|
+ fn set_protection(&self, level: ProtectionLevel) -> ReturnCode;
|
||||||
|
+}
|
||||||
|
diff --git a/kernel/src/hil/mod.rs b/kernel/src/hil/mod.rs
|
||||||
|
index 4f42afa..83e7702 100644
|
||||||
|
--- a/kernel/src/hil/mod.rs
|
||||||
|
+++ b/kernel/src/hil/mod.rs
|
||||||
|
@@ -8,6 +8,7 @@ pub mod dac;
|
||||||
|
pub mod digest;
|
||||||
|
pub mod eic;
|
||||||
|
pub mod entropy;
|
||||||
|
+pub mod firmware_protection;
|
||||||
|
pub mod flash;
|
||||||
|
pub mod gpio;
|
||||||
|
pub mod gpio_async;
|
||||||
|
|
||||||
3
setup.sh
3
setup.sh
@@ -44,3 +44,6 @@ rustup target add thumbv7em-none-eabi
|
|||||||
# Install dependency to create applications.
|
# Install dependency to create applications.
|
||||||
mkdir -p elf2tab
|
mkdir -p elf2tab
|
||||||
cargo install elf2tab --version 0.6.0 --root elf2tab/
|
cargo install elf2tab --version 0.6.0 --root elf2tab/
|
||||||
|
|
||||||
|
# Install python dependencies to factory configure OpenSK (crypto, JTAG lockdown)
|
||||||
|
pip3 install --user --upgrade colorama tqdm cryptography fido2
|
||||||
|
|||||||
@@ -13,14 +13,17 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
extract_array, extract_byte_string, extract_map, extract_text_string, extract_unsigned,
|
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
||||||
ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionExtensions, GetAssertionOptions,
|
extract_unsigned, ok_or_missing, ClientPinSubCommand, CoseKey, GetAssertionExtensions,
|
||||||
MakeCredentialExtensions, MakeCredentialOptions, PublicKeyCredentialDescriptor,
|
GetAssertionOptions, MakeCredentialExtensions, MakeCredentialOptions,
|
||||||
PublicKeyCredentialParameter, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
|
||||||
|
PublicKeyCredentialUserEntity,
|
||||||
};
|
};
|
||||||
|
use super::key_material;
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
use arrayref::array_ref;
|
||||||
use cbor::destructure_cbor_map;
|
use cbor::destructure_cbor_map;
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
|
|
||||||
@@ -41,6 +44,8 @@ pub enum Command {
|
|||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
AuthenticatorSelection,
|
AuthenticatorSelection,
|
||||||
// TODO(kaczmarczyck) implement FIDO 2.1 commands (see below consts)
|
// TODO(kaczmarczyck) implement FIDO 2.1 commands (see below consts)
|
||||||
|
// Vendor specific commands
|
||||||
|
AuthenticatorVendorConfigure(AuthenticatorVendorConfigureParameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<cbor::reader::DecoderError> for Ctap2StatusCode {
|
impl From<cbor::reader::DecoderError> for Ctap2StatusCode {
|
||||||
@@ -63,7 +68,8 @@ impl Command {
|
|||||||
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
|
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
|
||||||
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
|
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
|
||||||
const AUTHENTICATOR_CONFIG: u8 = 0xC0;
|
const AUTHENTICATOR_CONFIG: u8 = 0xC0;
|
||||||
const AUTHENTICATOR_VENDOR_FIRST: u8 = 0x40;
|
const AUTHENTICATOR_VENDOR_CONFIGURE: u8 = 0x40;
|
||||||
|
const AUTHENTICATOR_VENDOR_FIRST_UNUSED: u8 = 0x41;
|
||||||
const AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF;
|
const AUTHENTICATOR_VENDOR_LAST: u8 = 0xBF;
|
||||||
|
|
||||||
pub fn deserialize(bytes: &[u8]) -> Result<Command, Ctap2StatusCode> {
|
pub fn deserialize(bytes: &[u8]) -> Result<Command, Ctap2StatusCode> {
|
||||||
@@ -109,6 +115,12 @@ impl Command {
|
|||||||
// Parameters are ignored.
|
// Parameters are ignored.
|
||||||
Ok(Command::AuthenticatorSelection)
|
Ok(Command::AuthenticatorSelection)
|
||||||
}
|
}
|
||||||
|
Command::AUTHENTICATOR_VENDOR_CONFIGURE => {
|
||||||
|
let decoded_cbor = cbor::read(&bytes[1..])?;
|
||||||
|
Ok(Command::AuthenticatorVendorConfigure(
|
||||||
|
AuthenticatorVendorConfigureParameters::try_from(decoded_cbor)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
_ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND),
|
_ => Err(Ctap2StatusCode::CTAP1_ERR_INVALID_COMMAND),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,6 +384,62 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
|
pub struct AuthenticatorAttestationMaterial {
|
||||||
|
pub certificate: Vec<u8>,
|
||||||
|
pub private_key: [u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<cbor::Value> for AuthenticatorAttestationMaterial {
|
||||||
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
1 => certificate,
|
||||||
|
2 => private_key,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
let certificate = extract_byte_string(ok_or_missing(certificate)?)?;
|
||||||
|
let private_key = extract_byte_string(ok_or_missing(private_key)?)?;
|
||||||
|
if private_key.len() != key_material::ATTESTATION_PRIVATE_KEY_LENGTH {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
let private_key = array_ref!(private_key, 0, key_material::ATTESTATION_PRIVATE_KEY_LENGTH);
|
||||||
|
Ok(AuthenticatorAttestationMaterial {
|
||||||
|
certificate,
|
||||||
|
private_key: *private_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||||
|
pub struct AuthenticatorVendorConfigureParameters {
|
||||||
|
pub lockdown: bool,
|
||||||
|
pub attestation_material: Option<AuthenticatorAttestationMaterial>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<cbor::Value> for AuthenticatorVendorConfigureParameters {
|
||||||
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
|
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
1 => lockdown,
|
||||||
|
2 => attestation_material,
|
||||||
|
} = extract_map(cbor_value)?;
|
||||||
|
}
|
||||||
|
let lockdown = lockdown.map_or(Ok(false), extract_bool)?;
|
||||||
|
let attestation_material = attestation_material
|
||||||
|
.map(AuthenticatorAttestationMaterial::try_from)
|
||||||
|
.transpose()?;
|
||||||
|
Ok(AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown,
|
||||||
|
attestation_material,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::data_formats::{
|
use super::super::data_formats::{
|
||||||
@@ -570,4 +638,83 @@ mod test {
|
|||||||
let command = Command::deserialize(&cbor_bytes);
|
let command = Command::deserialize(&cbor_bytes);
|
||||||
assert_eq!(command, Ok(Command::AuthenticatorSelection));
|
assert_eq!(command, Ok(Command::AuthenticatorSelection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_configure() {
|
||||||
|
// Incomplete command
|
||||||
|
let mut cbor_bytes = vec![Command::AUTHENTICATOR_VENDOR_CONFIGURE];
|
||||||
|
let command = Command::deserialize(&cbor_bytes);
|
||||||
|
assert_eq!(command, Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR));
|
||||||
|
|
||||||
|
cbor_bytes.extend(&[0xA1, 0x01, 0xF5]);
|
||||||
|
let command = Command::deserialize(&cbor_bytes);
|
||||||
|
assert_eq!(
|
||||||
|
command,
|
||||||
|
Ok(Command::AuthenticatorVendorConfigure(
|
||||||
|
AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: true,
|
||||||
|
attestation_material: None
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
let dummy_cert = [0xddu8; 20];
|
||||||
|
let dummy_pkey = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
||||||
|
|
||||||
|
// Attestation key is too short.
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
1 => false,
|
||||||
|
2 => cbor_map! {
|
||||||
|
1 => dummy_cert,
|
||||||
|
2 => dummy_pkey[..key_material::ATTESTATION_PRIVATE_KEY_LENGTH - 1]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Missing private key
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
1 => false,
|
||||||
|
2 => cbor_map! {
|
||||||
|
1 => dummy_cert
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Missing certificate
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
1 => false,
|
||||||
|
2 => cbor_map! {
|
||||||
|
2 => dummy_pkey
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Valid
|
||||||
|
let cbor_value = cbor_map! {
|
||||||
|
1 => false,
|
||||||
|
2 => cbor_map! {
|
||||||
|
1 => dummy_cert,
|
||||||
|
2 => dummy_pkey
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
AuthenticatorVendorConfigureParameters::try_from(cbor_value),
|
||||||
|
Ok(AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: false,
|
||||||
|
attestation_material: Some(AuthenticatorAttestationMaterial {
|
||||||
|
certificate: dummy_cert.to_vec(),
|
||||||
|
private_key: dummy_pkey
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,9 +117,8 @@ impl CtapHid {
|
|||||||
// CTAP specification (version 20190130) section 8.1.9.1.3
|
// CTAP specification (version 20190130) section 8.1.9.1.3
|
||||||
const PROTOCOL_VERSION: u8 = 2;
|
const PROTOCOL_VERSION: u8 = 2;
|
||||||
|
|
||||||
// The device version number is vendor-defined. For now we define them to be zero.
|
// The device version number is vendor-defined.
|
||||||
// TODO: Update with device version?
|
const DEVICE_VERSION_MAJOR: u8 = 1;
|
||||||
const DEVICE_VERSION_MAJOR: u8 = 0;
|
|
||||||
const DEVICE_VERSION_MINOR: u8 = 0;
|
const DEVICE_VERSION_MINOR: u8 = 0;
|
||||||
const DEVICE_VERSION_BUILD: u8 = 0;
|
const DEVICE_VERSION_BUILD: u8 = 0;
|
||||||
|
|
||||||
@@ -574,7 +573,7 @@ mod test {
|
|||||||
0x00,
|
0x00,
|
||||||
0x01,
|
0x01,
|
||||||
0x02, // Protocol version
|
0x02, // Protocol version
|
||||||
0x00, // Device version
|
0x01, // Device version
|
||||||
0x00,
|
0x00,
|
||||||
0x00,
|
0x00,
|
||||||
CtapHid::CAPABILITIES
|
CtapHid::CAPABILITIES
|
||||||
@@ -634,7 +633,7 @@ mod test {
|
|||||||
cid[2],
|
cid[2],
|
||||||
cid[3],
|
cid[3],
|
||||||
0x02, // Protocol version
|
0x02, // Protocol version
|
||||||
0x00, // Device version
|
0x01, // Device version
|
||||||
0x00,
|
0x00,
|
||||||
0x00,
|
0x00,
|
||||||
CtapHid::CAPABILITIES
|
CtapHid::CAPABILITIES
|
||||||
|
|||||||
@@ -17,9 +17,3 @@ pub const AAGUID_LENGTH: usize = 16;
|
|||||||
|
|
||||||
pub const AAGUID: &[u8; AAGUID_LENGTH] =
|
pub const AAGUID: &[u8; AAGUID_LENGTH] =
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
|
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
|
||||||
|
|
||||||
pub const ATTESTATION_CERTIFICATE: &[u8] =
|
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_cert.bin"));
|
|
||||||
|
|
||||||
pub const ATTESTATION_PRIVATE_KEY: &[u8; ATTESTATION_PRIVATE_KEY_LENGTH] =
|
|
||||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_pkey.bin"));
|
|
||||||
|
|||||||
198
src/ctap/mod.rs
198
src/ctap/mod.rs
@@ -29,7 +29,7 @@ mod timed_permission;
|
|||||||
use self::command::MAX_CREDENTIAL_COUNT_IN_LIST;
|
use self::command::MAX_CREDENTIAL_COUNT_IN_LIST;
|
||||||
use self::command::{
|
use self::command::{
|
||||||
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
|
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
|
||||||
AuthenticatorMakeCredentialParameters, Command,
|
AuthenticatorMakeCredentialParameters, AuthenticatorVendorConfigureParameters, Command,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
use self::data_formats::AuthenticatorTransport;
|
use self::data_formats::AuthenticatorTransport;
|
||||||
@@ -44,7 +44,7 @@ use self::pin_protocol_v1::PinPermission;
|
|||||||
use self::pin_protocol_v1::PinProtocolV1;
|
use self::pin_protocol_v1::PinProtocolV1;
|
||||||
use self::response::{
|
use self::response::{
|
||||||
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
|
AuthenticatorGetAssertionResponse, AuthenticatorGetInfoResponse,
|
||||||
AuthenticatorMakeCredentialResponse, ResponseData,
|
AuthenticatorMakeCredentialResponse, AuthenticatorVendorResponse, ResponseData,
|
||||||
};
|
};
|
||||||
use self::status_code::Ctap2StatusCode;
|
use self::status_code::Ctap2StatusCode;
|
||||||
use self::storage::PersistentStore;
|
use self::storage::PersistentStore;
|
||||||
@@ -67,6 +67,7 @@ use crypto::sha256::Sha256;
|
|||||||
use crypto::Hash256;
|
use crypto::Hash256;
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
use libtock_drivers::console::Console;
|
use libtock_drivers::console::Console;
|
||||||
|
use libtock_drivers::crp;
|
||||||
use libtock_drivers::timer::{ClockValue, Duration};
|
use libtock_drivers::timer::{ClockValue, Duration};
|
||||||
|
|
||||||
// This flag enables or disables basic attestation for FIDO2. U2F is unaffected by
|
// This flag enables or disables basic attestation for FIDO2. U2F is unaffected by
|
||||||
@@ -358,6 +359,10 @@ where
|
|||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
Command::AuthenticatorSelection => self.process_selection(cid),
|
Command::AuthenticatorSelection => self.process_selection(cid),
|
||||||
// TODO(kaczmarczyck) implement FIDO 2.1 commands
|
// TODO(kaczmarczyck) implement FIDO 2.1 commands
|
||||||
|
// Vendor specific commands
|
||||||
|
Command::AuthenticatorVendorConfigure(params) => {
|
||||||
|
self.process_vendor_configure(params, cid)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
#[cfg(feature = "debug_ctap")]
|
#[cfg(feature = "debug_ctap")]
|
||||||
writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap();
|
writeln!(&mut Console::new(), "Sending response: {:#?}", response).unwrap();
|
||||||
@@ -919,6 +924,74 @@ where
|
|||||||
Ok(ResponseData::AuthenticatorSelection)
|
Ok(ResponseData::AuthenticatorSelection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_vendor_configure(
|
||||||
|
&mut self,
|
||||||
|
params: AuthenticatorVendorConfigureParameters,
|
||||||
|
cid: ChannelID,
|
||||||
|
) -> Result<ResponseData, Ctap2StatusCode> {
|
||||||
|
(self.check_user_presence)(cid)?;
|
||||||
|
|
||||||
|
// Sanity checks
|
||||||
|
let current_priv_key = self.persistent_store.attestation_private_key()?;
|
||||||
|
let current_cert = self.persistent_store.attestation_certificate()?;
|
||||||
|
|
||||||
|
let response = match params.attestation_material {
|
||||||
|
// Only reading values.
|
||||||
|
None => AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: current_cert.is_some(),
|
||||||
|
pkey_programmed: current_priv_key.is_some(),
|
||||||
|
},
|
||||||
|
// Device is already fully programmed. We don't leak information.
|
||||||
|
Some(_) if current_cert.is_some() && current_priv_key.is_some() => {
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: true,
|
||||||
|
pkey_programmed: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Device is partially or not programmed. We complete the process.
|
||||||
|
Some(data) => {
|
||||||
|
if let Some(current_cert) = ¤t_cert {
|
||||||
|
if current_cert != &data.certificate {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(current_priv_key) = ¤t_priv_key {
|
||||||
|
if current_priv_key != &data.private_key {
|
||||||
|
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current_cert.is_none() {
|
||||||
|
self.persistent_store
|
||||||
|
.set_attestation_certificate(&data.certificate)?;
|
||||||
|
}
|
||||||
|
if current_priv_key.is_none() {
|
||||||
|
self.persistent_store
|
||||||
|
.set_attestation_private_key(&data.private_key)?;
|
||||||
|
}
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: true,
|
||||||
|
pkey_programmed: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if params.lockdown {
|
||||||
|
// To avoid bricking the authenticator, we only allow lockdown
|
||||||
|
// to happen if both values are programmed or if both U2F/CTAP1 and
|
||||||
|
// batch attestation are disabled.
|
||||||
|
#[cfg(feature = "with_ctap1")]
|
||||||
|
let need_certificate = true;
|
||||||
|
#[cfg(not(feature = "with_ctap1"))]
|
||||||
|
let need_certificate = USE_BATCH_ATTESTATION;
|
||||||
|
|
||||||
|
if (need_certificate && !(response.pkey_programmed && response.cert_programmed))
|
||||||
|
|| crp::set_protection(crp::ProtectionLevel::FullyLocked).is_err()
|
||||||
|
{
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(response))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_auth_data(
|
pub fn generate_auth_data(
|
||||||
&self,
|
&self,
|
||||||
rp_id_hash: &[u8],
|
rp_id_hash: &[u8],
|
||||||
@@ -941,6 +1014,7 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::command::AuthenticatorAttestationMaterial;
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
CoseKey, GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
|
CoseKey, GetAssertionExtensions, GetAssertionOptions, MakeCredentialExtensions,
|
||||||
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
MakeCredentialOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
|
||||||
@@ -2052,4 +2126,124 @@ mod test {
|
|||||||
last_counter = next_counter;
|
last_counter = next_counter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_configure() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let user_immediately_present = |_| Ok(());
|
||||||
|
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||||
|
|
||||||
|
// Nothing should be configured at the beginning
|
||||||
|
let response = ctap_state.process_vendor_configure(
|
||||||
|
AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: false,
|
||||||
|
attestation_material: None,
|
||||||
|
},
|
||||||
|
DUMMY_CHANNEL_ID,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: false,
|
||||||
|
pkey_programmed: false,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Inject dummy values
|
||||||
|
let dummy_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
||||||
|
let dummy_cert = [0xddu8; 20];
|
||||||
|
let response = ctap_state.process_vendor_configure(
|
||||||
|
AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: false,
|
||||||
|
attestation_material: Some(AuthenticatorAttestationMaterial {
|
||||||
|
certificate: dummy_cert.to_vec(),
|
||||||
|
private_key: dummy_key,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
DUMMY_CHANNEL_ID,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: true,
|
||||||
|
pkey_programmed: true,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.attestation_certificate()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
dummy_cert
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.attestation_private_key()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
dummy_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to inject other dummy values and check that initial values are retained.
|
||||||
|
let other_dummy_key = [0x44u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
||||||
|
let response = ctap_state.process_vendor_configure(
|
||||||
|
AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: false,
|
||||||
|
attestation_material: Some(AuthenticatorAttestationMaterial {
|
||||||
|
certificate: dummy_cert.to_vec(),
|
||||||
|
private_key: other_dummy_key,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
DUMMY_CHANNEL_ID,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: true,
|
||||||
|
pkey_programmed: true,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.attestation_certificate()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
dummy_cert
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ctap_state
|
||||||
|
.persistent_store
|
||||||
|
.attestation_private_key()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
dummy_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now try to lock the device
|
||||||
|
let response = ctap_state.process_vendor_configure(
|
||||||
|
AuthenticatorVendorConfigureParameters {
|
||||||
|
lockdown: true,
|
||||||
|
attestation_material: None,
|
||||||
|
},
|
||||||
|
DUMMY_CHANNEL_ID,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
response,
|
||||||
|
Ok(ResponseData::AuthenticatorVendor(
|
||||||
|
AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: true,
|
||||||
|
pkey_programmed: true,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ pub enum ResponseData {
|
|||||||
AuthenticatorReset,
|
AuthenticatorReset,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
AuthenticatorSelection,
|
AuthenticatorSelection,
|
||||||
|
AuthenticatorVendor(AuthenticatorVendorResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ResponseData> for Option<cbor::Value> {
|
impl From<ResponseData> for Option<cbor::Value> {
|
||||||
@@ -48,6 +49,7 @@ impl From<ResponseData> for Option<cbor::Value> {
|
|||||||
ResponseData::AuthenticatorReset => None,
|
ResponseData::AuthenticatorReset => None,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
ResponseData::AuthenticatorSelection => None,
|
ResponseData::AuthenticatorSelection => None,
|
||||||
|
ResponseData::AuthenticatorVendor(data) => Some(data.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,6 +233,27 @@ impl From<AuthenticatorClientPinResponse> for cbor::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
|
||||||
|
pub struct AuthenticatorVendorResponse {
|
||||||
|
pub cert_programmed: bool,
|
||||||
|
pub pkey_programmed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AuthenticatorVendorResponse> for cbor::Value {
|
||||||
|
fn from(vendor_response: AuthenticatorVendorResponse) -> Self {
|
||||||
|
let AuthenticatorVendorResponse {
|
||||||
|
cert_programmed,
|
||||||
|
pkey_programmed,
|
||||||
|
} = vendor_response;
|
||||||
|
|
||||||
|
cbor_map_options! {
|
||||||
|
1 => cert_programmed,
|
||||||
|
2 => pkey_programmed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::data_formats::PackedAttestationStatement;
|
use super::super::data_formats::PackedAttestationStatement;
|
||||||
@@ -401,4 +424,34 @@ mod test {
|
|||||||
let response_cbor: Option<cbor::Value> = ResponseData::AuthenticatorSelection.into();
|
let response_cbor: Option<cbor::Value> = ResponseData::AuthenticatorSelection.into();
|
||||||
assert_eq!(response_cbor, None);
|
assert_eq!(response_cbor, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vendor_response_into_cbor() {
|
||||||
|
let response_cbor: Option<cbor::Value> =
|
||||||
|
ResponseData::AuthenticatorVendor(AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: true,
|
||||||
|
pkey_programmed: false,
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
assert_eq!(
|
||||||
|
response_cbor,
|
||||||
|
Some(cbor_map_options! {
|
||||||
|
1 => true,
|
||||||
|
2 => false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
let response_cbor: Option<cbor::Value> =
|
||||||
|
ResponseData::AuthenticatorVendor(AuthenticatorVendorResponse {
|
||||||
|
cert_programmed: false,
|
||||||
|
pkey_programmed: true,
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
assert_eq!(
|
||||||
|
response_cbor,
|
||||||
|
Some(cbor_map_options! {
|
||||||
|
1 => false,
|
||||||
|
2 => true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,36 +115,12 @@ impl PersistentStore {
|
|||||||
self.store.insert(key::CRED_RANDOM_SECRET, &cred_random)?;
|
self.store.insert(key::CRED_RANDOM_SECRET, &cred_random)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jmichel): remove this when vendor command is in place
|
|
||||||
#[cfg(not(test))]
|
|
||||||
self.load_attestation_data_from_firmware()?;
|
|
||||||
if self.store.find_handle(key::AAGUID)?.is_none() {
|
if self.store.find_handle(key::AAGUID)?.is_none() {
|
||||||
self.set_aaguid(key_material::AAGUID)?;
|
self.set_aaguid(key_material::AAGUID)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jmichel): remove this function when vendor command is in place.
|
|
||||||
#[cfg(not(test))]
|
|
||||||
fn load_attestation_data_from_firmware(&mut self) -> Result<(), Ctap2StatusCode> {
|
|
||||||
// The following 2 entries are meant to be written by vendor-specific commands.
|
|
||||||
if self
|
|
||||||
.store
|
|
||||||
.find_handle(key::ATTESTATION_PRIVATE_KEY)?
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
self.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)?;
|
|
||||||
}
|
|
||||||
if self
|
|
||||||
.store
|
|
||||||
.find_handle(key::ATTESTATION_CERTIFICATE)?
|
|
||||||
.is_none()
|
|
||||||
{
|
|
||||||
self.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the first matching credential.
|
/// Returns the first matching credential.
|
||||||
///
|
///
|
||||||
/// Returns `None` if no credentials are matched or if `check_cred_protect` is set and the first
|
/// Returns `None` if no credentials are matched or if `check_cred_protect` is set and the first
|
||||||
@@ -988,12 +964,14 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.is_none());
|
.is_none());
|
||||||
|
|
||||||
// Make sure the persistent keys are initialized.
|
// Make sure the persistent keys are initialized to dummy values.
|
||||||
|
let dummy_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
||||||
|
let dummy_cert = [0xddu8; 20];
|
||||||
persistent_store
|
persistent_store
|
||||||
.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
|
.set_attestation_private_key(&dummy_key)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
persistent_store
|
persistent_store
|
||||||
.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
|
.set_attestation_certificate(&dummy_cert)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(&persistent_store.aaguid().unwrap(), key_material::AAGUID);
|
assert_eq!(&persistent_store.aaguid().unwrap(), key_material::AAGUID);
|
||||||
|
|
||||||
@@ -1001,11 +979,11 @@ mod test {
|
|||||||
persistent_store.reset(&mut rng).unwrap();
|
persistent_store.reset(&mut rng).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&persistent_store.attestation_private_key().unwrap().unwrap(),
|
&persistent_store.attestation_private_key().unwrap().unwrap(),
|
||||||
key_material::ATTESTATION_PRIVATE_KEY
|
&dummy_key
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
persistent_store.attestation_certificate().unwrap().unwrap(),
|
persistent_store.attestation_certificate().unwrap().unwrap(),
|
||||||
key_material::ATTESTATION_CERTIFICATE
|
&dummy_cert
|
||||||
);
|
);
|
||||||
assert_eq!(&persistent_store.aaguid().unwrap(), key_material::AAGUID);
|
assert_eq!(&persistent_store.aaguid().unwrap(), key_material::AAGUID);
|
||||||
}
|
}
|
||||||
|
|||||||
52
third_party/libtock-drivers/src/crp.rs
vendored
Normal file
52
third_party/libtock-drivers/src/crp.rs
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use crate::result::TockResult;
|
||||||
|
use libtock_core::syscalls;
|
||||||
|
|
||||||
|
const DRIVER_NUMBER: usize = 0x00008;
|
||||||
|
|
||||||
|
mod command_nr {
|
||||||
|
pub const AVAILABLE: usize = 0;
|
||||||
|
pub const GET_PROTECTION: usize = 1;
|
||||||
|
pub const SET_PROTECTION: usize = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialOrd, PartialEq)]
|
||||||
|
pub enum ProtectionLevel {
|
||||||
|
/// Unsupported feature
|
||||||
|
Unknown = 0,
|
||||||
|
/// This should be the factory default for the chip.
|
||||||
|
NoProtection = 1,
|
||||||
|
/// At this level, only JTAG/SWD are disabled but other debugging
|
||||||
|
/// features may still be enabled.
|
||||||
|
JtagDisabled = 2,
|
||||||
|
/// This is the maximum level of protection the chip supports.
|
||||||
|
/// At this level, JTAG and all other features are expected to be
|
||||||
|
/// disabled and only a full chip erase may allow to recover from
|
||||||
|
/// that state.
|
||||||
|
FullyLocked = 0xff,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for ProtectionLevel {
|
||||||
|
fn from(value: usize) -> Self {
|
||||||
|
match value {
|
||||||
|
1 => ProtectionLevel::NoProtection,
|
||||||
|
2 => ProtectionLevel::JtagDisabled,
|
||||||
|
0xff => ProtectionLevel::FullyLocked,
|
||||||
|
_ => ProtectionLevel::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_available() -> TockResult<()> {
|
||||||
|
syscalls::command(DRIVER_NUMBER, command_nr::AVAILABLE, 0, 0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_protection() -> TockResult<ProtectionLevel> {
|
||||||
|
let current_level = syscalls::command(DRIVER_NUMBER, command_nr::GET_PROTECTION, 0, 0)?;
|
||||||
|
Ok(current_level.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_protection(level: ProtectionLevel) -> TockResult<()> {
|
||||||
|
syscalls::command(DRIVER_NUMBER, command_nr::SET_PROTECTION, level as usize, 0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
1
third_party/libtock-drivers/src/lib.rs
vendored
1
third_party/libtock-drivers/src/lib.rs
vendored
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
pub mod buttons;
|
pub mod buttons;
|
||||||
pub mod console;
|
pub mod console;
|
||||||
|
pub mod crp;
|
||||||
pub mod led;
|
pub mod led;
|
||||||
#[cfg(feature = "with_nfc")]
|
#[cfg(feature = "with_nfc")]
|
||||||
pub mod nfc;
|
pub mod nfc;
|
||||||
|
|||||||
206
tools/configure.py
Executable file
206
tools/configure.py
Executable file
@@ -0,0 +1,206 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2020 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
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import getpass
|
||||||
|
import datetime
|
||||||
|
import sys
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import colorama
|
||||||
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
|
|
||||||
|
from fido2 import ctap
|
||||||
|
from fido2 import ctap2
|
||||||
|
from fido2 import hid
|
||||||
|
|
||||||
|
OPENSK_VID_PID = (0x1915, 0x521F)
|
||||||
|
OPENSK_VENDOR_CONFIGURE = 0x40
|
||||||
|
|
||||||
|
|
||||||
|
def fatal(msg):
|
||||||
|
tqdm.write("{style_begin}fatal:{style_end} {message}".format(
|
||||||
|
style_begin=colorama.Fore.RED + colorama.Style.BRIGHT,
|
||||||
|
style_end=colorama.Style.RESET_ALL,
|
||||||
|
message=msg))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def error(msg):
|
||||||
|
tqdm.write("{style_begin}error:{style_end} {message}".format(
|
||||||
|
style_begin=colorama.Fore.RED,
|
||||||
|
style_end=colorama.Style.RESET_ALL,
|
||||||
|
message=msg))
|
||||||
|
|
||||||
|
|
||||||
|
def info(msg):
|
||||||
|
tqdm.write("{style_begin}info:{style_end} {message}".format(
|
||||||
|
style_begin=colorama.Fore.GREEN + colorama.Style.BRIGHT,
|
||||||
|
style_end=colorama.Style.RESET_ALL,
|
||||||
|
message=msg))
|
||||||
|
|
||||||
|
|
||||||
|
def get_opensk_devices(batch_mode):
|
||||||
|
devices = []
|
||||||
|
for dev in hid.CtapHidDevice.list_devices():
|
||||||
|
if (dev.descriptor["vendor_id"],
|
||||||
|
dev.descriptor["product_id"]) == OPENSK_VID_PID:
|
||||||
|
if dev.capabilities & hid.CAPABILITY.CBOR:
|
||||||
|
if batch_mode:
|
||||||
|
devices.append(ctap2.CTAP2(dev))
|
||||||
|
else:
|
||||||
|
return [ctap2.CTAP2(dev)]
|
||||||
|
return devices
|
||||||
|
|
||||||
|
|
||||||
|
def get_private_key(data, password=None):
|
||||||
|
# First we try without password.
|
||||||
|
try:
|
||||||
|
return serialization.load_pem_private_key(data, password=None)
|
||||||
|
except TypeError:
|
||||||
|
# Maybe we need a password then.
|
||||||
|
if sys.stdin.isatty():
|
||||||
|
password = getpass.getpass(prompt="Private key password: ")
|
||||||
|
else:
|
||||||
|
password = sys.stdin.readline().rstrip()
|
||||||
|
return get_private_key(data, password=password.encode(sys.stdin.encoding))
|
||||||
|
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
colorama.init()
|
||||||
|
# We need either both the certificate and the key or none
|
||||||
|
if bool(args.priv_key) ^ bool(args.certificate):
|
||||||
|
fatal("Certificate and private key must be set together or both omitted.")
|
||||||
|
|
||||||
|
cbor_data = {1: args.lock}
|
||||||
|
|
||||||
|
if args.priv_key:
|
||||||
|
cbor_data[1] = args.lock
|
||||||
|
priv_key = get_private_key(args.priv_key.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.")
|
||||||
|
|
||||||
|
cert = x509.load_pem_x509_certificate(args.certificate.read())
|
||||||
|
# Some sanity/validity checks
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
if cert.not_valid_before > now:
|
||||||
|
fatal("Certificate validity starts in the future.")
|
||||||
|
if cert.not_valid_after <= now:
|
||||||
|
fatal("Certificate expired.")
|
||||||
|
pub_key = cert.public_key()
|
||||||
|
if not isinstance(pub_key, ec.EllipticCurvePublicKey):
|
||||||
|
fatal("Certificate public key must be an Elliptic Curve one.")
|
||||||
|
if not isinstance(pub_key.curve, ec.SECP256R1):
|
||||||
|
fatal("Certificate public key must use Secp256r1 curve.")
|
||||||
|
if pub_key.key_size != 256:
|
||||||
|
fatal("Certificate public key must be 256 bits long.")
|
||||||
|
if pub_key.public_numbers() != priv_key.public_key().public_numbers():
|
||||||
|
fatal("Certificate public doesn't match with the private key.")
|
||||||
|
info("Certificate is valid.")
|
||||||
|
|
||||||
|
cbor_data[2] = {
|
||||||
|
1:
|
||||||
|
cert.public_bytes(serialization.Encoding.DER),
|
||||||
|
2:
|
||||||
|
priv_key.private_numbers().private_value.to_bytes(
|
||||||
|
length=32, byteorder='big', signed=False)
|
||||||
|
}
|
||||||
|
|
||||||
|
for authenticator in tqdm(get_opensk_devices(args.batch)):
|
||||||
|
# If the device supports it, wink to show which device
|
||||||
|
# we're going to program.
|
||||||
|
if authenticator.device.capabilities & hid.CAPABILITY.WINK:
|
||||||
|
authenticator.device.wink()
|
||||||
|
aaguid = uuid.UUID(bytes=authenticator.get_info().aaguid)
|
||||||
|
info(("Programming device {} AAGUID {} ({}). "
|
||||||
|
"Please touch the device to confirm...").format(
|
||||||
|
authenticator.device.descriptor.get("product_string", "Unknown"),
|
||||||
|
aaguid, authenticator.device))
|
||||||
|
try:
|
||||||
|
result = authenticator.send_cbor(
|
||||||
|
OPENSK_VENDOR_CONFIGURE,
|
||||||
|
data=cbor_data,
|
||||||
|
)
|
||||||
|
info("Certificate: {}".format("Present" if result[1] else "Missing"))
|
||||||
|
info("Private Key: {}".format("Present" if result[2] else "Missing"))
|
||||||
|
if args.lock:
|
||||||
|
info("Device is now locked down!")
|
||||||
|
except ctap.CtapError as ex:
|
||||||
|
if ex.code.value == ctap.CtapError.ERR.INVALID_COMMAND:
|
||||||
|
error("Failed to configure OpenSK (unsupported command).")
|
||||||
|
elif ex.code.value == 0xF2: # VENDOR_INTERNAL_ERROR
|
||||||
|
error(("Failed to configure OpenSK (lockdown conditions not met "
|
||||||
|
"or hardware error)."))
|
||||||
|
elif ex.code.value == ctap.CtapError.ERR.INVALID_PARAMETER:
|
||||||
|
error(
|
||||||
|
("Failed to configure OpenSK (device is partially programmed but "
|
||||||
|
"the given cert/key don't match the ones currently programmed)."))
|
||||||
|
else:
|
||||||
|
error("Failed to configure OpenSK (unknown error: {}".format(ex))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--batch",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help=(
|
||||||
|
"When batch processing is used, all plugged OpenSK devices will "
|
||||||
|
"be programmed the same way. Otherwise (default) only the first seen "
|
||||||
|
"device will be programmed."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--certificate",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
default=None,
|
||||||
|
metavar="PEM_FILE",
|
||||||
|
dest="certificate",
|
||||||
|
help=("PEM file containing the certificate to inject into "
|
||||||
|
"the OpenSK authenticator."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--private-key",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
default=None,
|
||||||
|
metavar="PEM_FILE",
|
||||||
|
dest="priv_key",
|
||||||
|
help=("PEM file containing the private key associated "
|
||||||
|
"with the certificate."),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--lock-device",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
dest="lock",
|
||||||
|
help=("Locks the device (i.e. bootloader and JTAG access). "
|
||||||
|
"This command can fail if the certificate or the private key "
|
||||||
|
"haven't been both programmed yet."),
|
||||||
|
)
|
||||||
|
main(parser.parse_args())
|
||||||
Reference in New Issue
Block a user