dropbox-sdk 0.2.0 → 0.3.0
raw patch · 5 files changed
+333/−577 lines, 5 filesdep +monad-controldep +pemdep +resourcetdep ~conduitdep ~http-conduitdep ~http-types
Dependencies added: monad-control, pem, resourcet, template-haskell
Dependency ranges changed: conduit, http-conduit, http-types, tls, transformers
Files
- Source/Dropbox.hs +135/−225
- Source/Dropbox/Certificates.hs +132/−0
- Source/Dropbox/Certificates/TH.hs +53/−0
- dropbox-sdk.cabal +13/−11
- trusted-certs.crt +0/−341
Source/Dropbox.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE FlexibleContexts #-}+ module Dropbox ( -- * Configuration mkConfig,@@ -27,10 +29,9 @@ getMetadata, getMetadataWithChildren, getMetadataWithChildrenIfChanged, Meta(..), MetaBase(..), MetaExtra(..), FolderContents(..), FileExtra(..), FolderHash(..), FileRevision(..),- -- * Get files+ -- * Upload/download files getFile, getFileBs,- -- * Upload files- addFile, forceFile, updateFile,+ putFile, WriteMode(..), -- * Common data types fileRevisionToString, folderHashToString, ErrorMessage, URL, Path,@@ -51,31 +52,27 @@ import Text.JSON (JSON, readJSON, showJSON) import Data.ByteString (ByteString) import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString.Char8 as BS8-import Data.CaseInsensitive (CI) import Data.Word (Word64) import Data.Int (Int64)-import Data.Time.Clock (UTCTime(utctDay), getCurrentTime)+import Data.Time.Clock (UTCTime) import Data.Time.Format (parseTime, formatTime) import System.Locale (defaultTimeLocale) import Control.Monad (liftM)-import qualified Control.Monad.Trans.Class as MT-import qualified Data.Conduit as C+import Control.Monad.IO.Class (MonadIO)+import Control.Monad.Trans.Control (MonadBaseControl)+import Control.Monad.Trans.Resource (ResourceT, MonadUnsafeIO, MonadThrow, MonadResource(..), runResourceT, allocate)+import Data.Conduit (Sink, Source, ($=), ($$+-)) import qualified Data.Conduit.List as CL import qualified Network.HTTP.Conduit as HC import qualified Network.HTTP.Types as HT-import qualified Network.TLS as TLS-import qualified Network.TLS.Extra as TLSExtra-import Data.Certificate.X509 (X509)-import qualified Data.Certificate.X509 as X509-import Data.Certificate.PEM as PEM-import Data.Conduit (Sink, Source, Resource)+import qualified Network.HTTP.Types.Header as HT import qualified Blaze.ByteString.Builder.ByteString as BlazeBS-import System.IO as IO-import qualified Paths_dropbox_sdk as Paths +import Dropbox.Certificates+ type ErrorMessage = String+ type URL = String -- |Dropbox file and folder paths. Should always start with "/".@@ -151,23 +148,8 @@ , configUserLocale :: Locale -- ^The locale that the Dropbox service should use when returning user-visible strings. , configAppId :: AppId -- ^Your app's key/secret , configAccessType :: AccessType -- ^The type of folder access your Dropbox application uses.- , configCertVerifier :: CertVerifier -- ^The server certificate validation routine. } deriving (Show) -type CertVerifierFunc =- HT.Ascii -- ^The server's host name.- -> [X509] -- ^The server's certificate chain.- -> IO TLS.TLSCertificateUsage -- ^Whether the certificate chain is valid or not.---- |How the server's SSL certificate will be verified.-data CertVerifier = CertVerifier- { certVerifierName :: String -- ^The human-friendly name of the policy (only for debug prints)- , certVerifierFunc :: CertVerifierFunc -- ^The function that implements certificate validation.- }--instance Show CertVerifier where- show (CertVerifier name _) = "CertVerifier " ++ show name- -- |A convenience function that constructs a 'Config'. It's in the 'IO' monad because we read from -- a file to get the list of trusted SSL certificates, which is used to verify the server over SSL. mkConfig ::@@ -177,18 +159,11 @@ -> AccessType -- ^'configAccessType' -> IO Config mkConfig userLocale appKey appSecret accessType = do- caFile <- Paths.getDataFileName "trusted-certs.crt"- vf <- do- r <- certVerifierFromPemFile caFile- case r of- Right vf -> return $ vf- Left err -> fail $ "Unable to load root certificates from " ++ (show caFile) ++ ": " ++ err return $ Config { configHosts = hostsDefault , configUserLocale = userLocale , configAppId = AppId appKey appSecret , configAccessType = accessType- , configCertVerifier = vf } -- |Contains a 'Config' and an 'AccessToken'. Every API call (after OAuth is complete)@@ -199,82 +174,6 @@ } ------------------------------------------------------------------------- SSL Certificate Validation---- |A dummy implementation that doesn't perform any verification.-certVerifierInsecure :: CertVerifier-certVerifierInsecure = CertVerifier "insecure" (\_ _ -> return TLS.CertificateUsageAccept)--rightsOrFirstLeft :: [Either a b] -> Either a [b]-rightsOrFirstLeft = foldr f (Right [])- where- f (Left e) _ = Left e- f _ (Left e) = Left e- f (Right v) (Right vs) = Right (v:vs)---- |Reads certificates in PEM format from the given file and uses those as the roots when--- verifying certificates. This function basically just loads the certificates and delegates--- to 'certVerifierFromRootCerts' for the actual checking.-certVerifierFromPemFile :: FilePath -> IO (Either ErrorMessage CertVerifier)-certVerifierFromPemFile filePath = do- raw <- withFile filePath IO.ReadMode BS.hGetContents- let pems = PEM.parsePEMs raw- let es = [X509.decodeCertificate (LBS.fromChunks [stuff]) | (_, stuff) <- pems]- case rightsOrFirstLeft es of- Left err -> return $ Left err- Right x509s -> return $ Right $ CertVerifier ("PEM file: " ++ show filePath) (certVerifierFromRootCerts x509s)--certAll :: [IO TLS.TLSCertificateUsage] -> IO TLS.TLSCertificateUsage-certAll [] = return TLS.CertificateUsageAccept-certAll (head:rest) = do- r <- head- case r of- TLS.CertificateUsageAccept -> certAll rest- reject -> return $ reject---- |A certificate validation routine. It's in 'IO' to match what 'HTTP.Enumerator'--- expects, but we don't actually do any I/O.-certVerifierFromRootCerts ::- [X509] -- ^The set of trusted root certificates.- -> HT.Ascii -- ^The remote server's domain name.- -> [X509] -- ^The certificate chain provided by the remote server.- -> IO TLS.TLSCertificateUsage--- TODO: Rewrite this crappy code. SSL cert checking needs to be more correct than this.-certVerifierFromRootCerts roots domain chain = do- utcTime <- getCurrentTime- let day = utctDay utcTime- certAll- [ return $ TLSExtra.certificateVerifyDomain (BS8.unpack domain) chain- , checkTrustChain day chain- ]- where- checkTrustChain _ [] = return $ TLS.CertificateUsageReject $ TLS.CertificateRejectOther "empty chain"- checkTrustChain day (head:rest) = do- if isUnexpired day head- then do- issuerMatch <- mapM (head `isIssuedBy`) roots- if any (== True) issuerMatch- then return $ TLS.CertificateUsageAccept- else case rest of- [] -> return $ TLS.CertificateUsageReject TLS.CertificateRejectUnknownCA- (next:_) -> do- nextOk <- TLSExtra.certificateVerifyAgainst head next- if nextOk- then checkTrustChain day rest- else return $ TLS.CertificateUsageReject $ TLS.CertificateRejectOther "break in verification chain"- else return $ TLS.CertificateUsageReject $ TLS.CertificateRejectExpired- isIssuedBy :: X509 -> X509 -> IO Bool- isIssuedBy c issuer =- if subjectDN issuer == issuerDN c- then TLSExtra.certificateVerifyAgainst c issuer- else return False- subjectDN c = X509.certSubjectDN $ X509.x509Cert c- issuerDN c = X509.certIssuerDN $ X509.x509Cert c- isUnexpired day cert =- let ((beforeDay, _, _), (afterDay, _, _)) = X509.certValidity (X509.x509Cert cert)- in beforeDay < day && day <= afterDay------------------------------------------------------------------------ -- Authentication/Authorization buildOAuthHeaderNoToken (AppId consumerKey consumerSecret) =@@ -303,7 +202,7 @@ -> Maybe URL -- ^The callback URL (optional) -> IO (Either ErrorMessage (RequestToken, URL)) authStart mgr config callback = do- result <- httpClientGet mgr vf uri oauthHeader (mkHandler handler)+ result <- httpClientGet mgr uri oauthHeader (mkHandler handler) return $ mergeLefts result where Locale locale = configUserLocale config@@ -312,7 +211,9 @@ consumerPair = configAppId config uri = "https://" ++ host ++ ":443/" ++ apiVersion ++ "/oauth/request_token?locale=" ++ urlEncode locale oauthHeader = buildOAuthHeaderNoToken consumerPair- vf = certVerifierFunc $ configCertVerifier config++ -- The handler is a callback that is executed on the response+ -- In case the OK: handler 200 _ body = do let sBody = UTF8.toString body -- toString should return a Maybe, but it doesn't. You too, Haskell? case parseTokenParts sBody of@@ -320,10 +221,13 @@ Right requestToken@(RequestToken requestTokenKey _) -> do let authorizeUrl = "https://" ++ webHost ++ "/"++apiVersion++"/oauth/authorize?locale=" ++ urlEncode locale ++ "&oauth_token=" ++ urlEncode requestTokenKey ++ callbackSuffix Right (requestToken, authorizeUrl)+ -- In case of an error: handler code reason body = Left $ "server returned " ++ show code ++ ": " ++ show reason ++ ": " ++ show body+ callbackSuffix = case callback of Nothing -> "" Just callbackUrl -> "&oauth_callback=" ++ urlEncode callbackUrl+ parseTokenParts :: String -> Either String RequestToken parseTokenParts s = do enc <- URLEncoded.importString s@@ -335,13 +239,13 @@ -- and the user has authorized your app, call this function to get a 'RequestToken', which -- is used to make Dropbox API calls. authFinish ::- Manager -- ^The HTTP connection manager to use.+ Manager -- ^The HTTP connection manager to use. -> Config -> RequestToken -- ^The 'RequestToken' obtained from 'authStart' -> IO (Either ErrorMessage (AccessToken, String)) -- ^The 'AccessToken' used to make Dropbox API calls and the user's Dropbox user ID. authFinish mgr config (RequestToken rtKey rtSecret) = do- result <- httpClientGet mgr vf uri oauthHeader (mkHandler handler)+ result <- httpClientGet mgr uri oauthHeader (mkHandler handler) return $ mergeLefts result where host = hostsApi (configHosts config)@@ -349,7 +253,6 @@ consumerPair = configAppId config uri = "https://" ++ host ++ ":443/"++apiVersion++"/oauth/access_token?locale=" ++ urlEncode locale oauthHeader = buildOAuthHeader consumerPair (rtKey, rtSecret)- vf = certVerifierFunc $ configCertVerifier config handler 200 _ body = do let sBody = UTF8.toString body -- toString should return a Maybe, but it doesn't. You too, Haskell? case parseResponse sBody of@@ -599,16 +502,17 @@ ---------------------------------------------------------------------- -- GetMetadata -checkPath :: Path -> IO (Either ErrorMessage a) -> IO (Either ErrorMessage a)+checkPath :: Monad m => Path -> m (Either ErrorMessage a) -> m (Either ErrorMessage a) checkPath ('/':_) action = action checkPath _ _ = return $ Left $ "path must start with \"/\"" -- |Get the metadata for the file or folder at the given path. getMetadata ::- Manager -- ^The HTTP connection manager to use.+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager -- ^The HTTP connection manager to use. -> Session -> Path -- ^The full path (relative to your 'DbAccessType' root)- -> IO (Either ErrorMessage Meta)+ -> m (Either ErrorMessage Meta) getMetadata mgr session path = checkPath path $ do result <- doGet mgr session hostsApi url params (mkHandler handler) return $ mergeLefts result@@ -622,15 +526,16 @@ -- |Get the metadata for the file or folder at the given path. If it's a folder, -- return the metadata for the folder's immediate children as well. getMetadataWithChildren ::- Manager -- ^The HTTP connection manager to use.+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager -- ^The HTTP connection manager to use. -> Session- -> Path -- ^The full path (relative to your 'DbAccessType' root)+ -> Path -- ^The full path (relative to your 'DbAccessType' root) -> Maybe Integer- -- ^A limit on folder contents (max: 10,000). If the path refers to a folder and this folder- -- has more than the specified number of immediate children, the entire- -- 'getMetadataWithChildren' call will fail with an HTTP 406 error code. If unspecified, or- -- if set to zero, the server will set this to 10,000.- -> IO (Either ErrorMessage (Meta, Maybe FolderContents))+ -- ^A limit on folder contents (max: 10,000). If the path refers to a folder and this folder+ -- has more than the specified number of immediate children, the entire+ -- 'getMetadataWithChildren' call will fail with an HTTP 406 error code. If unspecified, or+ -- if set to zero, the server will set this to 10,000.+ -> m (Either ErrorMessage (Meta, Maybe FolderContents)) getMetadataWithChildren mgr session path childLimit = checkPath path $ do result <- doGet mgr session hostsApi url params (mkHandler handler) return $ mergeLefts result@@ -646,16 +551,16 @@ -- |Same as 'getMetadataWithChildren' except it'll return @Nothing@ if the 'FolderHash' -- of the folder on Dropbox is the same as the 'FolderHash' passed in. getMetadataWithChildrenIfChanged ::- Manager -- ^The HTTP connection manager to use.+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager -- ^The HTTP connection manager to use. -> Session -> Path- -> Maybe Integer- -> FolderHash- -- ^For folders, the returned child metadata will include a 'folderHash' field that- -- is a short identifier for the current state of the folder. If the 'FolderHash'- -- for the specified path hasn't change, this call will return @Nothing@, which- -- indicates that the previously-retrieved metadata is still the latest.- -> IO (Either ErrorMessage (Maybe (Meta, Maybe FolderContents)))+ -> Maybe Integer + -> FolderHash -- ^For folders, the returned child metadata will include a 'folderHash' field that+ -- is a short identifier for the current state of the folder. If the 'FolderHash'+ -- for the specified path hasn't change, this call will return @Nothing@, which+ -- indicates that the previously-retrieved metadata is still the latest.+ -> m (Either ErrorMessage (Maybe (Meta, Maybe FolderContents))) getMetadataWithChildrenIfChanged mgr session path childLimit (FolderHash hash) = checkPath path $ do result <- doGet mgr session hostsApi url params (mkHandler handler) return $ mergeLefts result@@ -675,13 +580,14 @@ -- |Gets a file's contents and metadata. If you just want the entire contents of -- a file as a single 'ByteString', use 'getFileBs'. getFile ::- Manager -- ^The HTTP connection manager to use.+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m)+ => Manager -- ^The HTTP connection manager to use. -> Session -> Path -- ^The full path (relative to your 'DbAccessType' root) -> Maybe FileRevision -- ^The revision of the file to retrieve.- -> (Meta -> Sink ByteString IO r)+ -> (Meta -> Sink ByteString (ResourceT m) r) -- ^Given the file metadata, yield a 'Sink' to process the response body- -> IO (Either ErrorMessage (Meta, r))+ -> m (Either ErrorMessage (Meta, r)) -- ^This function returns whatever your 'Sink' returns, paired up with the file metadata. getFile mgr session path mrev sink = checkPath path $ do result <- doGet mgr session hostsApiContent url params handler@@ -690,13 +596,15 @@ at = accessTypePath $ configAccessType (sessionConfig session) url = "files/" ++ at ++ path params = maybe [] (\(FileRevision rev) -> [("rev", rev)]) mrev+ handler (HT.Status 200 _) headers = case getHeaders "X-Dropbox-Metadata" headers of [metaJson] -> case handleJsonBody metaJson of- Left err -> C.Sink $ return $ C.SinkNoData (Left err)+ Left err -> return (Left err) Right meta -> do r <- sink meta return $ Right (meta, r) l -> return $ Left $ "expecting response to have exactly one \"X-Dropbox-Metadata\" header, found " ++ show (length l)+ handler (HT.Status code reason) _ = do body <- bsSink return $ Left $ "non-200 response from Dropbox (" ++ (show code) ++ ":" ++ (BS8.unpack reason) ++ ": " ++ (show body) ++ ")"@@ -704,66 +612,42 @@ -- |A variant of 'getFile' that just returns a strict 'ByteString' (instead of having -- you pass in a 'Sink' to process the body. getFileBs ::- Manager -- ^The HTTP connection manager to use.+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager -- ^The HTTP connection manager to use. -> Session- -> Path -- ^The full path (relative to your 'DbAccessType' root)- -> Maybe FileRevision -- ^The revision of the file to retrieve.- -> IO (Either ErrorMessage (Meta, ByteString))+ -> Path -- ^The full path (relative to your 'DbAccessType' root)+ -> Maybe FileRevision -- ^The revision of the file to retrieve.+ -> m (Either ErrorMessage (Meta, ByteString)) getFileBs mgr session path mrev = getFile mgr session path mrev (\_ -> bsSink) ------------------------------------------------------------------------- AddFile/ForceFile/UpdateFile---- |Add a new file. If a file or folder already exists at the given path, your--- file will be automatically renamed. If successful, you'll get back the metadata--- for your newly-uploaded file.-addFile ::- Manager -- ^The HTTP connection manager to use.- -> Session- -> Path -- ^The full path (relative to your 'DbAccessType' root)- -> RequestBody -- ^The file contents.- -> IO (Either ErrorMessage Meta)-addFile mgr session path contents = putFile mgr session path contents [("overwrite", "false")]---- |Overwrite a file with new data if the version on Dropbox matches the version--- you specify. If the version doesn't match, create a new file with a unique--- name. Either way, you will be returned the metdata for whichever file was--- written.-updateFile ::- Manager -- ^The HTTP connection manager to use.- -> Session- -> Path -- ^The full path (relative to your 'DbAccessType' root)- -> RequestBody -- ^The file contents.- -> FileRevision -- ^The revision of the file you expect to be writing to.- -> IO (Either ErrorMessage Meta)-updateFile mgr session path contents (FileRevision rev) =- putFile mgr session path contents [("parent_rev", rev)]---- |Add a file. If a file already exists at the given path, that file will--- be overwritten. If successful, you'll get back the metadata for your--- newly-uploaded file.-forceFile ::- Manager -- ^The HTTP connection manager to use.- -> Session- -> Path -- ^The full path (relative to your 'DbAccessType' root)- -> RequestBody -- ^The file contents.- -> IO (Either ErrorMessage Meta)-forceFile mgr session path contents = putFile mgr session path contents [("overwrite", "true")]+-- putFile -------------------------------------------------------------------------- The underlying "put_file" call.+data WriteMode+ = WriteModeAdd+ -- ^If there is already a file at the specified path, rename the new file.+ | WriteModeUpdate FileRevision+ -- ^Check that there is a file there with the given revision. If so, overwrite+ -- it. If not, rename the new file.+ | WriteModeForce+ -- ^If there is already a file at the specified path, overwrite it. putFile ::- HC.Manager+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager -- ^The HTTP connection manager to use. -> Session- -> Path- -> RequestBody- -> [(String,String)]- -> IO (Either ErrorMessage Meta)-putFile mgr session path contents params = checkPath path $ do+ -> Path -- ^The full path (relative to your 'DbAccessType' root)+ -> WriteMode+ -> RequestBody m -- ^The file contents.+ -> m (Either ErrorMessage Meta)+putFile mgr session path writeMode contents = checkPath path $ do result <- doPut mgr session hostsApiContent url params contents (mkHandler handler) return $ mergeLefts result where+ params = case writeMode of+ WriteModeAdd -> [("overwrite", "false")]+ WriteModeUpdate (FileRevision rev) -> [("parent_rev", rev)]+ WriteModeForce -> [("overwrite", "true")] at = accessTypePath $ configAccessType (sessionConfig session) url = "files_put/" ++ at ++ path handler 200 _ body = handleJsonBody body@@ -786,64 +670,76 @@ oauthHeader = buildOAuthHeader consumerPair (atKey, atSecret) doPut ::- Manager+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager -> Session -> (Hosts -> String) -> String -> [(String,String)]- -> RequestBody- -> Handler r- -> IO (Either ErrorMessage r)+ -> RequestBody m+ -> Handler r m+ -> m (Either ErrorMessage r) doPut mgr session hostSelector path params requestBody handler = do let (uri, oauthHeader) = prepRequest session hostSelector path params- let vf = certVerifierFunc $ configCertVerifier $ sessionConfig session- httpClientPut mgr vf uri oauthHeader handler requestBody+ httpClientPut mgr uri oauthHeader handler requestBody doGet ::- Manager+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager -> Session -> (Hosts -> String) -> String -> [(String,String)]- -> Handler r- -> IO (Either ErrorMessage r)+ -> Handler r m+ -> m (Either ErrorMessage r) doGet mgr session hostSelector path params handler = do let (uri, oauthHeader) = prepRequest session hostSelector path params- let vf = certVerifierFunc $ configCertVerifier $ sessionConfig session- httpClientGet mgr vf uri oauthHeader handler+ httpClientGet mgr uri oauthHeader handler ---------------------------------------------------------------------- +-- |`ManagerSettings` that include the DropBox SSL certificates+managerSettings :: (MonadBaseControl IO m) => m HC.ManagerSettings+managerSettings = do + return $ HC.def { HC.managerCheckCerts = certVerifierFunc certVerifierFromDbX509s }+ -- |The HTTP connection manager. Using the same 'Manager' instance across -- multiple API calls type Manager = HC.Manager -- |A bracket around an HTTP connection manager.-withManager :: (Manager -> IO r) -> IO r-withManager inner = HC.withManager $ \manager ->- MT.lift $ inner manager+-- Uses default 'ManagerSettings' as computed by 'managerSettings'.+withManager ::+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m)+ => (HC.Manager -> ResourceT m a) -> m a+withManager inner = runResourceT $ do+ ms <- managerSettings+ (_, manager) <- allocate (HC.newManager ms) HC.closeManager+ inner manager ---------------------------------------------------------------------- type SimpleHandler r = Int -> String -> ByteString -> r -- HTTP response-handling function.-type Handler r = HT.Status -> HT.ResponseHeaders -> (Sink ByteString IO r)+type Handler r m = HT.Status -> HT.ResponseHeaders -> (Sink ByteString (ResourceT m) r) -- |An HTTP request body: an 'Int64' for the length and a 'Source' -- that yields the actual data.-data RequestBody = RequestBody Int64 (Source IO ByteString)+data RequestBody m = RequestBody Int64 (Source (ResourceT m) ByteString) -- |Create a 'RequestBody' from a single 'ByteString'-bsRequestBody :: ByteString -> RequestBody+bsRequestBody :: MonadIO m => ByteString -> RequestBody m bsRequestBody bs = RequestBody length (CL.sourceList [bs]) where length = fromInteger $ toInteger $ BS.length bs -getHeaders :: CI HT.Ascii -> [HT.Header] -> [HT.Ascii]+getHeaders :: HT.HeaderName -> [HT.Header] -> [ByteString] getHeaders name headers = [ val | (key, val) <- headers, key == name ] -mkHandler :: SimpleHandler r -> Handler r+mkHandler ::+ Monad m => SimpleHandler r + -> Handler r m mkHandler sh (HT.Status code reason) _headers = do bs <- bsSink return $ sh code (BS8.unpack reason) bs@@ -854,21 +750,22 @@ Right r -> r -- |A 'Sink' that reads in 'ByteString' chunks and constructs one concatenated 'ByteString'-bsSink :: Resource m => Sink ByteString m ByteString+bsSink :: (Monad m) => Sink ByteString m ByteString bsSink = do chunks <- CL.consume return $ BS.concat chunks +-- | Runs an http request with a given oauth header httpClientDo ::- Manager- -> HT.Ascii- -> RequestBody- -> CertVerifierFunc+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager+ -> HT.Method+ -> RequestBody m -> URL -> String- -> Handler r- -> IO (Either String r)-httpClientDo mgr method (RequestBody len bsSource) vf url oauthHeader handler =+ -> Handler r m+ -> m (Either String r)+httpClientDo mgr method (RequestBody len bsSource) url oauthHeader handler = case HC.parseUrl url of Just baseReq -> do let req = baseReq {@@ -876,19 +773,32 @@ HC.method = method, HC.requestHeaders = headers, HC.requestBody = HC.RequestBodySource len builderSource,- HC.checkCerts = vf, HC.checkStatus = \_ _ -> Nothing }- HC.Response code headers body <- C.runResourceT $ HC.http req mgr- result <- C.runResourceT (body C.$$ handler code headers)+ result <- runResourceT $ do+ HC.Response code _ headers body <- HC.http req mgr+ body $$+- handler code headers return $ Right result Nothing -> do return $ Left $ "bad URL: " ++ show url where headers = [("Authorization", UTF8.fromString oauthHeader)]- builderSource = bsSource C.$= (CL.map BlazeBS.fromByteString)+ builderSource = bsSource $= (CL.map BlazeBS.fromByteString) -httpClientGet :: Manager -> CertVerifierFunc -> URL -> String -> Handler r -> IO (Either String r)-httpClientGet mgr vf url oauthHeader handler = httpClientDo mgr "GET" (bsRequestBody BS.empty) vf url oauthHeader handler+httpClientGet ::+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager+ -> URL+ -> String+ -> Handler r m+ -> m (Either String r)+httpClientGet mgr url oauthHeader handler = httpClientDo mgr HT.methodGet (bsRequestBody BS.empty) url oauthHeader handler -httpClientPut :: Manager -> CertVerifierFunc -> URL -> String -> Handler r -> RequestBody -> IO (Either String r)-httpClientPut mgr vf url oauthHeader handler requestBody = httpClientDo mgr "PUT" requestBody vf url oauthHeader handler+httpClientPut ::+ (MonadBaseControl IO m, MonadThrow m, MonadUnsafeIO m, MonadIO m) + => Manager+ -> URL+ -> String+ -> Handler r m+ -> RequestBody m+ -> m (Either String r)+httpClientPut mgr url oauthHeader handler requestBody = httpClientDo mgr HT.methodPut requestBody url oauthHeader handler
+ Source/Dropbox/Certificates.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++module Dropbox.Certificates + ( CertVerifierFunc+ , CertVerifier(..)+ , certVerifierInsecure+ , certVerifierFromPemFile+ , certVerifierFromRootCerts+ , certVerifierFromDbX509s+ ) where++import System.IO (withFile, IOMode(ReadMode))++import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as LB+import qualified Data.ByteString.Char8 as B8++import Data.PEM (PEM(..))+import qualified Data.PEM as PEM++import Data.Certificate.X509 (X509)+import qualified Data.Certificate.X509 as X509++import Data.Time.Clock (UTCTime(utctDay), getCurrentTime)++import qualified Network.TLS as TLS+import qualified Network.TLS.Extra as TLSExtra++import Dropbox.Certificates.TH+++dbX509s :: [X509]+dbX509s = [x509File|trusted-certs.crt|]++-- | Use the buildin Dropbox certificates.+certVerifierFromDbX509s :: CertVerifier+certVerifierFromDbX509s = CertVerifier "compiled in Dropbox certificates" (certVerifierFromRootCerts dbX509s)++----------------------------------------------------------------------+-- SSL Certificate Validation++type CertVerifierFunc =+ ByteString -- ^The server's host name.+ -> [X509] -- ^The server's certificate chain.+ -> IO TLS.TLSCertificateUsage -- ^Whether the certificate chain is valid or not.++-- |How the server's SSL certificate will be verified.+data CertVerifier = CertVerifier+ { certVerifierName :: String -- ^The human-friendly name of the policy (only for debug prints)+ , certVerifierFunc :: CertVerifierFunc -- ^The function that implements certificate validation.+ }++instance Show CertVerifier where+ show (CertVerifier name _) = "CertVerifier " ++ show name++-- |A dummy implementation that doesn't perform any verification.+certVerifierInsecure :: CertVerifier+certVerifierInsecure = CertVerifier "insecure" (\_ _ -> return TLS.CertificateUsageAccept)++rightsOrFirstLeft :: [Either a b] -> Either a [b]+rightsOrFirstLeft = foldr f (Right [])+ where+ f (Left e) _ = Left e+ f _ (Left e) = Left e+ f (Right v) (Right vs) = Right (v:vs)++-- |Reads certificates in PEM format from the given file and uses those as the roots when+-- verifying certificates. This function basically just loads the certificates and delegates+-- to 'certVerifierFromRootCerts' for the actual checking.+certVerifierFromPemFile :: FilePath -> IO (Either String CertVerifier)+certVerifierFromPemFile filePath = do+ raw <- withFile filePath ReadMode B.hGetContents+ case PEM.pemParseBS raw of+ Left err -> return $ Left err+ Right pems -> do+ let es = [X509.decodeCertificate (LB.fromChunks [stuff]) | PEM _ _ stuff <- pems]+ case rightsOrFirstLeft es of+ Left err -> return $ Left err+ Right x509s -> return $ Right $ CertVerifier ("PEM file: " ++ show filePath) (certVerifierFromRootCerts x509s)++certAll :: [IO TLS.TLSCertificateUsage] -> IO TLS.TLSCertificateUsage+certAll [] = return TLS.CertificateUsageAccept+certAll (head:rest) = do+ r <- head+ case r of+ TLS.CertificateUsageAccept -> certAll rest+ reject -> return $ reject++-- |A certificate validation routine. It's in 'IO' to match what 'HTTP.Enumerator'+-- expects, but we don't actually do any I/O.+certVerifierFromRootCerts ::+ [X509] -- ^The set of trusted root certificates.+ -> ByteString -- ^The remote server's domain name.+ -> [X509] -- ^The certificate chain provided by the remote server.+ -> IO TLS.TLSCertificateUsage+-- TODO: Rewrite this crappy code. SSL cert checking needs to be more correct than this.+certVerifierFromRootCerts roots domain chain = do+ utcTime <- getCurrentTime+ let day = utctDay utcTime+ certAll+ [ return $ TLSExtra.certificateVerifyDomain (B8.unpack domain) chain+ , checkTrustChain day chain+ ]+ where+ checkTrustChain _ [] = return $ TLS.CertificateUsageReject $ TLS.CertificateRejectOther "empty chain"+ checkTrustChain day (head:rest) = do+ if isUnexpired day head+ then do+ issuerMatch <- mapM (head `isIssuedBy`) roots+ if any (== True) issuerMatch+ then return $ TLS.CertificateUsageAccept+ else case rest of+ [] -> return $ TLS.CertificateUsageReject TLS.CertificateRejectUnknownCA+ (next:_) -> do+ nextOk <- TLSExtra.certificateVerifyAgainst head next+ if nextOk+ then checkTrustChain day rest+ else return $ TLS.CertificateUsageReject $ TLS.CertificateRejectOther "break in verification chain"+ else return $ TLS.CertificateUsageReject $ TLS.CertificateRejectExpired+ isIssuedBy :: X509 -> X509 -> IO Bool+ isIssuedBy c issuer =+ if subjectDN issuer == issuerDN c+ then TLSExtra.certificateVerifyAgainst c issuer+ else return False+ subjectDN c = X509.certSubjectDN $ X509.x509Cert c+ issuerDN c = X509.certIssuerDN $ X509.x509Cert c+ isUnexpired day cert =+ let ((beforeDay, _, _), (afterDay, _, _)) = X509.certValidity (X509.x509Cert cert)+ in beforeDay < day && day <= afterDay+
+ Source/Dropbox/Certificates/TH.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-missing-fields #-}++-- | Quasi quoters for parsing PEM files and PEM encoded X509 Certificates.+--+-- Whem using hardcoded certificates, be aware that a certificate may be revoked+-- at any time before it expires.+--+module Dropbox.Certificates.TH + ( pem+ , pemFile+ , x509+ , x509File+ ) where++import Data.ByteString.Char8 (pack)+import Data.ByteString.Lazy (fromChunks)+import Data.PEM (PEM(..), pemParseBS)+import Data.Certificate.X509 (X509(..), decodeCertificate)++import Language.Haskell.TH.Quote++rightsOrFirstLeft :: [Either a b] -> Either a [b]+rightsOrFirstLeft = foldr f (Right [])+ where+ f (Left e) _ = Left e+ f _ (Left e) = Left e+ f (Right v) (Right vs) = Right (v:vs)++pem :: QuasiQuoter+pem = QuasiQuoter { quoteExp = \s -> [| parsePem s |] }++pemFile :: QuasiQuoter+pemFile = quoteFile pem++parsePem :: String -> [PEM]+parsePem s = case pemParseBS $ pack s of+ Left err -> error $ "Failed to parse PEM file: " ++ err+ Right x -> x++x509 :: QuasiQuoter+x509 = QuasiQuoter { quoteExp = \s -> [| decodeCert . parsePem $ s |] } ++x509File :: QuasiQuoter+x509File = quoteFile x509++decodeCert :: [PEM] -> [X509]+decodeCert pems = + let es = [decodeCertificate (fromChunks [stuff]) | PEM _ _ stuff <- pems]+ in case rightsOrFirstLeft es of+ Left err -> error $ "Failed to decode X509 file: " ++ err+ Right x509s -> x509s+
dropbox-sdk.cabal view
@@ -1,5 +1,5 @@ Name: dropbox-sdk-Version: 0.2.0+Version: 0.3.0 Synopsis: A library to access the Dropbox HTTP API. Description: A (very preliminary) library to access the Dropbox HTTP API:@@ -12,7 +12,6 @@ Category: Network APIs Build-Type: Simple Cabal-Version: >= 1.10-Data-Files: trusted-certs.crt Library Build-Depends:@@ -22,22 +21,25 @@ utf8-string == 0.3.*, urlencoded == 0.3.*, json >= 0.4 && < 0.6,- transformers == 0.2.*,+ transformers == 0.3.*, time == 1.4.*, old-locale == 1.0.*, network == 2.3.*,- conduit == 0.0.*,+ conduit == 0.5.*,+ resourcet == 0.3.3.*, case-insensitive >= 0.2 && < 0.5,- http-types >= 0.6.5 && < 0.7,- http-conduit == 1.1.*,+ http-types == 0.7.*,+ http-conduit == 1.5.*,+ monad-control == 0.3.1.*, blaze-builder == 0.3.*,- tls == 0.8.*,+ tls == 0.9.*, tls-extra == 0.4.*,- certificate == 1.*+ certificate == 1.*,+ pem == 0.1.*,+ template-haskell HS-Source-Dirs: Source- Exposed-Modules: Dropbox- Other-Modules: Paths_dropbox_sdk- GHC-Options: -Wall -fno-warn-missing-signatures -fno-warn-name-shadowing+ Exposed-Modules: Dropbox, Dropbox.Certificates, Dropbox.Certificates.TH+ GHC-Options: -Wall -fno-warn-missing-signatures -fno-warn-name-shadowing -fwarn-unused-imports Default-Language: Haskell2010 Default-Extensions: OverloadedStrings, ScopedTypeVariables, Rank2Types
− trusted-certs.crt
@@ -1,341 +0,0 @@-# Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com <server-certs@thawte.com>-# Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress= server-certs@thawte.com------BEGIN CERTIFICATE------MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx-FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD-VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv-biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm-MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx-MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT-DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3-dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl-cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3-DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD-gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91-yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX-L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj-EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG-7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e-QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ-qdq5snUb9kLy78fyGPmJvKP/iiMucEc=------END CERTIFICATE------# Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com <premium-server@thawte.com>-# Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com <premium-server@thawte.com>------BEGIN CERTIFICATE------MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx-FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD-VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv-biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy-dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t-MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB-MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG-A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp-b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl-cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv-bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE-VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ-ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR-uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG-9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI-hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM-pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==------END CERTIFICATE------# Subject: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority-# Issuer: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority------BEGIN CERTIFICATE------MIICPDCCAaUCEDJQM89Q0VbzXIGtZVxPyCUwDQYJKoZIhvcNAQECBQAwXzELMAkG-A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz-cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2-MDEyOTAwMDAwMFoXDTIwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV-BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt-YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN-ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f-zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi-TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G-CSqGSIb3DQEBAgUAA4GBAEtEZmBoZOSYG/OwcuaViXzde7OVwB0u2NgZ0C00PcZQ-mhCGjKo/O6gE/DdSlcPZydvN8oYGxLEb8IKIMEKOF1AcZHq4PplJdJf8rAJD+5YM-VgQlDHx8h50kp9jwMim1pN9dokzFFjKoQvZFprY2ueC/ZTaTwtLXa9zeWdaiNfhF------END CERTIFICATE------# Subject: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority-# Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority------BEGIN CERTIFICATE------MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG-A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz-cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2-MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV-BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt-YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN-ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE-BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is-I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G-CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do-lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc-AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k------END CERTIFICATE------# Subject: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority-# Issuer: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority------BEGIN CERTIFICATE------MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG-A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD-VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0-MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV-BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy-dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ-ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII-0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI-uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI-hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3-YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc-1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA==------END CERTIFICATE------# Subject: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1-# Issuer: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1------BEGIN CERTIFICATE------MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc-MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT-ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw-MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj-dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l-c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC-UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc-58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/-o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH-MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr-aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA-A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA-Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv-8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV------END CERTIFICATE------# Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware-# Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware------BEGIN CERTIFICATE------MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB-lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug-Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho-dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt-SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG-A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe-MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v-d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh-cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn-0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ-M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a-MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd-oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI-DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy-oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD-VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0-dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy-bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF-BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM-//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli-CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE-CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t-3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS-KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==------END CERTIFICATE------# Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority-# Issuer: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority------BEGIN CERTIFICATE------MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi-MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu-MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp-dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV-UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO-ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG-SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz-c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP-OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl-mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF-BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4-qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw-gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB-BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu-bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp-dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8-6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/-h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH-/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv-wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN-pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey------END CERTIFICATE------# Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority-# Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority------BEGIN CERTIFICATE------MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh-MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE-YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3-MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo-ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg-MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN-ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA-PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w-wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi-EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY-avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+-YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE-sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h-/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5-IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj-YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD-ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy-OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P-TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ-HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER-dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf-ReYNnyicsbkqWletNw+vHX/bvZ8=------END CERTIFICATE------# Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository<http://certificates.godaddy.com/repository>, CN=Go Daddy Secure Certification Authority/serialNumber=07969287-# Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority------BEGIN CERTIFICATE------MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx-ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g-RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw-MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH-QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j-b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j-b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj-YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN-AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H-KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm-VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR-SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT-cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ-6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu-MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS-kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB-BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f-BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv-c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH-AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO-BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG-OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU-A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o-0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX-RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH-qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV-U+4=------END CERTIFICATE------# Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2-# Issuer: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2------BEGIN CERTIFICATE------MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx-EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT-EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp-ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz-NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH-EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE-AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw-DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD-E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH-/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy-DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh-GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR-tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA-AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE-FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX-WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu-9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr-gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo-2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO-LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI-4uJEvlz36hz1------END CERTIFICATE------# Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA-# Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA------BEGIN CERTIFICATE------MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT-MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i-YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG-EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg-R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9-9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq-fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv-iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU-1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+-bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW-MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA-ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l-uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn-Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS-tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF-PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un-hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV-5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==------END CERTIFICATE------# Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority-# Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority------BEGIN CERTIFICATE------MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY-MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo-R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx-MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK-Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp-ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC-AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9-AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA-ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0-7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W-kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI-mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G-A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ-KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1-6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl-4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K-oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj-UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU-AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=------END CERTIFICATE------# Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority-# Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>------BEGIN CERTIFICATE------MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh-bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu-Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g-QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe-BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoX-DTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBE-YWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0-aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC-ggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv-2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+q-N1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiO-r18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN-f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEH-U1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHU-TBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMb-VmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwg-SW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlv-biBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEg-MB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUw-AwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdv-ZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMu-Z29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUd-IAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNv-bS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1-QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4O-WBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0Vmsf-SxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==------END CERTIFICATE------# Subject: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>-# Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com<http://www.valicert.com//emailAddress=info@valicert.com>------BEGIN CERTIFICATE------MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0-IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz-BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y-aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG-9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy-NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y-azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs-YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw-Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl-cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY-dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9-WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS-v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v-UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu-IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC-W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd------END CERTIFICATE-------