diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b19f5b62..bba25200 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,8 @@ jobs: - name: Cache cargo resources uses: Swatinem/rust-cache@v1 + with: + sharedKey: deps - name: Cargo check uses: actions-rs/cargo@v1 @@ -59,6 +61,47 @@ jobs: command: test args: --workspace + test-postgres: + name: Postgres + runs-on: ubuntu-latest + needs: [check] + + services: + postgres: + image: postgres + env: + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + + - name: Cache cargo resources + uses: Swatinem/rust-cache@v1 + with: + sharedKey: deps + + - name: Test + uses: actions-rs/cargo@v1 + env: + POSTGRES_URL: postgres://postgres:postgres@localhost:5432/test-db + with: + command: test + args: --features pg_test + build-manylinux: name: Build Library needs: [check] @@ -85,6 +128,8 @@ jobs: - name: Cache cargo resources uses: Swatinem/rust-cache@v1 + with: + sharedKey: deps - name: Build library env: @@ -126,6 +171,8 @@ jobs: - name: Cache cargo resources uses: Swatinem/rust-cache@v1 + with: + sharedKey: deps - name: Build library env: @@ -183,6 +230,7 @@ jobs: python setup.py bdist_wheel --python-tag=py3 --plat-name=${{ matrix.plat-name }} pip install pytest pytest-asyncio dist/* python -m pytest + TEST_STORE_URI=sqlite://test.db python -m pytest working-directory: wrappers/python - if: "runner.os == 'Linux'" diff --git a/Cargo.toml b/Cargo.toml index 9f8e4e04..125ea7c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["askar-crypto"] [package] name = "aries-askar" -version = "0.2.1" +version = "0.2.2" authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar secure storage" diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index e7d2142b..cd657554 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askar-crypto" -version = "0.2.1" +version = "0.2.2" authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar cryptography" diff --git a/src/backend/db_utils.rs b/src/backend/db_utils.rs index fc7bba50..97bb94a9 100644 --- a/src/backend/db_utils.rs +++ b/src/backend/db_utils.rs @@ -609,6 +609,13 @@ pub fn init_keys<'a>( method: StoreKeyMethod, pass_key: PassKey<'a>, ) -> Result<(ProfileKey, Vec, StoreKey, String), Error> { + if method == StoreKeyMethod::RawKey && pass_key.is_empty() { + // disallow random key for a new database + return Err(err_msg!( + Input, + "Cannot create a store with a blank raw key" + )); + } let (store_key, store_key_ref) = method.resolve(pass_key)?; let profile_key = ProfileKey::new()?; let enc_profile_key = encode_profile_key(&profile_key, &store_key)?; diff --git a/src/backend/postgres/mod.rs b/src/backend/postgres/mod.rs index 8e945af0..2723da5c 100644 --- a/src/backend/postgres/mod.rs +++ b/src/backend/postgres/mod.rs @@ -104,20 +104,25 @@ impl Backend for PostgresStore { fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { let name = name.unwrap_or_else(random_profile_name); Box::pin(async move { - let key = ProfileKey::new()?; - let enc_key = key.to_bytes()?; + let store_key = self.key_cache.store_key.clone(); + let (profile_key, enc_key) = unblock(move || { + let profile_key = ProfileKey::new()?; + let enc_key = encode_profile_key(&profile_key, &store_key)?; + Result::<_, Error>::Ok((profile_key, enc_key)) + }) + .await?; let mut conn = self.conn_pool.acquire().await?; if let Some(pid) = sqlx::query_scalar( "INSERT INTO profiles (name, profile_key) VALUES ($1, $2) ON CONFLICT DO NOTHING RETURNING id", ) .bind(&name) - .bind(enc_key.as_ref()) + .bind(enc_key) .fetch_optional(&mut conn) .await? { self.key_cache - .add_profile(name.clone(), pid, Arc::new(key)) + .add_profile(name.clone(), pid, Arc::new(profile_key)) .await; Ok(name) } else { @@ -585,7 +590,7 @@ async fn resolve_profile_key( if let Some((pid, key)) = cache.get_profile(profile.as_str()).await { Ok((pid, key)) } else { - if let Some(row) = sqlx::query("SELECT id, profile_key FROM profiles WHERE name=?1") + if let Some(row) = sqlx::query("SELECT id, profile_key FROM profiles WHERE name=$1") .bind(profile.as_str()) .fetch_optional(conn) .await? diff --git a/src/backend/postgres/test_db.rs b/src/backend/postgres/test_db.rs index c719f260..d6f98a5c 100644 --- a/src/backend/postgres/test_db.rs +++ b/src/backend/postgres/test_db.rs @@ -11,7 +11,7 @@ use super::PostgresStore; use crate::{ backend::db_utils::{init_keys, random_profile_name}, error::Error, - future::{block_on, sleep, timeout, unblock}, + future::{sleep, spawn_ok, timeout, unblock}, protect::{generate_raw_store_key, KeyCache, StoreKeyMethod}, storage::Store, }; @@ -94,12 +94,20 @@ impl std::ops::Deref for TestDB { impl Drop for TestDB { fn drop(&mut self) { if let Some(lock_txn) = self.lock_txn.take() { - block_on(lock_txn.close()).expect("Error closing database connection"); + spawn_ok(async { + lock_txn + .close() + .await + .expect("Error closing database connection"); + }); } if let Some(inst) = self.inst.take() { - block_on(timeout(Duration::from_secs(30), inst.close())) - .expect("Timed out waiting for the pool connection to close") - .expect("Error closing connection pool"); + spawn_ok(async { + timeout(Duration::from_secs(30), inst.close()) + .await + .expect("Timed out waiting for the pool connection to close") + .expect("Error closing connection pool"); + }); } } } diff --git a/src/backend/sqlite/mod.rs b/src/backend/sqlite/mod.rs index 5df97183..c34b65be 100644 --- a/src/backend/sqlite/mod.rs +++ b/src/backend/sqlite/mod.rs @@ -100,20 +100,29 @@ impl Backend for SqliteStore { fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { let name = name.unwrap_or_else(random_profile_name); Box::pin(async move { - let key = ProfileKey::new()?; - let enc_key = key.to_bytes()?; + let store_key = self.key_cache.store_key.clone(); + let (profile_key, enc_key) = unblock(move || { + let profile_key = ProfileKey::new()?; + let enc_key = encode_profile_key(&profile_key, &store_key)?; + Result::<_, Error>::Ok((profile_key, enc_key)) + }) + .await?; let mut conn = self.conn_pool.acquire().await?; let done = sqlx::query("INSERT OR IGNORE INTO profiles (name, profile_key) VALUES (?1, ?2)") .bind(&name) - .bind(enc_key.as_ref()) + .bind(enc_key) .execute(&mut conn) .await?; if done.rows_affected() == 0 { return Err(err_msg!(Duplicate, "Duplicate profile name")); } self.key_cache - .add_profile(name.clone(), done.last_insert_rowid(), Arc::new(key)) + .add_profile( + name.clone(), + done.last_insert_rowid(), + Arc::new(profile_key), + ) .await; Ok(name) }) diff --git a/tests/docker_pg.sh b/tests/docker_pg.sh new file mode 100755 index 00000000..4f3a0f1f --- /dev/null +++ b/tests/docker_pg.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh +docker run --rm -p 5432:5432 --name aries-test-postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres +if [ $? != "0" ]; then + echo "Error starting postgres container" + exit 1 +fi +echo POSTGRES_URL=postgres://postgres:mysecretpassword@localhost:5432/test-db cargo test --features pg_test diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index 9f92fbae..263f3d3f 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -271,7 +271,7 @@ def __init__(self, handle: StoreHandle, uri: str): @classmethod def generate_raw_key(cls, seed: Union[str, bytes] = None) -> str: """Generate a new raw key for a Store.""" - bindings.generate_raw_key(seed) + return bindings.generate_raw_key(seed) @property def handle(self) -> StoreHandle: diff --git a/wrappers/python/aries_askar/version.py b/wrappers/python/aries_askar/version.py index 88439fd2..2a0a134c 100644 --- a/wrappers/python/aries_askar/version.py +++ b/wrappers/python/aries_askar/version.py @@ -1,3 +1,3 @@ """aries_askar library wrapper version.""" -__version__ = "0.2.1" +__version__ = "0.2.2" diff --git a/wrappers/python/tests/test_store.py b/wrappers/python/tests/test_store.py index de26f371..391e93c5 100644 --- a/wrappers/python/tests/test_store.py +++ b/wrappers/python/tests/test_store.py @@ -18,9 +18,13 @@ } +def raw_key() -> str: + return Store.generate_raw_key(b"00000000000000000000000000000My1") + + @fixture async def store() -> Store: - key = Store.generate_raw_key(b"00000000000000000000000000000My1") + key = raw_key() store = await Store.provision(TEST_STORE_URI, "raw", key, recreate=True) yield store await store.close(remove=True) @@ -179,5 +183,51 @@ async def test_key_store(store: Store): @mark.asyncio async def test_profile(store: Store): + # New session in the default profile + async with store as session: + # Insert a new entry + await session.insert( + TEST_ENTRY["category"], + TEST_ENTRY["name"], + TEST_ENTRY["value"], + TEST_ENTRY["tags"], + ) + profile = await store.create_profile() + + async with store.session(profile) as session: + # Should not find previously stored record + assert ( + await session.count( + TEST_ENTRY["category"], {"~plaintag": "a", "enctag": "b"} + ) + ) == 0 + + # Insert a new entry + await session.insert( + TEST_ENTRY["category"], + TEST_ENTRY["name"], + TEST_ENTRY["value"], + TEST_ENTRY["tags"], + ) + assert ( + await session.count( + TEST_ENTRY["category"], {"~plaintag": "a", "enctag": "b"} + ) + ) == 1 + + if ":memory:" not in TEST_STORE_URI: + # Test accessing profile after re-opening + key = raw_key() + store_2 = await Store.open(TEST_STORE_URI, "raw", key) + print("try profile", profile) + async with store_2.session(profile) as session: + # Should not find previously stored record + assert ( + await session.count( + TEST_ENTRY["category"], {"~plaintag": "a", "enctag": "b"} + ) + ) == 1 + await store_2.close() + await store.remove_profile(profile)