EEBKW7VTILH6AGGV57ZIJ3DJGYHDSYBWGU3C7Q4WWAKSVNUGIYMQC
VICI7FB367TT4FELZNDAL2CCXMHLVAEOE6RS53BA46K75KV3OLMQC
TFPETWTVADLG2DL7WERHJPGMJVOY4WOKCRWB3NZ3YOOQ4CVAUHBAC
IXC43DSHDSXCE2X6H6N47GGVKTYM2D2BUUWF34CFWMFT6Z45NOOAC
TVVW53HZGYPODAXEQ4BFZNSPBOFG6JEDVOKIYIDZMWFAMOBKOR2QC
GUL4M5FIE7JYXJHR7MCTQS3543HIQXGPLBWZKASBAWWEA4SZ75CAC
QL6K2ZM35B3NIXEMMCJWUSFXOBQHAGXRDMO7ID5DCKTJH4QJVY7QC
SNZ3OAMCPUGFYON5SZHQQQK46ZZMVMJECJYEUCMG657UVLY2PNBQC
SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC
SLJ3OHD4F6GJGZ3SV2D7DMR3PXYHPSI64X77KZ3RJ24EGEX6ZNQAC
5BB266P6HPUGYEVR7QNNOA62EFPYPUYJ3UMLE5J3LLYMSUWXANIQC
BZSC7VMYSFRXDHDDAMCDR6X67FN5VWIBOSE76BQLX7OCVOJFUA3AC
IUGP6ZGBFLDRAKJOHFQNG67LZBDXUJ4QM25GOY3QT6GER3NVTHXQC
KWAGWB73AMLJFK2Z7SBKHHKKHFRX7AQKXCWDN2MBX72RYCNMB36QC
L4JXJHWXYNCL4QGJXNKKTOKKTAXKKXBJUUY7HFZGEUZ5A2V5H34QC
I52XSRUH5RVHQBFWVMAQPTUSPAJ4KNVID2RMI3UGCVKFLYUO6WZAC
I24UEJQLCH2SOXA4UHIYWTRDCHSOPU7AFTRUOTX7HZIAV4AZKYEQC
PGERZ3KJIQIREXAYLKLZR4526X5RPKIA6LSVZL2DGYQ5UVPIIBUQC
I7VL7VPZV2NKOZRKBWWEHFOGNGGTYLPONHABVJ767D6HPJJNY5RAC
Y6EVFMTA6FOH3OQH6QCSWMI3F6SYZT2FSHO6GF4M3ICENDCWFM4QC
F6V27C3M7GZHBHXMGAZMYO5XGWDYYNNIF2HLDE2VPNHTEVDSYGVQC
7MNTFTDF62XHPRL56GJLTGRVB6QOLYDRYGY2XOKDO745RXOCBB4QC
4H2XTVJ2BNXDNHQ3RQTMOG3I4NRGZT7JDLC2GRINS56TIYTYTO4QC
AEPEFS7O3YT7CRRFYQVJWUXUUSRGJ6K6XZQVK62B6N74UXOIFWYAC
IIV3EL2XYI2X7HZWKXEXQFAE3R3KC2Q7SGOT3Q332HSENMYVF32QC
VO5OQW4W2656DIYYRNZ3PO7TQ4JOKQ3GVWE5ALUTYVMX3WMXJOYQC
YN63NUZO4LVJ7XPMURDULTXBVJKW5MVCTZ24R7Z52QMHO3HPDUVQC
V435QOJRFHNKW3NKJHMVMFOGO3KGAZVSURLSGFUHVKOMHMF4Q2ZQC
6HNRL5RT76NH5YNSUN7B4FHNRZXKNLX4DROFGMO4R5P2U7JWOL2QC
Q45QHPO4HDTEZF2W4UDZSYYQ46BPEIWSW4GJILZR5HTJNLKXJABQC
3WIQYEISUMGOL5FY4LCWOJS55CTTQA7WMJZXAE3Q4GQUTBFLAE7QC
XWETQ4DE4KL2GQWSEBE5NENLTSLIDF7RIWOCCVWJFQOVQJE5P33AC
JRENVH5DF2F4SOV7UNJENFA7VDI3H63XK76R3LFZK6QCW7JIBLSQC
LYTVEPH3W5UHF7MAYFWBT6NVNC42HEVKJGGMFDKUDZDNDOI33YJQC
XSEODPNEN2Y2THBRO7L5QFPAEQVSQTLAFZFWCRMBGZ3YSRZB2UJAC
T3S4P4ETZABWNK6UET3IDKAWRUOUKQNGCSDMTBJZC7U5R47LQ5DQC
WEHUTJUKHOJIBMEK2M7ILPK532FMO7YGWTEAHOIXZP5WOOOSF3ZAC
UFCZKKLXVYQYQBYENCAFHY3ZPPSDAJJAIREZSYNQM4QNXV6G6RXAC
OUWD436ATBTZJR53B6XDSI5SXRRNZV7YGRUEA5ACHZC2RUDP7G5QC
3M7WBE24JTPTHWQOU5PO2ZJYKPKEH2F6R4M6RWIRFG334PQEA55QC
Q3GU26WDEYE2HXMM4WWNEGVMSXLQAKRJBSB2EJZA6JTHPKQHXCXQC
23LVKATNTT74YKHG7KJM6SBO2IVZEV24TQ46ZJIHQ2IXONWNVXJAC
ENKQ3QZGH2QW246C7GSZRKYLODJOQHKZZSYV7QHB7VPOFP5PASVQC
B5Z4IMEUYAEJPOU5EIAXI7VYZVUM6CWKV7CTSOXK3F4GXTNNMMAAC
TPEH2XNBS5RO4IEVKENVF6P65AH7IX64KK2JAYMSJT3J5GXO67EAC
H62VFFJEBL2I3O4D3BAJZ57ROPWUISC7JCDIWFBC5DAYJRHMMDXAC
let authors = if let Some(a) = author {
vec![libpijul::change::Author {
name: a.to_string(),
full_name: None,
email: None,
}]
} else if let Ok(global) = crate::config::Global::load() {
vec![global.author]
} else {
Vec::new()
};
let mut authors = Vec::new();
if let Some(ref a) = author {
authors.push(a.to_string());
} else if let Some(mut dir) = crate::config::global_config_dir() {
dir.push("publickey.json");
if let Ok(key) = std::fs::File::open(&dir) {
let k: libpijul::key::PublicKey = serde_json::from_reader(key).unwrap();
authors.push(k.key)
}
}
use log::{debug, error};
use serde_derive::{Deserialize, Serialize};
use thrussh_keys::PublicKeyBase64;
use log::debug;
let key = if let Some(mut dir) = crate::config::global_config_dir() {
dir.push("secretkey.json");
if let Ok(key) = std::fs::File::open(&dir) {
let k: libpijul::key::SecretKey = serde_json::from_reader(key)?;
let pass = if k.encryption.is_some() {
Some(rpassword::read_password_from_tty(Some(&format!(
"Password for {:?}: ",
dir
)))?)
} else {
None
};
k.load(pass.as_deref())?
} else {
bail!("Secret key not found, please use `pijul key generate` and try again")
}
} else {
bail!("Secret key not found, please use `pijul key generate` and try again")
};
if sign {
let mut key_path = dirs_next::home_dir().unwrap().join(".ssh");
match sign_hash(&mut key_path, hash).await? {
Some((pk, signature)) if !signature.is_empty() => {
let sig = toml::Value::try_from(vec![Signature {
public_key: pk,
timestamp: change.header.timestamp,
signature,
}])?;
let mut toml = toml::map::Map::new();
toml.insert("signatures".to_string(), sig);
change.unhashed = Some(toml.into());
let hash2 = repo.changes.save_change(&change).unwrap();
assert_eq!(hash2, hash);
}
_ => {
bail!("Could not sign the change");
}
}
}
change.unhashed = Some(serde_json::json!({
"signature": key.sign_raw(&hash.to_bytes())?,
}));
let authors = if let Some(ref a) = self.author {
vec![libpijul::change::Author {
name: a.clone(),
full_name: None,
email: None,
}]
} else if let Ok(global) = config.as_ref() {
vec![global.author.clone()]
} else {
Vec::new()
};
let mut authors = Vec::new();
if let Some(ref a) = self.author {
authors.push(a.clone());
} else if let Some(mut dir) = crate::config::global_config_dir() {
dir.push("publickey.json");
if let Ok(key) = std::fs::File::open(&dir) {
let k: libpijul::key::PublicKey = serde_json::from_reader(key).unwrap();
authors.push(k.key)
}
}
async fn sign_hash(
key_path: &mut PathBuf,
hash: libpijul::Hash,
) -> Result<Option<(String, String)>, anyhow::Error> {
let to_sign = hash.to_bytes();
match thrussh_keys::agent::client::AgentClient::connect_env().await {
Ok(agent) => {
let mut agent = Some(agent);
for k in &["id_ed25519.pub", "id_rsa.pub"] {
key_path.push(k);
if let Ok(key) = thrussh_keys::load_public_key(&key_path) {
debug!("key");
if let Some(a) = agent.take() {
debug!("authenticate future");
if let (_, Ok(sig)) = a.sign_request_base64(&key, &to_sign).await {
key_path.pop();
let key = key.public_key_base64();
return Ok(Some((key, sig)));
}
}
}
key_path.pop();
}
}
Err(e) => {
error!("{:?}", e);
}
}
for k in &["id_ed25519", "id_rsa"] {
key_path.push(k);
if let Some(k) = crate::remote::ssh::load_secret_key(&key_path, k) {
key_path.pop();
let pk = k.public_key_base64();
return Ok(Some((pk, k.sign_detached(&to_sign)?.to_base64())));
} else {
key_path.pop();
}
}
Ok(None)
}
use crate::config::*;
use anyhow::bail;
use chrono::Datelike;
use clap::Clap;
use log::debug;
use std::io::Write;
use std::path::Path;
#[derive(Clap, Debug)]
pub struct Key {
#[clap(subcommand)]
subcmd: Option<SubCommand>,
}
#[derive(Clap, Debug)]
pub enum SubCommand {
Generate {},
}
impl Key {
pub async fn run(self) -> Result<(), anyhow::Error> {
match self.subcmd {
Some(SubCommand::Generate {}) => {
if let Some(mut dir) = global_config_dir() {
dir.push("secretkey.json");
if std::fs::metadata(&dir).is_ok() {
bail!("Cannot overwrite key file {:?}", dir)
}
debug!("creating file {:?}", dir);
let mut f = open_secret_file(&dir)?;
let expires = chrono::Utc::now();
let expires = expires.with_year(expires.year() + 1).unwrap();
let pass = rpassword::read_password_from_tty(Some(
"Password for the new key (press enter to leave it unencrypted): ",
))?;
let pass = if pass.is_empty() {
None
} else {
Some(pass.as_ref())
};
let k = libpijul::key::SKey::generate(expires);
serde_json::to_writer_pretty(&mut f, &k.save(pass))?;
f.write_all(b"\n")?;
let mut stderr = std::io::stderr();
writeln!(stderr, "Wrote secret key in {:?}", dir)?;
dir.pop();
dir.push("publickey.json");
debug!("creating file {:?}", dir);
let mut f = std::fs::File::create(&dir)?;
serde_json::to_writer_pretty(&mut f, &k.public_key())?;
f.write_all(b"\n")?;
}
}
None => {}
}
Ok(())
}
}
#[cfg(unix)]
fn open_secret_file(path: &Path) -> Result<std::fs::File, std::io::Error> {
use std::fs::OpenOptions;
use std::os::unix::fs::OpenOptionsExt;
OpenOptions::new()
.write(true)
.create(true)
.mode(0o600)
.open(path)
}
#[cfg(not(unix))]
fn open_secret_file(path: &Path) -> Result<std::fs::File, std::io::Error> {
std::fs::File::create(path)
}
use hmac::Hmac;
use sha2::{Digest, Sha256};
pub const CONTEXT: &'static [u8] = b"pijul";
pub const VERSION: u64 = 0;
#[derive(Debug, Error)]
pub enum KeyError {
#[error("Base 32 decoding error")]
Encoding,
#[error(transparent)]
Dalek(#[from] ed25519_dalek::ed25519::Error),
#[error("No password supplied")]
NoPassword,
#[error("The key expired")]
Expired,
}
impl From<data_encoding::DecodePartial> for KeyError {
fn from(_: data_encoding::DecodePartial) -> Self {
KeyError::Encoding
}
}
impl From<data_encoding::DecodeError> for KeyError {
fn from(_: data_encoding::DecodeError) -> Self {
KeyError::Encoding
}
}
#[derive(Serialize, Deserialize)]
pub struct SecretKey {
pub version: u64,
pub algorithm: Algorithm,
pub expires: chrono::DateTime<chrono::Utc>,
pub encryption: Option<Encryption>,
pub key: String,
}
pub enum SKey {
Ed25519 {
key: ed25519_dalek::Keypair,
expires: chrono::DateTime<chrono::Utc>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PublicKey {
pub version: u64,
pub algorithm: Algorithm,
pub expires: chrono::DateTime<chrono::Utc>,
pub signature: String,
pub key: String,
}
#[derive(Debug)]
pub enum PKey {
Ed25519 {
expires: chrono::DateTime<chrono::Utc>,
signature: String,
key: ed25519_dalek::PublicKey,
},
}
#[test]
fn sign_public_key() {
use chrono::Datelike;
let expires = chrono::Utc::now();
let expires = expires.with_year(expires.year() + 1).unwrap();
let sk = SKey::generate(expires);
let pk = sk.public_key();
println!("{:?}", pk);
let pk = pk.load().unwrap();
println!("{:?}", pk);
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Signature {
pub version: u64,
pub key: PublicKey,
pub signature: String,
pub date: chrono::DateTime<chrono::Utc>,
}
impl SKey {
pub fn sign(&self, h: &[u8]) -> Result<Signature, KeyError> {
Ok(Signature {
version: VERSION,
signature: self.sign_raw(h)?,
key: self.public_key(),
date: chrono::Utc::now(),
})
}
pub fn sign_raw(&self, h: &[u8]) -> Result<String, KeyError> {
match self {
SKey::Ed25519 { key, expires } => {
if expires <= &chrono::Utc::now() {
return Err(KeyError::Expired);
}
let mut prehashed = ed25519_dalek::Sha512::new();
prehashed.update(h);
let sig = key.sign_prehashed(prehashed, Some(CONTEXT)).unwrap();
Ok(data_encoding::BASE32_NOPAD.encode(&sig.to_bytes()))
}
}
}
pub fn generate(expires: chrono::DateTime<chrono::Utc>) -> Self {
use rand::RngCore;
let mut key = [0; 32];
rand::thread_rng().fill_bytes(&mut key);
SKey::Ed25519 {
key: ed25519_dalek::Keypair::from_bytes(&key).unwrap(),
expires,
}
}
pub fn save(&self, password: Option<&str>) -> SecretKey {
match self {
SKey::Ed25519 { key, expires } => {
let mut key = key.to_bytes();
let encryption = if let Some(password) = password {
use rand::Rng;
let salt = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(32)
.collect();
let enc = Encryption::Aes128(Kdf::Pbkdf2 { salt });
enc.encrypt(password.as_bytes(), &mut key);
Some(enc)
} else {
None
};
SecretKey {
version: VERSION,
algorithm: Algorithm::Ed25519,
expires: expires.clone(),
encryption,
key: data_encoding::BASE32_NOPAD.encode(&key),
}
}
}
}
pub fn public_key(&self) -> PublicKey {
match self {
SKey::Ed25519 { key, expires } => {
let to_sign =
bincode::serialize(&(Algorithm::Ed25519, expires.clone(), key.public)).unwrap();
debug!("to_sign {:?}", to_sign);
let mut prehashed = ed25519_dalek::Sha512::new();
prehashed.update(&to_sign);
let sig = key.sign_prehashed(prehashed, Some(CONTEXT)).unwrap();
PublicKey {
version: VERSION,
algorithm: Algorithm::Ed25519,
expires: expires.clone(),
key: data_encoding::BASE32_NOPAD.encode(&key.public.clone().to_bytes()),
signature: data_encoding::BASE32_NOPAD.encode(&sig.to_bytes()),
}
}
}
}
pub fn pkey(&self) -> PKey {
match self {
SKey::Ed25519 { key, expires } => {
let to_sign =
bincode::serialize(&(Algorithm::Ed25519, expires.clone(), key.public)).unwrap();
debug!("to_sign {:?}", to_sign);
let mut prehashed = ed25519_dalek::Sha512::new();
prehashed.update(&to_sign);
let sig = key.sign_prehashed(prehashed, Some(CONTEXT)).unwrap();
PKey::Ed25519 {
expires: expires.clone(),
key: key.public.clone(),
signature: data_encoding::BASE32_NOPAD.encode(&sig.to_bytes()),
}
}
}
}
}
impl SecretKey {
pub fn load(&self, pw: Option<&str>) -> Result<SKey, KeyError> {
if self.expires <= chrono::Utc::now() {
return Err(KeyError::Expired);
}
match self.algorithm {
Algorithm::Ed25519 => {
let mut key_enc = data_encoding::BASE32_NOPAD.decode(self.key.as_bytes())?;
if let Some(ref enc) = self.encryption {
let password = if let Some(ref pw) = pw {
pw
} else {
return Err(KeyError::NoPassword);
};
enc.decrypt(password.as_bytes(), &mut key_enc);
}
Ok(SKey::Ed25519 {
key: ed25519_dalek::Keypair::from_bytes(&key_enc)?,
expires: self.expires,
})
}
}
}
}
impl PublicKey {
pub fn fingerprint(&self) -> String {
match self.algorithm {
Algorithm::Ed25519 => {
let signed =
bincode::serialize(&(Algorithm::Ed25519, self.expires.clone(), &self.key))
.unwrap();
let mut prehashed = ed25519_dalek::Sha512::new();
prehashed.update(&signed);
data_encoding::BASE32_NOPAD.encode(&prehashed.finalize())
}
}
}
pub fn load(&self) -> Result<PKey, KeyError> {
match self.algorithm {
Algorithm::Ed25519 => {
let key = ed25519_dalek::PublicKey::from_bytes(
&data_encoding::BASE32_NOPAD.decode(self.key.as_bytes())?,
)?;
let mut signature = [0; 64];
data_encoding::BASE32_NOPAD
.decode_mut(self.signature.as_bytes(), &mut signature)?;
let signature = ed25519_dalek::Signature::new(signature);
let signed =
bincode::serialize(&(Algorithm::Ed25519, self.expires.clone(), &key)).unwrap();
debug!("signed {:?}", signed);
key.verify_strict(&signed, &signature)?;
Ok(PKey::Ed25519 {
signature: self.signature.clone(),
expires: self.expires.clone(),
key,
})
}
}
}
}
impl PKey {
pub fn save(&self) -> PublicKey {
match self {
PKey::Ed25519 {
key,
expires,
signature,
} => PublicKey {
version: VERSION,
algorithm: Algorithm::Ed25519,
expires: expires.clone(),
signature: signature.clone(),
key: data_encoding::BASE32_NOPAD.encode(key.as_bytes()),
},
}
}
pub fn verify(
&self,
h: &[u8],
signature: &str,
date: &chrono::DateTime<chrono::Utc>,
) -> Result<(), KeyError> {
match self {
PKey::Ed25519 { key, expires, .. } => {
if expires <= date {
return Err(KeyError::Expired);
}
let mut sig = [0; 64];
data_encoding::BASE32_NOPAD.decode_mut(signature.as_bytes(), &mut sig)?;
let sig = ed25519_dalek::Signature::new(sig);
let mut prehashed = ed25519_dalek::Sha512::new();
prehashed.update(h);
key.verify_prehashed(prehashed, Some(CONTEXT), &sig)?;
Ok(())
}
}
}
}
#[test]
fn verify_test() {
use chrono::Datelike;
let expires = chrono::Utc::now();
let expires = expires.with_year(expires.year() + 1).unwrap();
let sk = SKey::generate(expires);
let m = b"blabla";
let signature = sk.sign(m).unwrap();
signature.verify(m).unwrap();
}
impl Signature {
pub fn verify(&self, h: &[u8]) -> Result<(), KeyError> {
self.key.load()?.verify(h, &self.signature, &self.date)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Algorithm {
Ed25519,
}
#[derive(Serialize, Deserialize)]
pub enum Encryption {
Aes128(Kdf),
}
#[derive(Serialize, Deserialize)]
pub enum Kdf {
Pbkdf2 { salt: String },
}
impl Encryption {
pub fn encrypt<'a>(&self, password: &[u8], bytes: &'a mut [u8]) {
match self {
Encryption::Aes128(Kdf::Pbkdf2 { ref salt }) => {
let mut kdf = [0; 32];
pbkdf2::pbkdf2::<Hmac<Sha256>>(password, salt.as_ref(), 10_000, &mut kdf);
use aes::{
cipher::FromBlockCipher, cipher::StreamCipher, Aes128, Aes128Ctr,
NewBlockCipher,
};
let (a, b) = kdf.split_at(16);
let cipher = Aes128::new(generic_array::GenericArray::from_slice(&a));
let mut cipher = Aes128Ctr::from_block_cipher(
cipher,
generic_array::GenericArray::from_slice(b),
);
cipher.apply_keystream(bytes);
}
}
}
pub fn decrypt<'a>(&self, password: &[u8], bytes: &'a mut [u8]) {
self.encrypt(password, bytes)
}
}
#[test]
fn encrypt_decrypt() {
let enc = Encryption::Aes128(Kdf::Pbkdf2 {
salt: "blabla".to_string(),
});
let b0 = b"very confidential secret".to_vec();
let mut b = b0.clone();
enc.encrypt(b"password", &mut b[..]);
println!("{:?}", b);
enc.decrypt(b"password", &mut b[..]);
println!("{:?}", b);
assert_eq!(b, b0);
}
pub authors: Vec<Author>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Author {
pub name: String,
#[serde(default)]
pub full_name: Option<String>,
#[serde(default)]
pub email: Option<String>,
}
impl std::fmt::Display for Author {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.full_name.is_none() && self.email.is_none() {
write!(fmt, "{:?}", self.name)
} else {
write!(fmt, "{{ name = {:?}", self.name)?;
if let Some(ref f) = self.full_name {
write!(fmt, ", full_name = {:?}", f)?;
}
if let Some(ref f) = self.email {
write!(fmt, ", email = {:?}", f)?;
}
write!(fmt, " }}")
}
}
pub authors: Vec<String>,
name = "ed25519"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d0860415b12243916284c67a9be413e044ee6668247b99ba26d94b2bc06c8f6"
dependencies = [
"serde",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand 0.7.3",
"serde",
"serde_bytes",
"sha2",
"zeroize",
]
[[package]]
name = "lexical-core"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"bitflags",
"cfg-if 1.0.0",
"ryu",
"static_assertions",
]
[[package]]
"lock_api",
"parking_lot_core",
"lock_api 0.4.4",
"parking_lot_core 0.8.3",
]
[[package]]
name = "parking_lot_core"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
dependencies = [
"cfg-if 0.1.10",
"cloudabi",
"libc",
"redox_syscall 0.1.57",
"smallvec",
"winapi",
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
]
name = "bitvec"
version = "0.19.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
dependencies = [
"funty",
"radium",
"tap",
"wyz",