noise-0.0.0.0: Usable security for the internet, Deux

Copyright(c) Austin Seipp 2014
LicenseMIT
Maintaineraseipp@pobox.com
Stabilityunstable
Portabilityportable
Safe HaskellTrustworthy
LanguageHaskell2010

Crypto.Network.Noise

Contents

Description

Noise pipes. Pipes encrypt meant for interactive communications like networking sockets, and are built on boxes.

This module exports a simple, high-level interface for communicating with pipes securely and easily. For a full tutorial, see Crypto.Noise.Tutorial.

Synopsis

Noise pipes

Noise pipes encrypt interactive channels like sockets. Noise pipes have some additional benefits in addition with the protections offered by boxes:

Full forward secrecy or full key erasure
Compromise of any long-term private keys never compromises old pipes. But furthermore, compromise of an active endpoint in an ongoinng communication does not compromise prior ciphertexts either - a Noise pipe forgets its chain secrets upon every message.
Resistance to key-compromise impersonation
Even with a compromised private key, the compromised party can still verify other parties' identities in a Noise pipe.
Efficient, encrypted handshakes with short roundtrip
Handshakes allow clients and servers to communicate after only one round trip, offering room for validation checks or certificates.

TCP Socket Interface

A simple, no-nonsense TCP interface for Noise servers/clients. This forms the basis of the TCP interface. In the simplest case, both clients and servers are anonymous - identified not by long-term public keys (which have been exchanged), but only by short-term ephemeral keys. This makes the TCP layer as easy to use as any unencrypted socket in the default case.

Client interface

Here's an example of a Noise client:

connect "www.example.org" "80" defaultPipeConfig $ \(ctx, remoteAddr) -> do
  putStrLn $ "Connection established to " ++ show remoteAddr
  -- Now you can use connSock as you see fit with send and recv.
  -- The socket handle will automatically be closed when it goes out of scope.

connect

Arguments

:: (MonadIO m, MonadCatch m) 
=> HostName

Server hostname

-> ServiceName

Server port

-> PipeConfig

Client configuration

-> ((Context, SockAddr) -> m r)

Connection handler

-> m r 

Connect to a Noise-secured TCP server and use the connection.

Any acquired network resources are properly closed and discarded when done or in case of exceptions.

Server interface

Here's an example of a Noise server:

serve (Host "127.0.0.1") "8000" defaultPipeConfig $ \(ctx, remoteAddr) -> do
  putStrLn $ "Noise connection established from " ++ show remoteAddr
  -- Now you may use ctx as you please within this scope,
  -- possibly using recv and send to interact with the remote end.

serve

Arguments

:: MonadIO m 
=> HostPreference

Preferred host to bind on

-> ServiceName

Service port

-> PipeConfig

Server configuration

-> ((Context, SockAddr) -> IO ())

Connection handler

-> m () 

Start a Noise-secured TCP server which accepts incoming connections and handles each of them concurrently in different threads.

Any acquired network resources are properly closed and discarded when done or in case of exceptions.

Internally, this function just performs listen and acceptFork.

listen

Arguments

:: (MonadIO m, MonadCatch m) 
=> HostPreference

Preferred host to bind

-> ServiceName

Port identifier

-> ((Socket, SockAddr) -> m r)

Handler for listening socket

-> m r 

Bind a TCP listening socket and use it

The listening socket is closed when done or in case of exceptions.

Note: maxListenQueue is tipically 128, which is too small for high performance servers. So, we use the maximum between maxListenQueue and 2048 as the default size of the listening queue. The NoDelay and ReuseAddr options are set on the socket.

accept

Arguments

:: (MonadIO m, MonadCatch m) 
=> Socket

Listening and bound socket

-> PipeConfig

Server configuration

-> ((Context, SockAddr) -> m r)

Handler for incoming connection.

-> m r 

Accept a single incoming connection and use it.

The connection socket is closed when done or in case of exceptions.

acceptFork

Arguments

:: MonadIO m 
=> Socket

Listening and bound socket

-> PipeConfig

Server configuration

-> ((Context, SockAddr) -> IO ())

Handler for incoming connection.

-> m ThreadId 

Accept a single incoming connection and use it in another thread.

The connection socket is closed when done or in case of exceptions.

Sending/receiving data

send

Arguments

:: MonadIO m 
=> Context

Connection context

-> ByteString

Message

-> m () 

Encrypts the given ByteString, and writes the bytes to the socket in a single message. The data can also be padded with random data to obscure the plaintext length.

recv :: MonadIO m => Context -> m (Maybe ByteString)

