From dff2dfbc3007af7ce44e78c06834d2d36271cc9a Mon Sep 17 00:00:00 2001 From: ljedrz Date: Thu, 17 Sep 2020 10:41:11 +0200 Subject: [PATCH 1/2] test: fix go-ipfs 0.7 interop tests Signed-off-by: ljedrz --- tests/common/interop.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/common/interop.rs b/tests/common/interop.rs index 78579adfd..f8c0a0a53 100644 --- a/tests/common/interop.rs +++ b/tests/common/interop.rs @@ -59,8 +59,6 @@ pub mod common { .arg("init") .arg("-p") .arg("test") - .arg("--bits") - .arg("2048") .stdout(Stdio::null()) .status() .unwrap(); From d0910ab2b0035a51d7ad995cc60f86852f28c3ce Mon Sep 17 00:00:00 2001 From: ljedrz Date: Thu, 17 Sep 2020 12:14:50 +0200 Subject: [PATCH 2/2] feat: use the Ed25519 key instead of RSA by default; removes openssl dep Signed-off-by: ljedrz --- .github/workflows/ci.yml | 20 +--- Cargo.lock | 56 +---------- conformance/patches/bits.patch | 10 ++ conformance/test/index.js | 4 +- http/Cargo.toml | 3 +- http/src/config.rs | 168 ++++----------------------------- http/src/main.rs | 15 +-- 7 files changed, 39 insertions(+), 237 deletions(-) create mode 100644 conformance/patches/bits.patch diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6e73640a..47b2adde8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,11 +84,11 @@ jobs: - name: Install dependencies ubuntu if: matrix.platform.host == 'ubuntu-latest' - run: sudo apt-get install llvm-dev libssl-dev pkg-config + run: sudo apt-get install llvm-dev pkg-config - name: Install dependencies macos if: matrix.platform.host == 'macos-latest' - run: brew install llvm openssl + run: brew install llvm - name: Download go-ipfs on Linux if: matrix.platform.host == 'ubuntu-latest' @@ -96,16 +96,6 @@ jobs: curl -L https://github.com/ipfs/go-ipfs/releases/download/v0.6.0/go-ipfs_v0.6.0_linux-amd64.tar.gz --output go_ipfs.tar.gz tar -xf go_ipfs.tar.gz - - name: Install dependencies windows (openssl) - uses: lukka/run-vcpkg@v3.3 - id: windows-runvcpkg - if: matrix.platform.host == 'windows-latest' - with: - vcpkgDirectory: '${{ runner.workspace }}/vcpkg' - vcpkgTriplet: 'x64-windows' - vcpkgArguments: 'openssl' - vcpkgGitCommitId: 'ffa41582f78478812c836a6e8ce315fb27431182' # ok for openssl-sys v0.9.58 - - name: Install rust toolchain uses: hecrj/setup-rust-action@v1 with: @@ -132,13 +122,11 @@ jobs: - name: Build (android) if: contains(matrix.platform.target, 'android') - run: cargo ndk --android-platform 29 --target ${{ matrix.platform.target }} build --locked --workspace --exclude ipfs-http - # exclude http on android because openssl + run: cargo ndk --android-platform 29 --target ${{ matrix.platform.target }} build --locked --workspace - name: Build other cross compilations if: contains(matrix.platform.target, 'android') == false && matrix.platform.cross == true - run: cargo build --locked --workspace --exclude ipfs-http --target ${{ matrix.platform.target }} - # exclude http on other cross compilation targets because openssl + run: cargo build --locked --workspace --target ${{ matrix.platform.target }} - name: Rust tests (macos) if: matrix.platform.cross == false && matrix.platform.host == 'macos-latest' diff --git a/Cargo.lock b/Cargo.lock index acee78131..e7581fe10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -766,21 +766,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1241,11 +1226,11 @@ dependencies = [ "humantime", "hyper", "ipfs", + "libp2p-core", "mime", "mpart-async", "multibase", "multihash", - "openssl", "parity-multiaddr", "percent-encoding", "prost", @@ -1873,33 +1858,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "lazy_static", - "libc", - "openssl-sys", -] - -[[package]] -name = "openssl-sys" -version = "0.9.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parity-multiaddr" version = "0.9.2" @@ -2016,12 +1974,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" - [[package]] name = "plotters" version = "0.2.15" @@ -3029,12 +2981,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9232eb53352b4442e40d7900465dfc534e8cb2dc8f18656fcb2ac16112b5593" -[[package]] -name = "vcpkg" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" - [[package]] name = "vergen" version = "3.1.0" diff --git a/conformance/patches/bits.patch b/conformance/patches/bits.patch new file mode 100644 index 000000000..355dc3687 --- /dev/null +++ b/conformance/patches/bits.patch @@ -0,0 +1,10 @@ +--- node_modules/ipfsd-ctl/src/ipfsd-daemon.js ++++ node_modules/ipfsd-ctl/src/ipfsd-daemon.js +@@ -88,7 +88,6 @@ class Daemon { + const opts = merge( + { + emptyRepo: false, +- bits: this.opts.test ? 1024 : 2048, + profiles: this.opts.test ? ['test'] : [] + }, + typeof this.opts.ipfsOptions.init === 'boolean' ? {} : this.opts.ipfsOptions.init, diff --git a/conformance/test/index.js b/conformance/test/index.js index 7971e7163..14d8edc76 100644 --- a/conformance/test/index.js +++ b/conformance/test/index.js @@ -22,9 +22,7 @@ const options = { disposable: true, ipfsHttpModule: require('ipfs-http-client'), ipfsOptions: { - init: { - bits: 2048 - } + init: true, } } diff --git a/http/Cargo.toml b/http/Cargo.toml index 6335079cb..9bb5d0864 100644 --- a/http/Cargo.toml +++ b/http/Cargo.toml @@ -18,12 +18,11 @@ futures = { default-features = false, version = "0.3" } humantime = { default-features = false, version = "2.0" } hyper = { default-features = false, version = "0.13" } ipfs = { path = "../" } +libp2p-core = { default-features = false, version = "0.22" } mime = { default-features = false, version = "0.3" } mpart-async = { default-features = false, version = "0.4" } multibase = { default-features = false, version = "0.8" } multihash = { default-features = false, version = "0.11" } -# openssl is required for rsa keygen but not used by the rust-ipfs or its dependencies -openssl = { default-features = false, version = "0.10" } parity-multiaddr = { default-features = false, version = "0.9" } percent-encoding = { default-features = false, version = "2.1" } prost = { default-features = false, version = "0.6.1" } diff --git a/http/src/config.rs b/http/src/config.rs index cd9395ccc..fb9b8ede2 100644 --- a/http/src/config.rs +++ b/http/src/config.rs @@ -1,8 +1,8 @@ //! go-ipfs compatible configuration file handling or at least setup. +use libp2p_core::identity::{ed25519, Keypair}; use serde::{Deserialize, Serialize}; use std::fs::{self, File}; -use std::num::NonZeroU16; use std::path::Path; use thiserror::Error; @@ -19,8 +19,6 @@ pub enum InitializationError { DirectoryCreationFailed(std::io::Error), #[error("configuration file creation failed: {0}")] ConfigCreationFailed(std::io::Error), - #[error("invalid RSA key length given: {0}")] - InvalidRsaKeyLength(u16), #[error("unsupported profiles selected: {0:?}")] InvalidProfiles(Vec), #[error("key generation failed: {0}")] @@ -31,13 +29,8 @@ pub enum InitializationError { ConfigWritingFailed(serde_json::Error), } -/// Creates the IPFS_PATH directory structure and creates a new compatible configuration file with -/// RSA key of length `bits`. -pub fn initialize( - ipfs_path: &Path, - bits: NonZeroU16, - profiles: Vec, -) -> Result<(), InitializationError> { +/// Creates the IPFS_PATH directory structure and creates a new compatible configuration file +pub fn initialize(ipfs_path: &Path, profiles: Vec) -> Result<(), InitializationError> { let config_path = ipfs_path.join("config"); fs::create_dir_all(&ipfs_path) @@ -45,25 +38,14 @@ pub fn initialize( .and_then(|_| { fs::File::create(&config_path).map_err(InitializationError::ConfigCreationFailed) }) - .and_then(|config_file| create(config_file, bits, profiles)) + .and_then(|config_file| create(config_file, profiles)) } -fn create( - config: File, - bits: NonZeroU16, - profiles: Vec, -) -> Result<(), InitializationError> { +fn create(config: File, profiles: Vec) -> Result<(), InitializationError> { use multibase::Base::Base64Pad; use prost::Message; use std::io::BufWriter; - let bits = bits.get(); - - if bits < 2048 || bits > 16 * 1024 { - // ring will not accept a less than 2048 key - return Err(InitializationError::InvalidRsaKeyLength(bits)); - } - if profiles.len() != 1 || profiles[0] != "test" { // profiles are expected to be (comma separated) "test" as there are no bootstrap peer // handling yet. the conformance test cases seem to init `go-ipfs` in this profile where @@ -72,32 +54,15 @@ fn create( return Err(InitializationError::InvalidProfiles(profiles)); } - let pk = openssl::rsa::Rsa::generate(bits as u32) - .map_err(|e| InitializationError::KeyGeneration(Box::new(e)))?; - - // sadly the pkcs8 to der functions are not yet exposed via the nicer interface - // https://github.com/sfackler/rust-openssl/issues/880 - let pkcs8 = openssl::pkey::PKey::from_rsa(pk.clone()) - .and_then(|pk| pk.private_key_to_pem_pkcs8()) - .map_err(|e| InitializationError::KeyGeneration(Box::new(e)))?; - - let mut pkcs8 = pem_to_der(&pkcs8); - - let kp = ipfs::Keypair::rsa_from_pkcs8(&mut pkcs8) - .expect("Failed to turn pkcs#8 into libp2p::identity::Keypair"); - - let peer_id = kp.public().into_peer_id().to_string(); - - // TODO: this part could be PR'd to rust-libp2p as they already have some public key - // import/export but probably not if ring does not support these required conversions. - - let pkcs1 = pk - .private_key_to_der() - .map_err(|e| InitializationError::KeyGeneration(Box::new(e)))?; + let kp = ed25519::Keypair::generate(); + let peer_id = Keypair::Ed25519(kp.clone()) + .public() + .into_peer_id() + .to_string(); let key_desc = keys_proto::PrivateKey { - r#type: keys_proto::KeyType::Rsa as i32, - data: pkcs1, + r#type: keys_proto::KeyType::Ed25519 as i32, + data: kp.encode().to_vec(), }; let private_key = { @@ -162,70 +127,6 @@ pub fn load(config: File) -> Result { Ok(kp) } -/// Converts a PEM format to DER where PEM is a container for Base64 data with padding, starting on -/// the first line with a magic 5 dashes, "BEGIN" and the end of line is a tag which is expected to -/// be found in the end, in a separate line with magic 5 dashes, "END" and the tag. DER is the -/// decoded representation of the Base64 data. -/// -/// Between the start and end lines there might be some rules on how long lines the base64 encoded -/// bytes are split to, but this function does not make any checks on that. -/// -/// Returns the DER bytes (decoded base64) in the first tag delimited part (PEM files could have -/// multiple) regardless of the tag contents, as long as they match. -/// -/// ### Panics -/// -/// * If the buffer is not valid utf-8 * If the buffer does not start with five dashes and "BEGIN" -/// * If the buffer does not end with five dashes and "END", and the corresponding start tag -/// * Garbage is allowed after this -/// * If the base64 decoding fails for the middle part -/// -/// This is used only to get `PKCS#8` from `openssl` crate to DER format expected by `rust-libp2p` -/// and `ring`. The `PKCS#8` pem tag `PRIVATE KEY` is not validated. -fn pem_to_der(bytes: &[u8]) -> Vec { - use multibase::Base::Base64Pad; - - // Initially tried this with `pem` crate but it will give back bytes for the ascii, but we need - // the ascii for multibase's base64pad decoding. - let mut base64_encoded = String::new(); - - let pem = std::str::from_utf8(&bytes).expect("PEM should be utf8"); - - // this will hold the end of the line after -----BEGIN - let mut begin_tag = None; - let mut found_end_tag = false; - - for line in pem.lines() { - if begin_tag.is_none() { - assert!( - line.starts_with("-----BEGIN"), - "Unexpected first line in PEM: {}", - line - ); - begin_tag = Some(&line[(5 + 5)..]); - continue; - } - - if line.starts_with("-----END") { - let tag = begin_tag.unwrap(); - - assert_eq!(tag, &line[(5 + 3)..], "Unexpected ending in PEM: {}", line); - found_end_tag = true; - break; - } - - base64_encoded.push_str(line); - } - assert!( - found_end_tag, - "Failed to parse PEM, failed to find the end tag" - ); - - Base64Pad - .decode(base64_encoded) - .expect("PEM should contain Base64Pad") -} - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] struct CompatibleConfigFile { @@ -251,22 +152,14 @@ impl Identity { .decode(&self.private_key) .map_err(|e| LoadingError::PrivateKeyLoadingFailed(Box::new(e)))?; - let private_key = keys_proto::PrivateKey::decode(bytes.as_slice()) + let mut private_key = keys_proto::PrivateKey::decode(bytes.as_slice()) .map_err(|e| LoadingError::PrivateKeyLoadingFailed(Box::new(e)))?; Ok(match KeyType::from_i32(private_key.r#type) { - Some(KeyType::Rsa) => { - let pk = openssl::rsa::Rsa::private_key_from_der(&private_key.data) + Some(KeyType::Ed25519) => { + let kp = ed25519::Keypair::decode(&mut private_key.data) .map_err(|e| LoadingError::PrivateKeyLoadingFailed(Box::new(e)))?; - - let pkcs8 = openssl::pkey::PKey::from_rsa(pk) - .and_then(|pk| pk.private_key_to_pem_pkcs8()) - .map_err(|e| LoadingError::PrivateKeyLoadingFailed(Box::new(e)))?; - - let mut pkcs8 = pem_to_der(&pkcs8); - - ipfs::Keypair::rsa_from_pkcs8(&mut pkcs8) - .map_err(|e| LoadingError::PrivateKeyLoadingFailed(Box::new(e)))? + Keypair::Ed25519(kp) } _keytype => return Err(LoadingError::UnsupportedPrivateKeyType(private_key.r#type)), }) @@ -275,37 +168,14 @@ impl Identity { #[cfg(test)] mod test { - #[test] - fn pem_to_der_helloworld() { - use super::pem_to_der; - let input = "-----BEGIN something anything----- -aGVsbG8gd29ybGQ= ------END something anything----- - -garbage in the end is ignored"; - - assert_eq!(pem_to_der(input.as_bytes()), b"hello world"); - } - - #[test] - #[should_panic] - fn pem_to_der_tag_mismatch() { - use super::pem_to_der; - let input = "-----BEGIN something something----- -aGVsbG8gd29ybGQ= ------END something foobar-----"; - - pem_to_der(input.as_bytes()); - } - #[test] fn read_private_key_from_goipfs() { use super::Identity; - // generated with go-ipfs 0.4.23, init --bits 2048 + // generated with go-ipfs 0.7.0-rc2, init let input = Identity { - peer_id: String::from("QmVNXj4TENKBjaUmQQzYMawDXu5LcEEzLyf4K6Ds3WgcF3"), - private_key: String::from("CAASqQkwggSlAgEAAoIBAQDVwj2MoXUccztSbZarmjQusB+7dZw1ZDycnGlOtLTjsc/Fl7keESwQB+nSXvt3DjV+ftTmK3nPODNVY2c+nooyX3k9svQogSmHxfQIwHkKe11VmrMNTdsYwfswcDq4PgNWrGX8/vUBtfvVb0qzgevBXwc4C9+SDIhRtjiHRNSexc2vFx59tQv03VTfj3sbxdBTwWN+ReeCTyf+7nE3Mg7NdHQ78mysMDFT3w1HDwZ+qt4dpyH5mZRm0anNWQUBtQue7IwzUsHzVCUzm+NeYXJf/miSNw2CCQUfA245+H6zu1F0SJFvTVTKCEmZ7D2ZkseRG73Srm0rdD1jajLiBhUtAgMBAAECggEBALy/mHOuOefWRGKDjBCYyE0Vjd+MeVOX4AF2B3LNFBEeeFWEpJxNE3hQVIJDBo7ZCBlbSwi3CQcWHBXhAVCE04ipTzhQ5VFCw/Y0sEhuFDNSPVcSk9pCjh1tZC0gXGlFsNL+xcvBIXzSQb30WKTrKs6D567wpQikclacrYucFpbee5/wE6GdE9mtrXK69vP5vgAtLQmg0TZljDI78agPwbEUlTVoVxA1JCcroWBfVjuY/xPjBcHUO+8fKsh2P5vqsiwcvbd8Pc4BwqJsct1LbE4sFvHjUvj6XQR3bS38z7XsaqWV9y65s/xgNQdw5zpt+wlRwNjN6+7djPKYRrSZO6ECgYEA6Fdv54Z4Yk7JRwjWSe2aWR3Mz/4o7UM2ILhMhb6DxEpiXfcErbPTqdFdnAuZ3Yp8cEyR2TZB4PYEAxh9zmS+akO1CqG9XaD6ZX76pvM/5p+Kpd6M/wbDNtYFY7tTuLX7J7IXA6vsUaMF4nZxsEp0EvF1wXB29ZiRp4oan7C/FYUCgYEA64ZlbjYb7LSfFGyJl/VaH5ka2Y1L9XWApgY+YphV6e2gCT7kaOKjxve0t0quYQMnpPJKw9MWPSNh2TE9XjJJcpR/EgkEX9/rBMg8VScqyxtItS/voUrW79qCwrHhRR5iY77a9rAZwVkl0EDyIx+cq7ebyK0OCz6891//FBWdnYkCgYBhfxeBU0c/EYqa2VV6zk7fqIainSe1cGfNUSkjUm/etcwTXC3FalmewDGE4sVdVtijEy58tKzuZq4GUoewTUwuMV1OKdLZ8ExCvQcXeanN8BLxSbNm7QKMB0FZuWkHcK4E2VGZA9L16u/0OPm6HXQZ4uMkGjqBEtXENUq4yiVVNQKBgQCzshydU+dGWCCvYogwSl/yj8vuhGGZ64a2JTlf3D5gdo6Nv1BhvdmbKs7UscQN/Gw46yuj8N+c0ewL3AeoYNGs/CNfTUXrKFqVkXiGt5Vs1WpJ40L/WqxW3+64QSNQqvgChlFlucJMxImXNJYJukq8sR/IolB+v+VJEBL77eoNkQKBgQCFQYL064rQZqEBc1dWy2Cucf5eWH5VFBHxCPC5Y6orxpmljYuduAIO0+InoVC+KEAkRPjHU3gFGdBvlDif3x2a8eFsZl//RCd9QdpTToynhl+WNKqQH87kfjsBoFW1L5QYLTbKK558QUp9yR6siKW0viXDbOvB7lK8WaDdYX8lcA=="), + peer_id: String::from("12D3KooWAPm5eb4mqMAW6ZqC4f2dmWdNw3XeezqoaebdTL8byqMg"), + private_key: String::from("CAESQJw7A9j4lW53GtGEEl7CuKHCwpf5LeyAhsvYDfuh+FhqCI4S09NZYclzYM7fwQC4us0s8VcSBkWqRt1SPRaHI28="), }; let peer_id = input diff --git a/http/src/main.rs b/http/src/main.rs index 761b53813..6eea77c12 100644 --- a/http/src/main.rs +++ b/http/src/main.rs @@ -1,4 +1,3 @@ -use std::num::NonZeroU16; use std::path::PathBuf; use structopt::StructOpt; @@ -11,11 +10,8 @@ extern crate tracing; #[derive(Debug, StructOpt)] enum Options { /// Should initialize the repository (create directories and such). `js-ipfsd-ctl` calls this - /// with two arguments by default, `--bits 1024` and `--profile test`. + /// with a `--profile test` argument by default. Init { - /// Generated key length - #[structopt(long)] - bits: NonZeroU16, /// List of configuration profiles to apply #[structopt(long, use_delimiter = true)] profile: Vec, @@ -60,7 +56,7 @@ fn main() { let config_path = home.join("config"); let keypair = match opts { - Options::Init { bits, profile } => { + Options::Init { profile } => { println!("initializing IPFS node at {:?}", home); if config_path.is_file() { @@ -69,7 +65,7 @@ fn main() { std::process::exit(1); } - let result = config::initialize(&home, bits, profile); + let result = config::initialize(&home, profile); match result { Ok(_) => { @@ -96,11 +92,6 @@ fn main() { eprintln!("Reinitializing would override your keys."); std::process::exit(1); } - Err(config::InitializationError::InvalidRsaKeyLength(bits)) => { - eprintln!("Error: --bits out of range [1024, 16384]: {}", bits); - eprintln!("This is a fake version of ipfs cli which does not support much"); - std::process::exit(1); - } Err(config::InitializationError::InvalidProfiles(profiles)) => { eprintln!("Error: unsupported profile selection: {:?}", profiles); eprintln!("This is a fake version of ipfs cli which does not support much");