1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
//! Decryptors for age.

use secrecy::SecretString;
use std::io::Read;

use super::{v1_payload_key, Callbacks, NoCallbacks};
use crate::{
    error::Error,
    format::{Header, RecipientLine},
    keys::{FileKey, Identity},
    primitives::{
        armor::ArmoredReader,
        stream::{Stream, StreamReader},
    },
};

struct BaseDecryptor<R: Read> {
    /// The age file.
    input: ArmoredReader<R>,
    /// The age file's header.
    header: Header,
}

impl<R: Read> BaseDecryptor<R> {
    fn obtain_payload_key<F>(&mut self, filter: F) -> Result<[u8; 32], Error>
    where
        F: FnMut(&RecipientLine) -> Option<Result<FileKey, Error>>,
    {
        match &self.header {
            Header::V1(header) => {
                let mut nonce = [0; 16];
                self.input.read_exact(&mut nonce)?;

                header
                    .recipients
                    .iter()
                    .find_map(filter)
                    .unwrap_or(Err(Error::NoMatchingKeys))
                    .and_then(|file_key| v1_payload_key(header, file_key, nonce))
            }
            Header::Unknown(_) => unreachable!(),
        }
    }
}

/// Decryptor for an age file encrypted to a list of recipients.
pub struct RecipientsDecryptor<R: Read>(BaseDecryptor<R>);

impl<R: Read> RecipientsDecryptor<R> {
    pub(super) fn new(input: ArmoredReader<R>, header: Header) -> Self {
        RecipientsDecryptor(BaseDecryptor { input, header })
    }

    /// Attempts to decrypt the age file.
    ///
    /// The decryptor will have no callbacks registered, so it will be unable to use
    /// identities that require e.g. a passphrase to decrypt.
    ///
    /// If successful, returns a reader that will provide the plaintext.
    pub fn decrypt(self, identities: &[Identity]) -> Result<StreamReader<R>, Error> {
        self.decrypt_with_callbacks(identities, &NoCallbacks)
    }

    /// Attempts to decrypt the age file.
    ///
    /// If successful, returns a reader that will provide the plaintext.
    pub fn decrypt_with_callbacks(
        mut self,
        identities: &[Identity],
        callbacks: &dyn Callbacks,
    ) -> Result<StreamReader<R>, Error> {
        self.0
            .obtain_payload_key(|r| {
                identities
                    .iter()
                    .find_map(|key| key.unwrap_file_key(r, callbacks))
            })
            .map(|payload_key| Stream::decrypt(&payload_key, self.0.input))
    }
}

/// Decryptor for an age file encrypted with a passphrase.
pub struct PassphraseDecryptor<R: Read>(BaseDecryptor<R>);

impl<R: Read> PassphraseDecryptor<R> {
    pub(super) fn new(input: ArmoredReader<R>, header: Header) -> Self {
        PassphraseDecryptor(BaseDecryptor { input, header })
    }

    /// Attempts to decrypt the age file.
    ///
    /// `max_work_factor` is the maximum accepted work factor. If `None`, the default
    /// maximum is adjusted to around 16 seconds of work.
    ///
    /// If successful, returns a reader that will provide the plaintext.
    pub fn decrypt(
        mut self,
        passphrase: &SecretString,
        max_work_factor: Option<u8>,
    ) -> Result<StreamReader<R>, Error> {
        self.0
            .obtain_payload_key(|r| {
                if let RecipientLine::Scrypt(s) = r {
                    s.unwrap_file_key(passphrase, max_work_factor).transpose()
                } else {
                    None
                }
            })
            .map(|payload_key| Stream::decrypt(&payload_key, self.0.input))
    }
}