Using Themis in Rust

Introduction

The Rust-Themis extension (wrapper) provides access to features and functions of Themis cryptographic library for applications written in Rust:

  • Key generation: creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: secure exchange of messages between two parties. RSA + PSS + PKCS#7 or ECC + ECDSA (based on key choice), AES GCM container.
  • Secure Storage (aka Secure Cell): provides secure storage of record-based data through symmetric encryption and data authentication. AES GCM / AES CTR containers.
  • Secure Session: a session between two peers, within which the data can be securely exchanged with higher security guarantees. EC + ECDH, AES container.
  • Secure Comparator: comparison of a secret between two parties without leaking anything related to the secret: Zero-Knowledge Proof-based authentication system. Hardened Socialist Millionaire Protocol + ed25519.

There are also example console utils available for the Rust wrapper for Themis (as well as for some other wrappers — see the full list). They help understand the specific mechanics of encryption/decryption processes of this specific wrapper.

You can learn more about the Themis library in the general documentation or see a bit of extra info in the additional Rust-Themis API documentation.

Supported versions

Rust-Themis is tested and supported on the stable Rust compiler (Rust 1.31+).

Quickstart

Requirements

Rust-Themis uses pkg-config to locate the core Themis library. Please install pkg-config and make sure it's properly configured (you may need to tweak PKG_CONFIG_PATH if non-standard installation paths are used).

Rust-Themis generates C bindings on the fly with bindgen. Note that it requires libclang to work so you have to install this library. Usually, it's a part of the clang package available for your operating system.

Installing stable version from packages

Rust-Themis wrapper is available on crates.io. In order to use the wrapper, you still need to have the core library installed.

1. Install Themis Core as a system library using your system's package manager.

2. Add the following line to your Cargo.toml file:

[dependencies]
themis = "0.11"

Building latest version from source

If the stable package version does not suit your needs, you can manually build and install the latest version of Themis from source code.

⚠️ IMPORTANT: Remember that in addition to the common set of build tools, Themis currently requires either the OpenSSL or LibreSSL package with the developer version of the package (as it provides header files). In either case, we strongly recommend you using the most recent version of these packages.

1. Build and install Themis Core library into your system.

2. Add the following line to your Cargo.toml file:

[dependencies]
themis = { path = "/path/to/cloned/themis/repo" }

Configuring pkg-config

Currently, it is necessary to tweak pkg-config configuration on CentOS (regardless of the way that you use to install Themis Core). Please set the following environment variable before using Rust-Themis:

export PKG_CONFIG_PATH=/usr/lib/pkgconfig

Importing Themis

Import the necessary functions and you're good to go:

use themis::keygen::gen_ec_key_pair;

Modules

Rust-themis extension (wrapper) contains the following modules:

  • keygen - generating key material.
  • keys - cryptographic keys.
  • secure_cell - Secure Cell for data storage.
  • secure_comparator - Secure Comparator protocol.
  • secure_message - Secure Message system.
  • secure_session - Secure Session service.

Structs

Error - error type for most Themis operations. Errors are usually caused by invalid, malformed or malicious input as well as incorrect usage of the library. However, they may also result from underlying OS errors.

Enums

ErrorKind - a list of Themis error categories.
This enumeration is used by Error type, returned by most Themis functions. Some error kinds are specific to particular functions, and some are used internally by the library.

Type Definitions

Result - Result type for most Themis operations.

Examples

There are some examples in the docs/examples/rust directory. You can run them with Cargo:

cargo run --example keygen

These examples illustrate how to use Themis to build simple applications.

You may also find it helpful to peek into tests if you are not sure how to use the API.

Using Themis

Keypair generation

Themis supports both Elliptic Curve and RSA algorithms for asymmetric cryptography. Algorithm type is chosen according to the generated key type. Asymmetric keys are necessary for Secure Message and Secure Session objects.

⚠️ WARNING: When you distribute private keys to your users, make sure the keys are sufficiently protected. You can find the guidelines here.

NOTE: When using public keys of other peers, make sure they come from trusted sources.

Keypair generation example

use themis::keygen::gen_ec_key_pair;
use themis::secure_message::SecureMessage;

// Here we generate a new random Elliptic Curve key pair.
let key_pair = gen_ec_key_pair();

let secure = SecureMessage::new(key_pair);

let encrypted = secure.encrypt(b"message")?;
let decrypted = secure.decrypt(&encrypted)?;
assert_eq!(decrypted, b"message");

Description:

gen_ec_key_pair generates a pair of Elliptic Curve (ECDSA) keys.
gen_rsa_key_pair generates a pair of RSA keys.

Secure Message

The Secure Message functions provide a sequence-independent, stateless, contextless messaging system. This may be preferred in cases that don't require frequent sequential message exchange and/or in low-bandwidth contexts. This is secure enough to exchange messages from time to time, but if you'd like to have Perfect Forward Secrecy and higher security guarantees, please consider using Secure Session instead.

The Secure Message functions offer two modes of operation:

In Sign/Verify mode, the message is signed using the sender's private key and is verified by the receiver using the sender's public key. The message is packed in a suitable container and ECDSA is used by default to sign the message (when RSA key is used, RSA+PSS+PKCS#7 digital signature is used).

In Encrypt/Decrypt mode, the message will be encrypted with a randomly generated key (in RSA) or a key derived by ECDH (in ECDSA), via symmetric algorithm with Secure Cell in seal mode (keys are 256 bits long).

The mode is selected by using appropriate methods. The sender uses wrap and unwrap methods for encrypt/decrypt mode. A valid public key of the receiver and a private key of the sender are required in this mode. For sign/verify mode sign and verify methods should be used. They only require a private key for signing and a public key for verification respectively.

Read more about the Secure Message's cryptographic internals here.

Secure Message example

use themis::secure_message::SecureMessage;
use themis::keygen::gen_ec_key_pair;

let key_pair = gen_ec_key_pair();

let secure = SecureMessage::new(key_pair);

let encrypted = secure.encrypt(b"message")?;
let decrypted = secure.decrypt(&encrypted)?;
assert_eq!(decrypted, b"message");

Description:

SecureMessage - Secure Message encryption and decryption.
SecureSign - Secure Message signing.
SecureVerify - Secure Message verification.

NOTE: For signing/verifying make sure that you use keys from the same keypair – private key for signing message and public key for verifying message.

Secure Cell

The Secure Сell functions provide the means of protection for arbitrary data contained in stores, i.e. database records or filesystem files. These functions provide both strong symmetric encryption and data authentication mechanisms.

The general approach is that given:

  • input: some source data to protect,
  • key: a password,
  • context: plus an optional "context information",

Secure Cell functions will produce:

  • cell: the encrypted data,
  • authentication tag: some authentication data.

The purpose of the optional "context information" (i.e. a database row number or file name) is to establish a secure association between this context and the protected data. In short, even when the password is known, if the context is incorrect, the decryption will fail.

The purpose of the authentication data is to verify that given a correct password (and context), the decrypted data is indeed the same as the original source data.

The authentication data must be stored somewhere. The most convenient way is to simply append it to the encrypted data, but this is not always possible due to the storage architecture of an application. The Secure Cell functions offer different variants that address this issue.

By default, the Secure Cell uses the AES-256 encryption algorithm. The generated authentication data is 16 bytes long.

Secure Cell is available in 3 modes:

  • Seal mode: the most secure and user-friendly mode. Your best choice most of the time.
  • Token protect mode: the most secure and user-friendly mode. Your best choice most of the time.
  • Context imprint mode: length-preserving version of Secure Cell with no additional data stored. Should be used with care and caution.

You can learn more about the underlying considerations, limitations, and features here.

Secure Cell examples

Here is how you use Secure Cell to seal away your data:

use themis::secure_cell::SecureCell;

let cell = SecureCell::with_key(b"seekryt")?.seal();

let encrypted = cell.encrypt(b"source data")?;
let decrypted = cell.decrypt(&encrypted)?;
assert_eq!(decrypted, b"source data");

Description:

SecureCell - Basic Secure Cell.
SecureCellContextImprint - Secure Cell in context imprint operation mode.
SecureCellSeal - Secure Cell in sealing operation mode.
SecureCellTokenProtect - Secure Cell in token protect operation mode.

Secure Cell Token-protect Mode

In this mode the input data is mixed with the provided context and encrypted, then the authentication token is computed and returned separately, along with the encrypted container. You will have to provide the authentication token later to decrypt the data, but it can be stored or transmitted separately. The encrypted data has the same length as the original input.

use themis::secure_cell::SecureCell;

let cell = SecureCell::with_key(b"password")?.token_protect();

let input = b"test input";
let (output, token) = cell.encrypt(input)?;

assert!(output.len() == input.len());

Secure Cell Context-Imprint Mode

In this mode, the input data is mixed with the provided context and encrypted, but there is no authentication token. Use this mode when you have no additional storage available for the authentication data and you absolutely need the output data to have the same length as the original input.

use themis::secure_cell::SecureCell;

let cell = SecureCell::with_key(b"password")?.context_imprint();

let input = b"test input";
let output = cell.encrypt_with_context(input, b"context")?;

assert!(output.len() == input.len());  

Note that in context imprint mode you must provide non-empty context. Also keep in mind that Secure Cell cannot verify integrity and correctness of the decrypted data so you have to have some other means in place to validate the output.

Secure Cell Seal mode

In this mode the input data is mixed with the provided context and encrypted, then the authentication tag is appended to the data, resulting in a single encrypted and authenticated container. Note that the resulting sealed cell takes more space than the input data.

use themis::secure_cell::SecureCell;

let cell = SecureCell::with_key(b"password")?.seal();

let input = b"test input";
let output = cell.encrypt(input)?;

assert!(output.len() > input.len());

Secure Session

Secure Session is a sequence- and session- dependent, stateful messaging system. It is suitable for protecting long-lived peer-to-peer message exchanges where the secure data exchange is tied to a specific session context.

Secure Session operates in two stages:

  • session negotiation where the keys are established and cryptographic material is exchanged to generate ephemeral keys, and
  • data exchange where exchanging of messages can be carried out between peers.

You can read a more detailed description of the process here.

Put simply, Secure Session takes the following form:

  • Both clients and server construct a Secure Session object, providing:
    • an arbitrary identifier,
    • a private key, and
    • a callback function that enables it to acquire the public key of the peers with which they may establish communication.
  • A client will generate a "connect request" and by whatever means it will dispatch that to the server.
  • A server will enter a negotiation phase in response to a client's "connect request".
  • Clients and servers will exchange messages until a "connection" is established.
  • Once a connection is established, clients and servers may exchange secure messages according to whatever application level protocol was chosen.

Secure Session interface

Secure Session usage is relatively involved so you can see s complete working example in the documentation for client and server.

To sum it up, you begin by implementing a SecureSessionTransport. You have to implement at least the get_public_key_for_id method and may want to implement some others. Then you acquire the asymmetric key pairs and distribute the public keys associated with peer IDs — arbitrary byte strings used to identify communicating Secure Sessions. With that, you can create an instance of SecureSession on both the client and the server.

Next, you go through the negotiation stage using connect and negotiate methods until the connection is_established. After that, the Secure Sessions are ready for data exchange which is performed using send and receive methods.

There is also an alternative buffer-oriented API.

Secure Session callback API

NOTE: SecureSessionTransport is an interface you need to provide for Secure Session operation. The only required method is get_public_key_for_id. It is required for public key authentication. Other methods are optional, you can use Secure Session without them, but some functionality may be unavailable.

Secure Session only provides security services and doesn’t do actual network communication. In fact, Secure Session is decoupled and independent from any networking implementation. It is your responsibility to provide network transport for Secure Session using the SecureSessionTransport trait. There are two types of APIs available: callback API and buffer-aware API. You can choose whatever API is more suitable for your application, or you can even mix them when appropriate.

Callback API

With the callback API, you delegate network communication to Secure Session. In order to use it, you have to implement the send_data and receive_data callbacks of SecureSessionTransport. Then you use connect and negotiate methods to negotiate and establish a connection. After that, send and receive methods can be used for data exchange. Secure Session will synchronously call the provided transport methods when necessary to perform network communication.

There is an example of server using the callback API available.

Buffer-aware API

With the buffer-aware API, you are responsible for transporting Secure Session messages between peers. Secure Session does not use send_data and receive_data callbacks in this mode. Instead, the connect_request and negotiate_reply methods return and receive data buffers that have to be exchanged between peers via some external transport (e.g., TLS). Similarly, wrap and unwrap methods are used to encrypt and decrypt data exchange messages after the connection has been negotiated. They too accept plaintext messages and return encrypted containers or vice versa.

There is an example of a using the buffer-aware API available.

Secure Comparator

Secure Comparator is an interactive protocol for two parties that compares whether they share the same secret or not. It is built around a Zero Knowledge Proof-based protocol (Socialist Millionaire's Protocol), with a number of security enhancements.

Secure Comparator is transport-agnostic and only requires the user(s) to pass messages in a certain sequence. The protocol itself is ingrained into the functions and requires minimal integration efforts from the developer.

Secure Comparator workflow

Secure Comparator has two parties — called "client" and "server" — the only difference between them is in who starts the comparison.

Before initiating the protocol both parties should append their secrets to be compared. This can be done incrementally so even multi-gigabyte data sets can be compared with ease.

use themis::secure_comparator::SecureComparator;

let mut comparison = SecureComparator::new();

comparison.append_secret(b"999-04-1234")?;

After that, the client initiates the comparison and runs a loop:

let mut request = comparison.begin_compare()?;

while !comparison.is_complete() {
    send(&request);         // This function should send the `request` to the server.
    let reply = receive();  // This function should receive a `reply` from the server.

    request = comparison.proceed_compare(&reply)?;
}

if !comparison.result()? {
    unimplemented!("handle failed comparison here");
}

While the server does almost the same thing:

while !comparison.is_complete() {
    // This function should receive a `request` from the client.
    let request = receive();

    let reply = comparison.proceed_compare(&request)?;

    send(&reply);   // This function should send the `reply` to the client.
}

if !comparison.result()? {
    unimplemented!("handle failed comparison here");
}

Both the server and the client use result to get the comparison result after it is_complete.

Developing Themis for Rust

Rust-Themis uses standard Cargo tooling so developing the library itself is equally easy. Check out the latest source code from GitHub:

git clone https://github.com/cossacklabs/themis.git

Then you can go ahead and, for example, build and run the test suite:

cargo test

or build and read the latest API documentation:

cargo doc --open

You can test your application with your local working copy of Themis by adding the following override to the application's Cargo.toml file:

[patch.crates-io]
themis = { path = "/path/to/themis/repo" }

We use the following Cargo tools to maintain the quality of the code base:

  • rustfmt: automated code formatting keeps the code style consistent.

  • clippy: static code analyzer detects possible issues early on.

If you wish to help develop and expand Rust-Themis, we strongly advise you to install and use these tools.

Please follow the general contribution process outlined in our guide.