Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/security/lockstore/lockstore_rs/tests/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 17 kB image not shown  

Quelle  password_tests.rs

  Sprache: Rust
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


//! Tests for the multi-instance Password `KekType`.
//!
//! Each test mints its own Password kek_ref via the test-only
//! single-iteration helper so tests don't pay the ~100 ms PBKDF2 cost.
//! Production code always goes through `create_kek(KekType::Password,
//! ...)`, which uses `pbkdf2::PBKDF2_ITERATIONS` (800 000).

use lockstore_rs::{KekType, Keystore, LockstoreError};
use std::sync::Arc;
use std::thread::sleep;
use std::time::Duration;
use tempfile::tempdir;

const PW: &[u8] = b"correct horse battery staple";
const PW_WRONG: &[u8] = b"Tr0ub4dor&3";
const PW_NEW: &[u8] = b"gs5^&mR2!fb@1";

fn on_disk_keystore() -> (Arc<Keystore>, tempfile::TempDir) {
    let dir = tempdir().expect("tempdir");
    let path = dir.path().join("lockstore.keys.sqlite");
    let ks = Keystore::get(path).expect("new");
    (ks, dir)
}

fn mint_local(ks: &Keystore) -> String {
    ks.create_kek(KekType::LocalKey, "", b"", Duration::ZERO)
        .expect("create local")
}

#[test]
fn create_password_kek_persists_record() {
    let ks = Keystore::new_in_memory().expect("new");
    let kek_ref = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    assert!(kek_ref.starts_with("lockstore::kek::password:"));
    // Newly-minted KEK starts locked: createKek doesn't open the cache.
    assert!(!ks.is_kek_unlocked(&kek_ref).unwrap());
}

#[test]
fn unlock_then_get_dek_succeeds() {
    let ks = Keystore::new_in_memory().expect("new");
    let kek_ref = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&kek_ref, PW, Duration::from_secs(60))
        .expect("unlock");
    assert!(ks.is_kek_unlocked(&kek_ref).unwrap());

    ks.create_dek("col", &kek_ref, true).expect("create_dek");
    let (dek, _cs) = ks.get_dek("col", &kek_ref).expect("get_dek");
    assert_eq!(dek.len(), 32);
}

#[test]
fn get_dek_when_locked_fails() {
    let ks = Keystore::new_in_memory().expect("new");
    let kek_ref = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&kek_ref, PW, Duration::from_secs(60))
        .expect("unlock");
    ks.create_dek("col", &kek_ref, true).expect("create_dek");

    ks.lock_kek(&kek_ref).unwrap();
    assert!(!ks.is_kek_unlocked(&kek_ref).unwrap());
    let err = ks.get_dek("col", &kek_ref).unwrap_err();
    assert!(matches!(err, LockstoreError::Locked), "got: {:?}", err);
}

#[test]
fn unlock_expires_after_timeout() {
    let ks = Keystore::new_in_memory().expect("new");
    let local = mint_local(&ks);
    let pw = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.create_dek("col", &local, true).expect("create_dek");
    ks.unlock_kek(&pw, PW, Duration::from_millis(100))
        .expect("unlock");
    ks.add_kek("col", &local, &pw).expect("add Password level");

    sleep(Duration::from_millis(200));
    assert!(!ks.is_kek_unlocked(&pw).unwrap());
    let err = ks.get_dek("col", &pw).unwrap_err();
    assert!(matches!(err, LockstoreError::Locked), "got: {:?}", err);

    // LocalKey access still works after the Password kek expires.
    ks.get_dek("col", &local).expect("local still ok");
}

#[test]
fn wrong_password_returns_wrong_password_and_does_not_cache() {
    let ks = Keystore::new_in_memory().expect("new");
    let kek_ref = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    let err = ks
        .unlock_kek(&kek_ref, PW_WRONG, Duration::from_secs(60))
        .unwrap_err();
    assert!(matches!(err, LockstoreError::WrongPassword));
    assert!(!ks.is_kek_unlocked(&kek_ref).unwrap());
}

#[test]
fn unlock_unknown_kek_ref_reports_invalid_kek_ref() {
    let ks = Keystore::new_in_memory().expect("new");
    let err = ks
        .unlock_kek(
            "lockstore::kek::password:not-a-real-id",
            PW,
            Duration::from_secs(60),
        )
        .unwrap_err();
    assert!(matches!(err, LockstoreError::InvalidKekRef(_)));
}

