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()andunlock()transitions, based on the current model
Example
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.
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 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 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:
;
// CryptProvider for encrypted fields
;
todo!
}
}
// HashingProvider for hashed fields
}
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:
- OriginalModel -> LockedModel // call
.lock() - LockedModel -> UnlockedModel // call
.unlock() - 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.
use ;
let provider = MySecurityProvider;
let user = User ;
let locked_user = user.lock;
let unlocked_user = locked_user.unlock.expect;
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.
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.
let user = User
let locked_user = user.clone.lock;
In cases you need to do so, please take a look at the zeroize crate for example.
Why use Typelock?
Without typelock, you typically end up writing code that looks like the following:
DecryptedUser
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 and postcard via the postcard-codec feature. Therefore the respective licenses (MIT/Apache-2.0) of these dependencies apply.