vendor patches & crates.io compliant metadata
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is failing

This commit is contained in:
shimunn 2019-09-25 17:31:44 +02:00
parent 95a4f97f58
commit c44b0c35ba
Signed by: shimun
GPG Key ID: E81D8382DC2F971B
33 changed files with 4120 additions and 19 deletions

20
Cargo.lock generated
View File

@ -50,7 +50,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "blkid-rs"
version = "0.1.1"
source = "git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot#09189246eac5b930d13a4e0d78d75157bf07c832"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -105,12 +104,11 @@ dependencies = [
[[package]]
name = "cryptsetup-rs"
version = "0.2.0"
source = "git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot#09189246eac5b930d13a4e0d78d75157bf07c832"
dependencies = [
"blkid-rs 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)",
"blkid-rs 0.1.1",
"errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"libcryptsetup-sys 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)",
"libcryptsetup-sys 0.1.1",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -118,7 +116,6 @@ dependencies = [
[[package]]
name = "ctap"
version = "0.1.0"
source = "git+https://github.com/shimunn/ctap.git?branch=hmac_ext#3d3679d5b9a8c8cc90edb969c0f187740a3f2480"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"cbor-codec 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -173,13 +170,13 @@ dependencies = [
[[package]]
name = "fido2luks"
version = "0.1.0"
version = "0.2.1"
dependencies = [
"cryptsetup-rs 0.2.0 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)",
"ctap 0.1.0 (git+https://github.com/shimunn/ctap.git?branch=hmac_ext)",
"cryptsetup-rs 0.2.0",
"ctap 0.1.0",
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libcryptsetup-sys 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)",
"libcryptsetup-sys 0.1.1",
"rpassword 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -221,7 +218,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libcryptsetup-sys"
version = "0.1.1"
source = "git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot#09189246eac5b930d13a4e0d78d75157bf07c832"
dependencies = [
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
@ -612,15 +608,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "5180c5a20655b14a819b652fd2378fa5f1697b6c9ddad3e695c2f9cedf6df4e2"
"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b"
"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
"checksum blkid-rs 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)" = "<none>"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum cbor-codec 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e083a023562b37c52837e850131a51b1154cceb9d149f41ee3d386737b140f46"
"checksum cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc9a35e1f4290eb9e5fc54ba6cf40671ed2a2514c3eeb2b2a908dda2ea5a1be"
"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum cryptsetup-rs 0.2.0 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)" = "<none>"
"checksum ctap 0.1.0 (git+https://github.com/shimunn/ctap.git?branch=hmac_ext)" = "<none>"
"checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e"
"checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067"
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
@ -631,7 +624,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum libcryptsetup-sys 0.1.1 (git+https://github.com/shimunn/cryptsetup-rs.git?branch=update_keyslot)" = "<none>"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"

View File

@ -1,14 +1,16 @@
[package]
name = "fido2luks"
version = "0.2.0"
version = "0.2.1"
authors = ["shimunn <shimun@shimun.net>"]
edition = "2018"
description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"
license = "GPL v3"
repository = "https://github.com/shimunn/fido2luks"
[dependencies]
ctap = { git = "https://github.com/shimunn/ctap.git", branch = "hmac_ext" }
#cryptsetup-rs = "0.2.0"
cryptsetup-rs = { git = "https://github.com/shimunn/cryptsetup-rs.git", branch = "update_keyslot" }
libcryptsetup-sys = { git = "https://github.com/shimunn/cryptsetup-rs.git", branch = "update_keyslot" }
ctap = "0.1.0"
cryptsetup-rs = "0.2.0"
libcryptsetup-sys = "0.1.1"
hex = "0.3.2"
rust-crypto = "0.2.36"
@ -16,9 +18,21 @@ failure = "0.1.5"
rpassword = "4.0.1"
structopt = "0.3.2"
[patch.crates-io]
#Until https://github.com/solidninja/cryptsetup-rs/pull/2 merges
cryptsetup-rs = { path = "./patch/cryptsetup-rs" }
libcryptsetup-sys = { path = "./patch/cryptsetup-rs/libcryptsetup-sys" }
ctap = { path = "./patch/ctap" }
[profile.release]
lto = true
opt-level = 'z'
panic = 'abort'
incremental = false
overflow-checks = false
[package.metadata.rpm]
buildflags = ["--release"]
[package.metadata.rpm.targets]
fido2luks = { path = "/usr/bin/fido2luks" }

4
patch/cryptsetup-rs/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target/
Cargo.lock
*.iml
.idea/

View File

@ -0,0 +1,22 @@
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
sudo: required
dist: trusty
os:
- linux
script:
- cargo build --all-targets --verbose
- sudo /bin/sh -c "PATH=/home/travis/.cargo/bin:$PATH LD_LIBRARY_PATH=/home/travis/.cargo/lib cargo test --verbose"
addons:
apt:
packages:
- libcryptsetup-dev
notifications:
email:
on_success: never

View File

@ -0,0 +1,32 @@
[package]
authors = ["Vladimir Lushnikov <vladimir@solidninja.is>"]
description = "Rust wrapper around the libcryptsetup library, allowing manipulation of LUKS devices in Linux"
homepage = "https://github.com/solidninja/cryptsetup-rs"
license = "LGPL-3.0"
name = "cryptsetup-rs"
version = "0.2.0"
[dependencies]
errno = "0.2.3"
libc = "0.2.42"
log = "0.4.2"
[dependencies.blkid-rs]
path = "blkid-rs"
version = "0.1.1"
[dependencies.libcryptsetup-sys]
path = "libcryptsetup-sys"
version = "0.1.1"
[dependencies.uuid]
features = ["v4"]
version = "0.6.5"
[dev-dependencies]
env_logger = "0.5.10"
expectest = "0.10.0"
tempdir = "0.3.7"
[lib]
name = "cryptsetup_rs"

165
patch/cryptsetup-rs/LICENSE Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -0,0 +1,29 @@
[![Build Status](https://travis-ci.org/solidninja/cryptsetup-rs.svg?branch=master)](https://travis-ci.org/solidninja/cryptsetup-rs)
[![crates.io Status](https://img.shields.io/crates/v/cryptsetup-rs.svg)](https://crates.io/crates/cryptsetup-rs)
[![docs.rs build](https://docs.rs/cryptsetup-rs/badge.svg)](https://docs.rs/crate/cryptsetup-rs/)
# cryptsetup-rs - Rust bindings to `libcryptsetup` on Linux
A safe binding to `libcryptsetup` that allows working with encrypted disks on Linux.
Features:
* High-level API for open/format/other operations
Documentation for the bindings can be found on [docs.rs](https://docs.rs/crate/cryptsetup-rs/).
The example [`luks_dump.rs`](examples/luks_dump.rs) shows how a command like `cryptsetup luksDump` can
be implemented.
## TODO
* Secure string for passing keys
* High-level API for non-LUKS1 disks (truecrypt, verity)
* LUKS2 and cryptsetup2 support
## Contributing
`cryptsetup-rs` is the work of its contributors and is a free software project licensed under the
LGPLv3 or later.
If you would like to contribute, please follow the [C4](https://rfc.zeromq.org/spec:42/C4/) process.

View File

@ -0,0 +1,2 @@
target/
Cargo.lock

View File

@ -0,0 +1,18 @@
[package]
name = "blkid-rs"
version = "0.1.1"
authors = ["Vladimir Lushnikov <vladimir@solidninja.is>"]
description = "Rust implementation of blkid for LUKS volumes"
license = "LGPL-3.0"
homepage = "https://github.com/solidninja/libcryptset-rs"
[lib]
name = "blkid_rs"
[dependencies]
byteorder = "1.1"
uuid = "0.6"
[dev-dependencies]
env_logger = "0.4"
tempdir = "0.3"

View File

@ -0,0 +1,286 @@
// The following code has been ported from libcryptsetup
extern crate byteorder;
extern crate uuid;
use std::convert;
use std::io;
use std::io::Read;
use std::mem;
use std::str;
pub trait LuksHeader {
fn version(&self) -> u16;
fn cipher_name(&self) -> Result<&str, Error>;
fn cipher_mode(&self) -> Result<&str, Error>;
fn hash_spec(&self) -> Result<&str, Error>;
fn payload_offset(&self) -> u32;
fn key_bytes(&self) -> u32;
fn mk_digest(&self) -> &[u8];
fn mk_digest_salt(&self) -> &[u8];
fn mk_digest_iterations(&self) -> u32;
fn uuid(&self) -> Result<uuid::Uuid, Error>;
}
#[derive(Debug)]
pub enum Error {
InvalidMagic,
InvalidStringEncoding(str::Utf8Error),
InvalidVersion,
InvalidUuid(uuid::ParseError),
ReadError(io::Error),
ReadIncorrectHeaderSize,
HeaderProcessingError,
}
pub struct BlockDevice;
impl BlockDevice {
pub fn read_luks_header<R: Read>(reader: R) -> Result<raw::luks_phdr, Error> {
let luks_phdr_size = mem::size_of::<raw::luks_phdr>();
let mut buf = Vec::<u8>::with_capacity(luks_phdr_size);
let read_size = try!(reader.take(luks_phdr_size as u64).read_to_end(&mut buf));
if read_size != luks_phdr_size {
Err(Error::ReadIncorrectHeaderSize)
} else {
raw::luks_phdr::from_buf(&buf)
}
}
}
impl convert::From<str::Utf8Error> for Error {
fn from(error: str::Utf8Error) -> Error {
Error::InvalidStringEncoding(error)
}
}
impl convert::From<uuid::ParseError> for Error {
fn from(error: uuid::ParseError) -> Error {
Error::InvalidUuid(error)
}
}
impl convert::From<io::Error> for Error {
fn from(error: io::Error) -> Error {
Error::ReadError(error)
}
}
/* FIXME
impl convert::From<byteorder::Error> for Error {
fn from(error: byteorder::Error) -> Error {
match error {
byteorder::Error::UnexpectedEOF => Error::HeaderProcessingError,
byteorder::Error::Io(io_error) => Error::ReadError(io_error),
}
}
}
*/
pub mod raw {
#![allow(non_snake_case)]
use std::convert::From;
use std::io::{Cursor, Read};
use std::mem;
use std::str;
use byteorder::{BigEndian, ReadBytesExt};
use uuid;
const LUKS_VERSION_SUPPORTED: u16 = 1;
const LUKS_MAGIC_L: usize = 6;
const LUKS_CIPHERNAME_L: usize = 32;
const LUKS_CIPHERMODE_L: usize = 32;
const LUKS_HASHSPEC_L: usize = 32;
const LUKS_DIGESTSIZE: usize = 20;
const LUKS_SALTSIZE: usize = 32;
const UUID_STRING_L: usize = 40;
const LUKS_MAGIC: &'static [u8; LUKS_MAGIC_L] = b"LUKS\xba\xbe";
#[repr(C, packed)]
pub struct luks_phdr {
pub magic: [u8; LUKS_MAGIC_L],
pub version: u16,
pub cipherName: [u8; LUKS_CIPHERNAME_L],
pub cipherMode: [u8; LUKS_CIPHERMODE_L],
pub hashSpec: [u8; LUKS_HASHSPEC_L],
pub payloadOffset: u32,
pub keyBytes: u32,
pub mkDigest: [u8; LUKS_DIGESTSIZE],
pub mkDigestSalt: [u8; LUKS_SALTSIZE],
pub mkDigestIterations: u32,
pub uuid: [u8; UUID_STRING_L],
}
impl luks_phdr {
pub fn from_buf(buf: &[u8]) -> Result<luks_phdr, super::Error> {
// FIXME - this is not particularly pretty
if buf.len() != mem::size_of::<luks_phdr>() {
return Err(super::Error::ReadIncorrectHeaderSize);
}
let mut cursor = Cursor::new(buf);
let mut magic_buf = [0u8; LUKS_MAGIC_L];
let magic_len = try!(cursor.read(&mut magic_buf));
if magic_len != LUKS_MAGIC_L || magic_buf != &LUKS_MAGIC[..] {
return Err(super::Error::InvalidMagic);
}
let version = try!(cursor.read_u16::<BigEndian>());
if version != LUKS_VERSION_SUPPORTED {
return Err(super::Error::InvalidVersion);
}
let mut cipher_name_buf = [0u8; LUKS_CIPHERNAME_L];
let cipher_name_len = try!(cursor.read(&mut cipher_name_buf));
if cipher_name_len != LUKS_CIPHERNAME_L {
return Err(super::Error::HeaderProcessingError);
}
let mut cipher_mode_buf = [0u8; LUKS_CIPHERMODE_L];
let cipher_mode_len = try!(cursor.read(&mut cipher_mode_buf));
if cipher_mode_len != LUKS_CIPHERMODE_L {
return Err(super::Error::HeaderProcessingError);
}
let mut hash_spec_buf = [0u8; LUKS_HASHSPEC_L];
let hash_spec_len = try!(cursor.read(&mut hash_spec_buf));
if hash_spec_len != LUKS_HASHSPEC_L {
return Err(super::Error::HeaderProcessingError);
}
let payload_offset = try!(cursor.read_u32::<BigEndian>());
let key_bytes = try!(cursor.read_u32::<BigEndian>());
let mut mk_digest_buf = [0u8; LUKS_DIGESTSIZE];
let mk_digest_len = try!(cursor.read(&mut mk_digest_buf));
if mk_digest_len != LUKS_DIGESTSIZE {
return Err(super::Error::HeaderProcessingError);
}
let mut mk_digest_salt_buf = [0u8; LUKS_SALTSIZE];
let mk_digest_salt_len = try!(cursor.read(&mut mk_digest_salt_buf));
if mk_digest_salt_len != LUKS_SALTSIZE {
return Err(super::Error::HeaderProcessingError);
}
let mk_digest_iterations = try!(cursor.read_u32::<BigEndian>());
let mut uuid_buf = [0u8; UUID_STRING_L];
let uuid_len = try!(cursor.read(&mut uuid_buf));
if uuid_len != UUID_STRING_L {
return Err(super::Error::HeaderProcessingError);
}
let res = luks_phdr {
magic: magic_buf,
version: version,
cipherName: cipher_name_buf,
cipherMode: cipher_mode_buf,
hashSpec: hash_spec_buf,
payloadOffset: payload_offset,
keyBytes: key_bytes,
mkDigest: mk_digest_buf,
mkDigestSalt: mk_digest_salt_buf,
mkDigestIterations: mk_digest_iterations,
uuid: uuid_buf,
};
Ok(res)
}
}
fn u8_buf_to_str(buf: &[u8]) -> Result<&str, super::Error> {
if let Some(pos) = buf.iter().position(|&c| c == 0) {
str::from_utf8(&buf[0..pos]).map_err(From::from)
} else {
str::from_utf8(buf).map_err(From::from)
}
}
impl super::LuksHeader for luks_phdr {
fn version(&self) -> u16 {
self.version
}
fn cipher_name(&self) -> Result<&str, super::Error> {
u8_buf_to_str(&self.cipherName)
}
fn cipher_mode(&self) -> Result<&str, super::Error> {
u8_buf_to_str(&self.cipherMode)
}
fn hash_spec(&self) -> Result<&str, super::Error> {
u8_buf_to_str(&self.hashSpec)
}
fn payload_offset(&self) -> u32 {
self.payloadOffset
}
fn key_bytes(&self) -> u32 {
self.keyBytes
}
fn mk_digest(&self) -> &[u8] {
&self.mkDigest
}
fn mk_digest_salt(&self) -> &[u8] {
&self.mkDigestSalt
}
fn mk_digest_iterations(&self) -> u32 {
self.mkDigestIterations
}
fn uuid(&self) -> Result<uuid::Uuid, super::Error> {
let uuid_str = try!(u8_buf_to_str(&self.uuid));
uuid::Uuid::parse_str(uuid_str).map_err(From::from)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
use uuid;
#[test]
fn test_luks_header_from_byte_buffer() {
let header = b"LUKS\xba\xbe\x00\x01aes\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ecb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00sha256\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00 \xcf^\xb4\xc00q\xbe\xd5\xe6\x90\xc8G\xb3\x00\xbe\xba\xd052qp\x92\x0c\x9c\xa9\x07R\\y_D\x08b\xf1\xe6\x8f\x0c\xa95\xad\xdb\x15+\xa5\xd7\xa7\xbf^\x96B\x90z\x00\x00\x03\xe8a1b49d2d-8a7e-4b04-ab2a-89f3408fd198\x00\x00\x00\x00";
let mut cursor: Cursor<&[u8]> = Cursor::new(header);
let luks_header = BlockDevice::read_luks_header(&mut cursor).unwrap();
assert_eq!(luks_header.version(), 1);
assert_eq!(luks_header.cipher_name().unwrap(), "aes");
assert_eq!(luks_header.cipher_mode().unwrap(), "ecb");
assert_eq!(luks_header.hash_spec().unwrap(), "sha256");
assert_eq!(luks_header.payload_offset(), 4096);
assert_eq!(luks_header.key_bytes(), 32);
assert_eq!(
luks_header.mk_digest(),
&[
207, 94, 180, 192, 48, 113, 190, 213, 230, 144, 200, 71, 179, 0, 190, 186, 208, 53,
50, 113
]
);
assert_eq!(
luks_header.mk_digest_salt(),
&[
112, 146, 12, 156, 169, 7, 82, 92, 121, 95, 68, 8, 98, 241, 230, 143, 12, 169, 53,
173, 219, 21, 43, 165, 215, 167, 191, 94, 150, 66, 144, 122
]
);
assert_eq!(luks_header.mk_digest_iterations(), 1000);
assert_eq!(
luks_header.uuid().unwrap(),
uuid::Uuid::parse_str("a1b49d2d-8a7e-4b04-ab2a-89f3408fd198").unwrap()
)
}
}

View File

@ -0,0 +1,78 @@
#![deny(warnings)]
#[warn(unused_must_use)]
extern crate cryptsetup_rs;
extern crate env_logger;
use cryptsetup_rs::*;
use std::env;
fn dump_slot(crypt_device: &Luks1CryptDevice, slot: Keyslot) -> Result<()> {
let status = match crypt_device.keyslot_status(slot) {
crypt_keyslot_info::CRYPT_SLOT_INVALID => "INVALID",
crypt_keyslot_info::CRYPT_SLOT_INACTIVE => "DISABLED",
crypt_keyslot_info::CRYPT_SLOT_ACTIVE | crypt_keyslot_info::CRYPT_SLOT_ACTIVE_LAST => "ENABLED",
};
println!("Key Slot {}: {}", slot, status);
match status {
"ENABLED" => /* TODO add keyslot information */ (),
_ => (),
}
Ok(())
}
fn dump(device_path: &str) -> Result<()> {
let dev = open(device_path)?.luks1()?;
println!("LUKS header information for {}", dev.device_name());
println!();
println!("{:<16}{}", "Version:", "1");
println!("{:<16}{}", "Cipher name:", dev.cipher());
println!("{:<16}{}", "Cipher mode:", dev.cipher_mode());
println!("{:<16}{}", "Hash spec:", dev.hash_spec());
println!("{:<16}{}", "Payload offset:", dev.payload_offset());
println!("{:<16}{}", "MK bits:", dev.mk_bits());
print!("{:<16}", "MK digest:");
for b in dev.mk_digest().iter() {
print!("{:x} ", b);
}
println!();
let salt = dev.mk_salt();
print!("{:<16}", "MK salt:");
for b in salt[0..16].iter() {
print!("{:x} ", b);
}
println!();
print!("{:<16}", "");
for b in salt[16..32].iter() {
print!("{:x} ", b);
}
println!();
println!("{:<16}{}", "MK iterations:", dev.mk_iterations());
println!("{:<16}{}", "UUID:", dev.uuid());
println!();
for slot in 0..8 {
dump_slot(&dev, slot)?;
}
Ok(())
}
fn main() {
let args: Vec<String> = env::args().skip(1).collect();
if args.len() != 1 {
println!("Usage: luks_dump <device path>");
::std::process::exit(1);
}
let device_path = args[0].as_str();
if let Err(e) = dump(device_path) {
println!("Error: {:?}", e);
::std::process::exit(2);
}
}

View File

@ -0,0 +1,21 @@
[package]
name = "libcryptsetup-sys"
version = "0.1.1"
authors = ["Vladimir Lushnikov <vladimir@solidninja.is>"]
license = "LGPL-3.0"
description = "FFI bindings to the libcryptsetup library"
homepage = "https://github.com/solidninja/cryptsetup-rs"
links = "cryptsetup"
build = "build.rs"
[lib]
name = "libcryptsetup_sys"
path = "lib.rs"
[dependencies]
libc = "0.2"
[build-dependencies]
pkg-config = "0.3"

View File

@ -0,0 +1,8 @@
extern crate pkg_config;
fn main() {
pkg_config::Config::new()
.statik(true)
.find("libcryptsetup")
.unwrap();
}

View File

@ -0,0 +1,492 @@
#![deny(warnings)]
#![allow(non_camel_case_types)]
extern crate libc;
use libc::{c_char, c_double, c_int, c_uint, c_void, size_t};
use std::str::FromStr;
pub enum crypt_device {}
#[repr(i32)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum crypt_log_level {
CRYPT_LOG_NORMAL = 0,
CRYPT_LOG_ERROR = 1,
CRYPT_LOG_VERBOSE = 2,
CRYPT_LOG_DEBUG = -1,
}
pub type crypt_log_cb = extern "C" fn(crypt_log_level, *const c_char, *mut c_void);
pub type crypt_confirm_cb = extern "C" fn(*const c_char, *mut c_void) -> c_int;
pub type crypt_password_cb =
extern "C" fn(*const c_char, *mut c_char, size_t, *mut c_void) -> c_int;
#[repr(i32)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum crypt_rng_type {
CRYPT_RNG_URANDOM = 0,
CRYPT_RNG_RANDOM = 1,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum crypt_device_type {
PLAIN,
LUKS1,
LOOPAES,
VERITY,
TCRYPT,
}
#[repr(C)]
pub struct crypt_params_plain {
pub hash: *const c_char,
pub offset: u64,
pub skip: u64,
pub size: u64,
}
#[repr(C)]
pub struct crypt_params_luks1 {
pub hash: *const c_char,
pub data_alignment: size_t,
pub data_device: *const c_char,
}
#[repr(C)]
pub struct crypt_params_loopaes {
pub hash: *const c_char,
pub offset: u64,
pub skip: u64,
}
#[repr(C)]
pub struct crypt_params_verity {
pub hash_name: *const c_char,
pub data_device: *const c_char,
pub hash_device: *const c_char,
pub salt: *const c_char,
pub salt_size: u32,
pub hash_type: u32,
pub data_block_size: u32,
pub hash_block_size: u32,
pub data_size: u64,
pub hash_area_offset: u64,
pub flags: u32,
}
#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum crypt_verity_flag {
CRYPT_VERITY_NO_HEADER = (1 << 0),
CRYPT_VERITY_CHECK_HASH = (1 << 1),
CRYPT_VERITY_CREATE_HASH = (1 << 2),
}
#[repr(C)]
pub struct crypt_params_tcrypt {
pub passphrase: *const c_char,
pub passphrase_size: size_t,
pub keyfiles: *const *const c_char,
pub keyfiles_count: c_uint,
pub hash_name: *const c_char,
pub cipher: *const c_char,
pub mode: *const c_char,
pub key_size: size_t,
pub flags: u32,
}
#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum crypt_tcrypt_flag {
CRYPT_TCRYPT_LEGACY_MODES = (1 << 0),
CRYPT_TCRYPT_HIDDEN_HEADER = (1 << 1),
CRYPT_TCRYPT_BACKUP_HEADER = (1 << 2),
CRYPT_TCRYPT_SYSTEM_HEADER = (1 << 3),
CRYPT_TCRYPT_VERA_MODES = (1 << 4),
}
pub const CRYPT_ANY_SLOT: c_int = -1;
#[repr(u32)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum crypt_activation_flag {
CRYPT_ACTIVATE_READONLY = (1 << 0),
CRYPT_ACTIVATE_NO_UUID = (1 << 1),
CRYPT_ACTIVATE_SHARED = (1 << 2),
CRYPT_ACTIVATE_ALLOW_DISCARDS = (1 << 3),
CRYPT_ACTIVATE_PRIVATE = (1 << 4),
CRYPT_ACTIVATE_CORRUPTED = (1 << 5),
CRYPT_ACTIVATE_SAME_CPU_CRYPT = (1 << 6),
CRYPT_ACTIVATE_SUBMIT_FROM_CRYPT_CPUS = (1 << 7),
CRYPT_ACTIVATE_IGNORE_CORRUPTION = (1 << 8),
CRYPT_ACTIVATE_RESTART_ON_CORRUPTION = (1 << 9),
CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS = (1 << 10),
}
#[repr(C)]
pub struct crypt_active_device {
pub offset: u64,
pub iv_offset: u64,
pub size: u64,
pub flags: u32,
}
#[repr(C)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum crypt_status_info {
CRYPT_INVALID,
CRYPT_INACTIVE,
CRYPT_ACTIVE,
CRYPT_BUSY,
}
#[repr(C)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum crypt_keyslot_info {
CRYPT_SLOT_INVALID,
CRYPT_SLOT_INACTIVE,
CRYPT_SLOT_ACTIVE,
CRYPT_SLOT_ACTIVE_LAST,
}
#[repr(C)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum crypt_debug_level {
CRYPT_DEBUG_ALL = -1,
CRYPT_DEBUG_NONE = 0,
}
extern "C" {
pub fn crypt_init(cd: *mut *mut crypt_device, device: *const c_char) -> c_int;
pub fn crypt_init_by_name_and_header(
cd: *mut *mut crypt_device,
name: *const c_char,
header_device: *const c_char,
) -> c_int;
pub fn crypt_init_by_name(cd: *mut *mut crypt_device, name: *const c_char) -> c_int;
pub fn crypt_set_log_callback(
cd: *mut crypt_device,
log: Option<crypt_log_cb>,
usrptr: *mut c_void,
);
pub fn crypt_log(cd: *mut crypt_device, level: crypt_log_level, msg: *const c_char);
pub fn crypt_set_confirm_callback(
cd: *mut crypt_device,
confirm: crypt_confirm_cb,
usrptr: *mut c_void,
);
#[deprecated]
pub fn crypt_set_password_callback(
cd: *mut crypt_device,
password: crypt_password_cb,
usrptr: *mut c_void,
);
#[deprecated]
pub fn crypt_set_timeout(cd: *mut crypt_device, timeout: u64);
#[deprecated]
pub fn crypt_set_password_retry(cd: *mut crypt_device, tries: c_int);
pub fn crypt_set_iteration_time(cd: *mut crypt_device, iteration_time_ms: u64);
#[deprecated]
pub fn crypt_set_password_verify(cd: *mut crypt_device, password_verify: c_int);
pub fn crypt_set_data_device(cd: *mut crypt_device, device: *const c_char) -> c_int;
pub fn crypt_set_rng_type(cd: *mut crypt_device, rng_type: crypt_rng_type);
pub fn crypt_get_rng_type(cd: *mut crypt_device) -> c_int;
pub fn crypt_memory_lock(cd: *mut crypt_device, lock: c_int) -> c_int;
pub fn crypt_get_type(cd: *mut crypt_device) -> *const c_char;
pub fn crypt_format(
cd: *mut crypt_device,
crypt_type: *const c_char,
cipher: *const c_char,
cipher_mode: *const c_char,
uuid: *const c_char,
volume_key: *const c_char,
volume_key_size: size_t,
params: *mut c_void,
) -> c_int;
pub fn crypt_set_uuid(cd: *mut crypt_device, uuid: *const c_char) -> c_int;
pub fn crypt_load(
cd: *mut crypt_device,
requested_type: *const c_char,
params: *mut c_void,
) -> c_int;
pub fn crypt_repair(
cd: *mut crypt_device,
requested_type: *const c_char,
params: *mut c_void,
) -> c_int;
pub fn crypt_resize(cd: *mut crypt_device, name: *const c_char, new_size: u64) -> c_int;
pub fn crypt_suspend(cd: *mut crypt_device, name: *const c_char) -> c_int;
pub fn crypt_resume_by_passphrase(
cd: *mut crypt_device,
name: *const c_char,
keyslot: c_int,
passphrase: *const c_char,
passphrase_size: size_t,
) -> c_int;
pub fn crypt_resume_by_keyfile_offset(
cd: *mut crypt_device,
name: *const c_char,
keyslot: c_int,
keyfile: *const c_char,
keyfile_size: size_t,
keyfile_offset: size_t,
) -> c_int;
pub fn crypt_resume_by_keyfile(
cd: *mut crypt_device,
name: *const c_char,
keyslot: c_int,
keyfile: *const c_char,
keyfile_size: size_t,
) -> c_int;
pub fn crypt_free(cd: *mut crypt_device);
pub fn crypt_keyslot_add_by_passphrase(
cd: *mut crypt_device,
keyslot: c_int,
passphrase: *const c_char,
passphrase_size: size_t,
new_passphrase: *const c_char,
new_passphrase_size: size_t,
) -> c_int;
pub fn crypt_keyslot_change_by_passphrase(
cd: *mut crypt_device,
keyslot_old: c_int,
keyslot_new: c_int,
passphrase: *const c_char,
passphrase_size: size_t,
new_passphrase: *const c_char,
new_passphrase_size: size_t,
) -> c_int;
pub fn crypt_keyslot_add_by_keyfile_offset(
cd: *mut crypt_device,
keyslot: c_int,
keyfile: *const c_char,
keyfile_size: size_t,
keyfile_offset: size_t,
new_keyfile: *const c_char,
new_keyfile_size: size_t,
new_keyfile_offset: size_t,
) -> c_int;
pub fn crypt_keyslot_add_by_keyfile(
cd: *mut crypt_device,
keyslot: c_int,
keyfile: *const c_char,
keyfile_size: size_t,
new_keyfile: *const c_char,
new_keyfile_size: size_t,
) -> c_int;
pub fn crypt_keyslot_add_by_volume_key(
cd: *mut crypt_device,
keyslot: c_int,
volume_key: *const c_char,
volume_key_size: size_t,
passphrase: *const c_char,
passphrase_size: size_t,
) -> c_int;
pub fn crypt_keyslot_destroy(cd: *mut crypt_device, keyslot: c_int) -> c_int;
pub fn crypt_get_active_device(
cd: *mut crypt_device,
name: *const c_char,
cad: *mut crypt_active_device,
) -> c_int;
pub fn crypt_activate_by_passphrase(
cd: *mut crypt_device,
name: *const c_char,
keyslot: c_int,
passphrase: *const c_char,
passphrase_size: size_t,
flags: u32,
) -> c_int;
pub fn crypt_activate_by_keyfile_offset(
cd: *mut crypt_device,
name: *const c_char,
keyslot: c_int,
keyfile: *const c_char,
keyfile_size: size_t,
keyfile_offset: size_t,
flags: u32,
) -> c_int;
pub fn crypt_activate_by_keyfile(
cd: *mut crypt_device,
name: *const c_char,
keyslot: c_int,
keyfile: *const c_char,
keyfile_size: size_t,
flags: u32,
) -> c_int;
pub fn crypt_activate_by_volume_key(
cd: *mut crypt_device,
name: *const c_char,
volume_key: *const c_char,
volume_key_size: size_t,
flags: u32,
) -> c_int;
pub fn crypt_deactivate(cd: *mut crypt_device, name: *const c_char) -> c_int;
pub fn crypt_volume_key_get(
cd: *mut crypt_device,
keyslot: c_int,
volume_key: *mut c_char,
volume_key_size: *mut size_t,
passphrase: *const c_char,
passphrase_size: size_t,
) -> c_int;
pub fn crypt_volume_key_verify(
cd: *mut crypt_device,
volume_key: *const c_char,
volume_key_size: size_t,
) -> c_int;
pub fn crypt_status(cd: *mut crypt_device, name: *const c_char) -> crypt_status_info;
pub fn crypt_dump(cd: *mut crypt_device) -> c_int;
pub fn crypt_get_cipher(cd: *mut crypt_device) -> *const c_char;
pub fn crypt_get_cipher_mode(cd: *mut crypt_device) -> *const c_char;
pub fn crypt_get_uuid(cd: *mut crypt_device) -> *const c_char;
pub fn crypt_get_device_name(cd: *mut crypt_device) -> *const c_char;
pub fn crypt_get_data_offset(cd: *mut crypt_device) -> u64;
pub fn crypt_get_iv_offset(cd: *mut crypt_device) -> u64;
pub fn crypt_get_volume_key_size(cd: *mut crypt_device) -> c_int;
pub fn crypt_get_verity_info(cd: *mut crypt_device, vp: *mut crypt_params_verity);
pub fn crypt_benchmark(
cd: *mut crypt_device,
cipher: *const c_char,
cipher_mode: *const c_char,
volume_key_size: size_t,
iv_size: size_t,
buffer_size: size_t,
encryption_mbs: *mut c_double,
decryption_mbs: *mut c_double,
) -> c_int;
pub fn crypt_benchmark_kdf(
cd: *mut crypt_device,
kdf: *const c_char,
hash: *const c_char,
password: *const c_char,
password_size: size_t,
salt: *const c_char,
salt_size: size_t,
iterations_sec: *mut u64,
) -> c_int;
pub fn crypt_keyslot_status(cd: *mut crypt_device, keyslot: c_int) -> crypt_keyslot_info;
pub fn crypt_keyslot_max(crypt_device_type: *const c_char) -> c_int;
pub fn crypt_keyslot_area(
cd: *mut crypt_device,
keyslot: c_int,
offset: *mut u64,
length: *mut u64,
) -> c_int;
pub fn crypt_header_backup(
cd: *mut crypt_device,
requested_type: *const c_char,
backup_file: *const c_char,
) -> c_int;
pub fn crypt_header_restore(
cd: *mut crypt_device,
requested_type: *const c_char,
backup_file: *const c_char,
) -> c_int;
#[deprecated]
pub fn crypt_last_error(cd: *mut crypt_device, buf: *mut c_char, size: size_t);
#[deprecated]
pub fn crypt_get_error(buf: *mut c_char, size: size_t);
pub fn crypt_get_dir() -> *const c_char;
pub fn crypt_set_debug_level(level: crypt_debug_level);
}
impl FromStr for crypt_device_type {
type Err = ();
fn from_str(s: &str) -> Result<crypt_device_type, ()> {
match s {
"PLAIN" => Ok(crypt_device_type::PLAIN),
"LUKS1" => Ok(crypt_device_type::LUKS1),
"LOOPAES" => Ok(crypt_device_type::LOOPAES),
"VERITY" => Ok(crypt_device_type::VERITY),
"TCRYPT" => Ok(crypt_device_type::TCRYPT),
_ => Err(()),
}
}
}
impl crypt_device_type {
pub fn to_str(&self) -> &str {
match self {
&crypt_device_type::PLAIN => "PLAIN",
&crypt_device_type::LUKS1 => "LUKS1",
&crypt_device_type::LOOPAES => "LOOPAES",
&crypt_device_type::VERITY => "VERITY",
&crypt_device_type::TCRYPT => "TCRYPT",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
use std::str::FromStr;
#[test]
fn test_device_type_conversion() {
assert_eq!(
Ok(crypt_device_type::PLAIN),
crypt_device_type::from_str("PLAIN")
);
assert_eq!(
Ok(crypt_device_type::LUKS1),
crypt_device_type::from_str("LUKS1")
);
assert_eq!(
Ok(crypt_device_type::LOOPAES),
crypt_device_type::from_str("LOOPAES")
);
assert_eq!(
Ok(crypt_device_type::VERITY),
crypt_device_type::from_str("VERITY")
);
assert_eq!(
Ok(crypt_device_type::TCRYPT),
crypt_device_type::from_str("TCRYPT")
);
}
#[test]
fn test_keyslot_max_gt_zero() {
unsafe {
let luks_type = CString::new("LUKS1").unwrap();
assert!(crypt_keyslot_max(luks_type.as_ptr()) > 0);
}
}
}

View File

@ -0,0 +1,2 @@
max_width = 120
format_strings = false

View File

@ -0,0 +1,377 @@
//! High-level API to work with `libcryptsetup` supported devices (disks)
use std::fmt;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::ptr;
use blkid_rs::{BlockDevice, LuksHeader};
use device;
pub use device::enable_debug;
use device::RawDevice;
pub use device::{Error, Keyslot, Result};
use raw;
use uuid;
pub type Luks1CryptDeviceHandle = CryptDeviceHandle<Luks1Params>;
/// Builder to open a crypt device at the specified path
///
/// # Examples
///
/// ```
/// use cryptsetup_rs::*;
/// # fn foo() -> Result<()> {
/// let device = open("/dev/loop0")?.luks1()?;
/// # Ok(())
/// # }
/// ```
pub fn open<P: AsRef<Path>>(path: P) -> Result<CryptDeviceOpenBuilder> {
let cd = device::init(path.as_ref())?;
Ok(CryptDeviceOpenBuilder {
path: path.as_ref().to_owned(),
cd,
})
}
/// Builder to format a crypt device at the specified path
///
/// # Examples
///
/// ```
/// # extern crate uuid;
/// # extern crate cryptsetup_rs;
/// use cryptsetup_rs::*;
/// use uuid::Uuid;
///
/// # fn foo() -> Result<()> {
/// let uuid = Uuid::new_v4();
/// let device = format("/dev/loop0")?
/// .rng_type(crypt_rng_type::CRYPT_RNG_URANDOM)
/// .iteration_time(5000)
/// .luks1("aes", "xts-plain", "sha256", 256, Some(&uuid))?;
/// # Ok(())
/// # }
/// ```
pub fn format<P: AsRef<Path>>(path: P) -> Result<CryptDeviceFormatBuilder> {
let cd = device::init(path.as_ref())?;
Ok(CryptDeviceFormatBuilder {
path: path.as_ref().to_owned(),
cd,
})
}
/// Read the UUID of a LUKS1 container without opening the device
pub fn luks1_uuid<P: AsRef<Path>>(path: P) -> Result<uuid::Uuid> {
let device_file = File::open(path.as_ref())?;
let luks_phdr = BlockDevice::read_luks_header(device_file)?;
let uuid = luks_phdr.uuid()?;
Ok(uuid)
}
fn load_luks1_params<P: AsRef<Path>>(path: P) -> Result<Luks1Params> {
let device_file = File::open(path.as_ref())?;
let luks_phdr = BlockDevice::read_luks_header(device_file)?;
Luks1Params::from(luks_phdr)
}
/// Struct containing state for the `open()` builder
pub struct CryptDeviceOpenBuilder {
path: PathBuf,
cd: RawDevice,
}
impl CryptDeviceOpenBuilder {
/// Loads an existing LUKS1 crypt device
pub fn luks1(self: CryptDeviceOpenBuilder) -> Result<CryptDeviceHandle<Luks1Params>> {
let _ = device::load(&self.cd, raw::crypt_device_type::LUKS1);
let params = load_luks1_params(&self.path)?;
Ok(CryptDeviceHandle {
cd: self.cd,
path: self.path,
params,
})
}
}
/// Struct containing state for the `format()` builder
pub struct CryptDeviceFormatBuilder {
path: PathBuf,
cd: RawDevice,
}
impl CryptDeviceFormatBuilder {
/// Set the iteration time for the `PBKDF2` function. Note that this does not affect the MK iterations.
pub fn iteration_time(mut self, iteration_time_ms: u64) -> Self {
device::set_iteration_time(&mut self.cd, iteration_time_ms);
self
}
/// Set the random number generator to use
pub fn rng_type(mut self, rng_type: raw::crypt_rng_type) -> Self {
device::set_rng_type(&mut self.cd, rng_type);
self
}
/// Formats a new block device as a LUKS1 crypt device with the specified parameters
pub fn luks1(
mut self: CryptDeviceFormatBuilder,
cipher: &str,
cipher_mode: &str,
hash: &str,
mk_bits: usize,
maybe_uuid: Option<&uuid::Uuid>,
) -> Result<CryptDeviceHandle<Luks1Params>> {
let _ = device::luks1_format(&mut self.cd, cipher, cipher_mode, hash, mk_bits, maybe_uuid)?;
let params = load_luks1_params(&self.path)?;
Ok(CryptDeviceHandle {
cd: self.cd,
path: self.path,
params,
})
}
}
/// Trait representing common operations on a crypt device
pub trait CryptDevice {
/// Path the device was opened/created with
fn path(&self) -> &Path;
/// Name of cipher used
fn cipher(&self) -> &str;
/// Name of cipher mode used
fn cipher_mode(&self) -> &str;
/// Path to the underlying device (as reported by `libcryptsetup`)
fn device_name(&self) -> &str;
/// Random number generator used for operations on this crypt device
fn rng_type(&self) -> raw::crypt_rng_type;
/// Sets the random number generator to use
fn set_rng_type(&mut self, rng_type: raw::crypt_rng_type);
/// Sets the iteration time for the `PBKDF2` function. Note that this does not affect the MK iterations.
fn set_iteration_time(&mut self, iteration_time_ms: u64);
/// Volume key size (in bytes)
fn volume_key_size(&self) -> u8;
}
/// Trait for querying the device type at runtime
pub trait CryptDeviceType {
/// Type of the crypt device
fn device_type(&self) -> raw::crypt_device_type;
}
/// Trait representing specific operations on a LUKS1 device
pub trait Luks1CryptDevice {
/// Activate the crypt device, and give it the specified name
fn activate(&mut self, name: &str, key: &[u8]) -> Result<Keyslot>;
/// Add a new keyslot with the specified key
fn add_keyslot(
&mut self,
key: &[u8],
maybe_prev_key: Option<&[u8]>,
maybe_keyslot: Option<Keyslot>,
) -> Result<Keyslot>;
/// Replace an old key with a new one
fn update_keyslot(&mut self, key: &[u8], prev_key: &[u8], maybe_keyslot: Option<Keyslot>) -> Result<Keyslot>;
/// Destroy (and disable) key slot
fn destroy_keyslot(&mut self, slot: Keyslot) -> Result<()>;
/// Dump text-formatted information about the current device to stdout
fn dump(&self);
/// Get the hash algorithm used
fn hash_spec(&self) -> &str;
/// Get status of key slot
fn keyslot_status(&self, keyslot: Keyslot) -> raw::crypt_keyslot_info;
/// Number of bits in the master key
fn mk_bits(&self) -> u32;
/// Master key header digest
fn mk_digest(&self) -> &[u8; 20];
/// Master key `PBKDF2` iterations
fn mk_iterations(&self) -> u32;
/// Master key salt
fn mk_salt(&self) -> &[u8; 32];
/// Get the offset of the payload
fn payload_offset(&self) -> u32;
/// UUID of the current device
fn uuid(&self) -> uuid::Uuid;
}
/// An opaque handle on an initialized crypt device
#[derive(PartialEq)]
pub struct CryptDeviceHandle<P: fmt::Debug> {
/// Pointer to the raw device
cd: RawDevice,
/// Path to the crypt device (useful for diagnostics)
path: PathBuf,
/// Additional parameters depending on type of crypt device opened
params: P,
}
impl<P: fmt::Debug> fmt::Debug for CryptDeviceHandle<P> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"CryptDeviceHandle(path={}, raw={:p}, params={:?})",
self.path.display(),
self.cd,
self.params
)
}
}
impl<P: fmt::Debug> Drop for CryptDeviceHandle<P> {
fn drop(&mut self) {
device::free(&mut self.cd);
self.cd = ptr::null_mut();
}
}
impl<P: fmt::Debug> CryptDevice for CryptDeviceHandle<P> {
fn path(&self) -> &Path {
self.path.as_ref()
}
fn cipher(&self) -> &str {
device::cipher(&self.cd).expect("Initialised device should have cipher")
}
fn cipher_mode(&self) -> &str {
device::cipher_mode(&self.cd).expect("Initialised device should have cipher mode")
}
fn device_name(&self) -> &str {
device::device_name(&self.cd).expect("Initialised device should have an underlying path")
}
fn rng_type(&self) -> raw::crypt_rng_type {
device::rng_type(&self.cd)
}
fn set_rng_type(&mut self, rng_type: raw::crypt_rng_type) {
device::set_rng_type(&mut self.cd, rng_type)
}
fn set_iteration_time(&mut self, iteration_time_ms: u64) {
device::set_iteration_time(&mut self.cd, iteration_time_ms)
}
fn volume_key_size(&self) -> u8 {
device::volume_key_size(&self.cd)
}
}
/// Struct for storing LUKS1 parameters in memory
#[derive(Debug, PartialEq)]
pub struct Luks1Params {
hash_spec: String,
payload_offset: u32,
mk_bits: u32,
mk_digest: [u8; 20],
mk_salt: [u8; 32],
mk_iterations: u32,
}
impl Luks1Params {
fn from(header: impl LuksHeader) -> Result<Luks1Params> {
let hash_spec = header.hash_spec()?.to_owned();
let payload_offset = header.payload_offset();
let mk_bits = header.key_bytes() * 8;
let mut mk_digest = [0u8; 20];
mk_digest.copy_from_slice(header.mk_digest());
let mut mk_salt = [0u8; 32];
mk_salt.copy_from_slice(header.mk_digest_salt());
let mk_iterations = header.mk_digest_iterations();
Ok(Luks1Params {
hash_spec,
payload_offset,
mk_bits,
mk_digest,
mk_salt,
mk_iterations,
})
}
}
impl Luks1CryptDevice for CryptDeviceHandle<Luks1Params> {
fn activate(&mut self, name: &str, key: &[u8]) -> Result<Keyslot> {
device::luks_activate(&mut self.cd, name, key)
}
fn add_keyslot(
&mut self,
key: &[u8],
maybe_prev_key: Option<&[u8]>,
maybe_keyslot: Option<Keyslot>,
) -> Result<Keyslot> {
device::luks_add_keyslot(&mut self.cd, key, maybe_prev_key, maybe_keyslot)
}
fn update_keyslot(&mut self, key: &[u8], prev_key: &[u8], maybe_keyslot: Option<Keyslot>) -> Result<Keyslot> {
device::luks_update_keyslot(&mut self.cd, key, prev_key, maybe_keyslot)
}
fn destroy_keyslot(&mut self, slot: Keyslot) -> Result<()> {
device::luks_destroy_keyslot(&mut self.cd, slot)
}
fn dump(&self) {
device::dump(&self.cd).expect("Dump should be fine for initialised device")
}
fn hash_spec(&self) -> &str {
self.params.hash_spec.as_ref()
}
fn keyslot_status(&self, keyslot: Keyslot) -> raw::crypt_keyslot_info {
device::keyslot_status(&self.cd, keyslot)
}
fn mk_bits(&self) -> u32 {
self.params.mk_bits
}
fn mk_digest(&self) -> &[u8; 20] {
&self.params.mk_digest
}
fn mk_iterations(&self) -> u32 {
self.params.mk_iterations
}
fn mk_salt(&self) -> &[u8; 32] {
&self.params.mk_salt
}
fn payload_offset(&self) -> u32 {
self.params.payload_offset
}
fn uuid(&self) -> uuid::Uuid {
device::uuid(&self.cd).expect("LUKS1 device should have UUID")
}
}
impl CryptDeviceType for CryptDeviceHandle<Luks1Params> {
fn device_type(&self) -> raw::crypt_device_type {
raw::crypt_device_type::LUKS1
}
}

View File

@ -0,0 +1,319 @@
//! Low-level cryptsetup binding that sits directly on top of the `libcryptsetup` C API
//!
//! Consider using the high-level binding in the `api` module instead
use std::ffi;
use std::mem;
use std::path::Path;
use std::ptr;
use std::result;
use std::str;
use blkid_rs;
use errno;
use libc;
use raw;
use uuid;
/// Raw pointer to the underlying `crypt_device` opaque struct
pub type RawDevice = *mut raw::crypt_device;
#[derive(Debug)]
pub enum Error {
/// Error that originates from `libcryptsetup` (with numeric error code)
CryptsetupError(errno::Errno),
/// IO error
IOError(::std::io::Error),
/// Error from the blkid-rs library (while reading LUKS1 header)
BlkidError(blkid_rs::Error),
}
impl From<::std::io::Error> for Error {
fn from(e: ::std::io::Error) -> Self {
Error::IOError(e)
}
}
impl From<blkid_rs::Error> for Error {
fn from(e: blkid_rs::Error) -> Self {
Error::BlkidError(e)
}
}
pub type Result<T> = result::Result<T, Error>;
pub type Keyslot = u8;
const ANY_KEYSLOT: libc::c_int = -1 as libc::c_int;
fn str_from_c_str<'a>(c_str: *const libc::c_char) -> Option<&'a str> {
if c_str.is_null() {
None
} else {
unsafe { Some(ffi::CStr::from_ptr(c_str).to_str().unwrap()) }
}
}
macro_rules! crypt_error {
($res:expr) => {
Err(Error::CryptsetupError(errno::Errno(-$res)))
};
}
macro_rules! check_crypt_error {
($res:expr) => {
if $res != 0 {
crypt_error!($res)
} else {
Ok(())
}
};
}
/// Log function callback used by `libcryptsetup`
#[allow(unused)]
#[no_mangle]
pub extern "C" fn cryptsetup_rs_log_callback(
level: raw::crypt_log_level,
message: *const libc::c_char,
usrptr: *mut libc::c_void,
) {
let msg = str_from_c_str(message).unwrap();
match level {
raw::crypt_log_level::CRYPT_LOG_NORMAL => info!("{}", msg.trim_right()),
raw::crypt_log_level::CRYPT_LOG_ERROR => error!("{}", msg.trim_right()),
raw::crypt_log_level::CRYPT_LOG_VERBOSE => debug!("{}", msg.trim_right()),
raw::crypt_log_level::CRYPT_LOG_DEBUG => debug!("{}", msg.trim_right()),
}
}
/// Enable internal `libcryptsetup` debugging
pub fn enable_debug(debug: bool) {
if debug {
unsafe { raw::crypt_set_debug_level(raw::crypt_debug_level::CRYPT_DEBUG_ALL) };
} else {
unsafe { raw::crypt_set_debug_level(raw::crypt_debug_level::CRYPT_DEBUG_NONE) };
}
}
/// Initialise crypt device and check if provided device exists
pub fn init<P: AsRef<Path>>(path: P) -> Result<RawDevice> {
let mut cd = ptr::null_mut();
let c_path = ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap();
let res = unsafe { raw::crypt_init(&mut cd as *mut *mut raw::crypt_device, c_path.as_ptr()) };
if res != 0 {
crypt_error!(res)
} else {
unsafe {
raw::crypt_set_log_callback(cd, Some(cryptsetup_rs_log_callback), ptr::null_mut());
}
Ok(cd)
}
}
/// Load crypt device parameters from the on-disk header
///
/// Note that typically you cannot query the crypt device for information before this function is
/// called.
pub fn load(cd: &RawDevice, requested_type: raw::crypt_device_type) -> Result<()> {
let c_type = ffi::CString::new(requested_type.to_str()).unwrap();
let res = unsafe { raw::crypt_load(*cd, c_type.as_ptr(), ptr::null_mut()) };
check_crypt_error!(res)
}
/// Get the cipher used by this crypt device
pub fn cipher<'a>(cd: &'a RawDevice) -> Option<&'a str> {
let c_cipher = unsafe { raw::crypt_get_cipher(*cd) };
str_from_c_str(c_cipher)
}
/// Get the cipher mode used by this crypt device
pub fn cipher_mode<'a>(cd: &'a RawDevice) -> Option<&'a str> {
let c_cipher_mode = unsafe { raw::crypt_get_cipher_mode(*cd) };
str_from_c_str(c_cipher_mode)
}
/// Get the path to the device (as `libcryptsetup` sees it)
pub fn device_name<'a>(cd: &'a RawDevice) -> Option<&'a str> {
let c_device_name = unsafe { raw::crypt_get_device_name(*cd) };
str_from_c_str(c_device_name)
}
/// Dump text-formatted information about this device to the console
pub fn dump(cd: &RawDevice) -> Result<()> {
let res = unsafe { raw::crypt_dump(*cd) };
check_crypt_error!(res)
}
/// Releases crypt device context and memory
pub fn free(cd: &mut RawDevice) {
unsafe { raw::crypt_free(*cd) }
}
/// Activate device based on provided key ("passphrase")
pub fn luks_activate(cd: &mut RawDevice, name: &str, key: &[u8]) -> Result<Keyslot> {
let c_name = ffi::CString::new(name).unwrap();
let c_passphrase_len = key.len() as libc::size_t;
// cast the passphrase to a pointer directly - it will not be NUL terminated but the passed length is used
let c_passphrase = key as *const [u8] as *const libc::c_char;
let res = unsafe {
raw::crypt_activate_by_passphrase(*cd, c_name.as_ptr(), ANY_KEYSLOT, c_passphrase, c_passphrase_len, 0u32)
};
if res < 0 {
crypt_error!(res)
} else {
Ok(res as u8)
}
}
/// Add key slot using provided passphrase. If there is no previous passphrase, use the volume key
/// that is in-memory to add the new key slot.
pub fn luks_add_keyslot(
cd: &mut RawDevice,
key: &[u8],
maybe_prev_key: Option<&[u8]>,
maybe_keyslot: Option<Keyslot>,
) -> Result<Keyslot> {
let c_key_len = key.len() as libc::size_t;
let c_key = key as *const [u8] as *const libc::c_char;;
let c_keyslot = maybe_keyslot
.map(|k| k as libc::c_int)
.unwrap_or(ANY_KEYSLOT as libc::c_int);
let res = if let Some(prev_key) = maybe_prev_key {
let c_prev_key_len = prev_key.len() as libc::size_t;
let c_prev_key = prev_key as *const [u8] as *const libc::c_char;;
unsafe { raw::crypt_keyslot_add_by_passphrase(*cd, c_keyslot, c_prev_key, c_prev_key_len, c_key, c_key_len) }
} else {
unsafe {
raw::crypt_keyslot_add_by_volume_key(*cd, c_keyslot, ptr::null(), 0 as libc::size_t, c_key, c_key_len)
}
};
if res < 0 {
crypt_error!(res)
} else {
Ok(res as Keyslot)
}
}
/// Add key slot using provided passphrase. If there is no previous passphrase, use the volume key
/// that is in-memory to add the new key slot.
pub fn luks_update_keyslot(
cd: &mut RawDevice,
key: &[u8],
prev_key: &[u8],
maybe_keyslot: Option<Keyslot>,
) -> Result<Keyslot> {
let c_key_len = key.len() as libc::size_t;
let c_key = key as *const [u8] as *const libc::c_char;;
let c_keyslot = maybe_keyslot
.map(|k| k as libc::c_int)
.unwrap_or(ANY_KEYSLOT as libc::c_int);
let c_prev_key_len = prev_key.len() as libc::size_t;
let c_prev_key = prev_key as *const [u8] as *const libc::c_char;;
let res = unsafe {
raw::crypt_keyslot_change_by_passphrase(*cd, c_keyslot, c_keyslot, c_prev_key, c_prev_key_len, c_key, c_key_len)
};
if res < 0 {
crypt_error!(res)
} else {
Ok(res as Keyslot)
}
}
/// Destroy (and disable) key slot
pub fn luks_destroy_keyslot(cd: &mut RawDevice, keyslot: Keyslot) -> Result<()> {
let res = unsafe { raw::crypt_keyslot_destroy(*cd, keyslot as libc::c_int) };
if res < 0 {
crypt_error!(res)
} else {
Ok(())
}
}
/// Format a new crypt device but do not activate it
///
/// Note this does not add an active keyslot
pub fn luks1_format(
cd: &mut RawDevice,
cipher: &str,
cipher_mode: &str,
hash: &str,
mk_bits: usize,
maybe_uuid: Option<&uuid::Uuid>,
) -> Result<()> {
let c_cipher = ffi::CString::new(cipher).unwrap();
let c_cipher_mode = ffi::CString::new(cipher_mode).unwrap();
let c_hash = ffi::CString::new(hash).unwrap();
let c_uuid = maybe_uuid.map(|uuid| ffi::CString::new(uuid.hyphenated().to_string()).unwrap());
let mut luks_params = raw::crypt_params_luks1 {
hash: c_hash.as_ptr(),
data_alignment: 0,
data_device: ptr::null(),
};
let c_luks_params: *mut raw::crypt_params_luks1 = &mut luks_params;
let c_luks_type = ffi::CString::new(raw::crypt_device_type::LUKS1.to_str()).unwrap();
let c_uuid_ptr = c_uuid.as_ref().map(|u| u.as_ptr()).unwrap_or(ptr::null());
let res = unsafe {
raw::crypt_format(
*cd,
c_luks_type.as_ptr(),
c_cipher.as_ptr(),
c_cipher_mode.as_ptr(),
c_uuid_ptr,
ptr::null(),
mk_bits / 8,
c_luks_params as *mut libc::c_void,
)
};
check_crypt_error!(res)
}
/// Get which RNG is used
pub fn rng_type(cd: &RawDevice) -> raw::crypt_rng_type {
unsafe {
let res = raw::crypt_get_rng_type(*cd);
mem::transmute(res)
}
}
/// Set the number of milliseconds for `PBKDF2` function iteration
pub fn set_iteration_time(cd: &mut RawDevice, iteration_time_ms: u64) {
unsafe {
raw::crypt_set_iteration_time(*cd, iteration_time_ms);
}
}
/// Set which RNG is used
pub fn set_rng_type(cd: &mut RawDevice, rng_type: raw::crypt_rng_type) {
unsafe { raw::crypt_set_rng_type(*cd, rng_type) }
}
/// Get information about a keyslot
pub fn keyslot_status(cd: &RawDevice, slot: Keyslot) -> raw::crypt_keyslot_info {
unsafe { raw::crypt_keyslot_status(*cd, slot as libc::c_int) }
}
/// Get size in bytes of the volume key
pub fn volume_key_size(cd: &RawDevice) -> u8 {
let res = unsafe { raw::crypt_get_volume_key_size(*cd) };
res as u8
}
/// Get device UUID
pub fn uuid<'a>(cd: &'a RawDevice) -> Option<uuid::Uuid> {
let c_uuid_str = unsafe { raw::crypt_get_uuid(*cd) };
str_from_c_str(c_uuid_str).and_then(|uuid_str| uuid::Uuid::parse_str(uuid_str).ok())
}

