Skip to content

Commit

Permalink
add rust bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisnc committed Dec 13, 2024
1 parent c0f66b0 commit f33f834
Show file tree
Hide file tree
Showing 13 changed files with 482 additions and 5 deletions.
15 changes: 10 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ jobs:
sudo apt-get update -qq
sudo apt-get install -qq clang gcc-arm-none-eabi gcc-powerpc-linux-gnu \
llvm scons
- name: Linux Host Build
- name: Linux Build
run: |
scons --jobs "$(nproc)"
- name: Linux Host Build -march=penryn
- name: Linux Build -march=penryn
run: |
scons --jobs "$(nproc)" --host-march=penryn
- name: Linux Host Build -march=nehalem
- name: Linux Build -march=nehalem
run: |
scons --jobs "$(nproc)" --host-march=nehalem
- name: Linux Host Build -march=skylake
- name: Linux Build -march=skylake
run: |
scons --jobs "$(nproc)" --host-march=skylake
- name: Linux Host Build with Sanitizers
- name: Linux Build with Sanitizers
run: |
scons --jobs "$(nproc)" --sanitize
- name: arm-eabi Cross Build
Expand All @@ -38,6 +38,11 @@ jobs:
- name: powerpc-linux Cross Build
run: |
scons --jobs "$(nproc)" --target=powerpc-linux
- name: Linux Rust Build
run: |
rustup update
cargo check --all-targets
cargo test
macos-test:
name: macOS Test
Expand Down
2 changes: 2 additions & 0 deletions .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
42 changes: 42 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[package]
name = "liblithium"
version = "0.1.0"
authors = ["Chris Copeland <[email protected]>"]
description = "A lightweight and portable cryptography library"
license = "Apache-2.0"
repository = "https://github.com/teslamotors/liblithium"
documentation = "https://docs.rs/liblithium"
keywords = ["x25519", "gimli", "signatures"]
categories = ["cryptography", "embedded", "no-std"]
edition = "2021"
rust-version = "1.82.0"
build = "rust/build.rs"
autobins = false
autoexamples = false
include = ["/src/", "/include/", "/rust/", "!SCons*", "!Dockerfile", "!*.bash"]

[lib]
path = "rust/src/lib.rs"

[build-dependencies]
bindgen = "0.71"
cc = { version = "1.2", features = ["parallel"] }

[profile.release]
debug = true

[[bin]]
name = "gimli-hash"
path = "rust/bin/hash.rs"

[[bin]]
name = "lith-keygen"
path = "rust/bin/keygen.rs"

[[bin]]
name = "lith-sign"
path = "rust/bin/sign.rs"

[[bin]]
name = "lith-verify"
path = "rust/bin/verify.rs"
30 changes: 30 additions & 0 deletions rust/bin/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Part of liblithium, under the Apache License v2.0.
// SPDX-License-Identifier: Apache-2.0

use std::{
env,
fs::read,
io::{stdin, Read},
};

use liblithium::gimli_hash::{hash_hex_string, DEFAULT_LEN};

fn main() -> std::io::Result<()> {
let mut args = env::args();
let _prog = args.next();
if args.len() < 1 {
let mut bytes = Vec::new();
stdin().read_to_end(&mut bytes)?;
println!("{} -", hash_hex_string::<DEFAULT_LEN>(bytes.as_slice()));
} else {
for path in args {
let bytes = read(&path)?;
println!(
"{} {}",
hash_hex_string::<DEFAULT_LEN>(bytes.as_slice()),
path
);
}
}
Ok(())
}
23 changes: 23 additions & 0 deletions rust/bin/keygen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Part of liblithium, under the Apache License v2.0.
// SPDX-License-Identifier: Apache-2.0

use std::{
env,
fs::File,
io::{Result, Write},
process::exit,
};

use liblithium::sign::keygen;

fn main() -> Result<()> {
let mut args = env::args();
let prog = args.next().unwrap();
let sk_path = args.next().unwrap_or_else(|| {
eprintln!("usage: {} <key-filename>", prog);
exit(1)
});
let (pk, sk) = keygen();
File::create(&sk_path)?.write_all(&sk)?;
File::create(sk_path + ".pub")?.write_all(&pk)
}
35 changes: 35 additions & 0 deletions rust/bin/sign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Part of liblithium, under the Apache License v2.0.
// SPDX-License-Identifier: Apache-2.0

use std::{
env,
fs::{read, File},
io::Write,
process::exit,
};

use liblithium::sign::create;

fn main() -> std::io::Result<()> {
let mut args = env::args();
let prog = args.next().unwrap();
if args.len() != 3 {
eprintln!(
"usage: {} <secret-key-file> <message-file> <signature-file>",
prog
);
exit(1);
}
let sk_path = args.next().unwrap();
let msg_path = args.next().unwrap();
let sig_path = args.next().unwrap();

let sk: [u8; liblithium::sign::SECRET_KEY_LEN] = read(sk_path)?
.as_slice()
.try_into()
.expect("incorrect secret key length");
let msg = read(msg_path)?;

let sig = create(&msg, &sk);
File::create(sig_path)?.write_all(&sig)
}
37 changes: 37 additions & 0 deletions rust/bin/verify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Part of liblithium, under the Apache License v2.0.
// SPDX-License-Identifier: Apache-2.0

