Skip to content
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

drivers/at86rf2xx; spi security module (AES) #14113

Merged
merged 2 commits into from
Aug 12, 2020

Conversation

fabian18
Copy link
Contributor

Contribution description

The at86rf2xx IEEE 802.15.4 transceivers are capable to perform AES in hardware and support block cipher modes ECB and CBC.

This PR adds a pseudomodule in the at86rf2xx driver. You will be able to perform hardware encryption and decryption of blocks of data using ECB and CBC block ciphers. Nothing is changed from the actual driver.

Only SPI transceivers are supported, for now. Although for MCU integrated transceivers atmega128rfa1 and atmega256rfr2 it is even simpler and could be added later.
The SPI security module supports fast SPI, which means that you will read output block i, if you write input block i + 1 to the SPI bus.

Testing procedure

I provided a test in tests/driver_at86rf2xx_aes/.
Test vectors are taken from: RFC3602

Issues/PRs references

None

@benpicco benpicco added Area: security Area: Security-related libraries and subsystems Type: new feature The issue requests / The PR implemements a new feature for RIOT labels May 21, 2020
Copy link
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!
Neat to have this feature.
Just a few questions:

  • do we need to take the radio state into account?
  • if we want to encrypt payload, do we really have to write the plaintext onto the device, read it back, and then send it? I would have imagined it would have a mode where it would handle encryption / decryption transparently.

Comment on lines +31 to +41
static inline
void at86rf2xx_spi_get_bus(const at86rf2xx_t *dev)
{
spi_acquire(dev->params.spi, dev->params.cs_pin, SPI_MODE_0, dev->params.spi_clk);
}