View File

@ -0,0 +1,32 @@
//! Rust bindings to `libcryptsetup` - working with encrypted disks on Linux
//!
//! # Example
//!
//! See `api` module documentation for more.
//!
//! ```
//! use cryptsetup_rs::*;
//! # fn foo() -> Result<()> {
//! let device = open("/dev/loop0")?.luks1()?;
//! println!("Device UUID: {}", device.uuid());
//! println!("Device cipher: {}", device.cipher());
//! # Ok(())
//! # }
//! ```
#[warn(unused_must_use)]
extern crate blkid_rs;
extern crate errno;
extern crate libc;
extern crate libcryptsetup_sys as raw;
extern crate uuid;
#[macro_use]
extern crate log;
pub mod api;
pub mod device;
pub use api::{enable_debug, format, luks1_uuid, open};
pub use api::{CryptDevice, CryptDeviceType, Error, Keyslot, Luks1CryptDevice, Luks1CryptDeviceHandle, Result};
pub use raw::{crypt_device_type, crypt_keyslot_info, crypt_rng_type};

View File

@ -0,0 +1,72 @@
#![deny(warnings)]
extern crate cryptsetup_rs;
extern crate env_logger;
extern crate log;
extern crate tempdir;
extern crate uuid;
#[macro_use]
extern crate expectest;
use std::process::Command;
use expectest::prelude::*;
use tempdir::TempDir;
use uuid::Uuid;
use cryptsetup_rs::*;
struct TestContext {
dir: TempDir,
name: String,
}
impl TestContext {
fn new(name: String) -> TestContext {
env_logger::init();
cryptsetup_rs::enable_debug(true);
let dir = tempdir::TempDir::new(&name).unwrap();
TestContext { name, dir }
}
fn new_crypt_device(&self) -> api::CryptDeviceFormatBuilder {
let crypt_file = self.dir.path().join(format!("{}.image", self.name));
let dd_status = Command::new("dd")
.arg("if=/dev/zero")
.arg(format!("of={}", crypt_file.display()))
.arg("bs=1M")
.arg("count=10")
.status()
.unwrap();
if !dd_status.success() {
panic!("Failed to create disk image at {}", crypt_file.display());
}
cryptsetup_rs::format(crypt_file).unwrap()
}
}
#[test]
fn test_create_new_luks1_cryptdevice_no_errors() {
let ctx = TestContext::new("new_luks1_cryptdevice".to_string());
let uuid = Uuid::new_v4();
let device_format = ctx.new_crypt_device()
.rng_type(crypt_rng_type::CRYPT_RNG_URANDOM)
.iteration_time(42);
let mut dev = device_format
.luks1("aes", "xts-plain", "sha256", 256, Some(&uuid))
.expect("LUKS format should succeed");
dev.dump();
expect!(dev.uuid()).to(be_equal_to(uuid));
expect!(dev.device_type()).to(be_equal_to(crypt_device_type::LUKS1));
expect!(dev.cipher()).to(be_equal_to("aes"));
expect!(dev.cipher_mode()).to(be_equal_to("xts-plain"));
expect!(dev.volume_key_size()).to(be_equal_to(32));
expect!(dev.add_keyslot(b"hello world", None, Some(3))).to(be_ok().value(3));
}

