Clean up keys api

This commit is contained in:
Bruce Leidl 2019-01-05 20:38:57 -05:00
parent b59188311d
commit dc9a49fd8a
5 changed files with 97 additions and 66 deletions

View File

@ -150,6 +150,11 @@ impl UpdateBuilder {
let metainfo = self.generate_metainfo(); let metainfo = self.generate_metainfo();
fs::write(self.config.workdir_path("metainfo"), &metainfo)?; fs::write(self.config.workdir_path("metainfo"), &metainfo)?;
hdr.set_metainfo_bytes(&metainfo); hdr.set_metainfo_bytes(&metainfo);
if self.config.channel() == "dev" {
let sig = devkeys().sign(&metainfo);
hdr.set_signature(sig.to_bytes())?;
}
Ok(hdr) Ok(hdr)
} }

View File

@ -1,18 +1,17 @@
use std::process::Command; use std::process::Command;
use libcitadel::{BlockDev,CommandLine,Config,ImageHeader,Partition,Result,verity}; use libcitadel::{BlockDev,CommandLine,Partition,Result,verity};
use std::path::Path; use std::path::Path;
use std::process::Stdio; use std::process::Stdio;
use BootSelection; use BootSelection;
use ResourceImage; use ResourceImage;
pub struct Rootfs { pub struct Rootfs {
config: Config,
} }
impl Rootfs { impl Rootfs {
pub fn new(config: Config) -> Rootfs { pub fn new() -> Rootfs {
Rootfs { config } Rootfs {}
} }
pub fn setup(&self) -> Result<()> { pub fn setup(&self) -> Result<()> {
@ -53,17 +52,8 @@ impl Rootfs {
self.setup_linear_mapping(&loopdev) self.setup_linear_mapping(&loopdev)
} }
fn maybe_check_signature(&self, hdr: &ImageHeader) -> Result<()> {
if !CommandLine::nosignatures() {
let signature = hdr.signature();
let metainfo = hdr.metainfo()?;
metainfo.verify(&self.config, &signature)?;
}
Ok(())
}
fn setup_resource_verified(&self, img: &ResourceImage) -> Result<()> { fn setup_resource_verified(&self, img: &ResourceImage) -> Result<()> {
let _ = img.setup_verity_device(&self.config)?; let _ = img.setup_verity_device()?;
Ok(()) Ok(())
} }
@ -74,7 +64,10 @@ impl Rootfs {
fn setup_partition_verified(&self, partition: &Partition) -> Result<()> { fn setup_partition_verified(&self, partition: &Partition) -> Result<()> {
info!("Creating /dev/mapper/rootfs dm-verity device"); info!("Creating /dev/mapper/rootfs dm-verity device");
self.maybe_check_signature(partition.header())?; if !CommandLine::nosignatures() {
partition.header().verify_signature()?;
info!("Image signature is valid for channel {}", partition.metainfo().channel());
}
verity::setup_partition_device(partition)?; verity::setup_partition_device(partition)?;
Ok(()) Ok(())
} }

View File

@ -8,7 +8,7 @@ use failure::ResultExt;
use toml; use toml;
use blockdev::AlignedBuffer; use blockdev::AlignedBuffer;
use {BlockDev, Channel, Config, Result}; use {BlockDev,Result,public_key_for_channel};
/// Expected magic value in header /// Expected magic value in header
const MAGIC: &[u8] = b"SGOS"; const MAGIC: &[u8] = b"SGOS";
@ -217,24 +217,25 @@ impl ImageHeader {
self.read_bytes(METAINFO_OFFSET + mlen, SIGNATURE_LENGTH) self.read_bytes(METAINFO_OFFSET + mlen, SIGNATURE_LENGTH)
} }
pub fn sign_metainfo(&self, channel: &Channel) -> Result<()> { pub fn set_signature(&self, signature: &[u8]) -> Result<()> {
if signature.len() != SIGNATURE_LENGTH {
bail!("Signature has invalid length: {}", signature.len());
}
let mlen = self.metainfo_len(); let mlen = self.metainfo_len();
// XXX assert mlen is good self.write_bytes(8 + mlen, signature);
let sig = channel.sign(&self.0.borrow()[8..8 + mlen])?;
self.write_bytes(8 + mlen, sig.to_bytes());
Ok(()) Ok(())
} }
pub fn verify_signature(&self, config: &Config) -> Result<()> { pub fn verify_signature(&self) -> Result<()> {
let metainfo = self.metainfo()?; let metainfo = self.metainfo()?;
let channel = match config.channel(metainfo.channel()) {
Some(channel) => channel, if let Some(pubkey) = public_key_for_channel(metainfo.channel())? {
None => bail!("Cannot verify signature for channel '{}' because it does not exist in configuration file", metainfo.channel()), if !pubkey.verify(&self.metainfo_bytes(), &self.signature()) {
}; bail!("Header signature verification failed");
channel }
.verify(metainfo.bytes(), &self.signature()) return Ok(())
.context("failed to verify header signature")?; }
Ok(()) Err(format_err!("Cannot verify signature because no public key found for channel '{}'", metainfo.channel()))
} }
pub fn write_header<W: Write>(&self, mut writer: W) -> Result<()> { pub fn write_header<W: Write>(&self, mut writer: W) -> Result<()> {
@ -315,10 +316,6 @@ impl MetaInfo {
} }
} }
fn bytes(&self) -> &[u8] {
&self.bytes
}
pub fn parse_toml(&mut self) -> Result<()> { pub fn parse_toml(&mut self) -> Result<()> {
if !self.is_parsed { if !self.is_parsed {
self.is_parsed = true; self.is_parsed = true;
@ -329,18 +326,6 @@ impl MetaInfo {
Ok(()) Ok(())
} }
pub fn verify(&self, config: &Config, signature: &[u8]) -> Result<()> {
let channel = match config.channel(self.channel()) {
Some(channel) => channel,
None => bail!("Channel '{}' not found in config file", self.channel()),
};
channel
.verify(&self.bytes, signature)
.context("Bad metainfo signature in header")?;
Ok(())
}
fn toml(&self) -> &MetaInfoToml { fn toml(&self) -> &MetaInfoToml {
self.toml.as_ref().unwrap() self.toml.as_ref().unwrap()
} }

View File

@ -16,17 +16,34 @@ pub struct KeyPair([u8; ED25519_PKCS8_V2_LEN]);
pub struct Signature(signature::Signature); pub struct Signature(signature::Signature);
impl PublicKey { impl PublicKey {
pub fn from_bytes(bytes: &[u8]) -> Result<PublicKey> {
pub fn from_hex(hex: &str) -> Result<PublicKey> {
let bytes = hex.from_hex()?;
if bytes.len() != ED25519_PUBLIC_KEY_LEN {
bail!("Hex encoded public key has invalid length: {}", bytes.len());
}
Ok(PublicKey::from_bytes(&bytes))
}
pub fn to_hex(&self) -> String {
self.0.to_hex()
}
fn from_bytes(bytes: &[u8]) -> PublicKey {
let mut key = [0u8; ED25519_PUBLIC_KEY_LEN]; let mut key = [0u8; ED25519_PUBLIC_KEY_LEN];
key.copy_from_slice(bytes); key.copy_from_slice(bytes);
Ok(PublicKey(key)) PublicKey(key)
} }
pub fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()> {
pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
let signature = Input::from(signature); let signature = Input::from(signature);
let data = Input::from(data); let data = Input::from(data);
let pubkey = Input::from(&self.0); let pubkey = Input::from(&self.0);
signature::verify(&signature::ED25519, pubkey, data, signature)?;
Ok(()) match signature::verify(&signature::ED25519, pubkey, data, signature) {
Ok(()) => true,
Err(_) => false,
}
} }
} }
@ -40,7 +57,6 @@ impl KeyPair {
let rng = rand::SystemRandom::new(); let rng = rand::SystemRandom::new();
let bytes = Ed25519KeyPair::generate_pkcs8(&rng)?; let bytes = Ed25519KeyPair::generate_pkcs8(&rng)?;
KeyPair::from_bytes(&bytes) KeyPair::from_bytes(&bytes)
} }
pub fn from_hex(hex: &str) -> Result<KeyPair> { pub fn from_hex(hex: &str) -> Result<KeyPair> {
@ -50,30 +66,32 @@ impl KeyPair {
fn from_bytes(bytes: &[u8]) -> Result<KeyPair> { fn from_bytes(bytes: &[u8]) -> Result<KeyPair> {
let mut pair = [0u8; ED25519_PKCS8_V2_LEN]; let mut pair = [0u8; ED25519_PKCS8_V2_LEN];
pair.copy_from_slice(bytes); pair.copy_from_slice(bytes);
let _ = Ed25519KeyPair::from_pkcs8(Input::from(&pair))?;
Ok(KeyPair(pair)) Ok(KeyPair(pair))
} }
pub fn public_key_bytes(&self) -> Vec<u8> { fn get_keys(&self) -> Ed25519KeyPair {
let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0)).expect("failed to parse pkcs8 key"); Ed25519KeyPair::from_pkcs8(Input::from(&self.0))
pair.public_key_bytes().to_vec() .expect("failed to parse pkcs8 key")
} }
pub fn private_key_bytes(&self) -> Vec<u8> { pub fn public_key(&self) -> PublicKey {
self.0.to_vec() let keys = self.get_keys();
PublicKey::from_bytes(keys.public_key_bytes())
} }
pub fn private_key_hex(&self) -> String { pub fn private_key_hex(&self) -> String {
self.0.to_hex() self.0.to_hex()
} }
pub fn public_key_hex(&self) -> String {
let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0)).expect("failed to parse pkcs8 key"); pub fn sign(&self, data: &[u8]) -> Signature {
pair.public_key_bytes().to_hex() let keys = self.get_keys();
let signature = keys.sign(data);
Signature(signature)
} }
pub fn sign(&self, data: &[u8]) -> Result<Signature> { pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0))?; self.public_key().verify(data, signature)
let signature = pair.sign(data);
Ok(Signature(signature))
} }
} }

