File: Tests.hs

package info (click to toggle)
haskell-cryptostore 0.3.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 964 kB
  • sloc: haskell: 8,241; makefile: 3
file content (370 lines) | stat: -rw-r--r-- 14,921 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
-- | CMS tests.
module CMS.Tests (cmsTests) where

import Control.Monad

import qualified Data.ByteString as B
import           Data.String (fromString)
import           Data.Maybe (isNothing)

import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck

import Crypto.Store.CMS
import Crypto.Store.PKCS8
import Crypto.Store.X509 (readSignedObject)

import CMS.Instances
import Util

message :: B.ByteString
message = fromString "hello, world\r\n"

testKey :: Int -> B.ByteString
testKey len = B.pack [0 .. toEnum (len - 1)]

hasType :: ContentType -> ContentInfo -> Bool
hasType t ci = t == getContentType ci

verifyInnerMessage :: B.ByteString -> ContentInfo -> IO ()
verifyInnerMessage msg ci = do
    assertBool "unexpected inner type" (hasType DataType ci)
    let DataCI bs = ci
    assertEqual "inner message differs" msg bs

dataTests :: TestTree
dataTests =
    testGroup "Data"
        [ testCase "read" $ do
              cms <- readCMSFile path
              length cms @?= count
              forM_ cms (verifyInnerMessage message)
        , testCase "write" $ do
              bs <- B.readFile path
              let ciList = [DataCI message]
              writeCMSFileToMemory ciList @?= bs
        ]
  where path  = testFile "cms-data.pem"
        count = 1

signedDataTests :: TestTree
signedDataTests = signedDataAnyTests "SignedData" path getAttached
  where path  = testFile "cms-signed-data.pem"

signedDataDetachedTests :: TestTree
signedDataDetachedTests =
    signedDataAnyTests "SignedDataDetached" path (getDetached message)
  where path  = testFile "cms-signed-data-detached.pem"

signedDataAnyTests :: TestName -> FilePath -> (SignedData (Encap EncapsulatedContent) -> IO (SignedData EncapsulatedContent)) -> TestTree
signedDataAnyTests caseName path getInner =
    testCaseSteps caseName $ \step -> do
        cms <- readCMSFile path
        assertEqual "unexpected parse count" (length names) (length cms)

        forM_ (zip [0..] cms) $ \(index, ci) -> do
            let name = names !! index

            step ("verifying " ++ name)
            assertBool "unexpected type" (hasType SignedDataType ci)
            let SignedDataCI sdEncap = ci
            sd <- getInner sdEncap
            result <- verifySignedData withSignerKey sd
            assertRight result (verifyInnerMessage message)
  where names = [ "RSA"
                , "DSA"
                , "EC (named curve)"
                , "EC (explicit prime curve)"
                , "RSA-PSS"
                ]