4
patch/ctap/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
**/*.rs.bk
Cargo.lock
src/bin

21
patch/ctap/Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "ctap"
description = "A Rust implementation of the FIDO2 CTAP protocol"
version = "0.1.0"
license = "Apache-2.0/MIT"
homepage = "https://github.com/ArdaXi/ctap"
repository = "https://github.com/ArdaXi/ctap"
authors = ["Arda Xi <arda@ardaxi.com>"]
edition = "2018"
[dependencies]
rand = "0.6"
failure = "0.1"
failure_derive = "0.1"
num-traits = "0.2"
num-derive = "0.2"
byteorder = "1"
cbor-codec = "0.7"
ring = "0.13"
untrusted = "0.6"
rust-crypto = "0.2"

59
patch/ctap/README.md Normal file
View File

@ -0,0 +1,59 @@
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.
# ctap
ctap is a library implementing the [FIDO2 CTAP](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html) protocol.
## Usage example
```rust
let devices = ctap::get_devices()?;
let device_info = &devices[0];
let mut device = ctap::FidoDevice::new(device_info)?;
// This can be omitted if the FIDO device is not configured with a PIN.
let pin = "test";
device.unlock(pin)?;
// In a real application these values would come from the requesting app.
let rp_id = "rp_id";
let user_id = [0];
let user_name = "user_name";
let client_data_hash = [0; 32];
let cred = device.make_credential(
rp_id,
&user_id,
user_name,
&client_data_hash
)?;
// In a real application the credential would be stored and used later.
let result = device.get_assertion(&cred, &client_data_hash);
```
## Limitations
Currently, this library only supports Linux. Testing and contributions for
other platforms is welcome.
## License
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,19 @@
Copyright (c) Ariën Holthuizen <contact@ardaxi.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.

715
patch/ctap/src/cbor.rs Normal file
View File

@ -0,0 +1,715 @@
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use cbor_codec::value;
use cbor_codec::value::Value;
use cbor_codec::{Config, Decoder, Encoder, GenericDecoder, GenericEncoder};
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
use failure::ResultExt;
use std::collections::HashMap;
use std::io::Cursor;
use super::error::*;
pub enum Request<'a> {
MakeCredential(MakeCredentialRequest<'a>),
GetAssertion(GetAssertionRequest<'a>),
GetInfo,
ClientPin(ClientPinRequest<'a>),
}
impl<'a> Request<'a> {
pub fn encode<W: WriteBytesExt>(&self, writer: &mut W) -> FidoResult<()> {
let mut encoder = Encoder::new(writer);
match self {
Request::MakeCredential(req) => req.encode(&mut encoder),
Request::GetAssertion(req) => req.encode(&mut encoder),
Request::GetInfo => encoder
.writer()
.write_u8(0x04)
.context(FidoErrorKind::CborEncode)
.map_err(From::from),
Request::ClientPin(req) => req.encode(&mut encoder),
}
}
pub fn decode<R: ReadBytesExt>(&self, reader: R) -> FidoResult<Response> {
Ok(match self {
Request::MakeCredential(_) => {
Response::MakeCredential(MakeCredentialResponse::decode(reader)?)
}
Request::GetAssertion(_) => {
Response::GetAssertion(GetAssertionResponse::decode(reader)?)
}
Request::GetInfo => Response::GetInfo(GetInfoResponse::decode(reader)?),
Request::ClientPin(_) => Response::ClientPin(ClientPinResponse::decode(reader)?),
})
}
}
#[derive(Debug)]
pub enum Response {
MakeCredential(MakeCredentialResponse),
GetAssertion(GetAssertionResponse),
GetInfo(GetInfoResponse),
ClientPin(ClientPinResponse),
}
#[derive(Default, Debug)]
pub struct MakeCredentialRequest<'a> {
pub client_data_hash: &'a [u8],
pub rp: PublicKeyCredentialRpEntity<'a>,
pub user: PublicKeyCredentialUserEntity<'a>,
pub pub_key_cred_params: &'a [(&'a str, i32)],
pub exclude_list: &'a [PublicKeyCredentialDescriptor],
pub extensions: &'a [(&'a str, &'a Value)],
pub options: Option<AuthenticatorOptions>,
pub pin_auth: Option<[u8; 16]>,
pub pin_protocol: Option<u8>,
}
impl<'a> MakeCredentialRequest<'a> {
pub fn encode<W: WriteBytesExt>(&self, mut encoder: &mut Encoder<W>) -> FidoResult<()> {
encoder
.writer()
.write_u8(0x01)
.context(FidoErrorKind::CborEncode)?; // authenticatorMakeCredential
let mut length = 4;
length += !self.exclude_list.is_empty() as usize;
length += !self.extensions.is_empty() as usize;
length += self.options.is_some() as usize;
length += self.pin_auth.is_some() as usize;
length += self.pin_protocol.is_some() as usize;
encoder.object(length)?;
encoder.u8(0x01)?; // clientDataHash
encoder.bytes(&self.client_data_hash)?;
encoder.u8(0x02)?; // rp
self.rp.encode(&mut encoder)?;
encoder.u8(0x03)?; // user
self.user.encode(&mut encoder)?;
encoder.u8(0x04)?; // pubKeyCredParams
encoder.array(self.pub_key_cred_params.len())?;
for (cred_type, alg) in self.pub_key_cred_params {
encoder.object(2)?;
encoder.text("alg")?;
encoder.i32(*alg)?;
encoder.text("type")?;
encoder.text(&cred_type)?;
}
if self.exclude_list.len() > 0 {
encoder.u8(0x05)?; // excludeList
encoder.array(self.exclude_list.len())?;
for item in self.exclude_list {
item.encode(&mut encoder)?;
}
}
if self.extensions.len() > 0 {
encoder.u8(0x06)?; // extensions
encoder.object(self.extensions.len())?;
for (key, value) in self.extensions {
encoder.text(key)?;
let mut generic = GenericEncoder::new(encoder.writer());
generic.value(value)?;
}
}
if let Some(options) = &self.options {
if options.encoded() {
encoder.u8(0x07)?; // options
options.encode(&mut encoder)?;
}
}
if let Some(pin_auth) = &self.pin_auth {
encoder.u8(0x08)?; // pinAuth
encoder.bytes(pin_auth)?;
}
if let Some(pin_protocol) = &self.pin_protocol {
encoder.u8(0x09)?; // pinProtocol
encoder.u8(*pin_protocol)?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct MakeCredentialResponse {
pub format: String,
pub auth_data: AuthenticatorData,
}
impl MakeCredentialResponse {
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
if status != 0 {
Err(FidoErrorKind::CborError(status))?
}
let mut decoder = Decoder::new(Config::default(), reader);
let mut response = MakeCredentialResponse::default();
for _ in 0..decoder.object()? {
let key = decoder.u8()?;
match key {
0x01 => response.format = decoder.text()?,
0x02 => response.auth_data = AuthenticatorData::from_bytes(&decoder.bytes()?)?,
0x03 => break, // TODO: parse attestation
_ => continue,
}
}
Ok(response)
}
}
#[derive(Debug, Default)]
pub struct GetAssertionRequest<'a> {
pub rp_id: &'a str,
pub client_data_hash: &'a [u8],
pub allow_list: &'a [PublicKeyCredentialDescriptor],
pub extensions: &'a [(&'a str, &'a Value)],
pub options: Option<AuthenticatorOptions>,
pub pin_auth: Option<[u8; 16]>,
pub pin_protocol: Option<u8>,
}
impl<'a> GetAssertionRequest<'a> {
pub fn encode<W: WriteBytesExt>(&self, mut encoder: &mut Encoder<W>) -> FidoResult<()> {
encoder
.writer()
.write_u8(0x02)
.context(FidoErrorKind::CborEncode)?; // authenticatorGetAssertion
let mut length = 2;
length += !self.allow_list.is_empty() as usize;
length += !self.extensions.is_empty() as usize;
length += self.options.is_some() as usize;
length += self.pin_auth.is_some() as usize;
length += self.pin_protocol.is_some() as usize;
encoder.object(length)?;
encoder.u8(0x01)?; // rpId
encoder.text(&self.rp_id)?;
encoder.u8(0x02)?; // clientDataHash
encoder.bytes(self.client_data_hash)?;
if !self.allow_list.is_empty() {
encoder.u8(0x03)?; // allowList
encoder.array(self.allow_list.len())?;
for item in self.allow_list {
item.encode(&mut encoder)?;
}
}
if self.extensions.len() > 0 {
encoder.u8(0x04)?; // extensions
encoder.object(self.extensions.len())?;
for (key, value) in self.extensions {
encoder.text(key)?;
let mut generic = GenericEncoder::new(encoder.writer());
generic.value(value)?;
}
}
if let Some(options) = &self.options {
if options.encoded() {
encoder.u8(0x05)?; // options
options.encode(&mut encoder)?;
}
}
if let Some(pin_auth) = &self.pin_auth {
encoder.u8(0x06)?; // pinAuth
encoder.bytes(pin_auth)?;
}
if let Some(pin_protocol) = &self.pin_protocol {
encoder.u8(0x07)?; // pinProtocol
encoder.u8(*pin_protocol)?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct GetAssertionResponse {
pub credential: Option<PublicKeyCredentialDescriptor>,
pub auth_data_bytes: Vec<u8>,
pub auth_data: AuthenticatorData,
pub signature: Vec<u8>,
}
impl GetAssertionResponse {
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
if status != 0 {
Err(FidoErrorKind::CborError(status))?
}
let mut decoder = Decoder::new(Config::default(), reader);
let mut response = GetAssertionResponse::default();
for _ in 0..decoder.object()? {
let key = decoder.u8()?;
match key {
0x01 => {
response.credential = Some(PublicKeyCredentialDescriptor::decode(&mut decoder)?)
}
0x02 => {
response.auth_data_bytes = decoder.bytes()?;
response.auth_data = AuthenticatorData::from_bytes(&response.auth_data_bytes)?;
}
0x03 => response.signature = decoder.bytes()?,
_ => continue,
}
}
Ok(response)
}
}
#[derive(Debug, Default)]
pub struct GetInfoResponse {
pub versions: Vec<String>,
pub extensions: Vec<String>,
pub aaguid: [u8; 16],
pub options: OptionsInfo,
pub max_msg_size: u16,
pub pin_protocols: Vec<u8>,
}
impl GetInfoResponse {
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
if status != 0 {
Err(FidoErrorKind::CborError(status))?
}
let mut decoder = Decoder::new(Config::default(), reader);
let mut response = GetInfoResponse::default();
for _ in 0..decoder.object()? {
match decoder.u8()? {
0x01 => {
for _ in 0..decoder.array()? {
response.versions.push(decoder.text()?);
}
}
0x02 => {
for _ in 0..decoder.array()? {
response.extensions.push(decoder.text()?);
}
}
0x03 => response.aaguid.copy_from_slice(&decoder.bytes()?[..]),
0x04 => response.options = OptionsInfo::decode(&mut decoder)?,
0x05 => response.max_msg_size = decoder.u16()?,
0x06 => {
for _ in 0..decoder.array()? {
response.pin_protocols.push(decoder.u8()?);
}
}
_ => continue,
}
}
Ok(response)
}
}
#[derive(Debug, Default)]
pub struct ClientPinRequest<'a> {
pub pin_protocol: u8,
pub sub_command: u8,
pub key_agreement: Option<&'a CoseKey>,
pub pin_auth: Option<[u8; 16]>,
pub new_pin_enc: Option<Vec<u8>>,
pub pin_hash_enc: Option<[u8; 16]>,
}
impl<'a> ClientPinRequest<'a> {
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
encoder
.writer()
.write_u8(0x06)
.context(FidoErrorKind::CborEncode)?; // authenticatorClientPIN
let mut length = 2;
length += self.key_agreement.is_some() as usize;
length += self.pin_auth.is_some() as usize;
length += self.new_pin_enc.is_some() as usize;
length += self.pin_hash_enc.is_some() as usize;
encoder.object(length)?;
encoder.u8(0x01)?; // pinProtocol
encoder.u8(self.pin_protocol)?;
encoder.u8(0x02)?; // subCommand
encoder.u8(self.sub_command)?;
if let Some(key_agreement) = self.key_agreement {
encoder.u8(0x03)?; // keyAgreement
key_agreement.encode(encoder)?;
}
if let Some(pin_auth) = &self.pin_auth {
encoder.u8(0x04)?; // pinAuth
encoder.bytes(pin_auth)?;
}
if let Some(new_pin_enc) = &self.new_pin_enc {
encoder.u8(0x05)?; // newPinEnc
encoder.bytes(&new_pin_enc)?;
}
if let Some(pin_hash_enc) = &self.pin_hash_enc {
encoder.u8(0x06)?; // pinHashEnc
encoder.bytes(pin_hash_enc)?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct ClientPinResponse {
pub key_agreement: Option<CoseKey>,
pub pin_token: Option<[u8; 16]>,
pub retries: Option<u8>,
}
impl ClientPinResponse {
pub fn decode<R: ReadBytesExt>(mut reader: R) -> FidoResult<Self> {
let status = reader.read_u8().context(FidoErrorKind::CborDecode)?;
if status != 0 {
Err(FidoErrorKind::CborError(status))?
}
let mut decoder = Decoder::new(Config::default(), reader);
let mut response = ClientPinResponse::default();
for _ in 0..decoder.object()? {
match decoder.u8()? {
0x01 => {
let mut generic = GenericDecoder::from_decoder(decoder);
response.key_agreement = Some(CoseKey::decode(&mut generic)?);
decoder = generic.into_inner();
}
0x02 => {
let mut pin_token = [0; 16];
pin_token.copy_from_slice(&decoder.bytes()?[..]);
response.pin_token = Some(pin_token)
}
0x03 => response.retries = Some(decoder.u8()?),
_ => continue,
}
}
Ok(response)
}
}
#[derive(Debug)]
pub struct OptionsInfo {
pub plat: bool,
pub rk: bool,
pub client_pin: Option<bool>,
pub up: bool,
pub uv: Option<bool>,
}
impl Default for OptionsInfo {
fn default() -> Self {
OptionsInfo {
plat: false,
rk: false,
client_pin: None,
up: true,
uv: None,
}
}
}
impl OptionsInfo {
pub fn decode<R: ReadBytesExt>(decoder: &mut Decoder<R>) -> FidoResult<Self> {
let mut options = OptionsInfo::default();
for _ in 0..decoder.object()? {
match decoder.text()?.as_ref() {
"plat" => options.plat = decoder.bool()?,
"rk" => options.rk = decoder.bool()?,
"clientPin" => options.client_pin = Some(decoder.bool()?),
"up" => options.up = decoder.bool()?,
"uv" => options.uv = Some(decoder.bool()?),
_ => continue,
}
}
Ok(options)
}
}
#[derive(Debug, Default)]
pub struct AuthenticatorData {
pub rp_id_hash: [u8; 32],
pub up: bool,
pub uv: bool,
pub sign_count: u32,
pub attested_credential_data: AttestedCredentialData,
pub extensions: HashMap<String, Value>,
}
impl AuthenticatorData {
pub fn from_bytes(bytes: &[u8]) -> FidoResult<Self> {
let mut data = AuthenticatorData::default();
data.rp_id_hash.copy_from_slice(&bytes[0..32]);
let flags = bytes[32];
data.up = (flags & 0x01) == 0x01;
data.uv = (flags & 0x02) == 0x02;
let is_attested = (flags & 0x40) == 0x40;
let has_extension_data = (flags & 0x80) == 0x80;
data.sign_count = BigEndian::read_u32(&bytes[33..37]);
if bytes.len() < 38 {
return Ok(data);
}
let mut cur = Cursor::new(&bytes[37..]);
if is_attested {
let attested_credential_data = AttestedCredentialData::from_bytes(&mut cur)?;
data.attested_credential_data = attested_credential_data;
if cur.position() >= (bytes.len() - 37) as u64 {
return Ok(data);
}
}
if has_extension_data {
let mut decoder = GenericDecoder::new(Config::default(), cur);
for _ in 0..decoder.borrow_mut().object()? {
let key = decoder.borrow_mut().text()?;
let value = decoder.value()?;
data.extensions.insert(key.to_string(), value);
}
}
Ok(data)
}
}
#[derive(Debug, Default)]
pub struct AttestedCredentialData {
pub aaguid: [u8; 16],
pub credential_id: Vec<u8>,
pub credential_public_key: CoseKey,
}
impl AttestedCredentialData {
pub fn from_bytes(cur: &mut Cursor<&[u8]>) -> FidoResult<Self> {
let mut response = AttestedCredentialData::default();
let bytes = cur.get_ref();
if bytes.is_empty() {
return Ok(response);
}
response.aaguid.copy_from_slice(&bytes[0..16]);
let id_length = BigEndian::read_u16(&bytes[16..18]) as usize;
response.credential_id = Vec::from(&bytes[18..(18 + id_length)]);
cur.set_position(18 + id_length as u64);
let mut decoder = GenericDecoder::new(Config::default(), cur);
response.credential_public_key = CoseKey::decode(&mut decoder)?;
Ok(response)
}
}
#[derive(Debug, Default)]
pub struct P256Key {
x: [u8; 32],
y: [u8; 32],
}
impl P256Key {
pub fn from_cose(cose: &CoseKey) -> FidoResult<Self> {
if cose.key_type != 2 || cose.algorithm != -7 {
Err(FidoErrorKind::KeyType)?
}
if let (
Some(Value::U8(curve)),
Some(Value::Bytes(value::Bytes::Bytes(x))),
Some(Value::Bytes(value::Bytes::Bytes(y))),
) = (
cose.parameters.get(&-1),
cose.parameters.get(&-2),
cose.parameters.get(&-3),
) {
if *curve != 1 {
Err(FidoErrorKind::KeyType)?
}
let mut key = P256Key::default();
key.x.copy_from_slice(&x);
key.y.copy_from_slice(&y);
return Ok(key);
}
Err(FidoErrorKind::KeyType)?
}
pub fn from_bytes(bytes: &[u8]) -> FidoResult<Self> {
if bytes.len() != 65 || bytes[0] != 0x04 {
Err(FidoErrorKind::CborDecode)?
}
let mut res = P256Key::default();
res.x.copy_from_slice(&bytes[1..33]);
res.y.copy_from_slice(&bytes[33..65]);
Ok(res)
}
pub fn to_cose(&self) -> CoseKey {
CoseKey {
key_type: 2,
algorithm: -7,
parameters: [
(-1, Value::U8(1)),
(-2, Value::Bytes(value::Bytes::Bytes(self.x.to_vec()))),
(-3, Value::Bytes(value::Bytes::Bytes(self.y.to_vec()))),
]
.iter()
.cloned()
.collect(),
}
}
pub fn bytes(&self) -> [u8; 65] {
let mut bytes = [0; 65];
bytes[0] = 0x04;
bytes[1..33].copy_from_slice(&self.x);
bytes[33..65].copy_from_slice(&self.y);
bytes
}
}
#[derive(Debug, Default)]
pub struct CoseKey {
key_type: u16,
algorithm: i32,
parameters: HashMap<i16, Value>,
}
impl CoseKey {
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
let size = 1 + self.parameters.len();
encoder.object(size)?;
encoder.i16(0x01)?; // keyType
encoder.u16(self.key_type)?;
//encoder.i16(0x02)?; // algorithm
//encoder.i32(self.algorithm)?;
for (key, value) in self.parameters.iter() {
encoder.i16(*key)?;
let mut generic = GenericEncoder::new(encoder.writer());
generic.value(value)?;
}
Ok(())
}
pub fn decode<R: ReadBytesExt>(generic: &mut GenericDecoder<R>) -> FidoResult<Self> {
let items;
{
let decoder = generic.borrow_mut();
items = decoder.object()?;
}
let mut cose_key = CoseKey::default();
cose_key.algorithm = -7;
for _ in 0..items {
match generic.borrow_mut().i16()? {
0x01 => cose_key.key_type = generic.borrow_mut().u16()?,
0x02 => cose_key.algorithm = generic.borrow_mut().i32()?,
key if key < 0 => {
cose_key.parameters.insert(key, generic.value()?);
}
_ => {
generic.value()?; // skip unknown parameter
}
}
}
Ok(cose_key)
}
}
#[derive(Debug, Default)]
pub struct PublicKeyCredentialRpEntity<'a> {
pub id: &'a str,
pub name: Option<&'a str>,
pub icon: Option<&'a str>,
}
impl<'a> PublicKeyCredentialRpEntity<'a> {
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
let mut length = 1;
length += self.name.is_some() as usize;
length += self.icon.is_some() as usize;
encoder.object(length)?;
encoder.text("id")?;
encoder.text(&self.id)?;
if let Some(icon) = &self.icon {
encoder.text("icon")?;
encoder.text(&icon)?;
}
if let Some(name) = &self.name {
encoder.text("name")?;
encoder.text(&name)?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct PublicKeyCredentialUserEntity<'a> {
pub id: &'a [u8],
pub name: &'a str,
pub icon: Option<&'a str>,
pub display_name: Option<&'a str>,
}
impl<'a> PublicKeyCredentialUserEntity<'a> {
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
let mut length = 2;
length += self.icon.is_some() as usize;
length += self.display_name.is_some() as usize;
encoder.object(length)?;
encoder.text("id")?;
encoder.bytes(&self.id)?;
if let Some(icon) = &self.icon {
encoder.text("icon")?;
encoder.text(&icon)?;
}
encoder.text("name")?;
encoder.text(&self.name)?;
if let Some(display_name) = &self.display_name {
encoder.text("displayName")?;
encoder.text(&display_name)?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct PublicKeyCredentialDescriptor {
pub cred_type: String,
pub id: Vec<u8>,
}
impl PublicKeyCredentialDescriptor {
pub fn decode<R: ReadBytesExt>(decoder: &mut Decoder<R>) -> FidoResult<Self> {
let mut response = PublicKeyCredentialDescriptor::default();
for _ in 0..decoder.object()? {
match decoder.text()?.as_ref() {
"id" => response.id = decoder.bytes()?,
"type" => response.cred_type = decoder.text()?,
_ => continue,
}
}
Ok(response)
}
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
encoder.object(2)?;
encoder.text("id")?;
encoder.bytes(&self.id)?;
encoder.text("type")?;
encoder.text(&self.cred_type)?;
Ok(())
}
}
#[derive(Debug)]
pub struct AuthenticatorOptions {
pub rk: bool,
pub uv: bool,
}
impl AuthenticatorOptions {
pub fn encoded(&self) -> bool {
self.rk || self.uv
}
pub fn encode<W: WriteBytesExt>(&self, encoder: &mut Encoder<W>) -> FidoResult<()> {
let length = (self.rk as usize) + (self.uv as usize);
encoder.object(length)?;
if self.rk {
encoder.text("rk")?;
encoder.bool(true)?;
}
if self.uv {
encoder.text("uv")?;
encoder.bool(true)?;
}
Ok(())
}
}

130
patch/ctap/src/crypto.rs Normal file
View File

@ -0,0 +1,130 @@
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use super::cbor::{CoseKey, P256Key};
use super::error::*;
use failure::ResultExt;
use ring::error::Unspecified;
use ring::{agreement, digest, hmac, rand, signature};
use rust_crypto::aes;
use rust_crypto::blockmodes::NoPadding;
use rust_crypto::buffer::{RefReadBuffer, RefWriteBuffer};
use rust_crypto::symmetriccipher::{Decryptor, Encryptor};
use untrusted::Input;
#[derive(Debug)]
pub struct SharedSecret {
pub public_key: CoseKey,
pub shared_secret: [u8; 32],
}
impl SharedSecret {
pub fn new(peer_key: &CoseKey) -> FidoResult<Self> {
let rng = rand::SystemRandom::new();
let private = agreement::EphemeralPrivateKey::generate(&agreement::ECDH_P256, &rng)
.context(FidoErrorKind::GenerateKey)?;
let public = &mut [0u8; agreement::PUBLIC_KEY_MAX_LEN][..private.public_key_len()];
private
.compute_public_key(public)
.context(FidoErrorKind::GenerateKey)?;
let peer = P256Key::from_cose(peer_key)
.context(FidoErrorKind::ParsePublic)?
.bytes();
let peer = Input::from(&peer);
let shared_secret = agreement::agree_ephemeral(
private,
&agreement::ECDH_P256,
peer,
Unspecified,
|material| Ok(digest::digest(&digest::SHA256, material)),
)
.context(FidoErrorKind::GenerateSecret)?;
let mut res = SharedSecret {
public_key: P256Key::from_bytes(&public)
.context(FidoErrorKind::ParsePublic)?
.to_cose(),
shared_secret: [0; 32],
};
res.shared_secret.copy_from_slice(shared_secret.as_ref());
Ok(res)
}
pub fn encryptor(&self) -> Box<dyn Encryptor + 'static> {
aes::cbc_encryptor(
aes::KeySize::KeySize256,
&self.shared_secret,
&[0u8; 16],
NoPadding,
)
}
pub fn encrypt_pin(&self, pin: &str) -> FidoResult<[u8; 16]> {
let mut encryptor = self.encryptor();
let pin_bytes = pin.as_bytes();
let hash = digest::digest(&digest::SHA256, &pin_bytes);
let in_bytes = &hash.as_ref()[0..16];
let mut input = RefReadBuffer::new(&in_bytes);
let mut out_bytes = [0; 16];
let mut output = RefWriteBuffer::new(&mut out_bytes);
encryptor
.encrypt(&mut input, &mut output, true)
.map_err(|_| FidoErrorKind::EncryptPin)?;
Ok(out_bytes)
}
pub fn decryptor(&self) -> Box<dyn Decryptor + 'static> {
aes::cbc_decryptor(
aes::KeySize::KeySize256,
&self.shared_secret,
&[0u8; 16],
NoPadding,
)
}
pub fn decrypt_token(&self, data: &mut [u8]) -> FidoResult<PinToken> {
let mut decryptor = self.decryptor();
let mut input = RefReadBuffer::new(data);
let mut out_bytes = [0; 16];
let mut output = RefWriteBuffer::new(&mut out_bytes);
decryptor
.decrypt(&mut input, &mut output, true)
.map_err(|_| FidoErrorKind::DecryptPin)?;
Ok(PinToken(hmac::SigningKey::new(&digest::SHA256, &out_bytes)))
}
}
pub struct PinToken(hmac::SigningKey);
impl PinToken {
pub fn auth(&self, data: &[u8]) -> [u8; 16] {
let signature = hmac::sign(&self.0, &data);
let mut out = [0; 16];
out.copy_from_slice(&signature.as_ref()[0..16]);
out
}
}
pub fn verify_signature(
public_key: &[u8],
client_data: &[u8],
auth_data: &[u8],
signature: &[u8],
) -> bool {
let public_key = Input::from(&public_key);
let msg_len = client_data.len() + auth_data.len();
let mut msg = Vec::with_capacity(msg_len);
msg.extend_from_slice(auth_data);
msg.extend_from_slice(client_data);
let msg = Input::from(&msg);
let signature = Input::from(signature);
signature::verify(
&signature::ECDSA_P256_SHA256_ASN1,
public_key,
msg,
signature,
)
.is_ok()
}

101
patch/ctap/src/error.rs Normal file
View File

@ -0,0 +1,101 @@
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use cbor_codec::{DecodeError, EncodeError};
use failure::{Backtrace, Context, Fail};
use std::fmt;
use std::fmt::Display;
pub type FidoResult<T> = Result<T, FidoError>;
#[derive(Debug)]
pub struct FidoError(Context<FidoErrorKind>);
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum FidoErrorKind {
#[fail(display = "Read/write error with device.")]
Io,
#[fail(display = "Error while reading packet from device.")]
ReadPacket,
#[fail(display = "Error while writing packet to device.")]
WritePacket,
#[fail(display = "Error while parsing CTAP from device.")]
ParseCtap,
#[fail(display = "Error while encoding CBOR for device.")]
CborEncode,
#[fail(display = "Error while decoding CBOR from device.")]
CborDecode,
#[fail(display = "Packets received from device in the wrong order.")]
InvalidSequence,
#[fail(display = "Failed to generate private keypair.")]
GenerateKey,
#[fail(display = "Failed to generate shared secret.")]
GenerateSecret,
#[fail(display = "Failed to parse public key.")]
ParsePublic,
#[fail(display = "Failed to encrypt PIN.")]
EncryptPin,
#[fail(display = "Failed to decrypt PIN.")]
DecryptPin,
#[fail(display = "Supplied key has incorrect type.")]
KeyType,
#[fail(display = "Device returned error: 0x{:x}", _0)]
CborError(u8),
#[fail(display = "Device does not support FIDO2")]
DeviceUnsupported,
#[fail(display = "This operating requires a PIN but none was provided.")]
PinRequired,
}
impl Fail for FidoError {
fn cause(&self) -> Option<&dyn Fail> {
self.0.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.0.backtrace()
}
}
impl Display for FidoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl FidoError {
pub fn kind(&self) -> FidoErrorKind {
*self.0.get_context()
}
}
impl From<FidoErrorKind> for FidoError {
#[inline(always)]
fn from(kind: FidoErrorKind) -> FidoError {
FidoError(Context::new(kind))
}
}
impl From<Context<FidoErrorKind>> for FidoError {
fn from(inner: Context<FidoErrorKind>) -> FidoError {
FidoError(inner)
}
}
impl From<EncodeError> for FidoError {
#[inline(always)]
fn from(err: EncodeError) -> FidoError {
FidoError(err.context(FidoErrorKind::CborEncode))
}
}
impl From<DecodeError> for FidoError {
#[inline(always)]
fn from(err: DecodeError) -> FidoError {
FidoError(err.context(FidoErrorKind::CborDecode))
}
}

View File

@ -0,0 +1,200 @@
use crate::cbor;
use crate::{FidoCredential, FidoDevice, FidoErrorKind, FidoResult};
use cbor_codec::value::{Bytes, Int, Key, Text, Value};
use cbor_codec::Encoder;
use cbor_codec::{Config, GenericDecoder};
use rust_crypto::buffer::{RefReadBuffer, RefWriteBuffer};
use rust_crypto::digest::Digest;
use rust_crypto::hmac::Hmac;
use rust_crypto::mac::Mac;
use rust_crypto::sha2::Sha256;
use std::collections::BTreeMap;
use std::io::Cursor;
#[derive(Debug, Clone)]
pub struct FidoHmacCredential {
pub id: Vec<u8>,
pub rp_id: String,
}
impl From<FidoCredential> for FidoHmacCredential {
fn from(cred: FidoCredential) -> Self {
FidoHmacCredential {
id: cred.id,
rp_id: cred.rp_id,
}
}
}
pub trait HmacExtension {
fn extension_name() -> &'static str {
"hmac-secret"
}
fn get_dict(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult<Value> {
let mut map = BTreeMap::new();
map.insert(
Key::Text(Text::Text(Self::extension_name().to_owned())),
self.get_data(salt, salt2)?,
);
Ok(Value::Map(map))
}
fn get_data(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult<Value>;
fn make_hmac_credential(&mut self) -> FidoResult<FidoHmacCredential>;
fn get_hmac_assertion(
&mut self,
credential: &FidoHmacCredential,
salt: &[u8; 32],
salt2: Option<&[u8; 32]>,
) -> FidoResult<([u8; 32], Option<[u8; 32]>)>;
fn hmac_challange(
&mut self,
credential: &FidoHmacCredential,
input: &[u8],
) -> FidoResult<[u8; 32]> {
let mut salt = [0u8; 32];
let mut digest = Sha256::new();
digest.input(input);
digest.result(&mut salt);
self.get_hmac_assertion(credential, &salt, None)
.map(|secret| secret.0)
}
}
impl HmacExtension for FidoDevice {
fn get_data(&mut self, salt: &[u8; 32], salt2: Option<&[u8; 32]>) -> FidoResult<Value> {
let shared_secret = self.shared_secret.as_ref().unwrap();
let mut encryptor = shared_secret.encryptor();
let mut salt_enc = [0u8; 64];
let mut output = RefWriteBuffer::new(&mut salt_enc);
let mut encrypt = || {
encryptor.encrypt(&mut RefReadBuffer::new(salt), &mut output, salt2.is_none())?;
if let Some(salt2) = salt2 {
encryptor
.encrypt(&mut RefReadBuffer::new(salt2), &mut output, true)
.map(|_| ())
} else {
Ok(())
}
};
encrypt().map_err(|_| FidoErrorKind::Io)?;
let key_agreement = || {
let mut cur = Cursor::new(Vec::new());
let mut encoder = Encoder::new(&mut cur);
shared_secret.public_key.encode(&mut encoder).unwrap();
cur.set_position(0);
let mut dec = GenericDecoder::new(Config::default(), cur);
dec.value()
};
let mut map = BTreeMap::new();
map.insert(
Key::Int(Int::from_i64(0x01)),
key_agreement().map_err(|_| FidoErrorKind::Io)?,
);
map.insert(
Key::Int(Int::from_i64(0x02)),
Value::Bytes(Bytes::Bytes(
salt_enc[0..((salt2.is_some() as usize + 1) * 32)].to_vec(),
)),
);
let mut salt_hmac = Hmac::new(Sha256::new(), &shared_secret.shared_secret);
salt_hmac.input(&salt_enc[0..((salt2.is_some() as usize + 1) * 32)]);
let mut authed_salt_enc = [0u8; 32];
authed_salt_enc.copy_from_slice(salt_hmac.result().code());
map.insert(
Key::Int(Int::from_i64(0x03)),
Value::Bytes(Bytes::Bytes(authed_salt_enc[0..16].to_vec())),
);
Ok(Value::Map(map))
}
fn make_hmac_credential(&mut self) -> FidoResult<FidoHmacCredential> {
self.make_credential("hmac", &[0u8], "commandline", &[0u8; 32])
.map(|cred| cred.into())
}
fn get_hmac_assertion(
&mut self,
credential: &FidoHmacCredential,
salt: &[u8; 32],
salt2: Option<&[u8; 32]>,
) -> FidoResult<([u8; 32], Option<[u8; 32]>)> {
let client_data_hash = [0u8; 32];
while self.shared_secret.is_none() {
self.init_shared_secret()?;
}
if self.needs_pin && self.pin_token.is_none() {
Err(FidoErrorKind::PinRequired)?
}
if client_data_hash.len() != 32 {
Err(FidoErrorKind::CborEncode)?
}
let pin_auth = self
.pin_token
.as_ref()
.map(|token| token.auth(&client_data_hash));
let ext_data: Value = self.get_data(salt, salt2)?;
let allow_list = [cbor::PublicKeyCredentialDescriptor {
cred_type: String::from("public-key"),
id: credential.id.clone(),
}];
let request = cbor::GetAssertionRequest {
rp_id: &credential.rp_id,
client_data_hash: &client_data_hash,
allow_list: &allow_list,
extensions: &[(<Self as HmacExtension>::extension_name(), &ext_data)],
options: Some(cbor::AuthenticatorOptions {
rk: false,
uv: true,
}),
pin_auth,
pin_protocol: pin_auth.and(Some(0x01)),
};
let response = match self.cbor(cbor::Request::GetAssertion(request))? {
cbor::Response::GetAssertion(resp) => resp,
_ => Err(FidoErrorKind::CborDecode)?,
};
let shared_secret = self.shared_secret.as_ref().unwrap();
let mut decryptor = shared_secret.decryptor();
let mut hmac_secret_combined = [0u8; 64];
let _output = RefWriteBuffer::new(&mut hmac_secret_combined);
let hmac_secret_enc = match response
.auth_data
.extensions
.get(<Self as HmacExtension>::extension_name())
.ok_or(FidoErrorKind::CborDecode)?
{
Value::Bytes(hmac_ciphered) => Ok(match hmac_ciphered {
Bytes::Bytes(hmac_ciphered) => hmac_ciphered.to_vec(),
Bytes::Chunks(hmac_ciphered) => hmac_ciphered.iter().fold(Vec::new(), |s, i| {
let mut s = s;
s.extend_from_slice(&i);
s
}),
}),
_ => Err(FidoErrorKind::CborDecode),
}?;
let mut hmac_secret = ([0u8; 32], [0u8; 32]);
decryptor
.decrypt(
&mut RefReadBuffer::new(&hmac_secret_enc),
&mut RefWriteBuffer::new(unsafe {
std::mem::transmute::<_, &mut [u8; 64]>(&mut hmac_secret)
}),
true,
)
.expect("failed to decrypt secret");
Ok((hmac_secret.0, salt2.map(|_| hmac_secret.1)))
}
}

View File

@ -0,0 +1 @@
pub mod hmac;

View File

@ -0,0 +1,16 @@
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use std::path::PathBuf;
#[derive(Debug, Clone)]
/// Storage for device related information
pub struct DeviceInfo {
pub path: PathBuf,
pub usage_page: u16,
pub usage: u16,
pub report_size: u16,
}

View File

@ -0,0 +1,88 @@
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
pub use super::hid_common::*;
use byteorder::{ByteOrder, LittleEndian};
use std::fs;
use std::io;
use std::path::PathBuf;
static REPORT_DESCRIPTOR_KEY_MASK: u8 = 0xfc;
static LONG_ITEM_ENCODING: u8 = 0xfe;
static USAGE_PAGE: u8 = 0x04;
static USAGE: u8 = 0x08;
static REPORT_SIZE: u8 = 0x74;
pub fn enumerate() -> io::Result<impl Iterator<Item = DeviceInfo>> {
fs::read_dir("/sys/class/hidraw").map(|entries| {
entries
.filter_map(|entry| entry.ok())
.filter_map(|entry| path_to_device(&entry.path()).ok())
})
}
fn path_to_device(path: &PathBuf) -> io::Result<DeviceInfo> {
let mut rd_path = path.clone();
rd_path.push("device/report_descriptor");
let rd = fs::read(rd_path)?;
let mut usage_page: u16 = 0;
let mut usage: u16 = 0;
let mut report_size: u16 = 0;
let mut pos: usize = 0;
while pos < rd.len() {
let key = rd[pos];
let mut key_size: usize = 1;
let mut size: u8;
if key == LONG_ITEM_ENCODING {
key_size = 3;
size = rd[pos + 1];
} else {
size = key & 0x03;
if size == 0x03 {
size = 0x04
}
}
if key & REPORT_DESCRIPTOR_KEY_MASK == USAGE_PAGE {
if size != 2 {
usage_page = u16::from(rd[pos + 1])
} else {
usage_page = LittleEndian::read_u16(&rd[(pos + 1)..(pos + 1 + (size as usize))]);
}
}
if key & REPORT_DESCRIPTOR_KEY_MASK == USAGE {
if size != 2 {
usage = u16::from(rd[pos + 1])
} else {
usage = LittleEndian::read_u16(&rd[(pos + 1)..(pos + 1 + (size as usize))]);
}
}
if key & REPORT_DESCRIPTOR_KEY_MASK == REPORT_SIZE {
if size != 2 {
report_size = u16::from(rd[pos + 1])
} else {
report_size = LittleEndian::read_u16(&rd[(pos + 1)..(pos + 1 + (size as usize))]);
}
}
pos = pos + key_size + size as usize;
}
let mut device_path = PathBuf::from("/dev");
device_path.push(path.file_name().unwrap());
Ok(DeviceInfo {
path: device_path,
usage_page,
usage,
report_size,
})
}

402
patch/ctap/src/lib.rs Normal file
View File

@ -0,0 +1,402 @@
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! An implementation of the CTAP2 protocol over USB.
//!
//! # Example
//!
//! ```
//! # fn do_fido() -> ctap::FidoResult<()> {
//! let mut devices = ctap::get_devices()?;
//! let device_info = &devices.next().unwrap();
//! let mut device = ctap::FidoDevice::new(device_info)?;
//!
//! // This can be omitted if the FIDO device is not configured with a PIN.
//! let pin = "test";
//! device.unlock(pin)?;
//!
//! // In a real application these values would come from the requesting app.
//! let rp_id = "rp_id";
//! let user_id = [0];
//! let user_name = "user_name";
//! let client_data_hash = [0; 32];
//! let cred = device.make_credential(
//! rp_id,
//! &user_id,
//! user_name,
//! &client_data_hash
//! )?;
//!
//! // In a real application the credential would be stored and used later.
//! let result = device.get_assertion(&cred, &client_data_hash);
//! # Ok(())
//! # }
#![allow(dead_code)]
extern crate failure;
extern crate rand;
#[macro_use]
extern crate failure_derive;
#[macro_use]
extern crate num_derive;
extern crate byteorder;
extern crate cbor as cbor_codec;
extern crate crypto as rust_crypto;
extern crate num_traits;
extern crate ring;
extern crate untrusted;
mod cbor;
mod crypto;
mod error;
pub mod extensions;
mod hid_common;
mod hid_linux;
mod packet;
use std::cmp;
use std::fs;
use std::io::{Cursor, Write};
use std::u16;
use std::u8;
pub use self::error::*;
use self::hid_linux as hid;
use self::packet::CtapCommand;
use failure::{Fail, ResultExt};
use num_traits::FromPrimitive;
use rand::prelude::*;
static BROADCAST_CID: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
/// Looks for any connected HID devices and returns those that support FIDO.
pub fn get_devices() -> FidoResult<impl Iterator<Item = hid::DeviceInfo>> {
hid::enumerate()
.context(FidoErrorKind::Io)
.map(|devices| devices.filter(|dev| dev.usage_page == 0xf1d0 && dev.usage == 0x21))
.map_err(From::from)
}
/// A credential created by a FIDO2 authenticator.
#[derive(Debug)]
pub struct FidoCredential {
/// The ID provided by the authenticator.
pub id: Vec<u8>,
/// The public key provided by the authenticator, in uncompressed form.
pub public_key: Vec<u8>,
/// The Relying Party ID provided by the platform when this key was generated.
pub rp_id: String,
}
/// An opened FIDO authenticator.
pub struct FidoDevice {
device: fs::File,
packet_size: u16,
channel_id: [u8; 4],
needs_pin: bool,
shared_secret: Option<crypto::SharedSecret>,
pin_token: Option<crypto::PinToken>,
aaguid: [u8; 16],
}
impl FidoDevice {
/// Open and initialize a given device. DeviceInfo is provided by the `get_devices`
/// function. This method will allocate a channel for this application, verify that
/// it supports FIDO2, and checks if a PIN is set.
///
/// This method will fail if the device can't be opened, if the device returns
/// malformed data or if the device is not supported.
pub fn new(device: &hid::DeviceInfo) -> error::FidoResult<Self> {
let mut options = fs::OpenOptions::new();
options.read(true).write(true);
let mut dev = FidoDevice {
device: options.open(&device.path).context(FidoErrorKind::Io)?,
packet_size: 64,
channel_id: BROADCAST_CID,
needs_pin: false,
shared_secret: None,
pin_token: None,
aaguid: [0; 16],
};
dev.init()?;
Ok(dev)
}
fn init(&mut self) -> FidoResult<()> {
let mut nonce = [0u8; 8];
thread_rng().fill_bytes(&mut nonce);
let response = self.exchange(CtapCommand::Init, &nonce)?;
if response.len() < 17 || response[0..8] != nonce {
Err(FidoErrorKind::ParseCtap)?
}
let flags = response[16];
if flags & 0x04 == 0 {
Err(FidoErrorKind::DeviceUnsupported)?
}
self.channel_id.copy_from_slice(&response[8..12]);
let response = match self.cbor(cbor::Request::GetInfo)? {
cbor::Response::GetInfo(resp) => resp,
_ => Err(FidoErrorKind::CborDecode)?,
};
if !response.versions.iter().any(|ver| ver == "FIDO_2_0") {
Err(FidoErrorKind::DeviceUnsupported)?
}
if !response.pin_protocols.iter().any(|ver| *ver == 1) {
Err(FidoErrorKind::DeviceUnsupported)?
}
self.needs_pin = response.options.client_pin == Some(true);
self.aaguid = response.aaguid;
Ok(())
}
/// Get the authenticator's AAGUID. This is not unique to an authenticator,
/// but it is unique to the specific brand and model.
pub fn aaguid(&self) -> &[u8] {
&self.aaguid
}
fn init_shared_secret(&mut self) -> FidoResult<()> {
let mut request = cbor::ClientPinRequest::default();
request.pin_protocol = 1;
request.sub_command = 0x02; // getKeyAgreement
let response = match self.cbor(cbor::Request::ClientPin(request))? {
cbor::Response::ClientPin(resp) => resp,
_ => Err(FidoErrorKind::CborDecode)?,
};
if let Some(key_agreement) = response.key_agreement {
self.shared_secret = Some(crypto::SharedSecret::new(&key_agreement)?);
Ok(())
} else {
Err(FidoErrorKind::CborDecode)?
}
}
/// Unlock the device with the provided PIN. Internally this will generate
/// an ECDH keypair, send the encrypted PIN to the device and store the PIN
/// token that the device generates on every power cycle. The PIN itself is
/// not stored.
///
/// This method will fail if the device returns malformed data or the PIN is
/// incorrect.
pub fn unlock(&mut self, pin: &str) -> FidoResult<()> {
while self.shared_secret.is_none() {
self.init_shared_secret()?;
}
// If the PIN is invalid the device should create a new agreementKey,
// so we only replace shared_secret on success.
let shared_secret = self.shared_secret.take().unwrap();
let mut request = cbor::ClientPinRequest::default();
request.pin_protocol = 1;
request.sub_command = 0x05; // getPINToken
request.key_agreement = Some(&shared_secret.public_key);
request.pin_hash_enc = Some(shared_secret.encrypt_pin(pin)?);
let response = match self.cbor(cbor::Request::ClientPin(request))? {
cbor::Response::ClientPin(resp) => resp,
_ => Err(FidoErrorKind::CborDecode)?,
};
if let Some(mut pin_token) = response.pin_token {
self.pin_token = Some(shared_secret.decrypt_token(&mut pin_token)?);
self.shared_secret = Some(shared_secret);
Ok(())
} else {
Err(FidoErrorKind::CborDecode)?
}
}
/// Request a new credential from the authenticator. The `rp_id` should be
/// a stable string used to identify the party for whom the credential is
/// created, for convenience it will be returned with the credential.
/// `user_id` and `user_name` are not required when requesting attestations
/// but they MAY be displayed to the user and MAY be stored on the device
/// to be returned with an attestation if the device supports this.
/// `client_data_hash` SHOULD be a SHA256 hash of provided `client_data`,
/// this is only used to verify the attestation provided by the
/// authenticator. When not implementing WebAuthN this can be any random
/// 32-byte array.
///
/// This method will fail if a PIN is required but the device is not
/// unlocked or if the device returns malformed data.
pub fn make_credential(
&mut self,
rp_id: &str,
user_id: &[u8],
user_name: &str,
client_data_hash: &[u8],
) -> FidoResult<FidoCredential> {
if self.needs_pin && self.pin_token.is_none() {
Err(FidoErrorKind::PinRequired)?
}
if client_data_hash.len() != 32 {
Err(FidoErrorKind::CborEncode)?
}
let pin_auth = self
.pin_token
.as_ref()
.map(|token| token.auth(&client_data_hash));
let rp = cbor::PublicKeyCredentialRpEntity {
id: rp_id,
name: None,
icon: None,
};
let user = cbor::PublicKeyCredentialUserEntity {
id: user_id,
name: user_name,
icon: None,
display_name: None,
};
let pub_key_cred_params = [("public-key", -7)];
let request = cbor::MakeCredentialRequest {
client_data_hash,
rp,
user,
pub_key_cred_params: &pub_key_cred_params,
exclude_list: Default::default(),
extensions: Default::default(),
options: Some(cbor::AuthenticatorOptions {
rk: false,
uv: true,
}),
pin_auth,
pin_protocol: pin_auth.and(Some(0x01)),
};
let response = match self.cbor(cbor::Request::MakeCredential(request))? {
cbor::Response::MakeCredential(resp) => resp,
_ => Err(FidoErrorKind::CborDecode)?,
};
let public_key = cbor::P256Key::from_cose(
&response
.auth_data
.attested_credential_data
.credential_public_key,
)?
.bytes();
Ok(FidoCredential {
id: response.auth_data.attested_credential_data.credential_id,
rp_id: String::from(rp_id),
public_key: Vec::from(&public_key[..]),
})
}
/// Request an assertion from the authenticator for a given credential.
/// `client_data_hash` SHOULD be a SHA256 hash of provided `client_data`,
/// this is signed and verified as part of the attestation. When not
/// implementing WebAuthN this can be any random 32-byte array.
///
/// This method will return whether the assertion matches the credential
/// provided, and will fail if a PIN is required but not provided or if the
/// device returns malformed data.
pub fn get_assertion(
&mut self,
credential: &FidoCredential,
client_data_hash: &[u8],
) -> FidoResult<bool> {
if self.needs_pin && self.pin_token.is_none() {
Err(FidoErrorKind::PinRequired)?
}
if client_data_hash.len() != 32 {
Err(FidoErrorKind::CborEncode)?
}
let pin_auth = self
.pin_token
.as_ref()
.map(|token| token.auth(&client_data_hash));
let allow_list = [cbor::PublicKeyCredentialDescriptor {
cred_type: String::from("public-key"),
id: credential.id.clone(),
}];
let request = cbor::GetAssertionRequest {
rp_id: &credential.rp_id,
client_data_hash: client_data_hash,
allow_list: &allow_list,
extensions: Default::default(),
options: Some(cbor::AuthenticatorOptions {
rk: false,
uv: true,
}),
pin_auth,
pin_protocol: pin_auth.and(Some(0x01)),
};
let response = match self.cbor(cbor::Request::GetAssertion(request))? {
cbor::Response::GetAssertion(resp) => resp,
_ => Err(FidoErrorKind::CborDecode)?,
};
Ok(crypto::verify_signature(
&credential.public_key,
&client_data_hash,
&response.auth_data_bytes,
&response.signature,
))
}
fn cbor(&mut self, request: cbor::Request) -> FidoResult<cbor::Response> {
let mut buf = Cursor::new(Vec::new());
request
.encode(&mut buf)
.context(FidoErrorKind::CborEncode)?;
let response = self.exchange(CtapCommand::Cbor, &buf.into_inner())?;
request
.decode(Cursor::new(response))
.context(FidoErrorKind::CborDecode)
.map_err(From::from)
}
fn exchange(&mut self, cmd: CtapCommand, payload: &[u8]) -> FidoResult<Vec<u8>> {
self.send(&cmd, payload)?;
self.receive(&cmd)
}
fn send(&mut self, cmd: &CtapCommand, payload: &[u8]) -> FidoResult<()> {
if payload.is_empty() || payload.len() > u16::MAX as usize {
Err(FidoErrorKind::WritePacket)?
}
let to_send = payload.len() as u16;
let max_payload = (self.packet_size - 7) as usize;
let (frame, payload) = payload.split_at(cmp::min(payload.len(), max_payload));
packet::write_init_packet(&mut self.device, 64, &self.channel_id, cmd, to_send, frame)?;
if payload.is_empty() {
return Ok(());
}
let max_payload = (self.packet_size - 5) as usize;
for (seq, frame) in (0..u8::MAX).zip(payload.chunks(max_payload)) {
packet::write_cont_packet(&mut self.device, 64, &self.channel_id, seq, frame)?;
}
self.device.flush().context(FidoErrorKind::WritePacket)?;
Ok(())
}
fn receive(&mut self, cmd: &CtapCommand) -> FidoResult<Vec<u8>> {
let mut first_packet: Option<packet::InitPacket> = None;
while first_packet.is_none() {
let packet = packet::InitPacket::from_reader(&mut self.device, 64)?;
if packet.cmd == CtapCommand::Error {
Err(packet::CtapError::from_u8(packet.payload[0])
.unwrap_or(packet::CtapError::Other)
.context(FidoErrorKind::ParseCtap))?
}
if packet.cid == self.channel_id && &packet.cmd == cmd {
first_packet = Some(packet);
}
}
let first_packet = first_packet.unwrap();
let mut data = first_packet.payload;
let mut to_read = (first_packet.size as isize) - data.len() as isize;
let mut seq = 0;
while to_read > 0 {
let packet = packet::ContPacket::from_reader(&mut self.device, 64, to_read as usize)?;
if packet.cid != self.channel_id {
continue;
}
if packet.seq != seq {
Err(FidoErrorKind::InvalidSequence)?
}
to_read -= packet.payload.len() as isize;
data.extend(&packet.payload);
seq += 1;
}
Ok(data)
}
}

179
patch/ctap/src/packet.rs Normal file
View File

@ -0,0 +1,179 @@
// This file is part of ctap, a Rust implementation of the FIDO2 protocol.
// Copyright (c) Ariën Holthuizen <contact@ardaxi.com>
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use super::error::*;
use failure::ResultExt;
use num_traits::{FromPrimitive, ToPrimitive};
use std::io::{Read, Write};
static FRAME_INIT: u8 = 0x80;
#[repr(u8)]
#[derive(FromPrimitive, ToPrimitive, PartialEq)]
pub enum CtapCommand {
Invalid = 0x00,
Ping = 0x01,
Msg = 0x03,
Lock = 0x04,
Init = 0x06,
Wink = 0x08,
Cbor = 0x10,
Cancel = 0x11,
Keepalive = 0x3b,
Error = 0x3f,
}
impl CtapCommand {
pub fn to_wire_format(&self) -> u8 {
match self.to_u8() {
Some(x) => x,
None => 0x00,
}
}
}
#[repr(u8)]
#[derive(FromPrimitive, Fail, Debug)]
pub enum CtapError {
#[fail(display = "The command in the request is invalid")]
InvalidCmd = 0x01,
#[fail(display = "The parameter(s) in the request is invalid")]
InvalidPar = 0x02,
#[fail(display = "The length field (BCNT) is invalid for the request ")]
InvalidLen = 0x03,
#[fail(display = "The sequence does not match expected value ")]
InvalidSeq = 0x04,
#[fail(display = "The message has timed out ")]
MsgTimeout = 0x05,
#[fail(display = "The device is busy for the requesting channel ")]
ChannelBusy = 0x06,
#[fail(display = "Command requires channel lock ")]
LockRequired = 0x0A,
#[fail(display = "Reserved error")]
NA = 0x0B,
#[fail(display = "Unspecified error")]
Other = 0x7F,
}
pub fn write_init_packet<W: Write>(
mut writer: W,
report_size: usize,
cid: &[u8],
cmd: &CtapCommand,
size: u16,
payload: &[u8],
) -> FidoResult<()> {
if cid.len() != 4 {
Err(FidoErrorKind::WritePacket)?
}
let mut packet = Vec::with_capacity(report_size);
packet.push(0);
packet.extend_from_slice(cid);
packet.push(FRAME_INIT | cmd.to_wire_format());
packet.push(((size >> 8) & 0xff) as u8);
packet.push((size & 0xff) as u8);
packet.extend_from_slice(payload);
if packet.len() > report_size + 1 {
Err(FidoErrorKind::WritePacket)?
}
packet.resize(report_size + 1, 0);
writer
.write_all(&packet)
.context(FidoErrorKind::WritePacket)?;
Ok(())
}
pub struct InitPacket {
pub cid: [u8; 4],
pub cmd: CtapCommand,
pub size: u16,
pub payload: Vec<u8>,
}
impl InitPacket {
pub fn from_reader<R: Read>(mut reader: R, report_size: usize) -> FidoResult<InitPacket> {
let mut buf = Vec::with_capacity(report_size);
buf.resize(report_size, 0);
reader
.read_exact(&mut buf[0..report_size])
.context(FidoErrorKind::ReadPacket)?;
let mut cid = [0; 4];
cid.copy_from_slice(&buf[0..4]);
let cmd = match CtapCommand::from_u8(buf[4] ^ FRAME_INIT) {
Some(cmd) => cmd,
None => CtapCommand::Invalid,
};
let size = ((u16::from(buf[5])) << 8) | u16::from(buf[6]);
let payload_end = if (size as usize) >= (report_size - 7) {
report_size
} else {
size as usize + 7
};
let payload = buf.drain(7..payload_end).collect();
Ok(InitPacket {
cid,
cmd,
size,
payload,
})
}
}
pub fn write_cont_packet<W: Write>(
mut writer: W,
report_size: usize,
cid: &[u8],
seq: u8,
payload: &[u8],
) -> FidoResult<()> {
if cid.len() != 4 {
Err(FidoErrorKind::WritePacket)?
}
let mut packet = Vec::with_capacity(report_size);
packet.push(0);
packet.extend_from_slice(cid);
packet.push(seq);
packet.extend_from_slice(payload);
if packet.len() > report_size + 1 {
Err(FidoErrorKind::WritePacket)?
}
packet.resize(report_size + 1, 0);
writer
.write_all(&packet)
.context(FidoErrorKind::WritePacket)?;
Ok(())
}
pub struct ContPacket {
pub cid: [u8; 4],
pub seq: u8,
pub payload: Vec<u8>,
}
impl ContPacket {
pub fn from_reader<R: Read>(
mut reader: R,
report_size: usize,
expected_data: usize,
) -> FidoResult<ContPacket> {
let mut buf = Vec::with_capacity(report_size);
buf.resize(report_size, 0);
reader
.read_exact(&mut buf[0..report_size])
.context(FidoErrorKind::ReadPacket)?;
let mut cid = [0; 4];
cid.copy_from_slice(&buf[0..4]);
let seq = buf[4];
let payload_end = if expected_data >= (report_size - 5) {
report_size
} else {
expected_data + 5
};
let payload = buf.drain(5..payload_end).collect();
Ok(ContPacket { cid, seq, payload })
}
}