4 releases (2 breaking)
new 0.3.1 | Apr 12, 2024 |
---|---|
0.3.0 | Apr 8, 2024 |
0.2.0 | Apr 2, 2024 |
0.1.0 | Mar 12, 2024 |
#241 in Cryptography
275 downloads per month
58KB
787 lines
rusTkey ("rusty key")
A library for developing bare-bones applications for the tillitis TKey in rust.
warning this library is still in development. There may be API-breaking changes in the future.
FIXME consider GPLv3 license given compatibility issues with GPLv2 and well-known licenses such as Apache 2.0
The crate provides:
- basic set-up for a new rust-based TKey application,
- functions to interact with the TKey device (encapsulating
unsafe
),
This library and the proposed set-up is chosen to keep things simple. The set-up itself requires no external dependencies and the linker-script and initial assembly-code are almost exactly as provided by tillitis.
note a few points of attention:
rustTkey
is provided to enable development in Rust, not as a statement against C. Although Rust is praised for safer, more secure applications, I do not think the difference is always as big as proclaimed. Applications written in C, e.g. with a stack-only development approach, already avoid some issues. It may prove beneficial to consider the larger ecosystem before choosing either one.rusTkey
will panic in case of improper use: there is no point in obscuring or working around incorrect uses. There is more risk in making things seem okay. Instead, assertions and panics will signal improper use (or a bug in rusTkey).
Changes
Changelog:
0.3.1
rustkey::random
: proper unsafe access to buffer without using&mut
(static_mut_refs
)
0.3.0
rustkey::random
: remix existing TRNG entropy if TRNG is not yet ready. Given TRNG's rate of approx. 66.6 times per second, this improves performance.- Renamed
bench
frombench_trng
, as it tests a few more aspects of TKey. - Moved changelog into
README.md
fromCHANGELOG.md
, to make it available wherever the README is readable.
0.2.0
- Module
cpumonitor
:monitor_range
allowfirst
andlast
to be same address. - Module
timer
:- provide
PRESCALE_SECONDS
prescaler constant. - provide
sleep
for timer-based blocking sleep in seconds.
- provide
- Module
touch
: tweak, improverequest
. - Module
trng
:- comment on production-rate of entropy
- add
read_next
, renameread_next_bytes
toread_bytes_next
.
- Module
frame
providesLENGTH_MAX
for maximum length of frame according to the protocol. Error
derivesDebug
trait.README.md
: comments documenting some configuration options, ref for minimizing rust binaries, clean-up.bench_trng
: application used to perform repeated sampling of TRNG entropy for purpose of testing performance. Seems to be stable at approximately 66.6 samples per second.
0.1.0
Initial release.
Open issues
These are known open issues.
rustkey::random
represents a random-bytes generator that should be (somewhat) cryptographically secure. This has not been verified.
It makes use ofblake2s
from firmware to produce randomness, with a persistent buffer to maintain some state between uses, and bytes from the TRNG to introduce new entropy into the mix. Theseed
parameter allows injecting additional entropy manually.cargo build
warns of an unstable feature. Maybe we should file an issue for this.cargo build --release --bins warning: unknown and unstable feature specified for `-Ctarget-feature`: `zmmul` | = note: it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future = help: consider filing a feature request
Apart from items listed here, I leave TODO
comments for things that need to be considered, even if they turn out to be irrelevant.
Decisions
- No heap/dynamic memory allocation (for now).
- use crate 'heapless' for some data structures implementation on stack.
- Not thread-safe (not needed until
std
or customized threading). - No use of dependencies w.r.t. bootstrapping the execution (such as rust-embedded/riscv):
- adds a layer of intransparency, non-straightforward logic.
- more comprehensive initialization which addresses features that are not supported, performing initialization that is not needed.
- instead, when initializing in global assembly, execution from entry-point (linker-script) to initialization to
main
is obvious.
- Incorrect use of the functions may result in panic.
- Implementations (in rare cases) may change.
For example, ifrandom
can be improved within reasonable constraints.
Getting started
The following describes the application set-up needed to build app binaries for loading onto the TKey.
The following steps are required:
- Configure the build for
riscv32i-unknown-none-elf
, with additional CPU features 'c
' (compressed instructions) and 'Zmmul
' (multiplication instructions only, i.e. not division). Other build-options contribute to a smaller binary such that it fits in 128 KiB of memory. - Apply a suitable linker-script.
- Create an entry-point that performs basic initialization of the TKey device for the application.
- Use
llvm-objcopy
to create the raw bare-bones application binary for the tillitis TKey. - The application:
src/main.rs
is an example that uses this set-up and demonstrates a working TKey-app.
Configure the build
The build target riscv32i-unknown-none-elf
must be available in the rust ecosystem.
rustup target add riscv32i-unknown-none-elf
The following build configuration enables the necessary features and options.
.cargo/config.toml
:
[build]
target = "riscv32i-unknown-none-elf"
[profile.dev]
panic = "abort"
[profile.release]
codegen-units = 1 # No parallelism, may increase ability to optimize.
debug = false # Do not produce a debug-build.
debug-assertions = false # Drop debug-assertions.
opt-level = 3 # Perform a high level of optimization, also 'z' to specifically target size.
overflow-checks = false
strip = true # Currently translates to 'strip = "symbols"', i.e. strip all excess information.
lto = true # Perform link-time optimization, to further optimize and reduce binary size.
panic = "abort"
[target.riscv32i-unknown-none-elf]
rustflags = [
"-Ctarget-feature=+c,+zmmul",
"-Ccode-model=medium",
"-Cpanic=abort",
"-Crelocation-model=pic",
"-Clink-arg=-Tapp.lds",
]
This configuration applies to --release
builds.
Note: the panic
settings should obviate the need for .eh_frame
section in the linker-script, but this is not currently the case.
Linker-script
The following linker-script defines the memory region for the TKey.
Changes to the original linker-script:
- defining
_stack_start
symbol such that this address can be modified outside of assembly-code. _stack_start
is assigned a different value: 'ORIGIN(RAM)+LENGTH(RAM)
' from the original value '0x4001fff0
'. The original value is slightly lower, which I suspect is unnecessary given that the stack-pointer is manipulated before use. (issue)- added
*(.eh_frame)
to the.text
region.
This should not be necessary, becausepanic=abort
should obviate the need for.eh_frame
section according to many references and from my own understanding, as it disables stack-unwinding. There should be no need to keep extra data. This changed, however, quite suddenly with no clear explanation. Adding the.eh_frame
section produces a memory-layout that accomodates for this, although it would be better if we could drop it completely.
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
OUTPUT_ARCH( "riscv" )
ENTRY(_start)
MEMORY
{
RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */
}
SECTIONS
{
.text.init :
{
*(.text.init)
} >RAM
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.srodata) /* .rodata sections (constants, strings, etc.) */
*(.srodata*) /* .rodata* sections (constants, strings, etc.) */
*(.eh_frame)
. = ALIGN(4);
_etext = .;
_sidata = _etext;
} >RAM
.data : AT (_etext)
{
. = ALIGN(4);
_sdata = .;
. = ALIGN(4);
*(.data) /* .data sections */
*(.data*) /* .data* sections */
*(.sdata) /* .sdata sections */
*(.sdata*) /* .sdata* sections */
. = ALIGN(4);
_edata = .;
} >RAM
/* Uninitialized data section */
.bss :
{
. = ALIGN(4);
_sbss = .;
*(.bss)
*(.bss*)
*(.sbss)
*(.sbss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} >RAM
}
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
Create an entry-point
The following (global) assembly-code create the entry-point _start
at which the device is initialized for the loaded application, after which function main
is called.
The response of the device on returning from 'main
' seems to deviate based on the optimization-level of the application binary. It is best to define main as '#[no_mangle] extern "C" fn main() -> !
', i.e. non-returning as to avoid running into this situation.
Changes to the original assembly-code:
- to expect symbol
_stack_start
to be defined in the linker-script for initialization of the stack-pointer.
// Note: this assembly provides the initialization procedure that clears memory and finally calls
// `main` to start execution of the app-binary.
global_asm!(
".section \".text.init\"",
".global _start",
"_start:",
"li x1, 0",
"li x2, 0",
"li x3, 0",
"li x4, 0",
"li x5, 0",
"li x6, 0",
"li x7, 0",
"li x8, 0",
"li x9, 0",
"li x10,0",
"li x11,0",
"li x12,0",
"li x13,0",
"li x14,0",
"li x15,0",
"li x16,0",
"li x17,0",
"li x18,0",
"li x19,0",
"li x20,0",
"li x21,0",
"li x22,0",
"li x23,0",
"li x24,0",
"li x25,0",
"li x26,0",
"li x27,0",
"li x28,0",
"li x29,0",
"li x30,0",
"li x31,0",
/* init stack below 0x40020000 (TK1_RAM_BASE+TK1_RAM_SIZE) */
"la sp, _stack_start",
/* zero-init bss section */
"la a0, _sbss",
"la a1, _ebss",
"bge a0, a1, end_init_bss",
"loop_init_bss:",
"sw zero, 0(a0)",
"addi a0, a0, 4",
"blt a0, a1, loop_init_bss",
"end_init_bss:",
"call main",
options(raw)
);
Create the raw application-binary
Use 'llvm-objcopy --input-target=elf32-littleriscv --output-target=binary target/riscv32i-unknown-none-elf/release/example example.bin
' to produce the raw binary data for loading onto the TKey.
The application
At this point, the set-up is done up to entering the application at function main
.
There are a few recommendations:
- '
#![no_std]
' to prevent standard library from being included. This also means thatstd::*
is unavailable, although most primitive functions can also be found atcore::*
. - '
#![no_main]
' to indicate that we provide our own entry-point into the application - '
#[no_mangle] extern "C" fn main() -> !
' as our definition for main, such that:main
can be found after initialization,#[no_mangle] extern "C"
to ensure availability under the expected, unchanged function-name,- non-returning, to avoid running into undefined behavior and/or making the device unavailable.
- need to define a
#[panic_handler]
, given that the run-time is minimized. - heapless provides static-friendly data-structures that do not require dynamic memory allocation.
An basic example src/main.rs
can be found in this code-base.
Alternative
An alternative approach, is to use a #![feature(start)]
macro with #[start]
annotated fn main()
. This is currently only possible if Rust nightly features are activated. This, together with appropriate build configuration to ensure that main
as a function is available in unmangled form, is sufficient get the application to run. Unfortunately, this does not resolve the issue with .eh_frame
described above.
For now, this crate will keep using the global_asm!
macro in an application. This creates a straight-forward path from linker-script (app.lds
) to global assembly (_start
) to calling main()
. This also leaves open possibilities for a call to a set-up of stack-protector and other security-features that need early initialization, with everything concentrated within the application.
LICENSE
rusTkey, a rust crate/library that provides a development API for the
tillitis TKey.
Copyright (C) 2024 Danny van Heumen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
See LICENSE
for the full license.
References
- Minimizing Rust Binary Size
- Note to self: Last evaluated: 66a1fd90eead93d9e0472a3a1a88e185ae8c1761
TODO support for heap memory (dynamic allocation)?
- Embedded RiscV (riscv, riscv-rt, etc.) code-base
- Heap Allocation