use failure::Error as FailureError;
use openssl::symm::decrypt_aead;
use reqwest::header::AUTHORIZATION;
use serde::{
de::{Error as SerdeError, Unexpected},
Deserialize, Deserializer,
};
use serde_json::{self, Value as JsonValue};
use super::exists::{Error as ExistsError, Exists as ExistsAction};
use crate::api::nonce::{header_nonce, request_nonce, NonceError};
use crate::api::request::{ensure_success, ResponseError};
use crate::api::url::UrlBuilder;
use crate::client::Client;
use crate::config::TAG_LEN;
use crate::crypto::b64;
use crate::crypto::key_set::KeySet;
use crate::crypto::sig::signature_encoded;
use crate::file::metadata::Metadata as MetadataData;
use crate::file::remote_file::RemoteFile;
pub struct Metadata<'a> {
file: &'a RemoteFile,
password: Option<String>,
check_exists: bool,
}
impl<'a> Metadata<'a> {
pub fn new(file: &'a RemoteFile, password: Option<String>, check_exists: bool) -> Self {
Self {
file,
password,
check_exists,
}
}
pub fn invoke(self, client: &Client) -> Result<MetadataResponse, Error> {
if self.check_exists {
let exist_response = ExistsAction::new(&self.file).invoke(&client)?;
if !exist_response.exists() {
return Err(Error::Expired);
}
if self.password.is_none() && exist_response.requires_password() {
return Err(Error::PasswordRequired);
}
}
let key = KeySet::from(self.file, self.password.as_ref());
let auth_nonce = self.fetch_auth_nonce(client)?;
self.fetch_metadata(&client, &key, &auth_nonce)
.map_err(|err| err.into())
}
fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, Error> {
request_nonce(client, UrlBuilder::download(self.file, false)).map_err(|err| err.into())
}
fn fetch_metadata(
&self,
client: &Client,
key: &KeySet,
auth_nonce: &[u8],
) -> Result<MetadataResponse, MetaError> {
let sig = signature_encoded(key.auth_key().unwrap(), &auth_nonce)
.map_err(|_| MetaError::ComputeSignature)?;
let mut response = client
.get(UrlBuilder::api_metadata(self.file))
.header(AUTHORIZATION.as_str(), format!("send-v1 {}", sig))
.send()
.map_err(|_| MetaError::NonceRequest)?;
ensure_success(&response).map_err(MetaError::NonceResponse)?;
let nonce = header_nonce(&response).map_err(MetaError::Nonce)?;
MetadataResponse::from(
&response
.json::<RawMetadataResponse>()
.map_err(|_| MetaError::Malformed)?,
&key,
nonce,
)
.map_err(|_| MetaError::Decrypt)
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum RawMetadataResponse {
V2 {
#[serde(rename = "metadata")]
meta: String,
#[serde(deserialize_with = "deserialize_u64")]
size: u64,
},
V3 {
#[serde(rename = "metadata")]
meta: String,
},
}
impl RawMetadataResponse {
pub fn decrypt_metadata(&self, key_set: &KeySet) -> Result<MetadataData, FailureError> {
let raw = b64::decode(self.meta())?;
let (encrypted, tag) = raw.split_at(raw.len() - TAG_LEN);
assert_eq!(tag.len(), TAG_LEN);
let meta = decrypt_aead(
KeySet::cipher(),
key_set.meta_key().unwrap(),
Some(key_set.iv()),
&[],
encrypted,
&tag,
)?;
Ok(serde_json::from_slice(&meta)?)
}
fn meta(&self) -> &str {
match self {
RawMetadataResponse::V2 { meta, size: _ } => &meta,
RawMetadataResponse::V3 { meta } => &meta,
}
}
pub fn size(&self) -> Option<u64> {
match self {
RawMetadataResponse::V2 { meta: _, size } => Some(*size),
RawMetadataResponse::V3 { meta: _ } => None,
}
}
}
fn deserialize_u64<'d, D>(d: D) -> Result<u64, D::Error>
where
D: Deserializer<'d>,
{
Deserialize::deserialize(d).and_then(|value: JsonValue| match value {
JsonValue::Number(n) => n.as_u64().ok_or_else(|| {
if let Some(n) = n.as_i64() {
SerdeError::invalid_type(Unexpected::Signed(n), &"a positive integer")
} else if let Some(n) = n.as_f64() {
SerdeError::invalid_type(Unexpected::Float(n), &"a positive integer")
} else {
SerdeError::invalid_type(Unexpected::Str(&n.to_string()), &"a positive integer")
}
}),
JsonValue::String(s) => s
.parse()
.map_err(|_| SerdeError::invalid_type(Unexpected::Str(&s), &"a positive integer")),
o => Err(SerdeError::invalid_type(
Unexpected::Other(&o.to_string()),
&"a positive integer",
)),
})
}
pub struct MetadataResponse {
metadata: MetadataData,
size: Option<u64>,
nonce: Vec<u8>,
}
impl<'a> MetadataResponse {
pub fn new(metadata: MetadataData, size: Option<u64>, nonce: Vec<u8>) -> Self {
MetadataResponse {
metadata,
size,
nonce,
}
}
pub fn from(
raw: &RawMetadataResponse,
key_set: &KeySet,
nonce: Vec<u8>,
) -> Result<Self, FailureError> {
Ok(Self::new(raw.decrypt_metadata(key_set)?, raw.size(), nonce))
}
pub fn metadata(&self) -> &MetadataData {
&self.metadata
}
pub fn size(&self) -> u64 {
self.size
.unwrap_or_else(|| self.metadata.size().expect("file size unknown, newer API?"))
}
pub fn nonce(&self) -> &Vec<u8> {
&self.nonce
}
}
#[derive(Fail, Debug)]
pub enum Error {
#[fail(display = "failed to check whether the file exists")]
Exists(#[cause] ExistsError),
#[fail(display = "failed to request file data")]
Request(#[cause] RequestError),
#[fail(display = "the file has expired or did never exist")]
Expired,
#[fail(display = "missing password, password required")]
PasswordRequired,
}
impl From<ExistsError> for Error {
fn from(err: ExistsError) -> Error {
Error::Exists(err)
}
}
impl From<RequestError> for Error {
fn from(err: RequestError) -> Error {
Error::Request(err)
}
}
impl From<MetaError> for Error {
fn from(err: MetaError) -> Error {
Error::Request(RequestError::Meta(err))
}
}
impl From<NonceError> for Error {
fn from(err: NonceError) -> Error {
match err {
NonceError::Expired => Error::Expired,
err => Error::Request(RequestError::Auth(err)),
}
}
}
#[derive(Fail, Debug)]
pub enum RequestError {
#[fail(display = "failed to authenticate")]
Auth(#[cause] NonceError),
#[fail(display = "failed to retrieve file metadata")]
Meta(#[cause] MetaError),
}
#[derive(Fail, Debug)]
pub enum MetaError {
#[fail(display = "failed to compute cryptographic signature")]
ComputeSignature,
#[fail(display = "failed to request metadata nonce")]
NonceRequest,
#[fail(display = "bad response from server while fetching metadata nonce")]
NonceResponse(#[cause] ResponseError),
#[fail(display = "failed to parse the metadata encryption nonce")]
Nonce(#[cause] NonceError),
#[fail(display = "received malformed metadata")]
Malformed,
#[fail(display = "failed to decrypt received metadata")]
Decrypt,
}