#[test]
fn two_independent_password_keks_unlock_independently() {
    let ks = Keystore::new_in_memory().expect("new");
    let kek_a = ks.create_password_kek_test_only(PW).expect("create A");
    let kek_b = ks.create_password_kek_test_only(PW_NEW).expect("create B");
    assert_ne!(kek_a, kek_b);

    ks.unlock_kek(&kek_a, PW, Duration::from_secs(60))
        .expect("unlock A");
    assert!(ks.is_kek_unlocked(&kek_a).unwrap());
    // B is unaffected by A's unlock.
    assert!(!ks.is_kek_unlocked(&kek_b).unwrap());

    ks.unlock_kek(&kek_b, PW_NEW, Duration::from_secs(60))
        .expect("unlock B");
    assert!(ks.is_kek_unlocked(&kek_b).unwrap());

    // Wrong password against the other kek is rejected even when both
    // are otherwise unlocked.
    let err = ks
        .unlock_kek(&kek_a, PW_NEW, Duration::from_secs(60))
        .unwrap_err();
    assert!(matches!(err, LockstoreError::WrongPassword));

    // lock_kek only affects the named kek_ref.
    ks.lock_kek(&kek_a).unwrap();
    assert!(!ks.is_kek_unlocked(&kek_a).unwrap());
    assert!(ks.is_kek_unlocked(&kek_b).unwrap());
}

#[test]
fn add_then_remove_local_level_leaves_password_only() {
    let ks = Keystore::new_in_memory().expect("new");
    let local = mint_local(&ks);
    let pw = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&pw, PW, Duration::from_secs(60))
        .expect("unlock");

    ks.create_dek("c", &local, true).expect("create");
    ks.add_kek("c", &local, &pw).expect("add");
    ks.remove_kek("c", &local).expect("remove");

    let err = ks.get_dek("c", &local).unwrap_err();
    assert!(matches!(err, LockstoreError::NotFound(_)));

    ks.get_dek("c", &pw).expect("password ok");

    ks.lock_kek(&pw).unwrap();
    let err = ks.get_dek("c", &pw).unwrap_err();
    assert!(matches!(err, LockstoreError::Locked));
}

#[test]
fn reopen_on_disk_keystore_requires_unlock() {
    let dir = tempdir().expect("tempdir");
    let path = dir.path().join("lockstore.keys.sqlite");

    let kek_ref;
    {
        let ks = Keystore::get(path.clone()).expect("new");
        kek_ref = ks
            .create_password_kek_test_only(PW)
            .expect("create password");
        ks.unlock_kek(&kek_ref, PW, Duration::from_secs(60))
            .expect("unlock");
        ks.create_dek("persisted", &kek_ref, true).expect("create");
        ks.close();
    }

    let ks2 = Keystore::get(path).expect("reopen");
    assert!(!ks2.is_kek_unlocked(&kek_ref).unwrap());
    let err = ks2.get_dek("persisted", &kek_ref).unwrap_err();
    assert!(matches!(err, LockstoreError::Locked));

    ks2.unlock_kek(&kek_ref, PW, Duration::from_secs(60))
        .expect("unlock reopened");
    ks2.get_dek("persisted", &kek_ref)
        .expect("get after reopen+unlock");
}

#[test]
fn close_locks_password() {
    let (ks, _dir) = on_disk_keystore();
    let kek_ref = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&kek_ref, PW, Duration::from_secs(3600))
        .expect("unlock");
    assert!(ks.is_kek_unlocked(&kek_ref).unwrap());
    ks.close();
    // A fresh keystore at the same path must not inherit the unlocked
    // state (covered by `reopen_on_disk_keystore_requires_unlock`).
}

#[test]
fn encrypt_decrypt_roundtrip_local() {
    let ks = Keystore::new_in_memory().expect("new");
    let local = mint_local(&ks);
    ks.create_dek("c", &local, false).expect("create");

    let plaintext = b"hello, lockstore";
    let blob = ks.encrypt("c", &local, plaintext).expect("encrypt");
    assert_ne!(blob, plaintext);
    let round = ks.decrypt("c", &local, &blob).expect("decrypt");
    assert_eq!(round, plaintext);
}

#[test]
fn encrypt_decrypt_roundtrip_password() {
    let ks = Keystore::new_in_memory().expect("new");
    let kek_ref = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&kek_ref, PW, Duration::from_secs(60))
        .expect("unlock");
    ks.create_dek("c", &kek_ref, false).expect("create");

    let plaintext = b"secret";
    let blob = ks.encrypt("c", &kek_ref, plaintext).expect("encrypt");
    let round = ks.decrypt("c", &kek_ref, &blob).expect("decrypt");
    assert_eq!(round, plaintext);

    ks.lock_kek(&kek_ref).unwrap();
    let err = ks.encrypt("c", &kek_ref, plaintext).unwrap_err();
    assert!(matches!(err, LockstoreError::Locked));
    let err = ks.decrypt("c", &kek_ref, &blob).unwrap_err();
    assert!(matches!(err, LockstoreError::Locked));
}

