Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions prdoc/pr_12621.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
title: 'feat(webrtc): certificate, can now be managed like node secret key'
doc:
- audience: Todo
description: |-
# Description

The WebRTC certificate, which determines the node's WebRTC `/certhash`, can now be managed like the node secret key.

## Integration

A new `key generate-webrtc-certificate` subcommand generates a certificate, writes it to a file (or stdout) and prints the corresponding certhash to stderr, mirroring `key generate-node-key`.

Additionally, `--experimental-webrtc` now accepts an optional certificate file path (`--experimental-webrtc=<CERT_FILE>`): the certificate is loaded from the file if it exists, or generated and persisted there otherwise.

Without a path the behavior is unchanged, the certificate is loaded from or generated at `<base_path>/chains/<chain_id>/network/webrtc_certificate`.
crates:
- name: sc-cli
bump: major
- name: sc-network
bump: major
159 changes: 159 additions & 0 deletions substrate/client/cli/src/commands/generate_webrtc_certificate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Implementation of the `generate-webrtc-certificate` subcommand

use crate::{build_network_key_dir_or_default, Error};
use clap::Parser;
use sc_network::webrtc::{
certhash, encode_certificate, generate_certificate, WEBRTC_CERTIFICATE_FILE,
};
use sc_service::BasePath;
use std::{
fs,
io::{self, Write},
path::PathBuf,
};

/// The `generate-webrtc-certificate` command
#[derive(Debug, Clone, Parser)]
#[command(
name = "generate-webrtc-certificate",
about = "Generate a WebRTC DTLS certificate, write it to a file or stdout \
and write the corresponding certhash to stderr"
)]
pub struct GenerateWebRtcCertificateCmd {
/// Name of file to save the certificate to.
/// If not given, the certificate is printed to stdout.
#[arg(long)]
file: Option<PathBuf>,

/// The output is in raw binary format.
/// If not given, the output is written as an hex encoded string.
#[arg(long)]
bin: bool,

Comment on lines +46 to +50

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should drop hexifying completely and store binary data only.

/// Specify the chain specification.
///
/// It can be any of the predefined chains like dev, local, staging, polkadot, kusama.
#[arg(long, value_name = "CHAIN_SPEC")]
pub chain: Option<String>,

/// A directory where the certificate should be saved. If a certificate already
/// exists in the directory, it won't be overwritten.
#[arg(long, conflicts_with_all = ["file", "default_base_path"])]
base_path: Option<PathBuf>,

/// Save the certificate in the default directory. If a certificate already
/// exists in the directory, it won't be overwritten.
#[arg(long, conflicts_with_all = ["base_path", "file"])]
default_base_path: bool,
}

impl GenerateWebRtcCertificateCmd {
/// Run the command
pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> {
let certificate = generate_certificate().map_err(Error::Input)?;

let encoded = encode_certificate(&certificate);
let file_data =
if self.bin { encoded } else { array_bytes::bytes2hex("", &encoded).into_bytes() };

match (&self.file, &self.base_path, self.default_base_path) {
(Some(file), None, false) => fs::write(file, file_data)?,
(None, Some(_), false) | (None, None, true) => {
let network_path = build_network_key_dir_or_default(
self.base_path.clone().map(BasePath::new),
chain_spec_id,
executable_name,
);

fs::create_dir_all(network_path.as_path())?;

let certificate_path = network_path.join(WEBRTC_CERTIFICATE_FILE);
if certificate_path.exists() {
eprintln!(
"Skip generation, a certificate already exists in {:?}",
certificate_path
);
return Err(Error::KeyAlreadyExistsInPath(certificate_path));
} else {
eprintln!("Generating certificate in {:?}", certificate_path);
fs::write(certificate_path, file_data)?
}
},
(None, None, false) => io::stdout().lock().write_all(&file_data)?,
(_, _, _) => {
// This should not happen, arguments are marked as mutually exclusive.
return Err(Error::Input("Mutually exclusive arguments provided".into()));
},
}

eprintln!("{}", certhash(&certificate));

Ok(())
}
}

#[cfg(test)]
pub mod tests {
use crate::DEFAULT_NETWORK_CONFIG_PATH;

use super::*;
use sc_network::webrtc::decode_certificate;
use std::io::Read;
use tempfile::Builder;

#[test]
fn generate_webrtc_certificate_file() {
let mut file = Builder::new().prefix("certfile").tempfile().unwrap();
let file_path = file.path().display().to_string();
let generate = GenerateWebRtcCertificateCmd::parse_from(&[
"generate-webrtc-certificate",
"--file",
&file_path,
]);
assert!(generate.run("test", &String::from("test")).is_ok());
let mut buf = String::new();
assert!(file.read_to_string(&mut buf).is_ok());
assert!(decode_certificate(buf.as_bytes()).is_ok());
}

#[test]
fn generate_webrtc_certificate_base_path() {
let base_dir = Builder::new().prefix("certfile").tempdir().unwrap();
let certificate_path = base_dir
.path()
.join("chains/test_id/")
.join(DEFAULT_NETWORK_CONFIG_PATH)
.join(WEBRTC_CERTIFICATE_FILE);
let base_path = base_dir.path().display().to_string();
let generate = GenerateWebRtcCertificateCmd::parse_from(&[
"generate-webrtc-certificate",
"--base-path",
&base_path,
]);
assert!(generate.run("test_id", &String::from("test")).is_ok());
let buf = fs::read_to_string(certificate_path.as_path()).unwrap();
assert!(decode_certificate(buf.as_bytes()).is_ok());

assert!(generate.run("test_id", &String::from("test")).is_err());
let new_buf = fs::read_to_string(certificate_path).unwrap();
assert_eq!(new_buf, buf);
}
}
11 changes: 10 additions & 1 deletion substrate/client/cli/src/commands/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
//! Key related CLI utilities

