Skip to main content

rustls_cng/
signer.rs

1//! SigningKey implementation
2
3use std::sync::Arc;
4
5use rustls::{
6    sign::{Signer, SigningKey},
7    SignatureAlgorithm, SignatureScheme, {Error, OtherError},
8};
9use windows_sys::Win32::Security::Cryptography::{
10    BCryptHash, CryptEncodeObjectEx, BCRYPT_SHA256_ALG_HANDLE, BCRYPT_SHA384_ALG_HANDLE,
11    BCRYPT_SHA512_ALG_HANDLE, CERT_ECC_SIGNATURE, CRYPT_INTEGER_BLOB, X509_ASN_ENCODING,
12    X509_ECC_SIGNATURE,
13};
14
15use crate::key::{AlgorithmGroup, NCryptKey, SignaturePadding};
16
17// Convert an IEEE-P1363 (raw r || s) signature into DER encoding using the Win32 API.
18// CryptEncodeObjectEx with X509_ECC_SIGNATURE produces the DER `SEQUENCE { INTEGER r, INTEGER s }`,
19// taking care of minimal-length and sign-byte padding of the integers.
20fn p1363_to_der(data: &mut [u8]) -> Result<Vec<u8>, Error> {
21    if data.is_empty() || !data.len().is_multiple_of(2) {
22        return Err(Error::General("Invalid signature size".to_owned()));
23    }
24
25    let (r, s) = data.split_at_mut(data.len() / 2);
26
27    // CNG integer blobs are little-endian, so reverse the big-endian halves in place.
28    r.reverse();
29    s.reverse();
30
31    let sig = CERT_ECC_SIGNATURE {
32        r: CRYPT_INTEGER_BLOB {
33            cbData: r.len() as u32,
34            pbData: r.as_mut_ptr(),
35        },
36        s: CRYPT_INTEGER_BLOB {
37            cbData: s.len() as u32,
38            pbData: s.as_mut_ptr(),
39        },
40    };
41    let sig_ptr = std::ptr::from_ref(&sig).cast();
42
43    unsafe {
44        // First call retrieves the required output buffer size.
45        let mut len = 0u32;
46        let status = CryptEncodeObjectEx(
47            X509_ASN_ENCODING,
48            X509_ECC_SIGNATURE,
49            sig_ptr,
50            0,
51            std::ptr::null(),
52            std::ptr::null_mut(),
53            &mut len,
54        );
55        if status == 0 {
56            return Err(Error::General(
57                "CryptEncodeObjectEx failed to size the signature".to_owned(),
58            ));
59        }
60
61        let mut der = vec![0u8; len as usize];
62        let status = CryptEncodeObjectEx(
63            X509_ASN_ENCODING,
64            X509_ECC_SIGNATURE,
65            sig_ptr,
66            0,
67            std::ptr::null(),
68            der.as_mut_ptr().cast(),
69            &mut len,
70        );
71        if status == 0 {
72            return Err(Error::General(
73                "CryptEncodeObjectEx failed to encode the signature".to_owned(),
74            ));
75        }
76
77        der.truncate(len as usize);
78        Ok(der)
79    }
80}
81
82/// Custom implementation of `rustls` SigningKey trait
83#[derive(Debug, Clone)]
84pub struct CngSigningKey {
85    key: NCryptKey,
86    algorithm_group: AlgorithmGroup,
87    bits: u32,
88}
89
90impl CngSigningKey {
91    /// Create an instance from the CNG key
92    pub fn new(key: NCryptKey) -> crate::Result<Self> {
93        let group = key.algorithm_group()?;
94        let bits = key.bits()?;
95        Ok(Self {
96            key,
97            algorithm_group: group,
98            bits,
99        })
100    }
101
102    /// Return a reference to the CNG key
103    pub fn key(&self) -> &NCryptKey {
104        &self.key
105    }
106
107    /// Return algorithm group of the key
108    pub fn algorithm_group(&self) -> AlgorithmGroup {
109        self.algorithm_group
110    }
111
112    /// Return a number of bits in the key material
113    pub fn bits(&self) -> u32 {
114        self.bits
115    }
116
117    /// Return supported signature schemes
118    pub fn supported_schemes(&self) -> &[SignatureScheme] {
119        match self.algorithm_group {
120            AlgorithmGroup::Rsa => &[
121                SignatureScheme::RSA_PKCS1_SHA256,
122                SignatureScheme::RSA_PKCS1_SHA384,
123                SignatureScheme::RSA_PKCS1_SHA512,
124                SignatureScheme::RSA_PSS_SHA256,
125                SignatureScheme::RSA_PSS_SHA384,
126                SignatureScheme::RSA_PSS_SHA512,
127            ],
128            AlgorithmGroup::Ecdsa | AlgorithmGroup::Ecdh => match self.bits {
129                256 => &[SignatureScheme::ECDSA_NISTP256_SHA256],
130                384 => &[SignatureScheme::ECDSA_NISTP384_SHA384],
131                521 => &[SignatureScheme::ECDSA_NISTP521_SHA512],
132                _ => &[],
133            },
134        }
135    }
136}
137
138#[derive(Debug)]
139struct CngSigner {
140    key: NCryptKey,
141    scheme: SignatureScheme,
142}
143
144impl CngSigner {
145    // hash function using BCryptHash function which uses FIPS certified SymCrypt
146    fn hash(&self, message: &[u8]) -> Result<(Vec<u8>, SignaturePadding), Error> {
147        let (alg, padding) = match self.scheme {
148            SignatureScheme::RSA_PKCS1_SHA256 => {
149                (BCRYPT_SHA256_ALG_HANDLE, SignaturePadding::Pkcs1)
150            }
151            SignatureScheme::RSA_PKCS1_SHA384 => {
152                (BCRYPT_SHA384_ALG_HANDLE, SignaturePadding::Pkcs1)
153            }
154            SignatureScheme::RSA_PKCS1_SHA512 => {
155                (BCRYPT_SHA512_ALG_HANDLE, SignaturePadding::Pkcs1)
156            }
157            SignatureScheme::RSA_PSS_SHA256 => (BCRYPT_SHA256_ALG_HANDLE, SignaturePadding::Pss),
158            SignatureScheme::RSA_PSS_SHA384 => (BCRYPT_SHA384_ALG_HANDLE, SignaturePadding::Pss),
159            SignatureScheme::RSA_PSS_SHA512 => (BCRYPT_SHA512_ALG_HANDLE, SignaturePadding::Pss),
160            SignatureScheme::ECDSA_NISTP256_SHA256 => {
161                (BCRYPT_SHA256_ALG_HANDLE, SignaturePadding::None)
162            }
163            SignatureScheme::ECDSA_NISTP384_SHA384 => {
164                (BCRYPT_SHA384_ALG_HANDLE, SignaturePadding::None)
165            }
166            SignatureScheme::ECDSA_NISTP521_SHA512 => {
167                (BCRYPT_SHA512_ALG_HANDLE, SignaturePadding::None)
168            }
169            _ => return Err(Error::General("Unsupported signature scheme".to_owned())),
170        };
171
172        let hash_len = match alg {
173            BCRYPT_SHA256_ALG_HANDLE => 32,
174            BCRYPT_SHA384_ALG_HANDLE => 48,
175            BCRYPT_SHA512_ALG_HANDLE => 64,
176            _ => return Err(Error::General("Unsupported hash algorithm!".to_owned())),
177        };
178
179        let mut hash = vec![0u8; hash_len];
180
181        unsafe {
182            let status = BCryptHash(
183                alg,
184                std::ptr::null_mut(), // pbSecret
185                0,                    // cbSecret
186                message.as_ptr().cast(),
187                message.len() as u32,
188                hash.as_mut_ptr(),
189                hash_len as u32,
190            );
191
192            if status != 0 {
193                return Err(Error::General(format!(
194                    "BCryptHash failed with status: 0x{status:X}"
195                )));
196            }
197        }
198        Ok((hash, padding))
199    }
200}
201
202impl Signer for CngSigner {
203    fn sign(&self, message: &[u8]) -> Result<Vec<u8>, Error> {
204        let (hash, padding) = self.hash(message)?;
205        let mut signature = self
206            .key
207            .sign(&hash, padding)
208            .map_err(|e| Error::Other(OtherError(Arc::new(e))))?;
209
210        if padding == SignaturePadding::None {
211            // For ECDSA keys Windows produces IEEE-P1363 signatures which must be converted to DER format
212            Ok(p1363_to_der(&mut signature)?)
213        } else {
214            Ok(signature)
215        }
216    }
217
218    fn scheme(&self) -> SignatureScheme {
219        self.scheme
220    }
221}
222
223impl SigningKey for CngSigningKey {
224    fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> {
225        let supported = self.supported_schemes();
226        for scheme in offered {
227            if supported.contains(scheme) {
228                return Some(Box::new(CngSigner {
229                    key: self.key.clone(),
230                    scheme: *scheme,
231                }));
232            }
233        }
234        None
235    }
236
237    fn algorithm(&self) -> SignatureAlgorithm {
238        match self.algorithm_group {
239            AlgorithmGroup::Rsa => SignatureAlgorithm::RSA,
240            AlgorithmGroup::Ecdsa | AlgorithmGroup::Ecdh => SignatureAlgorithm::ECDSA,
241        }
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use std::ptr;
248
249    use windows_sys::Win32::Security::Cryptography::{
250        CryptDecodeObjectEx, CERT_ECC_SIGNATURE, CRYPT_INTEGER_BLOB, X509_ASN_ENCODING,
251        X509_ECC_SIGNATURE,
252    };
253
254    // Extract the big-endian magnitude of a CNG integer blob, which is stored in little-endian order.
255    unsafe fn blob_to_be(blob: &CRYPT_INTEGER_BLOB) -> Vec<u8> {
256        let le = unsafe { std::slice::from_raw_parts(blob.pbData, blob.cbData as usize) };
257        let mut be = le.iter().rev().copied().collect::<Vec<u8>>();
258        while be.len() > 1 && be[0] == 0 {
259            be.remove(0);
260        }
261        be
262    }
263
264    // Decode a DER-encoded ECDSA signature via the Win32 API and return the (r, s) integers
265    // as big-endian magnitude byte vectors.
266    fn decode_der(data: &[u8]) -> (Vec<u8>, Vec<u8>) {
267        unsafe {
268            // First call retrieves the required output buffer size.
269            let mut len = 0u32;
270            let status = CryptDecodeObjectEx(
271                X509_ASN_ENCODING,
272                X509_ECC_SIGNATURE,
273                data.as_ptr(),
274                data.len() as u32,
275                0,
276                ptr::null(),
277                ptr::null_mut(),
278                &mut len,
279            );
280            assert_ne!(status, 0, "CryptDecodeObjectEx failed to size the output");
281
282            let mut buf = vec![0u8; len as usize];
283            let status = CryptDecodeObjectEx(
284                X509_ASN_ENCODING,
285                X509_ECC_SIGNATURE,
286                data.as_ptr(),
287                data.len() as u32,
288                0,
289                ptr::null(),
290                buf.as_mut_ptr().cast(),
291                &mut len,
292            );
293            assert_ne!(status, 0, "CryptDecodeObjectEx failed to decode");
294
295            let sig: &CERT_ECC_SIGNATURE = &*buf.as_ptr().cast();
296            (blob_to_be(&sig.r), blob_to_be(&sig.s))
297        }
298    }
299
300    fn validate_der(data: &[u8], r: &[u8], s: &[u8]) {
301        let (parsed_r, parsed_s) = decode_der(data);
302        assert_eq!(parsed_r, r);
303        assert_eq!(parsed_s, s);
304    }
305
306    #[test]
307    fn test_p1363_to_der() {
308        let mut p1363 = [1, 2, 3, 4, 5, 6, 7, 8];
309        let der = super::p1363_to_der(&mut p1363).unwrap();
310        validate_der(&der, &[1, 2, 3, 4], &[5, 6, 7, 8]);
311    }
312
313    #[test]
314    fn test_p1363_to_der_signed() {
315        let mut p1363 = [0x81, 2, 3, 4, 0x85, 6, 7, 8];
316        let der = super::p1363_to_der(&mut p1363).unwrap();
317        validate_der(&der, &[0x81, 2, 3, 4], &[0x85, 6, 7, 8]);
318    }
319
320    #[test]
321    fn test_p1363_to_der_zeroes_stripped() {
322        let mut p1363 = [0, 1, 2, 3, 4, 0, 5, 6, 7, 8];
323        let der = super::p1363_to_der(&mut p1363).unwrap();
324        validate_der(&der, &[1, 2, 3, 4], &[5, 6, 7, 8]);
325    }
326
327    #[test]
328    fn test_p1363_to_der_signed_zeroes_stripped() {
329        let mut p1363 = [0, 0x81, 2, 3, 4, 0, 0x85, 6, 7, 8];
330        let der = super::p1363_to_der(&mut p1363).unwrap();
331        validate_der(&der, &[0x81, 2, 3, 4], &[0x85, 6, 7, 8]);
332    }
333
334    #[test]
335    fn test_p1363_to_der_long() {
336        let r = (1..128).collect::<Vec<u8>>();
337        let s = (128..254).chain([0]).rev().collect::<Vec<u8>>();
338
339        let mut p1363 = r.clone().into_iter().chain(s.clone()).collect::<Vec<u8>>();
340        let der = super::p1363_to_der(&mut p1363).unwrap();
341
342        // The decoded magnitude has the padding zero stripped.
343        let expected_s = (128..254).rev().collect::<Vec<u8>>();
344        validate_der(&der, &r, &expected_s);
345    }
346}