summaryrefslogtreecommitdiff
path: root/KeyRing.hs
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2014-05-09 23:12:40 -0400
committerjoe <joe@jerkface.net>2014-05-09 23:12:40 -0400
commit073f2a99552a75e27c7fbe3fe8c328d2bf7277c6 (patch)
treea9d3197156cf88228ce2accf3304e89fbb9523f7 /KeyRing.hs
parentae090c58719726fd0027c4c606bc82faf55b1bc9 (diff)
readKeyFile "PEM" now accepts multiple keys and certs.
Diffstat (limited to 'KeyRing.hs')
-rw-r--r--KeyRing.hs151
1 files changed, 114 insertions, 37 deletions
diff --git a/KeyRing.hs b/KeyRing.hs
index fe0f3cd..ad3e2ae 100644
--- a/KeyRing.hs
+++ b/KeyRing.hs
@@ -80,6 +80,10 @@ module KeyRing
80 , accBindings 80 , accBindings
81 , isSubkeySignature 81 , isSubkeySignature
82 , torhash 82 , torhash
83 , ParsedCert(..)
84 , parseCertBlob
85 , packetFromPublicRSAKey
86 , decodeBlob
83 ) where 87 ) where
84 88
85import System.Environment 89import System.Environment
@@ -93,7 +97,7 @@ import Data.OpenPGP
93import Data.Functor 97import Data.Functor
94import Data.Monoid 98import Data.Monoid
95import Data.Tuple ( swap ) 99import Data.Tuple ( swap )
96import Data.Bits ( (.|.) ) 100import Data.Bits ( (.|.), (.&.) )
97import Control.Applicative ( Applicative, pure, liftA2, (<*>) ) 101import Control.Applicative ( Applicative, pure, liftA2, (<*>) )
98import System.Directory ( getHomeDirectory, doesFileExist, createDirectoryIfMissing ) 102import System.Directory ( getHomeDirectory, doesFileExist, createDirectoryIfMissing )
99import Control.Arrow ( first, second ) 103import Control.Arrow ( first, second )
@@ -107,17 +111,22 @@ import Data.ASN1.Types ( toASN1, ASN1Object, fromASN1
107import Data.ASN1.BitArray ( BitArray(..), toBitArray ) 111import Data.ASN1.BitArray ( BitArray(..), toBitArray )
108import Data.ASN1.Encoding ( encodeASN1, encodeASN1', decodeASN1, decodeASN1' ) 112import Data.ASN1.Encoding ( encodeASN1, encodeASN1', decodeASN1, decodeASN1' )
109import Data.ASN1.BinaryEncoding ( DER(..) ) 113import Data.ASN1.BinaryEncoding ( DER(..) )
110import Data.Time.Clock.POSIX ( POSIXTime ) 114import Data.Time.Clock.POSIX ( POSIXTime, utcTimeToPOSIXSeconds )
115import Data.Time.Clock ( UTCTime )
111import Data.Bits ( Bits ) 116import Data.Bits ( Bits )
112import Data.Text.Encoding ( encodeUtf8 ) 117import Data.Text.Encoding ( encodeUtf8 )
113import qualified Data.Map as Map 118import qualified Data.Map as Map
114import qualified Data.ByteString.Lazy as L ( unpack, null, readFile, writeFile 119import qualified Data.ByteString.Lazy as L ( unpack, null, readFile, writeFile
115 , ByteString, toChunks, hGetContents, hPut, concat, fromChunks ) 120 , ByteString, toChunks, hGetContents, hPut, concat, fromChunks, splitAt
116import qualified Data.ByteString as S ( ByteString, unpack, splitAt, concat, cons, spanEnd, hGetContents, readFile) 121 , index )
122import qualified Data.ByteString as S ( ByteString, unpack, splitAt, concat, cons, spanEnd, hGetContents, readFile, breakSubstring, drop, length, null )
117import qualified Crypto.Types.PubKey.ECC as ECC 123import qualified Crypto.Types.PubKey.ECC as ECC
118import qualified Codec.Binary.Base32 as Base32 124import qualified Codec.Binary.Base32 as Base32
119import qualified Codec.Binary.Base64 as Base64 125import qualified Codec.Binary.Base64 as Base64
120import qualified Crypto.Hash.SHA1 as SHA1 126import qualified Crypto.Hash.SHA1 as SHA1
127import qualified Data.X509 as X509
128import qualified Crypto.PubKey.RSA as RSA
129import qualified Codec.Compression.GZip as GZip
121import qualified Data.Text as T ( Text, unpack, pack, 130import qualified Data.Text as T ( Text, unpack, pack,
122 strip, reverse, drop, break, dropAround, length ) 131 strip, reverse, drop, break, dropAround, length )
123import qualified System.Posix.Types as Posix 132import qualified System.Posix.Types as Posix
@@ -1378,7 +1387,74 @@ try x body =
1378 Left e -> return e 1387 Left e -> return e
1379 Right x -> body x 1388 Right x -> body x
1380 1389
1381readKeyFromFile :: Bool -> String -> InputFile -> IO Message 1390
1391data ParsedCert = ParsedCert
1392 { pcertKey :: Packet
1393 , pcertTimestamp :: UTCTime
1394 , pcertBlob :: L.ByteString
1395 }
1396 deriving (Show,Eq)
1397data SecretPEMData = PEMPacket Packet | PEMCertificate ParsedCert
1398
1399spemPacket (PEMPacket p) = Just p
1400spemPacket _ = Nothing
1401
1402spemCert (PEMCertificate p) = Just p
1403spemCert _ = Nothing
1404
1405parseCertBlob comp bs = do
1406 asn1 <- either (const Nothing) Just
1407 $ decodeASN1 DER bs
1408 let asn1' = drop 2 asn1
1409 cert <- either (const Nothing) (Just . fst) (fromASN1 asn1')
1410 let _ = cert :: X509.Certificate
1411 (notBefore,_) = X509.certValidity cert
1412 case X509.certPubKey cert of
1413 X509.PubKeyRSA key -> do
1414 let withoutkey =
1415 let ekey = Char8.toStrict $ encodeASN1 DER (toASN1 key [])
1416 (pre,post) = S.breakSubstring ekey $ Char8.toStrict bs
1417 post' = S.drop (S.length ekey) post
1418 len :: Word16
1419 len = if S.null post then maxBound
1420 else fromIntegral $ S.length pre
1421 in if len < 4096
1422 then encode len <> GZip.compress (Char8.fromChunks [pre,post'])
1423 else bs
1424 return
1425 ParsedCert { pcertKey = packetFromPublicRSAKey notBefore
1426 (MPI $ RSA.public_n key)
1427 (MPI $ RSA.public_e key)
1428 , pcertTimestamp = notBefore
1429 , pcertBlob = if comp then withoutkey
1430 else bs
1431 }
1432 _ -> Nothing
1433
1434packetFromPublicRSAKey notBefore n e =
1435 PublicKeyPacket { version = 4
1436 , timestamp = round $ utcTimeToPOSIXSeconds notBefore
1437 , key_algorithm = RSA
1438 , key = [('n',n),('e',e)]
1439 , is_subkey = True
1440 , v3_days_of_validity = Nothing
1441 }
1442
1443decodeBlob cert =
1444 if 0 /= (bs `L.index` 0) .&. 0x10
1445 then bs
1446 else let (keypos0,bs') = L.splitAt 2 bs
1447 keypos :: Word16
1448 keypos = decode keypos0
1449 ds = GZip.decompress bs'
1450 (prekey,postkey) = L.splitAt (fromIntegral keypos) ds
1451 in prekey <> key <> postkey
1452 where
1453 bs = pcertBlob cert
1454 key = maybe "" (encodeASN1 DER . flip toASN1 []) $ rsaKeyFromPacket $ pcertKey cert
1455
1456
1457readKeyFromFile :: Bool -> String -> InputFile -> IO [SecretPEMData]
1382readKeyFromFile False "PEM" fname = do 1458readKeyFromFile False "PEM" fname = do
1383 -- warn $ fname ++ ": reading ..." 1459 -- warn $ fname ++ ": reading ..."
1384 let ctx = InputFileContext "" "" 1460 let ctx = InputFileContext "" ""
@@ -1386,37 +1462,37 @@ readKeyFromFile False "PEM" fname = do
1386 -- Therefore, we should attempt to preserve it. 1462 -- Therefore, we should attempt to preserve it.
1387 timestamp <- getInputFileTime ctx fname 1463 timestamp <- getInputFileTime ctx fname
1388 input <- readInputFileL ctx fname 1464 input <- readInputFileL ctx fname
1389 let dta = fmap pemBlob $ listToMaybe $ scanAndParse (pemParser $ Just "RSA PRIVATE KEY") $ Char8.lines input 1465 let dta = catMaybes $ scanAndParse pkcs1 $ Char8.lines input
1390 -- Char8.putStrLn $ "dta = " <> dta 1466 pkcs1 = fmap (parseRSAPrivateKey . pemBlob)
1391 let rsa = do 1467 $ pemParser $ Just "RSA PRIVATE KEY"
1392 e <- decodeASN1 DER <$> dta 1468 cert = fmap (fmap (PEMPacket . pcertKey) . parseCertBlob False . pemBlob)
1393 asn1 <- either (const Nothing) Just e 1469 $ pemParser $ Just "CERTIFICATE"
1394 k <- either (const Nothing) (Just . fst) (fromASN1 asn1) 1470 parseRSAPrivateKey dta = do
1395 let _ = k :: RSAPrivateKey 1471 let e = decodeASN1 DER dta
1396 return k 1472 asn1 <- either (const $ mzero) return e
1397 -- putStrLn $ "rsa = "++ show rsa 1473 rsa <- either (const mzero) (return . fst) (fromASN1 asn1)
1398 return . Message $ do 1474 let _ = rsa :: RSAPrivateKey
1399 rsa <- maybeToList rsa 1475 return $ PEMPacket $ SecretKeyPacket
1400 return $ SecretKeyPacket 1476 { version = 4
1401 { version = 4 1477 , timestamp = toEnum (fromEnum timestamp)
1402 , timestamp = toEnum (fromEnum timestamp) 1478 , key_algorithm = RSA
1403 , key_algorithm = RSA 1479 , key = [ -- public fields...
1404 , key = [ -- public fields... 1480 ('n',rsaN rsa)
1405 ('n',rsaN rsa) 1481 ,('e',rsaE rsa)
1406 ,('e',rsaE rsa) 1482 -- secret fields
1407 -- secret fields 1483 ,('d',rsaD rsa)
1408 ,('d',rsaD rsa) 1484 ,('p',rsaQ rsa) -- Note: p & q swapped
1409 ,('p',rsaQ rsa) -- Note: p & q swapped 1485 ,('q',rsaP rsa) -- Note: p & q swapped
1410 ,('q',rsaP rsa) -- Note: p & q swapped 1486 ,('u',rsaCoefficient rsa)
1411 ,('u',rsaCoefficient rsa) 1487 ]
1412 ] 1488 -- , ecc_curve = def
1413 -- , ecc_curve = def 1489 , s2k_useage = 0
1414 , s2k_useage = 0 1490 , s2k = S2K 100 ""
1415 , s2k = S2K 100 "" 1491 , symmetric_algorithm = Unencrypted
1416 , symmetric_algorithm = Unencrypted 1492 , encrypted_data = ""
1417 , encrypted_data = "" 1493 , is_subkey = True
1418 , is_subkey = True 1494 }
1419 } 1495 return dta
1420readKeyFromFile is_public fmt filename = error ("unimplemented key type: "++fmt) 1496readKeyFromFile is_public fmt filename = error ("unimplemented key type: "++fmt)
1421 1497
1422doImport 1498doImport
@@ -1429,7 +1505,8 @@ doImport doDecrypt db (fname,subspec,ms,_) = do
1429 let fetchkey = readKeyFromFile False "PEM" (ArgFile fname) 1505 let fetchkey = readKeyFromFile False "PEM" (ArgFile fname)
1430 flip (maybe $ return CannotImportMasterKey) 1506 flip (maybe $ return CannotImportMasterKey)
1431 subspec $ \tag -> do 1507 subspec $ \tag -> do
1432 Message parsedkey <- fetchkey 1508 ps <- fetchkey
1509 let parsedkey = mapMaybe spemPacket ps
1433 flip (maybe $ return $ KikiSuccess (db,[])) 1510 flip (maybe $ return $ KikiSuccess (db,[]))
1434 (listToMaybe parsedkey) $ \key -> do 1511 (listToMaybe parsedkey) $ \key -> do
1435 let (m0,tailms) = splitAt 1 ms 1512 let (m0,tailms) = splitAt 1 ms