2020-07-02 12:24:53 -07:00
|
|
|
// Copyright (c) Facebook, Inc. and its affiliates.
|
|
|
|
|
//
|
|
|
|
|
// This source code is licensed under the MIT license found in the
|
|
|
|
|
// LICENSE file in the root directory of this source tree.
|
|
|
|
|
|
2020-08-14 16:20:42 -04:00
|
|
|
use crate::{
|
2020-10-09 10:51:58 -07:00
|
|
|
errors::{InternalPakeError, PakeError, ProtocolError},
|
2020-08-14 16:20:42 -04:00
|
|
|
hash::Hash,
|
2020-10-09 10:51:58 -07:00
|
|
|
serialization::{serialize, tokenize, u8_to_credential_type, CredentialType},
|
2020-08-14 16:20:42 -04:00
|
|
|
};
|
2020-07-27 15:25:04 -07:00
|
|
|
use digest::Digest;
|
2020-07-02 12:24:53 -07:00
|
|
|
use generic_array::{
|
|
|
|
|
typenum::{Unsigned, U32},
|
|
|
|
|
GenericArray,
|
|
|
|
|
};
|
|
|
|
|
use hkdf::Hkdf;
|
|
|
|
|
use hmac::{Hmac, Mac, NewMac};
|
|
|
|
|
use rand_core::{CryptoRng, RngCore};
|
2020-10-09 10:51:58 -07:00
|
|
|
use std::collections::HashMap;
|
2020-07-02 12:24:53 -07:00
|
|
|
|
|
|
|
|
// Constant string used as salt for HKDF computation
|
|
|
|
|
const STR_ENVU: &[u8] = b"EnvU";
|
|
|
|
|
|
|
|
|
|
/// The length of the "export key" output by the client registration
|
|
|
|
|
/// and login finish steps
|
2020-07-03 18:28:33 -04:00
|
|
|
pub(crate) type ExportKeySize = U32;
|
|
|
|
|
|
|
|
|
|
const NONCE_LEN: usize = 32;
|
2020-07-02 12:24:53 -07:00
|
|
|
|
|
|
|
|
/// This struct is an instantiation of the envelope as described in
|
|
|
|
|
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
|
|
|
|
|
///
|
|
|
|
|
/// Note that earlier versions of this specification described an
|
|
|
|
|
/// implementation of this envelope using an encryption scheme that
|
|
|
|
|
/// satisfied random-key robustness
|
|
|
|
|
/// (https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4).
|
|
|
|
|
/// The specification update has simplified this assumption by taking
|
|
|
|
|
/// an XOR-based approach without compromising on security, and to avoid
|
|
|
|
|
/// the confusion around the implementation of an RKR-secure encryption.
|
2020-07-27 15:25:04 -07:00
|
|
|
pub(crate) struct Envelope<D: Hash> {
|
2020-07-02 12:24:53 -07:00
|
|
|
nonce: Vec<u8>,
|
|
|
|
|
ciphertext: Vec<u8>,
|
2020-10-09 10:51:58 -07:00
|
|
|
auth_data: Vec<u8>,
|
2020-07-27 15:25:04 -07:00
|
|
|
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
|
2020-07-02 12:24:53 -07:00
|
|
|
}
|
|
|
|
|
|
2020-10-09 10:51:58 -07:00
|
|
|
pub(crate) struct OpenedEnvelopeECF {
|
|
|
|
|
pub(crate) credentials_map: HashMap<CredentialType, Vec<u8>>,
|
|
|
|
|
pub(crate) export_key: GenericArray<u8, ExportKeySize>,
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-27 15:25:04 -07:00
|
|
|
pub(crate) struct OpenedEnvelope {
|
|
|
|
|
pub(crate) plaintext: Vec<u8>,
|
|
|
|
|
pub(crate) export_key: GenericArray<u8, ExportKeySize>,
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 10:51:58 -07:00
|
|
|
/// Representation for the format of the envelope
|
|
|
|
|
pub struct EnvelopeCredentialsFormat {
|
|
|
|
|
pub(crate) secret_credentials: Vec<CredentialType>,
|
|
|
|
|
pub(crate) cleartext_credentials: Vec<CredentialType>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EnvelopeCredentialsFormat {
|
|
|
|
|
/// Creates a new envelope credentials format with validity checking
|
|
|
|
|
/// An ECF is valid if:
|
|
|
|
|
/// - skU is a secret credential
|
|
|
|
|
/// - pkS is either a secret or cleartext credential
|
|
|
|
|
pub fn new(
|
|
|
|
|
secret_credentials: Vec<CredentialType>,
|
|
|
|
|
cleartext_credentials: Vec<CredentialType>,
|
|
|
|
|
) -> Result<Self, ProtocolError> {
|
|
|
|
|
if !secret_credentials.iter().any(|&v| v == CredentialType::SkU) {
|
|
|
|
|
// No skU found in secret credentials
|
|
|
|
|
return Err(ProtocolError::ServerInvalidEnvelopeCredentialsFormatError);
|
|
|
|
|
}
|
|
|
|
|
if !secret_credentials.iter().any(|&v| v == CredentialType::PkS)
|
|
|
|
|
&& !cleartext_credentials
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|&v| v == CredentialType::PkS)
|
|
|
|
|
{
|
|
|
|
|
// No pkS found in either secret credentials or cleartext_credentials
|
|
|
|
|
return Err(ProtocolError::ServerInvalidEnvelopeCredentialsFormatError);
|
|
|
|
|
}
|
|
|
|
|
Ok(Self {
|
|
|
|
|
secret_credentials,
|
|
|
|
|
cleartext_credentials,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Uses the default setting for the envelope credentials format
|
|
|
|
|
pub fn default() -> Result<Self, ProtocolError> {
|
|
|
|
|
Self::new(vec![CredentialType::SkU], vec![CredentialType::PkS])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-27 15:25:04 -07:00
|
|
|
impl<D: Hash> Envelope<D> {
|
2020-07-02 12:24:53 -07:00
|
|
|
/// The additional number of bytes added to the plaintext
|
|
|
|
|
pub(crate) fn additional_size() -> usize {
|
2020-07-27 15:25:04 -07:00
|
|
|
NONCE_LEN + <D as Digest>::OutputSize::to_usize()
|
2020-07-02 12:24:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn hmac_key_size() -> usize {
|
2020-07-27 15:25:04 -07:00
|
|
|
<D as Digest>::OutputSize::to_usize()
|
2020-07-02 12:24:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn export_key_size() -> usize {
|
|
|
|
|
ExportKeySize::to_usize()
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-27 15:25:04 -07:00
|
|
|
pub(crate) fn new(
|
|
|
|
|
nonce: Vec<u8>,
|
|
|
|
|
ciphertext: Vec<u8>,
|
2020-10-09 10:51:58 -07:00
|
|
|
auth_data: Vec<u8>,
|
2020-07-27 15:25:04 -07:00
|
|
|
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
|
|
|
|
|
) -> Self {
|
2020-07-02 12:24:53 -07:00
|
|
|
Self {
|
|
|
|
|
nonce,
|
|
|
|
|
ciphertext,
|
2020-10-09 10:51:58 -07:00
|
|
|
auth_data,
|
2020-07-03 18:28:33 -04:00
|
|
|
hmac,
|
2020-07-02 12:24:53 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The format of the output is:
|
|
|
|
|
/// nonce | ciphertext | hmac
|
|
|
|
|
/// nonce_size bytes | variable length | hmac_size bytes
|
|
|
|
|
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
2020-10-09 10:51:58 -07:00
|
|
|
let (result, remainder) = Self::deserialize(bytes)
|
|
|
|
|
.map_err(|_| InternalPakeError::IncompatibleEnvelopeCredentialsError)?;
|
|
|
|
|
if !remainder.is_empty() {
|
|
|
|
|
return Err(InternalPakeError::IncompatibleEnvelopeCredentialsError);
|
|
|
|
|
}
|
|
|
|
|
Ok(result)
|
2020-07-02 12:24:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
2020-10-09 10:51:58 -07:00
|
|
|
self.serialize()
|
2020-07-02 12:24:53 -07:00
|
|
|
}
|
|
|
|
|
|
2020-09-10 17:01:06 -04:00
|
|
|
pub(crate) fn serialize(&self) -> Vec<u8> {
|
|
|
|
|
[
|
|
|
|
|
&self.nonce[..],
|
2020-10-09 10:51:58 -07:00
|
|
|
&serialize(&self.ciphertext, 2)[..],
|
|
|
|
|
&serialize(&self.auth_data, 2)[..],
|
|
|
|
|
&serialize(&self.hmac, 2)[..],
|
2020-09-10 17:01:06 -04:00
|
|
|
]
|
|
|
|
|
.concat()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
|
2020-10-09 10:51:58 -07:00
|
|
|
if input.len() < NONCE_LEN {
|
|
|
|
|
return Err(ProtocolError::VerificationError(
|
|
|
|
|
PakeError::SerializationError,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-10 17:01:06 -04:00
|
|
|
let nonce = &input[..NONCE_LEN];
|
|
|
|
|
let (ciphertext, remainder) = tokenize(input[NONCE_LEN..].to_vec(), 2)?;
|
2020-10-09 10:51:58 -07:00
|
|
|
let (auth_data, remainder) = tokenize(remainder, 2)?;
|
2020-09-10 17:01:06 -04:00
|
|
|
let (hmac, remainder) = tokenize(remainder, 2)?;
|
|
|
|
|
Ok((
|
|
|
|
|
Self::new(
|
|
|
|
|
nonce.to_vec(),
|
|
|
|
|
ciphertext,
|
2020-10-09 10:51:58 -07:00
|
|
|
auth_data,
|
2020-09-10 17:01:06 -04:00
|
|
|
GenericArray::clone_from_slice(&hmac[..]),
|
|
|
|
|
),
|
|
|
|
|
remainder,
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 10:51:58 -07:00
|
|
|
fn serialize_extensions(
|
|
|
|
|
cred_format: Vec<CredentialType>,
|
|
|
|
|
credentials: &HashMap<CredentialType, Vec<u8>>,
|
|
|
|
|
) -> Result<Vec<u8>, InternalPakeError> {
|
|
|
|
|
let mut ret = Vec::new();
|
|
|
|
|
for index_type in cred_format {
|
|
|
|
|
match &credentials.get(&index_type) {
|
|
|
|
|
Some(v) => {
|
|
|
|
|
ret.push(index_type as u8 + 1);
|
|
|
|
|
ret.extend(serialize(&v, 2));
|
|
|
|
|
}
|
|
|
|
|
None => return Err(InternalPakeError::IncompatibleEnvelopeCredentialsError),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(ret)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn deserialize_extensions(
|
|
|
|
|
bytes: &[u8],
|
|
|
|
|
) -> Result<HashMap<CredentialType, Vec<u8>>, InternalPakeError> {
|
|
|
|
|
let mut credentials: HashMap<CredentialType, Vec<u8>> = HashMap::new();
|
|
|
|
|
let mut bytes_copy: Vec<u8> = Vec::new();
|
|
|
|
|
bytes_copy.extend_from_slice(&bytes);
|
|
|
|
|
while !bytes_copy.is_empty() {
|
|
|
|
|
let t = u8_to_credential_type(bytes_copy[0])
|
|
|
|
|
.ok_or(InternalPakeError::IncompatibleEnvelopeCredentialsError)?;
|
|
|
|
|
let (cred, remainder) = tokenize(bytes_copy[1..].to_vec(), 2)
|
|
|
|
|
.map_err(|_| InternalPakeError::IncompatibleEnvelopeCredentialsError)?;
|
|
|
|
|
bytes_copy = remainder;
|
|
|
|
|
credentials.insert(t, cred);
|
|
|
|
|
}
|
|
|
|
|
Ok(credentials)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn seal<R: RngCore + CryptoRng>(
|
|
|
|
|
key: &[u8],
|
|
|
|
|
ecf: EnvelopeCredentialsFormat,
|
|
|
|
|
credentials: HashMap<CredentialType, Vec<u8>>,
|
|
|
|
|
rng: &mut R,
|
|
|
|
|
) -> Result<(Self, GenericArray<u8, ExportKeySize>), InternalPakeError> {
|
|
|
|
|
let plaintext = Self::serialize_extensions(ecf.secret_credentials, &credentials)?;
|
|
|
|
|
let aad = Self::serialize_extensions(ecf.cleartext_credentials, &credentials)?;
|
|
|
|
|
Self::seal_raw(key, &plaintext, &aad, rng)
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-02 12:24:53 -07:00
|
|
|
/// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
|
|
|
|
|
/// Note that a new nonce is sampled for each call to seal.
|
2020-10-09 10:51:58 -07:00
|
|
|
pub(crate) fn seal_raw<R: RngCore + CryptoRng>(
|
2020-07-02 12:24:53 -07:00
|
|
|
key: &[u8],
|
|
|
|
|
plaintext: &[u8],
|
|
|
|
|
aad: &[u8],
|
|
|
|
|
rng: &mut R,
|
|
|
|
|
) -> Result<(Self, GenericArray<u8, ExportKeySize>), InternalPakeError> {
|
2020-07-03 18:28:33 -04:00
|
|
|
let mut nonce = vec![0u8; NONCE_LEN];
|
2020-07-02 12:24:53 -07:00
|
|
|
rng.fill_bytes(&mut nonce);
|
|
|
|
|
|
2020-07-27 15:25:04 -07:00
|
|
|
let h = Hkdf::<D>::new(Some(&nonce), &key);
|
2020-07-02 12:24:53 -07:00
|
|
|
let mut okm = vec![0u8; plaintext.len() + Self::hmac_key_size() + Self::export_key_size()];
|
|
|
|
|
h.expand(STR_ENVU, &mut okm)
|
|
|
|
|
.map_err(|_| InternalPakeError::HkdfError)?;
|
|
|
|
|
let xor_key = &okm[..plaintext.len()];
|
|
|
|
|
let hmac_key = &okm[plaintext.len()..plaintext.len() + Self::hmac_key_size()];
|
|
|
|
|
let export_key = &okm[plaintext.len() + Self::hmac_key_size()..];
|
|
|
|
|
|
|
|
|
|
let ciphertext: Vec<u8> = xor_key
|
|
|
|
|
.iter()
|
2020-07-20 15:44:27 -04:00
|
|
|
.zip(plaintext.iter())
|
2020-07-02 12:24:53 -07:00
|
|
|
.map(|(&x1, &x2)| x1 ^ x2)
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
let mut hmac =
|
2020-07-27 15:25:04 -07:00
|
|
|
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
2020-09-01 11:23:00 -07:00
|
|
|
hmac.update(&nonce);
|
2020-07-02 12:24:53 -07:00
|
|
|
hmac.update(&ciphertext);
|
|
|
|
|
hmac.update(&aad);
|
|
|
|
|
|
|
|
|
|
Ok((
|
2020-10-09 10:51:58 -07:00
|
|
|
Self::new(
|
|
|
|
|
nonce,
|
|
|
|
|
ciphertext.to_vec(),
|
|
|
|
|
aad.to_vec(),
|
|
|
|
|
hmac.finalize().into_bytes(),
|
|
|
|
|
),
|
2020-07-02 12:24:53 -07:00
|
|
|
*GenericArray::from_slice(&export_key),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 10:51:58 -07:00
|
|
|
pub(crate) fn open(&self, key: &[u8]) -> Result<OpenedEnvelopeECF, InternalPakeError> {
|
|
|
|
|
let mut credentials_map = Self::deserialize_extensions(&self.auth_data)?;
|
|
|
|
|
let opened = self.open_raw(key, &self.auth_data)?;
|
|
|
|
|
let plaintext_map = Self::deserialize_extensions(&opened.plaintext)?;
|
|
|
|
|
|
|
|
|
|
for (i, plaintext) in plaintext_map {
|
|
|
|
|
if credentials_map.contains_key(&i) {
|
|
|
|
|
// Trying to set a credential that was already provided in the aad
|
|
|
|
|
return Err(InternalPakeError::IncompatibleEnvelopeCredentialsError);
|
|
|
|
|
}
|
|
|
|
|
credentials_map.insert(i, plaintext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(OpenedEnvelopeECF {
|
|
|
|
|
credentials_map,
|
|
|
|
|
export_key: opened.export_key,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-02 12:24:53 -07:00
|
|
|
/// Attempts to decrypt the envelope using a key, which is successful only if the key and
|
|
|
|
|
/// aad used to construct the envelope are the same.
|
2020-10-09 10:51:58 -07:00
|
|
|
pub(crate) fn open_raw(
|
|
|
|
|
&self,
|
|
|
|
|
key: &[u8],
|
|
|
|
|
aad: &[u8],
|
|
|
|
|
) -> Result<OpenedEnvelope, InternalPakeError> {
|
2020-07-27 15:25:04 -07:00
|
|
|
let h = Hkdf::<D>::new(Some(&self.nonce), &key);
|
2020-07-02 12:24:53 -07:00
|
|
|
let mut okm =
|
|
|
|
|
vec![0u8; self.ciphertext.len() + Self::hmac_key_size() + Self::export_key_size()];
|
|
|
|
|
h.expand(STR_ENVU, &mut okm)
|
|
|
|
|
.map_err(|_| InternalPakeError::HkdfError)?;
|
|
|
|
|
let xor_key = &okm[..self.ciphertext.len()];
|
|
|
|
|
let hmac_key = &okm[self.ciphertext.len()..self.ciphertext.len() + Self::hmac_key_size()];
|
|
|
|
|
let export_key = &okm[self.ciphertext.len() + Self::hmac_key_size()..];
|
|
|
|
|
|
|
|
|
|
let mut hmac =
|
2020-07-27 15:25:04 -07:00
|
|
|
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
2020-09-01 11:23:00 -07:00
|
|
|
hmac.update(&self.nonce);
|
2020-07-02 12:24:53 -07:00
|
|
|
hmac.update(&self.ciphertext);
|
|
|
|
|
hmac.update(aad);
|
|
|
|
|
if hmac.verify(&self.hmac).is_err() {
|
|
|
|
|
return Err(InternalPakeError::SealOpenHmacError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let plaintext: Vec<u8> = xor_key
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(self.ciphertext.iter())
|
|
|
|
|
.map(|(&x1, &x2)| x1 ^ x2)
|
|
|
|
|
.collect();
|
2020-07-27 15:25:04 -07:00
|
|
|
Ok(OpenedEnvelope {
|
|
|
|
|
plaintext,
|
|
|
|
|
export_key: *GenericArray::from_slice(&export_key),
|
|
|
|
|
})
|
2020-07-02 12:24:53 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use rand_core::OsRng;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn seal_and_open() {
|
|
|
|
|
let mut rng = OsRng;
|
|
|
|
|
let mut key = [0u8; 32];
|
|
|
|
|
rng.fill_bytes(&mut key);
|
|
|
|
|
|
|
|
|
|
let mut msg = [0u8; 100];
|
|
|
|
|
rng.fill_bytes(&mut msg);
|
|
|
|
|
|
2020-07-27 15:25:04 -07:00
|
|
|
let (envelope, export_key_1) =
|
2020-10-09 10:51:58 -07:00
|
|
|
Envelope::<sha2::Sha256>::seal_raw(&key, &msg, b"aad", &mut rng).unwrap();
|
|
|
|
|
let opened_envelope = envelope.open_raw(&key, b"aad").unwrap();
|
2020-07-27 15:25:04 -07:00
|
|
|
assert_eq!(&msg.to_vec(), &opened_envelope.plaintext);
|
|
|
|
|
assert_eq!(&export_key_1.to_vec(), &opened_envelope.export_key.to_vec());
|
2020-07-02 12:24:53 -07:00
|
|
|
}
|
|
|
|
|
}
|