extern crate gbl;
extern crate env_logger;
#[macro_use]
extern crate failure;
#[macro_use]
#[allow(unused)] extern crate structopt;
use gbl::uuid::Uuid;
use gbl::{AesKey, AppImage, AppInfo, Gbl, P256KeyPair, P256PublicKey, ProgramData};
use failure::{err_msg, Error, ResultExt};
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::{fs, process};
use structopt::StructOpt;
fn aes_key_from_hex(raw: &str) -> Result<AesKey, gbl::Error> {
AesKey::from_hex_str(raw)
}
#[derive(StructOpt)]
#[structopt(name = "gbl", about = "GBL file creation, signing and encryption")]
enum Opts {
#[structopt(name = "dump")]
Dump {
#[structopt(parse(from_os_str))]
gbl: PathBuf,
#[structopt(parse(from_os_str))]
#[structopt(long = "output")]
output: Option<PathBuf>,
#[structopt(parse(from_os_str))]
#[structopt(long = "raw-app")]
raw_app: Option<PathBuf>,
},
#[structopt(name = "create")]
Create {
#[structopt(parse(from_os_str))]
#[structopt(long = "app")]
app: PathBuf,
#[structopt(long = "empty-appinfo")]
empty_appinfo: bool,
#[structopt(parse(from_os_str))]
output: PathBuf,
},
#[structopt(name = "sign")]
Sign {
#[structopt(long = "privkey")]
#[structopt(parse(from_os_str))]
privkey: PathBuf,
#[structopt(parse(from_os_str))]
gbl: PathBuf,
#[structopt(parse(from_os_str))]
#[structopt(long = "output")]
output: PathBuf,
},
#[structopt(name = "verify")]
Verify {
#[structopt(long = "pubkey")]
#[structopt(parse(from_os_str))]
pubkey: PathBuf,
#[structopt(parse(from_os_str))]
gbl: PathBuf,
},
#[structopt(name = "decrypt")]
Decrypt {
#[structopt(long = "keyfile")]
#[structopt(parse(from_os_str))]
keyfile: Option<PathBuf>,
#[structopt(name = "aes-key", long = "raw-key")]
#[structopt(parse(try_from_str = "aes_key_from_hex"))]
raw_key: Option<AesKey>,
#[structopt(parse(from_os_str))]
gbl: PathBuf,
#[structopt(parse(from_os_str))]
#[structopt(long = "output")]
output: Option<PathBuf>,
},
#[structopt(name = "encrypt")]
Encrypt {
#[structopt(long = "keyfile")]
#[structopt(parse(from_os_str))]
keyfile: Option<PathBuf>,
#[structopt(name = "aes-key", long = "raw-key")]
#[structopt(parse(try_from_str = "aes_key_from_hex"))]
raw_key: Option<AesKey>,
#[structopt(parse(from_os_str))]
gbl: PathBuf,
#[structopt(parse(from_os_str))]
#[structopt(long = "output")]
output: Option<PathBuf>,
},
#[structopt(name = "app-image")]
AppImage {
#[structopt(flatten)]
opts: AppImageOpts,
},
}
#[derive(StructOpt)]
enum AppImageOpts {
#[structopt(name = "sign")]
Sign {
#[structopt(long = "privkey")]
#[structopt(parse(from_os_str))]
privkey: PathBuf,
#[structopt(parse(from_os_str))]
image: PathBuf,
#[structopt(parse(from_os_str))]
#[structopt(long = "output")]
output: PathBuf,
},
}
fn appimage(opts: AppImageOpts) -> Result<(), Error> {
match opts {
AppImageOpts::Sign {
privkey,
image,
output,
} => {
let appimage = fs::read(image)?;
let key_str = fs::read_to_string(privkey)?;
let key = P256KeyPair::from_pem(key_str)?;
let appimage = AppImage::parse(&appimage)?;
let signed = appimage.sign(&key)?;
fs::write(&output, signed.into_raw())?;
println!("Wrote signed application image to {}", output.display());
Ok(())
}
}
}
fn run() -> Result<(), Error> {
env_logger::init();
let opts = Opts::from_args();
match opts {
Opts::Dump {
gbl,
output,
raw_app,
} => {
let content = fs::read(gbl)?;
let gbl = Gbl::parse(&content)?;
if let Some(raw_app) = raw_app {
let unencrypted_gbl = match gbl.clone().into_not_encrypted() {
Ok(g) => g,
Err(_) => bail!("`--raw-app` cannot be used with encrypted GBLs"),
};
let mut buf = Vec::new();
for section in unencrypted_gbl.data_sections() {
let end = section.start_addr() as usize + section.bytes().len();
buf.resize(end, 0);
buf[section.start_addr() as usize..end].copy_from_slice(section.bytes());
}
File::create(&raw_app)?.write_all(&buf)?;
println!("Wrote flash image to {}", raw_app.display());
}
if let Some(output) = output {
gbl.write(&mut File::create(&output)?)?;
println!("Wrote GBL to {}", output.display());
} else {
println!("{:#?}", gbl);
}
}
Opts::Create {
app,
empty_appinfo,
output,
} => {
let file_contents = fs::read(&app)?;
let app_image = match app.extension() {
Some(ext) => match &*ext.to_string_lossy() {
"bin" => file_contents,
ext => bail!(
"unsupported file extension '{}' (at the moment, only raw `.bin` \
files are supported)",
ext
),
},
None => bail!("application image must have a supported file extension"),
};
let gbl = if empty_appinfo {
Gbl::from_parts(
AppInfo::new(0, 0, 0, Uuid::nil()),
ProgramData::new(0, app_image),
)
} else {
let app_image = AppImage::parse(&app_image)?;
Gbl::from_app_image(app_image)
};
gbl.write(&mut File::create(&output)?)?;
println!("Wrote GBL to {}", output.display());
}
Opts::Sign {
gbl,
privkey,
output,
} => {
let content = fs::read(gbl)?;
let key_str = fs::read_to_string(&privkey).context(privkey.display().to_string())?;
let key = P256KeyPair::from_pem(key_str)?;
let gbl = Gbl::parse(&content)?;
let gbl = gbl.into_not_signed().unwrap_or_else(|signed| {
println!("Warning: GBL is already signed. Removing old signature.");
signed.remove_signature()
});
let gbl = gbl.sign(&key)?;
gbl.write(&mut File::create(&output)?)?;
println!("Wrote signed GBL to {}", output.display());
}
Opts::Verify { gbl, pubkey } => {
let content = fs::read(gbl)?;
let gbl = Gbl::parse(&content)?;
let pubkey_str = fs::read_to_string(pubkey)?;
let pubkey = P256PublicKey::from_pem(pubkey_str)?;
gbl.into_signed()
.map_err(|_| err_msg("file is not signed"))?
.verify_signature(&pubkey)?;
println!("Signature is valid!");
}
Opts::Encrypt {
keyfile,
raw_key,
gbl,
output,
} => {
let aes_key = match (keyfile, raw_key) {
(None, None) => {
bail!("either `--keyfile` or `--raw-key` must be specified");
}
(Some(file), None) => AesKey::from_token_file(fs::read_to_string(file)?)?,
(None, Some(aes_key)) => aes_key,
(Some(_), Some(_)) => {
bail!("cannot specify both `--keyfile` and `--raw-key`");
}
};
let gbl_path = &gbl;
let content = fs::read(&gbl)?;
let gbl = Gbl::parse(&content)?;
let gbl = gbl.into_not_encrypted().map_err(|_| {
format_err!("the GBL file '{}' is already encrypted", gbl_path.display())
})?;
let gbl = gbl.into_not_signed().unwrap_or_else(|gbl| {
println!(
"Warning: Attempting to encrypt a signed GBL. This will remove the signature."
);
gbl.remove_signature()
});
let encrypted = gbl.encrypt(aes_key);
if let Some(output) = output {
encrypted.write(&mut File::create(&output)?)?;
println!("Wrote decrypted GBL to {}", output.display());
} else {
println!("{:#?}", encrypted);
}
}
Opts::Decrypt {
keyfile,
raw_key,
gbl,
output,
} => {
let aes_key = match (keyfile, raw_key) {
(None, None) => {
bail!("either `--keyfile` or `--raw-key` must be specified");
}
(Some(file), None) => AesKey::from_token_file(fs::read_to_string(file)?)?,
(None, Some(aes_key)) => aes_key,
(Some(_), Some(_)) => {
bail!("cannot specify both `--keyfile` and `--raw-key`");
}
};
let gbl_path = &gbl;
let content = fs::read(&gbl)?;
let gbl = Gbl::parse(&content)?;
let gbl = gbl.into_encrypted().map_err(|_| {
format_err!(
"the GBL file '{}' doesn't seem to be encrypted",
gbl_path.display()
)
})?;
let gbl = gbl.into_not_signed().unwrap_or_else(|gbl| {
println!(
"Warning: Attempting to decrypt a signed GBL. This will remove the signature."
);
gbl.remove_signature()
});
let result = gbl.decrypt(aes_key);
let decrypted = result.with_context(|e| {
format!(
"decryption failed: {} (make sure the right AES key was used)",
e
)
})?;
if let Some(output) = output {
decrypted.write(&mut File::create(&output)?)?;
println!("Wrote decrypted GBL to {}", output.display());
} else {
println!("{:#?}", decrypted);
}
}
Opts::AppImage { opts } => appimage(opts)?,
}
Ok(())
}
fn main() {
match run() {
Ok(()) => {}
Err(e) => {
eprintln!("ERROR: {}", e);
process::exit(1);
}
}
}