envelopedDataTests :: TestTree
envelopedDataTests =
    testGroup "EnvelopedData"
        [ testKT "KTRI" path3 keys1
        , testKA "KARI" path4 keys1
        , test "KEKRI" path1 keys1 withRecipientKey
        , test "PWRI" path2 keys2 (\_ -> withRecipientPassword pwd)
        ]
  where test caseName path keys f = testCaseSteps caseName $ \step -> do
            cms <- readCMSFile path
            assertEqual "unexpected parse count" (length keys) (length cms)

            forM_ (zip [0..] cms) $ \(index, ci) -> do
                let (name, key) = keys !! index

                step ("testing " ++ name)
                ev <- getEnveloppedAttached ci
                result <- openEnvelopedData (f key) ev
                assertRight result (verifyInnerMessage message)
        testKT caseName path keys = testCaseSteps caseName $ \step -> do
            let rsaPath = testFile "rsa-unencrypted-pkcs8.pem"
            [Unprotected priv] <- readKeyFile rsaPath

            cms <- readCMSFile path
            assertEqual "unexpected parse count" (length modes * length keys) (length cms)

            let pairs = [ (c, m) | c <- map fst keys1, m <- modes ]
            forM_ (zip pairs cms) $ \((c, m), ci) -> do
                step ("testing " ++ c ++ " with " ++ m)
                ev <- getEnveloppedAttached ci
                result <- openEnvelopedData (withRecipientKeyTrans priv) ev
                assertRight result (verifyInnerMessage message)
        testKA caseName path keys = testCaseSteps caseName $ \step -> do
            let ecdsaKeyPath  = testFile "ecdsa-p256-unencrypted-pkcs8.pem"
                ecdsaCertPath = testFile "ecdsa-p256-self-signed-cert.pem"
            [Unprotected priv] <- readKeyFile ecdsaKeyPath
            [cert] <- readSignedObject ecdsaCertPath

            cms <- readCMSFile path
            assertEqual "unexpected parse count" (length mds * length keys) (length cms)

            let pairs = [ (c, h) | c <- map fst keys, h <- mds ]
            forM_ (zip pairs cms) $ \((c, h), ci) -> do
                step ("testing " ++ c ++ " with " ++ h)
                ev <- getEnveloppedAttached ci
                result <- openEnvelopedData (withRecipientKeyAgree priv cert) ev
                assertRight result (verifyInnerMessage message)
        getEnveloppedAttached ci = do
            assertBool "unexpected type" (hasType EnvelopedDataType ci)
            let EnvelopedDataCI evEncap = ci
            getAttached evEncap
        path1 = testFile "cms-enveloped-kekri-data.pem"
        path2 = testFile "cms-enveloped-pwri-data.pem"
        path3 = testFile "cms-enveloped-ktri-data.pem"
        path4 = testFile "cms-enveloped-kari-data.pem"
        pwd   = fromString "dontchangeme"
        keys2 = [ ("3DES_CBC",             testKey 24)
                , ("AES128_CBC",           testKey 16)
                , ("AES192_CBC",           testKey 24)
                , ("AES256_CBC",           testKey 32)
                , ("CAST5_CBC (128 bits)", testKey 16)
                , ("Camellia128_CBC",      testKey 16)
                , ("RC2 (128 bits)",       testKey 16)
                ]
        keys1 = keys2 ++
                [ ("AES128_ECB",           testKey 16)
                , ("AES192_ECB",           testKey 24)
                , ("AES256_ECB",           testKey 32)
                , ("Camellia128_ECB",      testKey 16)
                ]
        modes = [ "RSAES-PKCS1"
                , "RSAES-OAEP"
                ]
        mds   = [ "SHA1"
                , "SHA224"
                , "SHA256"
                , "SHA384"
                , "SHA512"
                ]

digestedDataTests :: TestTree
digestedDataTests =
    testCaseSteps "DigestedData" $ \step -> do
        cms <- readCMSFile path
        assertEqual "unexpected parse count" (length algs) (length cms)

        forM_ (zip [0..] cms) $ \(index, ci) -> do
            let (name, alg) = algs !! index

            step ("verifying " ++ name)
            assertBool "unexpected type" (hasType DigestedDataType ci)
            let DigestedDataCI ddEncap = ci
            dd <- getAttached ddEncap
            let result = digestVerify dd
            assertRight result (verifyInnerMessage message)

            step ("digesting " ++ name)
            let dd' = digestData alg (DataCI message)
                ci' = toAttachedCI dd'
            ci @?= ci'
  where path  = testFile "cms-digested-data.pem"
        algs  = [ ("MD5",    DigestAlgorithm MD5)
                , ("SHA1",   DigestAlgorithm SHA1)
                , ("SHA224", DigestAlgorithm SHA224)
                , ("SHA256", DigestAlgorithm SHA256)
                , ("SHA384", DigestAlgorithm SHA384)
                , ("SHA512", DigestAlgorithm SHA512)
                ]

