Skip to content

Commit 6c31673

Browse files
committed
Clients should accept messages secured by an expired SecurityToken
I encountered a bug where an incoming message would fail with an invalid signature error. After quite some debugging I noticed this only occurred when the message was received just a few milliseconds after a security channel renewal. After careful inspection it turned out that the message was still encrypted (which is according to spec) with the remote keys of the old security channel, but the client tried to validate the message with the renewed keys. According to the specs clients should accept messages secured by an expired SecurityToken for up to 25 % of the token lifetime (see https://reference.opcfoundation.org/Core/Part4/v105/docs/5.5.2 for more details), so I added a bit of logic to store the last 5 used remote keys and choose which one to use based on the token ID in the security header. After making these changes and testing again against the same OPC UA server the issue did not occur again (while it occurred multiple times per hour before this fix was implemented) so this seems like a nice little improvement making the package a little bit more complient.
1 parent fcc89d8 commit 6c31673

File tree

3 files changed

+78
-30
lines changed

3 files changed

+78
-30
lines changed

lib/src/core/comms/secure_channel.rs

+71-23
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
// Copyright (C) 2017-2024 Adam Lock
44

55
use std::{
6+
collections::HashMap,
67
io::{Cursor, Write},
78
ops::Range,
89
sync::Arc,
910
};
1011

11-
use chrono::{Duration, TimeDelta};
12+
use chrono::Duration;
1213

1314
use crate::crypto::{
1415
aeskey::AesKey,
@@ -35,6 +36,12 @@ pub enum Role {
3536
Server,
3637
}
3738

39+
#[derive(Debug)]
40+
struct RemoteKeys {
41+
keys: (Vec<u8>, AesKey, Vec<u8>),
42+
expires_at: DateTime,
43+
}
44+
3845
/// Holds all of the security information related to this session
3946
#[derive(Debug)]
4047
pub struct SecureChannel {
@@ -63,7 +70,14 @@ pub struct SecureChannel {
6370
/// Our nonce generated while handling open secure channel
6471
local_nonce: Vec<u8>,
6572
/// Client (i.e. other end's set of keys) Symmetric Signing Key, Encrypt Key, IV
66-
remote_keys: Option<(Vec<u8>, AesKey, Vec<u8>)>,
73+
///
74+
/// This is a map of channel token ids and their respective keys. We need to keep
75+
/// the old keys around as the client should accept messages secured by an expired
76+
/// SecurityToken for up to 25 % of the token lifetime.
77+
///
78+
/// See the "OpenSecureChannel" section in the spec for more info:
79+
/// https://reference.opcfoundation.org/Core/Part4/v105/docs/5.5.2
80+
remote_keys: HashMap<u32, RemoteKeys>,
6781
/// Server (i.e. our end's set of keys) Symmetric Signing Key, Decrypt Key, IV
6882
local_keys: Option<(Vec<u8>, AesKey, Vec<u8>)>,
6983
/// Decoding options
@@ -88,7 +102,7 @@ impl SecureChannel {
88102
private_key: None,
89103
remote_cert: None,
90104
local_keys: None,
91-
remote_keys: None,
105+
remote_keys: HashMap::new(),
92106
decoding_options: DecodingOptions::default(),
93107
}
94108
}
@@ -121,7 +135,7 @@ impl SecureChannel {
121135
private_key,
122136
remote_cert: None,
123137
local_keys: None,
124-
remote_keys: None,
138+
remote_keys: HashMap::new(),
125139
decoding_options,
126140
}
127141
}
@@ -226,10 +240,10 @@ impl SecureChannel {
226240
false
227241
} else {
228242
// Check if secure channel 75% close to expiration in which case send a renew
229-
let renew_lifetime = (self.token_lifetime() * 3) / 4;
230-
let renew_lifetime = TimeDelta::try_milliseconds(renew_lifetime as i64).unwrap();
243+
let renew_lifetime = (self.token_lifetime * 3) / 4;
244+
let renew_lifetime = Duration::milliseconds(renew_lifetime as i64);
231245
// Renew the token?
232-
DateTime::now() - self.token_created_at() > renew_lifetime
246+
DateTime::now() - self.token_created_at > renew_lifetime
233247
}
234248
}
235249

@@ -356,7 +370,7 @@ impl SecureChannel {
356370
/// are used to secure Messages sent by the Server.
357371
///
358372
pub fn derive_keys(&mut self) {
359-
self.remote_keys = Some(
373+
self.insert_remote_keys(
360374
self.security_policy
361375
.make_secure_channel_keys(&self.local_nonce, &self.remote_nonce),
362376
);
@@ -366,16 +380,16 @@ impl SecureChannel {
366380
);
367381
trace!("Remote nonce = {:?}", self.remote_nonce);
368382
trace!("Local nonce = {:?}", self.local_nonce);
369-
trace!("Derived remote keys = {:?}", self.remote_keys);
383+
trace!(
384+
"Derived remote keys = {:?}",
385+
self.get_remote_keys(self.token_id)
386+
);
370387
trace!("Derived local keys = {:?}", self.local_keys);
371388
}
372389

373390
/// Test if the token has expired yet
374391
pub fn token_has_expired(&self) -> bool {
375-
let token_created_at = self.token_created_at;
376-
let token_expires =
377-
token_created_at + TimeDelta::try_seconds(self.token_lifetime as i64).unwrap();
378-
DateTime::now().ge(&token_expires)
392+
DateTime::now() >= self.token_created_at + Duration::seconds(self.token_lifetime as i64)
379393
}
380394

381395
/// Calculates the signature size for a message depending on the supplied security header
@@ -739,11 +753,20 @@ impl SecureChannel {
739753
encrypted_range
740754
);
741755

756+
let SecurityHeader::Symmetric(security_header) = security_header else {
757+
error!(
758+
"Expected symmetric security header, got {:?}",
759+
security_header
760+
);
761+
return Err(StatusCode::BadUnexpectedError);
762+
};
763+
742764
let mut decrypted_data = vec![0u8; message_size];
743765
let decrypted_size = self.symmetric_decrypt_and_verify(
744766
src,
745767
signed_range,
746768
encrypted_range,
769+
security_header.token_id,
747770
&mut decrypted_data,
748771
)?;
749772

@@ -1048,8 +1071,32 @@ impl SecureChannel {
10481071
self.local_keys.as_ref().unwrap()
10491072
}
10501073

1051-
fn remote_keys(&self) -> &(Vec<u8>, AesKey, Vec<u8>) {
1052-
self.remote_keys.as_ref().unwrap()
1074+
fn insert_remote_keys(&mut self, keys: (Vec<u8>, AesKey, Vec<u8>)) {
1075+
let expires_at = (self.token_lifetime as f32 * 1.25).ceil();
1076+
let expires_at = Duration::milliseconds(expires_at as i64);
1077+
1078+
// Insert the remote keys.
1079+
self.remote_keys.insert(
1080+
self.token_id,
1081+
RemoteKeys {
1082+
keys,
1083+
expires_at: self.token_created_at + expires_at,
1084+
},
1085+
);
1086+
1087+
// Remove any expired keys.
1088+
self.remote_keys
1089+
.retain(|_, v| DateTime::now() < v.expires_at);
1090+
}
1091+
1092+
fn get_remote_keys(&self, token_id: u32) -> Result<&(Vec<u8>, AesKey, Vec<u8>), StatusCode> {
1093+
match self.remote_keys.get(&token_id) {
1094+
Some(remote_keys) => Ok(&remote_keys.keys),
1095+
None => {
1096+
error!("No remote keys found for token: {}", token_id);
1097+
Err(StatusCode::BadUnexpectedError)
1098+
}
1099+
}
10531100
}
10541101

10551102
fn encryption_keys(&self) -> (&AesKey, &[u8]) {
@@ -1061,13 +1108,13 @@ impl SecureChannel {
10611108
&(self.local_keys()).0
10621109
}
10631110

1064-
fn decryption_keys(&self) -> (&AesKey, &[u8]) {
1065-
let keys = self.remote_keys();
1066-
(&keys.1, &keys.2)
1111+
fn decryption_keys(&self, token_id: u32) -> Result<(&AesKey, &[u8]), StatusCode> {
1112+
let keys = self.get_remote_keys(token_id)?;
1113+
Ok((&keys.1, &keys.2))
10671114
}
10681115

1069-
fn verification_key(&self) -> &[u8] {
1070-
&(self.remote_keys()).0
1116+
fn verification_key(&self, token_id: u32) -> Result<&[u8], StatusCode> {
1117+
Ok(&(self.get_remote_keys(token_id))?.0)
10711118
}
10721119

10731120
/// Encode data using security. Destination buffer is expected to be same size as src and expected
@@ -1178,6 +1225,7 @@ impl SecureChannel {
11781225
src: &[u8],
11791226
signed_range: Range<usize>,
11801227
encrypted_range: Range<usize>,
1228+
token_id: u32,
11811229
dst: &mut [u8],
11821230
) -> Result<usize, StatusCode> {
11831231
match self.security_mode {
@@ -1198,7 +1246,7 @@ impl SecureChannel {
11981246
signed_range,
11991247
signed_range.end
12001248
);
1201-
let verification_key = self.verification_key();
1249+
let verification_key = self.verification_key(token_id)?;
12021250
self.security_policy.symmetric_verify_signature(
12031251
verification_key,
12041252
&dst[signed_range.clone()],
@@ -1222,7 +1270,7 @@ impl SecureChannel {
12221270

12231271
// Decrypt encrypted portion
12241272
let mut decrypted_tmp = vec![0u8; ciphertext_size + 16]; // tmp includes +16 for blocksize
1225-
let (key, iv) = self.decryption_keys();
1273+
let (key, iv) = self.decryption_keys(token_id)?;
12261274

12271275
trace!(
12281276
"Secure decrypt called with encrypted range {:?}",
@@ -1250,7 +1298,7 @@ impl SecureChannel {
12501298
signed_range,
12511299
signature_range
12521300
);
1253-
let verification_key = self.verification_key();
1301+
let verification_key = self.verification_key(token_id)?;
12541302
self.security_policy.symmetric_verify_signature(
12551303
verification_key,
12561304
&dst[signed_range],

lib/src/core/tests/chunk.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ fn set_chunk_sequence_number(
5353
sequence_number: u32,
5454
) -> u32 {
5555
// Read the sequence header
56-
let mut chunk_info = chunk.chunk_info(&secure_channel).unwrap();
56+
let mut chunk_info = chunk.chunk_info(secure_channel).unwrap();
5757
let old_sequence_number = chunk_info.sequence_header.sequence_number;
5858
chunk_info.sequence_header.sequence_number = sequence_number;
5959
// Write the sequence header out again with new value
@@ -69,7 +69,7 @@ fn set_chunk_request_id(
6969
request_id: u32,
7070
) -> u32 {
7171
// Read the sequence header
72-
let mut chunk_info = chunk.chunk_info(&secure_channel).unwrap();
72+
let mut chunk_info = chunk.chunk_info(secure_channel).unwrap();
7373
let old_request_id = chunk_info.sequence_header.request_id;
7474
chunk_info.sequence_header.request_id = request_id;
7575
// Write the sequence header out again with new value
@@ -341,7 +341,7 @@ fn chunk_open_secure_channel() {
341341
assert_eq!(request_header.timestamp.ticks(), 131284521470690000);
342342
assert_eq!(request_header.request_handle, 1);
343343
assert!(request_header.return_diagnostics.is_empty());
344-
assert_eq!(request_header.audit_entry_id.is_null(), true);
344+
assert!(request_header.audit_entry_id.is_null());
345345
assert_eq!(request_header.timeout_hint, 0);
346346
}
347347

@@ -408,7 +408,7 @@ fn open_secure_channel_response() {
408408
};
409409
assert_eq!(response.response_header.request_handle, 0);
410410
assert_eq!(response.response_header.service_result, StatusCode::Good);
411-
assert_eq!(response.response_header.string_table.is_none(), true);
411+
assert!(response.response_header.string_table.is_none());
412412
assert_eq!(response.server_nonce, ByteString::null());
413413
}
414414

@@ -464,7 +464,7 @@ fn security_policy_symmetric_encrypt_decrypt() {
464464

465465
let mut src2 = vec![0u8; 200];
466466
let decrypted_len = secure_channel2
467-
.symmetric_decrypt_and_verify(&dst, 0..80, 20..100, &mut src2)
467+
.symmetric_decrypt_and_verify(&dst, 0..80, 20..100, 0, &mut src2)
468468
.unwrap();
469469
assert_eq!(decrypted_len, 100);
470470

lib/src/core/tests/secure_channel.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fn test_symmetric_encrypt_decrypt(
2424

2525
let mut encrypted_data = vec![0u8; chunk.data.len() + 4096];
2626
let encrypted_size = secure_channel1
27-
.apply_security(&chunk, &mut encrypted_data[..])
27+
.apply_security(chunk, &mut encrypted_data[..])
2828
.unwrap();
2929
trace!("Result of applying security = {}", encrypted_size);
3030

@@ -81,7 +81,7 @@ fn test_asymmetric_encrypt_decrypt(
8181

8282
let mut encrypted_data = vec![0u8; chunk.data.len() + 4096];
8383
let encrypted_size = secure_channel
84-
.apply_security(&chunk, &mut encrypted_data[..])
84+
.apply_security(chunk, &mut encrypted_data[..])
8585
.unwrap();
8686
trace!("Result of applying security = {}", encrypted_size);
8787

0 commit comments

Comments
 (0)