#[test]
fn encrypt_non_extractable_dek_still_works() {
    let ks = Keystore::new_in_memory().expect("new");
    let local = mint_local(&ks);
    ks.create_dek("c", &local, false).expect("create");
    // get_dek rejects because not extractable...
    let err = ks.get_dek("c", &local).unwrap_err();
    assert!(matches!(err, LockstoreError::NotExtractable(_)));
    // ...but encrypt/decrypt bypass the extractability gate.
    let blob = ks.encrypt("c", &local, b"abc").expect("encrypt");
    let round = ks.decrypt("c", &local, &blob).expect("decrypt");
    assert_eq!(round, b"abc");
}

#[test]
fn password_dek_supports_non_extractable() {
    let ks = Keystore::new_in_memory().expect("new");
    let kek_ref = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&kek_ref, PW, Duration::from_secs(60))
        .expect("unlock");

    ks.create_dek("nonex", &kek_ref, false)
        .expect("create_dek non-extractable");

    let err = ks.get_dek("nonex", &kek_ref).unwrap_err();
    assert!(
        matches!(err, LockstoreError::NotExtractable(_)),
        "expected NotExtractable, got {:?}",
        err
    );

    let pt = b"Password-bound payload";
    let ct = ks
        .encrypt("nonex", &kek_ref, pt)
        .expect("encrypt under non-extractable Password DEK");
    let pt2 = ks
        .decrypt("nonex", &kek_ref, &ct)
        .expect("decrypt under non-extractable Password DEK");
    assert_eq!(pt2, pt);
}

#[test]
fn local_key_is_always_unlocked() {
    let ks = Keystore::new_in_memory().expect("new");
    let local = mint_local(&ks);
    assert!(ks.is_kek_unlocked(&local).unwrap());
    // lock / unlock are no-ops for LocalKey.
    ks.lock_kek(&local).unwrap();
    assert!(ks.is_kek_unlocked(&local).unwrap());
    ks.unlock_kek(&local, b"", Duration::from_secs(1))
        .expect("unlock no-op");
    assert!(ks.is_kek_unlocked(&local).unwrap());
}

#[test]
fn malformed_kek_ref_reports_invalid_kek_ref() {
    let ks = Keystore::new_in_memory().expect("new");
    // Every entry point that parses a kek_ref surfaces a malformed one
    // as `InvalidKekRef`: is_kek_unlocked, lock_kek, and unlock_kek.
    assert!(matches!(
        ks.is_kek_unlocked("lockstore::kek::bogus"),
        Err(LockstoreError::InvalidKekRef(_))
    ));
    assert!(matches!(
        ks.lock_kek("lockstore::kek::bogus"),
        Err(LockstoreError::InvalidKekRef(_))
    ));
    let err = ks
        .unlock_kek("lockstore::kek::bogus", b"x", Duration::from_secs(1))
        .unwrap_err();
    assert!(matches!(err, LockstoreError::InvalidKekRef(_)));
}

#[test]
fn lock_clears_password_cache() {
    let ks = Keystore::new_in_memory().expect("new");
    let kek_ref = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&kek_ref, PW, Duration::from_secs(60))
        .expect("unlock");
    assert!(ks.is_kek_unlocked(&kek_ref).unwrap());

    ks.lock().unwrap();
    assert!(!ks.is_kek_unlocked(&kek_ref).unwrap());
}

#[test]
fn remove_kek_leaves_password_record_intact() {
    let ks = Keystore::new_in_memory().expect("new");
    let local = mint_local(&ks);
    let pw = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&pw, PW, Duration::from_secs(60))
        .expect("unlock");

    ks.create_dek("col", &pw, false).expect("create_dek");
    ks.add_kek("col", &pw, &local).expect("add local");

    ks.remove_kek("col", &pw).expect("remove password kek");

    // remove_kek drops the wrapping only; the Password record persists
    // on disk until the caller explicitly invokes delete_kek.
    ks.unlock_kek(&pw, PW, Duration::from_secs(60))
        .expect("password KEK still resolvable after remove_kek");
    assert!(ks.is_kek_unlocked(&pw).unwrap());
}

#[test]
fn delete_dek_leaves_password_record_intact() {
    let ks = Keystore::new_in_memory().expect("new");
    let pw = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&pw, PW, Duration::from_secs(60))
        .expect("unlock");
    ks.create_dek("only", &pw, false).expect("create_dek");

    ks.delete_dek("only").expect("delete_dek");

    // delete_dek wipes the DEK metadata; the per-kek_ref Password
    // record stays on disk and remains usable until the caller invokes
    // delete_kek for it explicitly.
    ks.unlock_kek(&pw, PW, Duration::from_secs(60))
        .expect("password KEK still resolvable after delete_dek");
    assert!(ks.is_kek_unlocked(&pw).unwrap());
}