encryptedDataTests :: TestTree
encryptedDataTests =
    testCaseSteps "EncryptedData" $ \step -> do
        cms <- readCMSFile path
        assertEqual "unexpected parse count" (length keys) (length cms)

        forM_ (zip [0..] cms) $ \(index, ci) -> do
            let (name, key) = keys !! index

            step ("decrypting " ++ name)
            assertBool "unexpected type" (hasType EncryptedDataType ci)
            let EncryptedDataCI edEncap = ci
            ed <- getAttached edEncap
            let result = decryptData key ed
            assertRight result (verifyInnerMessage message)

            step ("encrypting " ++ name)
            let params = edContentEncryptionParams ed
                ed'    = encryptData key params [] (DataCI message)
                ci'    = toAttachedCI <$> ed'
            Right ci @?= ci'
  where path  = testFile "cms-encrypted-data.pem"
        keys  = [ ("DES_CBC",              testKey  8)
                , ("3DES_CBC",             testKey 24)
                , ("AES128_CBC",           testKey 16)
                , ("AES192_CBC",           testKey 24)
                , ("AES256_CBC",           testKey 32)
                , ("CAST5_CBC (40 bits)",  testKey  5)
                , ("CAST5_CBC (128 bits)", testKey 16)
                , ("Camellia128_CBC",      testKey 16)
                , ("RC2 (40 bits)",        testKey  5)
                , ("RC2 (64 bits)",        testKey  8)
                , ("RC2 (128 bits)",       testKey 16)
                , ("DES_ECB",              testKey  8)
                , ("AES128_ECB",           testKey 16)
                , ("AES192_ECB",           testKey 24)
                , ("AES256_ECB",           testKey 32)
                , ("Camellia128_ECB",      testKey 16)
                ]

authEnvelopedDataTests :: TestTree
authEnvelopedDataTests =
    testGroup "AuthEnvelopedData"
        [ testKT "KTRI" path3
        , testKA "KARI" path4
        , test "KEKRI" path1 withRecipientKey
        ]
  where test caseName path f = testCaseSteps caseName $ \step -> do
            cms <- readCMSFile path
            assertEqual "unexpected parse count" (length keys) (length cms)

            forM_ (zip [0..] cms) $ \(index, ci) -> do
                let (name, key) = keys !! index

                step ("testing " ++ name)
                ae <- getAuthEnveloppedAttached ci
                result <- openAuthEnvelopedData (f key) ae
                assertRight result (verifyInnerMessage message)
        testKT caseName path = testCaseSteps caseName $ \step -> do
            let rsaPath = testFile "rsa-unencrypted-pkcs8.pem"
            [Unprotected priv] <- readKeyFile rsaPath

            cms <- readCMSFile path
            assertEqual "unexpected parse count" (length modes * length keys) (length cms)

            let pairs = [ (c, m) | c <- map fst keys, m <- modes ]
            forM_ (zip pairs cms) $ \((c, m), ci) -> do
                step ("testing " ++ c ++ " with " ++ m)
                ae <- getAuthEnveloppedAttached ci
                result <- openAuthEnvelopedData (withRecipientKeyTrans priv) ae
                assertRight result (verifyInnerMessage message)
        testKA caseName path = testCaseSteps caseName $ \step -> do
            let ecdsaKeyPath  = testFile "ecdsa-p256-unencrypted-pkcs8.pem"
                ecdsaCertPath = testFile "ecdsa-p256-self-signed-cert.pem"
            [Unprotected priv] <- readKeyFile ecdsaKeyPath
            [cert] <- readSignedObject ecdsaCertPath

            cms <- readCMSFile path
            assertEqual "unexpected parse count" (length mds * length keys) (length cms)

            let pairs = [ (c, h) | c <- map fst keys, h <- mds ]
            forM_ (zip pairs cms) $ \((c, h), ci) -> do
                step ("testing " ++ c ++ " with " ++ h)
                ae <- getAuthEnveloppedAttached ci
                result <- openAuthEnvelopedData (withRecipientKeyAgree priv cert) ae
                assertRight result (verifyInnerMessage message)
        getAuthEnveloppedAttached ci = do
            assertBool "unexpected type" (hasType AuthEnvelopedDataType ci)
            let AuthEnvelopedDataCI aeEncap = ci
            getAttached aeEncap
        path1 = testFile "cms-auth-enveloped-kekri-data.pem"
        path3 = testFile "cms-auth-enveloped-ktri-data.pem"
        path4 = testFile "cms-auth-enveloped-kari-data.pem"
        keys  = [ ("AES128_GCM",           testKey 16)
                , ("AES192_GCM",           testKey 24)
                , ("AES256_GCM",           testKey 32)
                ]
        modes = [ "RSAES-PKCS1"
                , "RSAES-OAEP"
                ]
        mds   = [ "SHA1"
                , "SHA224"
                , "SHA256"
                , "SHA384"
                , "SHA512"
                ]

