crypto-pubkey-openssh 0.1.0 → 0.2.0
raw patch · 6 files changed
+182/−61 lines, 6 filesdep +asn1-datadep +pemdep ~crypto-pubkey-typesPVP ok
version bump matches the API change (PVP)
Dependencies added: asn1-data, pem
Dependency ranges changed: crypto-pubkey-types
API changes (from Hackage documentation)
- Crypto.PubKey.OpenSsh: decode :: ByteString -> Either String OpenSshPublicKey
- Crypto.PubKey.OpenSsh: encode :: OpenSshPublicKey -> ByteString
+ Crypto.PubKey.OpenSsh: OpenSshPrivateKeyDsa :: PrivateKey -> PublicNumber -> OpenSshPrivateKey
+ Crypto.PubKey.OpenSsh: OpenSshPrivateKeyRsa :: PrivateKey -> OpenSshPrivateKey
+ Crypto.PubKey.OpenSsh: data OpenSshPrivateKey
+ Crypto.PubKey.OpenSsh: decodePrivate :: ByteString -> Either String OpenSshPrivateKey
+ Crypto.PubKey.OpenSsh: decodePublic :: ByteString -> Either String OpenSshPublicKey
+ Crypto.PubKey.OpenSsh: encodePrivate :: OpenSshPrivateKey -> ByteString
+ Crypto.PubKey.OpenSsh: encodePublic :: OpenSshPublicKey -> ByteString
Files
- crypto-pubkey-openssh.cabal +13/−8
- src/Crypto/PubKey/OpenSsh.hs +8/−5
- src/Crypto/PubKey/OpenSsh/Decode.hs +62/−12
- src/Crypto/PubKey/OpenSsh/Encode.hs +56/−12
- src/Crypto/PubKey/OpenSsh/Types.hs +6/−2
- tests/Tests.hs +37/−22
crypto-pubkey-openssh.cabal view
@@ -1,5 +1,5 @@ Name: crypto-pubkey-openssh-Version: 0.1.0+Version: 0.2.0 Synopsis: OpenSSH public keys parser Description: OpenSSH public keys parser License: MIT@@ -7,6 +7,7 @@ Copyright: Fedor Gogolev <knsd@knsd.net> Author: Fedor Gogolev <knsd@knsd.net> Maxim Mitroshin <rocco66max@gmail.com>+ Thomas DuBuisson <thomas.dubuisson@gmail.com> Maintainer: Fedor Gogolev <knsd@knsd.net> Homepage: https://github.com/knsd/crypto-pubkey-openssh Bug-reports: https://github.com/knsd/crypto-pubkey-openssh/issues@@ -24,12 +25,14 @@ Hs-source-dirs: src Ghc-options: -Wall -fno-warn-orphans Default-language: Haskell2010- Build-depends: base == 4.6.* || == 4.5.*- , bytestring == 0.10.* || == 0.9.*- , base64-bytestring == 1.0.*- , cereal == 0.3.*- , attoparsec == 0.10.*- , crypto-pubkey-types == 0.2.*+ Build-depends: base == 4.6.* || == 4.5.*+ , bytestring == 0.10.* || == 0.9.*+ , base64-bytestring == 1.0.*+ , cereal == 0.3.*+ , attoparsec == 0.10.*+ , crypto-pubkey-types == 0.3.*+ , pem == 0.1.*+ , asn1-data == 0.7.* Exposed-modules: Crypto.PubKey.OpenSsh Other-modules: Crypto.PubKey.OpenSsh.Types@@ -50,7 +53,9 @@ , base64-bytestring == 1.0.* , cereal == 0.3.* , attoparsec == 0.10.*- , crypto-pubkey-types == 0.2.*+ , crypto-pubkey-types == 0.3.*+ , pem == 0.1.*+ , asn1-data == 0.7.* , test-framework == 0.8.* , test-framework-quickcheck2 == 0.3.*
src/Crypto/PubKey/OpenSsh.hs view
@@ -1,9 +1,12 @@ module Crypto.PubKey.OpenSsh ( OpenSshPublicKey(..)- , encode- , decode+ , OpenSshPrivateKey(..)+ , encodePublic+ , decodePublic+ , encodePrivate+ , decodePrivate ) where -import Crypto.PubKey.OpenSsh.Types (OpenSshPublicKey(..))-import Crypto.PubKey.OpenSsh.Encode (encode)-import Crypto.PubKey.OpenSsh.Decode (decode)+import Crypto.PubKey.OpenSsh.Types (OpenSshPublicKey(..), OpenSshPrivateKey(..))+import Crypto.PubKey.OpenSsh.Encode (encodePublic, encodePrivate)+import Crypto.PubKey.OpenSsh.Decode (decodePublic, decodePrivate)
src/Crypto/PubKey/OpenSsh/Decode.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, RecordWildCards #-} module Crypto.PubKey.OpenSsh.Decode where @@ -11,20 +11,24 @@ import Data.Attoparsec.ByteString.Char8 (Parser, parseOnly, take, space, isSpace, takeTill)+import Data.PEM (PEM(..), pemParseBS)+import Data.ASN1.Encoding (decodeASN1')+import Data.ASN1.Stream (ASN1(IntVal, Start, End), ASN1ConstructionType(Sequence))+import Data.ASN1.BinaryEncoding (DER(..)) import Data.Serialize (Get, getBytes, runGet, getWord32be, getWord8) import qualified Data.ByteString.Base64 as Base64 import qualified Crypto.Types.PubKey.DSA as DSA import qualified Crypto.Types.PubKey.RSA as RSA -import Crypto.PubKey.OpenSsh.Types (OpenSshPublicKeyType(..),- OpenSshPublicKey(..))+import Crypto.PubKey.OpenSsh.Types (OpenSshKeyType(..), OpenSshPublicKey(..),+ OpenSshPrivateKey(..)) typeSize :: Int typeSize = 7 -readType :: Monad m => ByteString -> m OpenSshPublicKeyType-readType "ssh-rsa" = return OpenSshPublicKeyTypeRsa-readType "ssh-dss" = return OpenSshPublicKeyTypeDsa+readType :: Monad m => ByteString -> m OpenSshKeyType+readType "ssh-rsa" = return OpenSshKeyTypeRsa+readType "ssh-dss" = return OpenSshKeyTypeDsa readType _ = fail "Invalid key type" calculateSize :: Integer -> Int@@ -44,10 +48,10 @@ getOpenSshPublicKey = do size <- fmap fromIntegral $ getWord32be getBytes size >>= readType >>= \typ -> case typ of- OpenSshPublicKeyTypeRsa -> parseRsa- OpenSshPublicKeyTypeDsa -> parseDsa+ OpenSshKeyTypeRsa -> parseRsa+ OpenSshKeyTypeDsa -> parseDsa where- parseRsa = do + parseRsa = do e <- getInteger n <- getInteger return $ OpenSshPublicKeyRsa $ RSA.PublicKey (calculateSize n) n e@@ -56,7 +60,7 @@ q <- getInteger g <- getInteger y <- getInteger- return $ OpenSshPublicKeyDsa $ DSA.PublicKey (p, g, q) y+ return $ OpenSshPublicKeyDsa $ DSA.PublicKey (DSA.Params p g q) y openSshPublicKeyParser :: Parser OpenSshPublicKey openSshPublicKeyParser = do@@ -70,5 +74,51 @@ commentParser = void space *> (takeTill $ \c -> isSpace c || isControl c) <|> return "" -decode :: ByteString -> Either String OpenSshPublicKey-decode = parseOnly openSshPublicKeyParser+decodePublic :: ByteString -> Either String OpenSshPublicKey+decodePublic = parseOnly openSshPublicKeyParser++decodePrivate :: ByteString -> Either String OpenSshPrivateKey+decodePrivate bs = pemParseBS bs >>= \pems -> case pems of+ [] -> Left "Private key not found"+ (_:_:_) -> Left "Too many private keys"+ [p@(PEM { .. })] -> do+ case pemName of+ "RSA PRIVATE KEY" -> parseRSA p+ "DSA PRIVATE KEY" -> parseDSA p+ _ -> Left "Unknown private key type"+ where+ parseDSA :: PEM -> Either String OpenSshPrivateKey+ parseDSA (PEM {..}) =+ case decodeASN1' DER pemContent of+ Left er -> Left (show er)+ Right [ Start Sequence+ , IntVal _version+ , IntVal params_p+ , IntVal params_q+ , IntVal params_g+ , IntVal public_y+ , IntVal private_x+ , End Sequence+ ] -> let private_params = DSA.Params {..}+ in Right (OpenSshPrivateKeyDsa ( DSA.PrivateKey {..} )public_y)+ Right _ -> Left "Invalid ASN1 stream found in PEM."++ parseRSA :: PEM -> Either String OpenSshPrivateKey+ parseRSA (PEM {..}) =+ case decodeASN1' DER pemContent of+ Left er -> Left (show er)+ Right [ Start Sequence+ , IntVal _version+ , IntVal public_n+ , IntVal public_e+ , IntVal private_d+ , IntVal private_p+ , IntVal private_q+ , IntVal private_dP+ , IntVal private_dQ+ , IntVal private_qinv+ , End Sequence+ ] -> let public_size = calculateSize public_n+ private_pub = RSA.PublicKey { .. }+ in Right (OpenSshPrivateKeyRsa (RSA.PrivateKey {..}))+ Right _ -> Left "Invalid ASN1 stream found in PEM."
src/Crypto/PubKey/OpenSsh/Encode.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, RecordWildCards #-} module Crypto.PubKey.OpenSsh.Encode where @@ -10,12 +10,16 @@ import qualified Data.ByteString as BS import Data.Serialize (Put, Putter, runPut, putByteString, putWord32be, put)+import Data.ASN1.Encoding (encodeASN1')+import Data.ASN1.Stream (ASN1(IntVal, Start, End), ASN1ConstructionType(Sequence))+import Data.ASN1.BinaryEncoding (DER(..))+import Data.PEM (PEM(..), pemWriteBS) import qualified Crypto.Types.PubKey.DSA as DSA import qualified Crypto.Types.PubKey.RSA as RSA import qualified Data.ByteString.Base64 as Base64 -import Crypto.PubKey.OpenSsh.Types (OpenSshPublicKeyType(..),- OpenSshPublicKey(..))+import Crypto.PubKey.OpenSsh.Types (OpenSshKeyType(..), OpenSshPublicKey(..),+ OpenSshPrivateKey(..)) fixZeroByte :: [Word8] -> [Word8] fixZeroByte bs = if testBit (head bs) msb then 0:bs else bs@@ -31,9 +35,9 @@ getResults :: (Integer, Integer) -> (Word8, Integer) getResults (i, w) = (fromIntegral w, i) -keyTypePutter :: Putter OpenSshPublicKeyType-keyTypePutter OpenSshPublicKeyTypeRsa = putByteString "ssh-rsa"-keyTypePutter OpenSshPublicKeyTypeDsa = putByteString "ssh-dss"+keyTypePutter :: Putter OpenSshKeyType+keyTypePutter OpenSshKeyTypeRsa = putByteString "ssh-rsa"+keyTypePutter OpenSshKeyTypeDsa = putByteString "ssh-dss" mpint :: Integer -> ByteString mpint i = runPut $ do@@ -42,7 +46,7 @@ where binary = fixZeroByte $ expandInteger i -commonPublicKeyPutter :: OpenSshPublicKeyType+commonPublicKeyPutter :: OpenSshKeyType -> ByteString -> ByteString -> Put@@ -59,22 +63,62 @@ putWord32be $ fromIntegral $ BS.length $ binaryType putByteString binaryType +commonPrivateKeyPutter :: OpenSshKeyType+ -> ByteString+ -> Put+commonPrivateKeyPutter OpenSshKeyTypeRsa body = do+ putByteString $ pemWriteBS $ PEM "RSA PRIVATE KEY" [] body+commonPrivateKeyPutter OpenSshKeyTypeDsa body = do+ putByteString $ pemWriteBS $ PEM "DSA PRIVATE KEY" [] body+ openSshPublicKeyPutter :: Putter OpenSshPublicKey openSshPublicKeyPutter (OpenSshPublicKeyRsa (RSA.PublicKey _ public_n public_e) comment) =- commonPublicKeyPutter OpenSshPublicKeyTypeRsa comment $ BS.concat+ commonPublicKeyPutter OpenSshKeyTypeRsa comment $ BS.concat [ mpint public_e , mpint public_n ] openSshPublicKeyPutter (OpenSshPublicKeyDsa- (DSA.PublicKey (public_p, public_g, public_q) public_y)+ (DSA.PublicKey (DSA.Params public_p public_g public_q) public_y) comment) =- commonPublicKeyPutter OpenSshPublicKeyTypeDsa comment $ BS.concat+ commonPublicKeyPutter OpenSshKeyTypeDsa comment $ BS.concat [ mpint public_p , mpint public_q , mpint public_g , mpint public_y ] -encode :: OpenSshPublicKey -> ByteString-encode = runPut . openSshPublicKeyPutter+openSshPrivateKeyPutter :: Putter OpenSshPrivateKey+openSshPrivateKeyPutter (OpenSshPrivateKeyRsa (RSA.PrivateKey {..})) =+ let RSA.PublicKey{..} = private_pub+ in commonPrivateKeyPutter OpenSshKeyTypeRsa $ encodeASN1' DER+ [ Start Sequence+ , IntVal 0 -- version+ , IntVal public_n+ , IntVal public_e+ , IntVal private_d+ , IntVal private_p+ , IntVal private_q+ , IntVal private_dP+ , IntVal private_dQ+ , IntVal private_qinv+ , End Sequence+ ]+openSshPrivateKeyPutter (OpenSshPrivateKeyDsa (DSA.PrivateKey {..}) public_y) =+ let DSA.Params{..} = private_params+ in commonPrivateKeyPutter OpenSshKeyTypeDsa $ encodeASN1' DER+ [ Start Sequence+ , IntVal 0 -- version+ , IntVal params_p+ , IntVal params_q+ , IntVal params_g+ , IntVal public_y+ , IntVal private_x+ , End Sequence+ ]++encodePublic :: OpenSshPublicKey -> ByteString+encodePublic = runPut . openSshPublicKeyPutter++encodePrivate :: OpenSshPrivateKey -> ByteString+encodePrivate k = runPut $ openSshPrivateKeyPutter k
src/Crypto/PubKey/OpenSsh/Types.hs view
@@ -5,10 +5,14 @@ import qualified Crypto.Types.PubKey.DSA as DSA import qualified Crypto.Types.PubKey.RSA as RSA +data OpenSshPrivateKey = OpenSshPrivateKeyRsa RSA.PrivateKey+ | OpenSshPrivateKeyDsa DSA.PrivateKey DSA.PublicNumber+ deriving (Eq, Show)+ data OpenSshPublicKey = OpenSshPublicKeyRsa RSA.PublicKey ByteString | OpenSshPublicKeyDsa DSA.PublicKey ByteString deriving (Eq, Show) -data OpenSshPublicKeyType = OpenSshPublicKeyTypeRsa- | OpenSshPublicKeyTypeDsa+data OpenSshKeyType = OpenSshKeyTypeRsa+ | OpenSshKeyTypeDsa deriving (Eq, Show)
tests/Tests.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE TypeSynonymInstances #-} module Main where @@ -6,55 +7,69 @@ import System.FilePath.Posix ((</>), (<.>)) import System.Process (runCommand, waitForProcess) import System.IO.Temp (withSystemTempDirectory)-import qualified Data.ByteString as SB+import qualified Data.ByteString.Char8 as SB import Test.Framework (Test, defaultMain, testGroup) import Test.Framework.Providers.QuickCheck2 (testProperty)-import Test.QuickCheck (Property, Arbitrary(..), elements)+import Test.QuickCheck (Property, Arbitrary(..), elements, suchThat) import Test.QuickCheck.Monadic (monadicIO, run, assert) -import Crypto.PubKey.OpenSsh.Types (OpenSshPublicKeyType(..),- OpenSshPublicKey(..))-import Crypto.PubKey.OpenSsh.Encode (encode)-import Crypto.PubKey.OpenSsh.Decode (decode)+import Crypto.PubKey.OpenSsh.Types (OpenSshKeyType(..), OpenSshPublicKey(..),+ OpenSshPrivateKey(..))+import Crypto.PubKey.OpenSsh (encodePublic, decodePublic,+ encodePrivate, decodePrivate) type StrictByteString = SB.ByteString+type PrivateKey = StrictByteString+type PublicKey = StrictByteString -instance Arbitrary OpenSshPublicKeyType where- arbitrary = elements [OpenSshPublicKeyTypeRsa, OpenSshPublicKeyTypeDsa]+instance Arbitrary OpenSshKeyType where+ arbitrary = elements [OpenSshKeyTypeRsa, OpenSshKeyTypeDsa] -openSshPubKey :: OpenSshPublicKeyType -> IO StrictByteString-openSshPubKey t = withSystemTempDirectory base $ \dir -> do+openSshKeys :: OpenSshKeyType -> IO (PrivateKey, PublicKey)+openSshKeys t = withSystemTempDirectory base $ \dir -> do let path = dir </> typ let run = "ssh-keygen -t " <> typ <> " -N \"\" -f " <> path waitForProcess =<< runCommand run- fmap SB.init $ SB.readFile $ path <.> "pub"+ priv <- SB.readFile $ path+ pub <- fmap SB.init $ SB.readFile $ path <.> "pub"+ return (priv, pub) where base = "crypto-pubkey-openssh-tests" typ = case t of- OpenSshPublicKeyTypeRsa -> "rsa"- OpenSshPublicKeyTypeDsa -> "dsa"+ OpenSshKeyTypeRsa -> "rsa"+ OpenSshKeyTypeDsa -> "dsa" -testWithOpenSsh :: OpenSshPublicKeyType -> Property+testWithOpenSsh :: OpenSshKeyType -> Property testWithOpenSsh t = monadicIO $ do- pub <- run $ openSshPubKey t- assert $ check (decode pub) pub+ (priv, pub) <- run $ openSshKeys t+ assert $ checkPublic (decodePublic pub) pub+ assert $ checkPrivate (decodePrivate priv) priv where- check = case t of- OpenSshPublicKeyTypeRsa -> \r b -> case r of+ checkPublic = case t of+ OpenSshKeyTypeRsa -> \r b -> case r of Right k@(OpenSshPublicKeyRsa _ _) ->- encode k == b+ encodePublic k == b _ -> False- OpenSshPublicKeyTypeDsa -> \r b -> case r of+ OpenSshKeyTypeDsa -> \r b -> case r of Right k@(OpenSshPublicKeyDsa _ _) ->- encode k == b+ encodePublic k == b _ -> False+ checkPrivate = case t of+ OpenSshKeyTypeRsa -> \r b -> case r of+ Right k@(OpenSshPrivateKeyRsa _) ->+ encodePrivate k == b+ _ -> False+ OpenSshKeyTypeDsa -> \r b -> case r of+ Right k@(OpenSshPrivateKeyDsa _ _) ->+ encodePrivate k == b+ _ -> False main :: IO () main = defaultMain [ #ifdef OPENSSH- testGroup "ssh-keygen" [ testProperty "decode" $ testWithOpenSsh+ testGroup "ssh-keygen" [ testProperty "decode/encode" $ testWithOpenSsh ] #endif ]