use std::{env, fs::read, process::exit};

use liblithium::sign::verify;

fn main() -> std::io::Result<()> {
let mut args = env::args();
let prog = args.next().unwrap();
if args.len() != 3 {
eprintln!(
"usage: {} <public-key-file> <message-file> <signature-file>",
prog
);
exit(1);
}
let pk_path = args.next().unwrap();
let msg_path = args.next().unwrap();
let sig_path = args.next().unwrap();

let pk: [u8; liblithium::sign::PUBLIC_KEY_LEN] = read(pk_path)?
.as_slice()
.try_into()
.expect("incorrect secret key length");
let sig: [u8; liblithium::sign::SIGN_LEN] = read(sig_path)?
.as_slice()
.try_into()
.expect("incorrect signature length");
let msg = read(msg_path)?;

if !verify(msg.as_slice(), &sig, &pk) {
eprintln!("could not verify signature");
exit(1);
}
Ok(())
}
53 changes: 53 additions & 0 deletions rust/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Part of liblithium, under the Apache License v2.0.
// SPDX-License-Identifier: Apache-2.0

use std::{env, path::PathBuf};

static SOURCES: &[&str] = &[
"fe.c",
"gimli.c",
"gimli_aead.c",
"gimli_common.c",
"gimli_hash.c",
"memzero.c",
"random.c",
"sign.c",
"x25519.c",
];

fn main() {
let root_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());

let src_dir = root_dir.join("src");
let include_dir = root_dir.join("include");

let mut cc = cc::Build::new();
cc.files(SOURCES.iter().map(|s| src_dir.join(s)));
cc.include(&include_dir);
cc.flag("-ansi");
cc.compile("liblithium");

let wrapper = "rust/wrapper.h";

println!("cargo:rerun-if-changed={}", wrapper);
for d in [&src_dir, &include_dir] {
println!("cargo:rerun-if-changed={}", d.display());
}

let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());

bindgen::builder()
.header(wrapper)
.clang_arg(format!("-I{}", include_dir.display()))
.disable_nested_struct_naming()
.use_core()
.default_enum_style(bindgen::EnumVariation::NewType {
is_bitfield: false,
is_global: false,
})
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Failed to generate bindings.")
.write_to_file(out_dir.join("bindings.rs"))
.expect("Failed to write bindings.");
}
9 changes: 9 additions & 0 deletions rust/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Part of liblithium, under the Apache License v2.0.
// SPDX-License-Identifier: Apache-2.0

#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(unused)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
76 changes: 76 additions & 0 deletions rust/src/gimli_hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Part of liblithium, under the Apache License v2.0.
// SPDX-License-Identifier: Apache-2.0

use std::mem::MaybeUninit;

use crate::bindings::{
gimli_hash_final, gimli_hash_init, gimli_hash_state, gimli_hash_update, GIMLI_HASH_DEFAULT_LEN,
};

/// An object for incrementally hashing a message.
#[repr(transparent)]
pub struct GimliHash {
state: gimli_hash_state,
}

pub const DEFAULT_LEN: usize = GIMLI_HASH_DEFAULT_LEN as usize;

impl GimliHash {
pub fn new() -> GimliHash {
let mut h = MaybeUninit::<GimliHash>::uninit();
unsafe {
gimli_hash_init(&raw mut (*h.as_mut_ptr()).state);
h.assume_init()
}
}

pub fn update(&mut self, data: &[u8]) {
unsafe { gimli_hash_update(&mut self.state, data.as_ptr(), data.len()) }
}

pub fn final_<const N: usize>(mut self) -> [u8; N] {
let mut h = MaybeUninit::<[u8; N]>::uninit();
unsafe {
gimli_hash_final(&mut self.state, &raw mut (*h.as_mut_ptr())[0], N);
h.assume_init()
}
}
}

impl Default for GimliHash {
fn default() -> Self {
Self::new()
}
}

/// Compute an N-byte GimliHash of data in one shot.
pub fn gimli_hash<const N: usize>(data: &[u8]) -> [u8; N] {
let mut g = GimliHash::new();
g.update(data);
g.final_()
}

#[doc(hidden)]
pub fn hash_hex_string<const N: usize>(data: &[u8]) -> String {
use std::fmt::Write;

let mut s = String::with_capacity(N * 2);
let h = gimli_hash::<N>(data);
for b in h {
write!(s, "{:02x}", b).unwrap();
}
s
}

#[cfg(test)]
mod test {
use crate::gimli_hash::{hash_hex_string, DEFAULT_LEN};

#[test]
fn test_gimli_hash() {
assert_eq!(
hash_hex_string::<DEFAULT_LEN>(&[]),
"27ae20e95fbc2bf01e972b0015eea431c20fc8818f25bc6dbe66232230db352f"
);
}
}
7 changes: 7 additions & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Part of liblithium, under the Apache License v2.0.
// SPDX-License-Identifier: Apache-2.0

mod bindings;

pub mod gimli_hash;
pub mod sign;
Loading

0 comments on commit f33f834

Please sign in to comment.