summaryrefslogtreecommitdiffstats
path: root/openpgp/src/crypto/backend/cng/hash.rs
blob: 1f58d4ccfc64b8ef0cccb1c169eb3e0616b8a578 (plain)
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use core::convert::{TryFrom, TryInto};
use std::sync::Mutex;

use crate::crypto::hash::Digest;
use crate::types::HashAlgorithm;
use crate::{Error, Result};

use win_crypto_ng::hash as cng;

struct Hash(Mutex<cng::Hash>);

impl From<cng::Hash> for Hash {
    fn from(h: cng::Hash) -> Self {
        Hash(Mutex::new(h))
    }
}

impl Clone for Hash {
    fn clone(&self) -> Self {
        self.0.lock().expect("Mutex not to be poisoned").clone().into()
    }
}

impl Digest for Hash {
    fn algo(&self) -> HashAlgorithm {
        self.0.lock().expect("Mutex not to be poisoned")
            .hash_algorithm().expect("CNG to not fail internally")
            .try_into()
            .expect("We created the object, algo is representable")
    }

    fn digest_size(&self) -> usize {
        self.0.lock().expect("Mutex not to be poisoned")
            .hash_size().expect("CNG to not fail internally")
    }

    fn update(&mut self, data: &[u8]) {
        let _ = self.0.lock().expect("Mutex not to be poisoned").hash(data);
    }

    fn digest(&mut self, digest: &mut [u8]) -> Result<()> {
        // TODO: Replace with CNG reusable hash objects, supported from Windows 8
        // This would allow us to not re-create the CNG hash object each time we
        // want to finish digest calculation
        let algorithm = self.0.lock().expect("Mutex not to be poisoned")
            .hash_algorithm()
            .expect("CNG hash object to know its algorithm");
        let new = cng::HashAlgorithm::open(algorithm)
            .expect("CNG to open a new correct hash provider")
            .new_hash()
            .expect("Failed to create a new CNG hash object");

        let old = std::mem::replace(
            self.0.get_mut().expect("Mutex not to be poisoned"), new);
        let buffer = old.finish()
            .expect("CNG to not fail internally");

        let l = buffer.len().min(digest.len());
        &mut digest[..l].copy_from_slice(&buffer.as_slice()[..l]);
        Ok(())
    }
}

impl TryFrom<HashAlgorithm> for cng::HashAlgorithmId {
    type Error = Error;

    fn try_from(value: HashAlgorithm) -> std::result::Result<Self, Self::Error> {
        Ok(match value {
            HashAlgorithm::SHA1 => cng::HashAlgorithmId::Sha1,
            HashAlgorithm::SHA256 => cng::HashAlgorithmId::Sha256,
            HashAlgorithm::SHA384 => cng::HashAlgorithmId::Sha384,
            HashAlgorithm::SHA512 => cng::HashAlgorithmId::Sha512,
            HashAlgorithm::MD5 => cng::HashAlgorithmId::Md5,
            algo => Err(Error::UnsupportedHashAlgorithm(algo))?,
        })
    }
}

impl TryFrom<cng::HashAlgorithmId> for HashAlgorithm {
    type Error = Error;

    fn try_from(value: cng::HashAlgorithmId) -> std::result::Result<Self, Self::Error> {
        Ok(match value {
            cng::HashAlgorithmId::Sha1 => HashAlgorithm::SHA1,
            cng::HashAlgorithmId::Sha256 => HashAlgorithm::SHA256,
            cng::HashAlgorithmId::Sha384 => HashAlgorithm::SHA384,
            cng::HashAlgorithmId::Sha512 => HashAlgorithm::SHA512,
            cng::HashAlgorithmId::Md5 => HashAlgorithm::MD5,
            algo => Err(Error::InvalidArgument(
                format!("Algorithm {:?} not representable", algo)))?,
        })
    }
}

impl HashAlgorithm {
    /// Whether Sequoia supports this algorithm.
    pub fn is_supported(self) -> bool {
        match self {
            HashAlgorithm::SHA1 => true,
            HashAlgorithm::SHA256 => true,
            HashAlgorithm::SHA384 => true,
            HashAlgorithm::SHA512 => true,
            HashAlgorithm::MD5 => true,
            _ => false,
        }
    }

    /// Creates a new hash context for this algorithm.
    ///
    /// # Errors
    ///
    /// Fails with `Error::UnsupportedHashAlgorithm` if the selected crypto
    /// backend does not support this algorithm. See
    /// [`HashAlgorithm::is_supported`].
    ///
    ///   [`HashAlgorithm::is_supported`]: #method.is_supported
    pub(crate) fn new_hasher(self) -> Result<Box<dyn Digest>> {
        let algo = cng::HashAlgorithmId::try_from(self)?;
        let algo = cng::HashAlgorithm::open(algo)?;

        Ok(Box::new(Hash::from(algo.new_hash().expect(
            "CNG to always create a hasher object for valid algo",
        ))))
    }
}