This commit is contained in:
Edouard Oger 2019-05-03 10:46:30 -04:00
Родитель 4347ea9c59
Коммит 08d1f8c3c9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: A2F740742307674A
33 изменённых файлов: 259 добавлений и 1509 удалений

Просмотреть файл

@ -1,25 +1,3 @@
Unless otherwise specified (below and/or in individual files), the code in
this repository is subject to the Mozilla Public License, version 2.0
<LICENSE-MPL> or <https://www.mozilla.org/en-US/MPL/2.0/>.
The following directories contain some amount of third-party
code and are subject to their own license terms.
* rc_crypto, in components/support/rc_crypto heavily borrows its API
and portions of its implementation from the Rust portions of ring,
which carries an ISC-style license, reproduced below, and available at
<https://github.com/briansmith/ring/blob/master/LICENSE>
Copyright 2015-2019 Brian Smith.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

508
Cargo.lock сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -14,8 +14,6 @@ members = [
"components/support/ffi",
"components/support/force-viaduct-reqwest",
"components/support/interrupt",
"components/support/rc_crypto",
"components/support/rc_crypto/nss_sys",
"components/viaduct",
"components/sync15",
"components/rc_log",

Просмотреть файл

@ -27,7 +27,6 @@ untrusted = "0.6.2"
url = "1.7.1"
ffi-support = { path = "../support/ffi" }
viaduct = { path = "../viaduct" }
rc_crypto = { path = "../support/rc_crypto" }
error-support = { path = "../support/error" }
[dev-dependencies]

Просмотреть файл

@ -111,9 +111,6 @@ pub enum ErrorKind {
info: String,
},
#[fail(display = "Crypto/NSS error: {}", _0)]
CryptoError(#[fail(cause)] rc_crypto::Error),
// Basically reimplement error_chain's foreign_links. (Ugh, this sucks)
#[fail(display = "http-ece encryption error: {}", _0)]
EceError(#[fail(cause)] ece::Error),
@ -156,7 +153,6 @@ pub enum ErrorKind {
error_support::define_error! {
ErrorKind {
(CryptoError, rc_crypto::Error),
(EceError, ece::Error),
(HexDecodeError, hex::FromHexError),
(Base64Decode, base64::DecodeError),

Просмотреть файл

@ -9,7 +9,7 @@ use crate::{
Config,
};
use hawk_request::HawkRequestBuilder;
use rc_crypto::{digest, hkdf, hmac};
use ring::{digest, hkdf, hmac};
use rsa::RSABrowserIDKeyPair;
use serde_derive::*;
use serde_json::json;
@ -201,7 +201,7 @@ pub fn derive_sync_key(kb: &[u8]) -> Result<Vec<u8>> {
pub fn compute_client_state(kb: &[u8]) -> Result<String> {
Ok(hex::encode(
&digest::digest(&digest::SHA256, &kb)?.as_ref()[0..16],
digest::digest(&digest::SHA256, &kb).as_ref()[0..16].to_vec(),
))
}
@ -228,8 +228,8 @@ fn derive_key_from_session_token(session_token: &[u8]) -> Result<Vec<u8>> {
fn derive_hkdf_sha256_key(ikm: &[u8], salt: &[u8], info: &[u8], len: usize) -> Result<Vec<u8>> {
let salt = hmac::SigningKey::new(&digest::SHA256, salt);
let mut out = vec![0u8; len];
hkdf::extract_and_expand(&salt, ikm, info, &mut out)?;
Ok(out)
hkdf::extract_and_expand(&salt, ikm, info, &mut out);
Ok(out.to_vec())
}
#[derive(Deserialize)]

Просмотреть файл

@ -7,7 +7,7 @@ use crate::{
scoped_keys::{ScopedKey, ScopedKeysFlow},
util, FirefoxAccount, RNG,
};
use rc_crypto::digest;
use ring::digest;
use serde_derive::*;
use std::{
collections::HashSet,
@ -123,9 +123,9 @@ impl FirefoxAccount {
}
fn oauth_flow(&mut self, mut url: Url, scopes: &[&str], wants_keys: bool) -> Result<String> {
let state = util::random_base64_url_string(16)?;
let code_verifier = util::random_base64_url_string(43)?;
let code_challenge = digest::digest(&digest::SHA256, &code_verifier.as_bytes())?;
let state = util::random_base64_url_string(&*RNG, 16)?;
let code_verifier = util::random_base64_url_string(&*RNG, 43)?;
let code_challenge = digest::digest(&digest::SHA256, &code_verifier.as_bytes());
let code_challenge = base64::encode_config(&code_challenge, base64::URL_SAFE_NO_PAD);
url.query_pairs_mut()
.append_pair("client_id", &self.state.config.client_id)

Просмотреть файл

@ -4,8 +4,7 @@
use crate::{errors::*, FirefoxAccount};
use byteorder::{BigEndian, ByteOrder};
use rc_crypto::digest;
use ring::{aead, agreement, agreement::EphemeralPrivateKey, rand::SecureRandom};
use ring::{aead, agreement, agreement::EphemeralPrivateKey, digest, rand::SecureRandom};
use serde_derive::*;
use serde_json::{self, json};
use untrusted::Input;
@ -118,7 +117,7 @@ impl ScopedKeysFlow {
buf.extend_from_slice(&to_32b_buf(apv.len() as u32));
buf.extend_from_slice(apv.as_bytes());
buf.extend_from_slice(&to_32b_buf(256));
Ok(digest::digest(&digest::SHA256, &buf)?)
Ok(digest::digest(&digest::SHA256, &buf).as_ref()[0..32].to_vec())
},
)?;

Просмотреть файл

@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::errors::*;
use rc_crypto::rand;
use ring::rand::SecureRandom;
use std::time::{SystemTime, UNIX_EPOCH};
// Gets the unix epoch in ms.
@ -21,9 +21,9 @@ pub fn now_secs() -> u64 {
since_epoch.as_secs()
}
pub fn random_base64_url_string(len: usize) -> Result<String> {
pub fn random_base64_url_string(rng: &dyn SecureRandom, len: usize) -> Result<String> {
let mut out = vec![0u8; len];
rand::fill(&mut out).map_err(|_| ErrorKind::RngFailure)?;
rng.fill(&mut out).map_err(|_| ErrorKind::RngFailure)?;
Ok(base64::encode_config(&out, base64::URL_SAFE_NO_PAD))
}

Просмотреть файл

@ -26,7 +26,6 @@ viaduct = { path = "../viaduct" }
ffi-support = { path = "../support/ffi" }
sql-support = { path = "../support/sql" }
error-support = { path = "../support/error" }
rc_crypto = { path = "../support/rc_crypto" }
[dev-dependencies]
env_logger = "0.6.1"

Просмотреть файл

@ -15,7 +15,6 @@ import com.sun.jna.Structure
open class PushError(msg: String) : Exception(msg)
open class InternalPanic(msg: String) : PushError(msg)
open class OpenSSLError(msg: String) : PushError(msg)
open class CryptoError(msg: String) : PushError(msg)
open class CommunicationError(msg: String) : PushError(msg)
open class CommunicationServerError(msg: String) : PushError(msg)
open class AlreadyRegisteredError : PushError(
@ -72,7 +71,6 @@ open class RustError : Structure() {
31 -> return TranscodingError(message)
32 -> return EncryptionError(message)
33 -> return UrlParseError(message)
34 -> return CryptoError(message)
-1 -> return InternalPanic(message)
// Note: `1` is used as a generic catch all, but we
// might as well handle the others the same way.

Просмотреть файл

@ -19,7 +19,7 @@ use ece::{
LocalKeyPairImpl,
};
use openssl::ec::EcKey;
use rc_crypto::rand;
use openssl::rand::rand_bytes;
use crate::error;
@ -172,7 +172,7 @@ pub struct Crypto;
pub fn get_bytes(size: usize) -> error::Result<Vec<u8>> {
let mut bytes = vec![0u8; size];
rand::fill(&mut bytes)?;
rand_bytes(bytes.as_mut_slice())?;
Ok(bytes)
}

Просмотреть файл

@ -25,7 +25,6 @@ impl From<Error> for ffi_support::ExternError {
error_support::define_error! {
ErrorKind {
(CryptoError, rc_crypto::Error),
(StorageSqlError, rusqlite::Error),
(UrlParseError, url::ParseError),
}
@ -41,9 +40,6 @@ pub enum ErrorKind {
#[fail(display = "Internal Error: {:?}", _0)]
InternalError(String),
#[fail(display = "Crypto/NSS error: {}", _0)]
CryptoError(#[fail(cause)] rc_crypto::Error),
/// An unknown OpenSSL Cryptography error
#[fail(display = "OpenSSL Error: {:?}", _0)]
OpenSSLError(String),
@ -99,7 +95,6 @@ impl ErrorKind {
ErrorKind::TranscodingError(_) => 31,
ErrorKind::EncryptionError(_) => 32,
ErrorKind::UrlParseError(_) => 33,
ErrorKind::CryptoError(_) => 34,
};
ffi_support::ErrorCode::new(code)
}

Просмотреть файл

@ -1,23 +0,0 @@
[package]
name = "rc_crypto"
version = "0.1.0"
authors = ["Edouard Oger <eoger@fastmail.com>"]
edition = "2018"
license = "ISC"
[lib]
crate-type = ["lib", "staticlib", "cdylib"]
[dependencies]
failure = "0.1.5"
failure_derive = "0.1.5"
error-support = { path = "../error" }
[target.'cfg(not(target_os = "ios"))'.dependencies]
nss_sys = { path = "nss_sys" }
[target.'cfg(target_os = "ios")'.dependencies]
ring = "0.14.5"
[dev-dependencies]
hex = "0.3.2"

Просмотреть файл

@ -1,11 +0,0 @@
# rc_crypto
rc_crypto, like its name infers, handles all of our cryptographic needs.
It is backed by the Mozilla-sponsored NSS library through the `nss-sys` crate (more information [here](nss_sys/README.md)).
It pretty much follows the very rust-idiomatic [ring crate API](https://briansmith.org/rustdoc/ring/).
## License
This derives its API and portions of its implementation from the the [`ring`](https://github.com/briansmith/ring/) project, which is available under an ISC-style license. See the COPYRIGHT file, or https://github.com/briansmith/ring/blob/master/LICENSE for details.

Просмотреть файл

@ -1,19 +0,0 @@
[package]
name = "nss_sys"
version = "0.1.0"
authors = ["Edouard Oger <eoger@fastmail.com>"]
edition = "2018"
license = "MPL-2.0"
[lib]
crate-type = ["lib", "staticlib", "cdylib"]
[dependencies]
libloading = "0.5.0"
lazy_static = "1.3.0"
[build-dependencies]
bindgen = "0.49.0"
serde = "1.0.90"
serde_derive = "1.0.90"
toml = "0.5.0"

Просмотреть файл

@ -1,9 +0,0 @@
## nss_sys
NSS bindings for Rust.
These bindings are implemented using `dlopen`/`dlsym` instead of linking against libnss.
This is so we can re-use the NSS library shipped with GeckoView on Fenix and reference-browser.
On Lockbox Android, or even in unit tests artifacts, we ship these library files ourselves alongside our compiled Rust library.
On iOS the situation is different, we dynamically link because Apple [discourages using `dlopen`](https://github.com/nicklockwood/GZIP/issues/24).

Просмотреть файл

@ -1,57 +0,0 @@
# In this file, every section corresponds to a header file.
# A corresponding binding file will be created in $OUT_DIR.
headers = [
"blapit.h",
"nss.h",
"seccomon.h",
"secoidt.h",
"prtypes.h",
"prerror.h",
"pk11pub.h",
"pkcs11t.h",
]
enums = [
"PK11Origin",
"SECOidTag",
"SECStatus",
"SECItemType",
]
types = [
"PRBool",
"PRInt32",
"PRUint32",
"PRErrorCode",
"NSSInitContext",
"NSSInitParameters",
"SECItem",
"SECOidTag",
"SECStatus",
"CK_NSS_HKDFParams",
"CK_ATTRIBUTE_TYPE",
"CK_MECHANISM_TYPE",
"PK11Context",
"PK11SlotInfo",
"PK11SymKey",
"PK11Origin",
]
opaque = [
"NSSInitContext",
"NSSInitParameters",
"PK11Context",
"PK11SlotInfo",
"PK11SymKey",
]
variables = [
"NSS_INIT_FORCEOPEN",
"NSS_INIT_NOCERTDB",
"NSS_INIT_NOMODDB",
"NSS_INIT_OPTIMIZESPACE",
"NSS_INIT_READONLY",
"CKA_SIGN",
"CKA_WRAP",
"CKM_SHA256_HMAC",
"CKM_SHA512_HMAC",
"CKM_NSS_HKDF_SHA256",
"HASH_LENGTH_MAX",
]

Просмотреть файл

@ -1,200 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use bindgen::Builder;
use serde_derive::Deserialize;
use std::{
env,
ffi::OsString,
fs,
path::{Path, PathBuf},
process::Command,
};
use toml;
const BINDINGS_CONFIG: &'static str = "bindings.toml";
// This is the format of a single section of the configuration file.
#[derive(Deserialize)]
struct Bindings {
// The .h header files to generate from.
headers: Vec<String>,
// types that are explicitly included
types: Option<Vec<String>>,
// (un-used) functions that are explicitly included
// functions: Option<Vec<String>>,
// variables (and `#define`s) that are explicitly included
variables: Option<Vec<String>>,
// types that should be explicitly marked as opaque
opaque: Option<Vec<String>>,
// enumerations that are turned into a module (without this, the enum is
// mapped using the default, which means that the individual values are
// formed with an underscore as <enum_type>_<enum_value_name>).
enums: Option<Vec<String>>,
// Any item that is specifically excluded; if none of the types, functions,
// or variables fields are specified, everything defined will be mapped,
// so this can be used to limit that.
exclude: Option<Vec<String>>,
}
fn env(name: &str) -> Option<OsString> {
println!("cargo:rerun-if-env-changed={}", name);
env::var_os(name)
}
fn main() {
let (lib_dir, include_dir) = get_nss();
// See https://kazlauskas.me/entries/writing-proper-buildrs-scripts.html.
let target_os = env::var("CARGO_CFG_TARGET_OS");
// Only iOS dynamically links with NSS. All the other platforms dlopen.
if let Ok("ios") = target_os.as_ref().map(|x| &**x) {
println!(
"cargo:rustc-link-search=native={}",
lib_dir.to_string_lossy()
);
println!("cargo:include={}", include_dir.to_string_lossy());
}
let config_file = PathBuf::from(BINDINGS_CONFIG);
println!("cargo:rerun-if-changed={}", config_file.to_str().unwrap());
let config = fs::read_to_string(config_file).expect("unable to read binding configuration");
let bindings: Bindings = toml::from_str(&config).unwrap();
build_bindings(&bindings, &include_dir.join("nss"));
}
fn get_nss() -> (PathBuf, PathBuf) {
let nss_dir = env("NSS_DIR").expect("To build nss_sys, NSS_DIR must be set!");
let nss_dir = Path::new(&nss_dir);
let lib_dir = nss_dir.join("lib");
let include_dir = nss_dir.join("include");
(lib_dir, include_dir)
}
fn build_bindings(bindings: &Bindings, include_dir: &PathBuf) {
let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join("nss_bindings.rs");
let mut builder = Builder::default().generate_comments(false);
for h in bindings.headers.iter().cloned() {
builder = builder.header(include_dir.join(h).to_str().unwrap());
}
// Fix our cross-compilation include directories.
builder = fix_include_dirs(builder);
// Apply the configuration.
let empty: Vec<String> = vec![];
for v in bindings.types.as_ref().unwrap_or_else(|| &empty).iter() {
builder = builder.whitelist_type(v);
}
// for v in bindings.functions.as_ref().unwrap_or_else(|| &empty).iter() {
// builder = builder.whitelist_function(v);
// }
for v in bindings.variables.as_ref().unwrap_or_else(|| &empty).iter() {
builder = builder.whitelist_var(v);
}
for v in bindings.exclude.as_ref().unwrap_or_else(|| &empty).iter() {
builder = builder.blacklist_item(v);
}
for v in bindings.opaque.as_ref().unwrap_or_else(|| &empty).iter() {
builder = builder.opaque_type(v);
}
for v in bindings.enums.as_ref().unwrap_or_else(|| &empty).iter() {
builder = builder.constified_enum_module(v);
}
let bindings = builder.generate().expect("unable to generate bindings");
bindings
.write_to_file(out)
.expect("couldn't write bindings");
}
fn fix_include_dirs(mut builder: Builder) -> Builder {
let target_os = env::var("CARGO_CFG_TARGET_OS");
let target_arch = env::var("CARGO_CFG_TARGET_ARCH");
match target_os.as_ref().map(|x| &**x) {
Ok("macos") => {
// Cheap and dirty way to detect we are cross-compiling.
if env::var_os("CI").is_some() {
builder = builder
.detect_include_paths(false)
.clang_arg("-isysroot/tmp/MacOSX10.11.sdk");
}
}
Ok("windows") => {
if env::var_os("CI").is_some() {
builder = builder.clang_arg("-D_M_X64");
}
}
Ok("ios") => {
let sdk_root;
match target_arch.as_ref().map(|x| &**x).unwrap() {
"aarch64" => {
sdk_root = get_ios_sdk_root("iphoneos");
builder = builder.clang_arg("--target=arm64-apple-ios") // See https://github.com/rust-lang/rust-bindgen/issues/1211
}
"x86_64" => {
sdk_root = get_ios_sdk_root("iphonesimulator");
}
_ => panic!("Unknown iOS architecture."),
}
builder = builder
.detect_include_paths(false)
.clang_arg(format!("-isysroot{}", &sdk_root));
}
Ok("android") => {
let (android_api_version, _ndk_root, toolchain_dir) = get_android_env();
let mut toolchain = target_arch.as_ref().map(|x| &**x).unwrap();
// The other architectures map perfectly to what libs/setup_toolchains_local.sh produces.
if toolchain == "aarch64" {
toolchain = "arm64";
}
builder = builder
.detect_include_paths(false)
.clang_arg(format!(
"--sysroot={}",
&toolchain_dir
.join(format!("{}-{}/sysroot", toolchain, android_api_version))
.to_str()
.unwrap()
))
.clang_arg(format!("-D__ANDROID_API__={}", android_api_version))
// stddef.h isn't defined otherwise.
.clang_arg(format!(
"-I{}",
toolchain_dir
.join(format!(
"{}-{}/lib64/clang/5.0/include/",
toolchain, android_api_version
))
.to_str()
.unwrap()
))
}
_ => {}
}
return builder;
}
fn get_android_env() -> (String, PathBuf, PathBuf) {
return (
// This variable is not mandatory for building yet, so fall back to 21.
env::var("ANDROID_NDK_API_VERSION").unwrap_or("21".to_string()),
PathBuf::from(env::var("ANDROID_NDK_ROOT").unwrap()),
PathBuf::from(env::var("ANDROID_NDK_TOOLCHAIN_DIR").unwrap()),
);
}
fn get_ios_sdk_root(sdk_name: &str) -> String {
let output = Command::new("xcrun")
.arg("--show-sdk-path")
.arg("-sdk")
.arg(sdk_name)
.output()
.unwrap();
if output.status.success() {
String::from_utf8(output.stdout).unwrap().trim().to_string()
} else {
panic!("Could not get iOS SDK root!")
}
}

Просмотреть файл

@ -1,5 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include!(concat!(env!("OUT_DIR"), "/nss_bindings.rs"));

Просмотреть файл

@ -1,81 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)]
#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))]
mod bindings;
pub use bindings::*;
use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
// Remap some constants.
pub const SECSuccess: SECStatus = _SECStatus_SECSuccess;
pub const SECFailure: SECStatus = _SECStatus_SECFailure;
pub const PR_FALSE: PRBool = 0;
pub const PR_TRUE: PRBool = 1;
pub const CK_FALSE: CK_BBOOL = 0;
pub const CK_TRUE: CK_BBOOL = 1;
// This is the version this crate is claiming to be compatible with.
// We check it at runtime using `NSS_VersionCheck`.
pub const COMPATIBLE_NSS_VERSION: &str = "3.26";
// Code adapted from https://stackoverflow.com/a/35591693. I am not this kind of smart.
macro_rules! nss_exports {
($(unsafe fn $fn_name:ident($($arg:ident: $argty:ty),*)$( -> $ret:ty)?;)*) => {$(
#[cfg(not(target_os = "ios"))]
lazy_static::lazy_static! {
pub static ref $fn_name: libloading::Symbol<'static, unsafe extern fn($($arg: $argty),*)$( -> $ret)?> = {
unsafe {
LIBNSS3.get(stringify!($fn_name).as_bytes()).expect(stringify!(Could not get $fn_name handle))
}
};
}
#[cfg(target_os = "ios")]
extern "C" {
pub fn $fn_name($($arg: $argty),*)$( -> $ret)?;
}
)*};
}
#[cfg(not(target_os = "ios"))]
lazy_static::lazy_static! {
// Lib handle.
static ref LIBNSS3: libloading::Library = {
#[cfg(any(target_os = "macos", target_os = "ios"))]
const LIB_NAME: &str = "libnss3.dylib";
#[cfg(any(target_os = "linux", target_os = "android"))]
const LIB_NAME: &str = "libnss3.so";
#[cfg(target_os = "windows")]
const LIB_NAME: &str = "nss3.dll";
libloading::Library::new(LIB_NAME).expect("Cannot load libnss3.")
};
}
nss_exports! {
unsafe fn PR_GetError() -> PRErrorCode;
unsafe fn PR_GetErrorTextLength() -> PRInt32;
unsafe fn PR_GetErrorText(out: *mut c_uchar) -> PRInt32;
unsafe fn NSS_NoDB_Init(configdir: *const c_char) -> SECStatus;
unsafe fn NSS_InitContext(configdir: *const c_char, certPrefix: *const c_char, keyPrefix: *const c_char, secmodName: *const c_char, initParams: *mut NSSInitParameters, flags: PRUint32) -> *mut NSSInitContext;
unsafe fn NSS_IsInitialized() -> PRBool;
unsafe fn NSS_GetVersion() -> *const c_char;
unsafe fn NSS_VersionCheck(importedVersion: *const c_char) -> PRBool;
unsafe fn NSS_SecureMemcmp(ia: *const c_void, ib: *const c_void, n: usize) -> c_int;
unsafe fn PK11_HashBuf(hashAlg: SECOidTag::Type, out: *mut c_uchar, r#in: *const c_uchar, len: PRInt32) -> SECStatus;
unsafe fn PK11_FreeSlot(slot: *mut PK11SlotInfo);
unsafe fn PK11_FreeSymKey(symKey: *mut PK11SymKey);
unsafe fn PK11_DestroyContext(context: *mut PK11Context, freeit: PRBool);
unsafe fn PK11_GetInternalSlot() -> *mut PK11SlotInfo;
unsafe fn PK11_ImportSymKey(slot: *mut PK11SlotInfo, r#type: CK_MECHANISM_TYPE, origin: PK11Origin::Type, operation: CK_ATTRIBUTE_TYPE, key: *mut SECItem, wincx: *mut c_void) -> *mut PK11SymKey;
unsafe fn PK11_CreateContextBySymKey(r#type: CK_MECHANISM_TYPE, operation: CK_ATTRIBUTE_TYPE, symKey: *mut PK11SymKey, param: *mut SECItem) -> *mut PK11Context;
unsafe fn PK11_DigestBegin(cx: *mut PK11Context) -> SECStatus;
unsafe fn PK11_DigestOp(context: *mut PK11Context, r#in: *const c_uchar, len: c_uint) -> SECStatus;
unsafe fn PK11_DigestFinal(context: *mut PK11Context, data: *mut c_uchar, outLen: *mut c_uint, len: c_uint) -> SECStatus;
unsafe fn PK11_GenerateRandom(data: *mut c_uchar, len: c_int) -> SECStatus;
unsafe fn PK11_Derive(baseKey: *mut PK11SymKey, mechanism: CK_MECHANISM_TYPE, param: *mut SECItem, target: CK_MECHANISM_TYPE, operation: CK_ATTRIBUTE_TYPE, keySize: c_int) -> *mut PK11SymKey;
unsafe fn PK11_ExtractKeyValue(symKey: *mut PK11SymKey) -> SECStatus;
unsafe fn PK11_GetKeyData(symKey: *mut PK11SymKey) -> *mut SECItem;
}

Просмотреть файл

@ -1,57 +0,0 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
use crate::error::*;
#[cfg(not(target_os = "ios"))]
use crate::util::ensure_nss_initialized;
use std::os::raw::c_void;
/// Returns `Ok(())` if `a == b` and `Error` otherwise.
/// The comparison of `a` and `b` is done in constant time with respect to the
/// contents of each, but NOT in constant time with respect to the lengths of
/// `a` and `b`.
#[cfg(not(target_os = "ios"))]
pub fn verify_slices_are_equal(a: &[u8], b: &[u8]) -> Result<()> {
// NSS_SecureMemcmp will compare N elements fron our slices,
// so make sure they are the same length first.
if a.len() != b.len() {
return Err(ErrorKind::InternalError.into());
}
ensure_nss_initialized();
let result = unsafe {
nss_sys::NSS_SecureMemcmp(
a.as_ptr() as *const c_void,
b.as_ptr() as *const c_void,
a.len(),
)
};
match result {
0 => Ok(()),
_ => Err(ErrorKind::InternalError.into()),
}
}
#[cfg(target_os = "ios")]
pub fn verify_slices_are_equal(a: &[u8], b: &[u8]) -> Result<()> {
ring::constant_time::verify_slices_are_equal(a, b).map_err(|_| ErrorKind::InternalError.into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn does_compare() {
assert!(verify_slices_are_equal(b"bobo", b"bobo").is_ok());
assert!(verify_slices_are_equal(b"bobo", b"obob").is_err());
assert!(verify_slices_are_equal(b"bobo", b"notbobo").is_err());
}
}

Просмотреть файл

@ -1,102 +0,0 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
use crate::error::*;
#[cfg(not(target_os = "ios"))]
use crate::util::{ensure_nss_initialized, map_nss_secstatus};
use std::convert::TryFrom;
pub enum Algorithm {
SHA256,
}
pub use Algorithm::*;
impl Algorithm {
fn result_len(&self) -> usize {
match self {
Algorithm::SHA256 => 32,
}
}
}
#[cfg(not(target_os = "ios"))]
impl From<&Algorithm> for nss_sys::SECOidTag::Type {
fn from(alg: &Algorithm) -> Self {
match alg {
Algorithm::SHA256 => nss_sys::SECOidTag::SEC_OID_SHA256,
}
}
}
/// A calculated digest value.
#[derive(Clone)]
pub struct Digest {
pub(crate) value: Vec<u8>,
pub(crate) algorithm: &'static Algorithm,
}
impl Digest {
pub fn algorithm(&self) -> &'static Algorithm {
self.algorithm
}
}
impl AsRef<[u8]> for Digest {
fn as_ref(&self) -> &[u8] {
self.value.as_ref()
}
}
/// Returns the digest of data using the given digest algorithm.
#[cfg(not(target_os = "ios"))]
pub fn digest(algorithm: &'static Algorithm, data: &[u8]) -> Result<Digest> {
let mut out_buf = vec![0u8; algorithm.result_len()];
ensure_nss_initialized();
let data_len = i32::try_from(data.len())?;
map_nss_secstatus(|| unsafe {
nss_sys::PK11_HashBuf(
algorithm.into(),
out_buf.as_mut_ptr(),
data.as_ptr(),
data_len,
)
})?;
Ok(Digest {
value: out_buf,
algorithm,
})
}
#[cfg(target_os = "ios")]
pub fn digest(algorithm: &'static Algorithm, data: &[u8]) -> Result<Digest> {
let ring_alg = match algorithm {
Algorithm::SHA256 => &ring::digest::SHA256,
};
let ring_digest = ring::digest::digest(&ring_alg, data);
Ok(Digest {
value: ring_digest.as_ref().to_vec(),
algorithm,
})
}
#[cfg(test)]
mod tests {
use super::*;
use hex;
#[test]
fn sha256_digest() {
assert_eq!(
hex::encode(&digest(&SHA256, b"bobo").unwrap()),
"bf0c97708b849de696e7373508b13c5ea92bafa972fc941d694443e494a4b84d"
);
}
}

Просмотреть файл

@ -1,31 +0,0 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
use failure::Fail;
#[derive(Debug, Fail)]
pub enum ErrorKind {
#[fail(display = "NSS could not be initialized")]
NSSInitFailure,
#[fail(display = "NSS error: {} {}", _0, _1)]
NSSError(i32, String),
#[fail(display = "Internal error")]
InternalError,
#[fail(display = "Conversion error: {}", _0)]
ConversionError(#[fail(cause)] std::num::TryFromIntError),
}
error_support::define_error! {
ErrorKind {
(ConversionError, std::num::TryFromIntError),
}
}

Просмотреть файл

@ -1,123 +0,0 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
use crate::{digest, error::*, hmac};
#[cfg(not(target_os = "ios"))]
use crate::{
p11,
util::{ensure_nss_initialized, map_nss_secstatus},
};
#[cfg(not(target_os = "ios"))]
use nss_sys::*;
use std::{
convert::TryFrom,
mem,
os::raw::{c_uchar, c_ulong},
ptr,
};
pub fn extract_and_expand(
salt: &hmac::SigningKey,
secret: &[u8],
info: &[u8],
out: &mut [u8],
) -> Result<()> {
let prk = extract(salt, secret)?;
expand(&prk, info, out)?;
Ok(())
}
pub fn extract(salt: &hmac::SigningKey, secret: &[u8]) -> Result<hmac::SigningKey> {
let prk = hmac::sign(salt, secret)?;
Ok(hmac::SigningKey::new(salt.digest_algorithm(), prk.as_ref()))
}
#[cfg(target_os = "ios")]
pub fn expand(prk: &hmac::SigningKey, info: &[u8], out: &mut [u8]) -> Result<()> {
let ring_digest = match prk.digest_alg {
digest::Algorithm::SHA256 => &ring::digest::SHA256,
};
let ring_prk = ring::hmac::SigningKey::new(&ring_digest, &prk.key_value);
ring::hkdf::expand(&ring_prk, info, out);
Ok(())
}
#[cfg(not(target_os = "ios"))]
pub fn expand(prk: &hmac::SigningKey, info: &[u8], out: &mut [u8]) -> Result<()> {
let mech = match prk.digest_algorithm() {
digest::Algorithm::SHA256 => CKM_NSS_HKDF_SHA256,
};
ensure_nss_initialized();
// Most of the following code is inspired by the Firefox WebCrypto implementation:
// https://searchfox.org/mozilla-central/rev/ee3905439acbf81e9c829ece0b46d09d2fa26c5c/dom/crypto/WebCryptoTask.cpp#2530-2597
// Except that we only do the expand part, which explains why we use null pointers bellow.
let mut hkdf_params = CK_NSS_HKDFParams {
bExtract: CK_FALSE,
pSalt: ptr::null_mut(),
ulSaltLen: 0,
bExpand: CK_TRUE,
pInfo: info.as_ptr() as *mut u8,
ulInfoLen: c_ulong::try_from(info.len())?,
};
let mut params = SECItem {
type_: SECItemType::siBuffer,
data: &mut hkdf_params as *mut _ as *mut c_uchar,
len: u32::try_from(mem::size_of::<CK_NSS_HKDFParams>())?,
};
let base_key = p11::import_sym_key(mech.into(), CKA_WRAP.into(), &prk.key_value)?;
let len = i32::try_from(out.len())?;
let sym_key = p11::SymKey::from_ptr(unsafe {
// CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
// derived symmetric key and don't matter because we ignore them anyway.
PK11_Derive(
base_key.as_mut_ptr(),
mech.into(),
&mut params,
CKM_SHA512_HMAC.into(),
CKA_SIGN.into(),
len,
)
})?;
map_nss_secstatus(|| unsafe { PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
// This doesn't leak, because the SECItem* returned by PK11_GetKeyData
// just refers to a buffer managed by `symKey` which we copy into `out`.
let key_data = unsafe { *PK11_GetKeyData(sym_key.as_mut_ptr()) };
if u32::try_from(out.len())? > key_data.len {
return Err(ErrorKind::InternalError.into());
}
let key_data_len = usize::try_from(key_data.len)?;
let buf = unsafe { std::slice::from_raw_parts(key_data.data, key_data_len) };
out.copy_from_slice(&buf[0..out.len()]);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use hex;
#[test]
fn hkdf_extract_expand() {
let secret = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
let salt = hex::decode("000102030405060708090a0b0c").unwrap();
let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap();
let expected_out = hex::decode(
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
)
.unwrap();
let salt = hmac::SigningKey::new(&digest::SHA256, &salt);
let mut out = vec![0u8; expected_out.len()];
extract_and_expand(&salt, &secret, &info, &mut out).unwrap();
assert_eq!(out, expected_out);
}
}

Просмотреть файл

@ -1,143 +0,0 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
use crate::{constant_time, digest, error::*};
#[cfg(not(target_os = "ios"))]
use crate::{
p11,
util::{ensure_nss_initialized, map_nss_secstatus},
};
use std::convert::TryFrom;
/// A calculated signature value.
#[derive(Clone)]
pub struct Signature(digest::Digest);
impl AsRef<[u8]> for Signature {
#[inline]
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
/// A key to use for HMAC signing.
pub struct SigningKey {
pub(crate) digest_alg: &'static digest::Algorithm,
pub(crate) key_value: Vec<u8>,
}
impl SigningKey {
pub fn new(digest_alg: &'static digest::Algorithm, key_value: &[u8]) -> Self {
SigningKey {
digest_alg,
key_value: key_value.to_vec(),
}
}
#[inline]
pub fn digest_algorithm(&self) -> &'static digest::Algorithm {
self.digest_alg
}
}
/// A key to use for HMAC authentication.
pub struct VerificationKey {
wrapped: SigningKey,
}
impl VerificationKey {
pub fn new(digest_alg: &'static digest::Algorithm, key_value: &[u8]) -> Self {
VerificationKey {
wrapped: SigningKey::new(digest_alg, key_value),
}
}
#[inline]
pub fn digest_algorithm(&self) -> &'static digest::Algorithm {
self.wrapped.digest_algorithm()
}
}
/// Calculate the HMAC of `data` using `key` and verify it correspond to the provided signature.
pub fn verify(key: &VerificationKey, data: &[u8], signature: &[u8]) -> Result<()> {
verify_with_own_key(&key.wrapped, data, signature)
}
/// Equivalent to `verify` but allows the consumer to pass a `SigningKey`.
pub fn verify_with_own_key(key: &SigningKey, data: &[u8], signature: &[u8]) -> Result<()> {
constant_time::verify_slices_are_equal(sign(key, data)?.as_ref(), signature)
}
/// Calculate the HMAC of `data` using `key`.
#[cfg(not(target_os = "ios"))]
pub fn sign(key: &SigningKey, data: &[u8]) -> Result<Signature> {
let mech = match key.digest_alg {
digest::Algorithm::SHA256 => nss_sys::CKM_SHA256_HMAC,
};
ensure_nss_initialized();
let sym_key = p11::import_sym_key(mech.into(), nss_sys::CKA_SIGN.into(), &key.key_value)?;
let context = p11::create_context_by_sym_key(mech.into(), nss_sys::CKA_SIGN.into(), &sym_key)?;
map_nss_secstatus(|| unsafe { nss_sys::PK11_DigestBegin(context.as_mut_ptr()) })?;
let data_len = u32::try_from(data.len())?;
map_nss_secstatus(|| unsafe {
nss_sys::PK11_DigestOp(context.as_mut_ptr(), data.as_ptr(), data_len)
})?;
// We allocate the maximum possible length for the out buffer then we'll
// slice it after nss fills `out_len`.
let mut out_len: u32 = 0;
let mut out = vec![0u8; nss_sys::HASH_LENGTH_MAX as usize];
map_nss_secstatus(|| unsafe {
nss_sys::PK11_DigestFinal(
context.as_mut_ptr(),
out.as_mut_ptr(),
&mut out_len,
nss_sys::HASH_LENGTH_MAX,
)
})?;
out.truncate(usize::try_from(out_len)?);
Ok(Signature(digest::Digest {
value: out,
algorithm: key.digest_alg,
}))
}
#[cfg(target_os = "ios")]
pub fn sign(key: &SigningKey, data: &[u8]) -> Result<Signature> {
let ring_digest = match key.digest_alg {
digest::Algorithm::SHA256 => &ring::digest::SHA256,
};
let ring_key = ring::hmac::SigningKey::new(&ring_digest, &key.key_value);
let ring_signature = ring::hmac::sign(&ring_key, data);
Ok(Signature(digest::Digest {
value: ring_signature.as_ref().to_vec(),
algorithm: key.digest_alg,
}))
}
#[cfg(test)]
mod tests {
use super::*;
use hex;
#[test]
fn hmac_sign_verify() {
let key = VerificationKey::new(&digest::SHA256, b"key");
let expected_signature =
hex::decode("f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8")
.unwrap();
assert!(verify(
&key,
b"The quick brown fox jumps over the lazy dog",
&expected_signature
)
.is_ok());
}
}

Просмотреть файл

@ -1,28 +0,0 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
/// This crate provides all the cryptographic primitives required by
/// this workspace, backed by the NSS library.
/// The exposed API is pretty much the same as the `ring` crate
/// (https://briansmith.org/rustdoc/ring/) as it is well thought.
pub mod constant_time;
pub mod digest;
mod error;
pub mod hkdf;
pub mod hmac;
#[cfg(not(target_os = "ios"))]
mod p11;
pub mod rand;
#[cfg(not(target_os = "ios"))]
mod util;
pub use crate::error::{Error, ErrorKind, Result};

Просмотреть файл

@ -1,119 +0,0 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
use crate::error::*;
use nss_sys::*;
use std::{
convert::TryFrom,
os::raw::{c_uchar, c_uint},
ptr,
};
// The macro defines a wrapper around pointers refering to
// types allocated by NSS and calling their NSS destructor
// method when they go out of scope avoiding memory leaks.
// The `as_ptr`/`as_mut_ptr` are provided to retrieve the
// raw pointers for the NSS functions consuming them.
macro_rules! scoped_ptr {
($scoped:ident, $target:ty, $dtor:path) => {
pub struct $scoped {
ptr: *mut $target,
}
impl $scoped {
pub fn from_ptr(ptr: *mut $target) -> Result<$scoped> {
if !ptr.is_null() {
Ok($scoped { ptr: ptr })
} else {
Err(ErrorKind::InternalError.into())
}
}
#[inline]
#[allow(dead_code)]
pub const fn as_ptr(&self) -> *const $target {
self.ptr
}
#[inline]
pub fn as_mut_ptr(&self) -> *mut $target {
self.ptr
}
}
impl Drop for $scoped {
fn drop(&mut self) {
unsafe { $dtor(self.ptr) };
}
}
};
}
scoped_ptr!(Context, PK11Context, pk11_destroy_context_true);
scoped_ptr!(SymKey, PK11SymKey, PK11_FreeSymKey);
scoped_ptr!(Slot, PK11SlotInfo, PK11_FreeSlot);
#[inline]
unsafe fn pk11_destroy_context_true(context: *mut PK11Context) {
PK11_DestroyContext(context, PR_TRUE);
}
/// Safe wrapper around `PK11_GetInternalSlot` that
/// de-allocates memory when the slot goes out of
/// scope.
pub(crate) fn get_internal_slot() -> Result<Slot> {
Slot::from_ptr(unsafe { PK11_GetInternalSlot() })
}
/// Safe wrapper around PK11_ImportSymKey that
/// de-allocates memory when the key goes out of
/// scope.
pub(crate) fn import_sym_key(
mechanism: CK_MECHANISM_TYPE,
operation: CK_ATTRIBUTE_TYPE,
buf: &[u8],
) -> Result<SymKey> {
let mut item = SECItem {
type_: SECItemType::siBuffer,
data: buf.as_ptr() as *mut c_uchar,
len: c_uint::try_from(buf.len())?,
};
let slot = get_internal_slot()?;
SymKey::from_ptr(unsafe {
PK11_ImportSymKey(
slot.as_mut_ptr(),
mechanism,
PK11Origin::PK11_OriginUnwrap,
operation,
&mut item,
ptr::null_mut(),
)
})
}
/// Safe wrapper around PK11_CreateContextBySymKey that
/// de-allocates memory when the context goes out of
/// scope.
pub(crate) fn create_context_by_sym_key(
mechanism: CK_MECHANISM_TYPE,
operation: CK_ATTRIBUTE_TYPE,
sym_key: &SymKey,
) -> Result<Context> {
let mut param = SECItem {
type_: SECItemType::siBuffer,
data: ptr::null_mut(),
len: 0,
};
Context::from_ptr(unsafe {
PK11_CreateContextBySymKey(mechanism, operation, sym_key.as_mut_ptr(), &mut param)
})
}

Просмотреть файл

@ -1,44 +0,0 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
use crate::error::*;
#[cfg(not(target_os = "ios"))]
use crate::util::{ensure_nss_initialized, map_nss_secstatus};
use std::convert::TryFrom;
/// Fill a buffer with cryptographically secure pseudo-random data.
#[cfg(not(target_os = "ios"))]
pub fn fill(dest: &mut [u8]) -> Result<()> {
// `NSS_Init` will initialize the RNG with data from `/dev/urandom`.
ensure_nss_initialized();
let len = i32::try_from(dest.len())?;
map_nss_secstatus(|| unsafe { nss_sys::PK11_GenerateRandom(dest.as_mut_ptr(), len) })?;
Ok(())
}
#[cfg(target_os = "ios")]
pub fn fill(dest: &mut [u8]) -> Result<()> {
use ring::rand::SecureRandom;
let rng = ring::rand::SystemRandom::new();
rng.fill(dest).map_err(|_| ErrorKind::InternalError.into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn random_fill() {
let mut out = vec![0u8; 64];
assert!(fill(&mut out).is_ok());
assert_ne!(out, vec![0u8; 64]);
}
}

Просмотреть файл

@ -1,75 +0,0 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
use crate::error::*;
use nss_sys::*;
use std::{convert::TryFrom, ffi::CString, sync::Once};
static NSS_INIT: Once = Once::new();
pub fn ensure_nss_initialized() {
NSS_INIT.call_once(|| {
let version_ptr = CString::new(nss_sys::COMPATIBLE_NSS_VERSION).unwrap();
if unsafe { NSS_VersionCheck(version_ptr.as_ptr()) == PR_FALSE } {
panic!("Incompatible NSS version!")
}
if unsafe { NSS_IsInitialized() } == PR_FALSE {
let empty = CString::default();
let flags = NSS_INIT_READONLY
| NSS_INIT_NOCERTDB
| NSS_INIT_NOMODDB
| NSS_INIT_FORCEOPEN
| NSS_INIT_OPTIMIZESPACE;
let context = unsafe {
NSS_InitContext(
empty.as_ptr(),
empty.as_ptr(),
empty.as_ptr(),
empty.as_ptr(),
std::ptr::null_mut(),
flags,
)
};
if context.is_null() {
let error = get_last_error();
panic!("Could not initialize NSS: {}", error);
}
}
})
}
pub fn map_nss_secstatus<F>(callback: F) -> Result<()>
where
F: FnOnce() -> SECStatus,
{
if callback() == SECSuccess {
return Ok(());
}
Err(get_last_error())
}
/// Retrieve and wrap the last NSS/NSPR error in the current thread.
pub fn get_last_error() -> Error {
let error_code = unsafe { PR_GetError() };
let error_text: String = usize::try_from(unsafe { PR_GetErrorTextLength() })
.map(|error_text_len| {
let mut out_str = vec![0u8; error_text_len + 1];
unsafe { PR_GetErrorText(out_str.as_mut_ptr()) };
CString::new(&out_str[0..error_text_len])
.unwrap_or_else(|_| CString::default())
.to_str()
.unwrap_or_else(|_| "")
.to_owned()
})
.unwrap_or_else(|_| "".to_string());
ErrorKind::NSSError(error_code, error_text).into()
}

Просмотреть файл

@ -21,7 +21,6 @@ log = "0.4"
lazy_static = "1.0"
base16 = "0.1.1"
failure = "0.1.3"
rc_crypto = { path = "../support/rc_crypto" }
viaduct = { path = "../viaduct" }
interrupt = { path = "../support/interrupt" }
error-support = { path = "../support/error" }

Просмотреть файл

@ -71,9 +71,6 @@ pub enum ErrorKind {
#[fail(display = "Store error: {}", _0)]
StoreError(#[fail(cause)] failure::Error),
#[fail(display = "Crypto/NSS error: {}", _0)]
CryptoError(#[fail(cause)] rc_crypto::Error),
// Basically reimplement error_chain's foreign_links. (Ugh, this sucks)
#[fail(display = "OpenSSL error: {}", _0)]
OpensslError(#[fail(cause)] openssl::error::ErrorStack),
@ -105,7 +102,6 @@ pub enum ErrorKind {
error_support::define_error! {
ErrorKind {
(CryptoError, rc_crypto::Error),
(OpensslError, openssl::error::ErrorStack),
(Base64Decode, base64::DecodeError),
(JsonError, serde_json::Error),

Просмотреть файл

@ -3,12 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::error::{ErrorKind, Result};
use openssl::symm;
use rc_crypto::{
digest,
hmac::{self, Signature, SigningKey, VerificationKey},
rand,
};
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use openssl::sign::Signer;
use openssl::{self, symm};
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct KeyBundle {
@ -36,7 +34,7 @@ impl KeyBundle {
pub fn new_random() -> Result<KeyBundle> {
let mut buffer = [0u8; 64];
rand::fill(&mut buffer)?;
openssl::rand::rand_bytes(&mut buffer)?;
KeyBundle::from_ksync_bytes(&buffer)
}
@ -77,9 +75,20 @@ impl KeyBundle {
[base64::encode(&self.enc_key), base64::encode(&self.mac_key)]
}
fn hmac(&self, ciphertext: &[u8]) -> Result<Signature> {
let key = SigningKey::new(&digest::SHA256, self.hmac_key());
Ok(hmac::sign(&key, ciphertext)?)
/// Returns the 32 byte digest by value since it's small enough to be passed
/// around cheaply, and easily convertable into a slice or vec if you want.
fn hmac(&self, ciphertext: &[u8]) -> Result<[u8; 32]> {
let mut out = [0u8; 32];
let key = PKey::hmac(self.hmac_key())?;
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
signer.update(ciphertext)?;
let size = signer.sign(&mut out)?;
// This isn't an Err since it really should not be possible.
assert!(
size == 32,
"Somehow the 256 bits from sha256 do not add up into 32 bytes..."
);
Ok(out)
}
/// Important! Don't compare against this directly! use `verify_hmac` or `verify_hmac_string`!
@ -87,25 +96,32 @@ impl KeyBundle {
Ok(base16::encode_lower(&self.hmac(ciphertext)?))
}
pub fn verify_hmac(&self, expected_hmac: &[u8], ciphertext_base64: &str) -> Result<()> {
let key = VerificationKey::new(&digest::SHA256, self.hmac_key());
Ok(hmac::verify(
&key,
ciphertext_base64.as_bytes(),
expected_hmac,
)?)
pub fn verify_hmac(&self, expected_hmac: &[u8], ciphertext_base64: &str) -> Result<bool> {
let computed_hmac = self.hmac(ciphertext_base64.as_bytes())?;
// I suspect this is unnecessary for our case, but the rust-openssl docs
// want us to use this over == to avoid sidechannels, and who am I to argue?
Ok(openssl::memcmp::eq(&expected_hmac, &computed_hmac))
}
pub fn verify_hmac_string(&self, expected_hmac: &str, ciphertext_base64: &str) -> Result<()> {
pub fn verify_hmac_string(&self, expected_hmac: &str, ciphertext_base64: &str) -> Result<bool> {
let computed_hmac = self.hmac(ciphertext_base64.as_bytes())?;
// Note: openssl::memcmp::eq panics if the sizes aren't the same. Desktop returns that it
// was a verification failure, so we will too.
if expected_hmac.len() != 64 {
log::warn!("Garbage HMAC verification string: Wrong length");
return Ok(false);
}
// Decode the expected_hmac into bytes to avoid issues if a client happens to encode
// this as uppercase. This shouldn't happen in practice, but doing it this way is more
// robust and avoids an allocation.
let mut decoded_hmac = [0u8; 32];
if base16::decode_slice(expected_hmac, &mut decoded_hmac).is_err() {
log::warn!("Garbage HMAC verification string: contained non base16 characters");
return Err(ErrorKind::HmacMismatch.into());
return Ok(false);
}
self.verify_hmac(&decoded_hmac, ciphertext_base64)
Ok(openssl::memcmp::eq(&decoded_hmac, &computed_hmac))
}
/// Decrypt the provided ciphertext with the given iv, and decodes the
@ -136,7 +152,7 @@ impl KeyBundle {
/// and the generated iv.
pub fn encrypt_bytes_rand_iv(&self, cleartext_bytes: &[u8]) -> Result<(Vec<u8>, [u8; 16])> {
let mut iv = [0u8; 16];
rand::fill(&mut iv)?;
openssl::rand::rand_bytes(&mut iv)?;
let ciphertext = self.encrypt_bytes_with_iv(cleartext_bytes, &iv)?;
Ok((ciphertext, iv))
}
@ -184,7 +200,7 @@ mod test {
let ciphertext_base64 = CIPHERTEXT_B64_PIECES.join("");
assert!(key_bundle
.verify_hmac_string(HMAC_B16, &ciphertext_base64)
.is_ok());
.unwrap());
}
#[test]