diff --git a/src/SSH.hs b/src/SSH.hs
--- a/src/SSH.hs
+++ b/src/SSH.hs
@@ -2,7 +2,8 @@
 
 import Control.Concurrent (forkIO)
 import Control.Concurrent.Chan
-import Control.Monad (replicateM, when)
+import Control.Exception (bracket)
+import Control.Monad (replicateM)
 import Control.Monad.Trans.State
 import Data.Digest.Pure.SHA (bytestringDigest, sha1)
 import Crypto.HMAC
@@ -10,7 +11,7 @@
 import Data.List (intercalate)
 import Data.List.Split (splitOn)
 import Network
-import OpenSSL.BN (randIntegerOneToNMinusOne)
+import OpenSSL.BN (randIntegerOneToNMinusOne, modexp)
 import System.IO
 import System.Random
 import qualified Data.ByteString.Lazy as LBS
@@ -65,12 +66,33 @@
 supportedLanguages :: String
 supportedLanguages = ""
 
+data Config =
+    Config {
+        cSession     :: SessionConfig,
+        cChannel     :: ChannelConfig,
+        cPort        :: PortNumber,
+        cReadyAction :: IO ()
+    }
+
+
+startedMessage :: PortNumber -> IO ()
+startedMessage p = putStrLn $ "ssh server listening on port " ++ show p
+
 start :: SessionConfig -> ChannelConfig -> PortNumber -> IO ()
-start sc cc p = withSocketsDo $ do
-    sock <- listenOn (PortNumber p)
-    putStrLn $ "ssh server listening on port " ++ show p
-    waitLoop sc cc sock
+start sc cc p = startConfig (Config sc cc p (startedMessage p))
 
+startConfig :: Config -> IO ()
+startConfig config = withSocketsDo $ do
+    -- waitLoop never actually exits so we could just use finally,
+    -- but bracket seems more future proof
+    bracket
+       (listenOn (PortNumber (cPort config)))
+       sClose
+       (\sock -> do
+           cReadyAction config
+           waitLoop (cSession config) (cChannel config) sock
+       )
+
 waitLoop :: SessionConfig -> ChannelConfig -> Socket -> IO ()
 waitLoop sc cc s = do
     (handle, hostName, port) <- accept s
@@ -232,9 +254,10 @@
 
 kexDHInit :: Session ()
 kexDHInit = do
-    e <- net readInteger
+    e <- net readInteger -- other party's public number
     dump ("KEXDH_INIT", e)
 
+   -- our private number
     y <- io $ randIntegerOneToNMinusOne ((safePrime - 1) `div` 2) -- q?
 
     let f = modexp generator y safePrime
@@ -335,36 +358,54 @@
     authMethods <- gets (scAuthMethods . ssConfig)
 
     dump ("userauth attempt", user, service, method)
-    check <- case fromLBS method of
-        x | not (x `elem` authMethods) ->
-            return False
 
+    let
+        authorized = do
+            sendPacket userAuthOK
+            modify (\s -> s { ssUser = Just (fromLBS user) })
+
+        authfailed = sendPacket $ userAuthFail authMethods
+
+    case fromLBS method of
+        x | not (x `elem` authMethods) -> authfailed
+
         "publickey" -> do
             b <- net readByte
             name <- net readLBS
             key <- net readLBS
-            ch <- auth (PublicKey (fromLBS user) (blobToKey key))
 
-            -- if it's signed, assume it's the second one after auth
-            if ch && b == 1
-                then sendPacket userAuthOK
-                else when ch (sendPacket $ userAuthPKOK name key)
+            let pkey = blobToKey key
+            ch <- auth (PublicKey (fromLBS user) pkey)
 
-            return ch
+            case (ch, b == 1) of
+              (False, _) -> authfailed
+              (True, True) ->
+                  do sig <- net readLBS
+                     sessionID <- gets ssID
+                     let message =
+                           doPacket $ do
+                               byteString sessionID
+                               byte 50 -- SSH_MSG_USERAUTH_REQUEST
+                               byteString user
+                               byteString service
+                               string "publickey"
+                               byte 1 -- TRUE
+                               byteString name
+                               byteString key
+                     ok <- io $ verify pkey message sig
+                     if ok then authorized else authfailed
+              (True, False) -> sendPacket $ userAuthPKOK name key
 
         "password" -> do
             0 <- net readByte
             password <- net readLBS
             ch <- auth (Password (fromLBS user) (fromLBS password))
-            when ch (sendPacket userAuthOK)
-            return ch
+            if ch then authorized else authfailed
 
         u -> error $ "unhandled authorization type: " ++ u
 
-    if check
-        then modify (\s -> s { ssUser = Just (fromLBS user) })
-        else sendPacket (userAuthFail authMethods)
   where
+
     userAuthFail ms = do
         byte 51
         string (intercalate "," ms)
diff --git a/src/SSH/Crypto.hs b/src/SSH/Crypto.hs
--- a/src/SSH/Crypto.hs
+++ b/src/SSH/Crypto.hs
@@ -2,8 +2,8 @@
 
 import Control.Monad (replicateM)
 import Control.Monad.Trans.State
-import Data.ASN1.BinaryEncoding (BER(..))
-import Data.ASN1.Encoding (decodeASN1)
+import Data.ASN1.BinaryEncoding (BER(..), DER(..))
+import Data.ASN1.Encoding (decodeASN1, encodeASN1)
 import Data.ASN1.Stream
 import Data.Digest.Pure.SHA (bytestringDigest, sha1)
 import Data.List (isPrefixOf)
@@ -52,6 +52,11 @@
     = RSAKeyPair
         { rprivPub :: PublicKey
         , rprivD :: Integer
+        , rprivPrime1 :: Integer
+        , rprivPrime2 :: Integer
+        , rprivExponent1 :: Integer
+        , rprivExponent2 :: Integer
+        , rprivCoefficient :: Integer
         }
     | DSAKeyPair
         { dprivPub :: PublicKey
@@ -59,28 +64,59 @@
         }
     deriving (Eq, Show)
 
-
+-- for API compatibility
 rsaKeyPairFromFile :: FilePath -> IO KeyPair
-rsaKeyPairFromFile fn = do
+rsaKeyPairFromFile = keyPairFromFile
+
+keyPairFromFile :: FilePath -> IO KeyPair
+keyPairFromFile fn = do
     x <- readFile fn
-    let asn1
-            = B64.decode
-            . concat
-            . filter (not . ("--" `isPrefixOf`))
-            . lines
-            $ x
+    return $ parseKeyPair x
 
-    case decodeASN1 BER (toLBS asn1) of
+removeKeyPairHeaderFooter :: [String] -> (String, [String])
+removeKeyPairHeaderFooter xs =
+   (reverse . drop 17 . reverse . drop 11 . head $ xs, filter (not . ("--" `isPrefixOf`)) xs)
+
+addKeyPairHeaderFooter :: String -> [String] -> [String]
+addKeyPairHeaderFooter what xs =
+   ["-----BEGIN " ++ what ++ " PRIVATE KEY-----"] ++ xs ++ ["-----END " ++ what ++ " PRIVATE KEY-----"]
+
+-- |Parse an key pair from OpenSSH private key file format.
+parseKeyPair :: String -> KeyPair
+parseKeyPair x =
+    let (what, body) = removeKeyPairHeaderFooter . lines $ x
+
+        asn1 = B64.decode . concat $ body
+
+    in case decodeASN1 BER (toLBS asn1) of
         Right (Start Sequence:ss)
             | all isIntVal (fst $ getConstructedEnd 0 ss) ->
             let (is, _) = getConstructedEnd 0 ss
