picky_krb/crypto/
diffie_hellman.rs

1use crypto_bigint::modular::{BoxedMontyForm, BoxedMontyParams};
2use crypto_bigint::{BoxedUint, Odd, RandomBits, Resize};
3use rand::TryCryptoRng;
4use sha1::{Digest, Sha1};
5use thiserror::Error;
6
7use crate::crypto::Cipher;
8
9#[derive(Error, Debug)]
10pub enum DiffieHellmanError {
11    #[error("Invalid bit len: {0}")]
12    BitLen(String),
13    #[error("Invalid data len: expected at least {0} but got {1}.")]
14    DataLen(usize, usize),
15    #[error("modulus is not odd")]
16    ModulusIsNotOdd,
17}
18
19pub type DiffieHellmanResult<T> = Result<T, DiffieHellmanError>;
20
21/// [Using Diffie-Hellman Key Exchange](https://www.rfc-editor.org/rfc/rfc4556.html#section-3.2.3.1)
22/// K-truncate truncates its input to the first K bits
23fn k_truncate(k: usize, mut data: Vec<u8>) -> DiffieHellmanResult<Vec<u8>> {
24    if k % 8 != 0 {
25        return Err(DiffieHellmanError::BitLen(format!(
26            "Seed bit len must be a multiple of 8. Got: {}",
27            k
28        )));
29    }
30
31    let bytes_len = k / 8;
32
33    if bytes_len > data.len() {
34        return Err(DiffieHellmanError::DataLen(bytes_len, data.len()));
35    }
36
37    data.resize(bytes_len, 0);
38
39    Ok(data)
40}
41
42/// [Using Diffie-Hellman Key Exchange](https://www.rfc-editor.org/rfc/rfc4556.html#section-3.2.3.1)
43/// octetstring2key(x) == random-to-key(K-truncate(
44///                          SHA1(0x00 | x) |
45///                          SHA1(0x01 | x) |
46///                          SHA1(0x02 | x) |
47///                          ...
48///                          ))
49fn octet_string_to_key(x: &[u8], cipher: &dyn Cipher) -> DiffieHellmanResult<Vec<u8>> {
50    let seed_len = cipher.seed_bit_len() / 8;
51
52    let mut key = Vec::new();
53
54    let mut i = 0;
55    while key.len() < seed_len {
56        let mut data = vec![i];
57        data.extend_from_slice(x);
58
59        let mut sha1 = Sha1::new();
60        sha1.update(data);
61
62        key.extend_from_slice(sha1.finalize().as_slice());
63        i += 1;
64    }
65
66    Ok(cipher.random_to_key(k_truncate(seed_len * 8, key)?))
67}
68
69pub struct DhNonce<'a> {
70    pub client_nonce: &'a [u8],
71    pub server_nonce: &'a [u8],
72}
73
74/// [Using Diffie-Hellman Key Exchange](https://www.rfc-editor.org/rfc/rfc4556.html#section-3.2.3.1)
75/// let n_c be the clientDHNonce and n_k be the serverDHNonce; otherwise, let both n_c and n_k be empty octet strings.
76/// k = octetstring2key(DHSharedSecret | n_c | n_k)
77pub fn generate_key_from_shared_secret(
78    mut dh_shared_secret: Vec<u8>,
79    nonce: Option<DhNonce>,
80    cipher: &dyn Cipher,
81) -> DiffieHellmanResult<Vec<u8>> {
82    if let Some(DhNonce {
83        client_nonce,
84        server_nonce,
85    }) = nonce
86    {
87        dh_shared_secret.extend_from_slice(client_nonce);
88        dh_shared_secret.extend_from_slice(server_nonce);
89    }
90
91    octet_string_to_key(&dh_shared_secret, cipher)
92}
93
94/// [Using Diffie-Hellman Key Exchange](https://www.rfc-editor.org/rfc/rfc4556.html#section-3.2.3.1)
95/// let DHSharedSecret be the shared secret value. DHSharedSecret is the value ZZ
96///
97/// [Generation of ZZ](https://www.rfc-editor.org/rfc/rfc2631#section-2.1.1)
98/// ZZ = g ^ (xb * xa) mod p
99/// ZZ = (yb ^ xa)  mod p  = (ya ^ xb)  mod p
100/// where ^ denotes exponentiation
101fn generate_dh_shared_secret(public_key: &[u8], private_key: &[u8], p: &[u8]) -> DiffieHellmanResult<Vec<u8>> {
102    let public_key = BoxedUint::from_be_slice_vartime(public_key);
103    let private_key = BoxedUint::from_be_slice_vartime(private_key);
104    let p = Odd::new(BoxedUint::from_be_slice_vartime(p))
105        .into_option()
106        .ok_or(DiffieHellmanError::ModulusIsNotOdd)?;
107    let p = BoxedMontyParams::new_vartime(p);
108
109    // ZZ = (public_key ^ private_key) mod p
110    let out = pow_mod_params(&public_key, &private_key, &p);
111    Ok(out.to_be_bytes().to_vec())
112}
113
114//= [Using Diffie-Hellman Key Exchange](https://www.rfc-editor.org/rfc/rfc4556.html#section-3.2.3.1) =//
115pub fn generate_key(
116    public_key: &[u8],
117    private_key: &[u8],
118    modulus: &[u8],
119    nonce: Option<DhNonce>,
120    cipher: &dyn Cipher,
121) -> DiffieHellmanResult<Vec<u8>> {
122    let dh_shared_secret = generate_dh_shared_secret(public_key, private_key, modulus)?;
123    generate_key_from_shared_secret(dh_shared_secret, nonce, cipher)
124}
125
126/// [Key and Parameter Requirements](https://www.rfc-editor.org/rfc/rfc2631#section-2.2)
127/// X9.42 requires that the private key x be in the interval [2, (q - 2)]
128pub fn generate_private_key<R: TryCryptoRng>(q: &[u8], rng: &mut R) -> Result<Vec<u8>, R::Error> {
129    let q = BoxedUint::from_be_slice_vartime(q);
130    let low_bound = BoxedUint::from_be_slice_vartime(&[2]);
131    let high_bound = q - 1_u32;
132
133    let min_bits = low_bound.bits();
134    let max_bits = high_bound.bits();
135    loop {
136        let bit_length = rng.try_next_u32()? % (max_bits - min_bits) + min_bits;
137        let x = BoxedUint::random_bits(rng, bit_length);
138
139        if (&low_bound..&high_bound).contains(&&x) {
140            return Ok(x.to_be_bytes().into_vec());
141        }
142    }
143}
144
145/// [Key and Parameter Requirements](https://www.rfc-editor.org/rfc/rfc2631#section-2.2)
146/// y is then computed by calculating g^x mod p.
147pub fn compute_public_key(private_key: &[u8], modulus: &[u8], base: &[u8]) -> DiffieHellmanResult<Vec<u8>> {
148    generate_dh_shared_secret(base, private_key, modulus)
149}
150
151// Copied from `rsa` crate: https://github.com/RustCrypto/RSA/blob/eb1cca7b7ea42445dc874c1c1ce38873e4adade7/src/algorithms/rsa.rs#L232-L241
152fn pow_mod_params(base: &BoxedUint, exp: &BoxedUint, n_params: &BoxedMontyParams) -> BoxedUint {
153    let base = reduce_vartime(base, n_params);
154    base.pow(exp).retrieve()
155}
156
157fn reduce_vartime(n: &BoxedUint, p: &BoxedMontyParams) -> BoxedMontyForm {
158    let modulus = p.modulus().as_nz_ref().clone();
159    let n_reduced = n.rem_vartime(&modulus).resize_unchecked(p.bits_precision());
160    BoxedMontyForm::new(n_reduced, p.clone())
161}