summaryrefslogtreecommitdiffstats
path: root/openpgp/src/crypto/backend/cng/hash.rs
blob: e71789d125b005f78733459f2f298b67d2b2ec6b (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use core::convert::{TryFrom, TryInto};
use std::io;
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());
        digest[..l].copy_from_slice(&buffer.as_slice()[..l]);
        Ok(())
    }
}

impl io::Write for Hash {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.update(buf);
        Ok(buf.len())
    }
    fn flush(&mut self) -> io::Result<()> {
        // Do nothing.
        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,

            // SHA3 support is on the horizon, see
            // https://blogs.windows.com/windows-insider/2023/03/23/announcing-windows-11-insider-preview-build-25324/
            HashAlgorithm::SHA3_256 |
            HashAlgorithm::SHA3_512 =>
                return Err(Error::UnsupportedHashAlgorithm(value)),

            HashAlgorithm::SHA224 |
            HashAlgorithm::RipeMD |
            HashAlgorithm::Private(_) |
            HashAlgorithm::Unknown(_) =>
                return Err(Error::UnsupportedHashAlgorithm(value)),
        })
    }
}

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,

            // SHA3 support is on the horizon, see
            // https://blogs.windows.com/windows-insider/2023/03/23/announcing-windows-11-insider-preview-build-25324/
            HashAlgorithm::SHA3_256 |
            HashAlgorithm::SHA3_512 => false,

            HashAlgorithm::SHA224 |
            HashAlgorithm::RipeMD |
            HashAlgorithm::Private(_) |
            HashAlgorithm::Unknown(_) => 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`]: Hash::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",
        ))))
    }
}