Skip to main content

conmonrs/streaming_server/web/
accept_key.rs

1//! The `Sec-WebSocket-Accept` header of the WebSocket handshake.
2
3use data_encoding::BASE64;
4
5/// The GUID which RFC 6455 appends to the key of the client.
6const GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
7
8/// The value of the `Sec-WebSocket-Accept` header for the
9/// `Sec-WebSocket-Key` of a client, see RFC 6455, section 4.2.2: the base64
10/// of the SHA-1 of the key and a fixed GUID.
11///
12/// The hash is an echo of the protocol, which tells the client that the
13/// server speaks WebSocket. It is no security boundary, so SHA-1 being
14/// broken for signatures does not matter here, and it is not worth the
15/// crates of a hash library.
16pub(super) fn accept_key(key: &[u8]) -> String {
17    BASE64.encode(&sha1(&[key, GUID].concat()))
18}
19
20/// The SHA-1 digest of `data`, see RFC 3174.
21fn sha1(data: &[u8]) -> [u8; 20] {
22    // The message gets padded to whole blocks of 64 bytes: a 1 bit, zeros,
23    // and the length of the data in bits as the last 8 bytes.
24    let mut message = data.to_vec();
25    message.push(0x80);
26    message.resize((message.len() + 8).next_multiple_of(64) - 8, 0);
27    let bits = (data.len() as u64).wrapping_mul(8);
28    message.extend_from_slice(&bits.to_be_bytes());
29
30    let mut state: [u32; 5] = [
31        0x6745_2301,
32        0xEFCD_AB89,
33        0x98BA_DCFE,
34        0x1032_5476,
35        0xC3D2_E1F0,
36    ];
37    for block in message.as_chunks::<64>().0 {
38        let mut w = [0u32; 80];
39        for (word, bytes) in w.iter_mut().zip(block.as_chunks::<4>().0) {
40            *word = u32::from_be_bytes(*bytes);
41        }
42        for t in 16..80 {
43            w[t] = (w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]).rotate_left(1);
44        }
45
46        let [mut a, mut b, mut c, mut d, mut e] = state;
47        for (t, word) in w.iter().enumerate() {
48            let (f, k) = match t {
49                0..20 => ((b & c) | (!b & d), 0x5A82_7999),
50                20..40 => (b ^ c ^ d, 0x6ED9_EBA1),
51                40..60 => ((b & c) | (b & d) | (c & d), 0x8F1B_BCDC),
52                _ => (b ^ c ^ d, 0xCA62_C1D6),
53            };
54            let temp = a
55                .rotate_left(5)
56                .wrapping_add(f)
57                .wrapping_add(e)
58                .wrapping_add(k)
59                .wrapping_add(*word);
60            (a, b, c, d, e) = (temp, a, b.rotate_left(30), c, d);
61        }
62        for (sum, value) in state.iter_mut().zip([a, b, c, d, e]) {
63            *sum = sum.wrapping_add(value);
64        }
65    }
66
67    let mut digest = [0; 20];
68    for (bytes, word) in digest.as_chunks_mut::<4>().0.iter_mut().zip(state) {
69        *bytes = word.to_be_bytes();
70    }
71    digest
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use data_encoding::HEXLOWER;
78
79    #[test]
80    fn accept_key_of_rfc_6455() {
81        assert_eq!(
82            accept_key(b"dGhlIHNhbXBsZSBub25jZQ=="),
83            "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
84        );
85    }
86
87    #[test]
88    fn sha1_test_vectors() {
89        for (data, repeat, digest) in [
90            // The test vectors of RFC 3174.
91            ("abc", 1, "a9993e364706816aba3e25717850c26c9cd0d89d"),
92            (
93                "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
94                1,
95                "84983e441c3bd26ebaae4aa1f95129e5e54670f1",
96            ),
97            ("a", 1_000_000, "34aa973cd4c4daa4f61eeb2bdbad27316534016f"),
98            (
99                "0123456701234567012345670123456701234567012345670123456701234567",
100                10,
101                "dea356a2cddd90c7a7ecedc5ebb563934f460452",
102            ),
103            // Around the sizes where the padding takes another block.
104            ("", 1, "da39a3ee5e6b4b0d3255bfef95601890afd80709"),
105            ("a", 55, "c1c8bbdc22796e28c0e15163d20899b65621d65a"),
106            ("a", 56, "c2db330f6083854c99d4b5bfb6e8f29f201be699"),
107            ("a", 63, "03f09f5b158a7a8cdad920bddc29b81c18a551f5"),
108            ("a", 64, "0098ba824b5c16427bd7a1122a5a442a25ec644d"),
109            ("a", 65, "11655326c708d70319be2610e8a57d9a5b959d3b"),
110        ] {
111            assert_eq!(
112                HEXLOWER.encode(&sha1(data.repeat(repeat).as_bytes())),
113                digest,
114                "{repeat} times {data:?}"
115            );
116        }
117    }
118}