1use ed25519_dalek::{Signature, VerifyingKey};
5use prost::Message;
6use sha2::{Digest, Sha256};
7
8use crate::heddle::api::v1alpha2::{
9 AuthenticationChallenge, CompleteAuthenticationRequest, PasswordChallengeMetadata,
10 PasswordChallengeProof, PasswordDeviceAdmission, PasswordOwnerEnvelopeV1, PasswordOwnerSetup,
11 PasswordOwnerSetupAuthorization, PasswordUnlockContinuation, SignedPasswordDeviceAdmission,
12 SignedPasswordOwnerSetupAuthorization,
13};
14
15pub const MAX_PASSWORD_ENVELOPE_BYTES: usize = 4096;
16pub const MAX_PASSWORD_SETUP_BYTES: usize = 4608;
17pub const PASSWORD_ARGON2_MEMORY_KIB: u32 = 65_536;
18pub const PASSWORD_ARGON2_ITERATIONS: u32 = 3;
19pub const PASSWORD_ARGON2_PARALLELISM: u32 = 4;
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
22pub enum PasswordOwnerError {
23 #[error("unsupported password owner format or KDF")]
24 Version,
25 #[error("unsupported password Argon2id costs")]
26 KdfCosts,
27 #[error("invalid password owner field length")]
28 Length,
29 #[error("password owner envelope is oversized or noncanonical")]
30 EnvelopeEncoding,
31 #[error("password owner field binding mismatch")]
32 Binding,
33 #[error("invalid password verifier or owner signature")]
34 Signature,
35}
36
37fn cost(memory: u32, iterations: u32, parallelism: u32) -> Result<(), PasswordOwnerError> {
38 if (memory, iterations, parallelism)
39 != (
40 PASSWORD_ARGON2_MEMORY_KIB,
41 PASSWORD_ARGON2_ITERATIONS,
42 PASSWORD_ARGON2_PARALLELISM,
43 )
44 {
45 return Err(PasswordOwnerError::KdfCosts);
46 }
47 Ok(())
48}
49
50fn size(value: &[u8], expected: usize) -> Result<(), PasswordOwnerError> {
51 if value.len() != expected {
52 return Err(PasswordOwnerError::Length);
53 }
54 Ok(())
55}
56
57fn public_key(value: &[u8]) -> Result<VerifyingKey, PasswordOwnerError> {
58 size(value, 32)?;
59 let bytes: &[u8; 32] = value.try_into().map_err(|_| PasswordOwnerError::Length)?;
60 let mut y = *bytes;
61 y[31] &= 0x7f;
62 let mut prime = [0xff; 32];
63 prime[0] = 0xed;
64 prime[31] = 0x7f;
65 for index in (0..32).rev() {
66 if y[index] < prime[index] {
67 break;
68 }
69 if y[index] > prime[index] || index == 0 {
70 return Err(PasswordOwnerError::Signature);
71 }
72 }
73 let key = VerifyingKey::from_bytes(bytes).map_err(|_| PasswordOwnerError::Signature)?;
74 if key.is_weak() {
75 return Err(PasswordOwnerError::Signature);
76 }
77 Ok(key)
78}
79
80fn operation_id(value: &str) -> Result<(), PasswordOwnerError> {
81 if value.is_empty() || value.len() > 128 {
82 return Err(PasswordOwnerError::Length);
83 }
84 Ok(())
85}
86
87pub fn validate_password_owner_envelope(
89 value: &PasswordOwnerEnvelopeV1,
90) -> Result<(), PasswordOwnerError> {
91 if value.format_version != 1 || value.kdf_id != 1 {
92 return Err(PasswordOwnerError::Version);
93 }
94 cost(value.memory_kib, value.iterations, value.parallelism)?;
95 size(&value.account_uuid, 16)?;
96 if value.account_uuid.iter().all(|byte| *byte == 0) {
97 return Err(PasswordOwnerError::Binding);
98 }
99 public_key(&value.owner_public_key)?;
100 size(&value.owner_id, 32)?;
101 size(&value.wrap_salt, 16)?;
102 size(&value.nonce, 12)?;
103 size(&value.ciphertext_and_tag, 48)?;
104 if value.encoded_len() > MAX_PASSWORD_ENVELOPE_BYTES {
105 return Err(PasswordOwnerError::EnvelopeEncoding);
106 }
107 Ok(())
108}
109
110pub fn decode_password_owner_envelope_canonical(
112 raw: &[u8],
113) -> Result<PasswordOwnerEnvelopeV1, PasswordOwnerError> {
114 if raw.len() > MAX_PASSWORD_ENVELOPE_BYTES {
115 return Err(PasswordOwnerError::EnvelopeEncoding);
116 }
117 let value =
118 PasswordOwnerEnvelopeV1::decode(raw).map_err(|_| PasswordOwnerError::EnvelopeEncoding)?;
119 validate_password_owner_envelope(&value)?;
120 Ok(value)
121}
122
123pub fn password_owner_wrap_aad(
126 value: &PasswordOwnerEnvelopeV1,
127) -> Result<Vec<u8>, PasswordOwnerError> {
128 validate_password_owner_envelope(value)?;
129 let mut aad = Vec::with_capacity(28 + 4 + 16 + 32 + 32 + 4 * 4 + 16);
130 aad.extend_from_slice(b"heddle-owner-wrap-aad-v1\0");
131 aad.extend_from_slice(&value.format_version.to_be_bytes());
132 aad.extend_from_slice(&value.account_uuid);
133 aad.extend_from_slice(&value.owner_public_key);
134 aad.extend_from_slice(&value.owner_id);
135 aad.extend_from_slice(&value.kdf_id.to_be_bytes());
136 aad.extend_from_slice(&value.memory_kib.to_be_bytes());
137 aad.extend_from_slice(&value.iterations.to_be_bytes());
138 aad.extend_from_slice(&value.parallelism.to_be_bytes());
139 aad.extend_from_slice(&value.wrap_salt);
140 Ok(aad)
141}
142
143fn validate_password_owner_setup_fields(
144 value: &PasswordOwnerSetup,
145) -> Result<(), PasswordOwnerError> {
146 let envelope = value.envelope.as_ref().ok_or(PasswordOwnerError::Length)?;
147 validate_password_owner_envelope(envelope)?;
148 cost(
149 value.auth_memory_kib,
150 value.auth_iterations,
151 value.auth_parallelism,
152 )?;
153 size(&value.auth_salt, 16)?;
154 public_key(&value.auth_verifier_public_key)?;
155 if value.format_version != 1 || value.auth_kdf_id != 1 {
156 return Err(PasswordOwnerError::Version);
157 }
158 if value.auth_salt == envelope.wrap_salt {
159 return Err(PasswordOwnerError::Binding);
160 }
161 if value.encoded_len() > MAX_PASSWORD_SETUP_BYTES {
162 return Err(PasswordOwnerError::EnvelopeEncoding);
163 }
164 Ok(())
165}
166
167pub fn validate_password_owner_setup(value: &PasswordOwnerSetup) -> Result<(), PasswordOwnerError> {
168 validate_password_owner_setup_fields(value)?;
169 size(&value.auth_verifier_possession_signature, 64)
170}
171
172pub fn decode_password_owner_setup_canonical(
174 raw: &[u8],
175) -> Result<PasswordOwnerSetup, PasswordOwnerError> {
176 if raw.len() > MAX_PASSWORD_SETUP_BYTES {
177 return Err(PasswordOwnerError::EnvelopeEncoding);
178 }
179 let value =
180 PasswordOwnerSetup::decode(raw).map_err(|_| PasswordOwnerError::EnvelopeEncoding)?;
181 validate_password_owner_setup(&value)?;
182 Ok(value)
183}
184
185pub fn validate_password_owner_setup_binding(
187 setup: &PasswordOwnerSetup,
188 account_uuid: &[u8],
189 owner_public_key: &[u8],
190 owner_id: &[u8],
191) -> Result<(), PasswordOwnerError> {
192 validate_password_owner_setup(setup)?;
193 let envelope = setup.envelope.as_ref().ok_or(PasswordOwnerError::Binding)?;
194 public_key(owner_public_key)?;
195 size(account_uuid, 16)?;
196 size(owner_id, 32)?;
197 if envelope.account_uuid != account_uuid
198 || envelope.owner_public_key != owner_public_key
199 || envelope.owner_id != owner_id
200 {
201 return Err(PasswordOwnerError::Binding);
202 }
203 Ok(())
204}
205
206pub fn password_registration_verifier_possession_digest(
208 challenge: &[u8],
209) -> Result<[u8; 32], PasswordOwnerError> {
210 size(challenge, 32)?;
211 let mut hash = Sha256::new();
212 hash.update(b"heddle-password-verifier-possession-v1");
213 hash.update(challenge);
214 Ok(hash.finalize().into())
215}
216
217pub fn verify_password_auth_verifier_possession(
219 setup: &PasswordOwnerSetup,
220 digest: &[u8; 32],
221) -> Result<(), PasswordOwnerError> {
222 validate_password_owner_setup(setup)?;
223 let signature: &[u8; 64] = setup
224 .auth_verifier_possession_signature
225 .as_slice()
226 .try_into()
227 .map_err(|_| PasswordOwnerError::Length)?;
228 public_key(&signature[..32])?;
229 public_key(&setup.auth_verifier_public_key)?
230 .verify_strict(digest, &Signature::from_bytes(signature))
231 .map_err(|_| PasswordOwnerError::Signature)
232}
233
234pub fn verify_password_auth_verifier_registration_possession(
236 setup: &PasswordOwnerSetup,
237 challenge: &[u8],
238) -> Result<(), PasswordOwnerError> {
239 let digest = password_registration_verifier_possession_digest(challenge)?;
240 verify_password_auth_verifier_possession(setup, &digest)
241}
242
243pub fn next_password_envelope_revision(
245 current: Option<u64>,
246 expected: u64,
247) -> Result<u64, PasswordOwnerError> {
248 let actual = current.unwrap_or(0);
249 if expected != actual || (current.is_some() && actual == 0) {
250 return Err(PasswordOwnerError::Binding);
251 }
252 actual.checked_add(1).ok_or(PasswordOwnerError::Binding)
253}
254
255pub fn password_mint_attachment_nonce(
257 challenge_nonce: &[u8],
258 continuation_id: &[u8],
259) -> Result<[u8; 32], PasswordOwnerError> {
260 size(challenge_nonce, 32)?;
261 size(continuation_id, 32)?;
262 let mut hash = Sha256::new();
263 hash.update(b"heddle-password-mint-nonce-v1");
264 hash.update(challenge_nonce);
265 hash.update(continuation_id);
266 Ok(hash.finalize().into())
267}
268
269pub fn validate_password_completion_bindings(
273 request: &CompleteAuthenticationRequest,
274 challenge: &AuthenticationChallenge,
275 continuation: &PasswordUnlockContinuation,
276 bound_device_key: &[u8],
277) -> Result<(), PasswordOwnerError> {
278 let password_challenge = challenge
279 .password_challenge
280 .as_ref()
281 .ok_or(PasswordOwnerError::Binding)?;
282 let completion = match request.proof.as_ref() {
283 Some(
284 crate::heddle::api::v1alpha2::complete_authentication_request::Proof::PasswordUnlock(
285 value,
286 ),
287 ) => value,
288 _ => return Err(PasswordOwnerError::Binding),
289 };
290 let admission = completion
291 .owner_admission
292 .as_ref()
293 .and_then(|signed| signed.admission.as_ref())
294 .ok_or(PasswordOwnerError::Binding)?;
295 let attachment = completion
296 .mint_root_attachment
297 .as_ref()
298 .and_then(|signed| signed.attachment.as_ref())
299 .ok_or(PasswordOwnerError::Binding)?;
300 let envelope = continuation
301 .envelope
302 .as_ref()
303 .ok_or(PasswordOwnerError::Binding)?;
304 let credential_expiry = challenge
305 .credential_expires_at
306 .as_ref()
307 .ok_or(PasswordOwnerError::Binding)?;
308 size(bound_device_key, 32)?;
309 if challenge.method != 2
310 || request.challenge.is_none()
311 || challenge.r#ref.is_none()
312 || request.challenge != challenge.r#ref
313 || !request.enroll_device
314 || !request.ephemeral_public_key.is_empty()
315 || completion.continuation_id != continuation.continuation_id
316 || admission.challenge_id != password_challenge.challenge_id
317 || admission.continuation_id != continuation.continuation_id
318 || admission.account_uuid != envelope.account_uuid
319 || admission.caller_device_public_key != request.caller_public_key
320 || admission.caller_device_public_key != bound_device_key
321 || admission.client_operation_id != request.client_operation_id
322 || admission.owner_state_hash != attachment.owner_state_hash
323 || admission.owner_sequence != attachment.owner_sequence
324 || admission.account_uuid != attachment.account_uuid
325 || attachment.mint_root_key.as_ref().is_none_or(|key| {
326 key.public_key != admission.caller_device_public_key || key.algorithm != 1
327 })
328 || attachment.nonce
329 != password_mint_attachment_nonce(
330 &password_challenge.nonce,
331 &continuation.continuation_id,
332 )?
333 || continuation.envelope_revision == 0
334 || attachment.expires_at_unix_seconds > credential_expiry.seconds
335 {
336 return Err(PasswordOwnerError::Binding);
337 }
338 Ok(())
339}
340
341pub fn validate_password_challenge_metadata(
342 value: &PasswordChallengeMetadata,
343) -> Result<(), PasswordOwnerError> {
344 cost(
345 value.auth_memory_kib,
346 value.auth_iterations,
347 value.auth_parallelism,
348 )?;
349 if value.auth_kdf_id != 1 || value.format_version != 1 {
350 return Err(PasswordOwnerError::Version);
351 }
352 size(&value.auth_salt, 16)?;
353 size(&value.challenge_id, 32)?;
354 size(&value.nonce, 32)?;
355 Ok(())
356}
357
358pub fn password_challenge_signing_digest(
360 challenge: &PasswordChallengeMetadata,
361 proof: &PasswordChallengeProof,
362 operation: &str,
363 expiry_unix_seconds: i64,
364) -> Result<[u8; 32], PasswordOwnerError> {
365 validate_password_challenge_metadata(challenge)?;
366 operation_id(operation)?;
367 size(&proof.caller_device_public_key, 32)?;
368 if proof.challenge_id != challenge.challenge_id {
369 return Err(PasswordOwnerError::Binding);
370 }
371 let mut canonical = Vec::with_capacity(32 + 32 + 32 + 4 + operation.len() + 8);
372 canonical.extend_from_slice(&challenge.challenge_id);
373 canonical.extend_from_slice(&challenge.nonce);
374 canonical.extend_from_slice(&proof.caller_device_public_key);
375 canonical.extend_from_slice(&(operation.len() as u32).to_be_bytes());
376 canonical.extend_from_slice(operation.as_bytes());
377 canonical.extend_from_slice(&expiry_unix_seconds.to_be_bytes());
378 let mut hash = Sha256::new();
379 hash.update(b"heddle-password-proof-v1");
380 hash.update(canonical);
381 Ok(hash.finalize().into())
382}
383
384pub fn verify_password_challenge_signature(
387 challenge: &PasswordChallengeMetadata,
388 proof: &PasswordChallengeProof,
389 operation: &str,
390 expiry_unix_seconds: i64,
391 verifier_public_key: &[u8],
392) -> Result<(), PasswordOwnerError> {
393 size(&proof.signature, 64)?;
394 size(verifier_public_key, 32)?;
395 let digest =
396 password_challenge_signing_digest(challenge, proof, operation, expiry_unix_seconds)?;
397 let signature: &[u8; 64] = proof
398 .signature
399 .as_slice()
400 .try_into()
401 .map_err(|_| PasswordOwnerError::Length)?;
402 public_key(&signature[..32])?;
403 public_key(verifier_public_key)?
404 .verify_strict(&digest, &Signature::from_bytes(signature))
405 .map_err(|_| PasswordOwnerError::Signature)
406}
407
408pub fn password_device_admission_digest(
411 value: &PasswordDeviceAdmission,
412) -> Result<[u8; 32], PasswordOwnerError> {
413 if value.format_version != 1 {
414 return Err(PasswordOwnerError::Version);
415 }
416 operation_id(&value.client_operation_id)?;
417 size(&value.account_uuid, 16)?;
418 if value.account_uuid.iter().all(|byte| *byte == 0) {
419 return Err(PasswordOwnerError::Binding);
420 }
421 size(&value.challenge_id, 32)?;
422 size(&value.continuation_id, 32)?;
423 size(&value.caller_device_public_key, 32)?;
424 size(&value.owner_state_hash, 32)?;
425 let mut hash = Sha256::new();
426 hash.update(b"heddle-password-device-admission-v1");
427 hash.update(value.format_version.to_be_bytes());
428 hash.update(&value.account_uuid);
429 hash.update(&value.challenge_id);
430 hash.update(&value.continuation_id);
431 hash.update(&value.caller_device_public_key);
432 hash.update(&value.owner_state_hash);
433 hash.update(value.owner_sequence.to_be_bytes());
434 hash.update((value.client_operation_id.len() as u32).to_be_bytes());
435 hash.update(value.client_operation_id.as_bytes());
436 Ok(hash.finalize().into())
437}
438
439pub fn verify_password_device_admission_signature(
443 signed: &SignedPasswordDeviceAdmission,
444 current_owner_public_key: &[u8],
445) -> Result<(), PasswordOwnerError> {
446 let statement = signed
447 .admission
448 .as_ref()
449 .ok_or(PasswordOwnerError::Binding)?;
450 let signature = signed
451 .owner_signature
452 .as_ref()
453 .ok_or(PasswordOwnerError::Signature)?;
454 let digest = password_device_admission_digest(statement)?;
455 verify_owner_signature(&digest, signature, current_owner_public_key)
456}
457
458pub fn password_owner_setup_digest(
461 value: &PasswordOwnerSetup,
462) -> Result<[u8; 32], PasswordOwnerError> {
463 validate_password_owner_setup_fields(value)?;
464 let envelope = value.envelope.as_ref().ok_or(PasswordOwnerError::Length)?;
465 let mut hash = Sha256::new();
466 hash.update(envelope.format_version.to_be_bytes());
467 hash.update(&envelope.account_uuid);
468 hash.update(&envelope.owner_public_key);
469 hash.update(&envelope.owner_id);
470 hash.update(envelope.kdf_id.to_be_bytes());
471 hash.update(envelope.memory_kib.to_be_bytes());
472 hash.update(envelope.iterations.to_be_bytes());
473 hash.update(envelope.parallelism.to_be_bytes());
474 hash.update(&envelope.wrap_salt);
475 hash.update(&envelope.nonce);
476 hash.update(&envelope.ciphertext_and_tag);
477 hash.update(&value.auth_salt);
478 hash.update(&value.auth_verifier_public_key);
479 hash.update(value.auth_memory_kib.to_be_bytes());
480 hash.update(value.auth_iterations.to_be_bytes());
481 hash.update(value.auth_parallelism.to_be_bytes());
482 hash.update(value.auth_kdf_id.to_be_bytes());
483 hash.update(value.format_version.to_be_bytes());
484 Ok(hash.finalize().into())
485}
486
487pub fn password_owner_setup_authorization_digest(
490 value: &PasswordOwnerSetupAuthorization,
491 setup: Option<&PasswordOwnerSetup>,
492) -> Result<[u8; 32], PasswordOwnerError> {
493 if value.format_version != 1 {
494 return Err(PasswordOwnerError::Version);
495 }
496 operation_id(&value.client_operation_id)?;
497 size(&value.account_uuid, 16)?;
498 size(&value.owner_state_hash, 32)?;
499 size(&value.setup_sha256, 32)?;
500 if value.account_uuid.iter().all(|byte| *byte == 0) {
501 return Err(PasswordOwnerError::Binding);
502 }
503 let expected_digest = match (value.action, setup) {
504 (1, Some(setup)) => {
505 let envelope = setup.envelope.as_ref().ok_or(PasswordOwnerError::Length)?;
506 if envelope.account_uuid != value.account_uuid {
507 return Err(PasswordOwnerError::Binding);
508 }
509 password_owner_setup_digest(setup)?
510 }
511 (2, None) => [0; 32],
512 _ => return Err(PasswordOwnerError::Binding),
513 };
514 if value.setup_sha256 != expected_digest {
515 return Err(PasswordOwnerError::Binding);
516 }
517 let expiry = value
518 .expires_at
519 .as_ref()
520 .ok_or(PasswordOwnerError::Binding)?;
521 if expiry.nanos != 0 || expiry.seconds <= 0 {
522 return Err(PasswordOwnerError::Binding);
523 }
524 let mut hash = Sha256::new();
525 hash.update(b"heddle-password-owner-setup-change-v1");
526 hash.update(value.format_version.to_be_bytes());
527 hash.update(value.action.to_be_bytes());
528 hash.update(&value.account_uuid);
529 hash.update(&value.owner_state_hash);
530 hash.update(value.expected_revision.to_be_bytes());
531 hash.update(&value.setup_sha256);
532 hash.update((value.client_operation_id.len() as u32).to_be_bytes());
533 hash.update(value.client_operation_id.as_bytes());
534 hash.update(expiry.seconds.to_be_bytes());
535 Ok(hash.finalize().into())
536}
537
538pub fn validate_password_owner_setup_authorization_expiry(
540 value: &PasswordOwnerSetupAuthorization,
541 now_unix_seconds: i64,
542) -> Result<(), PasswordOwnerError> {
543 let expiry = value
544 .expires_at
545 .as_ref()
546 .ok_or(PasswordOwnerError::Binding)?;
547 let latest = now_unix_seconds
548 .checked_add(600)
549 .ok_or(PasswordOwnerError::Binding)?;
550 if expiry.nanos != 0 || expiry.seconds <= now_unix_seconds || expiry.seconds > latest {
551 return Err(PasswordOwnerError::Binding);
552 }
553 Ok(())
554}
555
556pub fn verify_password_owner_setup_authorization_signature(
559 signed: &SignedPasswordOwnerSetupAuthorization,
560 setup: Option<&PasswordOwnerSetup>,
561 current_owner_public_key: &[u8],
562) -> Result<(), PasswordOwnerError> {
563 let statement = signed
564 .authorization
565 .as_ref()
566 .ok_or(PasswordOwnerError::Binding)?;
567 let signature = signed
568 .owner_signature
569 .as_ref()
570 .ok_or(PasswordOwnerError::Signature)?;
571 let digest = password_owner_setup_authorization_digest(statement, setup)?;
572 if let Some(setup) = setup {
573 let envelope = setup.envelope.as_ref().ok_or(PasswordOwnerError::Binding)?;
574 if envelope.owner_public_key != current_owner_public_key {
575 return Err(PasswordOwnerError::Binding);
576 }
577 verify_password_auth_verifier_possession(setup, &digest)?;
578 }
579 verify_owner_signature(&digest, signature, current_owner_public_key)
580}
581
582fn verify_owner_signature(
583 digest: &[u8; 32],
584 signature: &crate::heddle::api::v1alpha2::AuthorizationSignature,
585 current_owner_public_key: &[u8],
586) -> Result<(), PasswordOwnerError> {
587 size(current_owner_public_key, 32)?;
588 size(&signature.signer_key_id, 32)?;
589 size(&signature.signature, 64)?;
590 let mut key_id = Sha256::new();
591 key_id.update(b"heddle-key-v1");
592 key_id.update(1_u32.to_be_bytes());
593 key_id.update(current_owner_public_key);
594 if signature.signer_key_id != key_id.finalize().as_slice() {
595 return Err(PasswordOwnerError::Signature);
596 }
597 let signature: &[u8; 64] = signature
598 .signature
599 .as_slice()
600 .try_into()
601 .map_err(|_| PasswordOwnerError::Length)?;
602 public_key(&signature[..32])?;
603 public_key(current_owner_public_key)?
604 .verify_strict(digest, &Signature::from_bytes(signature))
605 .map_err(|_| PasswordOwnerError::Signature)
606}