Skip to content

Commit de40b7a

Browse files
author
Michał Papierski
committed
Add support for CA signed client certs
Squashed commit of the following: commit 04a0557 Author: Michał Papierski <[email protected]> Date: Tue Aug 30 15:49:52 2022 +0200 Remove ca_cert from the identity struct. commit 8491fed Author: Michał Papierski <[email protected]> Date: Tue Aug 30 15:14:02 2022 +0200 Remove outdated TODO comment commit 6060eed Author: Michał Papierski <[email protected]> Date: Tue Aug 30 15:12:17 2022 +0200 Apply @marc-casperlabs comments commit 2f04a17 Author: Michał Papierski <[email protected]> Date: Thu Aug 25 17:20:51 2022 +0200 Remove unnecessary arg. commit a158903 Author: Michał Papierski <[email protected]> Date: Thu Aug 25 16:19:23 2022 +0200 Simplify CA validation. This does not enforce ECC algorithm used, only basic expiration dates etc. commit b98f083 Author: Michał Papierski <[email protected]> Date: Thu Aug 25 14:00:25 2022 +0200 Use #[source] instead of #[from]
1 parent 3333778 commit de40b7a

11 files changed

Lines changed: 439 additions & 148 deletions

File tree

node/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ All notable changes to this project will be documented in this file. The format
1313

1414
## 1.4.8
1515

16+
### Added
17+
* Add an `identity` option to load existing network identity certificates signed by a CA.
18+
19+
20+
1621
### Changed
1722
* Update `casper-execution-engine`.
1823

node/src/components/small_network.rs

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ use tracing::{debug, error, info, trace, warn, Instrument, Span};
7373

