Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement KeyIdStorage in Rust #1134

Merged
merged 12 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions identity_storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ description = "Abstractions over storage for cryptographic keys used in DID Docu
async-trait = { version = "0.1.64", default-features = false }
identity_core = { version = "=0.7.0-alpha.6", path = "../identity_core", default-features = false }
identity_jose = { version = "=0.7.0-alpha.6", path = "../identity_jose", default-features = false }
identity_verification = { version = "=0.7.0-alpha.6", path = "../identity_verification", default_features = false }
iota-crypto = { version = "0.15", default-features = false, features = ["blake2b", "ed25519", "random"], optional = true }
rand = { version = "0.8.5", default-features = false, features = ["std"], optional = true }
seahash = { version = "4.1.0", default_features = false }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
tokio = { version = "1.23.0", default-features = false, features = ["macros", "sync"], optional = true }

[dev-dependencies]
identity_did = { version = "=0.7.0-alpha.6", path = "../identity_did", default-features = false }
rand = { version = "0.8.5" }
tokio = { version = "1.23.0", default-features = false, features = ["macros", "sync", "rt"] }

Expand Down
156 changes: 156 additions & 0 deletions identity_storage/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::borrow::Cow;
use std::error::Error;
use std::fmt::Debug;
use std::fmt::Display;

/// The error type for key storage operations.
///
/// Instances always carry a corresponding [`StorageErrorKind`] and may be extended with custom error messages and
/// source.
#[derive(Debug)]
pub struct StorageError<T: StorageErrorKind> {
abdulmth marked this conversation as resolved.
Show resolved Hide resolved
repr: Repr<T>,
}

/// Error types that can happen during storage operations.
pub trait StorageErrorKind: Display + Debug {
fn description(&self) -> &str;
}

impl<T: StorageErrorKind> Display for StorageError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.repr {
Repr::Simple(ref cause) => write!(f, "{}", cause.description()),
Repr::Extensive(ref extensive) => {
write!(f, "{}", extensive.cause.description())?;
let Some(ref message) = extensive.message else {return Ok(())};
write!(f, " message: {}", message.as_ref())
}
}
}
}

impl<T: StorageErrorKind> Error for StorageError<T> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.extensive().and_then(|err| {
err
.source
.as_ref()
.map(|source| source.as_ref() as &(dyn Error + 'static))
})
}
}

#[derive(Debug)]
struct Extensive<T: StorageErrorKind> {
cause: T,
source: Option<Box<dyn Error + Send + Sync + 'static>>,
message: Option<Cow<'static, str>>,
}

#[derive(Debug)]
enum Repr<T: StorageErrorKind> {
Simple(T),
Extensive(Box<Extensive<T>>),
}

impl<T: StorageErrorKind> From<T> for StorageError<T> {
fn from(cause: T) -> Self {
Self::new(cause)
}
}

impl<T: StorageErrorKind> From<Box<Extensive<T>>> for StorageError<T> {
fn from(extensive: Box<Extensive<T>>) -> Self {
Self {
repr: Repr::Extensive(extensive),
}
}
}

impl<T: StorageErrorKind> StorageError<T> {
/// Constructs a new [`StorageError`].
pub fn new(cause: T) -> Self {
Self {
repr: Repr::Simple(cause),
}
}

/// Returns a reference to corresponding [`StorageErrorKind`] of this error.
pub fn kind(&self) -> &T {
match self.repr {
Repr::Simple(ref cause) => cause,
Repr::Extensive(ref extensive) => &extensive.cause,
}
}

/// Converts this error into the corresponding [`StorageErrorKind`] of this error.
pub fn into_kind(self) -> T {
match self.repr {
Repr::Simple(cause) => cause,
Repr::Extensive(extensive) => extensive.cause,
}
}

/// Returns a reference to the custom message of the [`StorageError`] if it was set.
pub fn custom_message(&self) -> Option<&str> {
self
.extensive()
.into_iter()
.flat_map(|extensive| extensive.message.as_deref())
.next()
}

/// Returns a reference to the attached source of the [`StorageError`] if it was set.
pub fn source_ref(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
self.extensive().and_then(|extensive| extensive.source.as_deref())
}

/// Converts this error into the source error if it was set.
pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync + 'static>> {
self.into_extensive().source
}

fn extensive(&self) -> Option<&Extensive<T>> {
match self.repr {
Repr::Extensive(ref extensive) => Some(extensive.as_ref()),
_ => None,
}
}

fn into_extensive(self) -> Box<Extensive<T>> {
match self.repr {
Repr::Extensive(extensive) => extensive,
Repr::Simple(cause) => Box::new(Extensive {
cause,
source: None,
message: None,
}),
}
}