Receives data over the connection, decrypts it and returns the resulting ByteString. This receives a single Noise message.

Returns Nothing if the remote end closed the connection or end-of-input was reached.

io-streams interface

A simple io-streams based interface for Noise pipes. Using these APIs, you can turn any InputStream or OutputStream into an encrypted Noise channel. Additionally, you can take a socket Context and obtain the underlying streams, to layer in other transformations.

data PipeMsg

A message to send down a noise pipe - simply a message paired with an amount of random padding to add to the message.

Constructors

PipeMsg ByteString Word32 

pipeClientStream :: PipeConfig -> (InputStream ByteString, OutputStream ByteString) -> IO (InputStream ByteString, OutputStream ByteString)

Turn an InputStream and OutputStream into encrypted noise pipes for use by clients to talk to servers.

pipeServerStream :: PipeConfig -> (InputStream ByteString, OutputStream ByteString) -> IO (InputStream ByteString, OutputStream ByteString)

Turn an InputStream and OutputStream into encrypted noise pipes for use by servers to talk to clients.

pipeContextStreams :: Context -> (InputStream ByteString, OutputStream ByteString)

Pull out the underlying InputStream and OutputStream from the given TCP Context. Any data written/read from these streams will be encrypted/decrypted from the other side.

pipeContextSocket :: Context -> Socket

Pull out the underlying Socket from the Context.

closeContext :: Context -> IO ()

Close the given Context.

Utilities

type PipeMsgPadder = OutputStream PipeMsg -> IO (OutputStream ByteString)

Type of functions which pad pipe messages.

randomPadStream :: Word32 -> Word32 -> PipeMsgPadder

Given an OutputStream for a PipeMsg, create an OutputStream that attaches a random number of bytes (within the specified range) to every message sent down the pipe, to make plaintext analysis more difficult.

For example

let outStream' = randomPadStream (0, 64) outStream

creates an OutputStream where every message has between 0 and 64 bytes of padding, chosen randomly.

constantPadStream :: Word32 -> PipeMsgPadder

Given an OutputStream for a PipeMsg, create an OutputStream that attaches a constant number of random bytes to every message sent down the pipe.

For example

let outStream' = constantPadStream 0 outStream

creates an OutputStream where there is no padding for the messages sent down the pipe.

X509 certificate authentication support

Simple X509 support for pipe handshakes. The two functions offerX509 and validateX509 provide hooks you can use with PipeConfig to do X509 certificate validation as part of the initial Noise handshake. In addition, the extra validateX509_ function gives you more control over the client validation (to allow, for example, self-signed certificates).

This verification API can work for verifying server certificates to clients, vice versa, or both.

offerX509

Arguments

:: FilePath

Path to the server certificate chain to offer during the handshake, in PEM format.

-> IO ByteString

Resulting encoded certificate chain.

Offer up an X509 certificate chain as part of the initial handshake. Given a FilePath specifying a certificate, this will cause the configured party to issue the certificate as part of its initial message.

Note the other party MUST validate this response using validateX509 in their confInitialResponse setting.

When this function cannot parse the certificate chain correctly (for example, using an invalid file), it will throw an InvalidNoiseX509Certificate.

validateX509

Arguments

:: Maybe String

Optional certificate FQDN to verify.

-> ByteString

Input certificate

-> IO Bool 

Simple X509 certificate chain validation for initial handshakes.

This is a simple, easy-to-use client API that allows you to validate server-offered certificates using the system certificate store. All it needs is the expected FQDN of the certificate.

If you need more control (for example, TOFU policies or validation exceptions), please use validateX509_ instead.

Note the server MUST send the initial certificate using offerX509 in their confInitialMsg setting.

When this function cannot validate the X509 certificate offered to it, it will throw an InvalidNoiseX509Certificate exception.

validateX509_

Arguments

:: Maybe String

Optional certificate FQDN to verify.

-> Maybe ValidationCache

Optional X509 validation cache. You may use this to enforce self-signing exceptions or TOFU ("Trust On First Use") validation.

The default cache is exceptionValidationCache []

Note that fingerprints by default use SHA256, not SHA1.

-> Maybe ([FailedReason] -> IO Bool)

Optional testing predicate. If for some reason you expect the validation to fail, you can use this to check specific results. You're encouraged to never touch this.

If you need a self signed certificate, you should use the X509 ValidationCache instead (which also lets you do TOFU).

The default predicate is (x -> if x == [] then return True else throwIO InvalidNoiseX509Certificate ...) - i.e. the certificate must properly validate.