7474
use self::{
7575
chain_info::ChainInfo,
76+
config::IdentityConfig,
7677
counting_format::{ConnectionId, CountingFormat, Role},
7778
error::{ConnectionError, Result},
7879
event::{IncomingConnection, OutgoingConnection},
@@ -100,7 +101,10 @@ use crate::{
100101
EffectBuilder, EffectExt, Effects,
101102
},
102103
reactor::{EventQueueHandle, Finalize, ReactorEvent},
103-
tls::{self, TlsCert, ValidationError},
104+
tls::{
105+
self, validate_cert_with_authority, LoadCertError, LoadSecretKeyError, TlsCert,
106+
ValidationError,
107+
},
104108
types::NodeId,
105109
utils::{self, display_error, WithDir},
106110
NodeRng,
@@ -275,10 +279,31 @@ where
275279
.map_err(Error::LoadConsensusKeys)?
276280
.map(|(secret_key, public_key)| ConsensusKeyPair::new(secret_key, public_key));
277281

282+
// Load a ca certificate (if present)
283+
let ca_certificate = match &cfg.identity {
284+
Some(identity) => {
285+
let ca_cert = tls::load_cert(&identity.ca_certificate)?;
286+
287+
// A quick sanity check for the loaded cert against supplied CA.
288+
validate_cert_with_authority(
289+
small_network_identity.tls_certificate.as_x509().clone(),
290+
&ca_cert,
291+
)
292+
.map_err(|error| {
293+
warn!(%error, "the given node certificate is not signed by the network CA");
294+
Error::CertValidationError(error)
295+
})?;
296+
297+
Some(ca_cert)
298+
}
299+
None => None,
300+
};
301+
278302
let context = Arc::new(NetworkContext {
279303
event_queue,
280304
our_id: NodeId::from(&small_network_identity),
281305
our_cert: small_network_identity.tls_certificate,
306+
network_ca: ca_certificate.map(Arc::new),
282307
secret_key: small_network_identity.secret_key,
283308
net_metrics: Arc::downgrade(&net_metrics),
284309
chain_info: chain_info_source.into(),
@@ -967,6 +992,10 @@ pub(crate) enum SmallNetworkIdentityError {
967992
CouldNotGenerateTlsCertificate(OpenSslErrorStack),
968993
#[error(transparent)]
969994
ValidationError(#[from] ValidationError),
995+
#[error(transparent)]
996+
LoadCertError(#[from] LoadCertError),
997+
#[error(transparent)]
998+
LoadSecretKeyError(#[from] LoadSecretKeyError),
970999
}
9711000

9721001
/// An ephemeral [PKey<Private>] and [TlsCert] that identifies this node
@@ -977,14 +1006,37 @@ pub(crate) struct SmallNetworkIdentity {
9771006
}
9781007

9791008
impl SmallNetworkIdentity {
980-
pub(crate) fn new() -> result::Result<Self, SmallNetworkIdentityError> {
981-
let (not_yet_validated_x509_cert, secret_key) = tls::generate_node_cert()
982-
.map_err(SmallNetworkIdentityError::CouldNotGenerateTlsCertificate)?;
983-
let tls_certificate = tls::validate_cert(not_yet_validated_x509_cert)?;
984-
Ok(SmallNetworkIdentity {
1009+
fn new(secret_key: PKey<Private>, tls_certificate: TlsCert) -> Self {
1010+
Self {
9851011
secret_key: Arc::new(secret_key),
9861012
tls_certificate: Arc::new(tls_certificate),
987-
})
1013+
}
1014+
}
1015+
1016+
pub(crate) fn from_config(
1017+
config: WithDir<Config>,
1018+
) -> result::Result<Self, SmallNetworkIdentityError> {
1019+
match &config.value().identity {
1020+
Some(identity) => Self::from_identity_config(identity),
1021+
None => Self::with_generated_certs(),
1022+
}
1023+
}
1024+
1025+
fn from_identity_config(
1026+
identity: &IdentityConfig,
1027+
) -> result::Result<Self, SmallNetworkIdentityError> {
1028+
let not_yet_validated_x509_cert = tls::load_cert(&identity.tls_certificate)?;
1029+
let secret_key = tls::load_secret_key(&identity.secret_key)?;
1030+
let x509_cert = tls::tls_cert_from_x509(not_yet_validated_x509_cert)?;
1031+
1032+
Ok(SmallNetworkIdentity::new(secret_key, x509_cert))
1033+
}
1034+
1035+
pub(crate) fn with_generated_certs() -> result::Result<Self, SmallNetworkIdentityError> {
1036+
let (not_yet_validated_x509_cert, secret_key) = tls::generate_node_cert()
1037+
.map_err(SmallNetworkIdentityError::CouldNotGenerateTlsCertificate)?;
1038+
let tls_certificate = tls::validate_self_signed_cert(not_yet_validated_x509_cert)?;
1039+
Ok(SmallNetworkIdentity::new(secret_key, tls_certificate))
9881040
}
9891041
}
9901042

node/src/components/small_network/config.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[cfg(test)]
22
use std::net::{Ipv4Addr, SocketAddr};
3-
use std::str::FromStr;
3+
use std::{path::PathBuf, str::FromStr};
44

55
use datasize::DataSize;
66
use serde::{Deserialize, Serialize};
@@ -35,10 +35,24 @@ impl Default for Config {
3535
max_outgoing_byte_rate_non_validators: 0,
3636
max_incoming_message_rate_non_validators: 0,
3737
estimator_weights: Default::default(),
38+
identity: None,
3839
}
3940
}
4041
}
4142

43+
/// Small network identity configuration.
44+
#[derive(DataSize, Debug, Clone, Deserialize, Serialize)]
45+
// Disallow unknown fields to ensure config files and command-line overrides contain valid keys.
46+
#[serde(deny_unknown_fields)]
47+
pub struct IdentityConfig {
48+
/// Path to a signed certificate
49+
pub tls_certificate: PathBuf,
50+
/// Path to a secret key.
51+
pub secret_key: PathBuf,
52+
/// Path to a certificate authority certificate
53+
pub ca_certificate: PathBuf,
54+
}
55+
4256
/// Small network configuration.
4357
#[derive(DataSize, Debug, Clone, Deserialize, Serialize)]
4458
// Disallow unknown fields to ensure config files and command-line overrides contain valid keys.
@@ -64,6 +78,11 @@ pub struct Config {
6478
pub max_incoming_message_rate_non_validators: u32,
6579
/// Weight distribution for the payload impact estimator.
6680
pub estimator_weights: PayloadWeights,
81+
/// Small network identity configuration option.
82+
///
83+
/// An identity will be automatically generated when starting up a node if this option is
84+
/// unspecified.
85+
pub identity: Option<IdentityConfig>,
6786
}
6887

6988
#[cfg(test)]

node/src/components/small_network/error.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use thiserror::Error;
88

99
use crate::{
1010
crypto,
11-
tls::ValidationError,
11+
tls::{LoadCertError, ValidationError},
1212
utils::{LoadError, Loadable, ResolveAddressError},
1313
};
1414

@@ -63,14 +63,27 @@ pub enum Error {
6363
#[source]
6464
ResolveAddressError,
6565
),
66-
6766
/// Instantiating metrics failed.
6867
#[error(transparent)]
6968
Metrics(
7069
#[serde(skip_serializing)]
7170
#[from]
7271
prometheus::Error,
7372
),
73+
/// Failed to load a certificate.
74+
#[error("failed to load a certificate: {0}")]
75+
LoadCertificate(
76+
#[serde(skip_serializing)]
77+
#[from]
78+
LoadCertError,
79+
),
80+
/// Failed to validate a network CA certificate.
81+
#[error("failed to validate a cert against network CA: {0:?}")]
82+
CertValidationError(
83+
#[serde(skip_serializing)]
84+
#[source]
85+
ValidationError,
86+
),
7487
}
7588

7689
// Manual implementation for `DataSize` - the type contains too many FFI variants that are hard to

node/src/components/small_network/tasks.rs

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use futures::{
1919
use openssl::{
2020
pkey::{PKey, Private},
2121
ssl::Ssl,
22+
x509::X509,
2223
};
2324
use prometheus::IntGauge;
2425
use serde::{de::DeserializeOwned, Deserialize, Serialize};
@@ -45,7 +46,7 @@ use super::{
4546
};
4647
use crate::{
4748
reactor::{EventQueueHandle, QueueKind},
48-
tls::{self, TlsCert},
49+
tls::{self, TlsCert, ValidationError},
4950
types::NodeId,
5051
utils::display_error,
5152
};
@@ -87,11 +88,11 @@ where
8788
.peer_certificate()
8889
.ok_or(ConnectionError::NoPeerCertificate)?;
8990

90-
let peer_id = NodeId::from(
91-
tls::validate_cert(peer_cert)
92-
.map_err(ConnectionError::PeerCertificateInvalid)?
93-
.public_key_fingerprint(),
94-
);
91+
let validated_peer_cert = context
92+
.validate_peer_cert(peer_cert)
93+
.map_err(ConnectionError::PeerCertificateInvalid)?;
94+
95+
let peer_id = NodeId::from(validated_peer_cert.public_key_fingerprint());
9596

9697
Ok((peer_id, transport))
9798
}
@@ -171,6 +172,8 @@ where
171172
pub(super) our_id: NodeId,
172173
/// TLS certificate associated with this node's identity.
173174
pub(super) our_cert: Arc<TlsCert>,
175+
/// TLS certificate authority associated with this node's identity.
176+
pub(super) network_ca: Option<Arc<X509>>,
174177
/// Secret key associated with `our_cert`.
175178
pub(super) secret_key: Arc<PKey<Private>>,
176179
/// Weak reference to the networking metrics shared by all sender/receiver tasks.
@@ -185,6 +188,15 @@ where
185188
pub(super) payload_weights: PayloadWeights,
186189
}
187190

191+
impl<REv> NetworkContext<REv> {
192+
pub(crate) fn validate_peer_cert(&self, peer_cert: X509) -> Result<TlsCert, ValidationError> {
193+
match &self.network_ca {
194+
Some(ca_cert) => tls::validate_cert_with_authority(peer_cert, ca_cert),
195+
None => tls::validate_self_signed_cert(peer_cert),
196+
}
197+
}
198+
}
199+
188200
/// Handles an incoming connection.
189201
///
190202
/// Sets up a TLS stream and performs the protocol handshake.
@@ -199,13 +211,12 @@ where
199211
for<'de> P: Serialize + Deserialize<'de>,
200212
for<'de> Message<P>: Serialize + Deserialize<'de>,
201213
{
202-
let (peer_id, transport) =
203-
match server_setup_tls(stream, &context.our_cert, &context.secret_key).await {
204-
Ok(value) => value,
205-
Err(error) => {
206-
return IncomingConnection::FailedEarly { peer_addr, error };
207-
}
208-
};
214+
let (peer_id, transport) = match server_setup_tls(&context, stream).await {
215+
Ok(value) => value,
216+
Err(error) => {
217+
return IncomingConnection::FailedEarly { peer_addr, error };
218+
}
219+
};
209220

210221
// Register the `peer_id` on the [`Span`] for logging the ID from here on out.
211222
Span::current().record("peer_id", &field::display(peer_id));
@@ -256,15 +267,17 @@ where
256267
/// Server-side TLS setup.
257268
///
258269
/// This function groups the TLS setup into a convenient function, enabling the `?` operator.
259-
pub(super) async fn server_setup_tls(
270+
pub(super) async fn server_setup_tls<REv>(
271+
context: &NetworkContext<REv>,
260272
stream: TcpStream,
261-
cert: &TlsCert,
262-
secret_key: &PKey<Private>,
263273
) -> Result<(NodeId, Transport), ConnectionError> {
264-
let mut tls_stream = tls::create_tls_acceptor(cert.as_x509().as_ref(), secret_key.as_ref())
265-
.and_then(|ssl_acceptor| Ssl::new(ssl_acceptor.context()))
266-
.and_then(|ssl| SslStream::new(ssl, stream))
267-
.map_err(ConnectionError::TlsInitialization)?;
274+
let mut tls_stream = tls::create_tls_acceptor(
275+
context.our_cert.as_x509().as_ref(),
276+
context.secret_key.as_ref(),
277+
)
278+
.and_then(|ssl_acceptor| Ssl::new(ssl_acceptor.context()))
279+
.and_then(|ssl| SslStream::new(ssl, stream))
280+
.map_err(ConnectionError::TlsInitialization)?;
268281

269282
SslStream::accept(Pin::new(&mut tls_stream))
270283
.await
@@ -276,12 +289,12 @@ pub(super) async fn server_setup_tls(
276289
.peer_certificate()
277290
.ok_or(ConnectionError::NoPeerCertificate)?;
278291

292+
let validated_peer_cert = context
293+
.validate_peer_cert(peer_cert)
294+
.map_err(ConnectionError::PeerCertificateInvalid)?;
295+
279296
Ok((
280-
NodeId::from(
281-
tls::validate_cert(peer_cert)
282-
.map_err(ConnectionError::PeerCertificateInvalid)?
283-
.public_key_fingerprint(),
284-
),
297+
NodeId::from(validated_peer_cert.public_key_fingerprint()),
285298
tls_stream,
286299
))
287300
}

node/src/components/small_network/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ impl Reactor for TestReactor {
143143
event_queue: EventQueueHandle<Self::Event>,
144144
_rng: &mut NodeRng,
145145
) -> anyhow::Result<(Self, Effects<Self::Event>)> {
146-
let small_network_identity = SmallNetworkIdentity::new()?;
146+
let small_network_identity = SmallNetworkIdentity::with_generated_certs()?;
147147
let (net, effects) = SmallNetwork::new(
148148
event_queue,
149149
cfg,

node/src/reactor/initializer.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,8 @@ impl Reactor {
311311

312312
let effects = reactor::wrap_effects(Event::Chainspec, chainspec_effects);
313313

314-
let small_network_identity = SmallNetworkIdentity::new()?;
314+
let network_config = config.map_ref(|config| config.network.clone());
315+
let small_network_identity = SmallNetworkIdentity::from_config(network_config)?;
315316

316317
let reactor = Reactor {
317318
config,

0 commit comments

Comments
 (0)