blob: b23f9fe4ce217196eac0caa163a2536367836546 [file] [edit]
// Copyright 2026 The BoringSSL Authors
//
// 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
//
// https://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.
//! CPace
//!
//! This is an implementation of [CPace IETF draft].
//!
//! CPace is a quantum-annoying balanced Password-Authenticated Key Exchange scheme.
//!
//! ```
//! # use bssl_crypto::cpace::{Context, Role};
//!
//! let password = b"Password";
//! let channel_id = b"ChannelId";
//! let session_id = b"SessionId";
//! let alice_ad = b"ADa";
//! let bob_ad = b"ADb";
//!
//! // Alice (initiator) starts the exchange and generates a message.
//! let (alice_msg, alice_ctx) = Context::new(
//! Role::Initiator,
//! password,
//! channel_id,
//! alice_ad,
//! session_id,
//! );
//!
//! // Bob (responder) starts the exchange and generates a message.
//! let (bob_msg, bob_ctx) = Context::new(
//! Role::Responder,
//! password,
//! channel_id,
//! bob_ad,
//! session_id,
//! );
//!
//! // Alice and Bob exchange messages and associated data to derive the shared secret.
//! let alice_key = alice_ctx.finalize(&bob_msg, bob_ad).unwrap();
//! let bob_key = bob_ctx.finalize(&alice_msg, alice_ad).unwrap();
//! assert_eq!(alice_key, bob_key);
//! ```
//!
//! [CPace IETF draft]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-cpace
use core::{
mem::MaybeUninit,
ptr::{
null_mut,
NonNull, //
}, //
};
use crate::FfiSlice;
/// CPace context for a single protocol execution.
pub struct Context(NonNull<bssl_sys::CPACE_CTX>);
// Safety: this context data does not use thread-local storage
unsafe impl Send for Context {}
// Safety: public API guarantees that the access will be thread-safe
unsafe impl Sync for Context {}
/// The role that a party plays in the CPace key agreement protocol.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
/// Initiator role, corresponding to party A in the specification.
Initiator,
/// Responder role, corresponding to party B in the specification.
Responder,
}
impl From<Role> for bssl_sys::cpace_role_t {
fn from(role: Role) -> Self {
match role {
Role::Initiator => bssl_sys::cpace_role_t_cpace_role_initiator,
Role::Responder => bssl_sys::cpace_role_t_cpace_role_responder,
}
}
}
/// A CPace message to be sent to the peer.
pub type Message = [u8; bssl_sys::CPACE_MSG_LEN as usize];
/// The shared secret, ISK, derived from a CPace exchange.
pub type Output = [u8; bssl_sys::CPACE_SHARED_SECRET_LEN as usize];
impl Drop for Context {
fn drop(&mut self) {
unsafe {
// Safety: `self.0` is still a valid point at this point.
bssl_sys::CPACE_CTX_free(self.0.as_ptr());
}
}
}
impl Context {
/// Create a new CPace context and generate the message to send to the peer.
pub fn new(
role: Role,
password: &[u8],
channel_id: &[u8],
assoc_data: &[u8],
session_id: &[u8],
) -> (Message, Self) {
let (password, password_len) = (password.as_ffi_ptr(), password.len());
let (channel_id, channel_id_len) = (channel_id.as_ffi_ptr(), channel_id.len());
let (assoc_data, assoc_data_len) = (assoc_data.as_ffi_ptr(), assoc_data.len());
let (sess_id, sess_id_len) = (session_id.as_ffi_ptr(), session_id.len());
let ptr = unsafe {
// Safety: all pointer and length pairs reference valid slices for the
// duration of the call, and `CPACE_CTX_new` copies the inputs.
bssl_sys::CPACE_CTX_new(
role.into(),
password,
password_len,
channel_id,
channel_id_len,
assoc_data,
assoc_data_len,
sess_id,
sess_id_len,
)
};
let ptr = NonNull::new(ptr).expect("allocation failed");
let mut msg = [MaybeUninit::uninit(); bssl_sys::CPACE_MSG_LEN as usize];
let rc = unsafe {
// Safety: `ptr` is a valid newly initialized `CPACE_CTX` and `msg` has
// `CPACE_MSG_LEN` bytes of space.
bssl_sys::CPACE_generate_msg(ptr.as_ptr(), msg.as_mut_ptr() as *mut _)
};
assert!(rc == 1, "unexpected failure of message generation");
(
unsafe {
// Safety: By BoringSSL invariant, we always write out some point data to the out
// pointer.
MaybeUninit::from(msg).assume_init()
},
Self(ptr),
)
}
/// Finalize the CPace protocol by deriving the shared secret.
///
/// This function returns error if `peer_message` is an invalid message.
pub fn finalize(self, peer_message: &Message, peer_assoc_data: &[u8]) -> Result<Output, ()> {
let (peer_assoc_data, peer_assoc_data_len) =
(peer_assoc_data.as_ffi_ptr(), peer_assoc_data.len());
let mut out = [MaybeUninit::uninit(); bssl_sys::CPACE_SHARED_SECRET_LEN as usize];
let rc = unsafe {
// Safety:
// - `self` witnesses the validity of `self.0`
// - `peer_message` and `peer_assoc_data` do not escape the call
// - type state transition ensures that we are at the correct state of the protocol.
bssl_sys::CPACE_process_msg(
self.0.as_ptr(),
peer_message.as_ptr(),
peer_assoc_data,
peer_assoc_data_len,
out.as_mut_ptr() as *mut _,
null_mut(),
)
};
if rc == 1 {
unsafe {
// Safety: By BoringSSL invariant, we always write out some point data to the out
// pointer if the operation was successful.
Ok(MaybeUninit::from(out).assume_init())
}
} else {
unsafe {
// Safety: we are only clearing a thread-local error queue.
bssl_sys::ERR_clear_error();
}
Err(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const PASSWORD: &[u8] = b"Password";
const CHANNEL_ID: &[u8] = b"some channel";
const ADA: &[u8] = b"ADa";
const ADB: &[u8] = b"ADb";
const SID: &[u8] = &[
0x7e, 0x4b, 0x47, 0x91, 0xd6, 0xa8, 0xef, 0x01, 0x9b, 0x93, 0x6c, 0x79, 0xfb, 0x7f, 0x2c,
0x57,
];
#[test]
fn key_exchange() -> Result<(), ()> {
let (alice_msg, alice) = Context::new(Role::Initiator, PASSWORD, CHANNEL_ID, ADA, SID);
let (bob_msg, bob) = Context::new(Role::Responder, PASSWORD, CHANNEL_ID, ADB, SID);
let alice_key = alice.finalize(&bob_msg, ADB)?;
let bob_key = bob.finalize(&alice_msg, ADA)?;
assert_eq!(alice_key.as_ref(), bob_key.as_ref());
Ok(())
}
}