misc/keygen: Implement cli tool to handle key material (#2453)

- Load keys from file
- Generate new keys (with optional prefix)
- Replaces peer-id-generator

Co-authored-by: Fabricio Dematte <fabriciodematte7p@gmail.com>
Co-authored-by: Max Inden <mail@max-inden.de>
This commit is contained in:
Fabricio Demattê
2022-03-11 08:44:05 -03:00
committed by GitHub
parent 108c970a59
commit e38eb09f5d
5 changed files with 176 additions and 95 deletions

View File

@ -125,7 +125,7 @@ members = [
"core",
"misc/metrics",
"misc/multistream-select",
"misc/peer-id-generator",
"misc/keygen",
"muxers/mplex",
"muxers/yamux",
"protocols/dcutr",

View File

@ -1,16 +1,17 @@
[package]
name = "peer-id-generator"
edition = "2021"
rust-version = "1.56.1"
name = "keygen"
version = "0.1.0"
description = "Generate peer ids that are prefixed with a specific string"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
authors = ["demfabris <demfabris@gmail.com>"]
license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p"
keywords = ["peer-to-peer", "libp2p", "networking"]
categories = ["network-programming", "asynchronous"]
publish = false
[dependencies]
structopt = "0.3.26"
zeroize = "1"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
libp2p-core = { path = "../../core", default-features = false, version = "0.32.0"}
num_cpus = "1.8"
base64 = "0.13.0"

41
misc/keygen/src/config.rs Normal file
View File

@ -0,0 +1,41 @@
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::path::Path;
use libp2p_core::identity::Keypair;
use libp2p_core::PeerId;
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Config {
pub identity: Identity,
}
impl Config {
pub fn from_file(path: &Path) -> Result<Self, Box<dyn Error>> {
Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?)
}
pub fn from_key_material(peer_id: PeerId, keypair: &Keypair) -> Result<Self, Box<dyn Error>> {
let priv_key = base64::encode(keypair.to_protobuf_encoding()?);
let peer_id = peer_id.to_base58();
Ok(Self {
identity: Identity { peer_id, priv_key },
})
}
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Identity {
#[serde(rename = "PeerID")]
pub peer_id: String,
pub priv_key: String,
}
impl zeroize::Zeroize for Config {
fn zeroize(&mut self) {
self.identity.peer_id.zeroize();
self.identity.priv_key.zeroize();
}
}

126
misc/keygen/src/main.rs Normal file
View File

@ -0,0 +1,126 @@
use std::error::Error;
use std::path::PathBuf;
use std::str::{self, FromStr};
use std::sync::mpsc;
use std::thread;
mod config;
use libp2p_core::identity::{self, ed25519};
use libp2p_core::PeerId;
use structopt::StructOpt;
use zeroize::Zeroizing;
#[derive(Debug, StructOpt)]
#[structopt(name = "libp2p key material generator")]
struct Args {
/// JSON formatted output
#[structopt(long, global = true)]
json: bool,
#[structopt(subcommand)]
cmd: Command,
}
#[derive(Debug, StructOpt)]
enum Command {
/// Read from config file
From {
/// Provide a IPFS config file
#[structopt(parse(from_os_str))]
config: PathBuf,
},
/// Generate random
Rand {
/// The keypair prefix
#[structopt(long)]
prefix: Option<String>,
},
}
// Due to the fact that a peer id uses a SHA-256 multihash, it always starts with the
// bytes 0x1220, meaning that only some characters are valid.
const ALLOWED_FIRST_BYTE: &[u8] = b"NPQRSTUVWXYZ";
// The base58 alphabet is not necessarily obvious.
const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
fn main() -> Result<(), Box<dyn Error>> {
let args = Args::from_args();
let (local_peer_id, local_keypair) = match args.cmd {
// Generate keypair from some sort of key material. Currently supporting `IPFS` config file
Command::From { config } => {
let config = Zeroizing::new(config::Config::from_file(config.as_ref())?);
let keypair = identity::Keypair::from_protobuf_encoding(&Zeroizing::new(
base64::decode(config.identity.priv_key.as_bytes())?,
))?;
let peer_id = keypair.public().into();
assert_eq!(
PeerId::from_str(&config.identity.peer_id)?,
peer_id,
"Expect peer id derived from private key and peer id retrieved from config to match."
);
(peer_id, keypair)
}
// Generate a random keypair, optionally with a prefix
Command::Rand { prefix } => {
if let Some(prefix) = prefix {
if prefix.as_bytes().iter().any(|c| !ALPHABET.contains(c)) {
eprintln!("Prefix {} is not valid base58", prefix);
std::process::exit(1);
}
// Checking conformity to ALLOWED_FIRST_BYTE.
if !prefix.is_empty() && !ALLOWED_FIRST_BYTE.contains(&prefix.as_bytes()[0]) {
eprintln!("Prefix {} is not reachable", prefix);
eprintln!(
"Only the following bytes are possible as first byte: {}",
str::from_utf8(ALLOWED_FIRST_BYTE).unwrap()
);
std::process::exit(1);
}
let (tx, rx) = mpsc::channel::<(PeerId, identity::Keypair)>();
// Find peer IDs in a multithreaded fashion.
for _ in 0..thread::available_parallelism()?.get() {
let prefix = prefix.clone();
let tx = tx.clone();
thread::spawn(move || loop {
let keypair = ed25519::Keypair::generate();
let peer_id = identity::PublicKey::Ed25519(keypair.public()).to_peer_id();
let base58 = peer_id.to_base58();
if base58[8..].starts_with(&prefix) {
tx.send((peer_id, identity::Keypair::Ed25519(keypair)))
.expect("to send");
}
});
}
rx.recv().expect("to recv")
} else {
let keypair = identity::Keypair::Ed25519(ed25519::Keypair::generate());
(keypair.public().into(), keypair)
}
}
};
if args.json {
let config = config::Config::from_key_material(local_peer_id, &local_keypair)?;
println!("{}", serde_json::to_string(&config)?);
} else {
println!(
"PeerId: {:?} Keypair: {:?}",
local_peer_id,
local_keypair.to_protobuf_encoding()
);
}
Ok(())
}

View File

@ -1,87 +0,0 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use libp2p_core::identity;
use std::{env, str, thread, time::Duration};
fn main() {
// Due to the fact that a peer id uses a SHA-256 multihash, it always starts with the
// bytes 0x1220, meaning that only some characters are valid.
const ALLOWED_FIRST_BYTE: &'static [u8] = b"NPQRSTUVWXYZ";
let prefix = match env::args().nth(1) {
Some(prefix) => prefix,
None => {
println!(
"Usage: {} <prefix>\n\n\
Generates a peer id that starts with the chosen prefix using a secp256k1 public \
key.\n\n\
Prefix must be a sequence of characters in the base58 \
alphabet, and must start with one of the following: {}",
env::current_exe()
.unwrap()
.file_name()
.unwrap()
.to_str()
.unwrap(),
str::from_utf8(ALLOWED_FIRST_BYTE).unwrap()
);
return;
}
};
// The base58 alphabet is not necessarily obvious.
const ALPHABET: &'static [u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
if prefix.as_bytes().iter().any(|c| !ALPHABET.contains(c)) {
println!("Prefix {} is not valid base58", prefix);
return;
}
// Checking conformity to ALLOWED_FIRST_BYTE.
if !prefix.is_empty() {
if !ALLOWED_FIRST_BYTE.contains(&prefix.as_bytes()[0]) {
println!("Prefix {} is not reachable", prefix);
println!(
"Only the following bytes are possible as first byte: {}",
str::from_utf8(ALLOWED_FIRST_BYTE).unwrap()
);
return;
}
}
// Find peer IDs in a multithreaded fashion.
for _ in 0..num_cpus::get() {
let prefix = prefix.clone();
thread::spawn(move || loop {
let keypair = identity::ed25519::Keypair::generate();
let secret = keypair.secret();
let peer_id = identity::PublicKey::Ed25519(keypair.public()).to_peer_id();
let base58 = peer_id.to_base58();
if base58[2..].starts_with(&prefix) {
println!("Found {:?}", peer_id);
println!("=> Private key = {:?}", secret.as_ref());
}
});
}
loop {
thread::sleep(Duration::from_secs(3600));
}
}