| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
- //! `provision keygen` contract: fresh P-256 key, `0600` file, stable
- //! thumbprint, and no silent overwrite of an in-use key.
- use std::path::PathBuf;
- use provision::keygen::{DEFAULT_DPOP_KEY_PATH, generate_key};
- fn unique_dir(tag: &str) -> PathBuf {
- let dir = std::env::temp_dir().join(format!("provision-keygen-{}-{tag}", std::process::id()));
- std::fs::create_dir_all(&dir).expect("test tempdir must be creatable");
- dir
- }
- #[test]
- fn keygen_writes_0600_reloads_with_stable_thumbprint() {
- let dir = unique_dir("roundtrip");
- let path = dir.join("dpop-key.json");
- let first_jkt = generate_key(&path, false).expect("first keygen must succeed");
- assert!(path.exists(), "key file must exist after keygen");
- let loaded =
- oauth_core::dpop::key::DpopKey::load_from_file(&path).expect("generated file must reload");
- assert_eq!(loaded.thumbprint().expect("thumbprint"), first_jkt);
- let raw = std::fs::read_to_string(&path).expect("key file must be readable");
- let value: serde_json::Value = serde_json::from_str(&raw).expect("key file must be JSON");
- assert_eq!(value.get("kty").and_then(|v| v.as_str()), Some("EC"));
- assert!(value.get("d").is_some(), "persisted JWK carries the scalar");
- #[cfg(unix)]
- {
- use std::os::unix::fs::PermissionsExt;
- let mode = std::fs::metadata(&path)
- .expect("metadata")
- .permissions()
- .mode()
- & 0o777;
- assert_eq!(mode, 0o600, "DPoP key file must be owner-only");
- }
- std::fs::remove_file(&path).expect("cleanup");
- std::fs::remove_dir(&dir).expect("cleanup");
- }
- #[test]
- fn keygen_refuses_overwrite_without_force_and_rotates_with_force() {
- let dir = unique_dir("overwrite");
- let path = dir.join("dpop-key.json");
- let first_jkt = generate_key(&path, false).expect("first keygen must succeed");
- let before = std::fs::read(&path).expect("key file must be readable");
- let err = generate_key(&path, false).expect_err("second keygen must fail");
- assert!(
- matches!(err, provision::keygen::KeygenError::Exists { .. }),
- "rerun without --force must be a typed Exists error"
- );
- let kept = std::fs::read(&path).expect("key file must still be readable");
- assert_eq!(kept, before, "refused rerun must leave the key untouched");
- let second_jkt = generate_key(&path, true).expect("--force must rotate");
- assert_ne!(first_jkt, second_jkt, "forced rerun must mint a fresh key");
- let rotated =
- oauth_core::dpop::key::DpopKey::load_from_file(&path).expect("rotated file must reload");
- assert_eq!(rotated.thumbprint().expect("thumbprint"), second_jkt);
- std::fs::remove_file(&path).expect("cleanup");
- std::fs::remove_dir(&dir).expect("cleanup");
- }
- #[test]
- fn default_dpop_key_path_matches_server_convention() {
- assert_eq!(DEFAULT_DPOP_KEY_PATH, "config/dpop-key.json");
- }
|