static inline
void at86rf2xx_spi_release_bus(const at86rf2xx_t *dev)
{
spi_release(dev->params.spi);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could move those to at86rf2xx_internal.h if you want.
But might also be done in a later patch. I was wondering why you didn't just use at86rf2xx_sram_read() and friends, but I guess the problem there is that they will all acquire and release the bus.

Only recently I have learned that this is a bad idea (and even more so after the recent power optimizations after release() on sam0), so the right courser of action would be to convert the driver to not acquire() and release() the bus, just like you did here.

But that would be another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I guess the problem there is that they will all acquire and release the bus.

Yes.
And further at86rf2xx_sram_read() writes dummy bytes to the transceiver, while
at86rf2xx_sram_write() does not care what it reads from the transceiver.
For fast SPI, you read the result from the last block operation, while you write the next block.

drivers/at86rf2xx/aes_spi.c Outdated Show resolved Hide resolved
drivers/at86rf2xx/aes_spi.c Outdated Show resolved Hide resolved
drivers/at86rf2xx/aes_spi.c Outdated Show resolved Hide resolved
@jia200x
Copy link
Member

jia200x commented Jun 2, 2020

do we need to take the radio state into account?

No. The AES component is independent of the transceiver. It's indeed possible to encrypt/decrypt while sending/receiving (assuming the device is not sleeping).

if we want to encrypt payload, do we really have to write the plaintext onto the device, read it back, and then send it? I would have imagined it would have a mode where it would handle encryption / decryption transparently.

Yes :(. It's required to write the plaintext packet and then read it from SRAM since it's a "generic" AES crypto accelerator (intended to be used with IEEE802.15.4, but other use cases are also possible)

}

static inline
uint8_t _aes_status(at86rf2xx_t *dev)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't this be replaced with the at86rf2xx_sram_xxx functions (from 'at86rf2xx_internal.c`)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

static inline
void _aes_open_read(at86rf2xx_t *dev, uint8_t addr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

static inline
void _aes_open_write(at86rf2xx_t *dev, uint8_t addr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot reuse them because, they acquire / release the bus in each call and at86rf2xx_sram_write() ignores what is read from the transceiver, which is important for fast SPI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm I would say that these are "presentation layer" functions (not specific to AES). Maybe they can be added as generic functions? What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally I agree, they could be moved to at86rf2xx_internal because they are not related to the security module. But I am not sure if I should do this because nothing else uses it, so far.

On the one side it touches the actual driver, and I think this should be done, if some day someone decides to modify the at86rf2xx SPI functions in a way that they do not acquire / release the SPI bus for every call. Right now I see no benefit of moving them.

On the other side it doesn´t harm because it is a small change.

I would keep them in place for now.

@fabian18
Copy link
Contributor Author

fabian18 commented Jun 2, 2020

Git is showing a weird diff for 39c23ac.
But it just does the

    if (!nblocks) {
        return 0;
    }

thing.

else {
puts("CBC test 3 failed");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good if the test would print SUCCESS at the end.
Then you can also add a tests/01-run.py with

#!/usr/bin/env python3

import sys
from testrunner import run

def testfunc(child):
    child.expect('START')
    child.expect('ECB test 1 done')
    child.expect('ECB test 2 done')
    child.expect('CBC test 1 done')
    child.expect('CBC test 2 done')
    child.expect('CBC test 3 done')
    child.expect('SUCCESS')

if __name__ == "__main__":
    sys.exit(run(testfunc))

so we can have make test work for automatic tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm looks like you'll have to add a puts("START"); at the beginning of the test too.
This and chmod a+x tests/01-run.py.

Then make test will work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just remove matching start, test_utilis_interactive_sync will already match the start for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ups forgot that ...

}
uint8_t cfg = AT86RF2XX_AES_CTRL_AES_MODE__ECB |
AT86RF2XX_AES_CTRL_AES_DIR__ENC;
uint8_t mirror = cfg | AT86RF2XX_AES_CTRL_MIRROR_MASK__AES_REQUEST;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's that mirror about?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mirror must be the same value as the cfg and is transfered to the slave to start an AES operation, if AT86RF2XX_AES_CTRL_MIRROR_MASK__AES_REQUEST is set.

Comment on lines +62 to +73
while (!(status & AT86RF2XX_AES_STATUS_MASK__AES_DONE)) {
AES_DEBUG("status: %02x\n", status);
status = _aes_status(dev);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we still get the AT86RF2XX_AES_STATUS_MASK__AES_ER bit set here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No the error bit is set before the transceiver has processed a data block. There are two cases:

  1. The delay between initiating an AES operation and sending the next cfg to AT86RF2XX_REG__AES_CTRL was too short. Meaning the transceiver did not have enough time to process the current block.
  2. Less then 16 bytes of data have been sent to the transceiver. This never happens in the code, so this case cannot arrive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better add that as a comment so in the future this question doesn't come up again :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the first case should not arrive either because I am polling if the operation is complete.
I could make an assert() that the error bit is never set and change the return types of all functions to be void because that was the only thing that I expected that could go wrong. Every time I witnessed the error bit was set, there was a mistake in the code.
Or there may be something that I am not aware of and the current implementation would be a little safer.
But I don´t think so. Even when I reduce AT86RF2XX_AES_DELAY_US, the error is not set because the polling is safe.
So I would like to turn it into an assert()

@benpicco
Copy link
Contributor

This looks pretty good.
Some more comments would be nice as the code is pretty dense.

I'd like to have this, though it's not a requirement, it would sure encourage the work on MAC layer security. :)

@fabian18
Copy link
Contributor Author

Now I think it´s time to squash.

Copy link
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me and the automatic test is also working.
Please squash.

@fabian18 fabian18 force-pushed the at86rf2xx_spi_security_module branch from 50ae1b1 to 6bb3d81 Compare August 12, 2020 15:20
@benpicco benpicco added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Aug 12, 2020
Comment on lines +3 to +20
DISABLE_MODULE += auto_init_at86rf2xx

# define the driver to be used for selected boards
ifneq (,$(filter samr21-xpro,$(BOARD)))
DRIVER := at86rf233
endif
ifneq (,$(filter iotlab-m3 fox,$(BOARD)))
DRIVER := at86rf231
endif
ifneq (,$(filter mulle,$(BOARD)))
DRIVER := at86rf212b
endif

# use the at86rf231 as fallback device
DRIVER ?= at86rf231

# include the selected driver
USEMODULE += $(DRIVER)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need the base driver here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for this in main.c:

    at86rf2xx_setup(&dev, &at86rf2xx_params[0]);
    dev.netdev.netdev.event_callback = _event_cb;
    if (dev.netdev.netdev.driver->init(&dev.netdev.netdev) != 0) {
        return EXIT_FAILURE;
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea but do we need it?
I mean if we have to init the radio to use crypto that's fine.
But if we just init it and then never use it, we might as well drop it.

Copy link
Contributor Author

@fabian18 fabian18 Aug 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don´t think we need the driver capability but it initializes the params, SPI and checks if SPI is working.
You mean I can copy the params and init SPI manually? Then the driver is not needed. Is this better?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I would have to make an own driver rather then a pseudomodule, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A never mind then.
A separate init function would be possible, but that's not worth the hassle now.

If someone really wants to use the crypto without the radio, that could be added later.

@fabian18
Copy link
Contributor Author

I am going to insert two empty lines in the python script to make travis happy and squash right away.

@fabian18 fabian18 force-pushed the at86rf2xx_spi_security_module branch from 6bb3d81 to 7fe64c5 Compare August 12, 2020 15:48
@fabian18
Copy link
Contributor Author

Then I am going to add a Makefile.ci, to make murdock happy.

@fabian18 fabian18 force-pushed the at86rf2xx_spi_security_module branch from 7fe64c5 to 74eec82 Compare August 12, 2020 16:55
@benpicco benpicco merged commit add10b5 into RIOT-OS:master Aug 12, 2020
@fabian18
Copy link
Contributor Author

Hooray 🎉 , thanks for merging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: security Area: Security-related libraries and subsystems CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants