typelock 0.2.205

Enforce security boundaries at the Type level
Documentation
# Typelock
**Enforce security boundaries at the Type level**

`typelock` is a Rust framework for creating type-safe, encrypted and hashed data models. It uses a procedural macro to generate locked and unlocked versions of your data models to ensure sensitive data is only accessible through explicit, pre-defined, model conversions steps and to avoid type confusion.

## Features
Given a single input definition, `typelock` generates the following:
- An **unlocked** representation 
- A **locked** representation
- Explicit `lock()` and `unlock()` transitions, based on the current model

## Example
```rust
#[derive(LockSchema, Clone)]
#[typelock(unlocked(name = UserUnlocked, derives(Debug)), locked(name = UserLocked))]
pub struct User {
    pub id: i64,

    #[secure(policy(encrypt))]
    pub email: String,

    #[secure(policy(hash), rename = "password_hash")]
    pub password: String,

    #[secure(policy(encrypt))]
    pub user_data: UserData,
}
```

## Custom types
Secured fields must define how they are convered to and from byte. This is intentional, so that `typelock` doesn't force you into the usage of specific libraries. 

```rust
#[derive(Debug, Clone)]
pub struct UserData {
    first_name: String,
}

impl ToBytes for UserData {
    fn to_bytes(&self) -> Vec<u8> {
        self.first_name.as_bytes().to_vec()
    }
}

impl FromBytes for UserData {
    fn from_bytes(bytes: &[u8]) -> Result<Self, typelock::Error> {
        Ok(Self {
            first_name: String::from_utf8(bytes.to_vec())?,
        })
    }
}
```

You also have the choice to enable the `postcard-codec` feature, which lets you gain access to the ToBytes and FromBytes derive macros. As long as your struct also derives from [serde's](https://crates.io/crates/serde) `serde::Serialize` and `serde::Deserialize`, these macros automatically generate the necessary to_bytes and from_bytes implementations for you, removing the need to write them manually. This features leverages [postcard](https://crates.io/crates/postcard)) under the hood.

## Security providers
To stay flexible, `typelock` is intentionally backend agnostic, meaning you will have to provide your own implementation to encrypt/decrypt/hash your data. For that `typelock` provides the following traits:

```rust
pub struct MySecurityProvider;

// CryptProvider for encrypted fields
impl CryptProvider for MySecurityProvider {
    fn encrypt(&self, data: &[u8]) -> std::result::Result<Vec<u8>, crate::Error>;
        todo!("implement your own encryption logic here.")
    }

    fn decrypt(&self, data: &[u8]) -> std::result::Result<Vec<u8>, crate::Error>;
        todo!("implement your own decryption logic here.")
    }
}

// HashingProvider for hashed fields
impl HashingProvider for MySecurityProvider {
    fn hash(&self, data: &[u8]) -> std::result::Result<Vec<u8>, crate::Error>;
        todo!("implement your own hashing logic here.")
    }
}
```

`typelock` **does not** implement encryption or hashing itself, you will always have to provide **your own** cryptography.

## Converting between models
`typelock` automatically provides the following model conversions:
1. OriginalModel -> LockedModel // call `.lock()`
2. LockedModel -> UnlockedModel // call `.unlock()`
3. UnlockedModel -> LockedModel // call `.lock()`

You are encouraged to always instantiate your own models starting from OriginalModel and never from LockedModel or UnlockedModel. This is because otherwise `typelock` is not able to guarantee that you actually secured your data correctly. 

```rust
use typelock::{Lockable, Unlockable};

let provider = MySecurityProvider;
let user = User {
    id: 1,
    email: "user@email".to_string(),
    password: "user-password".to_string(),
    user_data: UserData {
        first_name: "user".to_string(),
    },
};
let locked_user = user.lock(&provider);
let unlocked_user = locked_user.unlock(&provider).expect("implement error handling");
```

Calling `.lock()` and `.unlock()` move the previous model into the function call. This means that trying to access `user` after calling `.lock()` is not allowed. `typelock` also provides default implementations to call `.lock()` and `.unlock()` on collections of Vec<Model>.

**Warning:** you are discouraged from deriving Clone on your **original model** or any of the **generated model's**. This is because doing the following could lead to ghost copies that stay in your programm's memory, which could compromise data. 

```rust
let user = User { ... }
let locked_user = user.clone().lock();
```

In cases you need to do so, please take a look at the [zeroize crate](https://crates.io/crates/zeroize) for example.

## Why use Typelock?
Without `typelock`, you typically end up writing code that looks like the following:

```rust
DecryptedUser {
    id: self.id,
    email: provider.decrypt(...),
    ...
}
```

This is verbose, easy to get wrong and typically repeated among multiple models. `typelock` generates that bondary once, correctly.

## Use cases
`typelock` is useful when you need:
- Type-safe boundaries between encrypted/decrypted or hashed data states
- Compile-time guarantees that sensitive data can't be accidentally used in the wrong state
- Automatic transitions between domain models and their storage representations
- To eliminate boilerplate for encrypt/decrypt/hash operations across multiple models
- To prevent accidentally serializing or logging sensitive data in plaintext form
- A strict separation between domain logic and persistence layer with security enforced at compile time

`typelock` complements your cryptography library by modeling security state transitions at the type level. It **does not** implement encryption or hashing itself.

## Status
`typelock` is still in an early-stage. Therefore some features are still missing and the API might change.

## License
This project is licensed under the MIT License.

This crate provides optional integration with [serde](https://crates.io/crates/serde) and [postcard](https://crates.io/crates/postcard) via the postcard-codec feature. Therefore the respective licenses (MIT/Apache-2.0) of these dependencies apply.