const K: [u32; 64] = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98,
0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8,
0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
0xc67178f2,
];
pub fn sha256(data: &[u8]) -> [u8; 32] {
let mut state: [u32; 8] =
[0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
let mut padded = data.to_vec();
padded.push(0x80);
while padded.len() % 64 != 56 {
padded.push(0);
}
padded.extend_from_slice(&(data.len() as u64 * 8).to_be_bytes());
for block in padded.as_chunks::<64>().0 {
let mut w = [0u32; 64];
for (index, word) in block.as_chunks::<4>().0.iter().enumerate() {
w[index] = u32::from_be_bytes(*word);
}
for index in 16..64 {
let s0 = w[index - 15].rotate_right(7) ^ w[index - 15].rotate_right(18) ^ (w[index - 15] >> 3);
let s1 = w[index - 2].rotate_right(17) ^ w[index - 2].rotate_right(19) ^ (w[index - 2] >> 10);
w[index] = w[index - 16].wrapping_add(s0).wrapping_add(w[index - 7]).wrapping_add(s1);
}
let mut v = state;
for index in 0..64 {
let s1 = v[4].rotate_right(6) ^ v[4].rotate_right(11) ^ v[4].rotate_right(25);
let choice = (v[4] & v[5]) ^ (!v[4] & v[6]);
let t1 = v[7].wrapping_add(s1).wrapping_add(choice).wrapping_add(K[index]).wrapping_add(w[index]);
let s0 = v[0].rotate_right(2) ^ v[0].rotate_right(13) ^ v[0].rotate_right(22);
let majority = (v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2]);
let t2 = s0.wrapping_add(majority);
v = [t1.wrapping_add(t2), v[0], v[1], v[2], v[3].wrapping_add(t1), v[4], v[5], v[6]];
}
for (slot, value) in state.iter_mut().zip(v) {
*slot = slot.wrapping_add(value);
}
}
let mut out = [0u8; 32];
for (chunk, word) in out.as_chunks_mut::<4>().0.iter_mut().zip(state) {
*chunk = word.to_be_bytes();
}
out
}
pub fn base64url(data: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let n = chunk.iter().enumerate().fold(0u32, |acc, (index, byte)| acc | u32::from(*byte) << (16 - 8 * index));
for index in 0..=chunk.len() {
out.push(ALPHABET[(n >> (18 - 6 * index) & 63) as usize] as char);
}
}
out
}
pub fn base64_decode(text: &str) -> Option<Vec<u8>> {
let mut out = Vec::new();
let (mut buffer, mut bits) = (0u32, 0);
for byte in text.bytes().filter(|byte| *byte != b'=') {
let value = match byte {
b'A'..=b'Z' => byte - b'A',
b'a'..=b'z' => byte - b'a' + 26,
b'0'..=b'9' => byte - b'0' + 52,
b'-' | b'+' => 62,
b'_' | b'/' => 63,
_ => return None,
};
buffer = buffer << 6 | u32::from(value);
bits += 6;
if bits >= 8 {
bits -= 8;
out.push((buffer >> bits) as u8);
buffer &= (1 << bits) - 1;
}
}
Some(out)
}
pub use crate::core::platform::random_bytes;
pub struct Pkce {
pub verifier: String,
pub challenge: String,
}
impl Pkce {
pub fn generate() -> Result<Self, String> {
let verifier = base64url(&random_bytes::<32>()?);
let challenge = base64url(&sha256(verifier.as_bytes()));
Ok(Self { verifier, challenge })
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
}
#[test]
fn sha256_coincide_con_los_vectores_conocidos() {
assert_eq!(hex(&sha256(b"")), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
assert_eq!(hex(&sha256(b"abc")), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
let long = vec![b'a'; 1000];
assert_eq!(hex(&sha256(&long)), "41edece42d63e8d9bf515a9ba6932e1c20cbc9f5a5d134645adb5db1b9737ea3");
}
#[test]
fn el_desafio_del_rfc_7636_sale_igual() {
let verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
assert_eq!(base64url(&sha256(verifier.as_bytes())), "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM");
}
#[test]
fn base64_ida_y_vuelta() {
for text in ["", "f", "fo", "foo", "foob", "fooba", "foobar"] {
assert_eq!(base64_decode(&base64url(text.as_bytes())).unwrap(), text.as_bytes());
}
assert!(base64_decode("a*b").is_none());
assert_eq!(Pkce::generate().unwrap().verifier.len(), 43);
}
}