Move QUIC TLS certificate code in its own file (#26569)
This commit is contained in:
parent
9d31216d23
commit
14ee87d793
|
@ -4948,13 +4948,11 @@ dependencies = [
|
||||||
"jsonrpc-http-server",
|
"jsonrpc-http-server",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"pkcs8",
|
|
||||||
"quinn",
|
"quinn",
|
||||||
"quinn-proto",
|
"quinn-proto",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rand_chacha 0.2.2",
|
"rand_chacha 0.2.2",
|
||||||
"rayon",
|
"rayon",
|
||||||
"rcgen",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rustls 0.20.6",
|
"rustls 0.20.6",
|
||||||
"semver 1.0.10",
|
"semver 1.0.10",
|
||||||
|
|
|
@ -27,13 +27,11 @@ itertools = "0.10.2"
|
||||||
jsonrpc-core = "18.0.0"
|
jsonrpc-core = "18.0.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
pkcs8 = { version = "0.8.0", features = ["alloc"] }
|
|
||||||
quinn = "0.8.3"
|
quinn = "0.8.3"
|
||||||
quinn-proto = "0.8.3"
|
quinn-proto = "0.8.3"
|
||||||
rand = "0.7.0"
|
rand = "0.7.0"
|
||||||
rand_chacha = "0.2.2"
|
rand_chacha = "0.2.2"
|
||||||
rayon = "1.5.3"
|
rayon = "1.5.3"
|
||||||
rcgen = "0.9.2"
|
|
||||||
reqwest = { version = "0.11.11", default-features = false, features = ["blocking", "brotli", "deflate", "gzip", "rustls-tls", "json"] }
|
reqwest = { version = "0.11.11", default-features = false, features = ["blocking", "brotli", "deflate", "gzip", "rustls-tls", "json"] }
|
||||||
rustls = { version = "0.20.6", features = ["dangerous_configuration"] }
|
rustls = { version = "0.20.6", features = ["dangerous_configuration"] }
|
||||||
semver = "1.0.10"
|
semver = "1.0.10"
|
||||||
|
|
|
@ -7,11 +7,10 @@ use {
|
||||||
tpu_connection::{BlockingConnection, ClientStats},
|
tpu_connection::{BlockingConnection, ClientStats},
|
||||||
},
|
},
|
||||||
indexmap::map::{Entry, IndexMap},
|
indexmap::map::{Entry, IndexMap},
|
||||||
pkcs8::{der::Document, AlgorithmIdentifier, ObjectIdentifier},
|
|
||||||
rand::{thread_rng, Rng},
|
rand::{thread_rng, Rng},
|
||||||
rcgen::{CertificateParams, DistinguishedName, DnType, SanType},
|
|
||||||
solana_measure::measure::Measure,
|
solana_measure::measure::Measure,
|
||||||
solana_sdk::{quic::QUIC_PORT_OFFSET, signature::Keypair, timing::AtomicInterval},
|
solana_sdk::{quic::QUIC_PORT_OFFSET, signature::Keypair, timing::AtomicInterval},
|
||||||
|
solana_streamer::tls_certificates::new_self_signed_tls_certificate_chain,
|
||||||
std::{
|
std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||||
|
@ -255,68 +254,6 @@ impl ConnectionPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_cert(
|
|
||||||
identity_keypair: &Keypair,
|
|
||||||
san: IpAddr,
|
|
||||||
) -> Result<QuicClientCertificate, Box<dyn Error>> {
|
|
||||||
// Generate a self-signed cert from client's identity key
|
|
||||||
let cert_params = new_cert_params(identity_keypair, san);
|
|
||||||
let cert = rcgen::Certificate::from_params(cert_params)?;
|
|
||||||
let cert_der = cert.serialize_der().unwrap();
|
|
||||||
let priv_key = cert.serialize_private_key_der();
|
|
||||||
let priv_key = rustls::PrivateKey(priv_key);
|
|
||||||
Ok(QuicClientCertificate {
|
|
||||||
certificates: vec![rustls::Certificate(cert_der)],
|
|
||||||
key: priv_key,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_to_rcgen_keypair(identity_keypair: &Keypair) -> rcgen::KeyPair {
|
|
||||||
// from https://datatracker.ietf.org/doc/html/rfc8410#section-3
|
|
||||||
const ED25519_IDENTIFIER: [u32; 4] = [1, 3, 101, 112];
|
|
||||||
let mut private_key = Vec::<u8>::with_capacity(34);
|
|
||||||
private_key.extend_from_slice(&[0x04, 0x20]); // ASN.1 OCTET STRING
|
|
||||||
private_key.extend_from_slice(identity_keypair.secret().as_bytes());
|
|
||||||
let key_pkcs8 = pkcs8::PrivateKeyInfo {
|
|
||||||
algorithm: AlgorithmIdentifier {
|
|
||||||
oid: ObjectIdentifier::from_arcs(&ED25519_IDENTIFIER).unwrap(),
|
|
||||||
parameters: None,
|
|
||||||
},
|
|
||||||
private_key: &private_key,
|
|
||||||
public_key: None,
|
|
||||||
};
|
|
||||||
let key_pkcs8_der = key_pkcs8
|
|
||||||
.to_der()
|
|
||||||
.expect("Failed to convert keypair to DER")
|
|
||||||
.to_der();
|
|
||||||
|
|
||||||
// Parse private key into rcgen::KeyPair struct.
|
|
||||||
rcgen::KeyPair::from_der(&key_pkcs8_der).expect("Failed to parse keypair from DER")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_cert_params(identity_keypair: &Keypair, san: IpAddr) -> CertificateParams {
|
|
||||||
// TODO(terorie): Is it safe to sign the TLS cert with the identity private key?
|
|
||||||
|
|
||||||
// Unfortunately, rcgen does not accept a "raw" Ed25519 key.
|
|
||||||
// We have to convert it to DER and pass it to the library.
|
|
||||||
|
|
||||||
// Convert private key into PKCS#8 v1 object.
|
|
||||||
// RFC 8410, Section 7: Private Key Format
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc8410#section-
|
|
||||||
|
|
||||||
let keypair = convert_to_rcgen_keypair(identity_keypair);
|
|
||||||
|
|
||||||
let mut cert_params = CertificateParams::default();
|
|
||||||
cert_params.subject_alt_names = vec![SanType::IpAddress(san)];
|
|
||||||
cert_params.alg = &rcgen::PKCS_ED25519;
|
|
||||||
cert_params.key_pair = Some(keypair);
|
|
||||||
cert_params.distinguished_name = DistinguishedName::new();
|
|
||||||
cert_params
|
|
||||||
.distinguished_name
|
|
||||||
.push(DnType::CommonName, "Solana node");
|
|
||||||
cert_params
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConnectionCache {
|
impl ConnectionCache {
|
||||||
pub fn new(connection_pool_size: usize) -> Self {
|
pub fn new(connection_pool_size: usize) -> Self {
|
||||||
// The minimum pool size is 1.
|
// The minimum pool size is 1.
|
||||||
|
@ -333,8 +270,11 @@ impl ConnectionCache {
|
||||||
keypair: &Keypair,
|
keypair: &Keypair,
|
||||||
ipaddr: IpAddr,
|
ipaddr: IpAddr,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let cert = new_cert(keypair, ipaddr)?;
|
let (certs, priv_key) = new_self_signed_tls_certificate_chain(keypair, ipaddr)?;
|
||||||
self.client_certificate = Arc::new(cert);
|
self.client_certificate = Arc::new(QuicClientCertificate {
|
||||||
|
certificates: certs,
|
||||||
|
key: priv_key,
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,6 +506,11 @@ impl ConnectionCache {
|
||||||
|
|
||||||
impl Default for ConnectionCache {
|
impl Default for ConnectionCache {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let (certs, priv_key) = new_self_signed_tls_certificate_chain(
|
||||||
|
&Keypair::new(),
|
||||||
|
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||||
|
)
|
||||||
|
.expect("Failed to initialize QUIC client certificates");
|
||||||
Self {
|
Self {
|
||||||
map: RwLock::new(IndexMap::with_capacity(MAX_CONNECTIONS)),
|
map: RwLock::new(IndexMap::with_capacity(MAX_CONNECTIONS)),
|
||||||
stats: Arc::new(ConnectionCacheStats::default()),
|
stats: Arc::new(ConnectionCacheStats::default()),
|
||||||
|
@ -577,9 +522,10 @@ impl Default for ConnectionCache {
|
||||||
.expect("Unable to bind to UDP socket"),
|
.expect("Unable to bind to UDP socket"),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
client_certificate: new_cert(&Keypair::new(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))
|
client_certificate: Arc::new(QuicClientCertificate {
|
||||||
.map(Arc::new)
|
certificates: certs,
|
||||||
.expect("Failed to initialize QUIC client certificates"),
|
key: priv_key,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,8 @@
|
||||||
//! server's flow control.
|
//! server's flow control.
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
client_error::ClientErrorKind,
|
client_error::ClientErrorKind, connection_cache::ConnectionCacheStats,
|
||||||
connection_cache::{new_cert, ConnectionCacheStats},
|
nonblocking::tpu_connection::TpuConnection, tpu_connection::ClientStats,
|
||||||
nonblocking::tpu_connection::TpuConnection,
|
|
||||||
tpu_connection::ClientStats,
|
|
||||||
},
|
},
|
||||||
async_mutex::Mutex,
|
async_mutex::Mutex,
|
||||||
async_trait::async_trait,
|
async_trait::async_trait,
|
||||||
|
@ -27,6 +25,7 @@ use {
|
||||||
signature::Keypair,
|
signature::Keypair,
|
||||||
transport::Result as TransportResult,
|
transport::Result as TransportResult,
|
||||||
},
|
},
|
||||||
|
solana_streamer::tls_certificates::new_self_signed_tls_certificate_chain,
|
||||||
std::{
|
std::{
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||||
sync::{atomic::Ordering, Arc},
|
sync::{atomic::Ordering, Arc},
|
||||||
|
@ -134,9 +133,15 @@ impl QuicLazyInitializedEndpoint {
|
||||||
|
|
||||||
impl Default for QuicLazyInitializedEndpoint {
|
impl Default for QuicLazyInitializedEndpoint {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let certificate = new_cert(&Keypair::new(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))
|
let (certs, priv_key) = new_self_signed_tls_certificate_chain(
|
||||||
|
&Keypair::new(),
|
||||||
|
IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|
||||||
|
)
|
||||||
.expect("Failed to create QUIC client certificate");
|
.expect("Failed to create QUIC client certificate");
|
||||||
Self::new(Arc::new(certificate))
|
Self::new(Arc::new(QuicClientCertificate {
|
||||||
|
certificates: certs,
|
||||||
|
key: priv_key,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4593,13 +4593,11 @@ dependencies = [
|
||||||
"jsonrpc-core",
|
"jsonrpc-core",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"pkcs8",
|
|
||||||
"quinn",
|
"quinn",
|
||||||
"quinn-proto",
|
"quinn-proto",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rand_chacha 0.2.2",
|
"rand_chacha 0.2.2",
|
||||||
"rayon",
|
"rayon",
|
||||||
"rcgen",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rustls 0.20.6",
|
"rustls 0.20.6",
|
||||||
"semver",
|
"semver",
|
||||||
|
|
|
@ -6,6 +6,7 @@ pub mod recvmmsg;
|
||||||
pub mod sendmmsg;
|
pub mod sendmmsg;
|
||||||
pub mod socket;
|
pub mod socket;
|
||||||
pub mod streamer;
|
pub mod streamer;
|
||||||
|
pub mod tls_certificates;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
|
@ -2,6 +2,7 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
quic::{configure_server, QuicServerError, StreamStats},
|
quic::{configure_server, QuicServerError, StreamStats},
|
||||||
streamer::StakedNodes,
|
streamer::StakedNodes,
|
||||||
|
tls_certificates::get_pubkey_from_tls_certificate,
|
||||||
},
|
},
|
||||||
crossbeam_channel::Sender,
|
crossbeam_channel::Sender,
|
||||||
futures_util::stream::StreamExt,
|
futures_util::stream::StreamExt,
|
||||||
|
@ -15,7 +16,6 @@ use {
|
||||||
solana_perf::packet::PacketBatch,
|
solana_perf::packet::PacketBatch,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
packet::{Packet, PACKET_DATA_SIZE},
|
packet::{Packet, PACKET_DATA_SIZE},
|
||||||
pubkey::Pubkey,
|
|
||||||
quic::{QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS, QUIC_MAX_UNSTAKED_CONCURRENT_STREAMS},
|
quic::{QUIC_CONNECTION_HANDSHAKE_TIMEOUT_MS, QUIC_MAX_UNSTAKED_CONCURRENT_STREAMS},
|
||||||
signature::Keypair,
|
signature::Keypair,
|
||||||
timing,
|
timing,
|
||||||
|
@ -32,7 +32,6 @@ use {
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
time::{sleep, timeout},
|
time::{sleep, timeout},
|
||||||
},
|
},
|
||||||
x509_parser::{prelude::*, public_key::PublicKey},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const QUIC_TOTAL_STAKED_CONCURRENT_STREAMS: f64 = 100_000f64;
|
const QUIC_TOTAL_STAKED_CONCURRENT_STREAMS: f64 = 100_000f64;
|
||||||
|
@ -139,21 +138,11 @@ fn get_connection_stake(connection: &Connection, staked_nodes: Arc<RwLock<Staked
|
||||||
.peer_identity()
|
.peer_identity()
|
||||||
.and_then(|der_cert_any| der_cert_any.downcast::<Vec<rustls::Certificate>>().ok())
|
.and_then(|der_cert_any| der_cert_any.downcast::<Vec<rustls::Certificate>>().ok())
|
||||||
.and_then(|der_certs| {
|
.and_then(|der_certs| {
|
||||||
der_certs.first().and_then(|der_cert| {
|
get_pubkey_from_tls_certificate(&der_certs).and_then(|pubkey| {
|
||||||
X509Certificate::from_der(der_cert.as_ref())
|
|
||||||
.ok()
|
|
||||||
.and_then(|(_, cert)| {
|
|
||||||
cert.public_key().parsed().ok().and_then(|key| match key {
|
|
||||||
PublicKey::Unknown(inner_key) => {
|
|
||||||
let pubkey = Pubkey::new(inner_key);
|
|
||||||
debug!("Peer public key is {:?}", pubkey);
|
debug!("Peer public key is {:?}", pubkey);
|
||||||
|
|
||||||
let staked_nodes = staked_nodes.read().unwrap();
|
let staked_nodes = staked_nodes.read().unwrap();
|
||||||
staked_nodes.pubkey_stake_map.get(&pubkey).copied()
|
staked_nodes.pubkey_stake_map.get(&pubkey).copied()
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
|
@ -657,7 +646,10 @@ impl ConnectionTable {
|
||||||
pub mod test {
|
pub mod test {
|
||||||
use {
|
use {
|
||||||
super::*,
|
super::*,
|
||||||
crate::quic::{new_cert, MAX_STAKED_CONNECTIONS, MAX_UNSTAKED_CONNECTIONS},
|
crate::{
|
||||||
|
quic::{MAX_STAKED_CONNECTIONS, MAX_UNSTAKED_CONNECTIONS},
|
||||||
|
tls_certificates::new_self_signed_tls_certificate_chain,
|
||||||
|
},
|
||||||
crossbeam_channel::{unbounded, Receiver},
|
crossbeam_channel::{unbounded, Receiver},
|
||||||
quinn::{ClientConfig, IdleTimeout, VarInt},
|
quinn::{ClientConfig, IdleTimeout, VarInt},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
|
@ -693,8 +685,8 @@ pub mod test {
|
||||||
|
|
||||||
pub fn get_client_config(keypair: &Keypair) -> ClientConfig {
|
pub fn get_client_config(keypair: &Keypair) -> ClientConfig {
|
||||||
let ipaddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
let ipaddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||||
let (certs, key) =
|
let (certs, key) = new_self_signed_tls_certificate_chain(keypair, ipaddr)
|
||||||
new_cert(keypair, ipaddr).expect("Failed to generate client certificate");
|
.expect("Failed to generate client certificate");
|
||||||
|
|
||||||
let mut crypto = rustls::ClientConfig::builder()
|
let mut crypto = rustls::ClientConfig::builder()
|
||||||
.with_safe_defaults()
|
.with_safe_defaults()
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use {
|
use {
|
||||||
crate::streamer::StakedNodes,
|
crate::{streamer::StakedNodes, tls_certificates::new_self_signed_tls_certificate_chain},
|
||||||
crossbeam_channel::Sender,
|
crossbeam_channel::Sender,
|
||||||
pem::Pem,
|
pem::Pem,
|
||||||
pkcs8::{der::Document, AlgorithmIdentifier, ObjectIdentifier},
|
|
||||||
quinn::{IdleTimeout, ServerConfig, VarInt},
|
quinn::{IdleTimeout, ServerConfig, VarInt},
|
||||||
rcgen::{CertificateParams, DistinguishedName, DnType, SanType},
|
|
||||||
rustls::{server::ClientCertVerified, Certificate, DistinguishedNames},
|
rustls::{server::ClientCertVerified, Certificate, DistinguishedNames},
|
||||||
solana_perf::packet::PacketBatch,
|
solana_perf::packet::PacketBatch,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
|
@ -13,7 +11,6 @@ use {
|
||||||
signature::Keypair,
|
signature::Keypair,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
error::Error,
|
|
||||||
net::{IpAddr, UdpSocket},
|
net::{IpAddr, UdpSocket},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||||
|
@ -59,7 +56,8 @@ pub(crate) fn configure_server(
|
||||||
gossip_host: IpAddr,
|
gossip_host: IpAddr,
|
||||||
) -> Result<(ServerConfig, String), QuicServerError> {
|
) -> Result<(ServerConfig, String), QuicServerError> {
|
||||||
let (cert_chain, priv_key) =
|
let (cert_chain, priv_key) =
|
||||||
new_cert(identity_keypair, gossip_host).map_err(|_e| QuicServerError::ConfigureFailed)?;
|
new_self_signed_tls_certificate_chain(identity_keypair, gossip_host)
|
||||||
|
.map_err(|_e| QuicServerError::ConfigureFailed)?;
|
||||||
let cert_chain_pem_parts: Vec<Pem> = cert_chain
|
let cert_chain_pem_parts: Vec<Pem> = cert_chain
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cert| Pem {
|
.map(|cert| Pem {
|
||||||
|
@ -94,66 +92,6 @@ pub(crate) fn configure_server(
|
||||||
Ok((server_config, cert_chain_pem))
|
Ok((server_config, cert_chain_pem))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_cert(
|
|
||||||
identity_keypair: &Keypair,
|
|
||||||
san: IpAddr,
|
|
||||||
) -> Result<(Vec<rustls::Certificate>, rustls::PrivateKey), Box<dyn Error>> {
|
|
||||||
// Generate a self-signed cert from validator identity key
|
|
||||||
let cert_params = new_cert_params(identity_keypair, san);
|
|
||||||
let cert = rcgen::Certificate::from_params(cert_params)?;
|
|
||||||
let cert_der = cert.serialize_der().unwrap();
|
|
||||||
let priv_key = cert.serialize_private_key_der();
|
|
||||||
let priv_key = rustls::PrivateKey(priv_key);
|
|
||||||
let cert_chain = vec![rustls::Certificate(cert_der)];
|
|
||||||
Ok((cert_chain, priv_key))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_to_rcgen_keypair(identity_keypair: &Keypair) -> rcgen::KeyPair {
|
|
||||||
// from https://datatracker.ietf.org/doc/html/rfc8410#section-3
|
|
||||||
const ED25519_IDENTIFIER: [u32; 4] = [1, 3, 101, 112];
|
|
||||||
let mut private_key = Vec::<u8>::with_capacity(34);
|
|
||||||
private_key.extend_from_slice(&[0x04, 0x20]); // ASN.1 OCTET STRING
|
|
||||||
private_key.extend_from_slice(identity_keypair.secret().as_bytes());
|
|
||||||
let key_pkcs8 = pkcs8::PrivateKeyInfo {
|
|
||||||
algorithm: AlgorithmIdentifier {
|
|
||||||
oid: ObjectIdentifier::from_arcs(&ED25519_IDENTIFIER).unwrap(),
|
|
||||||
parameters: None,
|
|
||||||
},
|
|
||||||
private_key: &private_key,
|
|
||||||
public_key: None,
|
|
||||||
};
|
|
||||||
let key_pkcs8_der = key_pkcs8
|
|
||||||
.to_der()
|
|
||||||
.expect("Failed to convert keypair to DER")
|
|
||||||
.to_der();
|
|
||||||
|
|
||||||
// Parse private key into rcgen::KeyPair struct.
|
|
||||||
rcgen::KeyPair::from_der(&key_pkcs8_der).expect("Failed to parse keypair from DER")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_cert_params(identity_keypair: &Keypair, san: IpAddr) -> CertificateParams {
|
|
||||||
// TODO(terorie): Is it safe to sign the TLS cert with the identity private key?
|
|
||||||
|
|
||||||
// Unfortunately, rcgen does not accept a "raw" Ed25519 key.
|
|
||||||
// We have to convert it to DER and pass it to the library.
|
|
||||||
|
|
||||||
// Convert private key into PKCS#8 v1 object.
|
|
||||||
// RFC 8410, Section 7: Private Key Format
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc8410#section-
|
|
||||||
|
|
||||||
let keypair = convert_to_rcgen_keypair(identity_keypair);
|
|
||||||
|
|
||||||
let mut cert_params = CertificateParams::default();
|
|
||||||
cert_params.subject_alt_names = vec![SanType::IpAddress(san)];
|
|
||||||
cert_params.alg = &rcgen::PKCS_ED25519;
|
|
||||||
cert_params.key_pair = Some(keypair);
|
|
||||||
cert_params.distinguished_name = DistinguishedName::new();
|
|
||||||
cert_params
|
|
||||||
.distinguished_name
|
|
||||||
.push(DnType::CommonName, "Solana node");
|
|
||||||
cert_params
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rt() -> Runtime {
|
fn rt() -> Runtime {
|
||||||
Builder::new_multi_thread()
|
Builder::new_multi_thread()
|
||||||
.worker_threads(NUM_QUIC_STREAMER_WORKER_THREADS)
|
.worker_threads(NUM_QUIC_STREAMER_WORKER_THREADS)
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
use {
|
||||||
|
pkcs8::{der::Document, AlgorithmIdentifier, ObjectIdentifier},
|
||||||
|
rcgen::{CertificateParams, DistinguishedName, DnType, SanType},
|
||||||
|
solana_sdk::{pubkey::Pubkey, signature::Keypair},
|
||||||
|
std::{error::Error, net::IpAddr},
|
||||||
|
x509_parser::{prelude::*, public_key::PublicKey},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn new_self_signed_tls_certificate_chain(
|
||||||
|
keypair: &Keypair,
|
||||||
|
san: IpAddr,
|
||||||
|
) -> Result<(Vec<rustls::Certificate>, rustls::PrivateKey), Box<dyn Error>> {
|
||||||
|
// TODO(terorie): Is it safe to sign the TLS cert with the identity private key?
|
||||||
|
|
||||||
|
// Unfortunately, rcgen does not accept a "raw" Ed25519 key.
|
||||||
|
// We have to convert it to DER and pass it to the library.
|
||||||
|
|
||||||
|
// Convert private key into PKCS#8 v1 object.
|
||||||
|
// RFC 8410, Section 7: Private Key Format
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc8410#section-
|
||||||
|
|
||||||
|
// from https://datatracker.ietf.org/doc/html/rfc8410#section-3
|
||||||
|
const ED25519_IDENTIFIER: [u32; 4] = [1, 3, 101, 112];
|
||||||
|
let mut private_key = Vec::<u8>::with_capacity(34);
|
||||||
|
private_key.extend_from_slice(&[0x04, 0x20]); // ASN.1 OCTET STRING
|
||||||
|
private_key.extend_from_slice(keypair.secret().as_bytes());
|
||||||
|
let key_pkcs8 = pkcs8::PrivateKeyInfo {
|
||||||
|
algorithm: AlgorithmIdentifier {
|
||||||
|
oid: ObjectIdentifier::from_arcs(&ED25519_IDENTIFIER).expect("Failed to convert OID"),
|
||||||
|
parameters: None,
|
||||||
|
},
|
||||||
|
private_key: &private_key,
|
||||||
|
public_key: None,
|
||||||
|
};
|
||||||
|
let key_pkcs8_der = key_pkcs8
|
||||||
|
.to_der()
|
||||||
|
.expect("Failed to convert keypair to DER")
|
||||||
|
.to_der();
|
||||||
|
|
||||||
|
let rcgen_keypair = rcgen::KeyPair::from_der(&key_pkcs8_der)?;
|
||||||
|
|
||||||
|
let mut cert_params = CertificateParams::default();
|
||||||
|
cert_params.subject_alt_names = vec![SanType::IpAddress(san)];
|
||||||
|
cert_params.alg = &rcgen::PKCS_ED25519;
|
||||||
|
cert_params.key_pair = Some(rcgen_keypair);
|
||||||
|
cert_params.distinguished_name = DistinguishedName::new();
|
||||||
|
cert_params
|
||||||
|
.distinguished_name
|
||||||
|
.push(DnType::CommonName, "Solana node");
|
||||||
|
|
||||||
|
let cert = rcgen::Certificate::from_params(cert_params)?;
|
||||||
|
let cert_der = cert.serialize_der().unwrap();
|
||||||
|
let priv_key = cert.serialize_private_key_der();
|
||||||
|
let priv_key = rustls::PrivateKey(priv_key);
|
||||||
|
let cert_chain = vec![rustls::Certificate(cert_der)];
|
||||||
|
Ok((cert_chain, priv_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pubkey_from_tls_certificate(certificates: &[rustls::Certificate]) -> Option<Pubkey> {
|
||||||
|
certificates.first().and_then(|der_cert| {
|
||||||
|
X509Certificate::from_der(der_cert.as_ref())
|
||||||
|
.ok()
|
||||||
|
.and_then(|(_, cert)| {
|
||||||
|
cert.public_key().parsed().ok().and_then(|key| match key {
|
||||||
|
PublicKey::Unknown(inner_key) => Some(Pubkey::new(inner_key)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use {super::*, solana_sdk::signer::Signer, std::net::Ipv4Addr};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_tls_certificate() {
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
|
||||||
|
if let Ok((certs, _)) =
|
||||||
|
new_self_signed_tls_certificate_chain(&keypair, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))
|
||||||
|
{
|
||||||
|
if let Some(pubkey) = get_pubkey_from_tls_certificate(&certs) {
|
||||||
|
assert_eq!(pubkey, keypair.pubkey());
|
||||||
|
} else {
|
||||||
|
panic!("Failed to get certificate pubkey");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Failed to generate certificates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue