Implement custom secure storage

We use the SecureStorage interface to securely store a wallet containing the endpoint's private VIDs and relations. This data must be securely encrypted because it contains the private keys used to send TSP messages.

We provide a AskarSecureStorage implementation of the SecureStorage interface, which is used to securely store the wallets used by the CLI. This implementation uses Askar to securely store the wallet in a database. Our CLI uses SQLite for this, as this is easy to use and does not require you to set up any external tools. However, other databases supported by Askar are also supported. For example, you can enable PostgreSQL using the tsp_sdk/postgres feature, and then you can open a PostgreSQL database with a postgres:// URL.

Secure storage trait

Developers using TSP are free to implement their own secure storage for wallets by implementing the SecureStorage trait:

#![allow(unused)]
fn main() {
#[async_trait]
pub trait SecureStorage: Sized {
    /// Create a new secure storage
    async fn new(url: &str, password: &[u8]) -> Result<Self, Error>;

    /// Open an existing secure storage
    async fn open(url: &str, password: &[u8]) -> Result<Self, Error>;

    /// Write data from memory to secure storage
    async fn persist(
        &self,
        vids: Vec<ExportVid>,
        extra_data: Option<serde_json::Value>,
    ) -> Result<(), Error>;

    /// Read data from secure storage to memory
    async fn read(&self) -> Result<(Vec<ExportVid>, Option<serde_json::Value>), Error>;

    /// Close the secure storage
    async fn close(self) -> Result<(), Error>;

    /// Destroy the secure storage
    async fn destroy(self) -> Result<(), Error>;
}
}

Caution: make sure that the data stored using persist() is always securely encrypted to prevent exposing private keys.