packages feed

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 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     ]