/// Updates the `source` of the [`StorageError`].
pub fn with_source(self, source: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self {
self._with_source(source.into())
}

fn _with_source(self, source: Box<dyn Error + Send + Sync + 'static>) -> Self {
let mut extensive = self.into_extensive();
extensive.as_mut().source = Some(source);
Self::from(extensive)
}

/// Updates the custom message of the [`StorageError`].
pub fn with_custom_message(self, message: impl Into<Cow<'static, str>>) -> Self {
self._with_custom_message(message.into())
}

fn _with_custom_message(self, message: Cow<'static, str>) -> Self {
let mut extensive = self.into_extensive();
extensive.as_mut().message = Some(message);
Self::from(extensive)
}
}
30 changes: 30 additions & 0 deletions identity_storage/src/key_id_storage/key_id_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use crate::key_storage::KeyId;
use async_trait::async_trait;

use super::key_id_storage_error::KeyIdStorageError;
use super::method_digest::MethodDigest;

/// Result of key id storage operations.
pub type KeyIdStorageResult<T> = Result<T, KeyIdStorageError>;

/// Key value Storage for [`KeyId`] under [`MethodDigest`].
#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
#[cfg_attr(feature = "send-sync-storage", async_trait)]
pub trait KeyIdStorage {
/// Insert a [`KeyId`] into the [`KeyIdStorage`] under the given [`MethodDigest`].
/// If an entry for `key` already exists in the storage an error must be returned
///
/// immediately without altering the state of the storage.
async fn insert_key_id(&self, key: MethodDigest, value: KeyId) -> KeyIdStorageResult<()>;

/// Obtain the [`KeyId`] associated with the given [`MethodDigest`].
async fn get_key_id(&self, key: &MethodDigest) -> KeyIdStorageResult<KeyId>;

/// Delete the [`KeyId`] associated with the given [`MethodDigest`] from the [`KeyIdStorage`].
///
/// If `key` is not found in storage, an Error must be returned.
async fn delete_key_id(&self, key: &MethodDigest) -> KeyIdStorageResult<()>;
}
66 changes: 66 additions & 0 deletions identity_storage/src/key_id_storage/key_id_storage_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Display;

use crate::StorageError;
use crate::StorageErrorKind;

/// Error type for key id storage operations.
pub type KeyIdStorageError = StorageError<KeyIdStorageErrorKind>;

/// The cause of the failed key id storage operation.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum KeyIdStorageErrorKind {
/// Indicates that the key id storage implementation is not able to find the requested key id.
KeyIdNotFound,

/// Indicates that the key id already exists in the storage.
KeyIdAlreadyExists,

/// Indicates that the storage is unavailable for an unpredictable amount of time.
///
/// Occurrences of this variant should hopefully be rare, but could occur if hardware fails, or a hosted key store
/// goes offline.
Unavailable,

/// Indicates that an attempt was made to authenticate with the key storage, but the operation did not succeed.
Unauthenticated,

/// Indicates an unsuccessful I/O operation that may be retried, such as a temporary connection failure or timeouts.
///
/// Returning this error signals to the caller that the operation may be retried with a chance of success.
/// It is at the caller's discretion whether to retry or not, and how often.
RetryableIOFailure,

/// Indicates a failure to serialize or deserialize.
SerializationError,

/// Indicates that something went wrong, but it is unclear whether the reason matches any of the other variants.
///
/// When using this variant one may want to attach additional context to the corresponding [`StorageError`]. See
/// [`KeyStorageError::with_custom_message`](KeyIdStorageError::with_custom_message()) and
/// [`KeyStorageError::with_source`](KeyIdStorageError::with_source()).
Unspecified,
}

impl StorageErrorKind for KeyIdStorageErrorKind {
fn description(&self) -> &str {
match self {
Self::KeyIdAlreadyExists => "Key id already exists in storage",
Self::KeyIdNotFound => "key id not found",
Self::Unavailable => "key id storage unavailable",
Self::Unauthenticated => "authentication with the key id storage failed",
Self::Unspecified => "key storage operation failed",
Self::RetryableIOFailure => "key id storage was unsuccessful because of an I/O failure",
Self::SerializationError => "(de)serialization error",
}
}
}

impl Display for KeyIdStorageErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.description())
}
}
119 changes: 119 additions & 0 deletions identity_storage/src/key_id_storage/memstore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use crate::key_id_storage::key_id_storage::KeyIdStorage;
use crate::key_id_storage::key_id_storage_error::KeyIdStorageError;
use crate::key_id_storage::key_id_storage_error::KeyIdStorageErrorKind;
use crate::key_storage::shared::Shared;
use crate::key_storage::KeyId;
use async_trait::async_trait;
use std::collections::HashMap;
use tokio::sync::RwLockReadGuard;
use tokio::sync::RwLockWriteGuard;