use super::{
generate::GenerateCmd, generate_node_key::GenerateNodeKeyCmd, insert_key::InsertKeyCmd,
generate::GenerateCmd, generate_node_key::GenerateNodeKeyCmd,
generate_webrtc_certificate::GenerateWebRtcCertificateCmd, insert_key::InsertKeyCmd,
inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd,
};
use crate::{Error, SubstrateCli};
Expand All @@ -31,6 +32,10 @@ pub enum KeySubcommand {
/// corresponding peer-id to stderr
GenerateNodeKey(GenerateNodeKeyCmd),

/// Generate a WebRTC DTLS certificate, write it to a file or stdout and write the
/// corresponding certhash to stderr
GenerateWebrtcCertificate(GenerateWebRtcCertificateCmd),

/// Generate a random account
Generate(GenerateCmd),

Expand All @@ -52,6 +57,10 @@ impl KeySubcommand {
let chain_spec = cli.load_spec(cmd.chain.as_deref().unwrap_or(""))?;
cmd.run(chain_spec.id(), &C::executable_name())
},
KeySubcommand::GenerateWebrtcCertificate(cmd) => {
let chain_spec = cli.load_spec(cmd.chain.as_deref().unwrap_or(""))?;
cmd.run(chain_spec.id(), &C::executable_name())
},
KeySubcommand::Generate(cmd) => cmd.run(),
KeySubcommand::Inspect(cmd) => cmd.run(),
KeySubcommand::Insert(cmd) => cmd.run(cli),
Expand Down
4 changes: 3 additions & 1 deletion substrate/client/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ mod export_chain_spec_cmd;
mod export_state_cmd;
mod generate;
mod generate_node_key;
mod generate_webrtc_certificate;
mod import_blocks_cmd;
mod insert_key;
mod inspect_key;
Expand All @@ -44,7 +45,8 @@ pub use self::{
build_spec_cmd::BuildSpecCmd, chain_info_cmd::ChainInfoCmd, check_block_cmd::CheckBlockCmd,
export_blocks_cmd::ExportBlocksCmd, export_chain_spec_cmd::ExportChainSpecCmd,
export_state_cmd::ExportStateCmd, generate::GenerateCmd,
generate_node_key::GenerateKeyCmdCommon, import_blocks_cmd::ImportBlocksCmd,
generate_node_key::GenerateKeyCmdCommon,
generate_webrtc_certificate::GenerateWebRtcCertificateCmd, import_blocks_cmd::ImportBlocksCmd,
insert_key::InsertKeyCmd, inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd,
key::KeySubcommand, purge_chain_cmd::PurgeChainCmd, revert_cmd::RevertCmd, run_cmd::RunCmd,
sign::SignCmd, vanity::VanityCmd, verify::VerifyCmd,
Expand Down
15 changes: 12 additions & 3 deletions substrate/client/cli/src/params/network_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,16 @@ pub struct NetworkParams {
///
/// Without this enabled, WebRTC addresses specified in `listen_addr`
/// will be skipped. Only works on litep2p network backend.
#[arg(long)]
pub experimental_webrtc: bool,
///
/// Optionally takes a path to the WebRTC DTLS certificate file
/// (`--experimental-webrtc=<CERT_FILE>`), which determines the node's `/certhash`.
/// The certificate is loaded from the file if it exists, otherwise a new certificate
/// is generated and persisted there. It can be generated ahead of time with the
/// `key generate-webrtc-certificate` subcommand, which accepts both hex and raw
/// binary formats. When no path is given, the certificate is loaded from or
/// generated at `<base_path>/chains/<chain_id>/network/webrtc_certificate`.
#[arg(long, value_name = "CERT_FILE", num_args = 0..=1, require_equals = true)]
pub experimental_webrtc: Option<Option<PathBuf>>,
Comment on lines +82 to +90

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a dedicated --webrtc-certificate=<CERT_FILE> switch from the very beginning. This way we avoid ambiguity with a switch having uncertain number of arguments, and once --experimental-webrtc is decomissioned, the --webrtc-certificate stays.


/// Specify p2p protocol TCP port.
#[arg(long, value_name = "PORT", conflicts_with_all = &[ "listen_addr" ])]
Expand Down Expand Up @@ -283,7 +291,8 @@ impl NetworkParams {
},
default_peers_set_num_full: self.in_peers + self.out_peers,
listen_addresses,
experimental_webrtc: self.experimental_webrtc,
experimental_webrtc: self.experimental_webrtc.is_some(),
webrtc_certificate_file: self.experimental_webrtc.clone().flatten(),
public_addresses,
node_key,
node_name: node_name.to_string(),
Expand Down
9 changes: 9 additions & 0 deletions substrate/client/network/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,14 @@ pub struct NetworkConfiguration {
/// Allow WebRtc addresses, this is an experimental feature.
pub experimental_webrtc: bool,

/// Path to the WebRTC DTLS certificate file, which determines the node's `/certhash`.
///
/// The certificate is loaded from the file if it exists, otherwise a new certificate
/// is generated and persisted there. If `None`, the certificate is loaded from or
/// generated at `<net_config_path>/webrtc_certificate`, falling back to an ephemeral
/// certificate when `net_config_path` is `None`.
pub webrtc_certificate_file: Option<PathBuf>,

/// Multiaddresses to advertise. Detected automatically if empty.
pub public_addresses: Vec<Multiaddr>,

Expand Down Expand Up @@ -710,6 +718,7 @@ impl NetworkConfiguration {
net_config_path,
listen_addresses: Vec::new(),
experimental_webrtc: false,
webrtc_certificate_file: None,
public_addresses: Vec::new(),
boot_nodes: Vec::new(),
node_key,
Expand Down
1 change: 1 addition & 0 deletions substrate/client/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ pub mod service;
pub mod transport;
pub mod types;
pub mod utils;
pub mod webrtc;

pub use crate::litep2p::Litep2pNetworkBackend;
pub use event::{DhtEvent, Event};
Expand Down
Loading
Loading