New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ieee802154] CCM* implementation and new symmetric encryption HIL #684
Conversation
- hil::symmetric_encryption - provide distinct traits for Ctr/CBC modes so we can be sure these hardware features exist at compile time - allow distinct src/dst encryption or in-place operation - fix up sam4l::aes to match the new interface, and track input/output with the hardware properly - add TakeCell.map_or_else() - tests are in boards/imix/src/aes_test.rs, and may be run by adding this just before the call to `kernel::main()`: aes_test::run(); NOTE: this commit removes capsules::symmetric_encryption, the userspace interface to AES. Future work will have to patch that back onto the modified HIL, and perhaps also provide a mux for userspace+kernel users.
These methods now have the same semantics as put/take for a TakeCell, except that when the hardware is busy (between a call to crypt() and the following invocation of crypt_done()), they have degenerate behavior: put has no effect, and take returns None.
This changes the interface once more so that there is no opportunity for confusion over the state of buffer ownership.
This build is failing due to the nrf AES implementation having not been updated to match the new HIL. I'm not familiar with how the nrf hardware works or if there are any subtleties about the current AES implementation. Can someone chime in if there should be anything special I should be looking for when porting it over? |
@bbbert I can have a look ;) |
Well, the AES implementation is only AES_CTR and not AES-CCM.
|
Is it feasible that we could update the capsule to match the new HIL before merging this PR? It'd be a bummer to lose that userspace support. |
What is the state of this PR? I can probably fix the NRF5X ports to the new capsule if you help with fixing user-space support to the new capsule! |
@niklasad1 I believe nothing has changed since the last discussion about whether to update the NRF5X AES capsule or drop support. I'd be happy to work on updating the userspace capsule. Could you take a look at the NRF5X AES driver and see if it can easily be refactored to fit the new HIL? Thanks! |
@bbbert But so far two things comes to mind:
pub trait AES128<'a> {
const KEY_SIZE = 16;
const BLOCK_SIZE = 16;
// methods
.....
} |
|
||
let start = DATA_OFFSET; | ||
let stop = DATA_OFFSET + DATA_LEN; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why start at DATA_OFFSET
(16)?
This will make my implementation overflow because I assume the output is as big the input and I don't use start and stop!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that the point of the AES refactor is to allow the AES hardware to operate on arbitrary ranges of slices. Previously you could only encrypt/decrypt a mutable buffer from the beginning, because if you had sliced into it and passed it to the driver, you would never get the original slice back. In other words, whenever you wanted to encrypt only part of a buffer, you would need a copy just because of the HIL's restrictiveness. There are also some new options for encrypting/decrypting different parts of buffers: for example if the source isn't present, then in-place encryption should be done directly in the destination buffer.
As for why exactly it's 16, I imagine that's just an arbitrary offset for testing purposes, but @daniel-scs might know better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, makes sense. I was a little bit concerned about this on nrf51dk which only has 16kB RAM but it seems to fit. You can check my PR
boards/imix/src/aes_test.rs
Outdated
fn static_init_test() -> &'static mut Test<'static, Aes<'static>> { | ||
unsafe { | ||
let source = static_init!([u8; 4 * AES128_BLOCK_SIZE], [0; 4 * AES128_BLOCK_SIZE]); | ||
let data = static_init!([u8; 6 * AES128_BLOCK_SIZE], [0; 6 * AES128_BLOCK_SIZE]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the data
buffer 6 blocks instead of 4 blocks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I think these are some arbitrary sizes to facilitate testing the ability to encrypt a range into a different part of a slice, but I might be wrong.
@niklasad1 Sure, thanks. Side note: my
|
I think you summarized the issue very well. Roughly the BTW, |
/// Call before `AES128::crypt()` to perform AES128CBC | ||
fn set_mode_aes128cbc(&self, encrypting: bool); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't want to be annoying but I would like the see consistency in the naming of the traits.
I think it has been discussed earlier that the first letter should uppercase and the rest lowercase?
So, I suggest:
- Aes128Ctr
- Aes128Cbc
Thoughts?
I have been thinking about AES userspace interface a little bit in regard to to the new HIL. use core::cell::Cell;
use kernel::{AppId, AppSlice, Callback, Driver, Grant, ReturnCode, Shared};
use kernel::common::take_cell::TakeCell;
use kernel::hil::symmetric_encryption::{Client, AES128, AES128Ctr, AES128CBC};
/// Syscall number
pub const DRIVER_NUM: usize = 0x40000;
/// This enum shall keep track of the state of the AESDriver
#[derive(Copy, Clone, Debug, PartialEq)]
enum AesState {
Idle,
Encrypt,
Decrypt,
SetKey,
SetIv,
}
#[allow(dead_code)]
#[derive(Copy, Clone)]
enum ConfidentialityMode {
ECB = 0,
CBC,
CFB,
OFB,
Ctr,
}
pub struct App {
callback: Option<Callback>,
key: Option<AppSlice<Shared, u8>>,
input: Option<AppSlice<Shared, u8>>,
output: Option<AppSlice<Shared, u8>>,
iv: Option<AppSlice<Shared, u8>>,
state: Option<AesState>,
}
impl Default for App {
fn default() -> App {
App {
callback: None,
key: None,
input: None,
output: None,
iv: None,
state: Some(AesState::Idle),
}
}
}
pub struct AesDriver<'a, A: AES128<'a> + 'a> {
crypto: &'a A,
apps: Grant<App>,
kernel_key: TakeCell<'static, [u8]>,
kernel_input: TakeCell<'static, [u8]>,
kernel_output: TakeCell<'static, [u8]>,
kernel_iv: TakeCell<'static, [u8]>,
busy: Cell<bool>,
current_app: Cell<Option<AppId>>,
}
impl<'a, A: AES128<'a> + 'a> AesDriver<'a, A> {
pub fn new(crypto: &'a A,
grant: Grant<App>,
key: &'static mut [u8],
input: &'static mut [u8],
output: &'static mut [u8],
iv: &'static mut [u8])
-> AesDriver<'a, A> {
AesDriver {
crypto: crypto,
apps: grant,
kernel_key: TakeCell::new(key),
kernel_input: TakeCell::new(input),
kernel_output: TakeCell::new(output),
kernel_iv: TakeCell::new(iv),
busy: Cell::new(false),
current_app: Cell::new(None),
}
}
}
impl<'a, A: AES128<'a>> Driver for AesDriver<'a, A> {
fn allow(&self, appid: AppId, allow_num: usize, slice: AppSlice<Shared, u8>) -> ReturnCode {
match allow_num {
// Allocate input buffer
0 => ReturnCode::SUCCESS,
// Allocate output buffer
1 => unimplemented!(),
// Allocate key buffer
2 => unimplemented!(),
// Allocate iv buffer (iv or initial counter)
3 => unimplemented!(),
_ => ReturnCode::EINVAL,
}
}
fn subscribe(&self, subscribe_num: usize, callback: Callback) -> ReturnCode {
// callback to user-space
unimplemented!()
}
fn command(&self, cmd: usize, sub_cmd: usize, _: usize, _: AppId) -> ReturnCode {
match cmd {
// Check if exists
0 => ReturnCode::SUCCESS,
// Set Confidentiality Mode
1 => unimplemented!(),
// Enable (maps direectly to the HIL)
2 => unimplemented!(),
// Disable (maps direectly to the HIL)
3 => unimplemented!(),
// Start message (maps directly to the HIL)
4 => unimplemented!(),
// Encrypt
// here we need to set the busy flag i.e., we need mutual exclusion to the chip
// and store AppId in shared region inorder to know which app to return the result to
5 => unimplemented!(),
// Decrypt
// here we need to set the busy flag i.e., we need mutual exclusion to the chip
// and store AppId in shared region inorder to know which app to return the result to
6 => unimplemented!(),
_ => ReturnCode::EINVAL,
}
}
}
impl<'a, A: AES128<'a>> Client<'a> for AesDriver<'a, A> {
fn crypt_done(&'a self, source: Option<&'a mut [u8]>, dest: &'a mut [u8]) {
unimplemented!()
}
} Thoughts? |
* The CBC-test fails because it is not supported. I suggest to change the test structure and the trait bounds accordingly
* Because the AES tests depended on trait bounds on both AES128Cbc and AES128Ctr => both the traits needed to implemented * Since NRF5x only support CTR ended up in test failed * That's why I splitted up them (I have not verified that tests are correct though) * The test passed on NRF5x
Add nik's changes to ccm branch so that we can make sure everything works
Uh oh... I just merged #692, which reorganized some of the mac layer, cause a bunch of conflicts in this PR now. I don't think they look super hard to resolve, but might require moving changes between files. |
@niklasad1 Your port looks great! Everything seems to be working now, I'm holding off including the userspace facing driver in this PR because it's a significant change on its own. @alevy Okay, I'm now super confused about where everything went. Is XMAC meant to be building on top of an existing MAC layer, or does it replace one interchangeably? I could distribute all my changes across those files by matching old code, but I have no guarantees that it'll even work. |
boards/nrf52dk/src/aes_test.rs
Outdated
} | ||
|
||
fn static_init_test() -> &'static mut TestAes128Ctr<'static, AesECB<'static>> { | ||
unsafe { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is minor, since it's in the test, but when using unsafe
:
-
Use it inside the function body, if the block exposes completely safe behavior: i.e., if there is no way to call the function in a way that could violate type-safety (for exmaple, by calling a function twice that allocates a value in static memory).
-
Use it to mark a function
unsafe
if the function needs to do unsafe things and it doesn't necessarily expose a totally safe API.
I think these initializers in the test harnesses fall into the second category, so should be
unsafe fn static_init_test() -> &'static mut TestAes128Ctr<'static, AesECB<'static>> {
and
pub unsafe fn run()
they are then called from the board's reset_handler
, which is also unsafe
and we're good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s great rules but it is still a grey area with mutating static buffers and dereferencing memory mapped I/O (data races)
Is it ”your rules” or idiomatic Rust?
I think I haven’t followed these rules to 100% to be honest!
|
||
/// The number of bytes used for AES block operations. Keys and IVs must have this length, | ||
/// and encryption/decryption inputs must be have a multiple of this length. | ||
pub const AES128_BLOCK_SIZE: usize = 16; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that despite commenting that you would add a constant for key size 16 days ago in response to @niklasad1 's suggestion, it appears you still assume all key sizes will be 128 bits. It doesn't look like this ended up happening, though I suppose that since all of the constant names imply this implementation is only for AES128 that is fine?
fn set_client(&self, client: &'static Client); | ||
/// Implement this trait and use `set_client()` in order to receive callbacks from an `AES128` | ||
/// instance. | ||
pub trait Client<'a> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this should be "AESClient" if the related trait defined below is called "CCMClient". Just "Client" seems a little vague
); | ||
aes_ccm.set_client(mac_device); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify: There shouldn't be any problem calling this function from a "Thread" capsule to set the client as the Thread control plane for instances when MLE messages are being sent and the link layer is not encrypting messages, correct? It would obviously need a reference to the mac_device as well so that it could set the client back once the MLE process was complete. I could see this being an issue in cases where one app is running Thread and another is simply trying to communicate using encrypted 15.4 - somehow there would need to be a mechanism to ensure that the mac_device is always set as the client when link-layer encryption is needed, while also allowing Thread to be set as the client when above-link-layer encryption is needed for MLE messages.
That seems like the only potential issue which could hinder the 6lowpan/Thread development down the road, and even if it is something that will eventually have to change I think the current implementation is fine and will not hinder the current 6lowpan/Thread development as I expect getting CCM* working on MLE messages will be one of the final steps towards a SED
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the current implementation looks generally good, and should not hinder the current development towards 6lowpan/Thread. I am not sure I entirely understand the rules governing how the client of AES/CCM can be assigned and I suspect this could eventually need to be addressed to allow for a Thread SED, as described in one of my comments, but regardless I think it is okay to merge this implementation for the 1.0 release.
@@ -380,6 +366,9 @@ pub unsafe fn reset_handler() { | |||
&mut PROCESSES, | |||
FAULT_RESPONSE, | |||
); | |||
|
|||
aes_test::run(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to do this by default, do we?
Pull Request Overview
capsules::aes_ccm
: CCM* implementation on top of newsymmetric_encryption
HILcapsules::ieee802154
MacDevice
to use the above for link-layer securitycapsules::test::aes_ccm
Testing Strategy
CCM* test vectors encrypt and decrypt correctly on an imix.
TODO or Help Wanted
Documentation Updated
/docs
have been updated or no updates are required.Formatting
make formatall
has been run.