use failure::Error as FailureError;
use openssl::symm::decrypt_aead;
use reqwest::Client;
use reqwest::header::Authorization;
use serde_json;
use api::nonce::{header_nonce, NonceError, request_nonce};
use api::request::{ensure_success, ResponseError};
use api::url::UrlBuilder;
use crypto::b64;
use crypto::key_set::KeySet;
use crypto::sig::signature_encoded;
use file::metadata::Metadata as MetadataData;
use file::remote_file::RemoteFile;
use super::exists::{
Error as ExistsError,
Exists as ExistsAction,
};
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.has_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(
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)]
pub struct RawMetadataResponse {
#[serde(rename = "metadata")]
meta: String,
size: u64,
}
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() - 16);
assert_eq!(tag.len(), 16);
let meta = decrypt_aead(
KeySet::cipher(),
key_set.meta_key().unwrap(),
Some(key_set.iv()),
&[],
encrypted,
&tag,
)?;
Ok(serde_json::from_slice(&meta)?)
}
pub fn size(&self) -> u64 {
self.size
}
}
pub struct MetadataResponse {
metadata: MetadataData,
size: u64,
nonce: Vec<u8>,
}
impl<'a> MetadataResponse {
pub fn new(metadata: MetadataData, size: 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
}
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,
}