{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} -- | Basic extensions are defined in RFC 6066 module Network.TLS.Extension ( Extension (..), supportedExtensions, definedExtensions, -- all implemented extensions ServerNameType (..), ServerName (..), MaxFragmentLength (..), MaxFragmentEnum (..), SecureRenegotiation (..), ApplicationLayerProtocolNegotiation (..), ExtendedMainSecret (..), SupportedGroups (..), Group (..), EcPointFormatsSupported (..), EcPointFormat ( EcPointFormat, EcPointFormat_Uncompressed, EcPointFormat_AnsiX962_compressed_prime, EcPointFormat_AnsiX962_compressed_char2 ), SessionTicket (..), HeartBeat (..), HeartBeatMode ( HeartBeatMode, HeartBeat_PeerAllowedToSend, HeartBeat_PeerNotAllowedToSend ), SignatureAlgorithms (..), SignatureAlgorithmsCert (..), SupportedVersions (..), KeyShare (..), KeyShareEntry (..), MessageType (..), PostHandshakeAuth (..), PskKexMode (PskKexMode, PSK_KE, PSK_DHE_KE), PskKeyExchangeModes (..), PskIdentity (..), PreSharedKey (..), EarlyDataIndication (..), Cookie (..), CertificateAuthorities (..), ) where import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC import Network.TLS.Crypto.Types import Network.TLS.Struct import Network.TLS.Types (HostName, Ticket) import Network.TLS.Imports import Network.TLS.Packet ( getBinaryVersion, getDNames, getSignatureHashAlgorithm, putBinaryVersion, putDNames, putSignatureHashAlgorithm, ) import Network.TLS.Wire ------------------------------------------------------------ definedExtensions :: [ExtensionID] definedExtensions = [ EID_ServerName , EID_MaxFragmentLength , EID_ClientCertificateUrl , EID_TrustedCAKeys , EID_TruncatedHMAC , EID_StatusRequest , EID_UserMapping , EID_ClientAuthz , EID_ServerAuthz , EID_CertType , EID_SupportedGroups , EID_EcPointFormats , EID_SRP , EID_SignatureAlgorithms , EID_SRTP , EID_Heartbeat , EID_ApplicationLayerProtocolNegotiation , EID_StatusRequestv2 , EID_SignedCertificateTimestamp , EID_ClientCertificateType , EID_ServerCertificateType , EID_Padding , EID_EncryptThenMAC , EID_ExtendedMainSecret , EID_SessionTicket , EID_PreSharedKey , EID_EarlyData , EID_SupportedVersions , EID_Cookie , EID_PskKeyExchangeModes , EID_KeyShare , EID_SignatureAlgorithmsCert , EID_CertificateAuthorities , EID_SecureRenegotiation , EID_QuicTransportParameters ] -- | all supported extensions by the implementation supportedExtensions :: [ExtensionID] supportedExtensions = [ EID_ServerName , EID_MaxFragmentLength , EID_ApplicationLayerProtocolNegotiation , EID_ExtendedMainSecret , EID_SecureRenegotiation , EID_SupportedGroups , EID_EcPointFormats , EID_SignatureAlgorithms , EID_SignatureAlgorithmsCert , EID_KeyShare , EID_PreSharedKey , EID_EarlyData , EID_SupportedVersions , EID_Cookie , EID_PskKeyExchangeModes , EID_CertificateAuthorities , EID_QuicTransportParameters ] ------------------------------------------------------------ data MessageType = MsgTClientHello | MsgTServerHello | MsgTHelloRetryRequest | MsgTEncryptedExtensions | MsgTNewSessionTicket | MsgTCertificateRequest deriving (Eq, Show) -- | Extension class to transform bytes to and from a high level Extension type. class Extension a where extensionID :: a -> ExtensionID extensionDecode :: MessageType -> ByteString -> Maybe a extensionEncode :: a -> ByteString ------------------------------------------------------------ -- | Server Name extension including the name type and the associated name. -- the associated name decoding is dependant of its name type. -- name type = 0 : hostname newtype ServerName = ServerName [ServerNameType] deriving (Show, Eq) data ServerNameType = ServerNameHostName HostName | ServerNameOther (Word8, ByteString) deriving (Show, Eq) instance Extension ServerName where extensionID _ = EID_ServerName extensionEncode (ServerName l) = runPut $ putOpaque16 (runPut $ mapM_ encodeNameType l) where encodeNameType (ServerNameHostName hn) = putWord8 0 >> putOpaque16 (BC.pack hn) -- FIXME: should be puny code conversion encodeNameType (ServerNameOther (nt, opaque)) = putWord8 nt >> putBytes opaque extensionDecode MsgTClientHello = decodeServerName extensionDecode MsgTServerHello = decodeServerName extensionDecode MsgTEncryptedExtensions = decodeServerName extensionDecode _ = error "extensionDecode: ServerName" decodeServerName :: ByteString -> Maybe ServerName decodeServerName = runGetMaybe $ do len <- fromIntegral <$> getWord16 ServerName <$> getList len getServerName where getServerName = do ty <- getWord8 snameParsed <- getOpaque16 let sname = B.copy snameParsed name = case ty of 0 -> ServerNameHostName $ BC.unpack sname -- FIXME: should be puny code conversion _ -> ServerNameOther (ty, sname) return (1 + 2 + B.length sname, name) ------------------------------------------------------------ -- | Max fragment extension with length from 512 bytes to 4096 bytes -- -- RFC 6066 defines: -- If a server receives a maximum fragment length negotiation request -- for a value other than the allowed values, it MUST abort the -- handshake with an "illegal_parameter" alert. -- -- So, if a server receives MaxFragmentLengthOther, it must send the alert. data MaxFragmentLength = MaxFragmentLength MaxFragmentEnum | MaxFragmentLengthOther Word8 deriving (Show, Eq) data MaxFragmentEnum = MaxFragment512 | MaxFragment1024 | MaxFragment2048 | MaxFragment4096 deriving (Show, Eq) instance Extension MaxFragmentLength where extensionID _ = EID_MaxFragmentLength extensionEncode (MaxFragmentLength l) = runPut $ putWord8 $ fromMaxFragmentEnum l where fromMaxFragmentEnum MaxFragment512 = 1 fromMaxFragmentEnum MaxFragment1024 = 2 fromMaxFragmentEnum MaxFragment2048 = 3 fromMaxFragmentEnum MaxFragment4096 = 4 extensionEncode (MaxFragmentLengthOther l) = runPut $ putWord8 l extensionDecode MsgTClientHello = decodeMaxFragmentLength extensionDecode MsgTServerHello = decodeMaxFragmentLength extensionDecode MsgTEncryptedExtensions = decodeMaxFragmentLength extensionDecode _ = error "extensionDecode: MaxFragmentLength" decodeMaxFragmentLength :: ByteString -> Maybe MaxFragmentLength decodeMaxFragmentLength = runGetMaybe $ toMaxFragmentEnum <$> getWord8 where toMaxFragmentEnum 1 = MaxFragmentLength MaxFragment512 toMaxFragmentEnum 2 = MaxFragmentLength MaxFragment1024 toMaxFragmentEnum 3 = MaxFragmentLength MaxFragment2048 toMaxFragmentEnum 4 = MaxFragmentLength MaxFragment4096 toMaxFragmentEnum n = MaxFragmentLengthOther n ------------------------------------------------------------ -- | Secure Renegotiation data SecureRenegotiation = SecureRenegotiation ByteString ByteString deriving (Show, Eq) instance Extension SecureRenegotiation where extensionID _ = EID_SecureRenegotiation extensionEncode (SecureRenegotiation cvd svd) = runPut $ putOpaque8 (cvd `B.append` svd) extensionDecode msgtype = runGetMaybe $ do opaque <- getOpaque8 case msgtype of MsgTServerHello -> let (cvd, svd) = B.splitAt (B.length opaque `div` 2) opaque in return $ SecureRenegotiation cvd svd MsgTClientHello -> return $ SecureRenegotiation opaque "" _ -> error "extensionDecode: SecureRenegotiation" ------------------------------------------------------------ -- | Application Layer Protocol Negotiation (ALPN) newtype ApplicationLayerProtocolNegotiation = ApplicationLayerProtocolNegotiation [ByteString] deriving (Show, Eq) instance Extension ApplicationLayerProtocolNegotiation where extensionID _ = EID_ApplicationLayerProtocolNegotiation extensionEncode (ApplicationLayerProtocolNegotiation bytes) = runPut $ putOpaque16 $ runPut $ mapM_ putOpaque8 bytes extensionDecode MsgTClientHello = decodeApplicationLayerProtocolNegotiation extensionDecode MsgTServerHello = decodeApplicationLayerProtocolNegotiation extensionDecode MsgTEncryptedExtensions = decodeApplicationLayerProtocolNegotiation extensionDecode _ = error "extensionDecode: ApplicationLayerProtocolNegotiation" decodeApplicationLayerProtocolNegotiation :: ByteString -> Maybe ApplicationLayerProtocolNegotiation decodeApplicationLayerProtocolNegotiation = runGetMaybe $ do len <- getWord16 ApplicationLayerProtocolNegotiation <$> getList (fromIntegral len) getALPN where getALPN = do alpnParsed <- getOpaque8 let alpn = B.copy alpnParsed return (B.length alpn + 1, alpn) ------------------------------------------------------------ -- | Extended Main Secret data ExtendedMainSecret = ExtendedMainSecret deriving (Show, Eq) instance Extension ExtendedMainSecret where extensionID _ = EID_ExtendedMainSecret extensionEncode ExtendedMainSecret = B.empty extensionDecode MsgTClientHello _ = Just ExtendedMainSecret extensionDecode MsgTServerHello _ = Just ExtendedMainSecret extensionDecode _ _ = error "extensionDecode: ExtendedMainSecret" ------------------------------------------------------------ newtype SupportedGroups = SupportedGroups [Group] deriving (Show, Eq) -- on decode, filter all unknown curves instance Extension SupportedGroups where extensionID _ = EID_SupportedGroups extensionEncode (SupportedGroups groups) = runPut $ putWords16 $ map (\(Group g) -> g) groups extensionDecode MsgTClientHello = decodeSupportedGroups extensionDecode MsgTEncryptedExtensions = decodeSupportedGroups extensionDecode _ = error "extensionDecode: SupportedGroups" decodeSupportedGroups :: ByteString -> Maybe SupportedGroups decodeSupportedGroups = runGetMaybe (SupportedGroups . map Group <$> getWords16) ------------------------------------------------------------ newtype EcPointFormatsSupported = EcPointFormatsSupported [EcPointFormat] deriving (Show, Eq) newtype EcPointFormat = EcPointFormat {fromEcPointFormat :: Word8} deriving (Eq) {- FOURMOLU_DISABLE -} pattern EcPointFormat_Uncompressed :: EcPointFormat pattern EcPointFormat_Uncompressed = EcPointFormat 0 pattern EcPointFormat_AnsiX962_compressed_prime :: EcPointFormat pattern EcPointFormat_AnsiX962_compressed_prime = EcPointFormat 1 pattern EcPointFormat_AnsiX962_compressed_char2 :: EcPointFormat pattern EcPointFormat_AnsiX962_compressed_char2 = EcPointFormat 2 instance Show EcPointFormat where show EcPointFormat_Uncompressed = "EcPointFormat_Uncompressed" show EcPointFormat_AnsiX962_compressed_prime = "EcPointFormat_AnsiX962_compressed_prime" show EcPointFormat_AnsiX962_compressed_char2 = "EcPointFormat_AnsiX962_compressed_char2" show (EcPointFormat x) = "EcPointFormat " ++ show x {- FOURMOLU_ENABLE -} -- on decode, filter all unknown formats instance Extension EcPointFormatsSupported where extensionID _ = EID_EcPointFormats extensionEncode (EcPointFormatsSupported formats) = runPut $ putWords8 $ map fromEcPointFormat formats extensionDecode MsgTClientHello = decodeEcPointFormatsSupported extensionDecode MsgTServerHello = decodeEcPointFormatsSupported extensionDecode _ = error "extensionDecode: EcPointFormatsSupported" decodeEcPointFormatsSupported :: ByteString -> Maybe EcPointFormatsSupported decodeEcPointFormatsSupported = runGetMaybe (EcPointFormatsSupported . map EcPointFormat <$> getWords8) ------------------------------------------------------------ newtype SessionTicket = SessionTicket Ticket deriving (Show, Eq) -- https://datatracker.ietf.org/doc/html/rfc5077#appendix-A instance Extension SessionTicket where extensionID _ = EID_SessionTicket extensionEncode (SessionTicket ticket) = runPut $ putBytes ticket extensionDecode MsgTClientHello = runGetMaybe $ SessionTicket <$> (remaining >>= getBytes) extensionDecode MsgTServerHello = runGetMaybe $ SessionTicket <$> (remaining >>= getBytes) extensionDecode _ = error "extensionDecode: SessionTicket" ------------------------------------------------------------ newtype HeartBeat = HeartBeat HeartBeatMode deriving (Show, Eq) newtype HeartBeatMode = HeartBeatMode {fromHeartBeatMode :: Word8} deriving (Eq) {- FOURMOLU_DISABLE -} pattern HeartBeat_PeerAllowedToSend :: HeartBeatMode pattern HeartBeat_PeerAllowedToSend = HeartBeatMode 1 pattern HeartBeat_PeerNotAllowedToSend :: HeartBeatMode pattern HeartBeat_PeerNotAllowedToSend = HeartBeatMode 2 instance Show HeartBeatMode where show HeartBeat_PeerAllowedToSend = "HeartBeat_PeerAllowedToSend" show HeartBeat_PeerNotAllowedToSend = "HeartBeat_PeerNotAllowedToSend" show (HeartBeatMode x) = "HeartBeatMode " ++ show x {- FOURMOLU_ENABLE -} instance Extension HeartBeat where extensionID _ = EID_Heartbeat extensionEncode (HeartBeat mode) = runPut $ putWord8 $ fromHeartBeatMode mode extensionDecode MsgTClientHello = decodeHeartBeat extensionDecode MsgTServerHello = decodeHeartBeat extensionDecode _ = error "extensionDecode: HeartBeat" decodeHeartBeat :: ByteString -> Maybe HeartBeat decodeHeartBeat = runGetMaybe $ HeartBeat . HeartBeatMode <$> getWord8 ------------------------------------------------------------ newtype SignatureAlgorithms = SignatureAlgorithms [HashAndSignatureAlgorithm] deriving (Show, Eq) instance Extension SignatureAlgorithms where extensionID _ = EID_SignatureAlgorithms extensionEncode (SignatureAlgorithms algs) = runPut $ putWord16 (fromIntegral (length algs * 2)) >> mapM_ putSignatureHashAlgorithm algs extensionDecode MsgTClientHello = decodeSignatureAlgorithms extensionDecode MsgTCertificateRequest = decodeSignatureAlgorithms extensionDecode _ = error "extensionDecode: SignatureAlgorithms" decodeSignatureAlgorithms :: ByteString -> Maybe SignatureAlgorithms decodeSignatureAlgorithms = runGetMaybe $ do len <- getWord16 sas <- getList (fromIntegral len) (getSignatureHashAlgorithm >>= \sh -> return (2, sh)) leftoverLen <- remaining when (leftoverLen /= 0) $ fail "decodeSignatureAlgorithms: broken length" return $ SignatureAlgorithms sas ------------------------------------------------------------ data PostHandshakeAuth = PostHandshakeAuth deriving (Show, Eq) instance Extension PostHandshakeAuth where extensionID _ = EID_PostHandshakeAuth extensionEncode _ = B.empty extensionDecode MsgTClientHello = runGetMaybe $ return PostHandshakeAuth extensionDecode _ = error "extensionDecode: PostHandshakeAuth" ------------------------------------------------------------ newtype SignatureAlgorithmsCert = SignatureAlgorithmsCert [HashAndSignatureAlgorithm] deriving (Show, Eq) instance Extension SignatureAlgorithmsCert where extensionID _ = EID_SignatureAlgorithmsCert extensionEncode (SignatureAlgorithmsCert algs) = runPut $ putWord16 (fromIntegral (length algs * 2)) >> mapM_ putSignatureHashAlgorithm algs extensionDecode MsgTClientHello = decodeSignatureAlgorithmsCert extensionDecode MsgTCertificateRequest = decodeSignatureAlgorithmsCert extensionDecode _ = error "extensionDecode: SignatureAlgorithmsCert" decodeSignatureAlgorithmsCert :: ByteString -> Maybe SignatureAlgorithmsCert decodeSignatureAlgorithmsCert = runGetMaybe $ do len <- getWord16 SignatureAlgorithmsCert <$> getList (fromIntegral len) (getSignatureHashAlgorithm >>= \sh -> return (2, sh)) ------------------------------------------------------------ data SupportedVersions = SupportedVersionsClientHello [Version] | SupportedVersionsServerHello Version deriving (Show, Eq) instance Extension SupportedVersions where extensionID _ = EID_SupportedVersions extensionEncode (SupportedVersionsClientHello vers) = runPut $ do putWord8 (fromIntegral (length vers * 2)) mapM_ putBinaryVersion vers extensionEncode (SupportedVersionsServerHello ver) = runPut $ putBinaryVersion ver extensionDecode MsgTClientHello = runGetMaybe $ do len <- fromIntegral <$> getWord8 SupportedVersionsClientHello <$> getList len getVer where getVer = do ver <- getBinaryVersion return (2, ver) extensionDecode MsgTServerHello = runGetMaybe (SupportedVersionsServerHello <$> getBinaryVersion) extensionDecode _ = error "extensionDecode: SupportedVersionsServerHello" ------------------------------------------------------------ data KeyShareEntry = KeyShareEntry { keyShareEntryGroup :: Group , keyShareEntryKeyExchange :: ByteString } deriving (Show, Eq) getKeyShareEntry :: Get (Int, Maybe KeyShareEntry) getKeyShareEntry = do grp <- Group <$> getWord16 l <- fromIntegral <$> getWord16 key <- getBytes l let len = l + 4 return (len, Just $ KeyShareEntry grp key) putKeyShareEntry :: KeyShareEntry -> Put putKeyShareEntry (KeyShareEntry (Group grp) key) = do putWord16 grp putWord16 $ fromIntegral $ B.length key putBytes key data KeyShare = KeyShareClientHello [KeyShareEntry] | KeyShareServerHello KeyShareEntry | KeyShareHRR Group deriving (Show, Eq) instance Extension KeyShare where extensionID _ = EID_KeyShare extensionEncode (KeyShareClientHello kses) = runPut $ do let len = sum [B.length key + 4 | KeyShareEntry _ key <- kses] putWord16 $ fromIntegral len mapM_ putKeyShareEntry kses extensionEncode (KeyShareServerHello kse) = runPut $ putKeyShareEntry kse extensionEncode (KeyShareHRR (Group grp)) = runPut $ putWord16 grp extensionDecode MsgTServerHello = runGetMaybe $ do (_, ment) <- getKeyShareEntry case ment of Nothing -> fail "decoding KeyShare for ServerHello" Just ent -> return $ KeyShareServerHello ent extensionDecode MsgTClientHello = runGetMaybe $ do len <- fromIntegral <$> getWord16 -- len == 0 allows for HRR grps <- getList len getKeyShareEntry return $ KeyShareClientHello $ catMaybes grps extensionDecode MsgTHelloRetryRequest = runGetMaybe $ KeyShareHRR . Group <$> getWord16 extensionDecode _ = error "extensionDecode: KeyShare" ------------------------------------------------------------ newtype PskKexMode = PskKexMode {fromPskKexMode :: Word8} deriving (Eq) {- FOURMOLU_DISABLE -} pattern PSK_KE :: PskKexMode pattern PSK_KE = PskKexMode 0 pattern PSK_DHE_KE :: PskKexMode pattern PSK_DHE_KE = PskKexMode 1 instance Show PskKexMode where show PSK_KE = "PSK_KE" show PSK_DHE_KE = "PSK_DHE_KE" show (PskKexMode x) = "PskKexMode " ++ show x {- FOURMOLU_ENABLE -} newtype PskKeyExchangeModes = PskKeyExchangeModes [PskKexMode] deriving (Eq, Show) instance Extension PskKeyExchangeModes where extensionID _ = EID_PskKeyExchangeModes extensionEncode (PskKeyExchangeModes pkms) = runPut $ putWords8 $ map fromPskKexMode pkms extensionDecode MsgTClientHello = runGetMaybe $ PskKeyExchangeModes . map PskKexMode <$> getWords8 extensionDecode _ = error "extensionDecode: PskKeyExchangeModes" ------------------------------------------------------------ data PskIdentity = PskIdentity ByteString Word32 deriving (Eq, Show) data PreSharedKey = PreSharedKeyClientHello [PskIdentity] [ByteString] | PreSharedKeyServerHello Int deriving (Eq, Show) instance Extension PreSharedKey where extensionID _ = EID_PreSharedKey extensionEncode (PreSharedKeyClientHello ids bds) = runPut $ do putOpaque16 $ runPut (mapM_ putIdentity ids) putOpaque16 $ runPut (mapM_ putBinder bds) where putIdentity (PskIdentity bs w) = do putOpaque16 bs putWord32 w putBinder = putOpaque8 extensionEncode (PreSharedKeyServerHello w16) = runPut $ putWord16 $ fromIntegral w16 extensionDecode MsgTServerHello = runGetMaybe $ PreSharedKeyServerHello . fromIntegral <$> getWord16 extensionDecode MsgTClientHello = runGetMaybe $ do len1 <- fromIntegral <$> getWord16 identities <- getList len1 getIdentity len2 <- fromIntegral <$> getWord16 binders <- getList len2 getBinder return $ PreSharedKeyClientHello identities binders where getIdentity = do identity <- getOpaque16 age <- getWord32 let len = 2 + B.length identity + 4 return (len, PskIdentity identity age) getBinder = do l <- fromIntegral <$> getWord8 binder <- getBytes l let len = l + 1 return (len, binder) extensionDecode _ = error "extensionDecode: PreShareKey" ------------------------------------------------------------ newtype EarlyDataIndication = EarlyDataIndication (Maybe Word32) deriving (Eq, Show) instance Extension EarlyDataIndication where extensionID _ = EID_EarlyData extensionEncode (EarlyDataIndication Nothing) = runPut $ putBytes B.empty extensionEncode (EarlyDataIndication (Just w32)) = runPut $ putWord32 w32 extensionDecode MsgTClientHello = return $ Just (EarlyDataIndication Nothing) extensionDecode MsgTEncryptedExtensions = return $ Just (EarlyDataIndication Nothing) extensionDecode MsgTNewSessionTicket = runGetMaybe $ EarlyDataIndication . Just <$> getWord32 extensionDecode _ = error "extensionDecode: EarlyDataIndication" ------------------------------------------------------------ newtype Cookie = Cookie ByteString deriving (Eq, Show) instance Extension Cookie where extensionID _ = EID_Cookie extensionEncode (Cookie opaque) = runPut $ putOpaque16 opaque extensionDecode MsgTServerHello = runGetMaybe (Cookie <$> getOpaque16) extensionDecode _ = error "extensionDecode: Cookie" ------------------------------------------------------------ newtype CertificateAuthorities = CertificateAuthorities [DistinguishedName] deriving (Eq, Show) instance Extension CertificateAuthorities where extensionID _ = EID_CertificateAuthorities extensionEncode (CertificateAuthorities names) = runPut $ putDNames names extensionDecode MsgTClientHello = runGetMaybe (CertificateAuthorities <$> getDNames) extensionDecode MsgTCertificateRequest = runGetMaybe (CertificateAuthorities <$> getDNames) extensionDecode _ = error "extensionDecode: CertificateAuthorities"