This library simplifies the task of securely connecting two servers to each other. It closely mimicks the regular socket API, and adds the concept of identity: each communicating server has an identity, and connections can only be established between two servers who know each other and expect to be communicating.
Under the hood, the library takes care of strongly authenticating the connection, and of encrypting all traffic. If you successfully establish a connection using this library, you have the guarantee that the connection is secure.
- class Identity a where
- data PeerIdentity
- data LocalIdentity
- toPeerIdentity :: LocalIdentity -> PeerIdentity
- newLocalIdentity :: MonadIO m => String -> Int -> m LocalIdentity
- connect :: LocalIdentity -> [PeerIdentity] -> (HostName, ServiceName) -> IO Connection
- data Socket
- newServer :: (Maybe HostName, ServiceName) -> IO Socket
- accept :: LocalIdentity -> [PeerIdentity] -> Socket -> IO Connection
- data Connection
- peer :: Connection -> PeerIdentity
- read :: Connection -> Int -> IO ByteString
- readPtr :: Connection -> Ptr a -> Int -> IO Int
- write :: Connection -> ByteString -> IO ()
- writePtr :: Connection -> Ptr a -> Int -> IO ()
- close :: Connection -> IO ()
- type HostName = String
- type ServiceName = String
First, each host needs to generate a local identity for itself. A local identity allows a server to authenticate itself to remote peers.
do id <- newLocalIdentity "server1.domain.com" 365 writeIdentity id >>= writeFile "server.key"
The name is not used at all by the library, it just allows you to identify the key later on if you need to.
This identity contains secret key material that only the generating host should have. From this, we need to generate a public identity that can be given to other hosts.
do id <- readFile "server.key" >>= readIdentity writeIdentity (toPeerIdentity id) >>= writeFile "server.pub"
This public file should be distributed to the servers with whom you want to communicate. Once everyone has the public identities of their peers, we can start connecting. First, one host needs to start listening for connections.
do me <- readFile "a.key" >>= readIdentity you <- readFile "b.pub" >>= readIdentity server <- newServer (Nothing, "4242") conn <- accept me [you] server
Then, another host needs to connect.
do me <- readFile "b.key" >>= readIdentity you <- readFile "a.pub" >>= readIdentity conn <- connect me [you] ("a.com", "4242")
Et voila! From there on, you can communicate using the usual socket-ish API:
do write conn "hello?" read conn 128 >>= putStrLn close conn
N.B. The program should start with
withOpenSSL in order to initialize SSL
main = withOpenSSL $ do).
Internals and caveats
Note that this section gives out internal implementation details which are subject to change! Compatibility breakages will be indicated by appropriate version number bumps for the package, and the internal details of new versions may bear no resemblance whatsoever to the old version.
The current implementation uses OpenSSL (via HsOpenSSL) for
transport security, with the
AES256-SHA ciphersuite and 4096 bit
Due to a current limitation of the HsOpenSSL API, we do not use a ciphersuite that makes use of ephemeral keys for encryption. The consequence is that connections established with this library do not provide perfect forward secrecy.
That is, if an attacker can compromise the private keys of the communicating servers, she can decrypt all past communications that she has recorded.
This shortcoming will be fixed at some point, either by adding Diffie-Hellman keying support to HsOpenSSL, or by switching to a different underlying implementation.
An identity, public or private.
Return the description that was associated with the identity when it was created.
Serialize an identity to a
ByteString for storage or
Read back an identity previously serialized with writeIdentity.
The public identity of a peer. This kind of identity can be used to authenticate the remote ends of connections.
A local identity. This kind of identity can be used to authenticate to remote ends of connections.
Generate a new
LocalIdentity, giving it an identifying name and
a validity period in days.
Note that this function may take quite a while to execute, as it is generating key material for the identity.
Connecting to peers
Accepting connections from peers
Create a new secure socket server, listening on the given
address/port. The host may be
Nothing to signify that the socket
should listen on all available addresses.
Accept one secure connection from a remote peer. The peer may
authenticate as any of the given peer identities. A
returned iff the autentication completes successfully.
Talking to connected peers
An established authenticated connection to a peer. It is guaranteed that all Connection objects are with a known peer, and that the connection is strongly encrypted.
PeerIdentity of the remote end of the connection.
Read at most
n bytes from the given connection, into the given raw buffer.
Send data from the given raw pointer to the connected peer.
Close the connection. No other operations on
be used after closing it.
Misc reexports from
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.,