I wrote a CRUD application to interface with JIRA. I ended up upgrading my haskell enviornment, because cabal-dev doesn't solve everything. As a result, I've got some breakage, with this error anytime I try to use any code that interfaces with JIRA.
Spike: HandshakeFailed (Error_Misc "user error (unexpected type received. expecting
handshake and got: Alert [(AlertLevel_Warning,UnrecognizedName)])")
After a little googling, I think this either has to do with tls or http-conduit which uses tls.
I'm currently using tls-1.1.2 and http-conduit-1.8.7.1
previously I was using
tls-0.9.11 and http-conduit >= 1.5 && < 1.7 (not sure which exactly, old install is gone.
This is where I believe the break is happening
manSettings :: ManagerSettings
manSettings = def { managerCheckCerts = \ _ _ _-> return CertificateUsageAccept }
this is what it used to look like
manSettings :: ManagerSettings
manSettings = def { managerCheckCerts = \ _ _ -> return CertificateUsageAccept }
Here's the code that uses it
initialRequest :: forall (m :: * -> *). URI -> IO (Request m,Manager)
initialRequest uri = do
initReq <- parseUrl uri -- let the server tell you what the request header
-- should look like
manager <- newManager manSettings -- a Manager manages http connections
-- we mod the settings to handle
-- the SSL cert. See manSettings below.
return (modReq initReq,manager)
where modReq initReq = applyBasicAuth username password initReq
Let me know if I'm left something out. I'm not sure at this point what broke between then and now.
It's a good guess about the error source, but very unlikely: managerCheckCerts simply uses the certificate package to inspect certificates for validity. The error message you're seeing seems to be coming from tls itself and indicates a failure in the data transport. It's probably a good idea to file a bug report with tls, preferably first by narrowing down the issue to a single HTTPS call that fails (or even better, using tls alone and demonstrating the same failure).
Related
Network.HTTP.Proxy has a nice function called fetchProxy:
fetchProxy :: Bool -> IO Proxy
fetchProxy flg gets the local proxy settings and parse the string into
a Proxy value. If you want to be informed of ill-formed proxy
configuration strings, supply True for flg. Proxy settings are sourced
from the HTTP_PROXY environment variable [...]
I want to use the Proxy obtained this way with Wreq library, which has it's own Proxy defined like this, by importing it from HTTP:
import Network.HTTP.Client.Internal (Proxy(..), Response)
There appears to be a type mismatch between Network.HTTP.Proxy.Proxy and Network.Wreq.Proxy, where I presume they must be identical.
I import both like this:
import Network.Wreq
import Network.HTTP.Proxy (fetchProxy)
How can I use HTTP.Proxy.Proxy with Wreq and why does GHC see them as different types?
It's likely that the Wreq authors were just unaware of the other Proxy as they seem to be storing equivalent information. It'll be tricky to get them to talk to each other, however, since fetchProxy stores the host:port as a string and Wreq's Proxy wants the individual host and port. You'll have to do some URI parsing:
import Control.Lens
import Data.Text.Strict.Lens
import Network.HTTP.Proxy
import Network.Wreq
import URI.ByteString
main :: IO ()
main = do
Network.HTTP.Proxy.Proxy host _ <- fetchProxy True
case parseURI strictURIParserOptions (host ^. packed . re utf8) of
Left e -> do
putStrLn "uh oh"
print e
Right uri ->
case ( uri ^? uriAuthorityL . _Just . authorityHostL . hostBSL
, uri ^? uriAuthorityL . _Just . authorityPortL . _Just . portNumberL) of
(Just host_, Just port_) -> do
let opts = defaults & proxy ?~ httpProxy host_ port_
response <- getWith opts "http://example.com"
print response
_ ->
putStrLn "uh oh"
I'm using lens here to do the boring bits and pieces of packing/unpacking strings, encoding UTF8, and talking to the uri-bytestring package to get URI parsing. But the general idea is that datatypes in Haskell can be sliced and diced simply by pattern matching on the constructor; once extracted, the host:string here is funneled down into the httpProxy call, which returns Wreq's Proxy type. By qualifying the name of the constructor (Newtork.HTTP.Proxy.Proxy) I've let the compiler know which module I want that name from.
It would also not be too difficult, and probably less code to boot, to manually parse proxy information from the environment variables yourself. You could even have a separate environment variable for host and port, which would obviate the need for URI parsing. URIs are have such massively low entropy that they're an awful format for storing configuration information.
I'm probably just overlooking something basic in the documentation of http-client-tls and tls, but: how can I establish an HTTPS connection to a server and only accept one particular certificate, specified by me, that is potentially not in the system certificate store?
I see that this is an old question, but I just spent some time writing code to do this and figured I'd post it here for posterity... and in the hopes of getting some code review from the community. Snoyman's comment is helpful, but there are so many code interdependencies here, and X.509 and TLS are so boil the ocean, that it's hard to debug and to know for sure that you're not screwing something up without digging pretty deep into the various libraries. I figured a more complete explanation with working code was in order.
Anyways, here's what I've come up with (this is a stack script so you can run it easily yourself) --
#!/usr/bin/env stack
{- stack --resolver lts-7.16 runghc -}
import qualified Data.ByteString as B
import Data.ByteString.Lazy (ByteString)
import Data.Default.Class (def)
import Data.String (fromString)
import Data.X509.CertificateStore (CertificateStore, readCertificateStore)
import Network.HTTP.Client (httpLbs, newManager, ManagerSettings)
import Network.HTTP.Client.TLS (mkManagerSettings)
import Network.Connection (TLSSettings(TLSSettings))
import qualified Network.TLS as TLS
import qualified Network.TLS.Extra.Cipher as TLS
import System.Environment (getArgs, getProgName)
managerSettings :: CertificateStore -> ManagerSettings
managerSettings store = mkManagerSettings settings Nothing
where settings = TLSSettings params
params = (TLS.defaultParamsClient "" B.empty) {
TLS.clientUseServerNameIndication = True
, TLS.clientShared = def {
TLS.sharedCAStore = store
}
, TLS.clientSupported = def {
TLS.supportedCiphers = TLS.ciphersuite_default
}
}
get :: FilePath -> String -> IO ()
get ca url = do
mstore <- readCertificateStore ca
case mstore of
Just store -> do
manager <- newManager $ managerSettings store
response <- httpLbs (fromString url) manager
putStrLn (show response)
Nothing -> do
putStrLn $ "error: invalid certificate store " ++ ca
main :: IO ()
main = do
args <- getArgs
case args of
ca:url:[] -> get ca url
_ -> do
name <- getProgName
putStrLn $ "usage: " ++ name ++ " ca url"
A couple notes:
The TLS.sharedCAStore settings is where the magic happens. If you want to add your CA to the system store (vs. using only your CA) you can load the system store using getSystemCertificateStore from System.X509, then use Data.X509.CertificateStore to convert back and forth between CertificateStore and [SignedCertificate] to create a store with the system certificates along with your own.
TLS.defaultParamsClient takes a hostname and server id, used for TLS server name indication (SNI), a TLS extension that allows a server to host multiple sites on a single IP (similar to how HTTP/1.1 host headers work). We don't necessarily know what to set this to when we're creating a manager. Fortunately, Network.Connection (used by http-client-tls) appears to override whatever settings we use, so it doesn't matter.
The default for TLS.supportedCiphers is an empty list, so this setting is required (unless you turn off validation or something). Network.Connection defaults to ciphersuite_all but that includes some "not recommended last resource cipher suites" so I opted to use ciphersuite_default instead.
I think you're looking for ClientHooks. You can create a TLSSettings value with that by using the TLSSettings constructor, and then create a ManagerSettings using mkManagerSettings.
I use the http-conduit library version 2.0+ to fetch the contents from a http:// URL:
import Network.HTTP.Conduit
myurl = ... -- Your URL goes here
main = do content <- simpleHttp myurl
print $ content
When running this program, I get this error:
*** Exception: TlsException (HandshakeFailed (Error_Protocol
("certificate rejected: certificate is not allowed to sign another certificate",
True,CertificateUnknown)))
As can be told from the error message, the problem is the inability of Network.HTTP.Conduit to validate the server certificate appropriately (in this case, there seem to be problems in the certificate chain)
How can I change the above code to ignore the certificate error (i.e. by not verifying certificates at all)?
simpleHttp itself does not support this feature. You'll need to create a manager with modified ManagerSettings and then use that to fetch the URL.
Note that this code only applies for http-conduits version 2.0+ -- the library version 1 has a similar yet different API for this purpose.
import Network.HTTP.Conduit
import Network.Connection
import qualified Data.ByteString.Lazy.Char8 as LB
myurl = ... -- Your URL goes here
-- | Get a new Manager that doesn't verify SSL certificates
noSSLVerifyManager :: IO Manager
noSSLVerifyManager = let tlsSettings = TLSSettingsSimple {
-- This is where we disable certificate verification
settingDisableCertificateValidation = True,
settingDisableSession=False,
settingUseServerName=True}
in newManager $ mkManagerSettings tlsSettings Nothing
-- | Download like with simpleHttp, but using an existing manager for the task
simpleHttpWithManager :: Manager -> String -> IO LB.ByteString
simpleHttpWithManager manager url = do url' <- parseUrl url
fmap responseBody $ httpLbs url' manager
main = do manager <- noSSLVerifyManager
content <- simpleHttpWithManager manager myurl
print $ content
Note that you should only disable SSL certificate verification if absolutely neccessary, as it makes you vulnerable for man-in-the-middle attacks
I'm trying to access the Mt Gox REST API using http-conduit. Queries that just have a path (e.g. https://data.mtgox.com/api/2/BTCUSD/money/ticker) work fine, but when I add a queryString to the request it times out.
So this works:
mtGoxRequest :: String -> QueryText -> Request m
mtGoxRequest p qt = def {
secure = True,
host = "data.mtgox.com",
port = 443,
method = "GET",
path = fromString $ "api/2/" ++ p,
queryString = renderQuery False $ queryTextToQuery qt,
responseTimeout = Just 10000000
}
currencyTicker :: Request m
currencyTicker = mtGoxRequest "BTCUSD/money/ticker" []
But this times out:
tradeStream :: Currency -> UTCTime -> Request m
tradeStream t = mtGoxRequest
"BTCUSD/money/trades/fetch"
[("since", Just $ T.pack $ utcToGoxTime t)]
The difference seems to be in the use of a queryString: when I added the bogus query "foo=bar" to the currencyTicker that timed out too.
However all this works fine in the web browser: going to https://data.mtgox.com/api/2/BTCUSD/money/ticker?foo=bar instantly returns the correct error message instead of timing out. The trade fetch URL works as well, although I won't include a link because the "since" argument says how far back to go. Conversely, if I remove the queryString from the trades list request it correctly returns the entire available trade history.
So something about the http-conduit query string is obviously different. Anyone know what it might be?
Here is the Haskell Request object being sent (as printed by "Show"):
Request {
host = "data.mtgox.com"
port = 443
secure = True
clientCertificates = []
requestHeaders = []
path = "api/2/BTCUSD/money/trades/fetch"
queryString = "since=1367142721624293"
requestBody = RequestBodyLBS Empty
method = "GET"
proxy = Nothing
rawBody = False
redirectCount = 10
responseTimeout = Just 10000000
}
According to its returned headers Mt Gox is using cloudflare-nginx and PHP 5.
Edit: Forgot to mention that when I use http-conduit to send a request with a queryString to http://scooterlabs.com/echo I get the correct response as well, so it seems to be some interaction between the Mt Gox webserver and http-conduit.
Got it figured out. You need to add a User-Agent string. So
requestHeaders = [(CI.mk "User-Agent", "Test/0.0.1")],
in the middle of the request function makes it work.
$ time curl https://data.mtgox.com/api/2/BTCUSD/money/trades/fetch?since=1367142721624293
...
real 0m20.993s
Looks to me like everything is working correctly: the API call takes a while to return, so http-conduit throws a timeout exception since 20s is longer than 10s.
So here i was, barely able to install the libzmq on a windows desktop and then zeromq-haskell with cabal. I wanted to test the api by binding a python program with a haskell program in a hello-world type application.
So the most basic pattern i see is the request-reply pattern . First i tried to make the server in haskell (REP) and the client in python (REQ), witch failed miserably no matter what i did. The generated exception message was Exception: receive: failed (No error).
So i look inside the System.ZMQ and System.ZMQ.Base source code and i see that receive throws an error on calling c_zmq_recv , witch in turn maps directly to a ffi (?) call to the C api. So i think perhaps i didn't do the installation properly , but then i try to make the client in Haskell and the server in python and i notice it works without any problem, so perhaps the recv interface isn't the problem here.
Here is the haskell code below , with both client and server functions
import System.ZMQ
import Control.Monad (forM_,forever)
import Data.ByteString.Char8 (pack,unpack)
import Control.Concurrent (threadDelay)
clientMain :: IO ()
clientMain = withContext 1 (\context->do
putStrLn "Connecting to server"
withSocket context Req $ (\socket-> do
connect socket "tcp://127.0.0.1:5554"
putStrLn $ unwords ["Sending request"]
send socket (pack "Hello...") []
threadDelay (1*1000*1000)
reply<-receive socket []
putStrLn $ unwords ["Received response : ",unpack reply]))
serverMain :: IO ()
serverMain = withContext 1 (\context-> do
putStrLn "Listening at 5554"
withSocket context Rep $ (\socket-> do
connect socket "tcp://127.0.0.1:5554"
forever $ do
message<-receive socket [] -- this throws an IO Exception
putStrLn $ unwords ["Received request : ",unpack message]
threadDelay (1*1000*1000)
send socket (pack "World") [] ))
main :: IO ()
main = serverMain -- replace with clientMain and it works
Now i really didn't get around to testing all other modes of communication (push/pull, subscribe/publish, pair etc.) and for what i need the python server/haskell client is probably better but i am curious about weather i'm doing something wrong or if any part of my code is broken in any way.
Thanks in advance
You need to make one of the sockets (usually the server) bind, you seem to have them both connecting.
Try changing connect socket "tcp://127.0.0.1:5554" to bind socket "tcp://127.0.0.1:5554" in the serverMain function.