diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d2798d..679d73a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ This file records each release. The format follows [Keep a Changelog](https://ke ## Unreleased +### Added + +- The `AF_ALG` sockets with the type `skcipher`, as crypto/algif_skcipher.c of Linux, for `ecb(aes)`, `cbc(aes)`, `ctr(aes)` and `xts(aes)` of `rk-crypto`. A `sendmsg` gives the operation with `ALG_SET_OP` and the IV with `ALG_SET_IV` in control messages, and adds the data to the queue. A `recv` encrypts or decrypts the data of the queue in blocks. With `MSG_MORE`, the operation continues in the next `send`, and the IV goes from one `recv` to the next. libcryptsetup uses these sockets to open a LUKS2 key slot without dm-crypt (#541). + ## [0.5.24] - 2026-11-12 ### Added diff --git a/_typos.toml b/_typos.toml index eb49f51..e850ebf 100644 --- a/_typos.toml +++ b/_typos.toml @@ -38,6 +38,7 @@ controllen = "controllen" # msg_controllen of struct msghdr BA = "BA" # BARs, PCI base address registers, split by the checker exportfs = "exportfs" # the NFS export code of Linux and the group of fstests IIF = "IIF" # RTA_IIF of rtnetlink, the input interface of a route +ede = "ede" # des3_ede, the triple DES of Linux (encrypt, decrypt, encrypt) OIF = "OIF" # RTA_OIF of rtnetlink, the output interface of a route Parm = "Parm" # InParmProbs of /proc/net/snmp, the ICMP parameter problems Ofo = "Ofo" # OfoPruned of /proc/net/netstat, the out of order queue of TCP diff --git a/net/rk-net/src/alg/hash.rs b/net/rk-net/src/alg/hash.rs index 9d44f48..a9476b4 100644 --- a/net/rk-net/src/alg/hash.rs +++ b/net/rk-net/src/alg/hash.rs @@ -80,8 +80,9 @@ impl HashSocket { return Ok(()); } self.bound.check_key(|tfm| { - let Tfm::Hash(h) = tfm; - ctx.hash = h.box_clone(); + if let Tfm::Hash(h) = tfm { + ctx.hash = h.box_clone(); + } })?; ctx.nokey = false; Ok(()) @@ -122,7 +123,10 @@ impl Socket for HashSocket { self.check_key(&mut ctx)?; ctx.more.then(|| ctx.hash.box_clone()) }; - let Child::Hash(child) = self.bound.accept()?; + // The bound socket of a hash socket has a hash. + let Child::Hash(child) = self.bound.accept()? else { + return Err(Errno::EINVAL); + }; if let Some(hash) = state { let mut c = child.ctx.lock(); c.hash = hash; diff --git a/net/rk-net/src/alg/mod.rs b/net/rk-net/src/alg/mod.rs index 216dd84..662aa90 100644 --- a/net/rk-net/src/alg/mod.rs +++ b/net/rk-net/src/alg/mod.rs @@ -5,7 +5,8 @@ //! `setsockopt` with the level `SOL_ALG` and `ALG_SET_KEY` sets the key. `accept` on the bound //! socket gives an operation socket. The program writes the data to the operation socket and //! reads the result from it. The module `hash` has the operation sockets of the type `hash`, as -//! crypto/algif_hash.c of Linux. +//! crypto/algif_hash.c of Linux, and the module `skcipher` has the operation sockets of the type +//! `skcipher`, as crypto/algif_skcipher.c of Linux. //! //! The algorithms come from the crate `rk_crypto`. A program can bind only the algorithms of the //! allow list of the type, as `af_alg_check_restriction` of Linux with `crypto.af_alg_restrict` @@ -17,6 +18,7 @@ //! key of the bound socket cannot change while it has an operation socket with a key. mod hash; +mod skcipher; #[cfg(test)] mod tests; @@ -84,6 +86,7 @@ fn check_restriction(name: &str, list: AllowList) -> Result<()> { /// The algorithm of a bound socket, as the `private` of a `struct alg_sock` of Linux. enum Tfm { Hash(Box), + Skcipher(Box), } impl Tfm { @@ -91,6 +94,7 @@ impl Tfm { fn bind(kind: &str, name: &str) -> Result { match kind { "hash" => hash::bind(name).map(Self::Hash), + "skcipher" => skcipher::bind(name).map(Self::Skcipher), _ => Err(Errno::ENOENT), } } @@ -98,12 +102,14 @@ impl Tfm { fn needs_key(&self) -> bool { match self { Self::Hash(h) => h.needs_key(), + Self::Skcipher(c) => c.needs_key(), } } fn set_key(&mut self, key: &[u8]) -> Result<()> { match self { Self::Hash(h) => h.set_key(key), + Self::Skcipher(c) => c.set_key(key), } } } @@ -123,12 +129,14 @@ struct Bound(SpinLock); /// A new operation socket. enum Child { Hash(Arc), + Skcipher(Arc), } impl Child { fn socket(self) -> Arc { match self { Self::Hash(s) => s, + Self::Skcipher(s) => s, } } } @@ -144,6 +152,9 @@ impl Bound { let nokey = tfm.needs_key(); let child = match tfm { Tfm::Hash(h) => Child::Hash(hash::HashSocket::new(self.clone(), h.box_clone(), nokey)), + Tfm::Skcipher(c) => { + Child::Skcipher(skcipher::SkcipherSocket::new(self.clone(), c.box_clone(), nokey)) + } }; s.children += 1; if nokey { diff --git a/net/rk-net/src/alg/skcipher.rs b/net/rk-net/src/alg/skcipher.rs new file mode 100644 index 0000000..c3bb956 --- /dev/null +++ b/net/rk-net/src/alg/skcipher.rs @@ -0,0 +1,389 @@ +//! The operation sockets of the type `skcipher`, as crypto/algif_skcipher.c of Linux. +//! +//! A send adds data to the queue of the socket. Its control messages of the level `SOL_ALG` set +//! the operation (`ALG_SET_OP`) and the IV (`ALG_SET_IV`). A send with `MSG_MORE` tells that more +//! data comes. A receive encrypts or decrypts the data of the queue and gives the result. While +//! more data comes, a receive takes only full blocks, and the IV of the next receive goes on from +//! the data before, as `CRYPTO_SKCIPHER_REQ_NOTFINAL` of Linux. A receive waits for data if the +//! socket has none yet, or if more data comes and the queue has less than one block. After a +//! receive takes all the data of an operation that ended, the next receive waits for a new send. +//! +//! The queue holds at most the size of the send buffer, as `af_alg_sndbuf` of Linux. A send +//! waits until a receive takes data from a full queue. + +use super::{AF_ALG, AllowList, Bound, SOL_ALG, Tfm, get_socket_option}; +use super::{ALG_SET_AEAD_ASSOCLEN, ALG_SET_IV, ALG_SET_OP}; +use crate::consts::{MSG_DONTWAIT, MSG_MORE, SOCK_SEQPACKET}; +use crate::options::{Options, Timeout}; +use crate::{Message, PathOps, Received, Socket, next_ino, wait_for}; +use alloc::boxed::Box; +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use alloc::vec; +use alloc::vec::Vec; +use core::any::Any; +use core::sync::atomic::{AtomicU64, Ordering}; +use rk_abi::Errno; +use rk_abi::native::poll::{POLLIN, POLLOUT, POLLRDNORM, POLLWRBAND, POLLWRNORM}; +use rk_abi::native::socket::SOL_SOCKET; +use rk_crypto::Skcipher; +use rk_frame::addr::PAGE_SIZE; +use rk_frame::sync::SpinLock; +use rk_vfs::inode::Result; +use rk_vfs::wait::{PollTable, WaitQueue}; + +/// The operation of `ALG_SET_OP` that decrypts. +pub(super) const ALG_OP_DECRYPT: u32 = 0; +/// The operation of `ALG_SET_OP` that encrypts. +pub(super) const ALG_OP_ENCRYPT: u32 = 1; + +/// The ciphers that a program can bind, as `skcipher_allowlist` of Linux. +const ALLOW: AllowList = &[ + ("adiantum(xchacha12,aes)", true), + ("adiantum(xchacha20,aes)", true), + ("cbc(aes)", false), + ("cbc(des)", false), + ("cbc(des3_ede)", false), + ("cbc(paes)", false), + ("ctr(aes)", false), + ("ecb(aes)", false), + ("ecb(des)", false), + ("hctr2(aes)", true), + ("xts(aes)", true), + ("xts(camellia)", true), + ("xts(serpent)", true), + ("xts(twofish)", true), +]; + +/// Finds a cipher, as `skcipher_bind` of Linux. +pub(super) fn bind(name: &str) -> Result> { + super::check_restriction(name, ALLOW)?; + rk_crypto::alloc_skcipher(name) +} + +/// The state of an operation socket, as `struct af_alg_ctx` of Linux. +struct Ctx { + cipher: Box, + /// The data that waits for a receive. + queue: VecDeque, + /// The IV of the next receive. + iv: Vec, + /// A send came. + init: bool, + /// The last send had `MSG_MORE`. + more: bool, + /// Encrypt, not decrypt. + enc: bool, + /// A send waits for space in the queue. + write: bool, + /// The socket has no key yet. + nokey: bool, +} + +/// The settings of the control messages of one send, as `struct af_alg_control` of Linux. +#[derive(Default)] +struct Control { + iv: Option>, + op: u32, +} + +/// Reads the control messages of the level `SOL_ALG`, as `af_alg_cmsg_send` of Linux. +fn control(msg: &Message) -> Result { + let mut con = Control::default(); + let word = |data: &[u8]| -> Result { + let (w, _) = data.split_first_chunk::<4>().ok_or(Errno::EINVAL)?; + Ok(u32::from_ne_bytes(*w)) + }; + for (level, kind, data) in &msg.control { + if *level != SOL_ALG { + continue; + } + match *kind { + ALG_SET_IV => { + let len = word(data)? as usize; + let iv = data.get(4..).and_then(|d| d.get(..len)).ok_or(Errno::EINVAL)?; + con.iv = Some(iv.to_vec()); + } + ALG_SET_OP => con.op = word(data)?, + ALG_SET_AEAD_ASSOCLEN => { + if word(data)? >= 0x8000_0000 { + return Err(Errno::EINVAL); + } + } + _ => return Err(Errno::EINVAL), + } + } + Ok(con) +} + +/// An operation socket of the type `skcipher`. +pub(super) struct SkcipherSocket { + ino: u64, + bound: Arc, + ctx: SpinLock, + options: SpinLock, + wait: Arc, + events: AtomicU64, +} + +impl SkcipherSocket { + pub(super) fn new(bound: Arc, cipher: Box, nokey: bool) -> Arc { + let iv = vec![0u8; cipher.iv_size()]; + Arc::new(Self { + ino: next_ino(), + bound, + ctx: SpinLock::new(Ctx { + cipher, + queue: VecDeque::new(), + iv, + init: false, + more: false, + enc: false, + write: false, + nokey, + }), + options: SpinLock::new(Options::default()), + wait: Arc::new(WaitQueue::new()), + events: AtomicU64::new(0), + }) + } + + /// A socket with no key takes the key of the bound socket, as `skcipher_check_key` of Linux. + fn check_key(&self) -> Result<()> { + let mut ctx = self.ctx.lock(); + if !ctx.nokey { + return Ok(()); + } + self.bound.check_key(|tfm| { + if let Tfm::Skcipher(c) = tfm { + ctx.cipher = c.box_clone(); + } + })?; + ctx.nokey = false; + Ok(()) + } + + /// The free space of the queue, as `af_alg_sndbuf` of Linux. + fn space(&self, ctx: &Ctx) -> usize { + let buf = (self.options.lock().sndbuf as usize & !(PAGE_SIZE - 1)).max(PAGE_SIZE); + buf.saturating_sub(ctx.queue.len()) + } + + /// The size of the receive buffer, as `af_alg_rcvbuf` of Linux with nothing in use. + fn rcvbuf(&self) -> usize { + (self.options.lock().rcvbuf as usize & !(PAGE_SIZE - 1)).max(PAGE_SIZE) + } + + fn wake(&self) { + self.events.fetch_add(1, Ordering::Relaxed); + self.wait.wake_all(); + } + + /// Adds data to the queue, as `af_alg_sendmsg` of Linux. + fn send_data(&self, msg: &Message, flags: u32, nonblock: bool) -> Result { + let con = if msg.control.is_empty() && msg.rights.is_empty() && msg.cred.is_none() { + None + } else { + Some(control(msg)?) + }; + let enc = match con.as_ref().map(|c| c.op) { + None | Some(ALG_OP_DECRYPT) => false, + Some(ALG_OP_ENCRYPT) => true, + Some(_) => return Err(Errno::EINVAL), + }; + let mut ctx = self.ctx.lock(); + if let Some(iv) = con.as_ref().and_then(|c| c.iv.as_ref()) + && iv.len() != ctx.cipher.iv_size() + { + return Err(Errno::EINVAL); + } + if ctx.write { + return Err(Errno::EBUSY); + } + // The data of an operation that ended waits for a receive. + if ctx.init && !ctx.more && !ctx.queue.is_empty() { + return Err(Errno::EINVAL); + } + ctx.init = true; + if let Some(con) = con { + ctx.enc = enc; + if let Some(iv) = con.iv { + ctx.iv = iv; + } + } + let data = &msg.data; + let mut copied = 0; + let mut err = None; + while copied < data.len() { + let space = self.space(&ctx); + if space < PAGE_SIZE { + // `af_alg_wait_for_wmem` of Linux: wait for a receive. + if nonblock || flags & MSG_DONTWAIT != 0 { + err = Some(Errno::EAGAIN); + break; + } + ctx.write = true; + drop(ctx); + let r = wait_for(&self.wait, Timeout::default(), || { + let c = self.ctx.lock(); + Ok((self.space(&c) >= PAGE_SIZE).then_some(())) + }); + ctx = self.ctx.lock(); + ctx.write = false; + if let Err(e) = r { + err = Some(e); + break; + } + continue; + } + let n = space.min(data.len() - copied); + ctx.queue.extend(&data[copied..copied + n]); + copied += n; + } + if err.is_none() { + ctx.more = flags & MSG_MORE != 0; + } + let used = !ctx.queue.is_empty(); + drop(ctx); + if used { + self.wake(); + } + match err { + Some(e) if copied == 0 => Err(e), + _ => Ok(copied), + } + } + + /// One operation of a receive, as `_skcipher_recvmsg` of Linux. Gives the result, at most + /// `len` bytes. + fn recv_once(&self, len: usize, wait: bool) -> Result> { + let mut ctx = self.ctx.lock(); + let bs = ctx.cipher.chunk_size(); + if !ctx.init || (ctx.more && ctx.queue.len() < bs) { + // `af_alg_wait_for_data` of Linux. + if !wait { + return Err(Errno::EAGAIN); + } + drop(ctx); + wait_for(&self.wait, Timeout::default(), || { + let c = self.ctx.lock(); + Ok((c.init && (!c.more || c.queue.len() >= bs)).then_some(())) + })?; + ctx = self.ctx.lock(); + } + let used = ctx.queue.len(); + let mut n = len.min(used).min(self.rcvbuf()); + // While more data comes, take only full blocks. + if ctx.more || n < used { + n -= n % bs; + if n == 0 { + return Err(Errno::EINVAL); + } + } + let mut data: Vec = ctx.queue.drain(..n).collect(); + // `af_alg_pull_tsgl` of Linux: an operation that ended needs a new send. + if ctx.queue.is_empty() { + ctx.init = ctx.more; + } + let ctx = &mut *ctx; + if ctx.enc { + ctx.cipher.encrypt(&mut ctx.iv, &mut data)?; + } else { + ctx.cipher.decrypt(&mut ctx.iv, &mut data)?; + } + Ok(data) + } +} + +impl Drop for SkcipherSocket { + fn drop(&mut self) { + let nokey = self.ctx.lock().nokey; + self.bound.release(nokey); + } +} + +impl Socket for SkcipherSocket { + fn family(&self) -> u32 { + AF_ALG + } + + fn kind(&self) -> u32 { + SOCK_SEQPACKET + } + + fn ino(&self) -> u64 { + self.ino + } + + fn send(&self, _: &dyn PathOps, msg: Message, flags: u32, nonblock: bool) -> Result { + self.check_key()?; + self.send_data(&msg, flags, nonblock) + } + + /// Encrypts or decrypts the data of the queue, as `skcipher_recvmsg` of Linux. The receive + /// does operations until it has `len` bytes. An error after some data gives the data. As + /// Linux, a blocking receive of more bytes than the operation has waits for a new send. + fn recv(&self, len: usize, flags: u32, nonblock: bool) -> Result { + self.check_key()?; + let wait = !nonblock && flags & MSG_DONTWAIT == 0; + let mut out = Vec::new(); + let mut result = Ok(()); + while out.len() < len { + match self.recv_once(len - out.len(), wait) { + Ok(d) if d.is_empty() => break, + Ok(d) => out.extend_from_slice(&d), + Err(e) => { + result = Err(e); + break; + } + } + } + self.wake(); + if let Err(e) = result + && out.is_empty() + { + return Err(e); + } + let n = out.len(); + Ok(Received { data: out, len: n, ..Received::default() }) + } + + fn name(&self) -> Vec { + (AF_ALG as u16).to_ne_bytes().to_vec() + } + + fn get_option(&self, level: u32, name: u32) -> Result> { + if level != SOL_SOCKET { + return Err(Errno::EOPNOTSUPP); + } + get_socket_option(&self.options, name) + } + + fn set_option(&self, level: u32, name: u32, value: &[u8]) -> Result<()> { + if level != SOL_SOCKET { + return Err(Errno::EOPNOTSUPP); + } + self.options.lock().set(name, value) + } + + /// The events, as `af_alg_poll` of Linux. + fn poll(&self) -> u32 { + let ctx = self.ctx.lock(); + let read = if !ctx.more || !ctx.queue.is_empty() { POLLIN | POLLRDNORM } else { 0 }; + let write = + if self.space(&ctx) >= PAGE_SIZE { POLLOUT | POLLWRNORM | POLLWRBAND } else { 0 }; + read | write + } + + fn poll_wait(&self, table: &mut PollTable) { + table.add(&self.wait); + } + + fn event_count(&self) -> u64 { + self.events.load(Ordering::Relaxed) + } + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/net/rk-net/src/alg/tests.rs b/net/rk-net/src/alg/tests.rs index 5b6794e..3be492a 100644 --- a/net/rk-net/src/alg/tests.rs +++ b/net/rk-net/src/alg/tests.rs @@ -4,6 +4,7 @@ use super::*; use crate::NoPaths; use crate::consts::{MSG_MORE, MSG_TRUNC, SOCK_STREAM}; use alloc::vec; +use rk_abi::native::poll::{POLLIN, POLLOUT}; const ABC: &str = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; const EMPTY: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; @@ -151,3 +152,122 @@ fn hmac_key() { send(&*op, b"what do ya want for nothing?", 0).unwrap(); assert_ne!(digest(&*op), JEFE); } + +/// The key, the IV and the text of NIST SP 800-38A F.2.1, CBC-AES128. +const NIST_KEY: &str = "2b7e151628aed2a6abf7158809cf4f3c"; +const NIST_IV: &str = "000102030405060708090a0b0c0d0e0f"; +const NIST_PTEXT: &str = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51"; +const NIST_CTEXT: &str = "7649abac8119b246cee98e9b12e9197d5086cb9b507219ee95db113a917678b2"; + +fn unhex(s: &str) -> Vec { + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect() +} + +/// A send with the control messages of an operation: `op` and, if not empty, the IV. +fn send_op(s: &dyn Socket, data: &[u8], op: u32, iv: &[u8], flags: u32) -> Result { + let mut control = vec![(SOL_ALG, ALG_SET_OP, op.to_ne_bytes().to_vec())]; + if !iv.is_empty() { + let mut v = (iv.len() as u32).to_ne_bytes().to_vec(); + v.extend_from_slice(iv); + control.push((SOL_ALG, ALG_SET_IV, v)); + } + let msg = Message { data: data.to_vec(), control, ..Message::default() }; + s.send(&NoPaths, msg, flags, false) +} + +fn cipher_op(name: &str, key: &[u8]) -> Arc { + let s = bound("skcipher", name); + s.set_option(SOL_ALG, ALG_SET_KEY, key).unwrap(); + s.accept(false).unwrap() +} + +#[test] +fn skcipher_cbc() { + let (key, iv) = (unhex(NIST_KEY), unhex(NIST_IV)); + let (ptext, ctext) = (unhex(NIST_PTEXT), unhex(NIST_CTEXT)); + let op = cipher_op("cbc(aes)", &key); + assert_eq!(send_op(&*op, &ptext, skcipher::ALG_OP_ENCRYPT, &iv, 0), Ok(32)); + // A blocking receive of more than the data would wait for a new send, as Linux, so the + // receives here ask for the length of the data. + assert_eq!(op.recv(32, 0, false).unwrap().data, ctext); + // The operation ended, so a receive waits for a new send. + assert_eq!(op.recv(64, 0, true).err(), Some(Errno::EAGAIN)); + // A decryption in two sends: the IV of the second block comes from the first block. + send_op(&*op, &ctext[..16], skcipher::ALG_OP_DECRYPT, &iv, MSG_MORE).unwrap(); + assert_eq!(op.recv(16, 0, false).unwrap().data, ptext[..16]); + send(&*op, &ctext[16..], 0).unwrap(); + assert_eq!(op.recv(16, 0, false).unwrap().data, ptext[16..]); + // A receive in pieces that are smaller than the data. + send_op(&*op, &ptext, skcipher::ALG_OP_ENCRYPT, &iv, 0).unwrap(); + assert_eq!(op.recv(20, 0, false).unwrap().data, ctext[..16]); + assert_eq!(op.recv(16, 0, false).unwrap().data, ctext[16..]); + // A send with no control message keeps the operation, and the IV goes on from the last + // block. + send(&*op, &ptext, 0).unwrap(); + let mut c = rk_crypto::alloc_skcipher("cbc(aes)").unwrap(); + c.set_key(&key).unwrap(); + let (mut next, mut want) = (ctext[16..].to_vec(), ptext.clone()); + c.encrypt(&mut next, &mut want).unwrap(); + assert_eq!(op.recv(32, 0, false).unwrap().data, want); +} + +#[test] +fn skcipher_errors() { + let (key, iv) = (unhex(NIST_KEY), unhex(NIST_IV)); + let s = bound("skcipher", "cbc(aes)"); + assert_eq!(s.bind(&NoPaths, &addr("skcipher", "cbc(aes-generic)")).err(), Some(Errno::ENOENT)); + assert_eq!(s.bind(&NoPaths, &addr("skcipher", "sha256")).err(), Some(Errno::ENOENT)); + assert_eq!(s.set_option(SOL_ALG, ALG_SET_KEY, &key[..15]).err(), Some(Errno::EINVAL)); + // An operation socket before the key has no key. + let early = s.accept(false).unwrap(); + assert_eq!(send(&*early, b"x", 0).err(), Some(Errno::ENOKEY)); + s.set_option(SOL_ALG, ALG_SET_KEY, &key).unwrap(); + let op = s.accept(false).unwrap(); + // A receive with no send waits. + assert_eq!(op.recv(16, 0, true).err(), Some(Errno::EAGAIN)); + assert_eq!(op.accept(false).err(), Some(Errno::EOPNOTSUPP)); + // A bad operation, a bad IV and an unknown control message. + assert_eq!(send_op(&*op, &[0; 16], 2, &iv, 0).err(), Some(Errno::EINVAL)); + assert_eq!(send_op(&*op, &[0; 16], 1, &iv[..8], 0).err(), Some(Errno::EINVAL)); + let msg = Message { + data: vec![0; 16], + control: vec![(SOL_ALG, 9, vec![0; 4])], + ..Message::default() + }; + assert_eq!(op.send(&NoPaths, msg, 0, false).err(), Some(Errno::EINVAL)); + // The data of an operation that ended waits for a receive. + send_op(&*op, &[0; 16], 1, &iv, 0).unwrap(); + assert_eq!(send(&*op, &[0; 16], 0).err(), Some(Errno::EINVAL)); + op.recv(16, 0, false).unwrap(); + // A length that is not a multiple of the block, as af_alg05 of LTP. + send_op(&*op, &[0; 15], 0, &iv, 0).unwrap(); + assert_eq!(op.recv(16, 0, false).err(), Some(Errno::EINVAL)); + // While more data comes, a receive waits for a full block. + let op = s.accept(false).unwrap(); + assert_eq!(op.poll() & (POLLIN | POLLOUT), POLLIN | POLLOUT); + send_op(&*op, &[0; 8], 1, &iv, MSG_MORE).unwrap(); + assert_eq!(op.recv(16, 0, true).err(), Some(Errno::EAGAIN)); + send(&*op, &[0; 8], MSG_MORE).unwrap(); + assert_eq!(op.recv(16, 0, false).unwrap().data.len(), 16); + // More data comes and the queue is empty: the socket is not readable. + assert_eq!(op.poll() & (POLLIN | POLLOUT), POLLOUT); +} + +#[test] +fn skcipher_xts() { + let key: Vec = (0..32).collect(); + let iv = [7u8; 16]; + let op = cipher_op("xts(aes)", &key); + let mut c = rk_crypto::alloc_skcipher("xts(aes)").unwrap(); + c.set_key(&key).unwrap(); + // Whole blocks, and lengths with ciphertext stealing. + for len in [16, 17, 32, 100] { + let data: Vec = (0..len as u8).collect(); + let mut want = data.clone(); + c.encrypt(&mut iv.clone(), &mut want).unwrap(); + send_op(&*op, &data, skcipher::ALG_OP_ENCRYPT, &iv, 0).unwrap(); + assert_eq!(op.recv(len, 0, false).unwrap().data, want, "{len}"); + send_op(&*op, &want, skcipher::ALG_OP_DECRYPT, &iv, 0).unwrap(); + assert_eq!(op.recv(len, 0, false).unwrap().data, data, "{len}"); + } +}