-            in return $ RSAKeyPair
-                { rprivPub = RSAPublicKey
-                    { rpubE = intValAt 2 is
-                    , rpubN = intValAt 1 is
+            in case what of
+                 "RSA" ->
+                   RSAKeyPair
+                     { rprivPub = RSAPublicKey
+                        { rpubE = intValAt 2 is
+                        , rpubN = intValAt 1 is
+                        }
+                    , rprivD = intValAt 3 is
+                    , rprivPrime1 = intValAt 4 is
+                    , rprivPrime2 = intValAt 5 is
+                    , rprivExponent1 = intValAt 6 is
+                    , rprivExponent2 = intValAt 7 is
+                    , rprivCoefficient = intValAt 8 is
                     }
-                , rprivD = intValAt 3 is
-                }
+                 "DSA" ->
+                   DSAKeyPair
+                    { dprivPub = DSAPublicKey
+                       { dpubP = intValAt 1 is
+                       , dpubQ = intValAt 2 is
+                       , dpubG = intValAt 3 is
+                       , dpubY = intValAt 4 is
+                       }
+                    , dprivX = intValAt 5 is
+                    }
+                 _ -> error ("unknown key type: " ++ what)
         Right u -> error ("unknown ASN1 decoding result: " ++ show u)
         Left e -> error ("ASN1 decoding of private key failed: " ++ show e)
   where
@@ -90,8 +126,34 @@
     intValAt i is =
         case is !! i of
             IntVal n -> n
-            x -> error ("not an IntVal: " ++ show x)
+            v -> error ("not an IntVal: " ++ show v)
 
+-- |Turn an key pair into OpenSSH private key file format.
+printKeyPair :: KeyPair -> String
+printKeyPair keyPair =
+  unlines . addKeyPairHeaderFooter what . lines . B64.encode . fromLBS . encodeASN1 DER $ asn1Structure
+
+    where
+
+      (what, asn1Structure) =
+        case keyPair of
+          (RSAKeyPair { rprivPub = RSAPublicKey { rpubE = e, rpubN = n },
+                        rprivD = d, rprivPrime1 = p1, rprivPrime2 = p2,
+                        rprivExponent1 = exp1, rprivExponent2 = exp2, rprivCoefficient = c
+                      })
+             -> ("RSA", [Start Sequence, IntVal 0, IntVal n, IntVal e, IntVal d,
+                         IntVal p1, IntVal p2, IntVal exp1, IntVal exp2, IntVal c, End Sequence])
+
+          (DSAKeyPair { dprivPub = DSAPublicKey { dpubP = p, dpubQ = q, dpubG = g, dpubY = y }, dprivX = x })
+             -> ("DSA", [Start Sequence, IntVal 0, IntVal p, IntVal q, IntVal g,
+                         IntVal y, IntVal x, End Sequence])
+
+          _ -> error "printKeyPair: unsupported key pair"
+
+
+
+-- these are the generator and prime for the "Second Oakley Group" described in RFC 2409
+
 generator :: Integer
 generator = 2
 
@@ -108,13 +170,12 @@
 fromBlocks :: [LBS.ByteString] -> LBS.ByteString
 fromBlocks = LBS.concat
 
-modexp :: Integer -> Integer -> Integer -> Integer
-modexp = modexp' 1
-  where
-    modexp' y _ 0 _ = y
-    modexp' y z e n
-        | e `mod` 2 == 1 = modexp' (y * z `mod` n) ((z ^ (2 :: Integer)) `mod` n) (e `div` 2) n
-        | otherwise = modexp' y ((z ^ (2 :: Integer)) `mod` n) (e `div` 2) n
+rsaKeyLen :: PublicKey -> Int
+-- There's no explicit indication of size in the key format so we just
+-- have to look at the magnitude of the numbers.
+-- This is consistent with what e.g. openssh does.
+rsaKeyLen (RSAPublicKey _e n) = (1 + integerLog2 n) `div` 8
+rsaKeyLen _ = error "rsaKeyLen: not an RSA public key"
 
 blob :: PublicKey -> LBS.ByteString
 blob (RSAPublicKey e n) = doPacket $ do
@@ -143,9 +204,11 @@
         u -> error $ "unknown public key format: " ++ u
 
 sign :: KeyPair -> LBS.ByteString -> IO LBS.ByteString
-sign (RSAKeyPair (RSAPublicKey e n) d) m = return $ LBS.concat
+sign (RSAKeyPair p@(RSAPublicKey e n) d _ _ _ _ _) m = do
+  let keyLen = rsaKeyLen p
+  return $ LBS.concat
     [ netString "ssh-rsa"
-    , netLBS (RSA.rsassa_pkcs1_v1_5_sign RSA.ha_SHA1 (RSAKey.PrivateKey (RSAKey.PublicKey 256 n e) d 0 0 0 0 0) m)
+    , netLBS (RSA.rsassa_pkcs1_v1_5_sign RSA.ha_SHA1 (RSAKey.PrivateKey (RSAKey.PublicKey keyLen n e) d 0 0 0 0 0) m)
     ]
 sign (DSAKeyPair (DSAPublicKey p q g y) x) m = do
     (r, s) <- DSA.signDigestedDataWithDSA (DSA.tupleToDSAKeyPair (p, q, g, y, x)) digest
@@ -159,3 +222,24 @@
   where
     digest = strictLBS . bytestringDigest . sha1 $ m
 sign _ _ = error "sign: invalid key pair"
+
+-- |The length of the actual signature for a given key
+-- The actual signature data is always found at the end of a complete signature,
+-- so can be extracted by just grabbing this many bytes at the end.
+actualSignatureLength :: PublicKey -> Int
+actualSignatureLength p@(RSAPublicKey {}) = rsaKeyLen p
+actualSignatureLength (DSAPublicKey {}) = 40
+
+verify :: PublicKey -> LBS.ByteString -> LBS.ByteString -> IO Bool
+verify p@(RSAPublicKey e n) message signature = do
+    let keyLen = rsaKeyLen p
+        realSignature = LBS.drop (LBS.length signature - fromIntegral keyLen) signature
+    return $ RSA.rsassa_pkcs1_v1_5_verify RSA.ha_SHA1 (RSAKey.PublicKey keyLen n e) message realSignature
+
+verify (DSAPublicKey p q g y) message signature = do
+    let realSignature = LBS.drop (LBS.length signature - 40) signature
+        r = fromOctets (256 :: Integer) (LBS.unpack (LBS.take 20 realSignature))
+        s = fromOctets (256 :: Integer) (LBS.unpack (LBS.take 20 (LBS.drop 20 realSignature)))
+    DSA.verifyDigestedDataWithDSA (DSA.tupleToDSAPubKey (p, q, g, y)) digest (r, s)
+  where
+    digest = strictLBS . bytestringDigest . sha1 $ message
diff --git a/src/SSH/Session.hs b/src/SSH/Session.hs
--- a/src/SSH/Session.hs
+++ b/src/SSH/Session.hs
@@ -13,7 +13,7 @@
 import qualified Data.Map as M
 
 import SSH.Channel
-import SSH.Crypto
+import SSH.Crypto hiding (verify)
 import SSH.Debug
 import SSH.NetReader
 import SSH.Packet
@@ -86,7 +86,7 @@
     SessionConfig
         { scAuthMethods = ["publickey"]
         , scAuthorize = const (return True)
-        , scKeyPair = RSAKeyPair (RSAPublicKey 0 0) 0
+        , scKeyPair = RSAKeyPair (RSAPublicKey 0 0) 0 0 0 0 0 0
         {-\(Password u p) ->-}
             {-return $ u == "test" && p == "test"-}
         }
diff --git a/src/SSH/Util.hs b/src/SSH/Util.hs
--- a/src/SSH/Util.hs
+++ b/src/SSH/Util.hs
@@ -1,10 +1,14 @@
+{-# LANGUAGE MagicHash #-}
 module SSH.Util where
 
 import Data.Word (Word8)
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as LBS
 
+import GHC.Base (Int(I#))
+import GHC.Integer.Logarithms (integerLog2#)
 
+
 toLBS :: String -> LBS.ByteString
 toLBS = LBS.pack . map (fromIntegral . fromEnum)
 
@@ -41,3 +45,7 @@
          pad = replicate (l - unPaddedLen) (0x00::Word8)
 	 z = toOctets (256 :: Integer) y
 	 unPaddedLen = length z
+
+integerLog2 :: Integer -> Int
+integerLog2 n | n <=0 = error "integerLog2: argument must be positive"
+integerLog2 n = I# (integerLog2# n)
diff --git a/ssh.cabal b/ssh.cabal
--- a/ssh.cabal
+++ b/ssh.cabal
@@ -1,30 +1,45 @@
 name:                ssh
-version:             0.2.13.1
+version:             0.3
 synopsis:            A pure-Haskell SSH server library.
 description:
     This a library for implementing your own servers that handle SSH
     requests and authorization, etc. Similar to Python's Twisted Conch
     library. It's used eg by darcsden to provide basic SSH access.
 
-    This package receives only basic maintenance; if you'd like to
-    take it over, please contact the current maintainer.
 
-homepage:            http://hub.darcs.net/simon/ssh
+homepage:            http://hub.darcs.net/ganesh/ssh
 license:             BSD3
 license-file:        LICENSE
 author:              Alex Suraci <suraci.alex@gmail.com>
-maintainer:          Simon Michael <simon@joyful.com>
+maintainer:          Ganesh Sittampalam <ganesh@earth.li>
 category:            Network
 build-type:          Simple
-cabal-version:       >= 1.6
+cabal-version:       >= 1.8
 stability:           Unstable
 tested-with:         GHC==7.8.2
-data-files:          CHANGES,
-                     README
+extra-source-files:  CHANGES,
+                     README,
+                     test/keys/host,
+                     test/keys/host.pub,
+                     test/keys/client/id_rsa_test,
+                     test/keys/client/id_rsa_test.pub,
+                     test/keys/client/id_rsa_test2,
+                     test/keys/client/id_rsa_test2.pub,
+                     test/keys/client/id_rsa_1024,
+                     test/keys/client/id_rsa_1024.pub,
+                     test/keys/client/id_rsa_2048,
+                     test/keys/client/id_rsa_2048.pub,
+                     test/keys/client/id_rsa_4096,
+                     test/keys/client/id_rsa_4096.pub,
+                     test/keys/client/id_dsa,
+                     test/keys/client/id_dsa.pub,
+                     test/keys/client/id_dsa2,
+                     test/keys/client/id_dsa2.pub
 
+
 source-repository   head
     type:           darcs
-    location:       http://hub.darcs.net/simon/ssh
+    location:       http://hub.darcs.net/ganesh/ssh
 
 library
   hs-source-dirs:   src
@@ -52,6 +67,7 @@
                     crypto-pubkey-types >= 0.2,
                     cryptohash-cryptoapi,
                     HsOpenSSL >= 0.8,
+                    integer-gmp >= 0.5 && < 0.6,
                     network,
                     process,
                     RSA >= 1.2 && < 1.3,
@@ -60,3 +76,28 @@
                     SimpleAES,
                     split,
                     transformers
+
+test-suite ssh-test
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is:        test.hs
+  other-modules:    EmbedTree
+
+  ghc-options: -Wall -threaded
+
+  build-depends:
+      tasty                      >= 0.10  && < 0.11,
+      tasty-hunit                >= 0.9   && < 0.10,
+      tasty-quickcheck           >= 0.8   && < 0.9,
+      HUnit                      >= 1.0   && < 1.3,
+      QuickCheck                 >= 2.8   && < 2.9,
+      libssh2                    >= 0.2   && < 0.3,
+      filepath                   >= 1.3   && < 1.4,
+      directory                  >= 1.2   && < 1.3,
+      bytestring                 >= 0.10  && < 0.11,
+      template-haskell           >= 2.8   && < 2.10,
+      th-lift-instances          >= 0.1   && < 0.2,
+      pseudomacros               >= 0.0   && < 0.1,
+      containers,
+      base,
+      ssh
diff --git a/test/EmbedTree.hs b/test/EmbedTree.hs
new file mode 100644
--- /dev/null
+++ b/test/EmbedTree.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE TemplateHaskell, TupleSections #-}
+module EmbedTree where
+
+import Control.Applicative ((<$>))
+import qualified Data.Map as Map
+import Data.Map (Map)
+
+import Instances.TH.Lift ()
+
+import Language.Haskell.TH
+import Language.Haskell.TH.Syntax
+
+import PseudoMacros
+
+import System.Directory
+  (doesFileExist, doesDirectoryExist
+  ,getDirectoryContents
+  )
+import System.FilePath (takeDirectory, (</>))
+
+data Entry = File String | Directory (Map String Entry)
+
+getFile :: Entry -> String
+getFile (File contents) = contents
+getFile (Directory _) = error $ "getFile: found a directory"
+
+getDirectory :: Entry -> Map String Entry
+getDirectory (File _) = error $ "getDirectory: found a file"
+getDirectory (Directory contents) = contents
+
+getEntry :: String -> Map String Entry -> Entry
+getEntry name entries =
+  case Map.lookup name entries of
+    Nothing -> error $ "getEntry: " ++ name ++ " not found"
+    Just v -> v
+
+instance Lift Entry where
+  lift (File contents) = AppE (ConE 'File) <$> [|contents|]
+  lift (Directory entries) = AppE (ConE 'Directory) <$> [|entries|]
+
+readTree :: FilePath -> IO Entry
+readTree path = do
+  isFile <- doesFileExist path
+  if isFile
+    then File <$> readFile path
+    else do
+      isDirectory <- doesDirectoryExist path
+      if isDirectory
+        then do
+          entries <- filter (`notElem` [".", ".."]) <$> getDirectoryContents path
+          Directory . Map.fromList <$> mapM (\entry -> (entry,) <$> readTree (path </> entry)) entries
+        else fail $ path ++ " is not a file or directory"
+
+embedTree :: FilePath -> ExpQ
+embedTree relPath = do
+    t <- runIO (readTree (takeDirectory $__FILE__ </> relPath))
+    [|t|]
diff --git a/test/keys/client/id_dsa b/test/keys/client/id_dsa
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_dsa
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQDOY5c/pQcpuFrzVB4OPxFsp/UDRUu5IwUncOr0Yr39n+Y8Jjy2
+l/lLoSrZiYO2An1xuTESkUmyw3AkTRGy2YXFgCbD/rQ7/SSFLtYnQWk3DXqNGIPe
+XeEQatSEB2VjFwGxkpL+TZ+oZCYyLTa535QYRmBu5OTikiJwlSicsHMmlwIVALT6
+OZGvc3s2fmGss6CNfnIB89LlAoGAbni81v5fJATvHhHikmw2y2C/10m24rq1uH9F
+XYjXMwoIOgB4/E2375CvMMsCHMF3g07vsetgOd93E3XHDFf7ZsKX8fPNN6BtMJgS
+Q5gRKHq8c1FmYKkhOwjKSrh1T10sbp6fvhs8hjh7xxv+GabMltnliowRJgP4z1/Z
+fEkg78oCgYEAt3f+TU+z/Ma7IgxU32Jd5BhBNg6wINctWs4ecaXg72F4CxlZSh6/
+a0SQeThYMaqYsGsOpAN3iJFiZ1F+GJZ/WrJ2Y/J4r09InawAW0ISHk7LZ9PmI9wU
+G6K63WNymNuOljZQPyfyd/hi0M2LqzBlEOsvYrRHZbIa6vcRv5SqjNYCFDxx6Mmt
+Y43fIInyCuhiek6d3gHl
+-----END DSA PRIVATE KEY-----
diff --git a/test/keys/client/id_dsa.pub b/test/keys/client/id_dsa.pub
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_dsa.pub
@@ -0,0 +1,1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAM5jlz+lBym4WvNUHg4/EWyn9QNFS7kjBSdw6vRivf2f5jwmPLaX+UuhKtmJg7YCfXG5MRKRSbLDcCRNEbLZhcWAJsP+tDv9JIUu1idBaTcNeo0Yg95d4RBq1IQHZWMXAbGSkv5Nn6hkJjItNrnflBhGYG7k5OKSInCVKJywcyaXAAAAFQC0+jmRr3N7Nn5hrLOgjX5yAfPS5QAAAIBueLzW/l8kBO8eEeKSbDbLYL/XSbbiurW4f0VdiNczCgg6AHj8TbfvkK8wywIcwXeDTu+x62A533cTdccMV/tmwpfx8803oG0wmBJDmBEoerxzUWZgqSE7CMpKuHVPXSxunp++GzyGOHvHG/4ZpsyW2eWKjBEmA/jPX9l8SSDvygAAAIEAt3f+TU+z/Ma7IgxU32Jd5BhBNg6wINctWs4ecaXg72F4CxlZSh6/a0SQeThYMaqYsGsOpAN3iJFiZ1F+GJZ/WrJ2Y/J4r09InawAW0ISHk7LZ9PmI9wUG6K63WNymNuOljZQPyfyd/hi0M2LqzBlEOsvYrRHZbIa6vcRv5SqjNY= ganesh@paxton
diff --git a/test/keys/client/id_dsa2 b/test/keys/client/id_dsa2
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_dsa2
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQD3ncQcimGZGV5mMZxfYRpfMcwUhMXbW7jUuWLDvfMXm9dpfw4H
+nKeXP7LtDSjzUQreXA6lf20wh3/dxsKNFAbqPwgDuOxZD5JXPahMaoNqOg+/V5DD
+qhTjJrxGzsMCWstYV1nsCQMN4aVlZLqJ4dhiVwwRIkCbPXFmhuJYxhL8aQIVAO9L
+HPk1IGpMSi2gcGqKS754Kr+JAoGAfWTPY44qm5T+z6Tn7EwVaak5wj4XwrzvfGKY
+ay/LKyPGn9m8Y+YfJ2zv1QQyTXmNC1wjpKI8ZL6ff8d0ft1RO6+HsFvoCSHDWPKR
+x5ZopfUg/pDeiVwwU83IhSmPUECz/+8jGZ+rKrQdLc3bc1GhyfvcsAWL5PBwzqDs
+z4X4WTUCgYAPrLJresqVe+jDHRIzvW5WhyYgyjN2PMHYxV/J7i54pM4Qj/zHPiK3
+TpeuaCLgih2DQbtJrn+4KfIbv+/v2AsHGcj/kZEGvS1+t+pX98zo10gdsSAVoOig
+nAp4Rt18vFbSc+0xQgj0NFrWvSrU6bszSVD1fYGkpdzbg+WSwscMsQIVALC8wPU1
+fTf6hMJojbMUTbOZWMRM
+-----END DSA PRIVATE KEY-----
diff --git a/test/keys/client/id_dsa2.pub b/test/keys/client/id_dsa2.pub
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_dsa2.pub
@@ -0,0 +1,1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAPedxByKYZkZXmYxnF9hGl8xzBSExdtbuNS5YsO98xeb12l/Dgecp5c/su0NKPNRCt5cDqV/bTCHf93Gwo0UBuo/CAO47FkPklc9qExqg2o6D79XkMOqFOMmvEbOwwJay1hXWewJAw3hpWVkuonh2GJXDBEiQJs9cWaG4ljGEvxpAAAAFQDvSxz5NSBqTEotoHBqiku+eCq/iQAAAIB9ZM9jjiqblP7PpOfsTBVpqTnCPhfCvO98YphrL8srI8af2bxj5h8nbO/VBDJNeY0LXCOkojxkvp9/x3R+3VE7r4ewW+gJIcNY8pHHlmil9SD+kN6JXDBTzciFKY9QQLP/7yMZn6sqtB0tzdtzUaHJ+9ywBYvk8HDOoOzPhfhZNQAAAIAPrLJresqVe+jDHRIzvW5WhyYgyjN2PMHYxV/J7i54pM4Qj/zHPiK3TpeuaCLgih2DQbtJrn+4KfIbv+/v2AsHGcj/kZEGvS1+t+pX98zo10gdsSAVoOignAp4Rt18vFbSc+0xQgj0NFrWvSrU6bszSVD1fYGkpdzbg+WSwscMsQ== ganesh@paxton
diff --git a/test/keys/client/id_rsa_1024 b/test/keys/client/id_rsa_1024
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_1024
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCVinDqmlOiQg+qIGP//qby2v9huGNp2z0pWngHGD1qdZtq5Mkj
+PgYEbWO1xm0CIE2dLKwb704+U6Cj0ZM7vi1PwteojKG3Opt4O9vfdLpM6LSkIUPi
+jGvIH0sbSwSTtOv2DPD60bvyeqwLDpfh+zZgLXC7dbi72W9IsN86k8h38QIDAQAB
+AoGANG0UqSrxpzEBzOMOK/FbFkkwv1GliLcT+it9aP9WiLwygIc0/OYBtgujqVan
+YNdyXmgK7sA27iLjbotK0ucZexPf1ZmCt9eAE0AHdfF+CJd/V0IfxKCO/9/KZlKT
+boCyOILyv2ilHQmyY2TZVShG2J/CD5+54irTA7mWVBYrpAECQQDGZyIWH4WhRipf
+RnUsUuQWGBxEM5XmaNvt01LiDxEBmK2CMQI9BYozTCyn0V8skuQZcc2GNRi9k42e
+3cdDrj4lAkEAwPP8Mn5vWIgrodAAHFOCQQ1kMU757RoX4jNCBp8243NyvC8v5Zkb
+dNcqcYIe9NVkHa6JA1/2Ia8AGY80EKPq3QJALY4wvXOvc0BxKj87WFoQFSKkGuTd
+XnJlTU5gkZd0CDHOZT5rjSdgfDbZag8hE1MHHPCMiGxYqe7fbcLuphO0IQJBAKyI
+/xh/qjz19l4IyjCKE8zrlo3o2t7DGFwyDXYyZDhagVv4rdGWaNC/nbpF6jxvm8hW
+yzlzU3mnLGWugZXnnRkCQGHJ3iEejJYUF6MAcjYS74eSJEXGyP1ylBb5jUZQ1o14
+BImqfcScySTgkt4s4ix7VsKaio8k7AW1OLOJvNYj09Q=
+-----END RSA PRIVATE KEY-----
diff --git a/test/keys/client/id_rsa_1024.pub b/test/keys/client/id_rsa_1024.pub
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_1024.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCVinDqmlOiQg+qIGP//qby2v9huGNp2z0pWngHGD1qdZtq5MkjPgYEbWO1xm0CIE2dLKwb704+U6Cj0ZM7vi1PwteojKG3Opt4O9vfdLpM6LSkIUPijGvIH0sbSwSTtOv2DPD60bvyeqwLDpfh+zZgLXC7dbi72W9IsN86k8h38Q== ganesh@paxton
diff --git a/test/keys/client/id_rsa_2048 b/test/keys/client/id_rsa_2048
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_2048
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAu5uFyWkM9i5vOiI8TKZUy5dLX0fJ147qkicwy0J+3dbYFZz8
+1OUC10OIJX0YO7i5QQdQoE4v1aq/v3KofZ47EnFna9tzz22rsEKVI/CvU+zIolSN
+2hn+URpDpTGKlz3aKFaxuuVFNlHX5KBYrY9Se1G1rriDLUFxKBssJwtjjELWheZL
+beyn2ZjEBuCbuFYsuQYQvVvPKqMnDBWVNARVYdwU3hqlPpEJi9eR5SZhKpLbJOuu
+mL/f+FsAdcvQzul8DaCmab6ffxx4+oFFJEIJ1ui+eMlU7KUqBq0KKklteHycOHRX
+SlYtwTwfJGobuCGQ40UrWOYLqaebbzebgIc0zwIDAQABAoIBAQCO1si0IwG1ZoFV
+N7/FdFbXc+f1MYliT/QVNzWVbJl/ehanzhFKXtsc3tQIBwiuX+TcuB8RDrao7gp0
+T24txo2fayydGEdCxXOVDfzTkmtLcNsJjWs7hdL2GRMr8V5d7L/vOqKzc+CQjRvd
+fKBH9PmN3xZ/YwitKkhnitjBGZC/ts2K3AK8THZ6VgK5h70rvvUu1C3eUj6qF5IU
+Pze/Kb5HXVLnykI4wcFFF/IrR0Yi7XE+OCpeALf5XqeUlHwoyLttPxsfog7vxJMp
+tJrKrcgmnsTlhOCktJys8o34QQrqtDe89bMWXbyeBadfbcLi7S1DPNr8j2ZKKimX
+1YnPJFTRAoGBAOmxUrZfJLUiwaJi755uhSgYXDVsHWucHwldLS3MQ+WbeTUbq8Sk
+W+zrFMCVYMo9VtqHUrNx5WTIjggxFs6gXQ8DJXYK8obGXW4Pocv54d+vgFSTwwwE
+TcAOsxZpUumQHABeuZinTK8DaGJR6/btbaYk7V1OBHbD+9ixEqtrZU7jAoGBAM2E
+B8y2IXXiyRGIeHC8mtiJlwY528QZn1EaCRsFXLvnxv+00jaf9CZUmR74ZyFi/bJ4
+V9oXFkrTlRq6gJJFA/G3BBvivIxfn0+iLOj2ScgELWfPZ+qu/zbR9qyZrczf4CEf
+q74x/5k3SMwi4Z8w4X68HaJtIKv3SWFQsgRn7VolAoGATJ5dvvtcvqKhl8sWQvx7
+XdT2znEfCDwMlPZerKhPDoW17Kqg230Dwp6klCulq0kHI+jAPaM3EZ8LqjXmA2Li
+f9yJOLWIJJX+5ensI0NPQhZ5XcGAbn0uvKxVHSD1FSyxcZGdCia35p2YaLvxQGlB
+zPpIdJHytrm05avQsjEo6v8CgYAVcjy6IRt2yNbArKQc56GlDR6keK81VJMqjHP8
+zN9zgGlkz8LMDn8U7OkZsURZ2JGu6R4J1TTvVAsQQfwex3L062tTWaLhZy0hy0Oy
+f9kNNVpjpeIrPF3Ho7uBIXxgj9A8xKhQbblFjN5c5xryWNB/QDQ7efXZ3DFdJWWe
+ThYXvQKBgQDQ+O0mYnsK2qibTaVIatiLwmFby7LIYbmz/LmeOqUm98P5VVzSoOmx
+54uONScpqp7BSKt7FMdqxVZzFVWQ6hZdI1O7NTQbst74R/RUDSE9+9stIXKlNNvz
+3Gyh68aTZWxmgwV4eguhIPwfFJAnJ08R3zcV1OAAlb8trUNy6u4FuA==
+-----END RSA PRIVATE KEY-----
diff --git a/test/keys/client/id_rsa_2048.pub b/test/keys/client/id_rsa_2048.pub
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_2048.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7m4XJaQz2Lm86IjxMplTLl0tfR8nXjuqSJzDLQn7d1tgVnPzU5QLXQ4glfRg7uLlBB1CgTi/Vqr+/cqh9njsScWdr23PPbauwQpUj8K9T7MiiVI3aGf5RGkOlMYqXPdooVrG65UU2UdfkoFitj1J7UbWuuIMtQXEoGywnC2OMQtaF5ktt7KfZmMQG4Ju4Viy5BhC9W88qoycMFZU0BFVh3BTeGqU+kQmL15HlJmEqktsk666Yv9/4WwB1y9DO6XwNoKZpvp9/HHj6gUUkQgnW6L54yVTspSoGrQoqSW14fJw4dFdKVi3BPB8kahu4IZDjRStY5gupp5tvN5uAhzTP ganesh@paxton
diff --git a/test/keys/client/id_rsa_4096 b/test/keys/client/id_rsa_4096
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_4096
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAxO3H+vhqmxoEuX4/bRfB393ueSWzyH+ziGaK5TiEvDZ4IdFv
+oVVqyzE4978ZdNADzD1ihjrWZTfuy67wXtRoByX34hM2OMdQlagE6oILDhZiV6ab
+AlyUfuz1nqK1e5Y9yw4OhbYIBIxuKgf0/tqStA/eMmhnNvjeDXLjkXcc5PDX9Ik5
+hLNUccbu53AYp/FWQcbZCLtdl88JTmKtR9mhzMQ1PvRu3KdTJnQm5FlA2u1Tupu6
+Cw6yuoMGSBbaNAfCoaiCdHDYJgMeYSASoa/rDodxL0xIFGBlE7v/vRDdkTu3fS6E
+c6kCU6u92lt1o+2FzVIVbf11H+K7v9v7iUMWSF4YLWrPwXJ3jSlsKd5XT3PBtnpQ
+wZcwnqRFxehZ8xA3DGJUfCLYYE3FyW72VVj+8+V3w0F6gJ3xOsx2XTmREftIScMz
+KtufS3fCiqz2HTwhhnFJ12HjzzmWkzfLQsicw0oP1t0HHHjKu95Jjy89KuxWeFLA
+fAT7FjACYrqpGTt5VPLkw+UAyTP98Ktm+zjgHq/EFunplUt4CGt/OblM4oQdh984
+D+KbUlLefVJ6kMUMz96Gg1ODxjA0whTpwBhTTD3iH2E6nzR9MKmJOTlhtut+bnWf
+9/69aBwSGfX0QXh2YuIICBXKEECBLzjtZwky6GasOBoc0+RNGU5UaM/oBPMCAwEA
+AQKCAgEAnXK3hzMCmQuOZ3hG4cQy3/gi6H16Zn2jXxUNTAOKLKkoF3HJ3KXcgqTS
+NVJ22exOfQc/NK7qJ3c212cBC/CrU7vJmtldarwA9AaoF47to8/FxOqR2KuIloqC
+Ptk8XJlcwnJ0rfdCJdDZa4V9Hh5HWOuu7YKHs236q6oFxJIFag4du4fTkwOGKomx
+DR2UuQG9w01mzRJw3CVN/XvrqxyXvo3JPJJV6NlnI8GOLtglgGRrozFK5/hScci8
+Rvrubtcdh/6gftSSHKI1sdXgz6FFQBKOLhDBOHlxlvuiltlbLPkf1VhR0d942sjf
+PCwPvIb1iZltRMl20Y2IKveI/s/CiaRD7kAdjwXiuk0jvhGGlHCkABKdo+zHnLy7
+1xx5Ef1IYr9fGx0PL2ELHdU+J0QXxKRZOgWWI9j6q55UPsQmC3Ue9zCPqhbhLLb8
+Nsi95uqmcBY1J/EmUMs/ENVD4Xl9yeIhqyv/Mp1coUT/Zmj9S65KlszaJfLQVIMd
+EVdyT/P5tTvf48sT+TK2RljDNhvHAIFlzjsXGKPN3HmTyVsDHdaqCVnr+K4aCRxi
+iP8KraTeHiRozXUJMSGSBPpsmEvlnyuTRSkZ5Ox1QG9G7VxZHgmHQBdUidO3QvFj
+3ji+1NYS4WXrxkJNb3F667Y5XRHPAkAUmT0LIFZOUwwRxINIpyECggEBAPuZqRhy
+CQHmVhH9YTJP4FqHo4n9gjhc1Gm43CKr86P4lwlLlFgDfe+qXCeqG63+C4pzXaQn
+vUaxvt3PtMDk6azRSY1tBgJRUZOYgPZuTkbTN3+a+HQhXUyVHKVLg1hLLg2PZcnZ
+Rpcdpr2mpOFh/bzIgMQOVsxbVsFMbfWT6x64QasPs2Ij/LPenwzqj45fCZULec6R
+gEIT+xh4jX5UajqewD8rRYQ4Q4qFPf593vl0q1oxCDmK6Wfwh5mOlj6wWGo6b6Ri
+Bo8e8bVT7Y+mAjddhwZNkREdP3AyUHi/JI82J31fm2Dg7WCnprwAwUYLqyVtXRhG
+G4cJxhHpKfxNWWsCggEBAMhfX35FD87mtrdKxT7tt0YOQ/VtX5+nHh07yEO/uwMD
+qffAdUpnsmYbgrIRzL7QmCvP9kQPovP6aEvy3A/wZI+Rw1i2VKhQh0g6bPdDL5Ck
++g6cKu3OoVQup8T78rFPxz8fZVnVf9UJIC5DoLbNU8Pfb3HsogQwg4vmXvtpyJYg
+QJUqylGYrmk9iVcRu4hHjBkd+9D3vOKCE/27BK/GcjNamypqcjOJOAGFyczXRs+S
+ielsgLsFRXCNmVSQ3QZ3zACrZ+CTJqvJffeGF4RL+mtOV758hVDguY7ZaIPbS+0w
+rbbZUdXF9ebxJQyJ5st35E+B8HKCtYEK4Q5BSMnJvJkCggEAXQ0RetcB+c/kWTbq
+sZ7VDOZJV4mIlavPa2JRGAmcTDJuOaPYM0znULIi7xB8uDbSsdvE8Cc6W2D1hDeD
+VCvVOHMWztVZeZX1o66tU3asQIlxZyI7bUfBp8cmFwP8ibUUTTORo7tV3iG7PzzY
+kfqZyy4kYV4kP+QwC8FmkYKpXG0s7EUcRNmmZieZjz9Y5IDFnHfoDrvFQar+HKjJ
+O8WgnBmGZFZumV3trNdmfC61PnElxm+H6TA07poIrIQNkRXLPU5rZ9JRNrFtF3D4
+1T3CaBOREoWxdzDn+2jAVkfrD4QpyraHUqcdY7fddH6a/HroSylNWuLi32h/9rPT
+MVqyDQKCAQAaO4JBAcGkEMhzDrLsHisUXOHAy+Ts/fAPW7hIRl2xc1VZPjUc3J6a
+h5eAwJvRj3WcpslS98kZr/rflpgA7jP8J9UvVA+ZSZGsfxms9XrQsQibyQ5Fu/ub
+DdChFWsck5k+Rln6fN0TgvJXnDr6M793sVTh0V0Ut1VBh5N/zsWYAfjyjnuWWyra
+VFgashOfL97Dmw1Qul5cOTNs0IM4j/57gq01zGHPJXPTnzRgQP7gRgsQKEhiM7p3
+ldIxJBYLtrtaixY6bIlvjB2VvlRt2ZSbX9JU+fBqOkGQ1h28xYUXNHxJqAHyvswG
+xCNZlORGVxfo4NYd58bjcg0s1Jc289ZhAoIBAQDnsQUF4Wl5XGWcaPySMGon9rG7
+gI1X01dW4v1xyPo+fN1TaIp/YzHE8WaPpB2qJZLsKnAGZyBf+9EgL72SBRlwSqq1
+Zt7o9TDxL/Ut/nxLGG54j+6JEXGnTzTLdLBKLwG6th7qsKwocNdaMMoI8u5ljVwA
+0wb996ww2CUwFpRB8ww6cS7oSyw13usSXeqJUk+sskQ4CKmTroUWhEg3ptDWvoyA
+0GuIcN7c9l91br3fRLZKzKXk6zmHj9JAq3rY+kLTtZEpWwmbSUFCICnwpk3ffSV2
+F/xxheoW7u2Js0bFDxdwnmEZZf5cInqgtSp9iq2sbb5U2wvs7jn8oPF/JhKj
+-----END RSA PRIVATE KEY-----
diff --git a/test/keys/client/id_rsa_4096.pub b/test/keys/client/id_rsa_4096.pub
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_4096.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDE7cf6+GqbGgS5fj9tF8Hf3e55JbPIf7OIZorlOIS8Nngh0W+hVWrLMTj3vxl00APMPWKGOtZlN+7LrvBe1GgHJffiEzY4x1CVqATqggsOFmJXppsCXJR+7PWeorV7lj3LDg6FtggEjG4qB/T+2pK0D94yaGc2+N4NcuORdxzk8Nf0iTmEs1Rxxu7ncBin8VZBxtkIu12XzwlOYq1H2aHMxDU+9G7cp1MmdCbkWUDa7VO6m7oLDrK6gwZIFto0B8KhqIJ0cNgmAx5hIBKhr+sOh3EvTEgUYGUTu/+9EN2RO7d9LoRzqQJTq73aW3Wj7YXNUhVt/XUf4ru/2/uJQxZIXhgtas/BcneNKWwp3ldPc8G2elDBlzCepEXF6FnzEDcMYlR8IthgTcXJbvZVWP7z5XfDQXqAnfE6zHZdOZER+0hJwzMq259Ld8KKrPYdPCGGcUnXYePPOZaTN8tCyJzDSg/W3QcceMq73kmPLz0q7FZ4UsB8BPsWMAJiuqkZO3lU8uTD5QDJM/3wq2b7OOAer8QW6emVS3gIa385uUzihB2H3zgP4ptSUt59UnqQxQzP3oaDU4PGMDTCFOnAGFNMPeIfYTqfNH0wqYk5OWG2635udZ/3/r1oHBIZ9fRBeHZi4ggIFcoQQIEvOO1nCTLoZqw4GhzT5E0ZTlRoz+gE8w== ganesh@paxton
diff --git a/test/keys/client/id_rsa_test b/test/keys/client/id_rsa_test
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_test
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEApAocCmMKaBUJ8AZ5KQbqDZcCsLTFrRm9qTvDiVxS2ee+nhgI
+sBuxYi0hhzjj2S7ZLgUYORIx2CPL//ldw7sjkUUnKQHxOM/wkScGp3C+QqtfABhI
+hD2S+KbA3HWqL4ByN3cWb3zDmWOysGuRbIwEGYyTYrkayHU+ewPqGyInP54uH8Ut
+1IlsDXO9waCiHxuBJFaE37Xycrtphetktv8JjvS3kfbSC6OZTsF0M7PvCnyUmvit
+SfliHQtzI26lWGHR08aZduyBd+txvbJKDzV8vyrSA+zYk9uWnuEju47RJslE9yrg
+IVtRnduRRszLXlnHCyWgm5lK3zlzf63S45Av1QIDAQABAoIBAE/1YY+qkSMExlBK
+R3q5FRNEvZn2s7hZqLo6GGj3KKdwr9iN7IYzKXaqewJXt7BghppI/3KqLMOnR0Ph
+gFPs/zxLUfhKKxO/QArw0+yAJy2GLQt1yrsy7FXpqm6LqEX5PTNOCBCV5x34m9wk
+wsD/SuxNOnZPtf9qLud1lAJf1nPKVbGeZHZuXmvExRSLv/sDsy80W8kUapDLZgMm
+6z6G6rR9+iofNdfz9O/xieHcsLuAuShIltnM9Mu2BHxYreZ7L6g4tLfTZXa6dhIJ
+2OU2/qHrmx5wIqy4JaK7A64CM/Wnx4zOrVhWiv25AsPG3MHiWxydHe4nkNOey/cT
+U3IwZcECgYEA1/llem5k9Most+FvLYjoebfnFCl29oUgavidnwLp7lh4cZ1SY9+i
+TTr84CiNgmtINbf0AbWzEPiYWAlYGsctfVNZSIa240wIrfmy9oDa5wxz9QjF7cDY
+i0txG8LIYlN/vM4T72bwl1zw+cJRf591zCK8y3moVSIRZppYbNlD4HECgYEAwnC9
+r4eeCTdwhwoK33XVkh5kkM/CoRMFpKg04ubttW9D/L2yZg15ZHjRWHti7GyLvVIv
+9aW678cXNdlV30WBcABtmN2d17mwrBbDqAdc2pfbtdFBBGnVIWekpXNEP9mINLly
+qi7NRTG68jHvSn1SKVkQt3zKF+BGnDcWeGM8d6UCgYBjrKAe3vAM4Xm963bKBxNz
+iWJGNdTHdS0+8TqddlTMQVxk4vxxR3a6Oe0W7uBQPn72+8zLNTZNMM3uY3Gb+iyO
+WHcuN64UPLUMxd7IUTO1ylOB1Oi0D3pg3xJ2g3DDoFGlq14b8OA8mxJD0mCWi9tr
+uOR069K6Z5ysQ7NnmOXwoQKBgArUQJxAD1swDUJYGtbrNyPWMX+nMo3KA2xyOc/R
+ULfkJIM1BXSNl48y82XcKVxFh1rZ8vXZbxnfmrlTC2dN9bGJNJFo9luHagGLmwYG
+svMxtfjgWKCoTEh5/z9/tfNgaCeXlH1J8gDCjkji4xLg++x1m2q8tnyx7vzQTJ4T
+2NBJAoGBAJVakHmGKh8NM9Oa4QZe3CT110HQidVTzZNZ5dzMpU5HhS7dAa8YbVp5
+IjrGXt/o0Yuid8T6UKmGFpiAL/gGx/fOVCMG9j2O9qF980082VWp+wRzVydnmC2c
+nk6U1NvEhanPSa3IbNIgFUOc6Of3Jhs44EH3jw7fraITEX14bTqH
+-----END RSA PRIVATE KEY-----
diff --git a/test/keys/client/id_rsa_test.pub b/test/keys/client/id_rsa_test.pub
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_test.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkChwKYwpoFQnwBnkpBuoNlwKwtMWtGb2pO8OJXFLZ576eGAiwG7FiLSGHOOPZLtkuBRg5EjHYI8v/+V3DuyORRScpAfE4z/CRJwancL5Cq18AGEiEPZL4psDcdaovgHI3dxZvfMOZY7Kwa5FsjAQZjJNiuRrIdT57A+obIic/ni4fxS3UiWwNc73BoKIfG4EkVoTftfJyu2mF62S2/wmO9LeR9tILo5lOwXQzs+8KfJSa+K1J+WIdC3MjbqVYYdHTxpl27IF363G9skoPNXy/KtID7NiT25ae4SO7jtEmyUT3KuAhW1Gd25FGzMteWccLJaCbmUrfOXN/rdLjkC/V ganesh@joyful.com
diff --git a/test/keys/client/id_rsa_test2 b/test/keys/client/id_rsa_test2
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_test2
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAwnECqcGljJ68vz/uoFsJxrlSPZOGLCCJD6DCv9NZlKLZERh5
+vRLK9WpdRrgT59gyPRmyQWNugZ3uPSTnM7qi7RlSaCZkt6pvQqfuDnoxe3QaFrZw
+3WoCM8hk3SolMyD/kMQW0Oss1mSJwjVrXRIctXf2Dh2kTEcBMDF+C+P1SIoDkL7x
+jea5XcbX/tt2kBsSAYhtnWiDiSgiraXtRGk0ox3rRdmH4Jd11WFYuTDtmqlTCcVe
+DhzsbT19th81msxSRRwnOj7foRwo1xVu2VrqNNMbYf7ley0pnYXbVmaBQui9X4Wg
+Rw6CZEFpzhJ4yXMsIr1i1cOC3WuJ9sUqucZ/EQIDAQABAoIBABp5Po6UYhDqT/KO
+JtRIcOVQuCTQIDWD3IV3MuzhPtIg9gMA3RpkI6QUbkzBpVwssHJnPEDw48vcD+Ld
+UmlVoPc9Ol4Z1B65otpleOEZvAD+BstO3cEXvOMGBs2h/lyewo0YVa7uRjWOjL8X
+fN24KJaAlczINmV4SW4hXvMJf4z3mGvr0/vDpABht9zHftp6BiWXZYl+Kx2UrWaW
+a7n5g0iTuC9dEINqQVUazA3ty91+YUSxrD7XtwklwTRBSpuZM+//SbZGdnQik2PM
+PSfPLu5ovZ0MwI9LZJPQ3eRLGo89Em4wXHvKLzmb0mX9fvhP/244A4QQVjZobMN3
+rCX2Vw0CgYEA+h/pyX/bWEzAm7xyOuLYrde42R2XfjBdLcjD7j01pvPxc7rgpYRb
+qVEOKXvoT5y9dX7PslwzSqwmUhbmdN6aX1YUvs6yPXBF851yMbY2qW+jn2ZAWJD4
+sQ25p7wV4GDRYJprLgnnUg9nHThuEbz9CoZTegKb2GF7wuXVw6lwxMcCgYEAxwJB
+LTsR11NdC0V/0pYjiB9wLrXvILlly+rTpGoApnDqoWYn6RayJuiQUXITzhFdtrkm
+7tfe2RKEuh/aXfV+iR6ed/QxwHED5Vd8XYbbpXRuzWH0umpr50x6ePESxvk51BVw
+5fg4hF6/2Mwn8map1n6e759CVvJK+laNlL+lFWcCgYBdm7n0FmyxtC/VWQZrMWCk
+VvqwDtoWeOU2cE+bhr7gl6VCiarvZwSi7lndfqjnuqJRKb9zYfw0Mw84Y6emD3U4
+vs+OxW6BfdZAISmOn0H/0W8sBamJO+BG6vsTYlnRmophnAkGtuAinu8ZSXgwHUma
+OcFeBUHsDjeyLi/9RRmWGwKBgH1+/nr3dRjEiThCa4jxBRciPCw4rsOEJp2hSDW2
+YxKSwmNleGWU2mOO5PN3bOXWLbK8r8COgQmClBCLZbk6xsDRfj1G0Nj6a+qEcPjJ
+wllkQzthOmMUGVeS8uixnZW8NKt5mehrz7gpx/F/TPGfrBqHXtLdK4iI4p9bVY0o
+DYKhAoGAWmSgza5IphlKzx0YNdSTzPZ6RbHIk/HWEIAtDlAV60ucTtxOMUdjDs0Z
+KuGXxwT3ahf1LK23PHDfC5wDBzMffSkm5w5AK/+1PdtTaVLRI2g3l6RCm6L15vhO
+AZ+26PUzo9zmIGeHXJl6naTiQUW234/WTt3pyFdQ28k8jhXnu7s=
+-----END RSA PRIVATE KEY-----
diff --git a/test/keys/client/id_rsa_test2.pub b/test/keys/client/id_rsa_test2.pub
new file mode 100644
--- /dev/null
+++ b/test/keys/client/id_rsa_test2.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCcQKpwaWMnry/P+6gWwnGuVI9k4YsIIkPoMK/01mUotkRGHm9Esr1al1GuBPn2DI9GbJBY26Bne49JOczuqLtGVJoJmS3qm9Cp+4OejF7dBoWtnDdagIzyGTdKiUzIP+QxBbQ6yzWZInCNWtdEhy1d/YOHaRMRwEwMX4L4/VIigOQvvGN5rldxtf+23aQGxIBiG2daIOJKCKtpe1EaTSjHetF2Yfgl3XVYVi5MO2aqVMJxV4OHOxtPX22HzWazFJFHCc6Pt+hHCjXFW7ZWuo00xth/uV7LSmdhdtWZoFC6L1fhaBHDoJkQWnOEnjJcywivWLVw4Lda4n2xSq5xn8R ganesh@paxton
diff --git a/test/keys/host b/test/keys/host
new file mode 100644
--- /dev/null
+++ b/test/keys/host
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAszT/56/xQlb08L+WdJv04XAxV3LFqNk73HGCd2MOul6Q5SiD
+LT+s2fisK2PFYodwRD1yId55ouy3xCVNkj1FdYrX4Fa3nddPt8qzhwrKLhmoWVEo
+4e59kjz8q5qhCFOHstYsHtKHNK1vfJiZaYlIuG9/XmBZHTLVmzvwkhn0nXKthMFS
+q5h8MLvjP00/EY438luYxkl6rao5O7JKOFnM/A1/oznxhYpbd/zQndyO7z5AchO9
+ODGNC+hbECkIC8c2V6XbJChfolHTUe6Wz809EdvyzwXCI5XU/Wk3r6P6pVa1A/qu
+J6lg33bic/FbZUxMhVJgxtWUHi/goON5UiPczwIDAQABAoIBAA9/sfIzwUXfh2ea
+6kRDiLZGob3Sa03jG85A8uuoYYm0zAtFqbKsIyLm4t0Nz/BpjkgYmFpdh1+T4YDR
+SRgwvGUiEPGSDmdUS8y1dzlisYGzLmArKMIBglK3e5LL2MmDj+TjqQUxaAgiR9Ya
+zInYGbX5zqY25v4je13lnTTFkeZb859N3XAVbay+82IJej3tFNSV1tR+H6FtRIZ/
+UBASKwAaw8Cbjys0E8gaSPBk7ZzPTD4h7EOAYvTGZgrxirV5HBjusg2AIIvcOY2m
+UUSRuvvJzjff1mxygJeBkNMDnRNYQRqrDXFzNs08poiVZ7u6yPhN4D4o/Xr+4sQn
+ACbTnIECgYEA2p8qnuJtnnvXIpwctzccykg/cjaBPIG3UeYM1TQSzbYdZ0KfZl7D
+OUGxJ+WKeHulXnJY78gVFKpqU0UMP5lqZSyYPA4TwOnIBZNiJ1dBh6nfhrrXd45g
+t65n52sx+b6o03natfKXn/2+Er0GS3K9W0YgnCFyBCIGXAs+mpqovvECgYEA0dix
+xK4egGLYGajuk3FT2xI7bop/+NE3GzIQHQ+9MWUc5/zomakR+V/BOU9zmbcfynK2
+5nxd8KZxzHmm6nS4KWuaw+7HNCFJyPZhIAd+5tbgsKewoGKUdTNyZNY4jGuWX981
+0yYLazoXTwbGMVGneyJXJAjg9YS6JlJbgcXx178CgYA8T+zSKGVVc5TGV4sUgH/Q
+zl2yhJbiumZ4kZ64ssT9O2ChPB/9fecdxKG6//hThMj6ZVFj1S77pIfwsPvQD+Lq
+RoM5Dikk71nfL+nEMK5DXvnrkWAf+4dzJQpFLa172L16mgNcbrCl0rq9MKir59uV
+cqNMb22k9j4K5o8+16v2AQKBgA0LdXGOiWLdwiVGNR6BSv8xUPR8M5xaFRzhrdLA
+qbgqWvOo6ySyN+XSqAZSBBOoKJfDLc+CJ6zQC/70CQZGHzSj9cj8TPHWp+mQN1Vw
+Ydkjvm/83KP7vNLUUeYm0vkXrw9ipsvrb4ZI5C4Lc8KZGtoytkwNKT7Z82ByejHF
+BlWtAoGAZdrfpNdPLYH/t570CtOMuCVAbNop56Yq9f3EmJefU/urFmu9tBLw8T1r
+2EmcPovH20MqjOdPVFqHe4tMN5c6xI4OTp1O4gFpYmzUsMIwVkbTuSMCdc96TdJw
+C3y11k+V3LDZ/DVMzr59LZd48LOQFYHUMFMwsyf7fU5oiuSae+Y=
+-----END RSA PRIVATE KEY-----
diff --git a/test/keys/host.pub b/test/keys/host.pub
new file mode 100644
--- /dev/null
+++ b/test/keys/host.pub
@@ -0,0 +1,1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCzNP/nr/FCVvTwv5Z0m/ThcDFXcsWo2TvccYJ3Yw66XpDlKIMtP6zZ+KwrY8Vih3BEPXIh3nmi7LfEJU2SPUV1itfgVred10+3yrOHCsouGahZUSjh7n2SPPyrmqEIU4ey1iwe0oc0rW98mJlpiUi4b39eYFkdMtWbO/CSGfSdcq2EwVKrmHwwu+M/TT8RjjfyW5jGSXqtqjk7sko4Wcz8DX+jOfGFilt3/NCd3I7vPkByE704MY0L6FsQKQgLxzZXpdskKF+iUdNR7pbPzT0R2/LPBcIjldT9aTevo/qlVrUD+q4nqWDfduJz8VtlTEyFUmDG1ZQeL+Cg43lSI9zP ganesh@paxton
diff --git a/test/test.hs b/test/test.hs
new file mode 100644
--- /dev/null
+++ b/test/test.hs
@@ -0,0 +1,266 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Main where
+
+import Test.Tasty
+    (TestTree, defaultMain, testGroup, withResource
+    )
+import Test.Tasty.HUnit (testCase)
+import Test.Tasty.QuickCheck (testProperty)
+import Test.HUnit (assertBool)
+import Test.QuickCheck
+    (Arbitrary(..), elements, forAll, choose, vectorOf
+    )
+
+import Control.Applicative ((<$>))
+import Control.Concurrent (forkIO, killThread)
+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
+import Control.Exception (bracket, try, catchJust, ErrorCall(..), evaluate)
+import Control.Monad (when)
+import Data.ByteString.Char8 (pack)
+import qualified Data.ByteString.Lazy as LBS
+import Data.List (isSuffixOf)
+import Data.Map (Map)
+import qualified Data.Map as Map
+import Data.Word (Word8)
+import System.Directory (createDirectoryIfMissing, removeFile)
+import System.FilePath ((<.>))
+import System.IO (hPutStr, openTempFile, hClose)
+import System.IO.Unsafe (unsafePerformIO)
+
+import Network.SSH.Client.LibSSH2
+import Network.SSH.Client.LibSSH2.Errors
+import Network.SSH.Client.LibSSH2.Foreign
+
+import qualified SSH
+import SSH.Channel
+import SSH.Crypto hiding (sign, verify)
+import qualified SSH.Crypto as Crypto
+import SSH.Session
+
+import EmbedTree
+
+keysDirectory :: Map String Entry
+keysDirectory = getDirectory $(embedTree "keys")
+
+sshPort :: Num a => a -- used as an Int or a PortNumber
+sshPort = 5032
+
+withOneUserServer :: KeyPair -> PublicKey -> TestTree -> TestTree
+withOneUserServer hostKp acceptedKey test = do
+  withResource
+    (do startedSignal <- newEmptyMVar
+        tid <- forkIO $ SSH.startConfig (config startedSignal)
+        takeMVar startedSignal
+        return tid
+    )
+    killThread
+    (\_ -> test)
+    where
+      config startedSignal =
+        SSH.Config
+          { SSH.cSession = session
+          , SSH.cChannel = channel
+          , SSH.cPort = sshPort
+          , SSH.cReadyAction = putMVar startedSignal ()
+          }
+
+      session =
+        SessionConfig
+          { scAuthMethods = ["publickey", "password"]
+          , scAuthorize = sshAuthorize
+          , scKeyPair = hostKp
+          }
+
+      channel =
+        ChannelConfig
+          { ccRequestHandler = channelRequest
+          }
+
+      sshAuthorize (PublicKey "testuser" k) = return (k == acceptedKey)
+      sshAuthorize _ = return False
+
+      channelRequest wr (Execute "check") = do
+        channelMessage "checked"
+        when wr channelSuccess
+        channelDone
+
+      channelRequest wr cmd = do
+        channelError $ "<" ++ show cmd ++ "> not supported"
+        when wr channelFail
+
+withTextInTempFile :: String -> String -> (FilePath -> IO a) -> IO a
+withTextInTempFile nameTemplate contents action = do
+  let tempFolder = "temp"
+  createDirectoryIfMissing False tempFolder
+  bracket
+    (do
+       (f, h) <- openTempFile tempFolder nameTemplate
+       hPutStr h contents
+       hClose h
+       return f
+    )
+    removeFile
+    action
+
+data AuthResult = OK | Error ErrorCode
+   deriving (Show, Eq)
+
+authWith :: String -> KeyPair -> IO AuthResult
+authWith publicKeyText privateKeyPair = do
+  withTextInTempFile "private" (printKeyPair privateKeyPair) $ \privateKeyFile ->
+    withTextInTempFile "public" publicKeyText $ \publicKeyFile ->
+      withSession "localhost" sshPort $ \session -> do
+        authResult <- try $ publicKeyAuthFile session "testuser" publicKeyFile privateKeyFile ""
+        case authResult of
+            Left e -> return $ Error e
+            Right () -> do
+              channel <- openChannelSession session
+              channelExecute channel "check"
+              checked <- readChannel channel 20
+              when (checked /= pack "checked\r\n") $ fail "incorrect check result"
+              return OK
+
+breakPrivateKey :: KeyPair -> KeyPair
+-- This leaves enough information intact to reconstruct the private key
+-- (e.g the primes), but in practice it seems to be enough to cause an
+-- authentication failure.
+-- Changing the numbers too much can cause segfaults or out of range signatures
+breakPrivateKey kp@RSAKeyPair {} =
+   kp
+   { rprivD = rprivD kp - 2
+   , rprivPrime1 = rprivPrime1 kp - 2
+   , rprivPrime2 = rprivPrime2 kp - 2
+   , rprivExponent1 = rprivExponent1 kp - 2
+   , rprivExponent2 = rprivExponent2 kp - 2
+   , rprivCoefficient = rprivCoefficient kp - 2
+   }
+breakPrivateKey kp@DSAKeyPair {} = kp { dprivX = 1 }
+
+publicKey :: KeyPair -> PublicKey
+publicKey (RSAKeyPair { rprivPub = k }) = k
+publicKey (DSAKeyPair { dprivPub = k }) = k
+
+hostKeyPair :: KeyPair
+hostKeyPair = parseKeyPair . getFile $ getEntry "host" keysDirectory
+
+clientKeysDirectory :: Map String Entry
+clientKeysDirectory = getDirectory $ getEntry "client" keysDirectory
+
+getClientPublicKeyFileText :: String -> String
+getClientPublicKeyFileText keyName = getFile $ getEntry (keyName <.> "pub") clientKeysDirectory
+
+getClientPrivateKeyPair :: String -> KeyPair
+getClientPrivateKeyPair keyName = parseKeyPair . getFile $ getEntry keyName clientKeysDirectory
+
+privateKeyPairFiles :: [String]
+privateKeyPairFiles = filter (not . isSuffixOf "pub") $ Map.keys clientKeysDirectory
+
+singleKeyAuthTests :: TestTree
+singleKeyAuthTests =
+  testGroup "Single key auth tests"
+    [
+      let publicKeyFileText = getClientPublicKeyFileText privateKeyPairFile
+          privateKeyPair = getClientPrivateKeyPair privateKeyPairFile
+      in
+        withOneUserServer hostKeyPair (publicKey privateKeyPair) $
+          testGroup ("Check auth with " ++ privateKeyPairFile)
+          [
+            testCase ("Works") $ do
+              authWith publicKeyFileText privateKeyPair
+                >>= assertBool "should auth with correct private key" . (==OK)
+
+          , testCase ("Fails with broken private key") $ do
+              authWith publicKeyFileText (breakPrivateKey privateKeyPair)
+                >>= assertBool "shouldn't auth with broken private key" . (==Error PUBLICKEY_UNVERIFIED)
+          ]
+
+    | privateKeyPairFile <- privateKeyPairFiles
+
+    ]
+
+wrongKeyAuthTest :: TestTree
+wrongKeyAuthTest =
+  withOneUserServer hostKeyPair (publicKey rightPrivateKeyPair) $
+  testCase "Check auth failure with wrong key" $ do
+      authWith wrongPublicKeyFileText wrongPrivateKeyPair
+        >>= assertBool "shouldn't auth with wrong private key" . (==Error AUTHENTICATION_FAILED)
+  where
+    rightPrivateKeyPair = getClientPrivateKeyPair "id_rsa_test"
+    wrongPrivateKeyPair = getClientPrivateKeyPair "id_rsa_test2"
+
+    wrongPublicKeyFileText = getClientPublicKeyFileText "id_rsa_test2"
+
+instance Arbitrary LBS.ByteString where
+  arbitrary = LBS.pack <$> arbitrary
+
+instance Arbitrary KeyPair where
+  arbitrary = elements $ map getClientPrivateKeyPair privateKeyPairFiles
+
+instance Arbitrary PublicKey where
+  arbitrary = publicKey <$> arbitrary
+
+-- QuickCheck tests end up using unsafePerformIO because sign and verify
+-- are in IO, which in turn is because the DSA operations are in IO,
+-- but hopefully they only have benign side-effects if any
+
+sign :: KeyPair -> LBS.ByteString -> LBS.ByteString
+sign kp message = unsafePerformIO $ Crypto.sign kp message
+
+verify :: PublicKey -> LBS.ByteString -> LBS.ByteString -> Bool
+verify key message sig =
+  unsafePerformIO $
+    catchJust
+      sigErrors
+      (Crypto.verify key message sig >>= evaluate)
+      (\() -> return False)
+
+  where
+    sigErrors (ErrorCall msg)
+      | msg == "signature representative out of range" = Just ()
+    sigErrors _ = Nothing
+
+
+signThenVerifyTest :: TestTree
+signThenVerifyTest = testProperty "signatures from sign work with verify" $
+  \kp message -> verify (publicKey kp) message $ sign kp message
+
+signThenMutatedVerifyTest :: TestTree
+signThenMutatedVerifyTest = testProperty "mutated signatures from sign fail with verify" $
+  \kp message ->
+    let sig = sign kp message
+        actualSignatureLen = fromIntegral $ actualSignatureLength (publicKey kp)
+    in forAll (choose (LBS.length sig - actualSignatureLen, LBS.length sig - 1)) $ \offset ->
+       forAll (choose (1, 255 :: Word8)) $ \mutation ->
+       let mutatedSig =
+             LBS.take offset sig `LBS.append`
+             LBS.pack [LBS.index sig offset + mutation] `LBS.append`
+             LBS.drop (offset+1) sig
+       in not $ verify (publicKey kp) message mutatedSig
+
+randomVerifyTest :: TestTree
+randomVerifyTest = testProperty "random signatures fail with verify" $
+  -- might be sensible to test some other lengths, but the actual code
+  -- just takes the last n bytes anyway, and it's not totally obvious
+  -- what would be a good range of values to test with.
+  \key message -> forAll (vectorOf (actualSignatureLength key) arbitrary) $ \sigBytes ->
+    not $ verify key message (LBS.pack sigBytes)
+
+
+allTests :: TestTree
+allTests =
+  testGroup "Tests"
+  [ testGroup "With server"
+    [ singleKeyAuthTests
+    , wrongKeyAuthTest
+    ]
+  , testGroup "Signatures"
+    [ signThenVerifyTest
+    , signThenMutatedVerifyTest
+    , randomVerifyTest
+    ]
+  ]
+
+
+main :: IO ()
+main = defaultMain allTests
