turn_server/codec/
crypto.rs

1use std::ops::Deref;
2
3use aws_lc_rs::{digest, hmac};
4use base64::{Engine, prelude::BASE64_STANDARD};
5use md5::{Digest, Md5};
6
7use super::message::attributes::PasswordAlgorithm;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Password {
11    Md5([u8; 16]),
12    Sha256([u8; 32]),
13}
14
15impl Deref for Password {
16    type Target = [u8];
17
18    fn deref(&self) -> &Self::Target {
19        match self {
20            Password::Md5(it) => it,
21            Password::Sha256(it) => it,
22        }
23    }
24}
25
26/// HMAC SHA1 digest.
27///
28/// # Test
29///
30/// ```
31/// use turn_server::codec::crypto::hmac_sha1;
32///
33/// let buffer = [
34///     0x00u8, 0x03, 0x00, 0x50, 0x21, 0x12, 0xa4, 0x42, 0x64, 0x4f, 0x5a,
35///     0x78, 0x6a, 0x56, 0x33, 0x62, 0x4b, 0x52, 0x33, 0x31, 0x00, 0x19, 0x00,
36///     0x04, 0x11, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x05, 0x70, 0x61, 0x6e,
37///     0x64, 0x61, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x09, 0x72, 0x61, 0x73,
38///     0x70, 0x62, 0x65, 0x72, 0x72, 0x79, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00,
39///     0x10, 0x31, 0x63, 0x31, 0x33, 0x64, 0x32, 0x62, 0x32, 0x34, 0x35, 0x62,
40///     0x33, 0x61, 0x37, 0x33, 0x34,
41/// ];
42///
43/// let key = [
44///     0x3eu8, 0x2f, 0x79, 0x1e, 0x1f, 0x14, 0xd1, 0x73, 0xfc, 0x91, 0xff,
45///     0x2f, 0x59, 0xb5, 0x0f, 0xd1,
46/// ];
47///
48/// let sign = [
49///     0xd6u8, 0x78, 0x26, 0x99, 0x0e, 0x15, 0x56, 0x15, 0xe5, 0xf4, 0x24,
50///     0x74, 0xe2, 0x3c, 0x26, 0xc5, 0xb1, 0x03, 0xb2, 0x6d,
51/// ];
52///
53/// let hmac_output = hmac_sha1(&key, &[&buffer]);
54///
55/// assert_eq!(&hmac_output, &sign);
56/// ```
57pub fn hmac_sha1(key: &[u8], source: &[&[u8]]) -> [u8; 20] {
58    let key = hmac::Key::new(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, key);
59    let mut ctx = hmac::Context::with_key(&key);
60
61    for buf in source {
62        ctx.update(buf);
63    }
64
65    let mut result = [0u8; 20];
66    result.copy_from_slice(ctx.sign().as_ref());
67    result
68}
69
70/// CRC32 Fingerprint.
71///
72/// # Test
73///
74/// ```
75/// use turn_server::codec::crypto::fingerprint;
76///
77/// assert_eq!(fingerprint(b"1"), 3498621689);
78/// ```
79pub fn fingerprint(bytes: &[u8]) -> u32 {
80    crc32fast::hash(bytes) ^ 0x5354_554e
81}
82
83/// generate create long term credential.
84///
85/// > key = MD5(username ":" OpaqueString(realm) ":" OpaqueString(password))
86///
87/// # Test
88///
89/// ```
90/// use turn_server::codec::crypto::{generate_password, Password};
91/// use turn_server::codec::message::attributes::PasswordAlgorithm;
92///
93/// let buffer = [
94///     0x3eu8, 0x2f, 0x79, 0x1e, 0x1f, 0x14, 0xd1, 0x73, 0xfc, 0x91, 0xff,
95///     0x2f, 0x59, 0xb5, 0x0f, 0xd1,
96/// ];
97///
98/// let password = generate_password(
99///     "panda",
100///     "panda",
101///     "raspberry",
102///     PasswordAlgorithm::Md5,
103/// );
104///
105/// match password {
106///     Password::Md5(it) => {
107///         assert_eq!(it, buffer);
108///     }
109///     Password::Sha256(it) => {
110///         unreachable!();
111///     }
112/// }
113/// ```
114pub fn generate_password(
115    username: &str,
116    password: &str,
117    realm: &str,
118    algorithm: PasswordAlgorithm,
119) -> Password {
120    match algorithm {
121        PasswordAlgorithm::Md5 => {
122            let mut hasher = Md5::new();
123
124            hasher.update([username, realm, password].join(":"));
125
126            Password::Md5(hasher.finalize().into())
127        }
128        PasswordAlgorithm::Sha256 => {
129            let mut ctx = digest::Context::new(&digest::SHA256);
130
131            ctx.update([username, realm, password].join(":").as_bytes());
132
133            let mut result = [0u8; 32];
134            result.copy_from_slice(ctx.finish().as_ref());
135            Password::Sha256(result)
136        }
137    }
138}
139
140// Because (TURN REST api) this RFC does not mandate the format of the username,
141// only suggested values. In principle, the RFC also indicates that the
142// timestamp part of username can be set at will, so the timestamp is not
143// verified here, and the external web service guarantees its security by
144// itself.
145//
146// https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00#section-2.2
147pub fn static_auth_secret(
148    username: &str,
149    secret: &str,
150    realm: &str,
151    algorithm: PasswordAlgorithm,
152) -> Password {
153    let password =
154        BASE64_STANDARD.encode(hmac_sha1(secret.as_bytes(), &[username.as_bytes()]).as_slice());
155
156    generate_password(username, &password, realm, algorithm)
157}