@@ -28,7 +28,7 @@ module Network.Wai.Handler.WarpTLS (
28import qualified Network.TLS as TLS
28import qualified Network.TLS as TLS
29import Network.Wai.Handler.Warp
29import Network.Wai.Handler.Warp
30import Network.Wai (Application)
30import Network.Wai (Application)
31−import Network.Socket (Socket, sClose, withSocketsDo)
31+import Network.Socket (Socket, sClose, withSocketsDo, SockAddr)
32import qualified Data.ByteString.Lazy as L
32import qualified Data.ByteString.Lazy as L
33import Control.Exception (bracket, finally, handle, fromException, try, IOException)
33import Control.Exception (bracket, finally, handle, fromException, try, IOException)
34import qualified Network.TLS.Extra as TLSExtra
34import qualified Network.TLS.Extra as TLSExtra
@@ -47,169 +47,76 @@ import Data.ByteString.Lazy.Internal (defaultChunkSize)
47import qualified System.IO as IO
47import qualified System.IO as IO
48import System.IO.Error (isEOFError)
48import System.IO.Error (isEOFError)
50+----------------------------------------------------------------
50−data TLSSettings = TLSSettings
52+data TLSSettings = TLSSettings {
51− { certFile :: FilePath
53+ certFile :: FilePath
52− -- ^ File containing the certificate.
54+ -- ^ File containing the certificate.
53− , keyFile :: FilePath
55+ , keyFile :: FilePath
54− -- ^ File containing the key
56+ -- ^ File containing the key
55− , onInsecure :: OnInsecure
57+ , onInsecure :: OnInsecure
56− -- ^ Do we allow insecure connections with this server as well? Default
58+ -- ^ Do we allow insecure connections with this server as well? Default
57− -- is a simple text response stating that a secure connection is required.
59+ -- is a simple text response stating that a secure connection is required.
59− -- Since 1.4.0
61+ -- Since 1.4.0
60− , tlsLogging :: TLS.Logging
62+ , tlsLogging :: TLS.Logging
61− -- ^ The level of logging to turn on.
63+ -- ^ The level of logging to turn on.
63− -- Default: 'TLS.defaultLogging'.
65+ -- Default: 'TLS.defaultLogging'.
65− -- Since 1.4.0
67+ -- Since 1.4.0
66− , tlsAllowedVersions :: [TLS.Version]
68+ , tlsAllowedVersions :: [TLS.Version]
67− -- ^ The TLS versions this server accepts.
69+ -- ^ The TLS versions this server accepts.
69− -- Default: '[TLS.SSL3,TLS.TLS10,TLS.TLS11,TLS.TLS12]'.
71+ -- Default: '[TLS.SSL3,TLS.TLS10,TLS.TLS11,TLS.TLS12]'.
71− -- Since 1.4.2
73+ -- Since 1.4.2
72− , tlsCiphers :: [TLS.Cipher]
74+ , tlsCiphers :: [TLS.Cipher]
73− -- ^ The TLS ciphers this server accepts.
75+ -- ^ The TLS ciphers this server accepts.
75− -- Default: '[TLSExtra.cipher_AES128_SHA1, TLSExtra.cipher_AES256_SHA1, TLSExtra.cipher_RC4_128_MD5, TLSExtra.cipher_RC4_128_SHA1]'
77+ -- Default: '[TLSExtra.cipher_AES128_SHA1, TLSExtra.cipher_AES256_SHA1, TLSEtra.cipher_RC4_128_MD5, TLSExtra.cipher_RC4_128_SHA1]'
77− -- Since 1.4.2
79+ -- Since 1.4.2
82+-- | Default 'TLSSettings'. Use this to create 'TLSSettings' with the field record name.
83+defaultTlsSettings :: TLSSettings
84+defaultTlsSettings = TLSSettings {
85+ certFile = "certificate.pem"
86+ , keyFile = "key.pem"
87+ , onInsecure = DenyInsecure "This server only accepts secure HTTPS connections."
89+ , tlsAllowedVersions = [TLS.SSL3,TLS.TLS10,TLS.TLS11,TLS.TLS12]
90+ , tlsCiphers = ciphers
93+-- taken from stunnel example in tls-extra
94+ciphers :: [TLS.Cipher]
96+ [ TLSExtra.cipher_AES128_SHA1
97+ , TLSExtra.cipher_AES256_SHA1
98+ , TLSExtra.cipher_RC4_128_MD5
99+ , TLSExtra.cipher_RC4_128_SHA1
102+----------------------------------------------------------------
80-- | An action when a plain HTTP comes to HTTP over TLS/SSL port.
104-- | An action when a plain HTTP comes to HTTP over TLS/SSL port.
81data OnInsecure = DenyInsecure L.ByteString
105data OnInsecure = DenyInsecure L.ByteString
82 | AllowInsecure
106 | AllowInsecure
108+----------------------------------------------------------------
84-- | A smart constructor for 'TLSSettings'.
110-- | A smart constructor for 'TLSSettings'.
85tlsSettings :: FilePath -- ^ Certificate file
111tlsSettings :: FilePath -- ^ Certificate file
86 -> FilePath -- ^ Key file
112 -> FilePath -- ^ Key file
87 -> TLSSettings
113 -> TLSSettings
88−tlsSettings cert key = defaultTlsSettings
114+tlsSettings cert key = defaultTlsSettings {
89− { certFile = cert
115+ certFile = cert
90− , keyFile = key
116+ , keyFile = key
93−-- | Default 'TLSSettings'. Use this to create 'TLSSettings' with the field record name.
119+----------------------------------------------------------------
94−defaultTlsSettings :: TLSSettings
95−defaultTlsSettings = TLSSettings
96− { certFile = "certificate.pem"
97− , keyFile = "key.pem"
98− , onInsecure = DenyInsecure "This server only accepts secure HTTPS connections."
100− , tlsAllowedVersions = [TLS.SSL3,TLS.TLS10,TLS.TLS11,TLS.TLS12]
101− , tlsCiphers = ciphers
104−-- | Running 'Application' with 'TLSSettings' and 'Settings' using
105−-- specified 'Socket'.
106−runTLSSocket :: TLSSettings -> Settings -> Socket -> Application -> IO ()
107−runTLSSocket TLSSettings {..} set sock app = do
108− credential <- either error id <$> TLS.credentialLoadX509 certFile keyFile
110− { TLS.serverWantClientCert = False
111− , TLS.serverSupported = def
112− { TLS.supportedVersions = tlsAllowedVersions
113− , TLS.supportedCiphers = tlsCiphers
115− , TLS.serverShared = def
116− { TLS.sharedCredentials = TLS.Credentials [credential]
119− runSettingsConnectionMakerSecure set (getter params) app
122− (s, sa) <- acceptSafe sock
123− let mkConn :: IO (Connection, Bool)
125− firstBS <- safeRecv s 4096
126− cachedRef <- I.newIORef firstBS
127− let getNext size = do
128− cached <- I.readIORef cachedRef
131− loop bs | B.length bs >= size = do
132− let (x, y) = B.splitAt size bs
133− I.writeIORef cachedRef y
136− bs2 <- safeRecv s 4096
139− -- FIXME does this deserve an exception being thrown?
140− I.writeIORef cachedRef B.empty
142− else loop $ B.append bs1 bs2
143− if not (B.null firstBS) && B.head firstBS == 0x16
145− gen <- Crypto.Random.AESCtr.makeSystem
146− ctx <- TLS.contextNew
148− { TLS.backendFlush = return ()
149− , TLS.backendClose = sClose s
150− , TLS.backendSend = sendAll s
151− , TLS.backendRecv = getNext
155− TLS.contextHookSetLogging ctx tlsLogging
157− readBuf <- allocateBuffer bufferSize
158− writeBuf <- allocateBuffer bufferSize
159− let conn = Connection
160− { connSendMany = TLS.sendData ctx . L.fromChunks
161− , connSendAll = TLS.sendData ctx . L.fromChunks . return
162− , connSendFile = \fp offset len _th headers -> do
163− TLS.sendData ctx $ L.fromChunks headers
164− IO.withBinaryFile fp IO.ReadMode $ \h -> do
165− IO.hSeek h IO.AbsoluteSeek offset
166− let loop remaining | remaining <= 0 = return ()
168− bs <- B.hGetSome h defaultChunkSize
169− unless (B.null bs) $ do
170− let x = B.take remaining bs
171− TLS.sendData ctx $ L.fromChunks [x]
172− loop $ remaining - B.length x
173− loop $ fromIntegral len
175− freeBuffer readBuf `finally`
176− freeBuffer writeBuf `finally`
177− void (tryIO $ TLS.bye ctx) `finally`
178− TLS.contextClose ctx
181− | Just TLS.Error_EOF <- fromException e = return B.empty
182− | Just ioe <- fromException e, isEOFError ioe = return B.empty
183− | otherwise = throwIO e
185− x <- TLS.recvData ctx
190− , connSendFileOverride = NotOverride
191− , connReadBuffer = readBuf
192− , connWriteBuffer = writeBuf
193− , connBufferSize = bufferSize
199− conn' <- socketConnection s
201− { connRecv = getNext 4096
203− DenyInsecure lbs -> do
204− sendAll s "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"
205− mapM_ (sendAll s) $ L.toChunks lbs
207− throwIO InsecureConnectionDenied
210−data WarpTLSException = InsecureConnectionDenied
211− deriving (Show, Typeable)
212−instance Exception WarpTLSException
214-- | Running 'Application' with 'TLSSettings' and 'Settings'.
121-- | Running 'Application' with 'TLSSettings' and 'Settings'.
215runTLS :: TLSSettings -> Settings -> Application -> IO ()
122runTLS :: TLSSettings -> Settings -> Application -> IO ()
@@ -219,14 +126,146 @@ runTLS tset set app = withSocketsDo $
220 (\sock -> runTLSSocket tset set sock app)
127 (\sock -> runTLSSocket tset set sock app)
222−-- taken from stunnel example in tls-extra
129+----------------------------------------------------------------
223−ciphers :: [TLS.Cipher]
130+
224−ciphers =
131+-- | Running 'Application' with 'TLSSettings' and 'Settings' using
225− [ TLSExtra.cipher_AES128_SHA1
132+-- specified 'Socket'.
226− , TLSExtra.cipher_AES256_SHA1
133+runTLSSocket :: TLSSettings -> Settings -> Socket -> Application -> IO ()
227− , TLSExtra.cipher_RC4_128_MD5
134+runTLSSocket tlsset@TLSSettings{..} set sock app = do
228− , TLSExtra.cipher_RC4_128_SHA1
135+ credential <- either error id <$> TLS.credentialLoadX509 certFile keyFile
229− ]
136+ runTLSSocket' tlsset set credential sock app
138+runTLSSocket' :: TLSSettings -> Settings -> TLS.Credential -> Socket -> Application -> IO ()
139+runTLSSocket' tlsset@TLSSettings{..} set credential sock app =
140+ runSettingsConnectionMakerSecure set get app
142+ get = getter tlsset sock params
144+ TLS.serverWantClientCert = False
145+ , TLS.serverSupported = def {
146+ TLS.supportedVersions = tlsAllowedVersions
147+ , TLS.supportedCiphers = tlsCiphers
149+ , TLS.serverShared = def {
150+ TLS.sharedCredentials = TLS.Credentials [credential]
154+----------------------------------------------------------------
156+getter :: TLS.TLSParams params => TLSSettings -> Socket -> params -> IO (IO (Connection, Bool), SockAddr)
157+getter tlsset@TLSSettings{..} sock params = do
158+ (s, sa) <- acceptSafe sock
159+ return (mkConn tlsset s params, sa)
161+mkConn :: TLS.TLSParams params => TLSSettings -> Socket -> params -> IO (Connection, Bool)
162+mkConn tlsset s params = do
163+ firstBS <- safeRecv s 4096
164+ cachedRef <- I.newIORef firstBS
165+ if not (B.null firstBS) && B.head firstBS == 0x16 then
166+ httpOverTls tlsset s cachedRef params
168+ plainHTTP tlsset s cachedRef
170+----------------------------------------------------------------
172+httpOverTls :: TLS.TLSParams params => TLSSettings -> Socket -> I.IORef B.ByteString -> params -> IO (Connection, Bool)
173+httpOverTls TLSSettings{..} s cachedRef params = do
174+ gen <- Crypto.Random.AESCtr.makeSystem
175+ ctx <- TLS.contextNew backend params gen
176+ TLS.contextHookSetLogging ctx tlsLogging
178+ readBuf <- allocateBuffer bufferSize
179+ writeBuf <- allocateBuffer bufferSize
180+ return (conn ctx readBuf writeBuf, True)
182+ backend = TLS.Backend {
183+ TLS.backendFlush = return ()
184+ , TLS.backendClose = sClose s
185+ , TLS.backendSend = sendAll s
186+ , TLS.backendRecv = getNext cachedRef s
188+ conn ctx readBuf writeBuf = Connection {
189+ connSendMany = TLS.sendData ctx . L.fromChunks
190+ , connSendAll = TLS.sendData ctx . L.fromChunks . return
191+ , connSendFile = sendfile
194+ , connSendFileOverride = NotOverride
195+ , connReadBuffer = readBuf
196+ , connWriteBuffer = writeBuf
197+ , connBufferSize = bufferSize
200+ sendfile fp offset len _th headers = do
201+ TLS.sendData ctx $ L.fromChunks headers
202+ IO.withBinaryFile fp IO.ReadMode $ \h -> do
203+ IO.hSeek h IO.AbsoluteSeek offset
204+ loop h $ fromIntegral len
206+ loop _ remaining | remaining <= 0 = return ()
207+ loop h remaining = do
208+ bs <- B.hGetSome h defaultChunkSize
209+ unless (B.null bs) $ do
210+ let x = B.take remaining bs
211+ TLS.sendData ctx $ L.fromChunks [x]
212+ loop h $ remaining - B.length x
214+ close = freeBuffer readBuf `finally`
215+ freeBuffer writeBuf `finally`
216+ void (tryIO $ TLS.bye ctx) `finally`
217+ TLS.contextClose ctx
219+ recv = handle onEOF go
222+ | Just TLS.Error_EOF <- fromException e = return B.empty
223+ | Just ioe <- fromException e, isEOFError ioe = return B.empty | otherwise = throwIO e
225+ x <- TLS.recvData ctx
231tryIO :: IO a -> IO (Either IOException a)
231tryIO :: IO a -> IO (Either IOException a)
232tryIO = try
232tryIO = try
234+----------------------------------------------------------------
236+plainHTTP :: TLSSettings -> Socket -> I.IORef B.ByteString -> IO (Connection, Bool)
237+plainHTTP TLSSettings{..} s cachedRef = case onInsecure of
239+ conn' <- socketConnection s
240+ return (conn' { connRecv = getNext cachedRef s 4096 }, False)
241+ DenyInsecure lbs -> do
242+ sendAll s "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"
243+ mapM_ (sendAll s) $ L.toChunks lbs
245+ throwIO InsecureConnectionDenied
247+----------------------------------------------------------------
249+getNext :: I.IORef B.ByteString -> Socket -> Int -> IO B.ByteString
250+getNext cachedRef s size = do
251+ cached <- I.readIORef cachedRef
254+ loop bs | B.length bs >= size = do
255+ let (x, y) = B.splitAt size bs
256+ I.writeIORef cachedRef y
259+ bs2 <- safeRecv s 4096
260+ if B.null bs2 then do
261+ -- FIXME does this deserve an exception being thrown?
262+ I.writeIORef cachedRef B.empty
265+ loop $ B.append bs1 bs2
267+----------------------------------------------------------------
269+data WarpTLSException = InsecureConnectionDenied
270+ deriving (Show, Typeable)
271+instance Exception WarpTLSException