use super::key_id_storage::KeyIdStorageResult;
use super::method_digest::MethodDigest;

type KeyIdStore = HashMap<MethodDigest, KeyId>;

/// An insecure, in-memory [`KeyIdStorage`] implementation that serves as an example and may be used in tests.
#[derive(Debug)]
pub struct KeyIdMemstore {
key_id_store: Shared<KeyIdStore>,
}

impl KeyIdMemstore {
/// Creates a new, empty `KeyIdMemstore` instance.
pub fn new() -> Self {
Self {
key_id_store: Shared::new(HashMap::new()),
}
}
}

impl Default for KeyIdMemstore {
fn default() -> Self {
Self::new()
}
}

#[cfg_attr(not(feature = "send-sync-storage"), async_trait(? Send))]
#[cfg_attr(feature = "send-sync-storage", async_trait)]
impl KeyIdStorage for KeyIdMemstore {
async fn insert_key_id(&self, key: MethodDigest, value: KeyId) -> KeyIdStorageResult<()> {
let mut key_id_store: RwLockWriteGuard<'_, KeyIdStore> = self.key_id_store.write().await;
if key_id_store.contains_key(&key) {
return Err(KeyIdStorageError::new(KeyIdStorageErrorKind::KeyIdAlreadyExists));
}
key_id_store.insert(key, value);
Ok(())
}

async fn get_key_id(&self, key: &MethodDigest) -> KeyIdStorageResult<KeyId> {
let key_id_store: RwLockReadGuard<'_, KeyIdStore> = self.key_id_store.read().await;
Ok(
key_id_store
.get(key)
.ok_or_else(|| KeyIdStorageError::new(KeyIdStorageErrorKind::KeyIdNotFound))?
.clone(),
)
}

async fn delete_key_id(&self, key: &MethodDigest) -> KeyIdStorageResult<()> {
let mut key_id_store: RwLockWriteGuard<'_, KeyIdStore> = self.key_id_store.write().await;
key_id_store
.remove(key)
.ok_or_else(|| KeyIdStorageError::new(KeyIdStorageErrorKind::KeyIdNotFound))?;
Ok(())
}
}

#[cfg(test)]
mod tests {
use crate::key_id_storage::key_id_storage::KeyIdStorage;
use crate::key_id_storage::memstore::KeyIdMemstore;
use crate::key_id_storage::method_digest::MethodDigest;
use crate::key_id_storage::KeyIdStorageError;
use crate::key_id_storage::KeyIdStorageErrorKind;
use crate::key_storage::KeyId;
use identity_core::crypto::KeyPair;
use identity_core::crypto::KeyType;
use identity_core::utils::BaseEncoding;
use identity_did::CoreDID;
use identity_verification::VerificationMethod;

#[tokio::test]
pub async fn memstore_operations() {
// Create a Verification Method.
let keypair: KeyPair = KeyPair::new(KeyType::Ed25519).unwrap();
let did: CoreDID =
CoreDID::parse(format!("did:example:{}", BaseEncoding::encode_base58(keypair.public()))).unwrap();
let verification_method: VerificationMethod =
VerificationMethod::new(did, KeyType::Ed25519, keypair.public(), "frag_1").unwrap();

// Test insertion.
let memstore: KeyIdMemstore = KeyIdMemstore::new();
let key_id_1 = KeyId::new("keyid");
let method_digest: MethodDigest = MethodDigest::new(&verification_method).unwrap();
memstore
.insert_key_id(method_digest.clone(), key_id_1.clone())
.await
.expect("inserting into memstore failed");

// Double insertion.
let insertion_result = memstore.insert_key_id(method_digest.clone(), key_id_1.clone()).await;
let _expected_error: KeyIdStorageError = KeyIdStorageError::new(KeyIdStorageErrorKind::KeyIdAlreadyExists);
assert!(matches!(insertion_result.unwrap_err(), _expected_error));

// Test retrieving.
let key_id: KeyId = memstore.get_key_id(&method_digest).await.unwrap();
assert_eq!(key_id_1, key_id);

// Test deletion.
memstore.delete_key_id(&method_digest).await.expect("deletion failed");

let repeat_deletion_result: Result<(), KeyIdStorageError> = memstore.delete_key_id(&method_digest).await;
let _expected_error: KeyIdStorageError = KeyIdStorageError::new(KeyIdStorageErrorKind::KeyIdNotFound);
assert!(matches!(repeat_deletion_result.unwrap_err(), _expected_error));
}
}
Loading