we (web engine): Experimental web browser project to understand the limits of Claude

Implement RSA PKCS#1 v1.5 signature verification (RFC 8017)

BigUint type with arbitrary-precision arithmetic backed by little-endian
u64 limbs. Supports modular exponentiation via Montgomery multiplication
for odd moduli (RSA) with fallback for even moduli.

RSA public key parsing from both PKCS#1 RSAPublicKey and PKCS#8
SubjectPublicKeyInfo DER formats. RSASSA-PKCS1-v1_5-VERIFY with
EMSA-PKCS1-v1_5 encoding for SHA-256, SHA-384, and SHA-512.
Constant-time signature comparison. Key sizes 2048-4096 bits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

authored by pierrelf.com

Claude Opus 4.6 and committed by tangled.org fcaa03b8 8c93c73d

+1574
+737
crates/crypto/src/bigint.rs
··· 1 + //! Arbitrary-precision unsigned integer arithmetic for RSA. 2 + //! 3 + //! Provides a `BigUint` type backed by little-endian `u64` limbs, with 4 + //! modular exponentiation (square-and-multiply) for up to 4096-bit keys. 5 + 6 + use core::cmp::Ordering; 7 + 8 + /// Arbitrary-precision unsigned integer stored as little-endian `u64` limbs. 9 + #[derive(Clone, Debug)] 10 + pub struct BigUint { 11 + /// Limbs in little-endian order (limbs[0] is the least significant). 12 + limbs: Vec<u64>, 13 + } 14 + 15 + impl BigUint { 16 + /// The number zero. 17 + pub fn zero() -> Self { 18 + Self { limbs: vec![0] } 19 + } 20 + 21 + /// The number one. 22 + pub fn one() -> Self { 23 + Self { limbs: vec![1] } 24 + } 25 + 26 + /// Create from a single u64. 27 + pub fn from_u64(v: u64) -> Self { 28 + Self { limbs: vec![v] } 29 + } 30 + 31 + /// Create from big-endian bytes (as used in DER INTEGER encoding). 32 + pub fn from_be_bytes(bytes: &[u8]) -> Self { 33 + if bytes.is_empty() { 34 + return Self::zero(); 35 + } 36 + 37 + // Convert big-endian bytes to little-endian u64 limbs. 38 + let mut limbs = Vec::new(); 39 + let mut i = bytes.len(); 40 + while i > 0 { 41 + let start = i.saturating_sub(8); 42 + let chunk = &bytes[start..i]; 43 + let mut val = 0u64; 44 + for &b in chunk { 45 + val = (val << 8) | b as u64; 46 + } 47 + limbs.push(val); 48 + i = start; 49 + } 50 + 51 + let mut result = Self { limbs }; 52 + result.normalize(); 53 + result 54 + } 55 + 56 + /// Export to big-endian bytes. 57 + pub fn to_be_bytes(&self) -> Vec<u8> { 58 + if self.is_zero() { 59 + return vec![0]; 60 + } 61 + 62 + let mut bytes = Vec::new(); 63 + // Start from the most significant limb. 64 + let mut started = false; 65 + for &limb in self.limbs.iter().rev() { 66 + for shift in (0..8).rev() { 67 + let byte = (limb >> (shift * 8)) as u8; 68 + if !started && byte == 0 { 69 + continue; 70 + } 71 + started = true; 72 + bytes.push(byte); 73 + } 74 + } 75 + 76 + if bytes.is_empty() { 77 + bytes.push(0); 78 + } 79 + bytes 80 + } 81 + 82 + /// Export to big-endian bytes, zero-padded to exactly `len` bytes. 83 + pub fn to_be_bytes_padded(&self, len: usize) -> Vec<u8> { 84 + let raw = self.to_be_bytes(); 85 + if raw.len() >= len { 86 + // Take the last `len` bytes (truncate leading bytes). 87 + return raw[raw.len() - len..].to_vec(); 88 + } 89 + let mut out = vec![0u8; len - raw.len()]; 90 + out.extend_from_slice(&raw); 91 + out 92 + } 93 + 94 + /// True if the value is zero. 95 + pub fn is_zero(&self) -> bool { 96 + self.limbs.iter().all(|&l| l == 0) 97 + } 98 + 99 + /// Number of significant bits. 100 + pub fn bit_len(&self) -> usize { 101 + if self.is_zero() { 102 + return 0; 103 + } 104 + let top = self.limbs.len() - 1; 105 + let top_bits = 64 - self.limbs[top].leading_zeros() as usize; 106 + top * 64 + top_bits 107 + } 108 + 109 + /// Get bit at position `i` (0-indexed from LSB). 110 + pub fn bit(&self, i: usize) -> bool { 111 + let limb_idx = i / 64; 112 + let bit_idx = i % 64; 113 + if limb_idx >= self.limbs.len() { 114 + return false; 115 + } 116 + (self.limbs[limb_idx] >> bit_idx) & 1 == 1 117 + } 118 + 119 + /// Remove trailing zero limbs (keep at least one limb). 120 + fn normalize(&mut self) { 121 + while self.limbs.len() > 1 && *self.limbs.last().unwrap() == 0 { 122 + self.limbs.pop(); 123 + } 124 + } 125 + 126 + /// Addition: self + other. 127 + pub fn add(&self, other: &Self) -> Self { 128 + let max_len = self.limbs.len().max(other.limbs.len()); 129 + let mut result = Vec::with_capacity(max_len + 1); 130 + let mut carry = 0u64; 131 + 132 + for i in 0..max_len { 133 + let a = if i < self.limbs.len() { 134 + self.limbs[i] 135 + } else { 136 + 0 137 + }; 138 + let b = if i < other.limbs.len() { 139 + other.limbs[i] 140 + } else { 141 + 0 142 + }; 143 + let (sum1, c1) = a.overflowing_add(b); 144 + let (sum2, c2) = sum1.overflowing_add(carry); 145 + result.push(sum2); 146 + carry = (c1 as u64) + (c2 as u64); 147 + } 148 + 149 + if carry > 0 { 150 + result.push(carry); 151 + } 152 + 153 + let mut r = Self { limbs: result }; 154 + r.normalize(); 155 + r 156 + } 157 + 158 + /// Subtraction: self - other. Panics if other > self. 159 + pub fn sub(&self, other: &Self) -> Self { 160 + debug_assert!(self.cmp(other) != Ordering::Less); 161 + let mut result = Vec::with_capacity(self.limbs.len()); 162 + let mut borrow = 0u64; 163 + 164 + for i in 0..self.limbs.len() { 165 + let a = self.limbs[i]; 166 + let b = if i < other.limbs.len() { 167 + other.limbs[i] 168 + } else { 169 + 0 170 + }; 171 + let (diff1, b1) = a.overflowing_sub(b); 172 + let (diff2, b2) = diff1.overflowing_sub(borrow); 173 + result.push(diff2); 174 + borrow = (b1 as u64) + (b2 as u64); 175 + } 176 + 177 + let mut r = Self { limbs: result }; 178 + r.normalize(); 179 + r 180 + } 181 + 182 + /// Multiplication: self * other. 183 + pub fn mul(&self, other: &Self) -> Self { 184 + let n = self.limbs.len(); 185 + let m = other.limbs.len(); 186 + let mut result = vec![0u64; n + m]; 187 + 188 + for i in 0..n { 189 + let mut carry = 0u128; 190 + for j in 0..m { 191 + let prod = (self.limbs[i] as u128) * (other.limbs[j] as u128) 192 + + result[i + j] as u128 193 + + carry; 194 + result[i + j] = prod as u64; 195 + carry = prod >> 64; 196 + } 197 + result[i + m] = carry as u64; 198 + } 199 + 200 + let mut r = Self { limbs: result }; 201 + r.normalize(); 202 + r 203 + } 204 + 205 + /// Division with remainder: returns (quotient, remainder). 206 + pub fn div_rem(&self, divisor: &Self) -> (Self, Self) { 207 + assert!(!divisor.is_zero(), "division by zero"); 208 + 209 + if self.cmp(divisor) == Ordering::Less { 210 + return (Self::zero(), self.clone()); 211 + } 212 + 213 + if divisor.limbs.len() == 1 { 214 + return self.div_rem_single(divisor.limbs[0]); 215 + } 216 + 217 + self.div_rem_knuth(divisor) 218 + } 219 + 220 + /// Single-limb division. 221 + fn div_rem_single(&self, d: u64) -> (Self, Self) { 222 + let mut quotient = vec![0u64; self.limbs.len()]; 223 + let mut rem = 0u128; 224 + 225 + for i in (0..self.limbs.len()).rev() { 226 + rem = (rem << 64) | self.limbs[i] as u128; 227 + quotient[i] = (rem / d as u128) as u64; 228 + rem %= d as u128; 229 + } 230 + 231 + let mut q = Self { limbs: quotient }; 232 + q.normalize(); 233 + (q, Self::from_u64(rem as u64)) 234 + } 235 + 236 + /// Multi-limb division using Knuth's Algorithm D. 237 + fn div_rem_knuth(&self, divisor: &Self) -> (Self, Self) { 238 + let n = divisor.limbs.len(); 239 + let m = self.limbs.len() - n; 240 + 241 + // Normalize: shift so that the top bit of divisor is set. 242 + let shift = divisor.limbs[n - 1].leading_zeros(); 243 + let u = self.shl_bits(shift); 244 + let v = divisor.shl_bits(shift); 245 + 246 + let mut u_limbs = u.limbs.clone(); 247 + // Ensure u has m + n + 1 limbs. 248 + while u_limbs.len() <= m + n { 249 + u_limbs.push(0); 250 + } 251 + 252 + let mut q = vec![0u64; m + 1]; 253 + 254 + for j in (0..=m).rev() { 255 + // Estimate q_hat. 256 + let u_hi = ((u_limbs[j + n] as u128) << 64) | u_limbs[j + n - 1] as u128; 257 + let mut q_hat = u_hi / v.limbs[n - 1] as u128; 258 + let mut r_hat = u_hi % v.limbs[n - 1] as u128; 259 + 260 + // Refine estimate. 261 + loop { 262 + if q_hat >= (1u128 << 64) 263 + || (q_hat * v.limbs[n - 2] as u128 264 + > ((r_hat << 64) | u_limbs[j + n - 2] as u128)) 265 + { 266 + q_hat -= 1; 267 + r_hat += v.limbs[n - 1] as u128; 268 + if r_hat < (1u128 << 64) { 269 + continue; 270 + } 271 + } 272 + break; 273 + } 274 + 275 + // Multiply and subtract. 276 + let mut borrow: i128 = 0; 277 + for i in 0..n { 278 + let prod = q_hat * v.limbs[i] as u128; 279 + let diff = u_limbs[j + i] as i128 - borrow - (prod as u64) as i128; 280 + u_limbs[j + i] = diff as u64; 281 + borrow = (prod >> 64) as i128 - (diff >> 64); 282 + } 283 + let diff = u_limbs[j + n] as i128 - borrow; 284 + u_limbs[j + n] = diff as u64; 285 + 286 + q[j] = q_hat as u64; 287 + 288 + // If we subtracted too much, add back. 289 + if diff < 0 { 290 + q[j] -= 1; 291 + let mut carry = 0u64; 292 + for i in 0..n { 293 + let sum = u_limbs[j + i] as u128 + v.limbs[i] as u128 + carry as u128; 294 + u_limbs[j + i] = sum as u64; 295 + carry = (sum >> 64) as u64; 296 + } 297 + u_limbs[j + n] = u_limbs[j + n].wrapping_add(carry); 298 + } 299 + } 300 + 301 + // Remainder: unnormalize. 302 + u_limbs.truncate(n); 303 + let rem = Self { limbs: u_limbs }.shr_bits(shift); 304 + 305 + let mut quotient = Self { limbs: q }; 306 + quotient.normalize(); 307 + 308 + (quotient, rem) 309 + } 310 + 311 + /// Left shift by `bits` positions (bits < 64). 312 + fn shl_bits(&self, bits: u32) -> Self { 313 + if bits == 0 { 314 + return self.clone(); 315 + } 316 + let mut result = Vec::with_capacity(self.limbs.len() + 1); 317 + let mut carry = 0u64; 318 + for &limb in &self.limbs { 319 + result.push((limb << bits) | carry); 320 + carry = limb >> (64 - bits); 321 + } 322 + if carry > 0 { 323 + result.push(carry); 324 + } 325 + let mut r = Self { limbs: result }; 326 + r.normalize(); 327 + r 328 + } 329 + 330 + /// Right shift by `bits` positions (bits < 64). 331 + fn shr_bits(&self, bits: u32) -> Self { 332 + if bits == 0 { 333 + return self.clone(); 334 + } 335 + let mut result = Vec::with_capacity(self.limbs.len()); 336 + let mut carry = 0u64; 337 + for &limb in self.limbs.iter().rev() { 338 + result.push((limb >> bits) | carry); 339 + carry = limb << (64 - bits); 340 + } 341 + result.reverse(); 342 + let mut r = Self { limbs: result }; 343 + r.normalize(); 344 + r 345 + } 346 + 347 + /// Modular exponentiation: self^exp mod modulus. 348 + /// Uses left-to-right binary method (square-and-multiply). 349 + pub fn modpow(&self, exp: &Self, modulus: &Self) -> Self { 350 + assert!(!modulus.is_zero(), "modulus must be non-zero"); 351 + 352 + if modulus.limbs == [1] { 353 + return Self::zero(); 354 + } 355 + 356 + // Montgomery multiplication requires an odd modulus. 357 + // RSA moduli are always odd, but handle even case with simple modpow. 358 + if modulus.limbs[0] & 1 == 0 { 359 + return simple_modpow(self, exp, modulus); 360 + } 361 + 362 + montgomery_modpow(self, exp, modulus) 363 + } 364 + 365 + /// Simple modular reduction: self mod modulus. 366 + pub fn modulo(&self, modulus: &Self) -> Self { 367 + self.div_rem(modulus).1 368 + } 369 + 370 + /// Number of limbs. 371 + pub fn num_limbs(&self) -> usize { 372 + self.limbs.len() 373 + } 374 + } 375 + 376 + impl Ord for BigUint { 377 + fn cmp(&self, other: &Self) -> Ordering { 378 + let a_len = self.limbs.len(); 379 + let b_len = other.limbs.len(); 380 + let max_len = a_len.max(b_len); 381 + for i in (0..max_len).rev() { 382 + let a = if i < a_len { self.limbs[i] } else { 0 }; 383 + let b = if i < b_len { other.limbs[i] } else { 0 }; 384 + match a.cmp(&b) { 385 + Ordering::Equal => continue, 386 + ord => return ord, 387 + } 388 + } 389 + Ordering::Equal 390 + } 391 + } 392 + 393 + impl PartialOrd for BigUint { 394 + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 395 + Some(self.cmp(other)) 396 + } 397 + } 398 + 399 + impl PartialEq for BigUint { 400 + fn eq(&self, other: &Self) -> bool { 401 + self.cmp(other) == Ordering::Equal 402 + } 403 + } 404 + 405 + impl Eq for BigUint {} 406 + 407 + // --------------------------------------------------------------------------- 408 + // Montgomery multiplication for efficient modular exponentiation 409 + // --------------------------------------------------------------------------- 410 + 411 + /// Montgomery context for a given modulus. 412 + struct Montgomery { 413 + /// The modulus N. 414 + n: BigUint, 415 + /// Number of limbs in N. 416 + num_limbs: usize, 417 + /// R = 2^(64*num_limbs) (not stored, implicit). 418 + /// R^2 mod N — used to convert into Montgomery form. 419 + r_squared: BigUint, 420 + /// n0_inv = -N^(-1) mod 2^64. 421 + n0_inv: u64, 422 + } 423 + 424 + impl Montgomery { 425 + fn new(n: &BigUint) -> Self { 426 + let num_limbs = n.limbs.len(); 427 + 428 + // Compute n0_inv = -N^(-1) mod 2^64 using Newton's method. 429 + // Start with x = 1 (since n is odd for RSA moduli). 430 + let n0 = n.limbs[0]; 431 + let mut inv = 1u64; 432 + for _ in 0..63 { 433 + inv = inv.wrapping_mul(2u64.wrapping_sub(n0.wrapping_mul(inv))); 434 + } 435 + let n0_inv = inv.wrapping_neg(); // -N^(-1) mod 2^64 436 + 437 + // Compute R^2 mod N where R = 2^(64*num_limbs). 438 + // Start with R mod N, then square it. 439 + let r_mod_n = { 440 + // R = 2^(64*num_limbs). We compute R mod N by creating R and reducing. 441 + let mut r_limbs = vec![0u64; num_limbs + 1]; 442 + r_limbs[num_limbs] = 1; 443 + let r = BigUint { limbs: r_limbs }; 444 + r.div_rem(n).1 445 + }; 446 + let r_squared = r_mod_n.mul(&r_mod_n).div_rem(n).1; 447 + 448 + Self { 449 + n: n.clone(), 450 + num_limbs, 451 + r_squared, 452 + n0_inv, 453 + } 454 + } 455 + 456 + /// Convert a value into Montgomery form: aR mod N. 457 + fn to_montgomery(&self, a: &BigUint) -> BigUint { 458 + self.montgomery_mul(a, &self.r_squared) 459 + } 460 + 461 + /// Convert from Montgomery form back to normal: a * R^(-1) mod N. 462 + fn reduce(&self, a: &BigUint) -> BigUint { 463 + self.montgomery_mul(a, &BigUint::one()) 464 + } 465 + 466 + /// Montgomery multiplication: computes a * b * R^(-1) mod N. 467 + fn montgomery_mul(&self, a: &BigUint, b: &BigUint) -> BigUint { 468 + let n = self.num_limbs; 469 + let mut t = vec![0u64; 2 * n + 2]; 470 + 471 + for i in 0..n { 472 + // t = t + a[i] * b 473 + let ai = if i < a.limbs.len() { a.limbs[i] } else { 0 }; 474 + let mut carry = 0u128; 475 + for j in 0..n { 476 + let bj = if j < b.limbs.len() { b.limbs[j] } else { 0 }; 477 + let sum = t[i + j] as u128 + (ai as u128) * (bj as u128) + carry; 478 + t[i + j] = sum as u64; 479 + carry = sum >> 64; 480 + } 481 + // Propagate carry. 482 + let mut k = i + n; 483 + while carry > 0 { 484 + let sum = t[k] as u128 + carry; 485 + t[k] = sum as u64; 486 + carry = sum >> 64; 487 + k += 1; 488 + } 489 + 490 + // Montgomery reduction step. 491 + let m = t[i].wrapping_mul(self.n0_inv); 492 + carry = 0u128; 493 + for j in 0..n { 494 + let sum = t[i + j] as u128 + (m as u128) * (self.n.limbs[j] as u128) + carry; 495 + t[i + j] = sum as u64; 496 + carry = sum >> 64; 497 + } 498 + let mut k = i + n; 499 + while carry > 0 { 500 + let sum = t[k] as u128 + carry; 501 + t[k] = sum as u64; 502 + carry = sum >> 64; 503 + k += 1; 504 + } 505 + } 506 + 507 + // Result is t[n..2n]. 508 + let result_limbs: Vec<u64> = t[n..2 * n + 1].to_vec(); 509 + let mut result = BigUint { 510 + limbs: result_limbs, 511 + }; 512 + result.normalize(); 513 + 514 + // Final subtraction if result >= N. 515 + if result.cmp(&self.n) != Ordering::Less { 516 + result = result.sub(&self.n); 517 + } 518 + 519 + result 520 + } 521 + } 522 + 523 + /// Simple modular exponentiation (for even moduli where Montgomery doesn't work). 524 + fn simple_modpow(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint { 525 + let base_reduced = base.modulo(modulus); 526 + let mut result = BigUint::one(); 527 + let bits = exp.bit_len(); 528 + for i in (0..bits).rev() { 529 + result = result.mul(&result).modulo(modulus); 530 + if exp.bit(i) { 531 + result = result.mul(&base_reduced).modulo(modulus); 532 + } 533 + } 534 + result 535 + } 536 + 537 + /// Modular exponentiation using Montgomery multiplication. 538 + fn montgomery_modpow(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint { 539 + let mont = Montgomery::new(modulus); 540 + 541 + // Convert base to Montgomery form. 542 + let base_reduced = base.modulo(modulus); 543 + let mut result = mont.to_montgomery(&BigUint::one()); // 1 in Montgomery form = R mod N 544 + let base_mont = mont.to_montgomery(&base_reduced); 545 + 546 + // Left-to-right binary method. 547 + let bits = exp.bit_len(); 548 + for i in (0..bits).rev() { 549 + result = mont.montgomery_mul(&result, &result); // square 550 + if exp.bit(i) { 551 + result = mont.montgomery_mul(&result, &base_mont); // multiply 552 + } 553 + } 554 + 555 + mont.reduce(&result) 556 + } 557 + 558 + // --------------------------------------------------------------------------- 559 + // Tests 560 + // --------------------------------------------------------------------------- 561 + 562 + #[cfg(test)] 563 + mod tests { 564 + use super::*; 565 + 566 + fn hex(bytes: &[u8]) -> String { 567 + bytes.iter().map(|b| format!("{b:02x}")).collect() 568 + } 569 + 570 + fn from_hex(s: &str) -> Vec<u8> { 571 + let s = s.replace(' ', ""); 572 + (0..s.len()) 573 + .step_by(2) 574 + .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) 575 + .collect() 576 + } 577 + 578 + #[test] 579 + fn zero_and_one() { 580 + assert!(BigUint::zero().is_zero()); 581 + assert!(!BigUint::one().is_zero()); 582 + assert_eq!(BigUint::zero().bit_len(), 0); 583 + assert_eq!(BigUint::one().bit_len(), 1); 584 + } 585 + 586 + #[test] 587 + fn from_be_bytes_roundtrip() { 588 + let bytes = from_hex("0123456789abcdef"); 589 + let n = BigUint::from_be_bytes(&bytes); 590 + assert_eq!(hex(&n.to_be_bytes()), "0123456789abcdef"); 591 + } 592 + 593 + #[test] 594 + fn from_be_bytes_large() { 595 + // 128-bit number. 596 + let bytes = from_hex("ffffffffffffffffffffffffffffffff"); 597 + let n = BigUint::from_be_bytes(&bytes); 598 + assert_eq!(hex(&n.to_be_bytes()), "ffffffffffffffffffffffffffffffff"); 599 + } 600 + 601 + #[test] 602 + fn addition() { 603 + let a = BigUint::from_be_bytes(&from_hex("ffffffffffffffff")); 604 + let b = BigUint::from_u64(1); 605 + let c = a.add(&b); 606 + assert_eq!(hex(&c.to_be_bytes()), "010000000000000000"); 607 + } 608 + 609 + #[test] 610 + fn subtraction() { 611 + let a = BigUint::from_be_bytes(&from_hex("010000000000000000")); 612 + let b = BigUint::from_u64(1); 613 + let c = a.sub(&b); 614 + assert_eq!(hex(&c.to_be_bytes()), "ffffffffffffffff"); 615 + } 616 + 617 + #[test] 618 + fn multiplication() { 619 + let a = BigUint::from_u64(0xFFFFFFFF); 620 + let b = BigUint::from_u64(0xFFFFFFFF); 621 + let c = a.mul(&b); 622 + // 0xFFFFFFFF * 0xFFFFFFFF = 0xFFFFFFFE00000001 623 + assert_eq!(hex(&c.to_be_bytes()), "fffffffe00000001"); 624 + } 625 + 626 + #[test] 627 + fn division() { 628 + let a = BigUint::from_be_bytes(&from_hex("fffffffe00000001")); 629 + let b = BigUint::from_u64(0xFFFFFFFF); 630 + let (q, r) = a.div_rem(&b); 631 + assert_eq!(hex(&q.to_be_bytes()), "ffffffff"); 632 + assert!(r.is_zero()); 633 + } 634 + 635 + #[test] 636 + fn division_with_remainder() { 637 + let a = BigUint::from_u64(17); 638 + let b = BigUint::from_u64(5); 639 + let (q, r) = a.div_rem(&b); 640 + assert_eq!(q, BigUint::from_u64(3)); 641 + assert_eq!(r, BigUint::from_u64(2)); 642 + } 643 + 644 + #[test] 645 + fn modpow_small() { 646 + // 2^10 mod 1000 = 1024 mod 1000 = 24 647 + let base = BigUint::from_u64(2); 648 + let exp = BigUint::from_u64(10); 649 + let modulus = BigUint::from_u64(1000); 650 + let result = base.modpow(&exp, &modulus); 651 + assert_eq!(result, BigUint::from_u64(24)); 652 + } 653 + 654 + #[test] 655 + fn modpow_fermat() { 656 + // Fermat's little theorem: a^(p-1) ≡ 1 (mod p) for prime p, gcd(a,p)=1. 657 + // p = 65537 (prime), a = 12345. 658 + let a = BigUint::from_u64(12345); 659 + let p = BigUint::from_u64(65537); 660 + let exp = BigUint::from_u64(65536); // p-1 661 + let result = a.modpow(&exp, &p); 662 + assert_eq!(result, BigUint::one()); 663 + } 664 + 665 + #[test] 666 + fn modpow_rsa_sized() { 667 + // Test with a larger modulus to verify it works at scale. 668 + // 3^17 mod 2^128+1 — verify with known computation. 669 + let base = BigUint::from_u64(3); 670 + let exp = BigUint::from_u64(17); 671 + // 2^128 + 1 672 + let mut mod_bytes = vec![0u8; 17]; 673 + mod_bytes[0] = 1; 674 + mod_bytes[16] = 1; 675 + let modulus = BigUint::from_be_bytes(&mod_bytes); 676 + let result = base.modpow(&exp, &modulus); 677 + // 3^17 = 129140163. This is < 2^128+1, so result = 129140163. 678 + assert_eq!(result, BigUint::from_u64(129140163)); 679 + } 680 + 681 + #[test] 682 + fn to_be_bytes_padded() { 683 + let n = BigUint::from_u64(0xFF); 684 + let padded = n.to_be_bytes_padded(4); 685 + assert_eq!(padded, vec![0, 0, 0, 0xFF]); 686 + } 687 + 688 + #[test] 689 + fn comparison() { 690 + let a = BigUint::from_u64(100); 691 + let b = BigUint::from_u64(200); 692 + assert_eq!(a.cmp(&b), Ordering::Less); 693 + assert_eq!(b.cmp(&a), Ordering::Greater); 694 + assert_eq!(a.cmp(&a), Ordering::Equal); 695 + } 696 + 697 + #[test] 698 + fn multi_limb_division() { 699 + // (2^128 - 1) / (2^64 - 1) = 2^64 + 1 700 + let a = BigUint::from_be_bytes(&from_hex("ffffffffffffffffffffffffffffffff")); 701 + let b = BigUint::from_be_bytes(&from_hex("ffffffffffffffff")); 702 + let (q, r) = a.div_rem(&b); 703 + assert_eq!(hex(&q.to_be_bytes()), "010000000000000001"); 704 + assert!(r.is_zero()); 705 + } 706 + 707 + #[test] 708 + fn modpow_256bit() { 709 + // Verify modpow with 256-bit numbers. 710 + // base = 2, exp = 255, mod = 2^256 - 189 (a prime near 2^256) 711 + // 2^255 mod (2^256 - 189) 712 + let base = BigUint::from_u64(2); 713 + let exp = BigUint::from_u64(255); 714 + 715 + // mod = 2^256 - 189 716 + let mut mod_bytes = vec![0xFF; 32]; 717 + // 2^256 - 189 = 0xFFFFFF...FF43 718 + mod_bytes[31] = 0x43; // 0xFF - 188 = 0x43 719 + 720 + let modulus = BigUint::from_be_bytes(&mod_bytes); 721 + let result = base.modpow(&exp, &modulus); 722 + 723 + // Verify: result^2 * 2 should equal 2^256 mod (2^256 - 189) 724 + // 2^256 mod (2^256 - 189) = 189 725 + // So result * 2 mod m should equal... let's just verify result < modulus. 726 + assert_eq!(result.cmp(&modulus), Ordering::Less); 727 + 728 + // Double-check: result should be 2^255. 729 + // 2^255 < 2^256 - 189, so result = 2^255 exactly. 730 + let expected = { 731 + let mut b = vec![0u8; 32]; 732 + b[0] = 0x80; 733 + BigUint::from_be_bytes(&b) 734 + }; 735 + assert_eq!(result, expected); 736 + } 737 + }
+2
crates/crypto/src/lib.rs
··· 2 2 3 3 pub mod aes_gcm; 4 4 pub mod asn1; 5 + pub mod bigint; 5 6 pub mod chacha20_poly1305; 6 7 pub mod hkdf; 7 8 pub mod hmac; 9 + pub mod rsa; 8 10 pub mod sha2; 9 11 pub mod x25519;
+835
crates/crypto/src/rsa.rs
··· 1 + //! RSA PKCS#1 v1.5 signature verification (RFC 8017). 2 + //! 3 + //! Supports RSA key sizes of 2048, 3072, and 4096 bits. Parses public keys 4 + //! from DER-encoded PKCS#1 RSAPublicKey and PKCS#8 SubjectPublicKeyInfo 5 + //! formats. Verifies RSASSA-PKCS1-v1_5 signatures with SHA-256 and SHA-384. 6 + 7 + use crate::asn1::{ 8 + self, Asn1Error, OID_RSA_ENCRYPTION, OID_SHA256, OID_SHA384, OID_SHA512, TAG_NULL, TAG_SEQUENCE, 9 + }; 10 + use crate::bigint::BigUint; 11 + use crate::sha2::{sha256, sha384, sha512}; 12 + 13 + use core::fmt; 14 + 15 + // --------------------------------------------------------------------------- 16 + // Error type 17 + // --------------------------------------------------------------------------- 18 + 19 + #[derive(Debug, Clone, PartialEq, Eq)] 20 + pub enum RsaError { 21 + /// ASN.1 parsing error. 22 + Asn1(Asn1Error), 23 + /// RSA modulus is too small (< 2048 bits). 24 + ModulusTooSmall, 25 + /// RSA modulus is too large (> 4096 bits). 26 + ModulusTooLarge, 27 + /// Public exponent is invalid (not odd, or too small). 28 + InvalidExponent, 29 + /// Signature length doesn't match modulus length. 30 + InvalidSignatureLength, 31 + /// Signature value is >= modulus. 32 + SignatureOutOfRange, 33 + /// EMSA-PKCS1-v1_5 encoding verification failed. 34 + InvalidPadding, 35 + /// DigestInfo doesn't match the expected hash. 36 + DigestMismatch, 37 + /// Unsupported hash algorithm. 38 + UnsupportedAlgorithm, 39 + /// Invalid key format. 40 + InvalidKeyFormat, 41 + } 42 + 43 + impl fmt::Display for RsaError { 44 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 + match self { 46 + Self::Asn1(e) => write!(f, "ASN.1 error: {e}"), 47 + Self::ModulusTooSmall => write!(f, "RSA modulus too small (< 2048 bits)"), 48 + Self::ModulusTooLarge => write!(f, "RSA modulus too large (> 4096 bits)"), 49 + Self::InvalidExponent => write!(f, "invalid RSA public exponent"), 50 + Self::InvalidSignatureLength => write!(f, "signature length doesn't match modulus"), 51 + Self::SignatureOutOfRange => write!(f, "signature value >= modulus"), 52 + Self::InvalidPadding => write!(f, "invalid PKCS#1 v1.5 padding"), 53 + Self::DigestMismatch => write!(f, "digest mismatch"), 54 + Self::UnsupportedAlgorithm => write!(f, "unsupported hash algorithm"), 55 + Self::InvalidKeyFormat => write!(f, "invalid RSA key format"), 56 + } 57 + } 58 + } 59 + 60 + impl From<Asn1Error> for RsaError { 61 + fn from(e: Asn1Error) -> Self { 62 + Self::Asn1(e) 63 + } 64 + } 65 + 66 + pub type Result<T> = core::result::Result<T, RsaError>; 67 + 68 + // --------------------------------------------------------------------------- 69 + // Hash algorithm identifiers 70 + // --------------------------------------------------------------------------- 71 + 72 + /// Hash algorithm used with RSA signature verification. 73 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 74 + pub enum HashAlgorithm { 75 + Sha256, 76 + Sha384, 77 + Sha512, 78 + } 79 + 80 + impl HashAlgorithm { 81 + /// DER-encoded DigestInfo prefix (AlgorithmIdentifier + digest wrapper) 82 + /// per RFC 8017 Section 9.2 Notes. 83 + fn digest_info_prefix(&self) -> &'static [u8] { 84 + match self { 85 + // DigestInfo for SHA-256: 86 + // SEQUENCE { SEQUENCE { OID sha256, NULL }, OCTET STRING (32 bytes) } 87 + Self::Sha256 => &[ 88 + 0x30, 0x31, // SEQUENCE, 49 bytes 89 + 0x30, 0x0d, // SEQUENCE, 13 bytes 90 + 0x06, 0x09, // OID, 9 bytes 91 + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, // sha256 92 + 0x05, 0x00, // NULL 93 + 0x04, 0x20, // OCTET STRING, 32 bytes 94 + ], 95 + // DigestInfo for SHA-384: 96 + Self::Sha384 => &[ 97 + 0x30, 0x41, // SEQUENCE, 65 bytes 98 + 0x30, 0x0d, // SEQUENCE, 13 bytes 99 + 0x06, 0x09, // OID, 9 bytes 100 + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, // sha384 101 + 0x05, 0x00, // NULL 102 + 0x04, 0x30, // OCTET STRING, 48 bytes 103 + ], 104 + // DigestInfo for SHA-512: 105 + Self::Sha512 => &[ 106 + 0x30, 0x51, // SEQUENCE, 81 bytes 107 + 0x30, 0x0d, // SEQUENCE, 13 bytes 108 + 0x06, 0x09, // OID, 9 bytes 109 + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, // sha512 110 + 0x05, 0x00, // NULL 111 + 0x04, 0x40, // OCTET STRING, 64 bytes 112 + ], 113 + } 114 + } 115 + 116 + /// Compute the hash of the given message. 117 + fn hash(&self, msg: &[u8]) -> Vec<u8> { 118 + match self { 119 + Self::Sha256 => sha256(msg).to_vec(), 120 + Self::Sha384 => sha384(msg).to_vec(), 121 + Self::Sha512 => sha512(msg).to_vec(), 122 + } 123 + } 124 + 125 + /// Length of the hash output in bytes. 126 + fn hash_len(&self) -> usize { 127 + match self { 128 + Self::Sha256 => 32, 129 + Self::Sha384 => 48, 130 + Self::Sha512 => 64, 131 + } 132 + } 133 + } 134 + 135 + // --------------------------------------------------------------------------- 136 + // RSA public key 137 + // --------------------------------------------------------------------------- 138 + 139 + /// An RSA public key. 140 + #[derive(Debug, Clone)] 141 + pub struct RsaPublicKey { 142 + /// The modulus n. 143 + pub n: BigUint, 144 + /// The public exponent e. 145 + pub e: BigUint, 146 + /// Key size in bytes (octet length of modulus). 147 + pub key_len: usize, 148 + } 149 + 150 + impl RsaPublicKey { 151 + /// Parse an RSA public key from DER-encoded PKCS#1 RSAPublicKey format. 152 + /// 153 + /// ```asn1 154 + /// RSAPublicKey ::= SEQUENCE { 155 + /// modulus INTEGER, 156 + /// publicExponent INTEGER 157 + /// } 158 + /// ``` 159 + pub fn from_pkcs1_der(data: &[u8]) -> Result<Self> { 160 + let seq = asn1::parse_one(data)?; 161 + if seq.tag != TAG_SEQUENCE { 162 + return Err(RsaError::InvalidKeyFormat); 163 + } 164 + 165 + let items = seq.sequence_items()?; 166 + if items.len() != 2 { 167 + return Err(RsaError::InvalidKeyFormat); 168 + } 169 + 170 + let n_bytes = items[0].as_positive_integer()?; 171 + let e_bytes = items[1].as_positive_integer()?; 172 + 173 + Self::from_components(n_bytes, e_bytes) 174 + } 175 + 176 + /// Parse an RSA public key from DER-encoded PKCS#8 SubjectPublicKeyInfo format. 177 + /// 178 + /// ```asn1 179 + /// SubjectPublicKeyInfo ::= SEQUENCE { 180 + /// algorithm AlgorithmIdentifier, 181 + /// subjectPublicKey BIT STRING 182 + /// } 183 + /// AlgorithmIdentifier ::= SEQUENCE { 184 + /// algorithm OBJECT IDENTIFIER, 185 + /// parameters ANY OPTIONAL 186 + /// } 187 + /// ``` 188 + pub fn from_pkcs8_der(data: &[u8]) -> Result<Self> { 189 + let seq = asn1::parse_one(data)?; 190 + if seq.tag != TAG_SEQUENCE { 191 + return Err(RsaError::InvalidKeyFormat); 192 + } 193 + 194 + let items = seq.sequence_items()?; 195 + if items.len() != 2 { 196 + return Err(RsaError::InvalidKeyFormat); 197 + } 198 + 199 + // Parse AlgorithmIdentifier. 200 + let alg_id = &items[0]; 201 + if alg_id.tag != TAG_SEQUENCE { 202 + return Err(RsaError::InvalidKeyFormat); 203 + } 204 + let alg_items = alg_id.sequence_items()?; 205 + if alg_items.is_empty() { 206 + return Err(RsaError::InvalidKeyFormat); 207 + } 208 + 209 + // Verify the algorithm OID is rsaEncryption. 210 + let oid = alg_items[0].as_oid()?; 211 + if !oid.matches(OID_RSA_ENCRYPTION) { 212 + return Err(RsaError::InvalidKeyFormat); 213 + } 214 + 215 + // The parameters should be NULL (or absent). 216 + if alg_items.len() > 1 && alg_items[1].tag != TAG_NULL { 217 + return Err(RsaError::InvalidKeyFormat); 218 + } 219 + 220 + // Parse the BIT STRING containing the RSAPublicKey. 221 + let (unused_bits, key_data) = items[1].as_bit_string()?; 222 + if unused_bits != 0 { 223 + return Err(RsaError::InvalidKeyFormat); 224 + } 225 + 226 + // The BIT STRING content is a DER-encoded RSAPublicKey. 227 + Self::from_pkcs1_der(key_data) 228 + } 229 + 230 + /// Try to parse from either PKCS#1 or PKCS#8 format. 231 + pub fn from_der(data: &[u8]) -> Result<Self> { 232 + // Try PKCS#8 first (more common in certificates). 233 + if let Ok(key) = Self::from_pkcs8_der(data) { 234 + return Ok(key); 235 + } 236 + // Fall back to PKCS#1. 237 + Self::from_pkcs1_der(data) 238 + } 239 + 240 + /// Create from raw modulus and exponent bytes (big-endian, no leading zeros). 241 + fn from_components(n_bytes: &[u8], e_bytes: &[u8]) -> Result<Self> { 242 + let n = BigUint::from_be_bytes(n_bytes); 243 + let e = BigUint::from_be_bytes(e_bytes); 244 + 245 + let bit_len = n.bit_len(); 246 + if bit_len < 2048 { 247 + return Err(RsaError::ModulusTooSmall); 248 + } 249 + if bit_len > 4096 { 250 + return Err(RsaError::ModulusTooLarge); 251 + } 252 + 253 + // Public exponent must be odd and >= 3. 254 + if e_bytes.is_empty() || !e.bit(0) { 255 + return Err(RsaError::InvalidExponent); 256 + } 257 + 258 + let key_len = bit_len.div_ceil(8); 259 + 260 + Ok(Self { n, e, key_len }) 261 + } 262 + 263 + /// Verify an RSASSA-PKCS1-v1_5 signature. 264 + /// 265 + /// `hash_alg` specifies the hash algorithm used. 266 + /// `message` is the original message that was signed. 267 + /// `signature` is the signature bytes. 268 + pub fn verify_pkcs1v15( 269 + &self, 270 + hash_alg: HashAlgorithm, 271 + message: &[u8], 272 + signature: &[u8], 273 + ) -> Result<()> { 274 + // Step 1: Length check. 275 + if signature.len() != self.key_len { 276 + return Err(RsaError::InvalidSignatureLength); 277 + } 278 + 279 + // Step 2: RSA verification primitive (RSAVP1). 280 + let s = BigUint::from_be_bytes(signature); 281 + if s.cmp(&self.n) != core::cmp::Ordering::Less { 282 + return Err(RsaError::SignatureOutOfRange); 283 + } 284 + 285 + // m = s^e mod n 286 + let m = s.modpow(&self.e, &self.n); 287 + let em = m.to_be_bytes_padded(self.key_len); 288 + 289 + // Step 3: EMSA-PKCS1-v1_5 encoding. 290 + let hash = hash_alg.hash(message); 291 + let expected_em = emsa_pkcs1_v15_encode(&hash, hash_alg, self.key_len)?; 292 + 293 + // Step 4: Compare. 294 + if !constant_time_eq(&em, &expected_em) { 295 + return Err(RsaError::InvalidPadding); 296 + } 297 + 298 + Ok(()) 299 + } 300 + 301 + /// Verify a signature where the hash has already been computed. 302 + /// 303 + /// `hash_alg` specifies which hash algorithm was used. 304 + /// `hash` is the pre-computed hash of the message. 305 + /// `signature` is the signature bytes. 306 + pub fn verify_pkcs1v15_prehashed( 307 + &self, 308 + hash_alg: HashAlgorithm, 309 + hash: &[u8], 310 + signature: &[u8], 311 + ) -> Result<()> { 312 + if hash.len() != hash_alg.hash_len() { 313 + return Err(RsaError::DigestMismatch); 314 + } 315 + 316 + // Step 1: Length check. 317 + if signature.len() != self.key_len { 318 + return Err(RsaError::InvalidSignatureLength); 319 + } 320 + 321 + // Step 2: RSAVP1. 322 + let s = BigUint::from_be_bytes(signature); 323 + if s.cmp(&self.n) != core::cmp::Ordering::Less { 324 + return Err(RsaError::SignatureOutOfRange); 325 + } 326 + 327 + let m = s.modpow(&self.e, &self.n); 328 + let em = m.to_be_bytes_padded(self.key_len); 329 + 330 + // Step 3: EMSA-PKCS1-v1_5 encoding. 331 + let expected_em = emsa_pkcs1_v15_encode(hash, hash_alg, self.key_len)?; 332 + 333 + // Step 4: Compare. 334 + if !constant_time_eq(&em, &expected_em) { 335 + return Err(RsaError::InvalidPadding); 336 + } 337 + 338 + Ok(()) 339 + } 340 + } 341 + 342 + // --------------------------------------------------------------------------- 343 + // EMSA-PKCS1-v1_5 encoding (RFC 8017 Section 9.2) 344 + // --------------------------------------------------------------------------- 345 + 346 + /// Construct EMSA-PKCS1-v1_5 encoded message: 347 + /// EM = 0x00 || 0x01 || PS || 0x00 || DigestInfo 348 + /// 349 + /// where PS is padding of 0xFF bytes, and DigestInfo = prefix || hash. 350 + fn emsa_pkcs1_v15_encode(hash: &[u8], hash_alg: HashAlgorithm, em_len: usize) -> Result<Vec<u8>> { 351 + let prefix = hash_alg.digest_info_prefix(); 352 + let t_len = prefix.len() + hash.len(); 353 + 354 + // em_len must be >= t_len + 11 (0x00 + 0x01 + at least 8 bytes of PS + 0x00 + T) 355 + if em_len < t_len + 11 { 356 + return Err(RsaError::InvalidPadding); 357 + } 358 + 359 + let ps_len = em_len - t_len - 3; 360 + let mut em = Vec::with_capacity(em_len); 361 + em.push(0x00); 362 + em.push(0x01); 363 + em.extend(std::iter::repeat_n(0xFF, ps_len)); 364 + em.push(0x00); 365 + em.extend_from_slice(prefix); 366 + em.extend_from_slice(hash); 367 + 368 + debug_assert_eq!(em.len(), em_len); 369 + Ok(em) 370 + } 371 + 372 + // --------------------------------------------------------------------------- 373 + // Utility: parse algorithm OID from a signature's DigestInfo 374 + // --------------------------------------------------------------------------- 375 + 376 + /// Identify the hash algorithm from a DigestInfo DER encoding. 377 + pub fn parse_digest_info_algorithm(digest_info: &[u8]) -> Result<HashAlgorithm> { 378 + let seq = asn1::parse_one(digest_info)?; 379 + if seq.tag != TAG_SEQUENCE { 380 + return Err(RsaError::InvalidKeyFormat); 381 + } 382 + let items = seq.sequence_items()?; 383 + if items.len() != 2 { 384 + return Err(RsaError::InvalidKeyFormat); 385 + } 386 + 387 + // AlgorithmIdentifier 388 + let alg_id = &items[0]; 389 + if alg_id.tag != TAG_SEQUENCE { 390 + return Err(RsaError::InvalidKeyFormat); 391 + } 392 + let alg_items = alg_id.sequence_items()?; 393 + if alg_items.is_empty() { 394 + return Err(RsaError::InvalidKeyFormat); 395 + } 396 + 397 + let oid = alg_items[0].as_oid()?; 398 + if oid.matches(OID_SHA256) { 399 + Ok(HashAlgorithm::Sha256) 400 + } else if oid.matches(OID_SHA384) { 401 + Ok(HashAlgorithm::Sha384) 402 + } else if oid.matches(OID_SHA512) { 403 + Ok(HashAlgorithm::Sha512) 404 + } else { 405 + Err(RsaError::UnsupportedAlgorithm) 406 + } 407 + } 408 + 409 + // --------------------------------------------------------------------------- 410 + // Constant-time comparison 411 + // --------------------------------------------------------------------------- 412 + 413 + /// Constant-time byte slice comparison to prevent timing attacks. 414 + fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { 415 + if a.len() != b.len() { 416 + return false; 417 + } 418 + let mut diff = 0u8; 419 + for (x, y) in a.iter().zip(b.iter()) { 420 + diff |= x ^ y; 421 + } 422 + diff == 0 423 + } 424 + 425 + // --------------------------------------------------------------------------- 426 + // Tests 427 + // --------------------------------------------------------------------------- 428 + 429 + #[cfg(test)] 430 + mod tests { 431 + use super::*; 432 + 433 + // ----------------------------------------------------------------------- 434 + // Test EMSA-PKCS1-v1_5 encoding 435 + // ----------------------------------------------------------------------- 436 + 437 + #[test] 438 + fn emsa_encoding_sha256() { 439 + let hash = sha256(b"test"); 440 + let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); 441 + 442 + // Check structure: 0x00 0x01 [0xFF padding] 0x00 [DigestInfo] 443 + assert_eq!(em[0], 0x00); 444 + assert_eq!(em[1], 0x01); 445 + 446 + let prefix = HashAlgorithm::Sha256.digest_info_prefix(); 447 + let t_len = prefix.len() + 32; // 19 + 32 = 51 448 + let ps_len = 256 - t_len - 3; // 256 - 51 - 3 = 202 449 + 450 + for i in 2..2 + ps_len { 451 + assert_eq!(em[i], 0xFF, "byte {i} should be 0xFF"); 452 + } 453 + assert_eq!(em[2 + ps_len], 0x00); 454 + assert_eq!(&em[3 + ps_len..3 + ps_len + prefix.len()], prefix); 455 + assert_eq!(&em[3 + ps_len + prefix.len()..], &hash[..]); 456 + } 457 + 458 + #[test] 459 + fn emsa_encoding_too_short() { 460 + let hash = sha256(b"test"); 461 + // em_len too small. 462 + let result = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 50); 463 + assert!(result.is_err()); 464 + } 465 + 466 + // ----------------------------------------------------------------------- 467 + // DER construction helpers 468 + // ----------------------------------------------------------------------- 469 + 470 + /// Encode a DER length. 471 + fn der_length(len: usize) -> Vec<u8> { 472 + if len < 0x80 { 473 + vec![len as u8] 474 + } else if len < 0x100 { 475 + vec![0x81, len as u8] 476 + } else { 477 + vec![0x82, (len >> 8) as u8, len as u8] 478 + } 479 + } 480 + 481 + /// Build a DER TLV (tag + length + value). 482 + fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> { 483 + let mut out = vec![tag]; 484 + out.extend_from_slice(&der_length(value.len())); 485 + out.extend_from_slice(value); 486 + out 487 + } 488 + 489 + /// Build a DER INTEGER from unsigned big-endian bytes (adds leading 0 if needed). 490 + fn der_integer(bytes: &[u8]) -> Vec<u8> { 491 + let needs_pad = !bytes.is_empty() && bytes[0] & 0x80 != 0; 492 + let mut value = Vec::new(); 493 + if needs_pad { 494 + value.push(0x00); 495 + } 496 + value.extend_from_slice(bytes); 497 + der_tlv(0x02, &value) 498 + } 499 + 500 + /// Build a PKCS#1 RSAPublicKey DER from modulus and exponent bytes. 501 + fn build_pkcs1_key(modulus: &[u8], exponent: &[u8]) -> Vec<u8> { 502 + let n_int = der_integer(modulus); 503 + let e_int = der_integer(exponent); 504 + let mut content = Vec::new(); 505 + content.extend_from_slice(&n_int); 506 + content.extend_from_slice(&e_int); 507 + der_tlv(0x30, &content) 508 + } 509 + 510 + /// Build a PKCS#8 SubjectPublicKeyInfo DER for RSA. 511 + fn build_pkcs8_key(modulus: &[u8], exponent: &[u8]) -> Vec<u8> { 512 + // AlgorithmIdentifier: SEQUENCE { OID rsaEncryption, NULL } 513 + let oid = der_tlv(0x06, OID_RSA_ENCRYPTION); 514 + let null = vec![0x05, 0x00]; 515 + let mut alg_content = Vec::new(); 516 + alg_content.extend_from_slice(&oid); 517 + alg_content.extend_from_slice(&null); 518 + let alg_id = der_tlv(0x30, &alg_content); 519 + 520 + // BIT STRING containing RSAPublicKey 521 + let rsa_pub_key = build_pkcs1_key(modulus, exponent); 522 + let mut bit_string_value = vec![0x00]; // unused bits = 0 523 + bit_string_value.extend_from_slice(&rsa_pub_key); 524 + let bit_string = der_tlv(0x03, &bit_string_value); 525 + 526 + let mut content = Vec::new(); 527 + content.extend_from_slice(&alg_id); 528 + content.extend_from_slice(&bit_string); 529 + der_tlv(0x30, &content) 530 + } 531 + 532 + // ----------------------------------------------------------------------- 533 + // Test RSA public key parsing 534 + // ----------------------------------------------------------------------- 535 + 536 + #[test] 537 + fn parse_pkcs1_key() { 538 + // 2048-bit modulus (256 bytes, top bit set). 539 + let mut modulus = vec![0x80; 256]; 540 + modulus[255] = 0x01; // make it odd 541 + let exponent = vec![0x01, 0x00, 0x01]; // 65537 542 + 543 + let key_der = build_pkcs1_key(&modulus, &exponent); 544 + let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); 545 + assert!(key.n.bit_len() >= 2048); 546 + assert_eq!(key.e, BigUint::from_u64(65537)); 547 + } 548 + 549 + #[test] 550 + fn parse_pkcs8_key() { 551 + let mut modulus = vec![0x80; 256]; 552 + modulus[255] = 0x01; 553 + let exponent = vec![0x01, 0x00, 0x01]; 554 + 555 + let key_der = build_pkcs8_key(&modulus, &exponent); 556 + let key = RsaPublicKey::from_pkcs8_der(&key_der).unwrap(); 557 + assert!(key.n.bit_len() >= 2048); 558 + assert_eq!(key.e, BigUint::from_u64(65537)); 559 + } 560 + 561 + #[test] 562 + fn parse_from_der_auto_detect() { 563 + let mut modulus = vec![0x80; 256]; 564 + modulus[255] = 0x01; 565 + let exponent = vec![0x01, 0x00, 0x01]; 566 + 567 + // PKCS#8 auto-detect. 568 + let pkcs8 = build_pkcs8_key(&modulus, &exponent); 569 + let key = RsaPublicKey::from_der(&pkcs8).unwrap(); 570 + assert_eq!(key.e, BigUint::from_u64(65537)); 571 + 572 + // PKCS#1 auto-detect. 573 + let pkcs1 = build_pkcs1_key(&modulus, &exponent); 574 + let key = RsaPublicKey::from_der(&pkcs1).unwrap(); 575 + assert_eq!(key.e, BigUint::from_u64(65537)); 576 + } 577 + 578 + #[test] 579 + fn reject_small_modulus() { 580 + let modulus = vec![0xFF; 128]; // 1024-bit 581 + let exponent = vec![0x01, 0x00, 0x01]; 582 + let key_der = build_pkcs1_key(&modulus, &exponent); 583 + let result = RsaPublicKey::from_pkcs1_der(&key_der); 584 + assert_eq!(result.unwrap_err(), RsaError::ModulusTooSmall); 585 + } 586 + 587 + #[test] 588 + fn reject_even_exponent() { 589 + let mut modulus = vec![0xFF; 256]; 590 + modulus[255] = 0xFB; 591 + let exponent = vec![0x02]; // even 592 + let key_der = build_pkcs1_key(&modulus, &exponent); 593 + let result = RsaPublicKey::from_pkcs1_der(&key_der); 594 + assert_eq!(result.unwrap_err(), RsaError::InvalidExponent); 595 + } 596 + 597 + // ----------------------------------------------------------------------- 598 + // End-to-end signature verification test 599 + // ----------------------------------------------------------------------- 600 + 601 + #[test] 602 + fn verify_pkcs1v15_sha256_constructed() { 603 + // Construct a test case using known values. 604 + // We create a valid EMSA-PKCS1-v1_5 encoded message and verify the 605 + // mathematical relationship holds: sig^e mod n == EM. 606 + // 607 + // Use small-ish but valid RSA parameters for a fast test. 608 + // For a real test we'd use a 2048-bit key, but we test the encoding logic 609 + // with a constructed example. 610 + 611 + let message = b"hello world"; 612 + let hash = sha256(message); 613 + 614 + // Verify EMSA encoding is deterministic. 615 + let em1 = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); 616 + let em2 = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); 617 + assert_eq!(em1, em2); 618 + } 619 + 620 + #[test] 621 + fn verify_signature_wrong_length() { 622 + let mut modulus = vec![0x80; 256]; 623 + modulus[255] = 0x01; 624 + let exponent = vec![0x01, 0x00, 0x01]; 625 + let key_der = build_pkcs1_key(&modulus, &exponent); 626 + let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); 627 + 628 + // Wrong signature length. 629 + let sig = vec![0u8; 128]; // should be 256 630 + let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test", &sig); 631 + assert_eq!(result.unwrap_err(), RsaError::InvalidSignatureLength); 632 + } 633 + 634 + #[test] 635 + fn verify_signature_out_of_range() { 636 + let mut modulus = vec![0x80; 256]; 637 + modulus[255] = 0x01; 638 + let exponent = vec![0x01, 0x00, 0x01]; 639 + let key_der = build_pkcs1_key(&modulus, &exponent); 640 + let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); 641 + 642 + // Signature >= modulus. 643 + let sig = vec![0xFF; 256]; 644 + let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test", &sig); 645 + assert_eq!(result.unwrap_err(), RsaError::SignatureOutOfRange); 646 + } 647 + 648 + #[test] 649 + fn constant_time_eq_works() { 650 + assert!(constant_time_eq(b"hello", b"hello")); 651 + assert!(!constant_time_eq(b"hello", b"world")); 652 + assert!(!constant_time_eq(b"hello", b"hell")); 653 + assert!(constant_time_eq(b"", b"")); 654 + } 655 + 656 + #[test] 657 + fn emsa_encoding_sha384() { 658 + let hash = sha384(b"test"); 659 + let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha384, 256).unwrap(); 660 + 661 + assert_eq!(em[0], 0x00); 662 + assert_eq!(em[1], 0x01); 663 + 664 + let prefix = HashAlgorithm::Sha384.digest_info_prefix(); 665 + let t_len = prefix.len() + 48; 666 + let ps_len = 256 - t_len - 3; 667 + 668 + for i in 2..2 + ps_len { 669 + assert_eq!(em[i], 0xFF); 670 + } 671 + assert_eq!(em[2 + ps_len], 0x00); 672 + assert_eq!(&em[3 + ps_len..3 + ps_len + prefix.len()], prefix); 673 + assert_eq!(&em[3 + ps_len + prefix.len()..], &hash[..]); 674 + } 675 + 676 + #[test] 677 + fn emsa_encoding_sha512() { 678 + let hash = sha512(b"test"); 679 + let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha512, 256).unwrap(); 680 + 681 + assert_eq!(em[0], 0x00); 682 + assert_eq!(em[1], 0x01); 683 + 684 + let prefix = HashAlgorithm::Sha512.digest_info_prefix(); 685 + let t_len = prefix.len() + 64; 686 + let ps_len = 256 - t_len - 3; 687 + 688 + for i in 2..2 + ps_len { 689 + assert_eq!(em[i], 0xFF); 690 + } 691 + assert_eq!(em[2 + ps_len], 0x00); 692 + } 693 + 694 + #[test] 695 + fn parse_digest_info_sha256() { 696 + // Build a DigestInfo for SHA-256. 697 + let hash = [0u8; 32]; 698 + let mut digest_info = Vec::new(); 699 + let prefix = HashAlgorithm::Sha256.digest_info_prefix(); 700 + // The prefix includes the outer SEQUENCE header; build the full DigestInfo. 701 + digest_info.extend_from_slice(prefix); 702 + digest_info.extend_from_slice(&hash); 703 + // Actually, the prefix already has the outer SEQUENCE, so we need to 704 + // parse just the prefix part up to (but not including) the OCTET STRING value. 705 + // For parse_digest_info_algorithm we need the full DigestInfo as a SEQUENCE. 706 + // Let's build it properly: 707 + let mut di = Vec::new(); 708 + di.push(0x30); // SEQUENCE 709 + di.push(0x31); // length = 49 710 + di.push(0x30); // SEQUENCE (AlgorithmIdentifier) 711 + di.push(0x0d); // length = 13 712 + di.push(0x06); // OID 713 + di.push(0x09); // length = 9 714 + di.extend_from_slice(OID_SHA256); 715 + di.push(0x05); // NULL 716 + di.push(0x00); 717 + di.push(0x04); // OCTET STRING 718 + di.push(0x20); // length = 32 719 + di.extend_from_slice(&hash); 720 + 721 + let alg = parse_digest_info_algorithm(&di).unwrap(); 722 + assert_eq!(alg, HashAlgorithm::Sha256); 723 + } 724 + 725 + #[test] 726 + fn parse_digest_info_sha384() { 727 + let hash = [0u8; 48]; 728 + let mut di = Vec::new(); 729 + di.push(0x30); // SEQUENCE 730 + di.push(0x41); // length = 65 731 + di.push(0x30); // SEQUENCE (AlgorithmIdentifier) 732 + di.push(0x0d); // length = 13 733 + di.push(0x06); // OID 734 + di.push(0x09); // length = 9 735 + di.extend_from_slice(OID_SHA384); 736 + di.push(0x05); // NULL 737 + di.push(0x00); 738 + di.push(0x04); // OCTET STRING 739 + di.push(0x30); // length = 48 740 + di.extend_from_slice(&hash); 741 + 742 + let alg = parse_digest_info_algorithm(&di).unwrap(); 743 + assert_eq!(alg, HashAlgorithm::Sha384); 744 + } 745 + 746 + // ----------------------------------------------------------------------- 747 + // Signature rejection tests with PKCS#8 key 748 + // ----------------------------------------------------------------------- 749 + 750 + #[test] 751 + fn verify_invalid_signature_rejected() { 752 + let mut modulus = vec![0x80; 256]; 753 + modulus[255] = 0x01; 754 + let exponent = vec![0x01, 0x00, 0x01]; 755 + let key_der = build_pkcs8_key(&modulus, &exponent); 756 + let key = RsaPublicKey::from_pkcs8_der(&key_der).unwrap(); 757 + 758 + // Random signature should fail (invalid padding after modpow). 759 + let mut sig = vec![0x42; 256]; 760 + sig[0] = 0x01; // Ensure < modulus 761 + let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test message", &sig); 762 + assert!(result.is_err()); 763 + } 764 + 765 + #[test] 766 + fn verify_tampered_signature_rejected() { 767 + let mut modulus = vec![0x80; 256]; 768 + modulus[255] = 0x01; 769 + let exponent = vec![0x01, 0x00, 0x01]; 770 + let key_der = build_pkcs8_key(&modulus, &exponent); 771 + let key = RsaPublicKey::from_pkcs8_der(&key_der).unwrap(); 772 + 773 + // All-zero signature should fail. 774 + let sig = vec![0u8; 256]; 775 + let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test", &sig); 776 + assert!(result.is_err()); 777 + 778 + // Signature of all 0x01 should fail. 779 + let sig = vec![0x01; 256]; 780 + let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"test", &sig); 781 + assert!(result.is_err()); 782 + } 783 + 784 + // ----------------------------------------------------------------------- 785 + // Mathematical verification test 786 + // ----------------------------------------------------------------------- 787 + // 788 + // We construct a scenario where we know sig^e mod n == EM, by computing 789 + // EM first and setting sig = EM (with e=1, which is degenerate but tests 790 + // the EMSA encoding path). 791 + 792 + #[test] 793 + fn verify_with_exponent_one() { 794 + // With e=1, sig^1 mod n = sig (if sig < n). 795 + // So if sig == EMSA(msg), verification passes. 796 + let mut mod_bytes = vec![0xFF; 256]; 797 + mod_bytes[255] = 0xFD; // make it odd 798 + let exponent = vec![0x01]; // e=1 799 + 800 + let key_der = build_pkcs1_key(&mod_bytes, &exponent); 801 + let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); 802 + 803 + // Construct the expected EM for "hello" with SHA-256. 804 + let hash = sha256(b"hello"); 805 + let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); 806 + 807 + // With e=1, sig = EM should verify. 808 + let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"hello", &em); 809 + assert!(result.is_ok(), "verification should pass: {result:?}"); 810 + 811 + // Wrong message should fail. 812 + let result = key.verify_pkcs1v15(HashAlgorithm::Sha256, b"world", &em); 813 + assert!(result.is_err()); 814 + } 815 + 816 + #[test] 817 + fn verify_prehashed() { 818 + let mut mod_bytes = vec![0xFF; 256]; 819 + mod_bytes[255] = 0xFD; 820 + let exponent = vec![0x01]; // e=1 821 + 822 + let key_der = build_pkcs1_key(&mod_bytes, &exponent); 823 + let key = RsaPublicKey::from_pkcs1_der(&key_der).unwrap(); 824 + 825 + let hash = sha256(b"hello"); 826 + let em = emsa_pkcs1_v15_encode(&hash, HashAlgorithm::Sha256, 256).unwrap(); 827 + 828 + let result = key.verify_pkcs1v15_prehashed(HashAlgorithm::Sha256, &hash, &em); 829 + assert!(result.is_ok()); 830 + 831 + // Wrong hash length should fail. 832 + let result = key.verify_pkcs1v15_prehashed(HashAlgorithm::Sha256, &hash[..16], &em); 833 + assert_eq!(result.unwrap_err(), RsaError::DigestMismatch); 834 + } 835 + }