packages feed

secure-sockets (empty) → 1.0

raw patch · 6 files changed

+481/−0 lines, 6 filesdep +HSHdep +HsOpenSSLdep +basesetup-changed

Dependencies added: HSH, HsOpenSSL, base, bytestring, directory, network

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright David Anderson 2010++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of David Anderson nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Network/Secure.hs view
@@ -0,0 +1,131 @@+-- |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.++{-# LANGUAGE NoImplicitPrelude #-}++module Network.Secure+    (+      -- * Tutorial+      -- $tutorial++      -- * Internals and caveats+      -- $internals++      -- * Managing identities++      -- ** Authenticating peers+      PeerIdentity+    , writePeerIdentity+    , readPeerIdentity++      -- ** Authenticating to peers+    , LocalIdentity+    , writeLocalIdentity+    , readLocalIdentity+    , toPeerIdentity+    , newLocalIdentity++      -- * Communicating++      -- ** Connecting to peers+    , connect++      -- ** Accepting connections from peers+    , Socket+    , newServer+    , accept++      -- ** Talking to connected peers+    , Connection+    , peer  +    , read+    , write+    , close++      -- ** Misc reexports from 'Network.Socket'+    , HostName+    , ServiceName+    ) where++import Network.Secure.Connection+import Network.Secure.Identity++-- $tutorial+--+-- 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+-- >   writeLocalIdentity 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" >>= readLocalIdentity+-- >   writePeerIdentity (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" >>= readLocalIdentity+-- >   you    <- readFile "b.pub" >>= readPeerIdentity+-- >   server <- newServer (Nothing, "4242")+-- >   conn   <- accept me [you] server+--+-- Then, another host needs to connect.+--+-- > do+-- >   me   <- readFile "b.key" >>= readLocalIdentity+-- >   you  <- readFile "a.pub" >>= readPeerIdentity+-- >   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 >>= putStrLn+-- >   close conn++-- $internals+--+-- 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+-- RSA keys.+--+-- 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.
+ Network/Secure/Connection.hs view
@@ -0,0 +1,151 @@+module Network.Secure.Connection+    (+      HostName+    , ServiceName++    , Connection+    , peer+    , Network.Secure.Connection.connect+    , Network.Secure.Connection.read+    , Network.Secure.Connection.write+    , close+    +    , Network.Secure.Connection.Socket+    , newServer+    , Network.Secure.Connection.accept+    ) where++import Prelude hiding (read)++import Control.Applicative ((<$>))+import Control.Exception (IOException, bracketOnError, onException, try)+import Control.Monad (liftM, unless)+import Data.ByteString (ByteString)+import Data.Maybe (fromJust)+import OpenSSL.Session (ShutdownType(Unidirectional), SSLContext, SSL,+                        VerificationMode(VerifyPeer), accept, connect,+                        connection, context, contextSetPrivateKey,+                        contextSetCertificate, contextSetCiphers,+                        contextSetVerificationMode, contextGetCAStore,+                        getPeerCertificate, getVerifyResult, read, shutdown,+                        write)+import OpenSSL.X509 (compareX509)+import OpenSSL.X509.Store (addCertToStore)+import Network.Socket hiding (shutdown)++import Network.Secure.Identity++-- |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.+data Connection = C +    {+      ssl  :: SSL+      -- |Return the 'PeerIdentity' of the remote end of the connection.+    , peer :: PeerIdentity+    }++-- |A server socket that accepts only secure connections.+newtype Socket = S { +    unSocket :: Network.Socket.Socket+    }++-- |Connect securely to the given host/port. The 'Connection' is+-- returned only if the peer accepts the given 'LocalIdentity', and if+-- the remote endpoint successfully authenticates as the given+-- 'PeerIdentity'.+connect :: LocalIdentity -> PeerIdentity -> (HostName, ServiceName)+        -> IO Connection+connect myId peerId (host, port) = bracketOnError newSock sClose tryConnect+  where+    tryConnect sock = do+        addr <- getSockAddr (Just host) port+        Network.Socket.connect sock addr+        connectSSL myId [peerId] False sock++-- |Read at most 'n' bytes from the given connection.+read :: Connection -> Int -> IO ByteString+read = OpenSSL.Session.read . ssl++-- |Send data to the connected peer.+write :: Connection -> ByteString -> IO ()+write = OpenSSL.Session.write . ssl++-- |Close the connection. No other operations on 'Connection's should+-- be used after closing it.+close :: Connection -> IO ()+close conn = shutdown (ssl conn) Unidirectional++-- |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.+newServer :: (Maybe HostName, ServiceName)+          -> IO Network.Secure.Connection.Socket+newServer (host, port) = do+    addr <- getSockAddr host port+    sock <- newSock+    bindSocket sock addr+    listen sock 10+    return $ S sock++-- |Accept one secure connection from a remote peer. The peer may+-- authenticate as any of the given peer identities. A 'Connection' is+-- returned iff the autentication completes successfully.+accept :: LocalIdentity -> [PeerIdentity] -> Network.Secure.Connection.Socket +       -> IO Connection+accept myId peerIds listenSock = do+    result <- try setup :: IO (Either IOException Connection)+    case result of+        Left _     -> Network.Secure.Connection.accept myId peerIds listenSock+        Right conn -> return conn+  where+    setup = do+        sock <- fst <$> Network.Socket.accept (unSocket listenSock)+        connectSSL myId peerIds True sock++getSockAddr :: Maybe HostName -> ServiceName -> IO SockAddr+getSockAddr hn sn = do+    let hints = defaultHints { addrFlags = [AI_PASSIVE, AI_ADDRCONFIG] }+    info <- getAddrInfo (Just hints) hn (Just sn)+    return . addrAddress . head $ info++connectSSL :: LocalIdentity -> [PeerIdentity] -> Bool -> Network.Socket.Socket+           -> IO Connection+connectSSL myId peerIds isServer sock = do+    sslCtx <- newSSLContext myId peerIds+    conn <- connection sslCtx sock+    flip onException (shutdown conn Unidirectional) $ do+        initiate conn+        verifyConnection conn >>= flip unless (fail "Peer verification error")+        peerId <- fromX509 . fromJust <$> getPeerCertificate conn+        return $ C conn peerId+  where+    verifyConnection conn = do+        verified <- getVerifyResult conn+        if not verified then return False else+            getPeerCertificate conn >>= \c -> case c of+                Nothing   -> return False+                Just cert -> do+                    let match = liftM (EQ ==) . compareX509 cert . piX509+                    anyM match peerIds+    initiate = if isServer+               then OpenSSL.Session.accept+               else OpenSSL.Session.connect++newSock :: IO Network.Socket.Socket+newSock = socket AF_INET Stream defaultProtocol++newSSLContext :: LocalIdentity -> [PeerIdentity] -> IO SSLContext+newSSLContext localId validCerts = do+    ctx <- context+    contextSetPrivateKey ctx (liKey localId)+    contextSetCertificate ctx (liX509 localId)+    contextSetCiphers ctx "AES256-SHA"+    contextSetVerificationMode ctx $ VerifyPeer True False+    store <- contextGetCAStore ctx+    mapM_ (addCertToStore store . piX509) validCerts+    return ctx++anyM :: (Monad m) => (a -> m Bool) -> [a] -> m Bool+anyM _ []        = return False+anyM test (x:xs) = test x >>= \r -> if r then return True else anyM test xs
+ Network/Secure/Identity.hs view
@@ -0,0 +1,132 @@+module Network.Secure.Identity+    (+      PeerIdentity+    , readPeerIdentity+    , writePeerIdentity+      +    , LocalIdentity+    , readLocalIdentity+    , writeLocalIdentity+    , toPeerIdentity+    , newLocalIdentity+      +    , piX509+    , liX509+    , liKey+    , fromX509+    ) where++import Control.Applicative ((<$>))+import Control.Exception (bracket)+import Control.Monad (when)+import Data.ByteString (ByteString, append, hPut)+import Data.ByteString.Char8 (pack, unpack)+import Data.Maybe (fromJust, isNothing)+import HSH+import OpenSSL.EVP.PKey (toKeyPair)+import OpenSSL.PEM (PemPasswordSupply(PwNone), readPrivateKey,+                    writePKCS8PrivateKey, readX509, writeX509)+import OpenSSL.RSA (RSAKeyPair)+import OpenSSL.Session (context, contextSetPrivateKey,+                        contextSetCertificate, contextCheckPrivateKey)+import OpenSSL.X509 (X509, compareX509)+import System.Directory (getTemporaryDirectory, removeFile)+import System.IO (openBinaryTempFile, hFlush)+import System.IO.Unsafe (unsafePerformIO)++-- |The public identity of a peer. This kind of identity can be used+-- to authenticate the remote ends of connections.+newtype PeerIdentity = PI {+      piX509 :: X509+    }++-- |Serialize a 'PeerIdentity' to a 'ByteString' for storage or+-- transmission.+writePeerIdentity :: PeerIdentity -> IO ByteString+writePeerIdentity (PI cert) = pack <$> writeX509 cert++-- |Read back a 'PeerIdentity' previously serialized with+-- 'writePeerIdentity'.+readPeerIdentity :: ByteString -> IO PeerIdentity+readPeerIdentity = fmap PI . readX509 . unpack++instance Eq PeerIdentity where+    a == b = compare a b == EQ++instance Ord PeerIdentity where+    compare (PI a) (PI b) = unsafePerformIO $ compareX509 a b++fromX509 :: X509 -> PeerIdentity+fromX509 = PI++-- |A local identity. This kind of identity can be used to+-- authenticate /to/ remote ends of connections.+data LocalIdentity = LI+    {+      liX509 :: X509 +    , liKey  :: RSAKeyPair+    }++instance Eq LocalIdentity where+    a == b = compare a b == EQ++instance Ord LocalIdentity where+    compare (LI c1 k1) (LI c2 k2) =+        case compare (PI c1) (PI c2) of+            EQ -> compare k1 k2+            GT -> GT+            LT -> LT++-- |Serialize a 'LocalIdentity' to a 'ByteString' for storage.+writeLocalIdentity :: LocalIdentity -> IO ByteString+writeLocalIdentity (LI cert key) = do+    c <- writeX509 cert+    k <- writePKCS8PrivateKey key Nothing+    return $ pack (c ++ k)++-- |Read back a 'LocalIdentity' previously serialized with+-- 'writeLocalIdentity'.+readLocalIdentity :: ByteString -> IO LocalIdentity+readLocalIdentity b = do+    let s = unpack b+    cert <- readX509 s+    key <- toKeyPair <$> readPrivateKey s PwNone+    when (isNothing key) $ fail "Bad private key"+    certMatchesKey cert (fromJust key) >>= \r ->+        if r+        then return $ LI cert (fromJust key)+        else fail "Cert and key don't match"++-- |Extract the public parts of a 'LocalIdentity' into a+-- 'PeerIdentity' suitable for sharing with peers. The resulting+-- 'PeerIdentity' will allow them to verify your identity when you+-- authenticate using the corresponding 'LocalIdentity'.+toPeerIdentity :: LocalIdentity -> PeerIdentity+toPeerIdentity (LI cert _) = PI cert++-- |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.+newLocalIdentity :: String -> Int -> IO LocalIdentity+newLocalIdentity commonName days = bracket mkKeyFile rmKeyFile $ \(p,h) -> do+    key <- run genKey+    hPut h key >> hFlush h+    cert <- run $ genCert p+    readLocalIdentity $ append key cert+  where+    mkKeyFile = getTemporaryDirectory >>= flip openBinaryTempFile "key.pem"+    rmKeyFile = removeFile . fst+    genKey = "openssl genrsa 4096 2>/dev/null"+    genCert p = ("openssl", ["req", "-batch", "-new", "-x509",+                             "-key", p, "-nodes",+                             "-subj", "/CN=" ++ commonName,+                             "-days", show days])++certMatchesKey :: X509 -> RSAKeyPair -> IO Bool+certMatchesKey cert key = do+    ctx <- context+    contextSetPrivateKey ctx key+    contextSetCertificate ctx cert+    contextCheckPrivateKey ctx
+ Setup.hs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+import Distribution.Simple+main = defaultMain
+ secure-sockets.cabal view
@@ -0,0 +1,34 @@+Name:                secure-sockets+Version:             1.0+Synopsis:            Secure point-to-point connectivity library+Description:+    This library simplifies the task of securely connecting two+    servers to each other, with strong authentication and+    encryption on the wire.+Homepage:            http://code.google.com/p/secure-hs/+License:             BSD3+License-file:        LICENSE+Author:              David Anderson+Maintainer:          dave@natulte.net+Copyright:           Google Inc. 2010+Stability:           Experimental+Category:            Network+Build-type:          Simple+Cabal-version:       >=1.6++Library+  Exposed-modules:     Network.Secure+  Other-modules:       Network.Secure.Identity+                       Network.Secure.Connection+  Build-depends:       base ==4.*,+                       bytestring ==0.9.*,+                       directory ==1.0.*,+                       HSH ==2.0.*,+                       HsOpenSSL ==0.8.*,+                       network ==2.*+  ghc-prof-options:    -auto-all+  ghc-options:         -Wall -funbox-strict-fields -fwarn-tabs++Source-repository      head+  Type:                hg+  Location:            http://secure-hs.googlecode.com/hg