forked from brl/citadel-tools
Bruce Leidl
43800cdc6e
Keyring is an encrypted file to store secrets. The encryption key is derived from the disk decryption passphrase so that the file can be automatically decrypted and processed during boot. The keys contained in the keyring file are loaded into the kernel key store so that they can later be retrieved by other components. Currenly during installation a signing key is generated and stored in the keyring so that the system can transparently sign RealmFS images when the user modifies or updates them.
253 lines
7.7 KiB
Rust
253 lines
7.7 KiB
Rust
use std::path::{Path,PathBuf};
|
|
use std::collections::HashMap;
|
|
use std::io::{self,Read,Write};
|
|
use std::fs;
|
|
use std::ffi::CString;
|
|
use std::os::raw::c_char;
|
|
|
|
use libc::{self,c_long,c_ulong, c_int, int32_t};
|
|
|
|
use hex;
|
|
use sodiumoxide::randombytes::randombytes_into;
|
|
use sodiumoxide::crypto::{
|
|
sign::{
|
|
self, SEEDBYTES,
|
|
},
|
|
pwhash::{
|
|
self,SALTBYTES, Salt,
|
|
},
|
|
secretbox::{
|
|
self, NONCEBYTES, Nonce,
|
|
},
|
|
};
|
|
|
|
use crate::{Result,Error};
|
|
|
|
#[derive(Serialize,Deserialize,Debug)]
|
|
pub struct KeyRing {
|
|
keypairs: HashMap<String, String>,
|
|
}
|
|
|
|
impl KeyRing {
|
|
pub fn create_new() -> KeyRing {
|
|
let seed = KeyRing::new_random_seed();
|
|
let mut keypairs = HashMap::new();
|
|
keypairs.insert("realmfs-user".to_string(), hex::encode(&seed.0));
|
|
KeyRing { keypairs }
|
|
}
|
|
|
|
pub fn load<P: AsRef<Path>>(path: P, passphrase: &str) -> Result<KeyRing> {
|
|
let mut sbox = SecretBox::new(path.as_ref());
|
|
sbox.read()?;
|
|
let mut bytes = sbox.open(passphrase)?;
|
|
let keyring = toml::from_slice::<KeyRing>(&bytes)?;
|
|
bytes.iter_mut().for_each(|b| *b = 0);
|
|
Ok(keyring)
|
|
}
|
|
|
|
pub fn load_with_cryptsetup_passphrase<P: AsRef<Path>>(path: P) -> Result<KeyRing> {
|
|
let passphrase = KeyRing::get_cryptsetup_passphrase()?;
|
|
KeyRing::load(path, &passphrase)
|
|
}
|
|
|
|
fn get_cryptsetup_passphrase() -> Result<String> {
|
|
let key = KernelKey::request_key("user", "cryptsetup")?;
|
|
info!("Got key {}", key.0);
|
|
let buf = key.read()?;
|
|
match buf.split(|b| *b == 0).map(|bs| String::from_utf8_lossy(bs).to_string()).last() {
|
|
Some(s) => Ok(s),
|
|
None => Ok(String::new()),
|
|
}
|
|
}
|
|
|
|
pub fn add_keys_to_kernel(&self) -> Result<()> {
|
|
for (k,v) in self.keypairs.iter() {
|
|
info!("Adding {} to kernel keystore", k.as_str());
|
|
let _key = KernelKey::add_key("user", k.as_str(), v.as_bytes(), KEY_SPEC_USER_KEYRING)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn write<P: AsRef<Path>>(&self, path: P, passphrase: &str) -> Result<()> {
|
|
let salt = pwhash::gen_salt();
|
|
let nonce = secretbox::gen_nonce();
|
|
let key = SecretBox::passphrase_to_key(passphrase, &salt)?;
|
|
let bytes = toml::to_vec(self)?;
|
|
let ciphertext = secretbox::seal(&bytes, &nonce, &key);
|
|
|
|
let mut file = fs::File::create(path.as_ref())?;
|
|
file.write_all(&salt.0)?;
|
|
file.write_all(&nonce.0)?;
|
|
file.write_all(&ciphertext)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn new_random_seed() -> sign::Seed {
|
|
let mut seedbuf = [0; SEEDBYTES];
|
|
randombytes_into(&mut seedbuf);
|
|
sign::Seed(seedbuf)
|
|
}
|
|
|
|
}
|
|
|
|
impl Drop for KeyRing {
|
|
fn drop(&mut self) {
|
|
for (_,v) in self.keypairs.drain() {
|
|
v.into_bytes().iter_mut().for_each(|b| *b = 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SecretBox {
|
|
path: PathBuf,
|
|
salt: Salt,
|
|
nonce: Nonce,
|
|
data: Vec<u8>,
|
|
}
|
|
|
|
impl SecretBox {
|
|
fn new(path: &Path) -> SecretBox {
|
|
SecretBox {
|
|
path: path.to_path_buf(),
|
|
salt: Salt([0; SALTBYTES]),
|
|
nonce: Nonce([0; NONCEBYTES]),
|
|
data: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn read(&mut self) -> Result<()> {
|
|
if !self.data.is_empty() {
|
|
self.data.clear();
|
|
}
|
|
let mut file = fs::File::open(&self.path)?;
|
|
file.read_exact(&mut self.salt.0)?;
|
|
file.read_exact(&mut self.nonce.0)?;
|
|
file.read_to_end(&mut self.data)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn open(&self, passphrase: &str) -> Result<Vec<u8>> {
|
|
let key = SecretBox::passphrase_to_key(passphrase, &self.salt)?;
|
|
let result = secretbox::open(&self.data, &self.nonce, &key)
|
|
.map_err(|_| format_err!("Failed to decrypt {}", self.path.display()))?;
|
|
Ok(result)
|
|
}
|
|
|
|
fn passphrase_to_key(passphrase: &str, salt: &Salt) -> Result<secretbox::Key> {
|
|
let mut keybuf = [0; secretbox::KEYBYTES];
|
|
pwhash::derive_key(&mut keybuf, passphrase.as_bytes(), salt, pwhash::OPSLIMIT_INTERACTIVE, pwhash::MEMLIMIT_INTERACTIVE)
|
|
.map_err(|_| format_err!("Failed to derive key"))?;
|
|
Ok(secretbox::Key(keybuf))
|
|
}
|
|
|
|
|
|
}
|
|
|
|
const KEYCTL_READ : c_int = 11; // read a key or keyring's contents
|
|
const KEY_SPEC_USER_KEYRING : c_int = -4; // - key ID for UID-specific keyring
|
|
|
|
|
|
pub struct KernelKey(int32_t);
|
|
|
|
impl KernelKey {
|
|
pub fn request_key(key_type: &str, description: &str) -> Result<KernelKey> {
|
|
let key_type = CString::new(key_type).unwrap();
|
|
let description = CString::new(description).unwrap();
|
|
let serial = _request_key(key_type.as_ptr(), description.as_ptr())?;
|
|
Ok(KernelKey(serial as i32))
|
|
}
|
|
|
|
pub fn add_key(key_type: &str, description: &str, payload: &[u8], ring_id: c_int) -> Result<KernelKey> {
|
|
let key_type = CString::new(key_type).unwrap();
|
|
let description = CString::new(description).unwrap();
|
|
let serial = _add_key(key_type.as_ptr(), description.as_ptr(), payload.as_ptr(), payload.len(), ring_id)?;
|
|
Ok(KernelKey(serial as i32))
|
|
}
|
|
|
|
pub fn read(&self) -> Result<Vec<u8>> {
|
|
let mut size = 0;
|
|
loop {
|
|
size = match self.buffer_request(KEYCTL_READ, size) {
|
|
BufferResult::Err(err) => return Err(err),
|
|
BufferResult::Ok(buffer) => return Ok(buffer),
|
|
BufferResult::TooSmall(sz) => sz + 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn buffer_request(&self, command: c_int, size: usize) -> BufferResult {
|
|
if size == 0 {
|
|
return match keyctl1(command, self.id()) {
|
|
Err(err) => BufferResult::Err(err),
|
|
Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctl returned bad size")),
|
|
Ok(n) => BufferResult::TooSmall(n as usize),
|
|
};
|
|
}
|
|
let mut buffer = vec![0u8; size];
|
|
match keyctl3(command, self.id(), buffer.as_ptr() as u64, buffer.len() as u64) {
|
|
Err(err) => BufferResult::Err(err),
|
|
Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctrl returned bad size {}", n)),
|
|
Ok(sz) if size >= (sz as usize) => {
|
|
let sz = sz as usize;
|
|
if size > sz {
|
|
buffer.truncate(sz)
|
|
}
|
|
BufferResult::Ok(buffer)
|
|
},
|
|
Ok(n) => BufferResult::TooSmall(n as usize)
|
|
}
|
|
}
|
|
|
|
fn id(&self) -> c_ulong {
|
|
self.0 as c_ulong
|
|
}
|
|
|
|
}
|
|
|
|
enum BufferResult {
|
|
Ok(Vec<u8>),
|
|
Err(Error),
|
|
TooSmall(usize),
|
|
}
|
|
|
|
|
|
fn keyctl1(command: c_int, arg2: c_ulong) -> Result<c_long> {
|
|
_keyctl(command, arg2, 0, 0, 0)
|
|
}
|
|
|
|
fn keyctl3(command: c_int, arg2: c_ulong, arg3: c_ulong, arg4: c_ulong) -> Result<c_long> {
|
|
_keyctl(command, arg2, arg3, arg4, 0)
|
|
}
|
|
|
|
fn _keyctl(command: c_int, arg2: c_ulong, arg3: c_ulong, arg4: c_ulong, arg5: c_ulong) -> Result<c_long> {
|
|
unsafe {
|
|
let r = libc::syscall(libc::SYS_keyctl, command, arg2, arg3, arg4, arg5);
|
|
if r == -1 {
|
|
Err(io::Error::last_os_error().into())
|
|
} else {
|
|
Ok(r)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn _request_key(key_type: *const c_char, description: *const c_char) -> Result<c_long> {
|
|
unsafe {
|
|
let r = libc::syscall(libc::SYS_request_key, key_type, description, 0, 0);
|
|
if r == -1 {
|
|
Err(io::Error::last_os_error().into())
|
|
} else {
|
|
Ok(r)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn _add_key(key_type: *const c_char, description: *const c_char, payload: *const u8, plen: usize, ring_id: c_int) -> Result<c_long> {
|
|
unsafe {
|
|
let r = libc::syscall(libc::SYS_add_key, key_type, description, payload, plen, ring_id);
|
|
if r == -1 {
|
|
Err(io::Error::last_os_error().into())
|
|
} else {
|
|
Ok(r)
|
|
}
|
|
}
|
|
} |