Skip to content

Instantly share code, notes, and snippets.

@Gelbpunkt
Last active December 31, 2022 23:51
Show Gist options
  • Save Gelbpunkt/39686a0f7cbd8f09a692ee8fe3531ae8 to your computer and use it in GitHub Desktop.
Save Gelbpunkt/39686a0f7cbd8f09a692ee8fe3531ae8 to your computer and use it in GitHub Desktop.
base64 0.21 issue with known lengths (wrong estimate?)
[package]
name = "testing_base64"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = { version = "0.21.0-beta.2" }
base64_013 = { package = "base64", version = "0.13" }
fastrand = "1"
sha1_smol = "1"
use base64::{engine::general_purpose, Engine};
use sha1_smol::Sha1;
/// The Globally Unique Identifier (GUID) used in the websocket protocol (see [the RFC](https://datatracker.ietf.org/doc/html/rfc6455#section-1.3)).
const GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
fn get_key() -> [u8; 16] {
[
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
fastrand::u8(0..=255),
]
}
fn digest(key: &[u8]) -> [u8; 20] {
let mut s = Sha1::new();
s.update(key);
s.update(GUID.as_bytes());
s.digest().bytes()
}
fn main() {
let key_bytes = get_key();
let mut key_base64 = [0; 24];
// SAFETY: We know that 16 bytes will be 24 bytes base64-encoded
unsafe {
general_purpose::STANDARD
.encode_slice(key_bytes, &mut key_base64)
.unwrap_unchecked()
};
// Websocket client sends this in a Sec-WebSocket-Key header
// Server reads the header
// SHA1 digests are always 20 bytes
let ws_accept = digest(&key_base64);
// Calculate the Sec-WebSocket-Accept value
let ws_accept_base64 = general_purpose::STANDARD.encode(ws_accept);
assert_eq!(ws_accept_base64.len(), 28);
// Client receives this as the header and validates it
// With 0.13, it works
let mut ws_accept = [0; 20];
assert!(base64_013::decode_config_slice(
&ws_accept_base64,
base64_013::STANDARD,
&mut ws_accept
)
.is_ok());
// With 0.21 it does not
let mut ws_accept = [0; 20];
// This is why
assert_eq!(base64::decoded_len_estimate(ws_accept_base64.len()), 21);
assert!(general_purpose::STANDARD
.decode_slice(&ws_accept_base64, &mut ws_accept)
.is_err());
// Unsafe hack
let mut ws_accept = [0; 20];
let mut ws_accept_fake_length = unsafe {
let ptr = ws_accept.as_mut_ptr();
std::slice::from_raw_parts_mut(ptr, 21)
};
assert!(general_purpose::STANDARD
.decode_slice(&ws_accept_base64, &mut ws_accept_fake_length)
.is_ok());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment