Skip to content

Commit 9697efd

Browse files
author
Lucas McDonald
committed
feat(native-rust): sync message-header structure from unreviewed
1 parent e694b15 commit 9697efd

7 files changed

Lines changed: 227 additions & 298 deletions

File tree

esdk/src/message/header_auth.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ pub(crate) fn read_header_auth_tag_v2(
170170
let tag_len = usize::from(get_tag_length(suite));
171171

172172
let header_auth_tag = read_vec(r, tag_len, raw)?;
173+
//= spec/client-apis/decrypt.md#verify-the-header
174+
//= type=implication
175+
//= reason=Cannot be tested externally because verifying the IV value requires the derived data key (not exposed by the public API). The literal `vec![0u8; ...]` here is the proof.
176+
//# For message format version [2.0](../data-format/message-header.md#supported-versions)
177+
//# the IV MUST be 0.
173178
let header_iv = vec![0u8; usize::from(get_iv_length(suite))];
174179
Ok(HeaderAuth::AESMac {
175180
header_iv,

esdk/src/message/header_types.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3+
34
//! Type definitions for message header fields.
45
56
use super::serializable_types::ESDKCanonicalEncryptionContext;
@@ -66,17 +67,19 @@ pub(crate) enum MessageType {
6667
}
6768

6869
pub(crate) fn write_msg_type(w: &mut dyn SafeWrite, data: MessageType) -> Result<(), Error> {
70+
//= spec/data-format/message-header.md#type
71+
//# The length of the serialized type field MUST be 1 byte.
6972
write_u8(w, data as u8)
7073
}
7174

7275
pub(crate) fn read_msg_type(
7376
r: &mut dyn SafeRead,
7477
raw: &mut dyn SafeWrite,
7578
) -> Result<MessageType, Error> {
76-
let msg_type = read_u8(r, raw)?;
7779
//= spec/data-format/message-header.md#type
7880
//# The length of the serialized type field MUST be 1 byte.
79-
//
81+
let msg_type = read_u8(r, raw)?;
82+
8083
//= spec/client-apis/decrypt.md#v1-header-deserialization
8184
//# The value MUST be a [supported type](../data-format/message-header.md#supported-types).
8285
match msg_type {
@@ -95,6 +98,8 @@ pub(crate) enum ContentType {
9598
}
9699

97100
pub(crate) fn write_content_type(w: &mut dyn SafeWrite, data: ContentType) -> Result<(), Error> {
101+
//= spec/data-format/message-header.md#content-type
102+
//# The length of the serialized content type field MUST be 1 byte.
98103
write_u8(w, data as u8)
99104
}
100105

esdk/src/message/v1_header_body.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,6 @@ pub(crate) fn read_v1_reserved_bytes(
159159
) -> Result<(), Error> {
160160
let mut result = [0; RESERVED_BYTES.len()];
161161
read_bytes(r, &mut result, raw)?;
162-
//= spec/data-format/message-header.md#reserved
163-
//# A reserved sequence of 4 bytes
164-
//# that MUST have the value (hex) of `00 00 00 00`.
165162
if result == RESERVED_BYTES {
166163
Ok(())
167164
} else {

esdk/tests/test_header_auth.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,6 @@ async fn test_v2_header_auth_structure() {
128128
//= type=test
129129
//= reason=Validated on decrypt. If this weren't the auth tag, decrypt would fail.
130130
//# The value MUST be the authentication tag calculated above.
131-
//
132-
//= spec/client-apis/decrypt.md#verify-the-header
133-
//= type=test
134-
//= reason=Successful round-trip decrypt proves the IV used for V2 header verification was 0.
135-
//# For message format version [2.0](../data-format/message-header.md#supported-versions)
136-
//# the IV MUST be 0.
137131
let result = decrypt_ciphertext(&ct).await;
138132
assert_eq!(result.plaintext, b"v2 header auth structure", "V2 round-trip must succeed");
139133
}

esdk/tests/test_header_types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ async fn test_content_type_invalid_value_rejected() {
108108
let dec_input = DecryptInput::with_legacy_keyring(&ct, EncryptionContext::new(), keyring.clone());
109109
//= spec/data-format/message-header.md#content-type
110110
//= type=test
111+
//= reason=Tampering a single byte at the content type offset changes the parsed value, proving the field is 1 byte.
111112
//# The length of the serialized content type field MUST be 1 byte.
112113
//
113114
//= spec/data-format/message-header.md#supported-content-types
@@ -154,6 +155,7 @@ async fn test_unsupported_type_rejected_v1() {
154155
let dec_input = DecryptInput::with_legacy_keyring(&ct, EncryptionContext::new(), keyring);
155156
//= spec/data-format/message-header.md#supported-types
156157
//= type=test
158+
//= reason=Tampering the type byte at offset 1 to an unsupported value proves the field is validated and only the specified types are accepted.
157159
//# The supported types MUST be:
158160
let err = decrypt(&dec_input).await.unwrap_err();
159161
assert!(matches!(err.kind, ErrorKind::SerializationError), "expected SerializationError, got {:?}", err.kind);

esdk/tests/test_v1_header_body.rs

Lines changed: 43 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -36,26 +36,16 @@ impl<'a> V1FixedFields<'a> {
3636
}
3737

3838
#[tokio::test(flavor = "multi_thread")]
39-
async fn test_v1_header_serialized() {
40-
let pt = b"test v1 header";
39+
async fn test_v1_header_version() {
40+
let pt = b"test v1 header version";
4141
let ct = encrypt_v1_with_ec(pt, EncryptionContext::new()).await;
4242
let hdr = V1FixedFields::parse(&ct);
4343
//= spec/client-apis/encrypt.md#v1-header
4444
//= type=test
4545
//# If the message format version associated with the [algorithm suite](../framework/algorithm-suites.md#supported-algorithm-suites) is 1.0,
4646
//# the remaining header fields MUST be serialized according to the
4747
//# [Header Body Version 1.0](../data-format/message-header.md#header-body-version-10) specification:
48-
// V1 header starts with version byte 0x01
49-
assert_eq!(hdr.version, 0x01, "first byte must be V1 version 0x01");
50-
// Round-trip proves the V1 message is valid end-to-end.
51-
let result = round_trip_v1(pt, EncryptionContext::new()).await;
52-
assert_eq!(result, pt);
53-
}
54-
55-
#[tokio::test(flavor = "multi_thread")]
56-
async fn test_v1_header_version() {
57-
let ct = encrypt_v1_with_ec(b"version test", EncryptionContext::new()).await;
58-
let hdr = V1FixedFields::parse(&ct);
48+
//
5949
//= spec/client-apis/encrypt.md#v1-header
6050
//= type=test
6151
//# - MUST serialize the [Version](../data-format/message-header.md#version).
@@ -65,9 +55,9 @@ async fn test_v1_header_version() {
6555
//= type=test
6656
//# The value of the `Version` field MUST be `01` in the Version 1.0 header body.
6757
assert_eq!(hdr.version, 0x01, "Version field must be 0x01 for V1");
68-
// Round-trip proves the version field is correctly processed.
69-
let result = round_trip_v1(b"version test", EncryptionContext::new()).await;
70-
assert_eq!(result, b"version test");
58+
// Round-trip proves the V1 message is valid end-to-end.
59+
let result = round_trip_v1(pt, EncryptionContext::new()).await;
60+
assert_eq!(result, pt);
7161
}
7262

7363
#[tokio::test(flavor = "multi_thread")]
@@ -132,11 +122,6 @@ async fn test_v1_header_message_id() {
132122
//# The length of the serialized message ID MUST be 16 bytes for [version 1.0](#header-body-version-10) headers.
133123
assert_eq!(hdr1.message_id.len(), 16, "V1 message ID must be 16 bytes");
134124

135-
//= spec/data-format/message-header.md#message-id
136-
//= type=test
137-
//# The message ID MUST be interpreted as bytes.
138-
assert_eq!(hdr1.message_id, &ct1[4..20], "V1 message ID must be preserved as raw bytes");
139-
140125
//= spec/data-format/message-header.md#message-id
141126
//= type=test
142127
//# While implementations cannot guarantee complete uniqueness,
@@ -222,15 +207,15 @@ async fn test_v1_header_aad() {
222207

223208
#[tokio::test(flavor = "multi_thread")]
224209
async fn test_v1_header_encrypted_data_keys() {
210+
let pt = b"edk test";
211+
let ct = encrypt_v1_with_ec(pt, EncryptionContext::new()).await;
212+
// Parse past AAD to find EDK count
213+
let edk_section = parse_edk_section(&ct, Version::V1);
225214
//= spec/client-apis/encrypt.md#v1-header
226215
//= type=test
227216
//# - MUST serialize the [Encrypted Data Keys](../data-format/message-header.md#encrypted-data-keys).
228217
//# The value MUST be the serialization of the
229218
//# [encrypted data keys](../framework/structures.md#encrypted-data-keys) in the [encryption materials](../framework/structures.md#encryption-materials).
230-
let pt = b"edk test";
231-
let ct = encrypt_v1_with_ec(pt, EncryptionContext::new()).await;
232-
// Parse past AAD to find EDK count
233-
let edk_section = parse_edk_section(&ct, Version::V1);
234219
assert!(edk_section.edk_count >= 1, "must have at least one EDK");
235220
assert!(!edk_section.edks[0].edk.is_empty(), "EDK ciphertext must be non-empty");
236221
// Round-trip proves EDKs are correctly serialized (decrypt uses them to recover the data key)
@@ -280,16 +265,33 @@ async fn test_v1_header_reserved() {
280265
#[tokio::test(flavor = "multi_thread")]
281266
async fn test_v1_header_iv_length() {
282267
let ct = encrypt_v1_with_ec(b"iv length test", EncryptionContext::new()).await;
283-
let (_, _, iv_length_offset, _) = parse_v1_trailing_offsets(&ct);
268+
let (_, _, iv_length_offset, frame_length_offset) = parse_v1_trailing_offsets(&ct);
284269
//= spec/client-apis/encrypt.md#v1-header
285270
//= type=test
286271
//# - MUST serialize the [IV Length](../data-format/message-header.md#iv-length).
287272
//# The value MUST match the [IV length](../framework/algorithm-suites.md#iv-length)
288273
//# specified by the [algorithm suite](../framework/algorithm-suites.md).
274+
//
275+
//= spec/data-format/message-header.md#iv-length
276+
//= type=test
277+
//# The IV length MUST be interpreted as a UInt8.
278+
//
279+
//= spec/data-format/message-header.md#iv-length
280+
//= type=test
281+
//# This value MUST be equal to the [IV length](../framework/algorithm-suites.md#iv-length) value of the
282+
//# [algorithm suite](../framework/algorithm-suites.md) specified by the [Algorithm Suite ID](#algorithm-suite-id) field.
289283
// AlgAes256GcmIv12Tag16HkdfSha256 has IV length 12
290284
assert_eq!(
291285
ct[iv_length_offset], 12,
292-
"IV Length must be 12, matching the algorithm suite"
286+
"IV Length must be 12, matching the algorithm suite (serialized as UInt8)"
287+
);
288+
//= spec/data-format/message-header.md#iv-length
289+
//= type=test
290+
//# The length of the serialized IV length field MUST be 1 byte.
291+
assert_eq!(
292+
frame_length_offset - iv_length_offset,
293+
1,
294+
"IV length field must be exactly 1 byte"
293295
);
294296
// Round-trip proves the IV length is correctly processed.
295297
let result = round_trip_v1(b"iv length test", EncryptionContext::new()).await;
@@ -299,18 +301,27 @@ async fn test_v1_header_iv_length() {
299301
#[tokio::test(flavor = "multi_thread")]
300302
async fn test_v1_header_frame_length() {
301303
let ct = encrypt_v1_with_ec(b"frame length test", EncryptionContext::new()).await;
302-
let (_, _, _, frame_length_offset) = parse_v1_trailing_offsets(&ct);
303-
//= spec/client-apis/encrypt.md#v1-header
304+
let (_, _, iv_length_offset, frame_length_offset) = parse_v1_trailing_offsets(&ct);
305+
//= spec/data-format/message-header.md#frame-length
304306
//= type=test
305-
//# - MUST serialize the [Frame Length](../data-format/message-header.md#frame-length).
306-
//# The value MUST be the value of the frame size determined above.
307+
//# The frame length MUST be interpreted as a UInt32.
308+
// Frame length field is 4 bytes (UInt32), immediately after IV length
309+
assert_eq!(
310+
frame_length_offset - iv_length_offset,
311+
1,
312+
"frame length immediately follows IV length (confirming IV length is 1 byte)"
313+
);
307314
let frame_length = u32::from_be_bytes([
308315
ct[frame_length_offset],
309316
ct[frame_length_offset + 1],
310317
ct[frame_length_offset + 2],
311318
ct[frame_length_offset + 3],
312319
]);
313-
assert_eq!(frame_length, 4096, "Frame Length must be the default 4096");
320+
//= spec/client-apis/encrypt.md#v1-header
321+
//= type=test
322+
//# - MUST serialize the [Frame Length](../data-format/message-header.md#frame-length).
323+
//# The value MUST be the value of the frame size determined above.
324+
assert_eq!(frame_length, 4096, "Frame Length must be the default 4096 (serialized as UInt32)");
314325
// Round-trip proves the frame length is correctly processed.
315326
let result = round_trip_v1(b"frame length test", EncryptionContext::new()).await;
316327
assert_eq!(result, b"frame length test");
@@ -420,89 +431,6 @@ async fn test_v1_reserved_field_is_4_bytes() {
420431
);
421432
}
422433

423-
#[tokio::test(flavor = "multi_thread")]
424-
async fn test_v1_iv_length_field_is_1_byte() {
425-
let ct = encrypt_v1_with_ec(b"iv length 1 byte test", EncryptionContext::new()).await;
426-
let (_, _, iv_length_offset, frame_length_offset) = parse_v1_trailing_offsets(&ct);
427-
//= spec/data-format/message-header.md#iv-length
428-
//= type=test
429-
//# The length of the serialized IV length field MUST be 1 byte.
430-
assert_eq!(
431-
frame_length_offset - iv_length_offset,
432-
1,
433-
"IV length field must be exactly 1 byte"
434-
);
435-
}
436-
437-
#[tokio::test(flavor = "multi_thread")]
438-
async fn test_v1_iv_length_serialized_as_uint8() {
439-
let ct = encrypt_v1_with_ec(b"iv length uint8 test", EncryptionContext::new()).await;
440-
let (_, _, iv_length_offset, _) = parse_v1_trailing_offsets(&ct);
441-
//= spec/data-format/message-header.md#iv-length
442-
//= type=test
443-
//# The IV length MUST be interpreted as a UInt8.
444-
// AlgAes256GcmIv12Tag16HkdfSha256 has IV length 12
445-
let iv_length = ct[iv_length_offset];
446-
assert_eq!(
447-
iv_length, 12,
448-
"IV length must be 12 for this algorithm suite, serialized as single UInt8 byte"
449-
);
450-
}
451-
452-
#[tokio::test(flavor = "multi_thread")]
453-
async fn test_v1_iv_length_equals_suite_iv_length() {
454-
let ct = encrypt_v1_with_ec(b"iv length suite test", EncryptionContext::new()).await;
455-
let (_, _, iv_length_offset, _) = parse_v1_trailing_offsets(&ct);
456-
//= spec/data-format/message-header.md#iv-length
457-
//= type=test
458-
//# This value MUST be equal to the [IV length](../framework/algorithm-suites.md#iv-length) value of the
459-
//# [algorithm suite](../framework/algorithm-suites.md) specified by the [Algorithm Suite ID](#algorithm-suite-id) field.
460-
// AlgAes256GcmIv12Tag16HkdfSha256 has IV length 12
461-
assert_eq!(
462-
ct[iv_length_offset], 12,
463-
"IV length must match algorithm suite IV length (12)"
464-
);
465-
// Round-trip proves the IV length is validated during decrypt.
466-
let result = round_trip_v1(b"iv length suite test", EncryptionContext::new()).await;
467-
assert_eq!(result, b"iv length suite test");
468-
}
469-
470-
#[tokio::test(flavor = "multi_thread")]
471-
async fn test_v1_frame_length_field_is_4_bytes() {
472-
let ct = encrypt_v1_with_ec(b"frame length 4 bytes v1 test", EncryptionContext::new()).await;
473-
let (_, _, _, frame_length_offset) = parse_v1_trailing_offsets(&ct);
474-
let frame_length = u32::from_be_bytes([
475-
ct[frame_length_offset],
476-
ct[frame_length_offset + 1],
477-
ct[frame_length_offset + 2],
478-
ct[frame_length_offset + 3],
479-
]);
480-
assert_eq!(frame_length, 4096, "default frame length should be 4096");
481-
}
482-
483-
#[tokio::test(flavor = "multi_thread")]
484-
async fn test_v1_frame_length_serialized_as_uint32() {
485-
let ct = encrypt_v1_with_ec(b"frame length uint32 v1 test", EncryptionContext::new()).await;
486-
let (_, _, _, frame_length_offset) = parse_v1_trailing_offsets(&ct);
487-
//= spec/data-format/message-header.md#frame-length
488-
//= type=test
489-
//# The frame length MUST be interpreted as a UInt32.
490-
// Parse as big-endian UInt32
491-
let frame_length = u32::from_be_bytes([
492-
ct[frame_length_offset],
493-
ct[frame_length_offset + 1],
494-
ct[frame_length_offset + 2],
495-
ct[frame_length_offset + 3],
496-
]);
497-
assert_eq!(
498-
frame_length, 4096,
499-
"default frame length should be 4096 when serialized as UInt32"
500-
);
501-
// Confirm round-trip succeeds
502-
let result = round_trip_v1(b"frame length uint32 v1 test", EncryptionContext::new()).await;
503-
assert_eq!(result, b"frame length uint32 v1 test");
504-
}
505-
506434
#[tokio::test(flavor = "multi_thread")]
507435
async fn test_v1_reserved_bytes_tampered_rejected() {
508436
let keyring = test_keyring().await;

0 commit comments

Comments
 (0)