Lower tor-proto::util::ct::lookup to tor-llcrypto

This is mostly code movement; you may want to review it with
`--color-moved`.

I'm doing this so we can also use the function in netdoc for
looking up hsdesc authentication.
This commit is contained in:
Nick Mathewson 2023-06-14 12:17:39 -04:00
parent a20c3eda6e
commit 25db56777c
5 changed files with 72 additions and 59 deletions

View File

@ -0,0 +1 @@
ADDED: ct_lookup

View File

@ -87,8 +87,55 @@ impl<const N: usize> AsMut<[u8; N]> for CtByteArray<N> {
}
}
/// Try to find an item in a slice without leaking where and whether the
/// item was found.
///
/// If there is any item `x` in the `array` for which `matches(x)`
/// is true, this function will return a reference to one such
/// item. (We don't specify which.)
///
/// Otherwise, this function returns none.
///
/// We evaluate `matches` on every item of the array, and try not to
/// leak by timing which element (if any) matched.
///
/// Note that this doesn't necessarily do a constant-time comparison,
/// and that it is not constant-time for found/not-found case.
pub fn ct_lookup<T, F>(array: &[T], matches: F) -> Option<&T>
where
F: Fn(&T) -> Choice,
{
// ConditionallySelectable isn't implemented for usize, so we need
// to use u64.
let mut idx: u64 = 0;
let mut found: Choice = 0.into();
for (i, x) in array.iter().enumerate() {
let equal = matches(x);
idx.conditional_assign(&(i as u64), equal);
found.conditional_assign(&equal, equal);
}
if found.into() {
Some(&array[idx as usize])
} else {
None
}
}
#[cfg(test)]
mod test {
// @@ begin test lint list maintained by maint/add_warning @@
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
use super::*;
use rand::Rng;
use tor_basic_utils::test_rng;
@ -122,4 +169,23 @@ mod test {
}
}
}
#[test]
fn test_lookup() {
use super::ct_lookup as lookup;
use subtle::ConstantTimeEq;
let items = vec![
"One".to_string(),
"word".to_string(),
"of".to_string(),
"every".to_string(),
"length".to_string(),
];
let of_word = lookup(&items[..], |i| i.len().ct_eq(&2));
let every_word = lookup(&items[..], |i| i.len().ct_eq(&5));
let no_word = lookup(&items[..], |i| i.len().ct_eq(&99));
assert_eq!(of_word.unwrap(), "of");
assert_eq!(every_word.unwrap(), "every");
assert_eq!(no_word, None);
}
}

View File

@ -8,6 +8,7 @@ use tor_error::into_internal;
use tor_llcrypto::d;
use tor_llcrypto::pk::curve25519::*;
use tor_llcrypto::pk::rsa::RsaIdentity;
use tor_llcrypto::util::ct::ct_lookup;
use digest::Mac;
use rand_core::{CryptoRng, RngCore};
@ -285,7 +286,7 @@ where
let my_key: PublicKey = cur.extract()?;
let their_pk: PublicKey = cur.extract()?;
let keypair = ct::lookup(keys, |key| key.matches_pk(&my_key));
let keypair = ct_lookup(keys, |key| key.matches_pk(&my_key));
let keypair = match keypair {
Some(k) => k,
None => return Err(RelayHandshakeError::MissingKey),

View File

@ -19,7 +19,7 @@ use tor_bytes::{EncodeResult, Reader, SecretBuf, Writeable, Writer};
use tor_error::into_internal;
use tor_llcrypto::d::{Sha3_256, Shake256};
use tor_llcrypto::pk::{curve25519, ed25519::Ed25519Identity};
use tor_llcrypto::util::rand_compat::RngCompatExt;
use tor_llcrypto::util::{ct::ct_lookup, rand_compat::RngCompatExt};
use cipher::{KeyIvInit, StreamCipher};
@ -444,7 +444,7 @@ fn server_handshake_ntor_v3_no_keygen<REPLY: MsgReply>(
r.should_be_exhausted()?;
// See if we recognize the provided (id,requested_pk) pair.
let keypair = ct::lookup(keys, |key| key.matches(id, requested_pk));
let keypair = ct_lookup(keys, |key| key.matches(id, requested_pk));
let keypair = match keypair {
Some(k) => k,
None => return Err(RelayHandshakeError::MissingKey),

View File

@ -1,41 +1,5 @@
//! Constant-time utilities.
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
/// Try to find an item in a slice without leaking where and whether the
/// item was found.
///
/// If there is any item `x` in the `array` for which `matches(x)`
/// is true, this function will return a reference to one such
/// item. (We don't specify which.)
///
/// Otherwise, this function returns none.
///
/// We evaluate `matches` on every item of the array, and try not to
/// leak by timing which element (if any) matched.
///
/// Note that this doesn't necessarily do a constant-time comparison,
/// and that it is not constant-time for found/not-found case.
pub(crate) fn lookup<T, F>(array: &[T], matches: F) -> Option<&T>
where
F: Fn(&T) -> Choice,
{
// ConditionallySelectable isn't implemented for usize, so we need
// to use u64.
let mut idx: u64 = 0;
let mut found: Choice = 0.into();
for (i, x) in array.iter().enumerate() {
let equal = matches(x);
idx.conditional_assign(&(i as u64), equal);
found.conditional_assign(&equal, equal);
}
if found.into() {
Some(&array[idx as usize])
} else {
None
}
}
use subtle::{Choice, ConstantTimeEq};
/// Convert a boolean into a Choice.
///
@ -72,23 +36,4 @@ mod test {
assert!(!bytes_eq(&b"hi"[..], &b"45"[..]));
assert!(bytes_eq(&b""[..], &b""[..]));
}
#[test]
fn test_lookup() {
use super::lookup;
use subtle::ConstantTimeEq;
let items = vec![
"One".to_string(),
"word".to_string(),
"of".to_string(),
"every".to_string(),
"length".to_string(),
];
let of_word = lookup(&items[..], |i| i.len().ct_eq(&2));
let every_word = lookup(&items[..], |i| i.len().ct_eq(&5));
let no_word = lookup(&items[..], |i| i.len().ct_eq(&99));
assert_eq!(of_word.unwrap(), "of");
assert_eq!(every_word.unwrap(), "every");
assert_eq!(no_word, None);
}
}