propertyTests :: TestTree
propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"
    [ testProperty "marshalling" $ \l ->
        let bs = writeCMSFileToMemory l
        in label (sizeRange bs) $ l === readCMSFileFromMemory bs
    , testProperty "signing" $ \alg ci ->
        collect alg $ do
            (sigFns, verFn) <- scale succ (arbitrarySigVer alg)
            r <- signData sigFns ci
            let Right sd = r
            r' <- verifySignedData verFn sd
            return (Right ci === r')
    , testProperty "enveloping" $ \alg ci ->
        collect alg $ do
            (oinfo, key, envFns, devFn, attrs) <- getCommon alg
            r <- envelopData oinfo key alg envFns attrs ci
            let Right ev = r
            r' <- openEnvelopedData devFn ev
            return (Right ci === r')
    , testProperty "digesting" $ \alg ci ->
        collect alg $
            let dd = digestData alg ci
             in Right ci === digestVerify dd
    , testProperty "encrypting" $ \alg ci ->
        collect alg $ do
            key <- generateKey alg
            attrs <- arbitraryAttributes
            let Right ed = encryptData key alg attrs ci
            return (Right ci === decryptData key ed)
    , testProperty "authenticating" $ \alg dig ci ->
        collect alg $ do
            (oinfo, key, envFns, devFn, uAttrs) <- getCommon alg
            aAttrs <- if isNothing dig then pure [] else arbitraryAttributes
            r <- generateAuthenticatedData oinfo key alg dig envFns aAttrs uAttrs ci
            let Right ad = r
            r' <- verifyAuthenticatedData devFn ad
            return (Right ci === r')
    , testProperty "enveloping with authentication" $ \alg ci ->
        collect alg $ do
            (oinfo, key, envFns, devFn, uAttrs) <- getCommon alg
            aAttrs <- arbitraryAttributes
            r <- authEnvelopData oinfo key alg envFns aAttrs uAttrs ci
            let Right ae = r
            r' <- openAuthEnvelopedData devFn ae
            return (Right ci === r')
    ]
  where
    sizeRange bs =
        let n = B.length bs `div` 1024
         in show n ++ " .. " ++ show (n + 1) ++ " KB"

getCommon :: HasKeySize params
          => params
          -> Gen (OriginatorInfo, B.ByteString, [ProducerOfRI Gen], ConsumerOfRI Gen, [Attribute])
getCommon alg = do
    oinfo <- arbitrary
    key <- generateKey alg
    (envFns, devFn) <- scale succ (arbitraryEnvDev key)
    attrs <- arbitraryAttributes
    return (oinfo, key, envFns, devFn, attrs)

cmsTests :: TestTree
cmsTests =
    testGroup "CMS"
        [ dataTests
        , signedDataTests
        , signedDataDetachedTests
        , envelopedDataTests
        , digestedDataTests
        , encryptedDataTests
        , authEnvelopedDataTests
        , propertyTests
        ]