[]Crate ossuary

Ossuary

Ossuary is a Rust library for establishing an encrypted and authenticated communication channel between a client and a server.

It establishes a 1-to-1 client/server communication channel that requires reliable, in-order packet delivery, such as provided by TCP sockets.

Authentication and verification of remote hosts is optional, and requires an out-of-band exchange of host public keys, or a Trust-On-First-Use policy.

Ossuary includes a C FFI API, and can be built as a native dynamic or static library for linking into C or C++ binaries.

Protocol Overview:

The Ossuary protocol consists of two distinct stages: a handshaking stage and an established channel stage.

Handshake

All connections begin by handshaking. A few packets are exchanged back and forth to establish an encrypted channel with a negotiated session key, and to optionally authenticate the identity of the hosts. Handshake packets are generated by the Ossuary library itself; the application asks Ossuary for the next packet to send, and then is responsible for putting it on the wire.

Established Channel

Once the handshake completes successfully, Ossuary drops into the established channel stage. In this stage, Ossuary is completely passive, where the application gives it data to encrypt for transmission or decrypt for reception. Ossuary does not generate any unrequested packets in this stage.

In the case of errors, Ossuary always terminates the encrypted channel and drops back into the handshake stage. If it believes the channel might still be viable, the handshake stage will generate a packet to attempt to reset the remote host back into the handshake as well, and they will both attempt to recover the channel.

API Overview:

Ossuary fits into an application as a 'filter' on network traffic, and does not implement any network communication itself. Similarly, it does not have any persistent storage, leaving the storage of keys to the caller.

The application must always check to see if it should drop back into the handshake, in case the connection failed. The main loop would typically be split between two cases: handshaking, or established channel.

In pseudocode, typical use of the API looks like this:

This example is not tested
fn main() {
  let socket = TCPSocket::get_socket_from_somewhere();
  let secret_key = FileSystem::get_securely_stored_file("secret.key");
  let ossuary = OssuaryConnection::new(ConnectionType::Whatever, Some(secret_key));
  let net_read_buf = [0u8; 1024];  // encrypted!
  let net_write_buf = [0u8; 1024]; // encrypted!
  let internal_buf = [0u8; 1024];  // plaintext!

  loop {
    // Always read encrypted data off the wire and append to our read buf
    socket.read_and_append(&mut net_read_buf);

    // Two paths depending on if we are handshaking
    match ossuary.handshake_done() {
      false => {
        // Parse received handshake packets.
        ossuary.recv_handshake(&net_read_buf);

        // Possibly write handshake packets.
        ossuary.send_handshake(&mut net_write_buf);
      },

      true => {
        // Decrypt data into internal buffer
        ossuary.recv_data(&net_read_buf, &mut internal_buf);

        // Do some application-specific thing with the data
        react_and_respond(&mut internal_buf);

        // Encrypt application's response
        ossuary.send_data(&internal_buf, &mut net_write_buf);
      },
    }

    // Always write all encrypted data onto the wire and clear the write buf
    socket.write_and_clear(&mut net_write_buf);
  }
}

A real implementation would also need to carefully handle removing consumed bytes from the read buffer, and handling errors at every step.

Since Ossuary leaves the actual wire transmission to the caller, it can be used over any physical channel, or with any transmission protocol, so long as data is delivered (to Ossuary) reliably and in-order. It is just as viable for UNIX Domain Sockets, D-Bus, named pipes, and other IPC mechanisms as for TCP, if you somehow have a threat model that distrusts those.

Ciphers:

The handshake protocol:

A 3-packet (1.5 roundtrip) handshake is always performed.

The necessary fields to perform an ECDH key exchange and establish a shared session key are sent in the clear, while fields for host verification are encrypted with the established session key.

In the following diagram, fields in [single brackets] are sent in the clear, and those in [[double brackets]] are encrypted with the shared session key:

<client> --> [  session x25519 public key,
                session nonce,
                client random challenge                ] --> <server>
<client> <-- [  session x25519 public key,
                session nonce],
             [[ auth ed25519 public key,
                server random challenge,
                signature(pubkey, nonce, challenge),  ]] <-- <server>
<client> --> [[ auth ed25519 public key,
                signature(pubkey, nonce, challenge),  ]] --> <server>

Host authentication (verifying the identity of the remote server or client) is optional. In non-authenticated flows, "auth public key", "challenge", and "signature" fields are set to all 0s, but are still transmitted.

Fields

Security Protections

Passive Snooping

Per-packet encryption with ChaCha20 prevents passive monitoring of the contents of the communication channel.

Malleability

Poly1305 MAC prevents active manipulation of packets in-flight, ensuring that any manipulation will cause the channel to terminate.

Replay Attacks

Poly1305 MAC combined with a nonce scheme prevents replay attacks, and prevents manipulation of message order.

Forward Secrecy

Per-session encryption with ephemeral x25519 keys ensures that the compromise of one session does not necessarily result in the compromise of any previous or future session.

Man-in-the-Middle

Host authentication with Ed25519 signature verification of ECDH parameters prevents man-in-the-middle attacks. Host authentication is optional, and requires out-of-band exchange of host public keys or a Trust On First Use policy, so MITM attacks may be possible if care is not taken.

Security Limitations

Code Quality

This software is not code reviewed, and no security analysis has been performed.

Keys In RAM

No efforts are taken to secure key data in RAM. Attacks from privileged local processes are possible.

Keys On Disk

No mechanisms are provided for storing keys on disk. Secure key storage is left as a task for the caller.

Side-Channel

No efforts are taken to protect against side channel attacks such as timing or cache analysis.

Software Dependencies

This software depends on third-party software libraries for all core cryptographic algorithms, which have not been code reviewed and are subject to change.

Trust-On-First-Use (TOFU)

Host authentication supports a trust-on-first-use policy, which opens the possibility of man-in-the-middle attacks if the first connection is compromised.

License

Copyright 2019 Trevor Bentley

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Modules

clib

Ossuary API exposed with a C FFI

Structs

OssuaryConnection

Context for interacting with an encrypted communication channel

Enums

ConnectionType

Enum specifying the client or server role of a OssuaryConnection

OssuaryError

Error produced by Ossuary or one of its dependencies

Functions

generate_auth_keypair

Generate secret/public Ed25519 keypair for host authentication