picky_krb/crypto/
diffie_hellman.rs1use 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
21fn 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
42fn 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
74pub 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
94fn 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 let out = pow_mod_params(&public_key, &private_key, &p);
111 Ok(out.to_be_bytes().to_vec())
112}
113
114pub 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
126pub 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
145pub 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
151fn 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}