h-gpgme 0.6.2.0 → 0.6.3.0
raw patch · 11 files changed
+303/−71 lines, 11 filesdep ~HUnitdep ~QuickCheckdep ~base
Dependency ranges changed: HUnit, QuickCheck, base, bindings-gpgme, bytestring, data-default, directory, email-validate, exceptions, filepath, tasty, tasty-hunit, tasty-quickcheck, temporary, time, transformers, unix
Files
- CHANGELOG.markdown +22/−0
- h-gpgme.cabal +32/−30
- src/Crypto/Gpgme.hs +6/−0
- src/Crypto/Gpgme/Internal.hs +11/−0
- src/Crypto/Gpgme/Key.hs +96/−8
- src/Crypto/Gpgme/Types.hs +14/−3
- test/CryptoTest.hs +2/−2
- test/KeyGenTest.hs +7/−6
- test/KeyTest.hs +108/−22
- test/Main.hs +2/−0
- test/TestUtil.hs +3/−0
CHANGELOG.markdown view
@@ -1,5 +1,27 @@ # Changelog +## 0.6.3.0++### New Features++- feat: add importKeyFromBytes function: https://github.com/rethab/h-gpgme/pull/66+- feat: add exportKey, exportSecretKey and exportKeys functions: https://github.com/rethab/h-gpgme/pull/70+- feat: support gpgme 2.x by allowing bindings-gpgme 0.2: https://github.com/rethab/h-gpgme/pull/69++### Bug fixes++- fix: release the gpgme data buffer after import and export operations: https://github.com/rethab/h-gpgme/pull/70+- fix: importKeyFromFile and importKeyFromBytes reported success instead of the actual error when the import operation failed: https://github.com/rethab/h-gpgme/pull/70+- docs: fix reversed doc comments on IncludeSecret: https://github.com/rethab/h-gpgme/pull/68+- fix(test): skip gpg-agent sockets when copying the key fixture, which broke removeAliceKey wherever GnuPG puts its sockets in the homedir: https://github.com/rethab/h-gpgme/pull/69++### Maintenance++- chore(ci): modernize CI with a real multi-GHC matrix, latest actions and hardened permissions: https://github.com/rethab/h-gpgme/pull/67+- chore(ci): release to Hackage from a version tag: https://github.com/rethab/h-gpgme/pull/69+- chore(ci): test against gpgme 1.x and 2.x, both built from source: https://github.com/rethab/h-gpgme/pull/69+- chore: give every dependency a PVP upper bound: https://github.com/rethab/h-gpgme/pull/69+ ## 0.6.2.0 ### New Features
h-gpgme.cabal view
@@ -1,5 +1,5 @@ Name: h-gpgme-Version: 0.6.2.0+Version: 0.6.3.0 Description: High Level Binding for GnuPG Made Easy (gpgme): A Haskell API for the gpgme C library. Synopsis: High Level Binding for GnuPG Made Easy (gpgme) License: MIT@@ -9,9 +9,12 @@ Copyright: (c) Reto 2022 Homepage: https://github.com/rethab/h-gpgme Bug-reports: https://github.com/rethab/h-gpgme/issues-Tested-With: GHC==8.8- , GHC==9.0- , GHC==9.2+Tested-With: GHC==8.10.7+ , GHC==9.2.8+ , GHC==9.4.8+ , GHC==9.6.6+ , GHC==9.8.4+ , GHC==9.10.3 Category: Cryptography Build-Type: Simple Cabal-Version: >=1.10@@ -36,15 +39,14 @@ , Crypto.Gpgme.Crypto , Crypto.Gpgme.Internal , Crypto.Gpgme.Types- build-depends: base == 4.*- , bindings-gpgme >= 0.1.8 && <0.2- , bytestring >= 0.9- , transformers >= 0.4.1 && <0.6- , time >= 1.4 && <2.0- , unix >= 2.5- , email-validate- , time- , data-default+ build-depends: base >= 4.14 && <5+ , bindings-gpgme >= 0.1.8 && <0.3+ , bytestring >= 0.10 && <0.13+ , transformers >= 0.4.1 && <0.7+ , time >= 1.4 && <2+ , unix >= 2.5 && <2.9+ , email-validate >= 2.0 && <2.4+ , data-default >= 0.5 && <0.9 default-language: Haskell2010 test-suite tests@@ -53,24 +55,24 @@ ghc-options: -Wall hs-source-dirs: src, test main-is: Main.hs- build-depends: base == 4.*- , bindings-gpgme >= 0.1.6 && <0.2- , bytestring >= 0.9- , transformers >= 0.4.1 && <0.6- , time >= 1.4 && <2.0- , unix >= 2.5- , directory- , filepath- , email-validate- , data-default- , temporary- , exceptions+ build-depends: base >= 4.14 && <5+ , bindings-gpgme >= 0.1.8 && <0.3+ , bytestring >= 0.10 && <0.13+ , transformers >= 0.4.1 && <0.7+ , time >= 1.4 && <2+ , unix >= 2.5 && <2.9+ , email-validate >= 2.0 && <2.4+ , data-default >= 0.5 && <0.9+ , directory >= 1.2 && <2+ , filepath >= 1.3 && <2+ , temporary >= 1.2 && <2+ , exceptions >= 0.8 && <0.11 - , HUnit- , tasty- , tasty-quickcheck- , tasty-hunit- , QuickCheck + , HUnit >= 1.3 && <2+ , tasty >= 1.0 && <2+ , tasty-quickcheck >= 0.9 && <0.12+ , tasty-hunit >= 0.10 && <0.11+ , QuickCheck >= 2.10 && <3 other-modules: Crypto.Gpgme , Crypto.Gpgme.Crypto , Crypto.Gpgme.Ctx
src/Crypto/Gpgme.hs view
@@ -50,12 +50,18 @@ -- * Keys , Key , importKeyFromFile+ , importKeyFromBytes , getKey , listKeys , removeKey , RemoveKeyFlags(..) , searchKeys+ -- * Exporting keys+ , exportKey+ , exportSecretKey+ , exportKeys+ , ExportMode(..) -- * Information about keys , Validity (..) , PubKeyAlgo (..)
src/Crypto/Gpgme/Internal.hs view
@@ -94,6 +94,17 @@ fromKeyListingMode KeyListingValidate = c'GPGME_KEYLIST_MODE_VALIDATE +-- The GPGME_EXPORT_MODE_* values are replicated from gpgme.h:+-- bindings-gpgme 0.1 does not bind these constants and 0.2 only+-- binds the ones known to the gpgme version it was built against.+fromExportMode :: ExportMode -> CUInt+fromExportMode ExportMinimal = 4 -- GPGME_EXPORT_MODE_MINIMAL+fromExportMode ExportSecret = 16 -- GPGME_EXPORT_MODE_SECRET+fromExportMode ExportRaw = 32 -- GPGME_EXPORT_MODE_RAW+fromExportMode ExportPKCS12 = 64 -- GPGME_EXPORT_MODE_PKCS12+fromExportMode ExportSSH = 256 -- GPGME_EXPORT_MODE_SSH+fromExportMode ExportSecretSubkey = 512 -- GPGME_EXPORT_MODE_SECRET_SUBKEY+ fromProtocol :: (Num a) => Protocol -> a fromProtocol CMS = c'GPGME_PROTOCOL_CMS fromProtocol GPGCONF = c'GPGME_PROTOCOL_GPGCONF
src/Crypto/Gpgme/Key.hs view
@@ -1,9 +1,15 @@ module Crypto.Gpgme.Key ( getKey , importKeyFromFile+ , importKeyFromBytes , listKeys , removeKey , searchKeys+ -- * Exporting keys+ , exportKey+ , exportSecretKey+ , exportKeys+ , ExportMode (..) -- * Information about keys , Validity (..) , PubKeyAlgo (..)@@ -18,6 +24,7 @@ ) where import Bindings.Gpgme+import Control.Monad (when) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BSC8 import Data.Time.Clock@@ -31,13 +38,13 @@ -- | Returns a list of all known 'Key's from the @context@. listKeys :: Ctx -- ^ context to operate in- -> IncludeSecret -- ^ whether to include the secrets+ -> IncludeSecret -- ^ whether to restrict to secret keys -> IO [Key] listKeys ctx secret = listKeys' ctx secret nullPtr -- | Returns a list of known 'Key's from the @context@ that match a given pattern. searchKeys :: Ctx -- ^ context to operate in- -> IncludeSecret -- ^ whether to include the secrets+ -> IncludeSecret -- ^ whether to restrict to secret keys -> String -- ^ The pattern to look for; It is typically -- matched against the user ids of a key. -> IO [Key]@@ -45,7 +52,7 @@ -- | Internal helper function used by both `listKeys` and `searchKeys`. listKeys' :: Ctx -- ^ context to operate in- -> IncludeSecret -- ^ whether to include the secrets+ -> IncludeSecret -- ^ whether to restrict to secret keys -> CString -- ^ The pattern to look for; It is typically -- matched against the user ids of a key. -> IO [Key]@@ -68,7 +75,7 @@ -- Returns 'Nothing' if no 'Key' with this 'Fpr' exists. getKey :: Ctx -- ^ context to operate in -> Fpr -- ^ fingerprint- -> IncludeSecret -- ^ whether to include secrets when searching for the key+ -> IncludeSecret -- ^ whether to look for a secret key -> IO (Maybe Key) getKey Ctx {_ctx=ctxPtr} fpr secret = do key <- allocKey@@ -85,11 +92,38 @@ importKeyFromFile :: Ctx -- ^ context to operate in -> FilePath -- ^ file path to read from -> IO (Maybe GpgmeError)-importKeyFromFile Ctx {_ctx=ctxPtr} fp = do- dataPtr <- newDataBuffer- ret <-+importKeyFromFile ctx fp =+ importData ctx $ \dataPtr -> BS.useAsCString (BSC8.pack fp) $ \cFp -> c'gpgme_data_new_from_file dataPtr cFp 1++-- | Import a key from a 'BS.ByteString', this happens in two steps: populate a+-- @gpgme_data_t@ with the contents of the buffer, import the @gpgme_data_t@+importKeyFromBytes :: Ctx -- ^ context to operate in+ -> BS.ByteString -- ^ buffer to read the key from+ -> IO (Maybe GpgmeError)+importKeyFromBytes ctx key =+ importData ctx $ \dataPtr ->+ BS.useAsCString key $ \cKey -> do+ -- gpgme must copy the buffer: the import in 'importData' runs after+ -- useAsCString returns, at which point cKey has been freed+ let copyData = 1+ let keylen = fromIntegral (BS.length key)+ c'gpgme_data_new_from_mem dataPtr cKey keylen copyData++-- | Populate a fresh @gpgme_data_t@ using the given action and import it.+-- The action is responsible for filling the buffer and returns the result+-- of doing so.+importData :: Ctx -- ^ context to operate in+ -> (Ptr C'gpgme_data_t -> IO C'gpgme_error_t) -- ^ populate the buffer+ -> IO (Maybe GpgmeError)+importData Ctx {_ctx=ctxPtr} populate = do+ -- the populate action allocates the data object itself, so it only+ -- needs an empty cell; it is initialized to NULL because gpgme+ -- leaves it untouched if populating fails+ dataPtr <- malloc+ poke dataPtr nullData+ ret <- populate dataPtr mGpgErr <- case ret of x | x == noError -> do@@ -99,10 +133,64 @@ c'gpgme_op_import ctx dat pure $ if retIn == noError then Nothing- else Just $ GpgmeError ret+ else Just $ GpgmeError retIn err -> pure $ Just $ GpgmeError err+ dat <- peek dataPtr+ when (dat /= nullData) $ c'gpgme_data_release dat free dataPtr pure mGpgErr+ where+ -- gpgme_data_t is bound as an integral type, so NULL is 0+ nullData = 0 :: C'gpgme_data_t++-- | Export the public key with the given @fingerprint@ from the+-- @context@. Returns an empty 'BS.ByteString' if no key with+-- this 'Fpr' exists.+exportKey :: Ctx -- ^ context to operate in+ -> Fpr -- ^ fingerprint of the key to export+ -> IO (Either GpgmeError BS.ByteString)+exportKey ctx fpr = exportKeys ctx [] [fpr]++-- | Export the secret key with the given @fingerprint@ from the+-- @context@.+--+-- Exporting a protected secret key requires its passphrase, which+-- may be supplied through @setPassphraseCallback@.+exportSecretKey :: Ctx -- ^ context to operate in+ -> Fpr -- ^ fingerprint of the key to export+ -> IO (Either GpgmeError BS.ByteString)+exportSecretKey ctx fpr = exportKeys ctx [ExportSecret] [fpr]++-- | Export all keys matching the given @fingerprints@ from the+-- @context@, or all keys if no fingerprint is given.+--+-- The keys are returned in armored format if armor has been+-- enabled on the @context@ (see @setArmor@) and in binary+-- format otherwise.+exportKeys :: Ctx -- ^ context to operate in+ -> [ExportMode] -- ^ modes for the export+ -> [Fpr] -- ^ fingerprints of the keys to export,+ -- or empty to export all keys+ -> IO (Either GpgmeError BS.ByteString)+exportKeys Ctx {_ctx=ctxPtr} modes fprs = do+ dataPtr <- newDataBuffer+ dat <- peek dataPtr+ ret <- withMany BS.useAsCString fprs $ \cFprs ->+ withArray0 nullPtr cFprs $ \pats -> do+ ctx <- peek ctxPtr+ c'gpgme_op_export_ext ctx pats cMode dat+ result <- if ret == noError+ then do+ -- collectResult is lazy: force the copy while the buffer+ -- it reads from is still alive+ let key = collectResult dat+ key `seq` return (Right key)+ else return (Left (GpgmeError ret))+ c'gpgme_data_release dat+ free dataPtr+ return result+ where+ cMode = foldl (\memo -> (memo .|.) . fromExportMode) 0 modes -- | Removes the 'Key' from @context@ removeKey :: Ctx -- ^ context to operate in
src/Crypto/Gpgme/Types.hs view
@@ -34,6 +34,17 @@ | KeyListingSigNotations | KeyListingValidate +-- | Modes for exporting keys. Multiple modes can be combined,+-- e.g. @[ExportSecret, ExportPKCS12]@.+data ExportMode+ = ExportMinimal -- ^ Strip all signatures except the most recent self-signatures+ | ExportSecret -- ^ Export the secret keys instead of the public keys+ | ExportRaw -- ^ Only with 'ExportSecret' and 'CMS': export in raw format+ | ExportPKCS12 -- ^ Only with 'ExportSecret' and 'CMS': export in PKCS#12 format+ | ExportSSH -- ^ Export the public key in SSH format+ | ExportSecretSubkey -- ^ Export the secret subkeys instead of the public keys+ deriving (Show, Eq, Ord)+ -- | Modes for signing with GPG data SignMode = Normal | Detach | Clear deriving Show @@ -103,10 +114,10 @@ withKeyPtr :: Key -> (Ptr C'gpgme_key_t -> IO a) -> IO a withKeyPtr (Key fPtr) = withForeignPtr fPtr --- | Whether to include secret keys when searching+-- | Whether to restrict an operation to secret keys data IncludeSecret =- WithSecret -- ^ do not include secret keys- | NoSecret -- ^ include secret keys+ WithSecret -- ^ only consider secret keys+ | NoSecret -- ^ do not consider secret keys deriving (Show, Eq, Ord) data Flag =
test/CryptoTest.hs view
@@ -176,14 +176,14 @@ resVerify <- withCtx "test/bob/" "C" OpenPGP $ \ctx -> do key <- getKey ctx bobPubFpr NoSecret resSign <- sign ctx [fromJust key] Clear "Clear text message from bob specifying signing key"- verifyPlain ctx (fromRight resSign) ""+ verify ctx (fromRight resSign) assertBool "Could not verify bob's signature was correct" $ isVerifyValid resVerify bobClearSignAndVerifyDefaultKeyPrompt :: Assertion bobClearSignAndVerifyDefaultKeyPrompt = do resVerify <- withCtx "test/bob/" "C" OpenPGP $ \ctx -> do resSign <- sign ctx [] Clear "Clear text message from bob with default key"- verifyPlain ctx (fromRight resSign) ""+ verify ctx (fromRight resSign) assertBool "Could not verify bob's signature was correct" $ isVerifyValid resVerify bobNormalSignAndVerifySpecifyKeyPrompt :: Assertion
test/KeyGenTest.hs view
@@ -18,6 +18,7 @@ import Data.Time.Clock import Data.Default import Data.List ( isPrefixOf )+import Data.Maybe ( fromJust ) import Data.ByteString.Char8 ( unpack ) import Crypto.Gpgme@@ -118,7 +119,7 @@ -- Other ExpireDate to string possibilities expireDateDays :: Assertion expireDateDays =- let (Just p) = G.toPositive 10+ let p = fromJust $ G.toPositive 10 params = (def :: G.GenKeyParams) { G.expireDate = Just $ G.ExpireD p }@@ -130,7 +131,7 @@ expireDateWeeks :: Assertion expireDateWeeks =- let (Just p) = G.toPositive 10+ let p = fromJust $ G.toPositive 10 params = (def :: G.GenKeyParams) { G.expireDate = Just $ G.ExpireW p }@@ -142,7 +143,7 @@ expireDateMonths :: Assertion expireDateMonths =- let (Just p) = G.toPositive 10+ let p = fromJust $ G.toPositive 10 params = (def :: G.GenKeyParams) { G.expireDate = Just $ G.ExpireM p }@@ -154,7 +155,7 @@ expireDateYears :: Assertion expireDateYears =- let (Just p) = G.toPositive 10+ let p = fromJust $ G.toPositive 10 params = (def :: G.GenKeyParams) { G.expireDate = Just $ G.ExpireY p }@@ -166,7 +167,7 @@ expireDateSeconds :: Assertion expireDateSeconds =- let (Just p) = G.toPositive 123456+ let p = fromJust $ G.toPositive 123456 params = (def :: G.GenKeyParams) { G.expireDate = Just $ G.ExpireS p }@@ -178,7 +179,7 @@ creationDateSeconds :: Assertion creationDateSeconds =- let (Just p) = G.toPositive 123456+ let p = fromJust $ G.toPositive 123456 params = (def :: G.GenKeyParams) { G.creationDate = Just $ G.CreationS p }
test/KeyTest.hs view
@@ -1,18 +1,20 @@ {-# LANGUAGE OverloadedStrings #-}-module KeyTest (tests) where+module KeyTest (tests, cbTests) where +import qualified Data.ByteString as BS import Data.Maybe-import Data.List-import Test.Tasty (TestTree)+import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase) import Test.HUnit +import Control.Monad (filterM) import System.FilePath ((</>)) import System.Directory ( removeDirectoryRecursive , createDirectory , listDirectory , copyFile )+import System.Posix.Files (getFileStatus, isRegularFile) import Crypto.Gpgme import TestUtil@@ -29,8 +31,23 @@ , testCase "removeAliceKey" removeAliceKey , testCase "readFromFileWorks" readFromFileWorks , testCase "readFromFileDoesn'tExist" readFromFileDoesn'tExist+ , testCase "readFromBytesWorks" readFromBytesWorks+ , testCase "exportAlicePubArmored" exportAlicePubArmored+ , testCase "exportImportRoundtrip" exportImportRoundtrip+ , testCase "exportInexistentIsEmpty" exportInexistentIsEmpty+ , testCase "exportAllKeys" exportAllKeys+ , testCase "exportMinimal" exportMinimal ] +cbTests :: IO TestTree+cbTests = do+ supported <- withCtx "test/alice" "C" OpenPGP $ \ctx ->+ return $ isPassphraseCbSupported ctx+ if supported+ then return $ testGroup "key-passphrase-cb"+ [ testCase "exportAliceSecretArmored" exportAliceSecretArmored ]+ else return $ testGroup "key-passphrase-cb" []+ getAlicePubFromAlice :: Assertion getAlicePubFromAlice = do withCtx "test/alice" "C" OpenPGP $ \ctx ->@@ -77,27 +94,29 @@ checkAlicePubUserIds = do withCtx "test/alice" "C" OpenPGP $ \ctx -> do Just key <- getKey ctx alicePubFpr NoSecret- let uids = keyUserIds key- length uids @?= 1- let kuid = head uids- uid = keyuserId kuid- keyuserValidity kuid @?= ValidityUltimate- userId uid @?= "Alice (Test User A) <alice@email.com>"- userName uid @?= "Alice"- userEmail uid @?= "alice@email.com"- userComment uid @?= "Test User A"+ case keyUserIds key of+ [kuid] -> do+ let uid = keyuserId kuid+ keyuserValidity kuid @?= ValidityUltimate+ userId uid @?= "Alice (Test User A) <alice@email.com>"+ userName uid @?= "Alice"+ userEmail uid @?= "alice@email.com"+ userComment uid @?= "Test User A"+ uids -> assertFailure $+ "expected exactly one user id, got " ++ show (length uids) checkAlicePubSubkeys :: Assertion checkAlicePubSubkeys = do withCtx "test/alice" "C" OpenPGP $ \ctx -> do Just key <- getKey ctx alicePubFpr NoSecret- let subs = keySubKeys key- length subs @?= 2- let sub = head subs- subkeyAlgorithm sub @?= Rsa- subkeyLength sub @?= 2048- subkeyKeyId sub @?= "6B9809775CF91391"- subkeyFpr sub @?= "3F10159E56ECB494ED42EFA36B9809775CF91391"+ case keySubKeys key of+ [sub, _] -> do+ subkeyAlgorithm sub @?= Rsa+ subkeyLength sub @?= 2048+ subkeyKeyId sub @?= "6B9809775CF91391"+ subkeyFpr sub @?= "3F10159E56ECB494ED42EFA36B9809775CF91391"+ subs -> assertFailure $+ "expected exactly two subkeys, got " ++ show (length subs) removeAliceKey :: Assertion removeAliceKey = do@@ -106,11 +125,13 @@ -- Copy alice's key into temporary directory so we can safely remove it let aliceTmpDir = tmpDir </> "alice" createDirectory aliceTmpDir+ -- Where GnuPG has no /run/user to fall back on (macOS, some containers) it+ -- puts its agent sockets straight into the homedir, so skip anything that is+ -- not a regular file rather than listing socket names one by one. aliceFiles <- listDirectory "test/alice"+ >>= filterM (fmap isRegularFile . getFileStatus . ("test/alice" </>)) mapM_ (\f -> copyFile ("test/alice" </> f) (tmpDir </> "alice" </> f))- $ filter (\f -> not ("S.gpg-agent" `isPrefixOf` f)- && f /= "private-keys-v1.d"- && f /= ".gpg-v21-migrated"+ $ filter (\f -> f /= ".gpg-v21-migrated" && f /= "randomSeed" ) aliceFiles @@ -137,3 +158,68 @@ withCtx "test/real-person" "C" OpenPGP $ \ctx -> do mRet <- importKeyFromFile ctx "this-file-doesn't-exist" isJust mRet @? "shouldn't be able to read this file"++exportAlicePubArmored :: Assertion+exportAlicePubArmored =+ withCtx "test/alice" "C" OpenPGP $ \ctx -> do+ setArmor True ctx+ key <- fromRight <$> exportKey ctx alicePubFpr+ ("-----BEGIN PGP PUBLIC KEY BLOCK-----" `BS.isPrefixOf` key)+ @? "exported key must be armored"++exportImportRoundtrip :: Assertion+exportImportRoundtrip = do+ key <- withCtx "test/alice" "C" OpenPGP $ \ctx ->+ fromRight <$> exportKey ctx alicePubFpr+ tmpDir <- createTemporaryTestDir "exportImportRoundtrip"+ withCtx tmpDir "C" OpenPGP $ \ctx -> do+ mErr <- importKeyFromBytes ctx key+ mErr @?= Nothing+ imported <- getKey ctx alicePubFpr NoSecret+ isJust imported @? "imported key should be present"+ removeDirectoryRecursive tmpDir++exportInexistentIsEmpty :: Assertion+exportInexistentIsEmpty =+ withCtx "test/alice" "C" OpenPGP $ \ctx -> do+ key <- fromRight <$> exportKey ctx "ABCDEF"+ key @?= BS.empty++exportAllKeys :: Assertion+exportAllKeys =+ withCtx "test/alice" "C" OpenPGP $ \ctx -> do+ single <- fromRight <$> exportKeys ctx [] [alicePubFpr]+ all' <- fromRight <$> exportKeys ctx [] []+ (BS.length all' > BS.length single)+ @? "exporting all keys must yield more than a single key"++exportMinimal :: Assertion+exportMinimal =+ withCtx "test/bob" "C" OpenPGP $ \ctx -> do+ full <- fromRight <$> exportKeys ctx [] [alicePubFpr]+ minimal <- fromRight <$> exportKeys ctx [ExportMinimal] [alicePubFpr]+ not (BS.null minimal) @? "minimal export must not be empty"+ (BS.length minimal <= BS.length full)+ @? "minimal export must not be larger than the full export"++exportAliceSecretArmored :: Assertion+exportAliceSecretArmored =+ withCtx "test/alice" "C" OpenPGP $ \ctx -> do+ setPassphraseCallback ctx (Just (\_ _ _ -> return (Just "alice123")))+ setArmor True ctx+ key <- fromRight <$> exportSecretKey ctx alicePubFpr+ ("-----BEGIN PGP PRIVATE KEY BLOCK-----" `BS.isPrefixOf` key)+ @? "exported secret key must be armored"++readFromBytesWorks :: Assertion+readFromBytesWorks = do+ key <- BS.readFile "test/real-person/real-person.key"+ tmpDir <- createTemporaryTestDir "readFromBytesWorks"+ withCtx tmpDir "C" OpenPGP $ \ctx -> do+ before <- getKey ctx realPersonPubFpr NoSecret+ isNothing before @? "key shouldn't be present before import"+ mRet <- importKeyFromBytes ctx key+ mRet @?= Nothing+ after <- getKey ctx realPersonPubFpr NoSecret+ isJust after @? "key should be present after import"+ removeDirectoryRecursive tmpDir
test/Main.hs view
@@ -10,8 +10,10 @@ main :: IO () main = do passphraseCbTests <- CryptoTest.cbTests+ keyCbTests <- KeyTest.cbTests defaultMain $ testGroup "tests" [ testGroup "key" KeyTest.tests+ , keyCbTests , testGroup "keyGen" KeyGenTest.tests , testGroup "ctx" CtxTest.tests , CryptoTest.tests
test/TestUtil.hs view
@@ -20,6 +20,9 @@ bobPubFpr :: BS.ByteString bobPubFpr = "6C4FB8F2" +realPersonPubFpr :: BS.ByteString+realPersonPubFpr = "2DA4C89E28F515B4"+ -- Orphan instance here! Because this is only a test, orphans are probably OK. -- http://stackoverflow.com/a/3081367/350221 instance Arbitrary BS.ByteString where