blob: 70db4a305227100bdbe3b056b5fc5b0a0419eadc [file] [log] [blame]
#![allow(missing_docs)]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub(crate) use crate::proto_adapter::{
CipherCommitment, ClientFinished, ClientInit, GenericPublicKey, HandshakeCipher,
IntoAdapter as _, ServerInit, ToWrappedMessage as _,
};
use crypto_provider::elliptic_curve::EphemeralSecret;
use crypto_provider::p256::{P256EcdhProvider, P256PublicKey, P256};
use crypto_provider::x25519::X25519;
use crypto_provider::CryptoProvider;
use crypto_provider::{
elliptic_curve::{EcdhProvider, PublicKey},
hkdf::Hkdf,
sha2::{Sha256, Sha512},
CryptoRng,
};
use std::{
collections::hash_set,
fmt::{self, Formatter},
marker::PhantomData,
};
use ukey2_proto::protobuf::Message;
use ukey2_proto::ukey2_all_proto::{securemessage, ukey};
pub trait WireCompatibilityLayer {
fn encode_public_key<C: CryptoProvider>(
&self,
key: Vec<u8>,
cipher: HandshakeCipher,
) -> Option<Vec<u8>>;
fn decode_public_key<C: CryptoProvider>(
&self,
key: Vec<u8>,
cipher: HandshakeCipher,
) -> Option<Vec<u8>>;
}
#[derive(Clone)]
pub enum HandshakeImplementation {
/// Implementation of ukey2 exchange handshake according to the specs in
/// <https://github.com/google/ukey2/blob/master/README.md>.
///
/// In particular, when encoding for the P256 public key, this uses the standardized encoding
/// described in [SEC 1](https://www.secg.org/sec1-v2.pdf).
///
/// For X25519, the public key is the x-coordinate in little endian per RFC 7748.
Spec,
/// Implementation of ukey2 exchange handshake that matches
/// [the Java implementation](https://github.com/google/ukey2/blob/master/src/main/java/com/google/security/cryptauth/lib/securegcm/Ukey2Handshake.java),
/// but different from what the specs says.
///
/// In particular, when encoding for the P256 curve, the public key is represented as serialized
/// bytes of the following proto:
/// ```text
/// message EcP256PublicKey {
/// // x and y are encoded in big-endian two's complement (slightly wasteful)
/// // Client MUST verify (x,y) is a valid point on NIST P256
/// required bytes x = 1;
/// required bytes y = 2;
/// }
/// ```
///
/// Encoding for X25519 is not supported in this mode.
PublicKeyInProtobuf,
}
impl WireCompatibilityLayer for HandshakeImplementation {
fn encode_public_key<C: CryptoProvider>(
&self,
key: Vec<u8>,
cipher: HandshakeCipher,
) -> Option<Vec<u8>> {
match self {
HandshakeImplementation::Spec => Some(key),
HandshakeImplementation::PublicKeyInProtobuf => match cipher {
HandshakeCipher::P256Sha512 => {
let p256_key =
<C::P256 as P256EcdhProvider>::PublicKey::from_bytes(key.as_slice())
.unwrap();
let (x, y) = p256_key.to_affine_coordinates().unwrap();
let bigboi_x = num_bigint::BigInt::from_biguint(
num_bigint::Sign::Plus,
num_bigint::BigUint::from_bytes_be(x.to_vec().as_slice()),
);
let bigboi_y = num_bigint::BigInt::from_biguint(
num_bigint::Sign::Plus,
num_bigint::BigUint::from_bytes_be(y.to_vec().as_slice()),
);
let proto_key = securemessage::EcP256PublicKey {
x: Some(bigboi_x.to_signed_bytes_be()),
y: Some(bigboi_y.to_signed_bytes_be()),
..Default::default()
};
let key = securemessage::GenericPublicKey {
type_: Some(securemessage::PublicKeyType::EC_P256.into()),
ec_p256_public_key: Some(proto_key).into(),
..Default::default()
};
key.write_to_bytes().ok()
}
HandshakeCipher::Curve25519Sha512 => None,
},
}
}
fn decode_public_key<C: CryptoProvider>(
&self,
key: Vec<u8>,
cipher: HandshakeCipher,
) -> Option<Vec<u8>> {
match self {
HandshakeImplementation::Spec => Some(key),
HandshakeImplementation::PublicKeyInProtobuf => {
// key will be wrapped in a genericpublickey
let public_key: GenericPublicKey<C> =
securemessage::GenericPublicKey::parse_from_bytes(key.as_slice())
.ok()?
.into_adapter()
.ok()?;
match public_key {
GenericPublicKey::Ec256(key) => {
debug_assert_eq!(cipher, HandshakeCipher::P256Sha512);
Some(key.to_bytes().to_vec())
}
}
}
}
}
}
pub struct Ukey2ServerStage1<C: CryptoProvider> {
pub(crate) next_protocols: hash_set::HashSet<String>,
pub(crate) handshake_impl: HandshakeImplementation,
_marker: PhantomData<C>,
}
impl<C: CryptoProvider> fmt::Debug for Ukey2ServerStage1<C> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Ukey2ServerS1")
}
}
impl<C: CryptoProvider> Ukey2ServerStage1<C> {
pub fn from(
next_protocols: hash_set::HashSet<String>,
handshake_impl: HandshakeImplementation,
) -> Self {
Self { next_protocols, handshake_impl, _marker: PhantomData }
}
pub(crate) fn handle_client_init<R: rand::Rng + rand::CryptoRng>(
self,
rng: &mut R,
client_init: ClientInit,
client_init_msg_bytes: Vec<u8>,
) -> Result<Ukey2ServerStage2<C>, ClientInitError> {
if client_init.version() != &1 {
return Err(ClientInitError::BadVersion);
}
let next_protocol = client_init.next_protocol();
if !self.next_protocols.contains(next_protocol) {
return Err(ClientInitError::BadNextProtocol);
}
// nothing to check here about client_init.random -- already been validated as 32 bytes
// all cipher types are supported, so no BAD_HANDSHAKE_CIPHER case
let commitment = client_init
.commitments()
.iter()
// we want to get the first matching cipher, but max_by_key returns the last max,
// so iterate in reverse direction
.rev()
// proto enum uses the priority as the numeric value
.max_by_key(|c| c.cipher().as_proto() as i32)
.ok_or(ClientInitError::BadHandshakeCipher)?;
match *commitment.cipher() {
// pick in priority order
HandshakeCipher::Curve25519Sha512 => {
let secret = ServerKeyPair::Curve25519(
<C::X25519 as EcdhProvider<X25519>>::EphemeralSecret::generate_random(&mut
<<<C::X25519 as EcdhProvider<X25519>>::EphemeralSecret as EphemeralSecret<
X25519,
>>::Rng as CryptoRng>::new(),
),
);
Ok(Ukey2ServerStage2::from(
&mut *rng,
client_init_msg_bytes,
commitment.clone(),
secret,
self.handshake_impl,
next_protocol.to_string(),
))
}
HandshakeCipher::P256Sha512 => {
let secret = ServerKeyPair::P256(
<C::P256 as EcdhProvider<P256>>::EphemeralSecret::generate_random(
&mut<<<C::P256 as EcdhProvider<P256>>::EphemeralSecret as EphemeralSecret<
P256,
>>::Rng as CryptoRng>::new(),
),
);
Ok(Ukey2ServerStage2::from(
&mut *rng,
client_init_msg_bytes,
commitment.clone(),
secret,
self.handshake_impl,
next_protocol.to_string(),
))
}
}
}
}
enum ServerKeyPair<C: CryptoProvider> {
Curve25519(<C::X25519 as EcdhProvider<X25519>>::EphemeralSecret),
P256(<C::P256 as EcdhProvider<P256>>::EphemeralSecret),
}
pub struct Ukey2ServerStage2<C: CryptoProvider> {
client_init_msg: Vec<u8>,
server_init_msg: Vec<u8>,
commitment: CipherCommitment,
key_pair: ServerKeyPair<C>,
pub(crate) handshake_impl: HandshakeImplementation,
next_protocol: String,
_marker: PhantomData<C>,
}
impl<C: CryptoProvider> fmt::Debug for Ukey2ServerStage2<C> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Ukey2ServerS2")
}
}
const HKDF_SALT_AUTH: &[u8] = b"UKEY2 v1 auth";
const HKDF_SALT_NEXT: &[u8] = b"UKEY2 v1 next";
impl<C: CryptoProvider> Ukey2ServerStage2<C> {
fn from<R: rand::Rng + rand::CryptoRng>(
rng: &mut R,
client_init_msg: Vec<u8>,
commitment: CipherCommitment,
key_pair: ServerKeyPair<C>,
handshake_impl: HandshakeImplementation,
next_protocol: String,
) -> Self {
let random: [u8; 32] = rng.gen();
let mut server_init = ukey::Ukey2ServerInit::default();
server_init.set_version(1);
server_init.set_random(random.to_vec());
server_init.set_handshake_cipher(commitment.cipher().as_proto());
server_init.set_public_key(match &key_pair {
ServerKeyPair::Curve25519(es) => es.public_key_bytes().as_ref().to_vec(),
ServerKeyPair::P256(es) => handshake_impl
.encode_public_key::<C>(
es.public_key_bytes().as_ref().to_vec(),
HandshakeCipher::P256Sha512,
)
.unwrap(),
});
Self {
client_init_msg,
server_init_msg: server_init.to_wrapped_msg().write_to_bytes().unwrap(),
commitment,
key_pair,
handshake_impl,
next_protocol,
_marker: PhantomData,
}
}
pub fn server_init_msg(&self) -> &[u8] {
&self.server_init_msg
}
pub(crate) fn handle_client_finished_msg(
self,
msg: ClientFinished,
client_finished_msg_bytes: &[u8],
) -> Result<Ukey2Server, ClientFinishedError> {
let hash_bytes = C::Sha512::sha512(client_finished_msg_bytes);
// must be constant time to avoid timing attack on hash equality
if C::constant_time_eq(hash_bytes.as_slice(), self.commitment.commitment()) {
// handshake is complete
// independently derive shared DH key
let shared_secret_bytes = match self.key_pair {
ServerKeyPair::Curve25519(es) => {
let buf = msg.public_key.into_iter().collect::<Vec<u8>>();
let public_key: [u8; 32] =
(&buf[..]).try_into().map_err(|_| ClientFinishedError::BadEd25519Key)?;
es.diffie_hellman(
&<C::X25519 as EcdhProvider<X25519>>::PublicKey::from_bytes(&public_key)
.map_err(|_| ClientFinishedError::BadEd25519Key)?,
)
.map_err(|_| ClientFinishedError::BadKeyExchange)?
.into()
}
ServerKeyPair::P256(es) => {
let other_public_key =
&<C::P256 as P256EcdhProvider>::PublicKey::from_sec1_bytes(
self.handshake_impl
.decode_public_key::<C>(msg.public_key, HandshakeCipher::P256Sha512)
.ok_or(ClientFinishedError::BadP256Key)?
.as_slice(),
)
.map_err(|_| ClientFinishedError::BadP256Key)?;
es.diffie_hellman(other_public_key)
.map_err(|_| ClientFinishedError::BadKeyExchange)?
.into()
}
};
let shared_secret_sha256 = C::Sha256::sha256(&shared_secret_bytes).to_vec();
Ok(Ukey2Server {
completed_handshake: CompletedHandshake::new(
self.client_init_msg,
self.server_init_msg,
shared_secret_sha256,
self.next_protocol,
),
})
} else {
Err(ClientFinishedError::UnknownCommitment)
}
}
}
/// Representation of the UKEY2 server information after the handshake has been completed. An
/// instance of this can be created by going through the handshake state machine (starting from
/// [`Ukey2ServerStage1`]).
pub struct Ukey2Server {
completed_handshake: CompletedHandshake,
}
impl fmt::Debug for Ukey2Server {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Ukey2Server")
}
}
impl Ukey2Server {
pub fn completed_handshake(&self) -> &CompletedHandshake {
&self.completed_handshake
}
}
pub struct Ukey2ClientStage1<C: CryptoProvider> {
curve25519_secret: <C::X25519 as EcdhProvider<X25519>>::EphemeralSecret,
p256_secret: <C::P256 as EcdhProvider<P256>>::EphemeralSecret,
curve25519_client_finished_bytes: Vec<u8>,
p256_client_finished_bytes: Vec<u8>,
client_init_bytes: Vec<u8>,
commitment_ciphers: Vec<HandshakeCipher>,
handshake_impl: HandshakeImplementation,
next_protocol: String,
_marker: PhantomData<C>,
}
impl<C: CryptoProvider> fmt::Debug for Ukey2ClientStage1<C> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Ukey2Client1")
}
}
impl<C: CryptoProvider> Ukey2ClientStage1<C> {
pub fn from<R: rand::Rng + rand::SeedableRng + rand::CryptoRng>(
rng: &mut R,
next_protocol: String,
handshake_impl: HandshakeImplementation,
) -> Self {
let random = rng.gen::<[u8; 32]>().to_vec();
// Curve25519 ClientFinished Message
let curve25519_secret =
<C::X25519 as EcdhProvider<X25519>>::EphemeralSecret::generate_random(
&mut <<<C::X25519 as EcdhProvider<X25519>>::EphemeralSecret as EphemeralSecret<
X25519,
>>::Rng as CryptoRng>::new(),
);
let curve25519_client_finished_bytes = {
let client_finished = ukey::Ukey2ClientFinished {
public_key: Some(curve25519_secret.public_key_bytes().as_ref().to_vec()),
..Default::default()
};
client_finished.to_wrapped_msg().write_to_bytes().unwrap()
};
let curve25519_client_finished_hash =
C::Sha512::sha512(&curve25519_client_finished_bytes).to_vec();
// P256 ClientFinished Message
let p256_secret = <C::P256 as EcdhProvider<P256>>::EphemeralSecret::generate_random(
&mut<<<C::P256 as EcdhProvider<P256>>::EphemeralSecret as EphemeralSecret<
P256,
>>::Rng as CryptoRng>::new(),
);
let p256_client_finished_bytes = {
let client_finished = ukey::Ukey2ClientFinished {
public_key: Some(
handshake_impl
.encode_public_key::<C>(
p256_secret.public_key_bytes().as_ref().to_vec(),
HandshakeCipher::P256Sha512,
)
.expect("Output of p256_secret.public_key_bytes should always be valid input for encode_public_key"),
),
..Default::default()
};
client_finished.to_wrapped_msg().write_to_bytes().unwrap()
};
let p256_client_finished_hash = C::Sha512::sha512(&p256_client_finished_bytes).to_vec();
// ClientInit Message
let client_init_bytes = {
let curve25519_commitment = ukey::ukey2client_init::CipherCommitment {
handshake_cipher: Some(HandshakeCipher::Curve25519Sha512.as_proto().into()),
commitment: Some(curve25519_client_finished_hash),
..Default::default()
};
let p256_commitment = ukey::ukey2client_init::CipherCommitment {
handshake_cipher: Some(HandshakeCipher::P256Sha512.as_proto().into()),
commitment: Some(p256_client_finished_hash),
..Default::default()
};
let client_init = ukey::Ukey2ClientInit {
version: Some(1),
random: Some(random),
cipher_commitments: vec![curve25519_commitment, p256_commitment],
next_protocol: Some(next_protocol.to_string()),
..Default::default()
};
client_init.to_wrapped_msg().write_to_bytes().unwrap()
};
Self {
curve25519_secret,
p256_secret,
curve25519_client_finished_bytes,
p256_client_finished_bytes,
client_init_bytes,
commitment_ciphers: vec![
HandshakeCipher::Curve25519Sha512,
HandshakeCipher::P256Sha512,
],
handshake_impl,
next_protocol,
_marker: PhantomData,
}
}
pub fn client_init_msg(&self) -> &[u8] {
&self.client_init_bytes
}
pub(crate) fn handle_server_init(
self,
server_init: ServerInit,
server_init_bytes: Vec<u8>,
) -> Result<Ukey2Client, ServerInitError> {
if server_init.version() != &1 {
return Err(ServerInitError::BadVersion);
}
// loop over all commitments every time for a semblance of constant time-ness
let server_cipher = self
.commitment_ciphers
.iter()
.fold(None, |accum, c| {
if server_init.handshake_cipher() == c {
match accum {
None => Some(c),
Some(_) => accum,
}
} else {
accum
}
})
.ok_or(ServerInitError::BadHandshakeCipher)?;
let (server_shared_secret, client_finished_bytes) = match server_cipher {
HandshakeCipher::P256Sha512 => {
let other_public_key = &<C::P256 as P256EcdhProvider>::PublicKey::from_sec1_bytes(
self.handshake_impl
.decode_public_key::<C>(
server_init.public_key.to_vec(),
HandshakeCipher::P256Sha512,
)
.ok_or(ServerInitError::BadPublicKey)?
.as_slice(),
)
.map_err(|_| ServerInitError::BadPublicKey)?;
let shared_secret = self
.p256_secret
.diffie_hellman(other_public_key)
.map_err(|_| ServerInitError::BadKeyExchange)?;
let shared_secret_bytes: [u8; 32] = shared_secret.into();
(shared_secret_bytes, self.p256_client_finished_bytes)
}
HandshakeCipher::Curve25519Sha512 => {
let pub_key: [u8; 32] =
server_init.public_key.try_into().map_err(|_| ServerInitError::BadPublicKey)?;
(
self.curve25519_secret
.diffie_hellman(
&<C::X25519 as EcdhProvider<X25519>>::PublicKey::from_bytes(&pub_key)
.map_err(|_| ServerInitError::BadPublicKey)?,
)
.map_err(|_| ServerInitError::BadKeyExchange)?
.into(),
self.curve25519_client_finished_bytes,
)
}
};
let shared_secret_bytes = C::Sha256::sha256(&server_shared_secret).to_vec();
Ok(Ukey2Client {
client_finished_bytes,
completed_handshake: CompletedHandshake::new(
self.client_init_bytes,
server_init_bytes.to_vec(),
shared_secret_bytes,
self.next_protocol,
),
})
}
}
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum ServerInitError {
BadVersion,
BadHandshakeCipher,
BadPublicKey,
/// The diffie-hellman key exchange failed to generate a shared secret
BadKeyExchange,
}
#[derive(Clone)]
pub struct Ukey2Client {
completed_handshake: CompletedHandshake,
client_finished_bytes: Vec<u8>,
}
impl fmt::Debug for Ukey2Client {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Ukey2Client")
}
}
impl Ukey2Client {
pub fn client_finished_msg(&self) -> &[u8] {
&self.client_finished_bytes
}
pub fn completed_handshake(&self) -> &CompletedHandshake {
&self.completed_handshake
}
}
#[allow(clippy::enum_variant_names)]
pub enum ClientInitError {
BadVersion,
BadHandshakeCipher,
BadNextProtocol,
}
pub enum ClientFinishedError {
BadEd25519Key,
BadP256Key,
UnknownCommitment,
/// The diffie-hellman key exchange failed to generate a shared secret
BadKeyExchange,
}
/// The result of completing the UKEY2 handshake.
#[derive(Clone)]
pub struct CompletedHandshake {
client_init_bytes: Vec<u8>,
server_init_bytes: Vec<u8>,
shared_secret: Vec<u8>,
pub next_protocol: String,
}
impl CompletedHandshake {
fn new(
client_init_bytes: Vec<u8>,
server_init_bytes: Vec<u8>,
shared_secret: Vec<u8>,
next_protocol: String,
) -> Self {
Self { client_init_bytes, server_init_bytes, shared_secret, next_protocol }
}
/// Returns an HKDF for the UKEY2 `AUTH_STRING`.
pub fn auth_string<C: CryptoProvider>(&self) -> HandshakeHkdf<C> {
HandshakeHkdf::new(
&self.client_init_bytes,
&self.server_init_bytes,
C::HkdfSha256::new(Some(HKDF_SALT_AUTH), &self.shared_secret),
)
}
/// Returns an HKDF for the UKEY2 `NEXT_SECRET`.
pub fn next_protocol_secret<C: CryptoProvider>(&self) -> HandshakeHkdf<C> {
HandshakeHkdf::new(
&self.client_init_bytes,
&self.server_init_bytes,
C::HkdfSha256::new(Some(HKDF_SALT_NEXT), &self.shared_secret),
)
}
}
/// A UKEY2 handshake secret that can derive output at the caller's preferred length.
pub struct HandshakeHkdf<'a, C: CryptoProvider> {
client_init_bytes: &'a [u8],
server_init_bytes: &'a [u8],
hkdf: C::HkdfSha256,
}
impl<'a, C: CryptoProvider> HandshakeHkdf<'a, C> {
/// Returns `None` if the requested size > 255 * 512 bytes.
pub fn derive_array<const N: usize>(&self) -> Option<[u8; N]> {
let mut buf = [0; N];
self.derive_slice(&mut buf).map(|_| buf)
}
/// Returns `None` if the requested `length` > 255 * 512 bytes.
pub fn derive_vec(&self, length: usize) -> Option<Vec<u8>> {
let mut buf = vec![0; length];
self.derive_slice(&mut buf).map(|_| buf)
}
/// Returns `None` if the provided `buf` has size > 255 * 512 bytes.
pub fn derive_slice(&self, buf: &mut [u8]) -> Option<()> {
self.hkdf.expand_multi_info(&[self.client_init_bytes, self.server_init_bytes], buf).ok()
}
fn new(client_init_bytes: &'a [u8], server_init_bytes: &'a [u8], hkdf: C::HkdfSha256) -> Self {
Self { client_init_bytes, server_init_bytes, hkdf }
}
}