mariadb-haskell-0.1.0.0: src/Database/MySQL/TLS.hs
{-|
Module : Database.MySQL.TLS
Description : MariaDB TLS connections via the @tls@ package
Copyright : (c) Winterland, 2016
License : BSD
Maintainer : Anton Antich <anton.antich@gmail.com>
Stability : experimental
Portability : macOS and Linux tested
TLS connections use the same ownership, limits and session initialization as
TCP. Supply certificate/hostname validation through t'Network.TLS.ClientParams'.
Use 'makeClientParams' with 'SystemCAStore' or a managed 'CustomCAStore', then
reuse the resulting parameters in the connection factory. The tuple's name is
the server certificate DNS identity (and default SNI), which can differ from
the address in 'ciHost'. Do not disable verification to work around CA/name
errors. @makeClientParams'@ also provides a client certificate for X.509 accounts.
@MozillaCAStore@ uses an installed static snapshot, not automatically updated
OS roots; see the packaged @docs/CA_BUNDLE.md@ for provenance and update policy.
TLS protects transport; PAM/GSSAPI options do not replace peer verification. See
the <https://github.com/aantich/mariadb-haskell/blob/main/docs/USER_GUIDE.md connection guide>.
-}
module Database.MySQL.TLS (
connect
, connectDetail
, connectWithOptions
, connectWithAuthentication
, connectDetailWithAuthentication
, connectDetailWithOptions
, module Data.TLSSetting
) where
import Control.Exception (bracketOnError, throwIO, finally)
import qualified Data.Binary as Binary
import qualified Data.Binary.Put as Binary
import qualified Data.Connection as Conn
import Data.IORef (writeIORef)
import Data.Bits (testBit)
import Control.Monad (when)
import Data.TLSSetting
import Database.MySQL.Authentication
import Database.MySQL.Connection hiding (connect, connectDetail, connectWithOptions, connectDetailWithOptions, connectWithAuthentication, connectDetailWithAuthentication)
import Database.MySQL.Protocol.Auth
import Database.MySQL.Protocol.Packet
import qualified Network.TLS as TLS
import qualified System.IO.Streams.TCP as TCP
import qualified System.IO.Streams.TLS as TLS
--------------------------------------------------------------------------------
-- | Provide a t'Network.TLS.ClientParams' and a subject name to establish a TLS connection.
--
connect :: ConnectInfo -> (TLS.ClientParams,String) -> IO MySQLConn
connect = connectWithOptions defaultConnectionOptions
-- | Connect over TLS with explicit resource/session options. Failure during
-- handshake, authentication or initialization closes both TLS and raw transport.
connectWithOptions :: ConnectionOptions -> ConnectInfo -> (TLS.ClientParams,String) -> IO MySQLConn
connectWithOptions options ci params = snd <$> connectDetailWithOptions options ci params
-- | Establish TLS and return the MariaDB greeting alongside the connection.
connectDetail :: ConnectInfo -> (TLS.ClientParams,String) -> IO (Greeting,MySQLConn)
connectDetail = connectDetailWithOptions defaultConnectionOptions
-- | Explicit-options variant of 'connectDetail'. The plaintext greeting uses
-- sequence zero, SSLRequest one and the authenticated TLS response sequence two.
connectDetailWithOptions :: ConnectionOptions -> ConnectInfo -> (TLS.ClientParams,String) -> IO (Greeting,MySQLConn)
connectDetailWithOptions options = connectDetailWithAuthentication options defaultAuthenticationOptions
-- | TLS connection with explicit PAM callbacks or a pinned GSSAPI principal.
-- Certificate verification remains the responsibility of the supplied TLS
-- parameters; external authentication does not replace peer verification.
connectWithAuthentication :: ConnectionOptions -> AuthenticationOptions -> ConnectInfo -> (TLS.ClientParams,String) -> IO MySQLConn
connectWithAuthentication options auth ci params = snd <$> connectDetailWithAuthentication options auth ci params
-- | Greeting-returning variant of 'connectWithAuthentication'.
connectDetailWithAuthentication :: ConnectionOptions -> AuthenticationOptions -> ConnectInfo -> (TLS.ClientParams,String) -> IO (Greeting,MySQLConn)
connectDetailWithAuthentication options auth ci (params,name) = withConnectDeadline options $ do
validateOptions options
bracketOnError (TCP.connectSocketTimeout (connectTimeoutMicros options) (ciHost ci) (ciPort ci) >>= TCP.socketToConnectionAdaptive bUFSIZE) Conn.close $ \raw -> do
input <- decodeInputStreamWithLimit (authenticationWireLimit options) (Conn.source raw)
packet <- readPacket input
if pSeqN packet /= 0 then throwIO (ProtocolError "invalid greeting sequence") else pure ()
if isERR packet then authenticationDiagnostic (decodeFromPacket packet) >>= throwIO . ERRException else pure ()
greeting <- authenticationDiagnostic (getFromPacketExact getGreeting packet)
if not (supportTLS (greetingCaps greeting)) then throwIO (AuthException "MariaDB server does not offer TLS") else pure ()
let caps=negotiatedCapabilities options greeting
params'=params {TLS.clientServerIdentification=(name,"")}
(sock,addr)=Conn.connExtraInfo raw
ssl=SSLRequest (caps + 0x800) (fromIntegral (maximumMessageBytes options)) (ciCharset ci)
Conn.send raw (Binary.runPut (Binary.put (encodeToPacket 1 ssl)))
bracketOnError (TLS.contextNew sock params') (\ctx -> TLS.contextClose ctx `finally` Conn.close raw) $ \ctx -> do
TLS.handshake ctx
secure <- TLS.tLsToConnection (ctx,addr)
(authConn,conn) <- newAuthenticatingTransportConnection options (Conn.source secure) (Conn.send secure) (TLS.contextClose ctx `finally` Conn.close raw)
setExchangeSequence conn 2
writeIORef (connectionCapabilities conn) (caps+0x800)
mysqlWrite conn (encodeToPacket 2 (mkAuthForAuthentication auth options (caps+0x800) ci greeting))
response <- authenticationDiagnostic (readConnPacket authConn)
completeAuthWithOptions auth authConn greeting True (ciPassword ci) response
when (testBit caps 5) (connectionCompressionStart conn)
role <- initializeSession conn
rememberInitialSession conn ci role
pure (greeting,conn)