use crate::chacha20::*;
use crate::error;
use crate::error::Result;
use crate::mechanism::*;
use crate::misc::{bytes_to_slice, bytes_to_slice_mut, bytes_to_vec, zeromem};
use crate::object::Object;
use crate::ossl::common::osslctx;
use crate::pkcs11::*;
use ossl::cipher::{AeadParams, EncAlg, OsslCipher};
use ossl::OsslSecret;
const CHACHA20_NONCE_SIZE: usize = 12;
const CHACHA20_COUNTER_SIZE: usize = 4;
const POLY1305_TAG_SIZE: usize = 16;
unsafe fn cast_params<T: Copy + Clone>(
ptr: CK_VOID_PTR,
len: CK_ULONG,
) -> Result<T> {
let Ok(len) = usize::try_from(len) else {
return Err(CKR_ARGUMENTS_BAD)?;
};
if len != std::mem::size_of::<T>() {
return Err(CKR_ARGUMENTS_BAD)?;
}
Ok(unsafe { std::ptr::read_unaligned(ptr as *const T) })
}
fn object_to_raw_key(key: &Object) -> Result<OsslSecret> {
let val = key.get_attr_as_bytes(CKA_VALUE)?;
if val.len() != CHACHA20_KEY_SIZE {
return Err(CKR_KEY_INDIGESTIBLE)?;
}
Ok(OsslSecret::from_slice(&val))
}
#[derive(Debug)]
struct ChaChaParams {
iv: Vec<u8>,
aad: Vec<u8>,
}
fn init_params(mech: &CK_MECHANISM) -> Result<ChaChaParams> {
match mech.mechanism {
CKM_CHACHA20 => {
let params = mech.get_parameters::<CK_CHACHA20_PARAMS>()?;
if params.blockCounterBits != 32 || params.ulNonceBits != 96 {
return Err(CKR_MECHANISM_PARAM_INVALID)?;
}
if params.pBlockCounter.is_null() || params.pNonce.is_null() {
return Err(CKR_MECHANISM_PARAM_INVALID)?;
}
let mut iv =
bytes_to_vec(params.pBlockCounter, CHACHA20_COUNTER_SIZE);
iv.extend_from_slice(&bytes_to_vec(
params.pNonce,
CHACHA20_NONCE_SIZE,
));
Ok(ChaChaParams {
iv: iv,
aad: Vec::new(),
})
}
CKM_CHACHA20_POLY1305 => {
let params =
mech.get_parameters::<CK_SALSA20_CHACHA20_POLY1305_PARAMS>()?;
if params.pNonce.is_null()
|| usize::try_from(params.ulNonceLen)? != CHACHA20_NONCE_SIZE
{
return Err(CKR_MECHANISM_PARAM_INVALID)?;
}
if params.ulAADLen > 0 && params.pAAD.is_null() {
return Err(CKR_MECHANISM_PARAM_INVALID)?;
}
Ok(ChaChaParams {
iv: bytes_to_vec(params.pNonce, CHACHA20_NONCE_SIZE),
aad: bytes_to_vec(
params.pAAD,
usize::try_from(params.ulAADLen)?,
),
})
}
_ => Err(CKR_MECHANISM_INVALID)?,
}
}
fn get_cipher(mech: CK_MECHANISM_TYPE) -> Result<EncAlg> {
match mech {
CKM_CHACHA20 => Ok(EncAlg::ChaCha20),
CKM_CHACHA20_POLY1305 => Ok(EncAlg::ChaCha20Poly1305),
_ => Err(CKR_MECHANISM_INVALID)?,
}
}
#[derive(Debug)]
pub struct ChaChaOperation {
mech: CK_MECHANISM_TYPE,
op: CK_FLAGS,
key: OsslSecret,
finalized: bool,
in_use: bool,
ctx: Option<OsslCipher>,
buffer: Vec<u8>,
}
impl Drop for ChaChaOperation {
fn drop(&mut self) {
zeromem(self.buffer.as_mut_slice());
}
}
impl ChaChaOperation {
pub fn register_mechanisms(mechs: &mut Mechanisms) {
mechs.add_mechanism(CKM_CHACHA20, &(*CHACHA20_MECHS)[0]);
mechs.add_mechanism(CKM_CHACHA20_POLY1305, &(*CHACHA20_MECHS)[1]);
mechs.add_mechanism(CKM_CHACHA20_KEY_GEN, &(*CHACHA20_MECHS)[2]);
}
fn cipher_initialize(
mech: CK_MECHANISM_TYPE,
params: &ChaChaParams,
key: &[u8],
enc: bool,
) -> Result<OsslCipher> {
Ok(OsslCipher::new(
osslctx(),
get_cipher(mech)?,
enc,
OsslSecret::from_slice(key),
Some(params.iv.clone()),
match mech {
CKM_CHACHA20_POLY1305 => Some(AeadParams::new(
if params.aad.len() > 0 {
Some(params.aad.clone())
} else {
None
},
POLY1305_TAG_SIZE,
0,
)),
_ => None,
},
)?)
}
pub fn encrypt_new(
mech: &CK_MECHANISM,
key: &Object,
) -> Result<ChaChaOperation> {
let params = init_params(mech)?;
let chachakey = object_to_raw_key(key)?;
let ctx =
Self::cipher_initialize(mech.mechanism, ¶ms, &chachakey, true)?;
Ok(ChaChaOperation {
mech: mech.mechanism,
op: CKF_ENCRYPT,
key: chachakey,
finalized: false,
in_use: false,
ctx: Some(ctx),
buffer: Vec::new(),
})
}
pub fn decrypt_new(
mech: &CK_MECHANISM,
key: &Object,
) -> Result<ChaChaOperation> {
let params = init_params(mech)?;
let chachakey = object_to_raw_key(key)?;
let ctx = Self::cipher_initialize(
mech.mechanism,
¶ms,
&chachakey,
false,
)?;
Ok(ChaChaOperation {
mech: mech.mechanism,
op: CKF_DECRYPT,
key: chachakey,
finalized: false,
in_use: false,
ctx: Some(ctx),
buffer: Vec::new(),
})
}
pub fn msg_encrypt_init(
mech: &CK_MECHANISM,
key: &Object,
) -> Result<ChaChaOperation> {
if mech.mechanism != CKM_CHACHA20_POLY1305 {
return Err(CKR_MECHANISM_INVALID)?;
}
Ok(ChaChaOperation {
mech: mech.mechanism,
op: CKF_MESSAGE_ENCRYPT,
key: object_to_raw_key(key)?,
finalized: false,
in_use: false,
ctx: None,
buffer: Vec::new(),
})
}
pub fn msg_decrypt_init(
mech: &CK_MECHANISM,
key: &Object,
) -> Result<ChaChaOperation> {
if mech.mechanism != CKM_CHACHA20_POLY1305 {
return Err(CKR_MECHANISM_INVALID)?;
}
Ok(ChaChaOperation {
mech: mech.mechanism,
op: CKF_MESSAGE_DECRYPT,
key: object_to_raw_key(key)?,
finalized: false,
in_use: false,
ctx: None,
buffer: Vec::new(),
})
}
fn parse_msg_params(
parameter: CK_VOID_PTR,
parameter_len: CK_ULONG,
) -> Result<CK_SALSA20_CHACHA20_POLY1305_MSG_PARAMS> {
let params = unsafe {
cast_params::<CK_SALSA20_CHACHA20_POLY1305_MSG_PARAMS>(
parameter,
parameter_len,
)
}?;
if params.pNonce.is_null()
|| usize::try_from(params.ulNonceLen)? != CHACHA20_NONCE_SIZE
{
return Err(CKR_MECHANISM_PARAM_INVALID)?;
}
if params.pTag.is_null() {
return Err(CKR_MECHANISM_PARAM_INVALID)?;
}
Ok(params)
}
fn msg_encrypt_new(
&mut self,
parameter: CK_VOID_PTR,
parameter_len: CK_ULONG,
aad: &[u8],
) -> Result<()> {
if self.op != CKF_MESSAGE_ENCRYPT {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if self.in_use {
return Err(CKR_OPERATION_ACTIVE)?;
}
let params = Self::parse_msg_params(parameter, parameter_len)?;
let chacha_params = ChaChaParams {
iv: bytes_to_vec(params.pNonce, CHACHA20_NONCE_SIZE),
aad: aad.to_vec(),
};
self.ctx = Some(Self::cipher_initialize(
self.mech,
&chacha_params,
&self.key,
true,
)?);
self.finalized = false;
self.in_use = true;
Ok(())
}
fn msg_decrypt_new(
&mut self,
parameter: CK_VOID_PTR,
parameter_len: CK_ULONG,
aad: &[u8],
) -> Result<()> {
if self.op != CKF_MESSAGE_DECRYPT {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if self.in_use {
return Err(CKR_OPERATION_ACTIVE)?;
}
let params = Self::parse_msg_params(parameter, parameter_len)?;
let chacha_params = ChaChaParams {
iv: bytes_to_vec(params.pNonce, CHACHA20_NONCE_SIZE),
aad: aad.to_vec(),
};
self.ctx = Some(Self::cipher_initialize(
self.mech,
&chacha_params,
&self.key,
false,
)?);
self.finalized = false;
self.in_use = true;
Ok(())
}
fn op_err(&mut self, err: CK_RV) -> error::Error {
self.finalized = true;
error::Error::ck_rv(err)
}
}
impl MechOperation for ChaChaOperation {
fn mechanism(&self) -> Result<CK_MECHANISM_TYPE> {
Ok(self.mech)
}
fn finalized(&self) -> bool {
self.finalized
}
}
impl Encryption for ChaChaOperation {
fn encrypt(&mut self, plain: &[u8], cipher: &mut [u8]) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
let outl = self.encrypt_update(plain, cipher)?;
if outl > cipher.len() {
return Err(self.op_err(CKR_GENERAL_ERROR));
}
Ok(outl + self.encrypt_final(&mut cipher[outl..])?)
}
fn encrypt_update(
&mut self,
plain: &[u8],
cipher: &mut [u8],
) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
self.in_use = true;
let outlen = self.encryption_len(plain.len(), false)?;
if cipher.len() < outlen {
return Err(error::Error::buf_too_small(outlen));
}
let ctx = match &mut self.ctx {
Some(c) => c,
None => return Err(self.op_err(CKR_GENERAL_ERROR)),
};
if plain.len() == 0 {
return Ok(0);
}
ctx.update(plain, cipher)
.or_else(|_| Err(self.op_err(CKR_DEVICE_ERROR)))
}
fn encrypt_final(&mut self, cipher: &mut [u8]) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if !self.in_use {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
let ctx = match &mut self.ctx {
Some(c) => c,
None => return Err(self.op_err(CKR_GENERAL_ERROR)),
};
let outlen = match self.mech {
CKM_CHACHA20_POLY1305 => {
if cipher.len() < POLY1305_TAG_SIZE {
return Err(error::Error::buf_too_small(POLY1305_TAG_SIZE));
}
let outlen = ctx.finalize(cipher)?;
if outlen != 0 {
self.finalized = true;
return Err(CKR_DEVICE_ERROR)?;
}
ctx.get_tag(&mut cipher[..POLY1305_TAG_SIZE])?;
POLY1305_TAG_SIZE
}
CKM_CHACHA20 => 0,
_ => {
self.finalized = true;
return Err(CKR_GENERAL_ERROR)?;
}
};
self.finalized = true;
Ok(outlen)
}
fn encryption_len(&mut self, data_len: usize, _fin: bool) -> Result<usize> {
match self.mech {
CKM_CHACHA20 => Ok(data_len),
CKM_CHACHA20_POLY1305 => Ok(data_len + POLY1305_TAG_SIZE),
_ => Err(self.op_err(CKR_GENERAL_ERROR)),
}
}
}
impl Decryption for ChaChaOperation {
fn decrypt(&mut self, cipher: &[u8], plain: &mut [u8]) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
let outl = self.decrypt_update(cipher, plain)?;
if outl > plain.len() {
return Err(self.op_err(CKR_GENERAL_ERROR));
}
Ok(outl + self.decrypt_final(&mut plain[outl..])?)
}
fn decrypt_update(
&mut self,
cipher: &[u8],
plain: &mut [u8],
) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
self.in_use = true;
let outlen = match self.mech {
CKM_CHACHA20 => cipher.len(),
CKM_CHACHA20_POLY1305 => {
let tlen = cipher.len() + self.buffer.len();
if tlen > POLY1305_TAG_SIZE {
tlen - POLY1305_TAG_SIZE
} else {
0
}
}
_ => return Err(self.op_err(CKR_GENERAL_ERROR)),
};
if plain.len() < outlen {
return Err(error::Error::buf_too_small(outlen));
}
let ctx = match &mut self.ctx {
Some(c) => c,
None => return Err(self.op_err(CKR_GENERAL_ERROR)),
};
let mut plain_offset = 0;
let mut cipher_offset = 0;
let mut cipher_end = cipher.len();
match self.mech {
CKM_CHACHA20_POLY1305 => {
if self.buffer.len() > 0 {
if cipher_end > POLY1305_TAG_SIZE {
plain_offset = ctx
.update(self.buffer.as_slice(), plain)
.or_else(|_| {
self.finalized = true;
Err(CKR_DEVICE_ERROR)
})?;
zeromem(self.buffer.as_mut_slice());
self.buffer.clear();
cipher_end -= POLY1305_TAG_SIZE;
self.buffer.extend_from_slice(&cipher[cipher_end..]);
} else {
self.buffer.extend_from_slice(cipher);
if self.buffer.len() > POLY1305_TAG_SIZE {
let buflen = self.buffer.len() - POLY1305_TAG_SIZE;
plain_offset = ctx
.update(
&self.buffer.as_slice()[..buflen],
plain,
)
.or_else(|_| {
self.finalized = true;
Err(CKR_DEVICE_ERROR)
})?;
zeromem(&mut self.buffer.as_mut_slice()[..buflen]);
let _ = self.buffer.drain(..buflen);
}
}
cipher_offset = cipher_end;
} else if cipher_end > POLY1305_TAG_SIZE {
cipher_end -= POLY1305_TAG_SIZE;
self.buffer.extend_from_slice(&cipher[cipher_end..]);
} else {
self.buffer.extend_from_slice(cipher);
cipher_offset = cipher_end;
}
}
_ => (),
}
if cipher_end - cipher_offset > 0 {
let outlen = ctx
.update(
&cipher[cipher_offset..cipher_end],
&mut plain[plain_offset..],
)
.or_else(|_| {
self.finalized = true;
Err(CKR_DEVICE_ERROR)
})?;
plain_offset += outlen;
}
Ok(plain_offset)
}
fn decrypt_final(&mut self, plain: &mut [u8]) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if !self.in_use {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
let ctx = match &mut self.ctx {
Some(c) => c,
None => return Err(self.op_err(CKR_GENERAL_ERROR)),
};
self.finalized = true;
let outlen = 0;
match self.mech {
CKM_CHACHA20_POLY1305 => {
if self.buffer.len() != POLY1305_TAG_SIZE {
return Err(CKR_ENCRYPTED_DATA_LEN_RANGE)?;
}
ctx.set_tag(self.buffer.as_slice())?;
match ctx.finalize(plain) {
Ok(len) => {
if len != 0 {
return Err(CKR_DEVICE_ERROR)?;
}
}
Err(_) => return Err(CKR_ENCRYPTED_DATA_INVALID)?,
}
}
CKM_CHACHA20 => (),
_ => return Err(CKR_GENERAL_ERROR)?,
}
Ok(outlen)
}
fn decryption_len(&mut self, data_len: usize, fin: bool) -> Result<usize> {
let outlen = if fin {
match self.mech {
CKM_CHACHA20_POLY1305 => {
if self.buffer.len() + data_len < POLY1305_TAG_SIZE {
return Err(self.op_err(CKR_ENCRYPTED_DATA_LEN_RANGE));
}
self.buffer.len() + data_len - POLY1305_TAG_SIZE
}
CKM_CHACHA20 => data_len,
_ => return Err(self.op_err(CKR_GENERAL_ERROR)),
}
} else {
match self.mech {
CKM_CHACHA20_POLY1305 => {
if self.buffer.len() + data_len < POLY1305_TAG_SIZE {
0
} else {
self.buffer.len() + data_len
}
}
CKM_CHACHA20 => data_len,
_ => return Err(self.op_err(CKR_GENERAL_ERROR)),
}
};
Ok(outlen)
}
}
impl MessageOperation for ChaChaOperation {
fn busy(&self) -> bool {
self.in_use
}
fn finalize(&mut self) -> Result<()> {
if self.in_use {
return Err(CKR_OPERATION_ACTIVE)?;
}
self.finalized = true;
Ok(())
}
}
impl MsgEncryption for ChaChaOperation {
fn msg_encrypt(
&mut self,
param: CK_VOID_PTR,
paramlen: CK_ULONG,
aad: &[u8],
plain: &[u8],
cipher: &mut [u8],
) -> Result<usize> {
self.msg_encrypt_begin(param, paramlen, aad)?;
self.msg_encrypt_final(param, paramlen, plain, cipher)
}
fn msg_encrypt_begin(
&mut self,
param: CK_VOID_PTR,
paramlen: CK_ULONG,
aad: &[u8],
) -> Result<()> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
self.msg_encrypt_new(param, paramlen, aad)
}
fn msg_encrypt_next(
&mut self,
_param: CK_VOID_PTR,
_paramlen: CK_ULONG,
plain: &[u8],
cipher: &mut [u8],
) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if !self.in_use {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if cipher.len() < plain.len() {
return Err(error::Error::buf_too_small(plain.len()));
}
let ctx = match &mut self.ctx {
Some(c) => c,
None => return Err(self.op_err(CKR_GENERAL_ERROR)),
};
if plain.len() == 0 {
return Ok(0);
}
ctx.update(plain, cipher)
.or_else(|_| Err(self.op_err(CKR_DEVICE_ERROR)))
}
fn msg_encrypt_final(
&mut self,
param: CK_VOID_PTR,
paramlen: CK_ULONG,
plain: &[u8],
cipher: &mut [u8],
) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if !self.in_use {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
let params = Self::parse_msg_params(param, paramlen)?;
let outlen = self.msg_encrypt_next(param, paramlen, plain, cipher)?;
let ctx = match &mut self.ctx {
Some(c) => c,
None => return Err(self.op_err(CKR_GENERAL_ERROR)),
};
if !ctx.finalize(cipher).is_ok_and(|len| len == 0) {
zeromem(cipher);
return Err(self.op_err(CKR_DEVICE_ERROR));
}
let tagbuf = bytes_to_slice_mut(params.pTag, POLY1305_TAG_SIZE)?;
if ctx.get_tag(tagbuf).is_err() {
zeromem(cipher);
return Err(self.op_err(CKR_DEVICE_ERROR));
}
self.in_use = false;
Ok(outlen)
}
fn msg_encryption_len(
&mut self,
data_len: usize,
_fin: bool,
) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
Ok(data_len)
}
}
impl MsgDecryption for ChaChaOperation {
fn msg_decrypt(
&mut self,
param: CK_VOID_PTR,
paramlen: CK_ULONG,
aad: &[u8],
cipher: &[u8],
plain: &mut [u8],
) -> Result<usize> {
self.msg_decrypt_begin(param, paramlen, aad)?;
self.msg_decrypt_final(param, paramlen, cipher, plain)
}
fn msg_decrypt_begin(
&mut self,
param: CK_VOID_PTR,
paramlen: CK_ULONG,
aad: &[u8],
) -> Result<()> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
self.msg_decrypt_new(param, paramlen, aad)
}
fn msg_decrypt_next(
&mut self,
_param: CK_VOID_PTR,
_paramlen: CK_ULONG,
cipher: &[u8],
plain: &mut [u8],
) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if !self.in_use {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if plain.len() < cipher.len() {
return Err(error::Error::buf_too_small(cipher.len()));
}
let ctx = match &mut self.ctx {
Some(c) => c,
None => return Err(self.op_err(CKR_GENERAL_ERROR)),
};
if cipher.len() == 0 {
return Ok(0);
}
ctx.update(cipher, plain)
.or_else(|_| Err(self.op_err(CKR_DEVICE_ERROR)))
}
fn msg_decrypt_final(
&mut self,
param: CK_VOID_PTR,
paramlen: CK_ULONG,
cipher: &[u8],
plain: &mut [u8],
) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
if !self.in_use {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
let params = Self::parse_msg_params(param, paramlen)?;
let outlen = self.msg_decrypt_next(param, paramlen, cipher, plain)?;
let tag = bytes_to_slice(params.pTag, POLY1305_TAG_SIZE);
let ctx = match &mut self.ctx {
Some(c) => c,
None => return Err(self.op_err(CKR_GENERAL_ERROR)),
};
ctx.set_tag(tag).or_else(|_| {
self.finalized = true;
Err(CKR_DEVICE_ERROR)
})?;
match ctx.finalize(&mut plain[outlen..]) {
Ok(len) => {
if len != 0 {
self.finalized = true;
return Err(CKR_DEVICE_ERROR)?;
}
}
Err(_) => {
self.finalized = true;
return Err(CKR_ENCRYPTED_DATA_INVALID)?;
}
}
self.in_use = false;
Ok(outlen)
}
fn msg_decryption_len(
&mut self,
data_len: usize,
_fin: bool,
) -> Result<usize> {
if self.finalized {
return Err(CKR_OPERATION_NOT_INITIALIZED)?;
}
Ok(data_len)
}
}