Skip to content

Commit ebc26f8

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 ebc26f8

File tree

3 files changed

+58
-24
lines changed

3 files changed

+58
-24
lines changed

lib/src/core/comms/secure_channel.rs

+51-17
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub enum Role {
3535
Server,
3636
}
3737

38+
// Number of stored remote keys.
39+
const STORED_REMOTE_KEYS: usize = 5;
40+
3841
/// Holds all of the security information related to this session
3942
#[derive(Debug)]
4043
pub struct SecureChannel {
@@ -63,7 +66,15 @@ pub struct SecureChannel {
6366
/// Our nonce generated while handling open secure channel
6467
local_nonce: Vec<u8>,
6568
/// Client (i.e. other end's set of keys) Symmetric Signing Key, Encrypt Key, IV
66-
remote_keys: Option<(Vec<u8>, AesKey, Vec<u8>)>,
69+
///
70+
/// Store the last 5 used remote keys and choose which one to use based on the
71+
/// token ID in the security header of the received message. This allows us to
72+
/// properly decrypt messages that were encrypted with a previous key.
73+
///
74+
/// See the "OpenSecureChannel" section in the spec for more info:
75+
/// https://reference.opcfoundation.org/Core/Part4/v105/docs/5.5.2
76+
#[allow(clippy::type_complexity)]
77+
remote_keys: [Option<(Vec<u8>, AesKey, Vec<u8>)>; STORED_REMOTE_KEYS],
6778
/// Server (i.e. our end's set of keys) Symmetric Signing Key, Decrypt Key, IV
6879
local_keys: Option<(Vec<u8>, AesKey, Vec<u8>)>,
6980
/// Decoding options
@@ -88,7 +99,7 @@ impl SecureChannel {
8899
private_key: None,
89100
remote_cert: None,
90101
local_keys: None,
91-
remote_keys: None,
102+
remote_keys: [const { None }; STORED_REMOTE_KEYS],
92103
decoding_options: DecodingOptions::default(),
93104
}
94105
}
@@ -121,7 +132,7 @@ impl SecureChannel {
121132
private_key,
122133
remote_cert: None,
123134
local_keys: None,
124-
remote_keys: None,
135+
remote_keys: [const { None }; STORED_REMOTE_KEYS],
125136
decoding_options,
126137
}
127138
}
@@ -226,10 +237,10 @@ impl SecureChannel {
226237
false
227238
} else {
228239
// Check if secure channel 75% close to expiration in which case send a renew
229-
let renew_lifetime = (self.token_lifetime() * 3) / 4;
240+
let renew_lifetime = (self.token_lifetime * 3) / 4;
230241
let renew_lifetime = TimeDelta::try_milliseconds(renew_lifetime as i64).unwrap();
231242
// Renew the token?
232-
DateTime::now() - self.token_created_at() > renew_lifetime
243+
DateTime::now() - self.token_created_at > renew_lifetime
233244
}
234245
}
235246

@@ -356,7 +367,7 @@ impl SecureChannel {
356367
/// are used to secure Messages sent by the Server.
357368
///
358369
pub fn derive_keys(&mut self) {
359-
self.remote_keys = Some(
370+
self.insert_remote_keys(
360371
self.security_policy
361372
.make_secure_channel_keys(&self.local_nonce, &self.remote_nonce),
362373
);
@@ -366,7 +377,10 @@ impl SecureChannel {
366377
);
367378
trace!("Remote nonce = {:?}", self.remote_nonce);
368379
trace!("Local nonce = {:?}", self.local_nonce);
369-
trace!("Derived remote keys = {:?}", self.remote_keys);
380+
trace!(
381+
"Derived remote keys = {:?}",
382+
self.get_remote_keys(self.token_id)
383+
);
370384
trace!("Derived local keys = {:?}", self.local_keys);
371385
}
372386

@@ -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,18 @@ 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+
self.remote_keys[self.token_id as usize % STORED_REMOTE_KEYS] = Some(keys);
1076+
}
1077+
1078+
fn get_remote_keys(&self, token_id: u32) -> Result<&(Vec<u8>, AesKey, Vec<u8>), StatusCode> {
1079+
match self.remote_keys[token_id as usize % STORED_REMOTE_KEYS] {
1080+
Some(ref keys) => Ok(keys),
1081+
None => {
1082+
error!("No remote keys found for channel token id: {}", token_id);
1083+
Err(StatusCode::BadUnexpectedError)
1084+
}
1085+
}
10531086
}
10541087

10551088
fn encryption_keys(&self) -> (&AesKey, &[u8]) {
@@ -1061,13 +1094,13 @@ impl SecureChannel {
10611094
&(self.local_keys()).0
10621095
}
10631096

1064-
fn decryption_keys(&self) -> (&AesKey, &[u8]) {
1065-
let keys = self.remote_keys();
1066-
(&keys.1, &keys.2)
1097+
fn decryption_keys(&self, token_id: u32) -> Result<(&AesKey, &[u8]), StatusCode> {
1098+
let keys = self.get_remote_keys(token_id)?;
1099+
Ok((&keys.1, &keys.2))
10671100
}
10681101

1069-
fn verification_key(&self) -> &[u8] {
1070-
&(self.remote_keys()).0
1102+
fn verification_key(&self, token_id: u32) -> Result<&[u8], StatusCode> {
1103+
Ok(&(self.get_remote_keys(token_id))?.0)
10711104
}
10721105

10731106
/// Encode data using security. Destination buffer is expected to be same size as src and expected
@@ -1178,6 +1211,7 @@ impl SecureChannel {
11781211
src: &[u8],
11791212
signed_range: Range<usize>,
11801213
encrypted_range: Range<usize>,
1214+
token_id: u32,
11811215
dst: &mut [u8],
11821216
) -> Result<usize, StatusCode> {
11831217
match self.security_mode {
@@ -1198,7 +1232,7 @@ impl SecureChannel {
11981232
signed_range,
11991233
signed_range.end
12001234
);
1201-
let verification_key = self.verification_key();
1235+
let verification_key = self.verification_key(token_id)?;
12021236
self.security_policy.symmetric_verify_signature(
12031237
verification_key,
12041238
&dst[signed_range.clone()],
@@ -1222,7 +1256,7 @@ impl SecureChannel {
12221256

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

12271261
trace!(
12281262
"Secure decrypt called with encrypted range {:?}",
@@ -1250,7 +1284,7 @@ impl SecureChannel {
12501284
signed_range,
12511285
signature_range
12521286
);
1253-
let verification_key = self.verification_key();
1287+
let verification_key = self.verification_key(token_id)?;
12541288
self.security_policy.symmetric_verify_signature(
12551289
verification_key,
12561290
&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)