// ---------------------------------------------------------------------------
// delete_kek
// ---------------------------------------------------------------------------

#[test]
fn delete_kek_drops_unreferenced_password_kek() {
    let ks = Keystore::new_in_memory().expect("new");
    let pw = ks
        .create_password_kek_test_only(PW)
        .expect("create password");

    // Just-minted, no wrapping yet — delete must succeed and the
    // record must be gone.
    ks.delete_kek(&pw).expect("delete unreferenced kek");

    let err = ks.unlock_kek(&pw, PW, Duration::from_secs(60)).unwrap_err();
    assert!(
        matches!(err, LockstoreError::InvalidKekRef(_)),
        "expected InvalidKekRef after delete_kek, got {:?}",
        err
    );
}

#[test]
fn delete_kek_rejects_when_in_use() {
    let ks = Keystore::new_in_memory().expect("new");
    let pw = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&pw, PW, Duration::from_secs(60))
        .expect("unlock");
    ks.create_dek("col", &pw, false).expect("create_dek");

    let err = ks.delete_kek(&pw).unwrap_err();
    assert!(
        matches!(err, LockstoreError::InvalidConfiguration(_)),
        "expected InvalidConfiguration for in-use kek, got {:?}",
        err
    );

    // Sanity: the record is still usable after the rejected delete.
    let _ = ks.encrypt("col", &pw, b"still here").expect("encrypt");
}

#[test]
fn delete_kek_unknown_kek_ref_reports_not_found() {
    let ks = Keystore::new_in_memory().expect("new");
    let bogus = "lockstore::kek::password:not-a-real-id";
    let err = ks.delete_kek(bogus).unwrap_err();
    assert!(
        matches!(err, LockstoreError::NotFound(_)),
        "expected NotFound for missing kek_ref, got {:?}",
        err
    );
}

#[test]
fn delete_kek_invalid_kek_ref_format_reports_invalid_kek_ref() {
    let ks = Keystore::new_in_memory().expect("new");
    let err = ks.delete_kek("not-a-lockstore-kek-ref").unwrap_err();
    assert!(
        matches!(err, LockstoreError::InvalidKekRef(_)),
        "expected InvalidKekRef for malformed kek_ref, got {:?}",
        err
    );
}

#[test]
fn delete_kek_drops_unreferenced_local_kek() {
    let ks = Keystore::new_in_memory().expect("new");
    let local = mint_local(&ks);

    ks.delete_kek(&local)
        .expect("delete unreferenced local kek");

    // A subsequent encrypt under the deleted local kek must fail
    // because the record is gone — get_kek_symkey returns NotFound.
    ks.create_dek(
        "col",
        // Re-use a different local kek_ref so the create_dek itself
        // succeeds; the assertion is that the deleted ref no longer
        // resolves.
        &mint_local(&ks),
        false,
    )
    .expect("create_dek under a fresh local");
    let err = ks.encrypt("col", &local, b"x").unwrap_err();
    assert!(
        matches!(err, LockstoreError::NotFound(_)),
        "expected NotFound after delete_kek on local, got {:?}",
        err
    );
}

#[test]
fn delete_kek_after_switch_kek_succeeds() {
    let ks = Keystore::new_in_memory().expect("new");
    let pw = ks
        .create_password_kek_test_only(PW)
        .expect("create password");
    ks.unlock_kek(&pw, PW, Duration::from_secs(60))
        .expect("unlock");
    ks.create_dek("col", &pw, false).expect("create_dek");

    // Switch the collection's wrapping to a new password kek.
    let pw2 = ks
        .create_password_kek_test_only(PW_NEW)
        .expect("create password 2");
    ks.unlock_kek(&pw2, PW_NEW, Duration::from_secs(60))
        .expect("unlock pw2");
    ks.switch_kek("col", &pw, &pw2).expect("switch_kek");

    // After switch_kek, `pw` no longer wraps any collection but its
    // record is still on disk. delete_kek can drop it cleanly because
    // no collection references it any more.
    ks.delete_kek(&pw).expect("delete unreferenced pw");

    // Subsequent unlock surfaces InvalidKekRef: the kek_ref parses,
    // but no Password record exists at this id.
    let err = ks.unlock_kek(&pw, PW, Duration::from_secs(60)).unwrap_err();
    assert!(
        matches!(err, LockstoreError::InvalidKekRef(_)),
        "expected InvalidKekRef after delete_kek, got {:?}",
        err
    );
}

Messung V0.5 in Prozent
C=88 H=96 G=91

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.