themis 0.0.2

High-level cryptographic services for storage and messaging
// Copyright 2018 (c) rust-themis developers
//
// 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.

//! Secure Comparator service.
//!
//! **Secure Comparator** is an implementation of _Zero-Knowledge Proof_-based protocol,
//! built around OTR SMP implementation.

use std::os::raw::c_void;
use std::ptr;

use bindings::{
    secure_comparator_append_secret, secure_comparator_begin_compare, secure_comparator_create,
    secure_comparator_destroy, secure_comparator_get_result, secure_comparator_proceed_compare,
    secure_comparator_t,
};
use error::{Error, ErrorKind, Result};
use utils::into_raw_parts;

/// Secure Comparison context.
pub struct SecureComparator {
    comp_ctx: *mut secure_comparator_t,
}

impl SecureComparator {
    /// Prepares for a new comparison.
    ///
    /// # Panics
    ///
    /// May panic on internal unrecoverable errors (like memory allocation).
    pub fn new() -> Self {
        match SecureComparator::try_new() {
            Ok(comparator) => comparator,
            Err(e) => panic!("secure_comparator_create() failed: {}", e),
        }
    }

    /// Prepares for a new comparison.
    fn try_new() -> Result<Self> {
        let comp_ctx = unsafe { secure_comparator_create() };

        if comp_ctx.is_null() {
            // This function is most likely to fail on memory allocation. Some internal errors
            // in the crypto library are also possible, but unlikely. We have no way to find out.
            return Err(Error::with_kind(ErrorKind::NoMemory));
        }

        Ok(Self { comp_ctx })
    }

    /// Collects the data to be compared.
    ///
    /// Note that there is no way to remove data. If even a single byte is mismatched by the peers
    /// then the comparison will always return `false`. In this case you will need to recreate
    /// a `SecureComparator` to make a new comparison.
    ///
    /// You can use this method between completed comparisons, but not when you're in the middle
    /// of one. That is, [`append_secret`] is safe call either before [`begin_compare`]
    /// or after [`get_result`]. Otherwise it will fail and return an error.
    ///
    /// [`append_secret`]: struct.SecureComparator.html#method.append_secret
    /// [`begin_compare`]: struct.SecureComparator.html#method.begin_compare
    /// [`get_result`]: struct.SecureComparator.html#method.get_result
    pub fn append_secret<S: AsRef<[u8]>>(&mut self, secret: S) -> Result<()> {
        let (secret_ptr, secret_len) = into_raw_parts(secret.as_ref());

        unsafe {
            let status = secure_comparator_append_secret(
                self.comp_ctx,
                secret_ptr as *const c_void,
                secret_len,
            );
            let error = Error::from_compare_status(status);
            if error.kind() != ErrorKind::Success {
                return Err(error);
            }
        }

        Ok(())
    }

    /// Starts comparison on the client returning the first message.
    ///
    /// This method should be called by the client which initiates the comparison. Make sure you
    /// have appended all the data you need before you call this method.
    ///
    /// The resulting message should be transferred to the remote peer and passed to the
    /// [`proceed_compare`] of its `SecureComparator`. The remote peer should have also appended
    /// all the data by this point.
    ///
    /// [`proceed_compare`]: struct.SecureComparator.html#method.proceed_compare
    pub fn begin_compare(&mut self) -> Result<Vec<u8>> {
        let mut compare_data = Vec::new();
        let mut compare_data_len = 0;

        unsafe {
            let status = secure_comparator_begin_compare(
                self.comp_ctx,
                ptr::null_mut(),
                &mut compare_data_len,
            );
            let error = Error::from_compare_status(status);
            if error.kind() != ErrorKind::BufferTooSmall {
                return Err(error);
            }
        }

        compare_data.reserve(compare_data_len);

        unsafe {
            let status = secure_comparator_begin_compare(
                self.comp_ctx,
                compare_data.as_mut_ptr() as *mut c_void,
                &mut compare_data_len,
            );
            let error = Error::from_compare_status(status);
            if error.kind() != ErrorKind::CompareSendOutputToPeer {
                return Err(error);
            }
            debug_assert!(compare_data_len <= compare_data.capacity());
            compare_data.set_len(compare_data_len);
        }

        Ok(compare_data)
    }

    /// Continues comparison process with given message.
    ///
    /// This method should be called by the responding server with a message received from the
    /// client. It returns another message which should be passed back to the client and put
    /// into its [`proceed_compare`] method (that is, this method again). The client then should
    /// do the same. The process repeats at both sides until [`is_complete`] signals that the
    /// comparison is complete.
    ///
    /// Both peers should have appended all the compared data before using this method, and no
    /// additional data should be appended while the comparison is underway.
    ///
    /// [`proceed_compare`]: struct.SecureComparator.html#method.proceed_compare
    /// [`is_complete`]: struct.SecureComparator.html#method.is_complete
    pub fn proceed_compare<D: AsRef<[u8]>>(&mut self, peer_data: D) -> Result<Vec<u8>> {
        let (peer_compare_data_ptr, peer_compare_data_len) = into_raw_parts(peer_data.as_ref());

        let mut compare_data = Vec::new();
        let mut compare_data_len = 0;

        unsafe {
            let status = secure_comparator_proceed_compare(
                self.comp_ctx,
                peer_compare_data_ptr as *const c_void,
                peer_compare_data_len,
                ptr::null_mut(),
                &mut compare_data_len,
            );
            let error = Error::from_compare_status(status);
            if error.kind() != ErrorKind::BufferTooSmall {
                return Err(error);
            }
        }

        compare_data.reserve(compare_data_len);

        unsafe {
            let status = secure_comparator_proceed_compare(
                self.comp_ctx,
                peer_compare_data_ptr as *const c_void,
                peer_compare_data_len,
                compare_data.as_mut_ptr() as *mut c_void,
                &mut compare_data_len,
            );
            let error = Error::from_compare_status(status);
            match error.kind() {
                ErrorKind::CompareSendOutputToPeer => {}
                // TODO: signal that this does not need to be sent
                ErrorKind::Success => {}
                _ => {
                    return Err(error);
                }
            }
            debug_assert!(compare_data_len <= compare_data.capacity());
            compare_data.set_len(compare_data_len);
        }

        Ok(compare_data)
    }

    /// Returns the result of comparison.
    ///
    /// Let it be a surprise: `true` if data has been found equal on both peers, `false` otherwise.
    /// Or an error if you call this method too early.
    pub fn get_result(&self) -> Result<bool> {
        let status = unsafe { secure_comparator_get_result(self.comp_ctx) };
        let error = Error::from_match_status(status);
        match error.kind() {
            ErrorKind::CompareMatch => Ok(true),
            ErrorKind::CompareNoMatch => Ok(false),
            _ => Err(error),
        }
    }

    /// Checks if this comparison is complete.
    ///
    /// Comparison that failed irrecoverably due to an error is also considered complete.
    pub fn is_complete(&self) -> bool {
        match self.get_result() {
            Err(ref e) if e.kind() == ErrorKind::CompareNotReady => false,
            _ => true,
        }
    }
}

impl Default for SecureComparator {
    fn default() -> Self {
        SecureComparator::new()
    }
}

#[doc(hidden)]
impl Drop for SecureComparator {
    fn drop(&mut self) {
        unsafe {
            let status = secure_comparator_destroy(self.comp_ctx);
            let error = Error::from_themis_status(status);
            if (cfg!(debug) || cfg!(test)) && error.kind() != ErrorKind::Success {
                panic!("secure_comparator_destroy() failed: {}", error);
            }
        }
    }
}