diff options
author | Joe Crayne <joe@jerkface.net> | 2019-11-14 16:45:14 -0500 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2019-11-14 16:45:14 -0500 |
commit | b42c0d847a785487f3222b0d5360746d25d3209c (patch) | |
tree | 11ef85e3e4577eb047227f9938761bdac94a1309 | |
parent | 76bf7e08bccbb1a3a689068016b8a9c29d1e060e (diff) |
Cv25519 encryption.
-rw-r--r-- | Crypto/JOSE/AESKW.hs | 123 | ||||
-rw-r--r-- | Data/OpenPGP.hs | 77 | ||||
-rw-r--r-- | Data/OpenPGP/Util/Cv25519.hs | 231 | ||||
-rw-r--r-- | Data/OpenPGP/Util/DecryptSecretKey.hs | 27 | ||||
-rw-r--r-- | Data/OpenPGP/Util/Ed25519.hs | 4 | ||||
-rw-r--r-- | openpgp-util.cabal | 5 | ||||
-rw-r--r-- | tests/test-cv25519.hs | 110 |
7 files changed, 524 insertions, 53 deletions
diff --git a/Crypto/JOSE/AESKW.hs b/Crypto/JOSE/AESKW.hs new file mode 100644 index 0000000..6b3d28e --- /dev/null +++ b/Crypto/JOSE/AESKW.hs | |||
@@ -0,0 +1,123 @@ | |||
1 | -- Copyright (C) 2016 Fraser Tweedale | ||
2 | -- | ||
3 | -- Licensed under the Apache License, Version 2.0 (the "License"); | ||
4 | -- you may not use this file except in compliance with the License. | ||
5 | -- You may obtain a copy of the License at | ||
6 | -- | ||
7 | -- http://www.apache.org/licenses/LICENSE-2.0 | ||
8 | -- | ||
9 | -- Unless required by applicable law or agreed to in writing, software | ||
10 | -- distributed under the License is distributed on an "AS IS" BASIS, | ||
11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
12 | -- See the License for the specific language governing permissions and | ||
13 | -- limitations under the License. | ||
14 | |||
15 | {-# LANGUAGE ScopedTypeVariables #-} | ||
16 | |||
17 | {- | | ||
18 | |||
19 | Advanced Encryption Standard (AES) Key Wrap Algorithm; | ||
20 | <https://https://tools.ietf.org/html/rfc3394>. | ||
21 | |||
22 | -} | ||
23 | module Crypto.JOSE.AESKW | ||
24 | ( | ||
25 | aesKeyWrap | ||
26 | , aesKeyUnwrap | ||
27 | ) where | ||
28 | |||
29 | import Control.Monad.State | ||
30 | import Crypto.Cipher.Types | ||
31 | import Data.Bits (xor) | ||
32 | import Data.ByteArray as BA hiding (replicate, xor) | ||
33 | import Data.Memory.Endian (BE(..), toBE) | ||
34 | import Data.Memory.PtrMethods (memCopy) | ||
35 | import Data.Word (Word64) | ||
36 | import Foreign.Ptr (Ptr, plusPtr) | ||
37 | import Foreign.Storable (peek, peekElemOff, poke, pokeElemOff) | ||
38 | import System.IO.Unsafe (unsafePerformIO) | ||
39 | |||
40 | iv :: Word64 | ||
41 | iv = 0xA6A6A6A6A6A6A6A6 | ||
42 | |||
43 | aesKeyWrapStep | ||
44 | :: BlockCipher128 cipher | ||
45 | => cipher | ||
46 | -> Ptr Word64 -- ^ register | ||
47 | -> (Int, Int) -- ^ step (t) and offset (i) | ||
48 | -> StateT Word64 IO () | ||
49 | aesKeyWrapStep cipher p (t, i) = do | ||
50 | a <- get | ||
51 | r_i <- lift $ peekElemOff p i | ||
52 | m :: ScrubbedBytes <- | ||
53 | lift $ alloc 16 $ \p' -> poke p' a >> pokeElemOff p' 1 r_i | ||
54 | let b = ecbEncrypt cipher m | ||
55 | b_hi <- lift $ withByteArray b peek | ||
56 | b_lo <- lift $ withByteArray b (`peekElemOff` 1) | ||
57 | put (b_hi `xor` unBE (toBE (fromIntegral t))) | ||
58 | lift $ pokeElemOff p i b_lo | ||
59 | |||
60 | -- | Wrap a secret. | ||
61 | -- | ||
62 | -- Input size must be a multiple of 8 bytes, and at least 16 bytes. | ||
63 | -- Output size is input size plus 8 bytes. | ||
64 | -- | ||
65 | aesKeyWrap | ||
66 | :: (ByteArrayAccess m, ByteArray c, BlockCipher128 cipher) | ||
67 | => cipher | ||
68 | -> m | ||
69 | -> c | ||
70 | aesKeyWrap cipher m = unsafePerformIO $ do | ||
71 | let n = BA.length m | ||
72 | c <- withByteArray m $ \p -> | ||
73 | alloc (n + 8) $ \p' -> | ||
74 | memCopy (p' `plusPtr` 8) p n | ||
75 | withByteArray c $ \p -> do | ||
76 | let coords = zip [1..] (join (replicate 6 [1 .. n `div` 8])) | ||
77 | a <- execStateT (mapM_ (aesKeyWrapStep cipher p) coords) iv | ||
78 | poke p a | ||
79 | return c | ||
80 | |||
81 | aesKeyUnwrapStep | ||
82 | :: BlockCipher128 cipher | ||
83 | => cipher | ||
84 | -> Ptr Word64 -- ^ register | ||
85 | -> (Int, Int) -- ^ step (t) and offset (i) | ||
86 | -> StateT Word64 IO () | ||
87 | aesKeyUnwrapStep cipher p (t, i) = do | ||
88 | a <- get | ||
89 | r_i <- lift $ peekElemOff p i | ||
90 | let a_t = a `xor` unBE (toBE (fromIntegral t)) | ||
91 | m :: ScrubbedBytes <- | ||
92 | lift $ alloc 16 $ \p' -> poke p' a_t >> pokeElemOff p' 1 r_i | ||
93 | let b = ecbDecrypt cipher m | ||
94 | b_hi <- lift $ withByteArray b peek | ||
95 | b_lo <- lift $ withByteArray b (`peekElemOff` 1) | ||
96 | put b_hi | ||
97 | lift $ pokeElemOff p i b_lo | ||
98 | |||
99 | -- | Unwrap a secret. | ||
100 | -- | ||
101 | -- Input size must be a multiple of 8 bytes, and at least 24 bytes. | ||
102 | -- Output size is input size minus 8 bytes. | ||
103 | -- | ||
104 | -- Returns 'Nothing' if inherent integrity check fails. Otherwise, | ||
105 | -- the chance that the key data is corrupt is 2 ^ -64. | ||
106 | -- | ||
107 | aesKeyUnwrap | ||
108 | :: (ByteArrayAccess c, ByteArray m, BlockCipher128 cipher) | ||
109 | => cipher | ||
110 | -> c | ||
111 | -> Maybe m | ||
112 | aesKeyUnwrap cipher c = unsafePerformIO $ do | ||
113 | let n = BA.length c - 8 | ||
114 | m <- withByteArray c $ \p' -> | ||
115 | alloc n $ \p -> | ||
116 | memCopy p (p' `plusPtr` 8) n | ||
117 | a <- withByteArray c $ \p' -> peek p' | ||
118 | a' <- withByteArray m $ \p -> do | ||
119 | let n' = n `div` 8 | ||
120 | let tMax = n' * 6 | ||
121 | let coords = zip [tMax,tMax-1..1] (cycle [n'-1,n'-2..0]) | ||
122 | execStateT (mapM_ (aesKeyUnwrapStep cipher p) coords) a | ||
123 | return $ if a' == iv then Just m else Nothing | ||
diff --git a/Data/OpenPGP.hs b/Data/OpenPGP.hs index 45ca27e..17a6927 100644 --- a/Data/OpenPGP.hs +++ b/Data/OpenPGP.hs | |||
@@ -66,7 +66,10 @@ module Data.OpenPGP ( | |||
66 | secret_key_fields, | 66 | secret_key_fields, |
67 | eccOID, | 67 | eccOID, |
68 | encode_public_key_material, | 68 | encode_public_key_material, |
69 | decode_public_key_material | 69 | decode_public_key_material, |
70 | getEllipticCurvePublicKey, | ||
71 | encodeOID, | ||
72 | hashLen | ||
70 | ) where | 73 | ) where |
71 | 74 | ||
72 | import Control.Applicative | 75 | import Control.Applicative |
@@ -361,7 +364,8 @@ secret_key_fields ELGAMAL = ['x'] | |||
361 | secret_key_fields DSA = ['x'] | 364 | secret_key_fields DSA = ['x'] |
362 | secret_key_fields ECDSA = ['d'] | 365 | secret_key_fields ECDSA = ['d'] |
363 | secret_key_fields Ed25519 = ['d'] | 366 | secret_key_fields Ed25519 = ['d'] |
364 | secret_key_fields alg = error ("Unkown secret fields for "++show alg) -- Nothing in the spec. Maybe empty | 367 | secret_key_fields ECC = ['d'] |
368 | secret_key_fields alg = error ("Unknown secret fields for "++show alg) -- Nothing in the spec. Maybe empty | ||
365 | 369 | ||
366 | (!) :: (HasCallStack, Show k, Eq k) => [(k,v)] -> k -> v | 370 | (!) :: (HasCallStack, Show k, Eq k) => [(k,v)] -> k -> v |
367 | (!) xs k = case lookup k xs of | 371 | (!) xs k = case lookup k xs of |
@@ -425,16 +429,21 @@ eccOID PublicKeyPacket { key = k } = lookup 'c' k >>= \(MPI oid) -> Just (snd $ | |||
425 | eccOID SecretKeyPacket { key = k } = lookup 'c' k >>= \(MPI oid) -> Just (snd $ putBigNum oid) | 429 | eccOID SecretKeyPacket { key = k } = lookup 'c' k >>= \(MPI oid) -> Just (snd $ putBigNum oid) |
426 | eccOID _ = Nothing | 430 | eccOID _ = Nothing |
427 | 431 | ||
432 | encodeOID :: MPI -> B.ByteString | ||
433 | encodeOID c = | ||
434 | let (bitlen,oid) = B.splitAt 2 (encode c) | ||
435 | len16 = decode bitlen :: Word16 | ||
436 | (fullbytes,rembits) = len16 `quotRem` 8 | ||
437 | len8 = fromIntegral (fullbytes + if rembits/=0 then 1 else 0) :: Word8 | ||
438 | in len8 `B.cons` oid | ||
439 | |||
428 | encode_public_key_material :: Packet -> [B.ByteString] | 440 | encode_public_key_material :: Packet -> [B.ByteString] |
429 | encode_public_key_material k | key_algorithm k `elem` [ECDSA,Ed25519,ECC] = do | 441 | encode_public_key_material k | key_algorithm k `elem` [ECDSA,Ed25519,ECC] = do |
430 | -- http://tools.ietf.org/html/rfc6637 | 442 | -- http://tools.ietf.org/html/rfc6637 |
431 | c <- maybeToList $ lookup 'c' (key k) | 443 | c <- maybeToList $ lookup 'c' (key k) |
432 | MPI l <- maybeToList $ lookup 'l' (key k) | 444 | MPI l <- maybeToList $ lookup 'l' (key k) |
433 | MPI flag <- maybeToList $ lookup 'f' (key k) | 445 | MPI flag <- maybeToList $ lookup 'f' (key k) |
434 | let (bitlen,oid) = B.splitAt 2 (encode c) | 446 | let oid = encodeOID c |
435 | len16 = decode bitlen :: Word16 | ||
436 | (fullbytes,rembits) = len16 `quotRem` 8 | ||
437 | len8 = fromIntegral (fullbytes + if rembits/=0 then 1 else 0) :: Word8 | ||
438 | eccstuff = case lookup 'e' (key k) of | 447 | eccstuff = case lookup 'e' (key k) of |
439 | Just (MPI stuff) -> encode (fromIntegral stuff :: Word32) | 448 | Just (MPI stuff) -> encode (fromIntegral stuff :: Word32) |
440 | Nothing -> B.empty | 449 | Nothing -> B.empty |
@@ -442,39 +451,45 @@ encode_public_key_material k | key_algorithm k `elem` [ECDSA,Ed25519,ECC] = do | |||
442 | 0x40 -> do | 451 | 0x40 -> do |
443 | MPI n <- maybeToList $ lookup 'n' (key k) | 452 | MPI n <- maybeToList $ lookup 'n' (key k) |
444 | let xy = flag*(4^l) + n | 453 | let xy = flag*(4^l) + n |
445 | [ len8 `B.cons` oid, encode (MPI xy), eccstuff ] | 454 | [ oid, encode (MPI xy), eccstuff ] |
446 | _ -> do | 455 | _ -> do |
447 | MPI x <- maybeToList $ lookup 'x' (key k) | 456 | MPI x <- maybeToList $ lookup 'x' (key k) |
448 | MPI y <- maybeToList $ lookup 'y' (key k) | 457 | MPI y <- maybeToList $ lookup 'y' (key k) |
449 | let xy = flag*(4^l) + x*(2^l) + y | 458 | let xy = flag*(4^l) + x*(2^l) + y |
450 | [ len8 `B.cons` oid, encode (MPI xy), eccstuff ] | 459 | [ oid, encode (MPI xy), eccstuff ] |
451 | encode_public_key_material k = map (encode . (key k !)) (public_key_fields $ key_algorithm k) | 460 | encode_public_key_material k = map (encode . (key k !)) (public_key_fields $ key_algorithm k) |
452 | 461 | ||
453 | decode_public_key_material :: KeyAlgorithm -> Get [(Char,MPI)] | 462 | getEllipticCurvePublicKey :: Get [(Char,MPI)] |
454 | decode_public_key_material algorithm | algorithm `elem` [ECDSA, Ed25519] = do | 463 | getEllipticCurvePublicKey = do |
455 | -- http://tools.ietf.org/html/rfc6637 (9) Algorithm-Specific Fields for ECDSA keys | ||
456 | oidlen <- get :: Get Word8 | ||
457 | oidbytes <- getSomeByteString (fromIntegral oidlen) | ||
458 | let mpiFromBytes bytes = MPI (getBigNum $ B.toStrict bytes) | ||
459 | oid = mpiFromBytes oidbytes | ||
460 | MPI fxy <- get | 464 | MPI fxy <- get |
461 | let integerBytesize i = fromIntegral $ LZ.length (encode (MPI i)) - 2 | 465 | let integerBytesize i = fromIntegral $ LZ.length (encode (MPI i)) - 2 |
462 | width = ( integerBytesize fxy - 1 ) `div` 2 | 466 | width = ( integerBytesize fxy - 1 ) `div` 2 |
463 | l = width*8 | 467 | l = width*8 |
464 | (flag,xy) = fxy `quotRem` (256^(2*width)) | 468 | (flag,xy) = fxy `quotRem` (256^(2*width)) |
465 | return $ case flag of | 469 | return $ case flag of |
466 | 0x40 -> [('c',oid), ('l',MPI l), ('n',MPI xy), ('f',MPI flag)] | 470 | 0x40 -> [('l',MPI l), ('n',MPI xy), ('f',MPI flag)] |
467 | _ -> let (x,y) = xy `quotRem` (256^width) | 471 | _ -> let (x,y) = xy `quotRem` (256^width) |
468 | -- (fx,y) = xy `quotRem` (256^width) | 472 | -- (fx,y) = xy `quotRem` (256^width) |
469 | -- (flag,x) = fx `quotRem` (256^width) | 473 | -- (flag,x) = fx `quotRem` (256^width) |
470 | in [('c',oid), ('l',MPI l), ('x',MPI x), ('y',MPI y), ('f',MPI flag)] | 474 | in [('l',MPI l), ('x',MPI x), ('y',MPI y), ('f',MPI flag)] |
471 | decode_public_key_material ECC = do | 475 | |
472 | -- http://tools.ietf.org/html/rfc6637 (9) Algorithm-Specific Fields for ECDH keys: | 476 | getOID :: Get MPI |
477 | getOID = do | ||
473 | oidlen <- get :: Get Word8 | 478 | oidlen <- get :: Get Word8 |
474 | oidbytes <- getSomeByteString (fromIntegral oidlen) | 479 | oidbytes <- getSomeByteString (fromIntegral oidlen) |
475 | let mpiFromBytes bytes = MPI (getBigNum $ B.toStrict bytes) | 480 | let mpiFromBytes bytes = MPI (getBigNum $ B.toStrict bytes) |
476 | oid = mpiFromBytes oidbytes | 481 | oid = mpiFromBytes oidbytes |
477 | MPI fxy <- get | 482 | return oid |
483 | |||
484 | decode_public_key_material :: KeyAlgorithm -> Get [(Char,MPI)] | ||
485 | decode_public_key_material algorithm | algorithm `elem` [ECDSA, Ed25519] = do | ||
486 | -- http://tools.ietf.org/html/rfc6637 (9) Algorithm-Specific Fields for ECDSA keys | ||
487 | oid <- getOID | ||
488 | fmap (('c',oid) :) getEllipticCurvePublicKey | ||
489 | decode_public_key_material ECC = do | ||
490 | -- http://tools.ietf.org/html/rfc6637 (9) Algorithm-Specific Fields for ECDH keys: | ||
491 | oid <- getOID | ||
492 | result <- getEllipticCurvePublicKey | ||
478 | eccstuff <- get :: Get Word32 | 493 | eccstuff <- get :: Get Word32 |
479 | {- eccstuff is 4 one-byte fields: | 494 | {- eccstuff is 4 one-byte fields: |
480 | flen <- get :: Get Word8 | 495 | flen <- get :: Get Word8 |
@@ -482,17 +497,7 @@ decode_public_key_material ECC = do | |||
482 | hashid <- get :: Get Word8 | 497 | hashid <- get :: Get Word8 |
483 | algoid <- get :: Get Word8 | 498 | algoid <- get :: Get Word8 |
484 | -} | 499 | -} |
485 | let integerBytesize i = fromIntegral $ LZ.length (encode (MPI i)) - 2 | 500 | return $ ('c', oid) : result ++ [('e',MPI (fromIntegral eccstuff))] |
486 | width = ( integerBytesize fxy - 1 ) `div` 2 | ||
487 | l = width*8 | ||
488 | (flag,xy) = fxy `quotRem` (256^(2*width)) | ||
489 | result = case flag of | ||
490 | 0x40 -> [('c',oid), ('l',MPI l), ('n',MPI xy), ('f',MPI flag)] | ||
491 | _ -> let (x,y) = xy `quotRem` (256^width) | ||
492 | -- (fx,y) = xy `quotRem` (256^width) | ||
493 | -- (flag,x) = fx `quotRem` (256^width) | ||
494 | in [('c',oid), ('l',MPI l), ('x',MPI x), ('y',MPI y), ('f',MPI flag)] | ||
495 | return $ result ++ [('e',MPI (fromIntegral eccstuff))] | ||
496 | decode_public_key_material algorithm = mapM (\f -> fmap ((,)f) get) (public_key_fields algorithm) | 501 | decode_public_key_material algorithm = mapM (\f -> fmap ((,)f) get) (public_key_fields algorithm) |
497 | 502 | ||
498 | put_packet :: Packet -> (B.ByteString, Word8) | 503 | put_packet :: Packet -> (B.ByteString, Word8) |
@@ -907,6 +912,16 @@ infiniHashes hsh s = LZ.fromChunks (hs 0) | |||
907 | data HashAlgorithm = MD5 | SHA1 | RIPEMD160 | SHA256 | SHA384 | SHA512 | SHA224 | HashAlgorithm Word8 | 912 | data HashAlgorithm = MD5 | SHA1 | RIPEMD160 | SHA256 | SHA384 | SHA512 | SHA224 | HashAlgorithm Word8 |
908 | deriving (Show, Read, Eq, Ord) | 913 | deriving (Show, Read, Eq, Ord) |
909 | 914 | ||
915 | hashLen :: HashAlgorithm -> Int | ||
916 | hashLen MD5 = 16 | ||
917 | hashLen SHA1 = 20 | ||
918 | hashLen RIPEMD160 = 20 | ||
919 | hashLen SHA256 = 32 | ||
920 | hashLen SHA384 = 48 | ||
921 | hashLen SHA512 = 64 | ||
922 | hashLen SHA224 = 28 | ||
923 | hashLen (HashAlgorithm _) = 0 | ||
924 | |||
910 | instance Enum HashAlgorithm where | 925 | instance Enum HashAlgorithm where |
911 | toEnum 01 = MD5 | 926 | toEnum 01 = MD5 |
912 | toEnum 02 = SHA1 | 927 | toEnum 02 = SHA1 |
diff --git a/Data/OpenPGP/Util/Cv25519.hs b/Data/OpenPGP/Util/Cv25519.hs new file mode 100644 index 0000000..aef3521 --- /dev/null +++ b/Data/OpenPGP/Util/Cv25519.hs | |||
@@ -0,0 +1,231 @@ | |||
1 | {-# LANGUAGE ExistentialQuantification #-} | ||
2 | {-# LANGUAGE QuasiQuotes #-} | ||
3 | module Data.OpenPGP.Util.Cv25519 where | ||
4 | |||
5 | import Control.Arrow | ||
6 | import Control.Monad | ||
7 | import Data.Binary | ||
8 | import Data.Binary.Get | ||
9 | import Data.ByteString (ByteString) | ||
10 | import Data.Bits | ||
11 | import qualified Data.ByteArray as BA | ||
12 | import qualified Data.ByteString.Char8 as B8 | ||
13 | import qualified Data.ByteString as BS | ||
14 | import qualified Data.ByteString.Lazy as BL | ||
15 | import Data.Char | ||
16 | import Numeric | ||
17 | import Data.Int | ||
18 | |||
19 | import Data.OpenPGP.Internal | ||
20 | import Data.OpenPGP.Util | ||
21 | import Data.OpenPGP.Util.Base | ||
22 | import Data.OpenPGP as OpenPGP | ||
23 | import Crypto.Cipher.SBox | ||
24 | import Data.OpenPGP.Util.Ed25519 (zeroExtend,zeroPad) | ||
25 | import qualified Crypto.PubKey.Curve25519 as Cv25519 | ||
26 | import Crypto.Error | ||
27 | import Crypto.Cipher.AES | ||
28 | import Crypto.Cipher.Types | ||
29 | import Data.OpenPGP.Util.DecryptSecretKey -- (withS2K, simpleUnCFB, Enciphered(..)) | ||
30 | |||
31 | import Crypto.JOSE.AESKW | ||
32 | |||
33 | oid_cv25519 = 0x2B060104019755010501 | ||
34 | |||
35 | getEphemeralKey :: OpenPGP.Packet -> Maybe ([(Char,MPI)],BL.ByteString) | ||
36 | getEphemeralKey AsymmetricSessionKeyPacket | ||
37 | { version = 3 | ||
38 | , key_algorithm = ECC | ||
39 | , encrypted_data = dta } = do | ||
40 | -- Algorithm-Specific Fields for ECDH encryption: | ||
41 | -- | ||
42 | -- * MPI of an EC point representing an ephemeral public key. | ||
43 | -- | ||
44 | -- * a one-octet size, followed by a symmetric key encoded using the | ||
45 | -- method described in Section 13.5. | ||
46 | (b,_,d) <- either (const Nothing) Just $ runGetOrFail getEllipticCurvePublicKey dta | ||
47 | (sz,m) <- BL.uncons b | ||
48 | guard $ BL.length m == fromIntegral sz | ||
49 | return (d,m) | ||
50 | getEphemeralKey _ = Nothing | ||
51 | |||
52 | -- The value "m" in the above formulas is derived from the session key | ||
53 | -- as follows. First, the session key is prefixed with a one-octet | ||
54 | -- algorithm identifier that specifies the symmetric encryption | ||
55 | -- algorithm used to encrypt the following Symmetrically Encrypted Data | ||
56 | -- Packet. Then a two-octet checksum is appended, which is equal to the | ||
57 | -- sum of the preceding session key octets, not including the algorithm | ||
58 | -- identifier, modulo 65536. This value is then encoded as described in | ||
59 | -- PKCS#1 block encoding EME-PKCS1-v1_5 in Section 7.2.1 of [RFC3447] to | ||
60 | -- form the "m" value used in the formulas above. See Section 14.1 of | ||
61 | -- this document for notes on OpenPGP's use of PKCS#1. | ||
62 | |||
63 | privateCv25519Key :: OpenPGP.Packet -> Maybe Cv25519.SecretKey | ||
64 | privateCv25519Key k@SecretKeyPacket { key_algorithm = ECC, symmetric_algorithm = Unencrypted } = do | ||
65 | guard $ oid_cv25519 == keyParam 'c' k | ||
66 | case Cv25519.secretKey $ zeroExtend 32 $ integerToLE (keyParam 'd' k) of | ||
67 | CryptoPassed cv25519sec -> Just cv25519sec | ||
68 | CryptoFailed err -> Nothing | ||
69 | |||
70 | hexify = map toUpper . hexString . BS.unpack | ||
71 | |||
72 | |||
73 | |||
74 | hexString :: [Word8] -> String | ||
75 | hexString = foldr (pad `oo` showHex) "" | ||
76 | where | ||
77 | pad s | odd $ length s = '0':s | ||
78 | | otherwise = s | ||
79 | |||
80 | oo :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c | ||
81 | oo = (.) . (.) | ||
82 | |||
83 | cv25519Key :: [(Char,MPI)] -> Maybe Cv25519.PublicKey | ||
84 | cv25519Key k = do | ||
85 | MPI flag <- lookup 'f' k | ||
86 | n <- case flag of | ||
87 | 0x40 -> zeroPad 32 . integerToBS . (\(MPI n)-> n) <$> lookup 'n' k | ||
88 | -- TODO: The following was based on Ed25519. Verify that it is correct for Cv25519. | ||
89 | _ -> do MPI y <- lookup 'y' k | ||
90 | MPI x <- lookup 'x' k | ||
91 | let ybs = zeroExtend 32 $ integerToLE y | ||
92 | lb = BS.last ybs | ||
93 | return $ if x < 0 then BS.take 31 ybs `BS.snoc` (lb .|. 0x80) | ||
94 | else BS.take 31 ybs `BS.snoc` (lb .&. 0x7F) | ||
95 | maybeCryptoError $ Cv25519.publicKey n | ||
96 | |||
97 | kdfParams :: OpenPGP.Packet -> (OpenPGP.HashAlgorithm, OpenPGP.SymmetricAlgorithm) | ||
98 | kdfParams k = toEnum *** toEnum $ divMod e 256 | ||
99 | where | ||
100 | e = 0x0FFFF .&. (fromIntegral $ keyParam 'e' k) | ||
101 | -- flen <- get :: Get Word8 -- always 3 (length of following bytes) | ||
102 | -- one <- get :: Get Word8 -- always 0x01 (reserved) | ||
103 | -- hashid <- get :: Get Word8 -- HashAlgorithm | ||
104 | -- algoid <- get :: Get Word8 -- SymmetricAlgorithm | ||
105 | |||
106 | data SomeKeyCipher = forall c. BlockCipher128 c => SomeKeyCipher c | ||
107 | |||
108 | someAES128 :: AES128 -> SomeKeyCipher | ||
109 | someAES192 :: AES192 -> SomeKeyCipher | ||
110 | someAES256 :: AES256 -> SomeKeyCipher | ||
111 | someAES128 = SomeKeyCipher | ||
112 | someAES192 = SomeKeyCipher | ||
113 | someAES256 = SomeKeyCipher | ||
114 | |||
115 | keyCipher :: OpenPGP.SymmetricAlgorithm -> BS.ByteString -> Maybe SomeKeyCipher | ||
116 | keyCipher OpenPGP.AES128 key = someAES128 <$> maybeCryptoError (cipherInit key) | ||
117 | keyCipher OpenPGP.AES192 key = someAES192 <$> maybeCryptoError (cipherInit key) | ||
118 | keyCipher OpenPGP.AES256 key = someAES256 <$> maybeCryptoError (cipherInit key) | ||
119 | keyCipher _ _ = Nothing | ||
120 | |||
121 | keyCipherSize OpenPGP.AES128 = cipherKeySize (undefined :: AES128) | ||
122 | keyCipherSize OpenPGP.AES192 = cipherKeySize (undefined :: AES192) | ||
123 | keyCipherSize OpenPGP.AES256 = cipherKeySize (undefined :: AES256) | ||
124 | |||
125 | |||
126 | kdfParamBytes :: OpenPGP.Packet -> BL.ByteString | ||
127 | kdfParamBytes k = BL.fromChunks | ||
128 | [ BL.toStrict $ encodeOID (MPI $ keyParam 'c' k) -- curve_OID_len || curve_OID | ||
129 | , BS.singleton $ fromIntegral $ fromEnum $ key_algorithm k -- public_key_alg_ID | ||
130 | , BL.toStrict $ encode (fromIntegral (keyParam 'e' k) :: Word32) -- 03 || 01 || KDF_hash_ID || KEK_alg_ID for AESKeyWrap | ||
131 | , B8.pack "Anonymous Sender " | ||
132 | , let Fingerprint fp = fingerprint k in fp | ||
133 | ] | ||
134 | |||
135 | -- The Concatenation Key Derivation Function (Approved Alternative 1) [SP800-56A] | ||
136 | kdf :: OpenPGP.HashAlgorithm -> Cv25519.DhSecret -> Int -> BL.ByteString -> Maybe BL.ByteString | ||
137 | kdf hsh z keybytelen otherinfo | ||
138 | | reps > 2^32 - 1 = Nothing | ||
139 | -- XXX: I don't understand /max_hash_inputlen/. | ||
140 | -- | ||
141 | -- max_hash_inputlen: an integer that indicates the maximum length (in | ||
142 | -- bits) of the bit string(s) input to the hash function. | ||
143 | -- | ||
144 | -- | 8 * (BS.length zo) > max_hash_inputlen - 32 = Nothing | ||
145 | | otherwise = Just derivedKeyingMaterial | ||
146 | where | ||
147 | keydatalen = 8 * fromIntegral keybytelen :: Int64 | ||
148 | hashlen = 8 * fromIntegral (hashLen hsh) :: Int64 | ||
149 | reps = fromIntegral $ (keydatalen + hashlen - 1) `div` hashlen | ||
150 | counter = 0x00000001 :: Word32 | ||
151 | zo = BL.fromStrict (BA.convert z) <> otherinfo | ||
152 | hashes = [ hashBySymbol hsh (encode (i::Word32) <> zo) | ||
153 | | i <- [1 .. reps] ] -- Compute Hash i = H(counter || Z || OtherInfo). | ||
154 | -- Let Hhash be set to Hash[reps] if (keydatalen / hashlen) is an integer; otherwise, let Hhash | ||
155 | -- be set to the (keydatalen mod hashlen) leftmost bits of Hash[reps]. | ||
156 | hhash = case keydatalen `mod` hashlen of | ||
157 | 0 -> last hashes | ||
158 | r -> BS.take (fromIntegral $ (r + 7) `div` 8) $ last hashes -- TODO: Zero out the 8 - (r `mod` 8) last bits? | ||
159 | derivedKeyingMaterial = BL.fromChunks $ init hashes ++ [ hhash ] | ||
160 | |||
161 | |||
162 | -- The input to the key wrapping method is the value "m" derived from | ||
163 | -- the session key, as described in Section 5.1, "Public-Key Encrypted | ||
164 | -- Session Key Packets (Tag 1)", except that the PKCS #1.5 padding step | ||
165 | -- is omitted. The result is padded using the method described in | ||
166 | -- [PKCS5] to the 8-byte granularity. For example, the following | ||
167 | -- AES-256 session key, in which 32 octets are denoted from k0 to k31, | ||
168 | -- is composed to form the following 40 octet sequence: | ||
169 | -- | ||
170 | -- 09 k0 k1 ... k31 c0 c1 05 05 05 05 05 | ||
171 | -- | ||
172 | -- The octets c0 and c1 above denote the checksum. This encoding allows | ||
173 | -- the sender to obfuscate the size of the symmetric encryption key used | ||
174 | -- to encrypt the data. For example, assuming that an AES algorithm is | ||
175 | -- used for the session key, the sender MAY use 21, 13, and 5 bytes of | ||
176 | -- padding for AES-128, AES-192, and AES-256, respectively, to provide | ||
177 | -- the same number of octets, 40 total, as an input to the key wrapping | ||
178 | -- method. | ||
179 | -- | ||
180 | -- From Section 5.1, "Public-Key Encrypted Session Key Packets (Tag 1)" | ||
181 | -- | ||
182 | -- The value "m" in the above formulas is derived from the session key | ||
183 | -- as follows. First, the session key is prefixed with a one-octet | ||
184 | -- algorithm identifier that specifies the symmetric encryption | ||
185 | -- algorithm used to encrypt the following Symmetrically Encrypted Data | ||
186 | -- Packet. Then a two-octet checksum is appended, which is equal to the | ||
187 | -- sum of the preceding session key octets, not including the algorithm | ||
188 | -- identifier, modulo 65536. This value is then encoded as described in | ||
189 | -- PKCS#1 block encoding EME-PKCS1-v1_5 in Section 7.2.1 of [RFC3447] to | ||
190 | -- form the "m" value used in the formulas above. See Section 14.1 of | ||
191 | -- this document for notes on OpenPGP's use of PKCS#1. | ||
192 | decodeEncryptedKey :: ByteString -> Maybe (SymmetricAlgorithm, ByteString) | ||
193 | decodeEncryptedKey m = do | ||
194 | (algb,ks) <- BS.uncons m | ||
195 | let alg = toEnum $ fromIntegral algb :: OpenPGP.SymmetricAlgorithm | ||
196 | sz <- case keyCipherSize alg of | ||
197 | KeySizeFixed n -> Just n | ||
198 | _ -> Nothing | ||
199 | let (key,macbs) = BS.splitAt sz ks | ||
200 | (macb,trail) = BS.splitAt 2 macbs | ||
201 | mac = decode $ BL.fromStrict macb :: Word16 | ||
202 | chk = sum $ map fromIntegral $ BS.unpack key | ||
203 | guard $ chk == mac | ||
204 | Just (alg, key) | ||
205 | |||
206 | decryptMessage :: Packet -- ^ local secret key (ecdh cv25519) | ||
207 | -> Packet -- ^ ephemeral remote public key (ecdh cv25519) and encrypted symmetric key. | ||
208 | -> Packet -- ^ symmetrically encrypted data packet | ||
209 | -> Maybe [Packet] | ||
210 | decryptMessage ecdhkey asym encdta = do | ||
211 | (pubk,m) <- getEphemeralKey asym | ||
212 | pub25519 <- cv25519Key pubk | ||
213 | sec25519 <- privateCv25519Key ecdhkey | ||
214 | let shared = Cv25519.dh pub25519 sec25519 | ||
215 | (hsh, alg) = kdfParams ecdhkey | ||
216 | miv = let sz = case keyCipherSize alg of | ||
217 | KeySizeFixed n -> n | ||
218 | KeySizeEnum ns -> head ns | ||
219 | KeySizeRange mn mx -> mn | ||
220 | in kdf hsh shared sz (kdfParamBytes ecdhkey) | ||
221 | (alg,k) <- do | ||
222 | iv <- BL.toStrict <$> miv | ||
223 | SomeKeyCipher c <- keyCipher alg iv | ||
224 | m' <- aesKeyUnwrap c (BL.toStrict m) :: Maybe BS.ByteString | ||
225 | decodeEncryptedKey m' | ||
226 | withS2K' alg Nothing (BL.fromStrict k) $ \cipher -> do | ||
227 | let blksize = blockSize cipher | ||
228 | b0 = simpleUnCFB cipher nullIV (encrypted_data encdta) | ||
229 | b1 = BL.drop (2 + fromIntegral blksize) b0 | ||
230 | (_,_, Message ps) <- either (const Nothing) Just $ decodeOrFail b1 | ||
231 | return ps | ||
diff --git a/Data/OpenPGP/Util/DecryptSecretKey.hs b/Data/OpenPGP/Util/DecryptSecretKey.hs index 1188f3e..a637b29 100644 --- a/Data/OpenPGP/Util/DecryptSecretKey.hs +++ b/Data/OpenPGP/Util/DecryptSecretKey.hs | |||
@@ -90,7 +90,7 @@ decryptSecretKey pass k@(OpenPGP.SecretKeyPacket { | |||
90 | | OpenPGP.s2k_useage k == 254 = (20, sha1 . toStrictBS) | 90 | | OpenPGP.s2k_useage k == 254 = (20, sha1 . toStrictBS) |
91 | | otherwise = (2, toStrictBS . encode . checksum . toStrictBS) | 91 | | otherwise = (2, toStrictBS . encode . checksum . toStrictBS) |
92 | -- Words16s are written as 2 bytes in big-endian (network) order | 92 | -- Words16s are written as 2 bytes in big-endian (network) order |
93 | decd = withS2K simpleUnCFB salgo s2k (toLazyBS pass) (EncipheredWithIV encd) | 93 | decd = withS2K simpleUnCFB salgo (Just s2k) (toLazyBS pass) (EncipheredWithIV encd) |
94 | 94 | ||
95 | #if defined(VERSION_cryptonite) | 95 | #if defined(VERSION_cryptonite) |
96 | sha1 x = Bytes.convert (hash x :: Digest SHA1) | 96 | sha1 x = Bytes.convert (hash x :: Digest SHA1) |
@@ -122,7 +122,7 @@ maybeGet g bs = unsafePerformIO $ | |||
122 | 122 | ||
123 | withS2K :: (forall k. (Vincent.BlockCipher k) => k -> Vincent.IV k -> LZ.ByteString -> LZ.ByteString) | 123 | withS2K :: (forall k. (Vincent.BlockCipher k) => k -> Vincent.IV k -> LZ.ByteString -> LZ.ByteString) |
124 | -> OpenPGP.SymmetricAlgorithm | 124 | -> OpenPGP.SymmetricAlgorithm |
125 | -> OpenPGP.S2K | 125 | -> Maybe OpenPGP.S2K |
126 | -> LZ.ByteString -> Enciphered -> LZ.ByteString | 126 | -> LZ.ByteString -> Enciphered -> LZ.ByteString |
127 | withS2K codec OpenPGP.AES128 s2k s = withIV $ codec (string2key s2k s :: Vincent.AES128) | 127 | withS2K codec OpenPGP.AES128 s2k s = withIV $ codec (string2key s2k s :: Vincent.AES128) |
128 | withS2K codec OpenPGP.AES192 s2k s = withIV $ codec (string2key s2k s :: Vincent.AES192) | 128 | withS2K codec OpenPGP.AES192 s2k s = withIV $ codec (string2key s2k s :: Vincent.AES192) |
@@ -131,7 +131,7 @@ withS2K codec OpenPGP.Blowfish s2k s = withIV $ codec (string2key s2k s :: Vince | |||
131 | withS2K codec OpenPGP.CAST5 s2k s = withIV $ codec (string2key s2k s :: ThomasToVincent CAST5_128) | 131 | withS2K codec OpenPGP.CAST5 s2k s = withIV $ codec (string2key s2k s :: ThomasToVincent CAST5_128) |
132 | withS2K codec algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.withS2K" | 132 | withS2K codec algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.withS2K" |
133 | 133 | ||
134 | withS2K' :: OpenPGP.SymmetricAlgorithm -> OpenPGP.S2K -> LZ.ByteString | 134 | withS2K' :: OpenPGP.SymmetricAlgorithm -> Maybe OpenPGP.S2K -> LZ.ByteString |
135 | -> (forall b. Vincent.BlockCipher b => b -> x) -> x | 135 | -> (forall b. Vincent.BlockCipher b => b -> x) -> x |
136 | withS2K' OpenPGP.AES128 s2k s f = f (string2key s2k s :: Vincent.AES128) | 136 | withS2K' OpenPGP.AES128 s2k s f = f (string2key s2k s :: Vincent.AES128) |
137 | withS2K' OpenPGP.AES192 s2k s f = f (string2key s2k s :: Vincent.AES192) | 137 | withS2K' OpenPGP.AES192 s2k s f = f (string2key s2k s :: Vincent.AES192) |
@@ -169,27 +169,16 @@ padThenUnpad k f s = dropPadEnd (f padded) | |||
169 | padAmount = blksize - (LZ.length s `mod` blksize) | 169 | padAmount = blksize - (LZ.length s `mod` blksize) |
170 | blksize = fromIntegral $ Vincent.blockSize k | 170 | blksize = fromIntegral $ Vincent.blockSize k |
171 | 171 | ||
172 | {- | 172 | string2key :: (Vincent.BlockCipher k) => Maybe OpenPGP.S2K -> LZ.ByteString -> k |
173 | Data/OpenPGP/Util/DecryptSecretKey.hs:172:20: | 173 | string2key ms2k s = cipher |
174 | Couldn't match expected type ‘k’ | ||
175 | with actual type ‘cryptonite-0.15:Crypto.Error.Types.CryptoFailable | ||
176 | cipher0’ | ||
177 | ‘k’ is a rigid type variable bound by | ||
178 | the type signature for | ||
179 | string2key :: Vincent.BlockCipher k => | ||
180 | OpenPGP.S2K -> LZ.ByteString -> k | ||
181 | at Data/OpenPGP/Util/DecryptSecretKey.hs:171:15 | ||
182 | -} | ||
183 | string2key :: (Vincent.BlockCipher k) => OpenPGP.S2K -> LZ.ByteString -> k | ||
184 | string2key s2k s = cipher | ||
185 | where | 174 | where |
186 | #if defined(VERSION_cryptonite) | 175 | #if defined(VERSION_cryptonite) |
187 | CryptoPassed cipher = Vincent.cipherInit k | 176 | CryptoPassed cipher = Vincent.cipherInit k |
188 | k = toStrictBS $ LZ.take ksize $ OpenPGP.string2key hashBySymbol s2k s | 177 | k = toStrictBS $ LZ.take ksize $ maybe s (\s2k -> OpenPGP.string2key hashBySymbol s2k s) ms2k |
189 | #else | 178 | #else |
190 | cipher = Vincent.cipherInit k | 179 | cipher = Vincent.cipherInit k |
191 | Right k = Vincent.makeKey $ toStrictBS $ | 180 | Right k = Vincent.makeKey $ toStrictBS $ |
192 | LZ.take ksize $ OpenPGP.string2key hashBySymbol s2k s | 181 | LZ.take ksize $ maybe s (\s2k -> OpenPGP.string2key hashBySymbol s2k s) ms2k |
193 | #endif | 182 | #endif |
194 | ksize = case Vincent.cipherKeySize cipher of | 183 | ksize = case Vincent.cipherKeySize cipher of |
195 | Vincent.KeySizeFixed n -> fromIntegral n | 184 | Vincent.KeySizeFixed n -> fromIntegral n |
@@ -217,7 +206,7 @@ encryptSecretKey passphrase s2k salgo plain = do | |||
217 | maybeToList $ lookup f (OpenPGP.key plain) | 206 | maybeToList $ lookup f (OpenPGP.key plain) |
218 | chk = LZ.fromChunks [ chkF material ] | 207 | chk = LZ.fromChunks [ chkF material ] |
219 | decd = LZ.append material chk | 208 | decd = LZ.append material chk |
220 | encd g = fst $ withS2K' salgo s2k (toLazyBS passphrase) (simpleCFB g) decd | 209 | encd g = fst $ withS2K' salgo (Just s2k) (toLazyBS passphrase) (simpleCFB g) decd |
221 | 210 | ||
222 | -- If the string-to-key usage octet is zero or 255, then a two-octet | 211 | -- If the string-to-key usage octet is zero or 255, then a two-octet |
223 | -- checksum of the plaintext of the algorithm-specific portion (sum | 212 | -- checksum of the plaintext of the algorithm-specific portion (sum |
diff --git a/Data/OpenPGP/Util/Ed25519.hs b/Data/OpenPGP/Util/Ed25519.hs index 7504e7e..67eeba3 100644 --- a/Data/OpenPGP/Util/Ed25519.hs +++ b/Data/OpenPGP/Util/Ed25519.hs | |||
@@ -43,8 +43,8 @@ ed25519Key k = | |||
43 | x = keyParam 'x' k | 43 | x = keyParam 'x' k |
44 | ybs = zeroExtend 32 $ integerToLE y | 44 | ybs = zeroExtend 32 $ integerToLE y |
45 | lb = BS.last ybs | 45 | lb = BS.last ybs |
46 | in if x < 0 then BS.take 31 ybs `BS.snoc` (lb .|. 1) | 46 | in if x < 0 then BS.take 31 ybs `BS.snoc` (lb .|. 0x80) |
47 | else BS.take 31 ybs `BS.snoc` (lb .&. 0xFE) | 47 | else BS.take 31 ybs `BS.snoc` (lb .&. 0x7F) |
48 | in case Ed25519.publicKey n of | 48 | in case Ed25519.publicKey n of |
49 | CryptoPassed ed25519 -> Just ed25519 | 49 | CryptoPassed ed25519 -> Just ed25519 |
50 | CryptoFailed _ -> Nothing | 50 | CryptoFailed _ -> Nothing |
diff --git a/openpgp-util.cabal b/openpgp-util.cabal index b0d7f53..ae8d373 100644 --- a/openpgp-util.cabal +++ b/openpgp-util.cabal | |||
@@ -122,12 +122,14 @@ Flag cryptonite | |||
122 | 122 | ||
123 | library | 123 | library |
124 | exposed-modules: | 124 | exposed-modules: |
125 | Data.OpenPGP.Util | ||
126 | Data.OpenPGP | 125 | Data.OpenPGP |
126 | Data.OpenPGP.Util | ||
127 | Data.OpenPGP.Util.Cv25519 | ||
127 | Crypto.Cipher.Feistel | 128 | Crypto.Cipher.Feistel |
128 | Crypto.Cipher.Cast5 | 129 | Crypto.Cipher.Cast5 |
129 | Crypto.Cipher.SBox | 130 | Crypto.Cipher.SBox |
130 | Crypto.Cipher.ThomasToVincent | 131 | Crypto.Cipher.ThomasToVincent |
132 | Crypto.JOSE.AESKW | ||
131 | other-modules: | 133 | other-modules: |
132 | Data.OpenPGP.Internal | 134 | Data.OpenPGP.Internal |
133 | Data.OpenPGP.Util.Fingerprint | 135 | Data.OpenPGP.Util.Fingerprint |
@@ -140,6 +142,7 @@ library | |||
140 | build-depends: | 142 | build-depends: |
141 | base == 4.*, | 143 | base == 4.*, |
142 | transformers, | 144 | transformers, |
145 | mtl, | ||
143 | bytestring, | 146 | bytestring, |
144 | binary >= 0.5.1.0, | 147 | binary >= 0.5.1.0, |
145 | utf8-string, | 148 | utf8-string, |
diff --git a/tests/test-cv25519.hs b/tests/test-cv25519.hs new file mode 100644 index 0000000..faf2573 --- /dev/null +++ b/tests/test-cv25519.hs | |||
@@ -0,0 +1,110 @@ | |||
1 | {-# LANGUAGE QuasiQuotes #-} | ||
2 | {-# LANGUAGE ExistentialQuantification #-} | ||
3 | {-# LANGUAGE OverloadedStrings #-} | ||
4 | |||
5 | import Control.Arrow | ||
6 | import Control.Monad | ||
7 | import Data.Binary | ||
8 | import Data.Binary.Get | ||
9 | import Data.ByteString (ByteString) | ||
10 | import Data.Bits | ||
11 | import qualified Data.ByteArray as BA | ||
12 | import qualified Data.ByteString.Char8 as B8 | ||
13 | import qualified Data.ByteString as BS | ||
14 | import qualified Data.ByteString.Lazy as BL | ||
15 | import Data.Char | ||
16 | import Text.Show.Pretty | ||
17 | import Numeric | ||
18 | import Data.Int | ||
19 | |||
20 | import Data.OpenPGP.Internal | ||
21 | import Data.OpenPGP.Util | ||
22 | import Data.OpenPGP.Util.Base | ||
23 | import Data.OpenPGP as OpenPGP | ||
24 | import Crypto.Cipher.SBox | ||
25 | import Data.OpenPGP.Util.Ed25519 (zeroExtend,zeroPad) | ||
26 | import qualified Crypto.PubKey.Curve25519 as Cv25519 | ||
27 | import Crypto.Error | ||
28 | import Crypto.Cipher.AES | ||
29 | import Crypto.Cipher.Types | ||
30 | import Data.OpenPGP.Util.DecryptSecretKey -- (withS2K, simpleUnCFB, Enciphered(..)) | ||
31 | |||
32 | import Data.OpenPGP.Util.Cv25519 | ||
33 | |||
34 | msg :: ByteString | ||
35 | msg = [bytes| | ||
36 | 84 5e 03 c2 69 bf c6 b5 ad 64 55 12 01 07 40 ed | ||
37 | 3a 49 32 b5 2b 4e f5 c8 b1 3d 25 8c 73 c6 bc f3 | ||
38 | 16 c2 4d ed 23 3c ef ac 01 df ff ea 8a 38 75 30 | ||
39 | ab 5e d2 7b eb 5c 1f 7d 65 9d 7b 84 b4 5e fb b9 | ||
40 | 43 81 29 d4 e9 ba 0d 08 34 be 95 40 9d 57 0c 85 | ||
41 | 0d 94 4c fe 42 21 a0 23 d9 70 3d a2 03 ec 0a d1 | ||
42 | |||
43 | d2 70 01 95 06 62 3d 1b 66 ba 5b 67 b8 a7 2a b7 | ||
44 | 86 0b 28 94 18 c5 3b ef fc 2f e2 5d 87 78 80 9e | ||
45 | 89 dc c7 e7 87 14 b8 bc 8c 9f 93 2f bb 2b c3 7c | ||
46 | 3c da c5 32 32 bf 58 3f fa 7f 1c 53 b7 14 63 b7 | ||
47 | 1c 2e d6 29 c8 8e 15 a8 48 6d 97 a9 35 49 21 c7 | ||
48 | 73 20 b4 00 4e db 80 04 30 4a df 59 77 79 22 aa | ||
49 | 0c 7c 08 a1 d0 d6 a7 30 9d e9 59 8d 2d 9f e8 c4 | ||
50 | 0c 2b | ||
51 | |] | ||
52 | |||
53 | keyring :: ByteString | ||
54 | keyring = | ||
55 | [bytes| | ||
56 | 94 58 04 5d c8 7f 7e 16 09 2b 06 01 04 01 da 47 | ||
57 | 0f 01 01 07 40 bb a7 dc 2a e0 b0 ef 05 d1 69 07 | ||
58 | 31 b3 91 0d c9 69 38 6f 3f 97 e6 19 45 cb 6c 76 | ||
59 | 3b 15 29 f5 e5 00 01 00 96 eb 2a 5c d5 5b 65 25 | ||
60 | e9 dd ed b8 58 1c e9 1e 75 f2 26 92 9d 9f 35 d7 | ||
61 | 35 a7 65 e5 41 44 f5 f5 11 a4 b4 18 47 75 79 20 | ||
62 | 54 2e 20 3c 67 75 79 40 65 78 61 6d 70 6c 65 2e | ||
63 | 63 6f 6d 3e 88 90 04 13 16 08 00 38 16 21 04 00 | ||
64 | 7d a7 19 91 02 5a 09 bd da 46 78 38 ed ab 61 d8 | ||
65 | 66 c7 02 05 02 5d c8 7f 7e 02 1b 03 05 0b 09 08 | ||
66 | 07 02 06 15 0a 09 08 0b 02 04 16 02 03 01 02 1e | ||
67 | 01 02 17 80 00 0a 09 10 38 ed ab 61 d8 66 c7 02 | ||
68 | 3e d4 00 ff 7a 88 a3 af cd 96 bd 46 b4 31 76 3c | ||
69 | 40 35 1c ef 0b 0b 1d e2 66 03 7e 22 4c 32 34 f7 | ||
70 | db dd 20 98 00 ff 55 20 65 55 ed 70 a4 a0 03 58 | ||
71 | c3 1c 0a 12 63 b5 5c 3f f8 18 de 62 c8 0b e7 85 | ||
72 | 37 ee 8c 7a 2a 0d 9c 5d 04 5d c8 7f 7e 12 0a 2b | ||
73 | 06 01 04 01 97 55 01 05 01 01 07 40 71 8a c9 e8 | ||
74 | 0d cf 0d d0 16 10 c2 26 50 f1 f4 1c 49 b4 af 4d | ||
75 | aa 0e 06 2b 35 8c 1e 86 79 8c 25 31 03 01 08 07 | ||
76 | 00 00 ff 68 a2 f7 b1 31 2c 6a 09 82 f2 55 a2 44 | ||
77 | cb d4 a1 0d 62 ef f0 77 18 68 d9 6c 86 c1 b2 c7 | ||
78 | e1 4d 40 12 28 88 78 04 18 16 08 00 20 16 21 04 | ||
79 | 00 7d a7 19 91 02 5a 09 bd da 46 78 38 ed ab 61 | ||
80 | d8 66 c7 02 05 02 5d c8 7f 7e 02 1b 0c 00 0a 09 | ||
81 | 10 38 ed ab 61 d8 66 c7 02 86 10 01 00 83 99 5d | ||
82 | 74 90 f5 4a b5 74 bc 07 77 7a f7 25 14 3e 5e bf | ||
83 | ae 52 99 0c 01 05 0b 4b 57 ee 95 02 1b 01 00 eb | ||
84 | db e4 27 95 f9 a4 4f bc f0 ce cc 44 33 90 ab 42 | ||
85 | 0f aa ca 06 89 ce 48 f1 85 27 62 05 73 e3 03 | ||
86 | |] | ||
87 | |||
88 | |||
89 | expected_result = CompressedDataPacket | ||
90 | { compression_algorithm = ZLIB | ||
91 | , message = Message [ LiteralDataPacket | ||
92 | { format = 'b' | ||
93 | , filename = "secret-message.txt" | ||
94 | , timestamp = 1573421489 | ||
95 | , content = "This is a secret that will be encrypted.\n"} | ||
96 | ] | ||
97 | } | ||
98 | |||
99 | main = do | ||
100 | let Message [asym,encdta] = decode (BL.fromStrict msg) | ||
101 | Message | ||
102 | [ master -- ---Secret 007DA71991025A09BDDA467838EDAB61D866C702 Ed25519 | ||
103 | , uid -- UserID "Guy T. <guy@example.com>" | ||
104 | , uidsig -- Signature ^ signed: 38EDAB61D866C702 ["vouch-sign"] | ||
105 | , ecdhkey -- SecretKey 8CF3B7D9CDCA47086F3C509AC269BFC6B5AD6455 ECC | ||
106 | , ecdhsig -- Signature ^ signed: 38EDAB61D866C702 ["encrypt"] | ||
107 | ] = decode (BL.fromStrict keyring) | ||
108 | m = decryptMessage ecdhkey asym encdta | ||
109 | print m | ||
110 | putStrLn $ "decrypt cv25519: " ++ show (m == Just [expected_result]) | ||