View File

@ -66,16 +66,46 @@ pub mod util;
pub mod verity; pub mod verity;
mod mount; mod mount;
pub use config::Config; pub use config::OsRelease;
pub use config::Channel;
pub use blockdev::BlockDev; pub use blockdev::BlockDev;
pub use cmdline::CommandLine; pub use cmdline::CommandLine;
pub use header::{ImageHeader,MetaInfo}; pub use header::{ImageHeader,MetaInfo};
pub use partition::Partition; pub use partition::Partition;
pub use resource::ResourceImage; pub use resource::ResourceImage;
pub use keys::KeyPair; pub use keys::{KeyPair,PublicKey};
pub use mount::Mount; pub use mount::Mount;
const DEVKEYS_HEX: &str =
"3053020101300506032b6570042204206ed2849c6c5168e1aebc50005ac3d4a4e84af4889e4e0189bb4c787e6ee0be49a1230321006b652764c62a1de35e7e37af2b743e9a5b82cee2211cf3091d2514441b417f5f";
pub fn devkeys() -> KeyPair {
KeyPair::from_hex(&DEVKEYS_HEX)
.expect("Error parsing built in dev channel keys")
}
pub fn public_key_for_channel(channel: &str) -> Result<Option<PublicKey>> {
if channel == "dev" {
return Ok(Some(devkeys().public_key()));
}
// Look in /etc/os-release
if Some(channel) == OsRelease::citadel_channel() {
if let Some(hex) = OsRelease::citadel_image_pubkey() {
let pubkey = PublicKey::from_hex(hex)?;
return Ok(Some(pubkey));
}
}
// Does kernel command line have citadel.channel=name:[hex encoded pubkey]
if Some(channel) == CommandLine::channel_name() {
if let Some(hex) = CommandLine::channel_pubkey() {
let pubkey = PublicKey::from_hex(hex)?;
return Ok(Some(pubkey))
}
}
Ok(None)
}
pub type Result<T> = result::Result<T,Error>; pub type Result<T> = result::Result<T,Error>;