use crate::codec::StunError;
pub const ATTR_DIG_IDENTITY: u16 = 0xD160;
pub const ATTR_DIG_SIGNATURE: u16 = 0xD161;
pub const ATTR_ERROR_CODE: u16 = 0x0009;
pub const ATTR_REALM: u16 = 0x0014;
pub const ATTR_NONCE: u16 = 0x0015;
pub const BINDING_ERROR: u16 = 0x0111;
pub const REALM: &str = "dig-stun";
pub const CREDENTIAL_VERSION: u8 = 0x01;
pub const P256_SPKI_LEN: usize = 91;
pub const P256_SPKI_PREFIX: [u8; 26] = [
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
];
pub const MAX_SIGNATURE_LEN: usize = 72;
const MIN_SIGNATURE_ATTR_LEN: usize = 9;
pub const ERR_BAD_REQUEST: u16 = 400;
pub const ERR_UNAUTHENTICATED: u16 = 401;
pub const ERR_STALE_NONCE: u16 = 438;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum CredentialError {
#[error("underlying STUN error: {0}")]
Stun(#[from] StunError),
#[error("malformed DIG credential attribute")]
Malformed,
#[error("signature did not verify")]
BadSignature,
}
pub(super) fn is_valid_spki_der(spki: &[u8]) -> bool {
spki.len() == P256_SPKI_LEN && spki[..26] == P256_SPKI_PREFIX && spki[26] == 0x04
}
pub(super) fn is_valid_signature_der_len(sig_der: &[u8]) -> bool {
let attr_len = sig_der.len() + 1; (MIN_SIGNATURE_ATTR_LEN..=1 + MAX_SIGNATURE_LEN).contains(&attr_len)
}
pub(super) fn write_attr(msg: &mut Vec<u8>, attr_type: u16, value: &[u8]) {
msg.extend_from_slice(&attr_type.to_be_bytes());
msg.extend_from_slice(&(value.len() as u16).to_be_bytes());
msg.extend_from_slice(value);
let pad = (4 - (value.len() % 4)) % 4;
msg.resize(msg.len() + pad, 0);
}
pub(super) fn write_header(
msg: &mut Vec<u8>,
msg_type: u16,
attrs_len: u16,
txid: &crate::codec::TransactionId,
) {
msg.extend_from_slice(&msg_type.to_be_bytes());
msg.extend_from_slice(&attrs_len.to_be_bytes());
msg.extend_from_slice(&crate::codec::MAGIC_COOKIE.to_be_bytes());
msg.extend_from_slice(txid);
}
const B64URL_ALPHABET: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
pub(super) fn base64url_encode(input: &[u8]) -> String {
let mut out = String::with_capacity(input.len().div_ceil(3) * 4);
for chunk in input.chunks(3) {
let b0 = chunk[0];
let b1 = chunk.get(1).copied().unwrap_or(0);
let b2 = chunk.get(2).copied().unwrap_or(0);
let n = ((b0 as u32) << 16) | ((b1 as u32) << 8) | (b2 as u32);
out.push(B64URL_ALPHABET[((n >> 18) & 0x3f) as usize] as char);
out.push(B64URL_ALPHABET[((n >> 12) & 0x3f) as usize] as char);
if chunk.len() > 1 {
out.push(B64URL_ALPHABET[((n >> 6) & 0x3f) as usize] as char);
}
if chunk.len() > 2 {
out.push(B64URL_ALPHABET[(n & 0x3f) as usize] as char);
}
}
out
}
pub(super) fn base64url_decode(input: &[u8]) -> Option<Vec<u8>> {
fn char_value(c: u8) -> Option<u8> {
match c {
b'A'..=b'Z' => Some(c - b'A'),
b'a'..=b'z' => Some(c - b'a' + 26),
b'0'..=b'9' => Some(c - b'0' + 52),
b'-' => Some(62),
b'_' => Some(63),
_ => None,
}
}
let mut out = Vec::with_capacity(input.len() * 3 / 4);
for group in input.chunks(4) {
let vals: Vec<u8> = group
.iter()
.map(|&c| char_value(c))
.collect::<Option<Vec<u8>>>()?;
match vals.len() {
4 => {
let n = ((vals[0] as u32) << 18)
| ((vals[1] as u32) << 12)
| ((vals[2] as u32) << 6)
| (vals[3] as u32);
out.push((n >> 16) as u8);
out.push((n >> 8) as u8);
out.push(n as u8);
}
3 => {
let n =
((vals[0] as u32) << 18) | ((vals[1] as u32) << 12) | ((vals[2] as u32) << 6);
out.push((n >> 16) as u8);
out.push((n >> 8) as u8);
}
2 => {
let n = ((vals[0] as u32) << 18) | ((vals[1] as u32) << 12);
out.push((n >> 16) as u8);
}
_ => return None, }
}
Some(out)
}