Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ghpages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
uses: actions/checkout@v6

- name: "Cache Setup"
id: cache-setup
Expand All @@ -30,7 +30,7 @@ jobs:
date -u "+::set-output name=date::%FT%T"

- name: "Cache References"
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
${{ steps.cache-setup.outputs.path }}
Expand All @@ -53,7 +53,7 @@ jobs:
token: ${{ github.token }}

- name: "Archive Built Drafts"
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v6
with:
path: |
draft-*.html
Expand Down
4 changes: 4 additions & 0 deletions reference-implementation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ pub mod header;
/// Cipher agility layer
pub mod cipher;

/// Per-SSRC stream key derivation for RTP
pub mod per_ssrc_key_derivation;

pub use crate::{
cipher::{new_cipher, Cipher, CipherSuite, SFrameIntermediateValues},
header::{Counter, Header, KeyId},
per_ssrc_key_derivation::derive_ssrc_key,
};
use std::collections::HashMap;

Expand Down
38 changes: 38 additions & 0 deletions reference-implementation/src/per_ssrc_key_derivation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use hkdf::Hkdf;
use hmac::SimpleHmac;
use sha2::{Sha256, Sha512};

/// Derive the per-SSRC stream key for a given SFrame cipher suite as defined in Section 7 of draft-ietf-avtcore-rtp-sframe
///
/// This implements the RTP stream key derivation from the SFrame RTP payload format draft:
/// ```text
/// PRK = HKDF-Extract(salt=SSRC_big_endian_4_bytes, ikm=base_key)
/// stream_key = HKDF-Expand(PRK, info="SFrame 1.0 RTP Stream", L=Nh)
/// ```
///
/// Hash function and output length (Nh) are determined by cipher suite:
/// - Suites 0x0001–0x0004: SHA-256, Nh = 32
/// - Suites 0x0005–0x0008: SHA-512, Nh = 64
///
/// # Panics
/// Panics if `cipher_suite` is not in the range 0x0001–0x0008.
pub fn derive_ssrc_key(ssrc: u32, base_key: &[u8], cipher_suite: u16) -> Vec<u8> {
static INFO: &[u8] = b"SFrame 1.0 RTP Stream";
let ssrc_salt = ssrc.to_be_bytes();

match cipher_suite {
0x0001..=0x0004 => {
let (_, h) = Hkdf::<Sha256>::extract(Some(&ssrc_salt), base_key);
let mut key = vec![0u8; 32];
h.expand(INFO, &mut key).expect("HKDF-Expand failed");
key
}
0x0005..=0x0008 => {
let (_, h) = Hkdf::<Sha512, SimpleHmac<Sha512>>::extract(Some(&ssrc_salt), base_key);
let mut key = vec![0u8; 64];
h.expand(INFO, &mut key).expect("HKDF-Expand failed");
key
}
_ => panic!("Unsupported SFrame cipher suite: 0x{:04x}", cipher_suite),
}
}
57 changes: 57 additions & 0 deletions reference-implementation/tests/per_ssrc_key_derivation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Test harness for SFrame per-SSRC stream key derivation vectors.
// Loads per-ssrc-key-derivation.json from the project's test-vectors directory
// and verifies that the Rust implementation produces identical keys.
Comment on lines +1 to +3

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code should go in a new module in examples/test_vectors.rs, and follow the new/verify pattern there. Following that pattern will also get you an MD file you can include in the AVTCORE spec.


use serde::Deserialize;
use sframe_reference::derive_ssrc_key;

#[derive(Deserialize)]
struct VectorEntry {
cipher_suite: u16,
base_key: String,
ssrc_a: String,
ssrc_b: String,
stream_key_a: String,
stream_key_b: String,
}

fn hex_to_bytes(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("invalid hex"))
.collect()
}

fn parse_ssrc(s: &str) -> u32 {
u32::from_str_radix(s.trim_start_matches("0x"), 16).expect("invalid ssrc")
}

#[test]
fn per_ssrc_stream_key_vectors() {
let json = include_str!("../../test-vectors/test-vectors-per-ssrc-key-derivation.json");
let entries: Vec<VectorEntry> = serde_json::from_str(json).expect("failed to parse vectors");

assert!(!entries.is_empty(), "Expected at least one vector entry");

for entry in &entries {
let base_key = hex_to_bytes(&entry.base_key);
let ssrc_a = parse_ssrc(&entry.ssrc_a);
let ssrc_b = parse_ssrc(&entry.ssrc_b);

let computed_a = derive_ssrc_key(ssrc_a, &base_key, entry.cipher_suite);
let computed_b = derive_ssrc_key(ssrc_b, &base_key, entry.cipher_suite);

assert_eq!(
computed_a,
hex_to_bytes(&entry.stream_key_a),
"stream_key_a mismatch for cipher_suite 0x{:04x}",
entry.cipher_suite
);
assert_eq!(
computed_b,
hex_to_bytes(&entry.stream_key_b),
"stream_key_b mismatch for cipher_suite 0x{:04x}",
entry.cipher_suite
);
}
}
66 changes: 66 additions & 0 deletions test-vectors/test-vectors-per-ssrc-key-derivation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[
{
"cipher_suite": 1,
"base_key": "000102030405060708090a0b0c0d0e0f",
"ssrc_a": "0x12345678",
"ssrc_b": "0x98765432",
"stream_key_a": "f309f118aecdacfbc165cc080022b297100bbb55e483b002d762000a0faa684d",
"stream_key_b": "06e5d1b8c6e01f10b592bd2e13ff4b37182b895ec8330d5cc6ec690704043b5f"
},
{
"cipher_suite": 2,
"base_key": "000102030405060708090a0b0c0d0e0f",
"ssrc_a": "0x12345678",
"ssrc_b": "0x98765432",
"stream_key_a": "f309f118aecdacfbc165cc080022b297100bbb55e483b002d762000a0faa684d",
"stream_key_b": "06e5d1b8c6e01f10b592bd2e13ff4b37182b895ec8330d5cc6ec690704043b5f"
},
{
"cipher_suite": 3,
"base_key": "000102030405060708090a0b0c0d0e0f",
"ssrc_a": "0x12345678",
"ssrc_b": "0x98765432",
"stream_key_a": "f309f118aecdacfbc165cc080022b297100bbb55e483b002d762000a0faa684d",
"stream_key_b": "06e5d1b8c6e01f10b592bd2e13ff4b37182b895ec8330d5cc6ec690704043b5f"
},
{
"cipher_suite": 4,
"base_key": "000102030405060708090a0b0c0d0e0f",
"ssrc_a": "0x12345678",
"ssrc_b": "0x98765432",
"stream_key_a": "f309f118aecdacfbc165cc080022b297100bbb55e483b002d762000a0faa684d",
"stream_key_b": "06e5d1b8c6e01f10b592bd2e13ff4b37182b895ec8330d5cc6ec690704043b5f"
},
{
"cipher_suite": 5,
"base_key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"ssrc_a": "0x12345678",
"ssrc_b": "0x98765432",
"stream_key_a": "4715e2a3e3ecb408c83135a30b8e660c75a3488dbd4d9dd3553a01b39df76bc43a5a0b130159edd90353b09c810e849db7398182ac7c1dea072a2c1b6bac7dda",
"stream_key_b": "f7891c6b18d88a93ba7638875b87146df4cb9693ca3837b480e9221539e86c1c98391cb1273423b33f94abe7f67c1070997a588084a54391ebda18b3d6ff92f1"
},
{
"cipher_suite": 6,
"base_key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"ssrc_a": "0x12345678",
"ssrc_b": "0x98765432",
"stream_key_a": "4715e2a3e3ecb408c83135a30b8e660c75a3488dbd4d9dd3553a01b39df76bc43a5a0b130159edd90353b09c810e849db7398182ac7c1dea072a2c1b6bac7dda",
"stream_key_b": "f7891c6b18d88a93ba7638875b87146df4cb9693ca3837b480e9221539e86c1c98391cb1273423b33f94abe7f67c1070997a588084a54391ebda18b3d6ff92f1"
},
{
"cipher_suite": 7,
"base_key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"ssrc_a": "0x12345678",
"ssrc_b": "0x98765432",
"stream_key_a": "4715e2a3e3ecb408c83135a30b8e660c75a3488dbd4d9dd3553a01b39df76bc43a5a0b130159edd90353b09c810e849db7398182ac7c1dea072a2c1b6bac7dda",
"stream_key_b": "f7891c6b18d88a93ba7638875b87146df4cb9693ca3837b480e9221539e86c1c98391cb1273423b33f94abe7f67c1070997a588084a54391ebda18b3d6ff92f1"
},
{
"cipher_suite": 8,
"base_key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"ssrc_a": "0x12345678",
"ssrc_b": "0x98765432",
"stream_key_a": "4715e2a3e3ecb408c83135a30b8e660c75a3488dbd4d9dd3553a01b39df76bc43a5a0b130159edd90353b09c810e849db7398182ac7c1dea072a2c1b6bac7dda",
"stream_key_b": "f7891c6b18d88a93ba7638875b87146df4cb9693ca3837b480e9221539e86c1c98391cb1273423b33f94abe7f67c1070997a588084a54391ebda18b3d6ff92f1"
}
]
Loading