-> ByteString

Input certificate

-> IO Bool 

X509 certificate validation. This is the 'extended' version of the basic x509ClientValidate API, allowing more control. Given an optional hostname and the input certificate chain (encoded), as well as control over the validation cache and a predicate, this validates the certificate against the system store.

Note the server MUST send the initial certificate using x509ServerMsg in their serverInitialMsg setting.

When this function cannot validate the X509 certificate offered to it, it will throw an InvalidNoiseX509Certificate exception.

Windows support

withSocketsDo :: IO a -> IO a

On Windows operating systems, the networking subsystem has to be initialised using withSocketsDo before any networking operations can be used. eg.

main = withSocketsDo $ do {...}

Although this is only strictly necessary on Windows platforms, it is harmless on other platforms, so for portability it is good practice to use it all the time.

Types

data Context

TCP connection context, containing internal connection streams.

data PipeConfig

Pipe configuration. This sets options for the long-term connection and initial setup phase. Most people are probably fine using defaultPipeConfig, although depending on your use case, you may also want to customise the padder (probably using randomPadStream).

Note that if you override the initial message/response handler, you must overwrite the dual option in the other parties' handler.

Constructors

PipeConfig 

Fields

confPadding :: Maybe Word32

Padding length for the initial handshake. Default is Nothing, which signifies a random amount of padding between 0 and 32 bytes.

confKeypair :: Maybe KeyPair

Our keypair for the Noise handshake. If unspecified, the party is considered anonymous for the duration of the connection.

confExpectedKey :: Maybe (PublicKey Noise)

Expected public key for the other end of the Noise pipe. If unspecified, the party is considered anonymous for the duration of the connection.

confPadder :: Maybe PipeMsgPadder

Padding function. For every outgoing Noise message down the pipe, this function transforms the OutputStream to yield a padding value upstream. For example, randomPadStream yields a random padding length for every message, while constantPadStream yields a constant amount of padding for each message.

confInitialMsg :: Maybe (IO ByteString)

Message to send to other party in the initial Box during the handshake. If Nothing, then the default message of 16 zero bytes is used.

confInitialResponse :: Maybe (ByteString -> IO Bool)

Handler of the initial response box from the other party. If the result is False, then the connection is terminated. By default, verifies the 16 zero bytes.

Instances

defaultPipeConfig :: PipeConfig

The default PipeConfig uses randomized initial padding length, no server keypair (anonymous), no expected client key (anonymous), and no padding for messages (using constantPadStream 0).

Re-exported from Data.X509.Validation

data FailedReason

Possible reason of certificate and chain failure

Constructors

UnknownCriticalExtension

certificate contains an unknown critical extension

Expired

validity ends before checking time

InFuture

validity starts after checking time

SelfSigned

certificate is self signed

UnknownCA

unknown Certificate Authority (CA)

NotAllowedToSign

certificate is not allowed to sign

NotAnAuthority

not a CA

AuthorityTooDeep

Violation of the optional Basic constraint's path length

NoCommonName

Certificate doesn't have any common name (CN)

InvalidName String

Invalid name in certificate

NameMismatch String

connection name and certificate do not match

InvalidWildcard

invalid wildcard in certificate

LeafKeyUsageNotAllowed

the requested key usage is not compatible with the leaf certificate's key usage

LeafKeyPurposeNotAllowed

the requested key purpose is not compatible with the leaf certificate's extended key usage

LeafNotV3

Only authorized an X509.V3 certificate as leaf certificate.

EmptyChain

empty chain of certificate

CacheSaysNo String

the cache explicitely denied this certificate

InvalidSignature SignatureFailure

signature failed

Re-exported from Network.Simple.TCP

data HostPreference

Preferred host to bind.

Constructors

HostAny

Any available host.

HostIPv4

Any available IPv4 host.

HostIPv6

Any available IPv6 host.

Host HostName

An explicit host name.

Instances

Eq HostPreference 
Ord HostPreference 
Read HostPreference 
Show HostPreference 
IsString HostPreference

The following special values are recognized:

type HostName = String

Either a host name e.g., "haskell.org" or a numeric host address string consisting of a dotted decimal IPv4 address or an IPv6 address e.g., "192.168.0.1".

Exceptions

data IncorrectNoisePipeNegotiation

An exception which is thrown when the Noise pipe cannot be properly negotiated due to key failure or some other error.

data InvalidNoiseX509Certificate

Exception throw by clients when an invalid X509 certificate is offered. This may also be thrown by the server when it cannot decode the X509 certificate.