summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2019-11-14 16:45:14 -0500
committerJoe Crayne <joe@jerkface.net>2019-11-14 16:45:14 -0500
commitb42c0d847a785487f3222b0d5360746d25d3209c (patch)
tree11ef85e3e4577eb047227f9938761bdac94a1309
parent76bf7e08bccbb1a3a689068016b8a9c29d1e060e (diff)
Cv25519 encryption.
-rw-r--r--Crypto/JOSE/AESKW.hs123
-rw-r--r--Data/OpenPGP.hs77
-rw-r--r--Data/OpenPGP/Util/Cv25519.hs231
-rw-r--r--Data/OpenPGP/Util/DecryptSecretKey.hs27
-rw-r--r--Data/OpenPGP/Util/Ed25519.hs4
-rw-r--r--openpgp-util.cabal5
-rw-r--r--tests/test-cv25519.hs110
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
19Advanced Encryption Standard (AES) Key Wrap Algorithm;
20<https://https://tools.ietf.org/html/rfc3394>.
21
22-}
23module Crypto.JOSE.AESKW
24 (
25 aesKeyWrap
26 , aesKeyUnwrap
27 ) where
28
29import Control.Monad.State
30import Crypto.Cipher.Types
31import Data.Bits (xor)
32import Data.ByteArray as BA hiding (replicate, xor)
33import Data.Memory.Endian (BE(..), toBE)
34import Data.Memory.PtrMethods (memCopy)
35import Data.Word (Word64)
36import Foreign.Ptr (Ptr, plusPtr)
37import Foreign.Storable (peek, peekElemOff, poke, pokeElemOff)
38import System.IO.Unsafe (unsafePerformIO)
39
40iv :: Word64
41iv = 0xA6A6A6A6A6A6A6A6
42
43aesKeyWrapStep
44 :: BlockCipher128 cipher
45 => cipher
46 -> Ptr Word64 -- ^ register
47 -> (Int, Int) -- ^ step (t) and offset (i)
48 -> StateT Word64 IO ()
49aesKeyWrapStep 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--
65aesKeyWrap
66 :: (ByteArrayAccess m, ByteArray c, BlockCipher128 cipher)
67 => cipher
68 -> m
69 -> c
70aesKeyWrap 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
81aesKeyUnwrapStep
82 :: BlockCipher128 cipher
83 => cipher
84 -> Ptr Word64 -- ^ register
85 -> (Int, Int) -- ^ step (t) and offset (i)
86 -> StateT Word64 IO ()
87aesKeyUnwrapStep 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--
107aesKeyUnwrap
108 :: (ByteArrayAccess c, ByteArray m, BlockCipher128 cipher)
109 => cipher
110 -> c
111 -> Maybe m
112aesKeyUnwrap 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
72import Control.Applicative 75import Control.Applicative
@@ -361,7 +364,8 @@ secret_key_fields ELGAMAL = ['x']
361secret_key_fields DSA = ['x'] 364secret_key_fields DSA = ['x']
362secret_key_fields ECDSA = ['d'] 365secret_key_fields ECDSA = ['d']
363secret_key_fields Ed25519 = ['d'] 366secret_key_fields Ed25519 = ['d']
364secret_key_fields alg = error ("Unkown secret fields for "++show alg) -- Nothing in the spec. Maybe empty 367secret_key_fields ECC = ['d']
368secret_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 $
425eccOID SecretKeyPacket { key = k } = lookup 'c' k >>= \(MPI oid) -> Just (snd $ putBigNum oid) 429eccOID SecretKeyPacket { key = k } = lookup 'c' k >>= \(MPI oid) -> Just (snd $ putBigNum oid)
426eccOID _ = Nothing 430eccOID _ = Nothing
427 431
432encodeOID :: MPI -> B.ByteString
433encodeOID 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
428encode_public_key_material :: Packet -> [B.ByteString] 440encode_public_key_material :: Packet -> [B.ByteString]
429encode_public_key_material k | key_algorithm k `elem` [ECDSA,Ed25519,ECC] = do 441encode_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 ]
451encode_public_key_material k = map (encode . (key k !)) (public_key_fields $ key_algorithm k) 460encode_public_key_material k = map (encode . (key k !)) (public_key_fields $ key_algorithm k)
452 461
453decode_public_key_material :: KeyAlgorithm -> Get [(Char,MPI)] 462getEllipticCurvePublicKey :: Get [(Char,MPI)]
454decode_public_key_material algorithm | algorithm `elem` [ECDSA, Ed25519] = do 463getEllipticCurvePublicKey = 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)]
471decode_public_key_material ECC = do 475
472 -- http://tools.ietf.org/html/rfc6637 (9) Algorithm-Specific Fields for ECDH keys: 476getOID :: Get MPI
477getOID = 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
484decode_public_key_material :: KeyAlgorithm -> Get [(Char,MPI)]
485decode_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
489decode_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))]
496decode_public_key_material algorithm = mapM (\f -> fmap ((,)f) get) (public_key_fields algorithm) 501decode_public_key_material algorithm = mapM (\f -> fmap ((,)f) get) (public_key_fields algorithm)
497 502
498put_packet :: Packet -> (B.ByteString, Word8) 503put_packet :: Packet -> (B.ByteString, Word8)
@@ -907,6 +912,16 @@ infiniHashes hsh s = LZ.fromChunks (hs 0)
907data HashAlgorithm = MD5 | SHA1 | RIPEMD160 | SHA256 | SHA384 | SHA512 | SHA224 | HashAlgorithm Word8 912data HashAlgorithm = MD5 | SHA1 | RIPEMD160 | SHA256 | SHA384 | SHA512 | SHA224 | HashAlgorithm Word8
908 deriving (Show, Read, Eq, Ord) 913 deriving (Show, Read, Eq, Ord)
909 914
915hashLen :: HashAlgorithm -> Int
916hashLen MD5 = 16
917hashLen SHA1 = 20
918hashLen RIPEMD160 = 20
919hashLen SHA256 = 32
920hashLen SHA384 = 48
921hashLen SHA512 = 64
922hashLen SHA224 = 28
923hashLen (HashAlgorithm _) = 0
924
910instance Enum HashAlgorithm where 925instance 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 #-}
3module Data.OpenPGP.Util.Cv25519 where
4
5import Control.Arrow
6import Control.Monad
7import Data.Binary
8import Data.Binary.Get
9import Data.ByteString (ByteString)
10import Data.Bits
11import qualified Data.ByteArray as BA
12import qualified Data.ByteString.Char8 as B8
13import qualified Data.ByteString as BS
14import qualified Data.ByteString.Lazy as BL
15import Data.Char
16import Numeric
17import Data.Int
18
19import Data.OpenPGP.Internal
20import Data.OpenPGP.Util
21import Data.OpenPGP.Util.Base
22import Data.OpenPGP as OpenPGP
23import Crypto.Cipher.SBox
24import Data.OpenPGP.Util.Ed25519 (zeroExtend,zeroPad)
25import qualified Crypto.PubKey.Curve25519 as Cv25519
26import Crypto.Error
27import Crypto.Cipher.AES
28import Crypto.Cipher.Types
29import Data.OpenPGP.Util.DecryptSecretKey -- (withS2K, simpleUnCFB, Enciphered(..))
30
31import Crypto.JOSE.AESKW
32
33oid_cv25519 = 0x2B060104019755010501
34
35getEphemeralKey :: OpenPGP.Packet -> Maybe ([(Char,MPI)],BL.ByteString)
36getEphemeralKey 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)
50getEphemeralKey _ = 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
63privateCv25519Key :: OpenPGP.Packet -> Maybe Cv25519.SecretKey
64privateCv25519Key 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
70hexify = map toUpper . hexString . BS.unpack
71
72
73
74hexString :: [Word8] -> String
75hexString = 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
83cv25519Key :: [(Char,MPI)] -> Maybe Cv25519.PublicKey
84cv25519Key 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
97kdfParams :: OpenPGP.Packet -> (OpenPGP.HashAlgorithm, OpenPGP.SymmetricAlgorithm)
98kdfParams 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
106data SomeKeyCipher = forall c. BlockCipher128 c => SomeKeyCipher c
107
108someAES128 :: AES128 -> SomeKeyCipher
109someAES192 :: AES192 -> SomeKeyCipher
110someAES256 :: AES256 -> SomeKeyCipher
111someAES128 = SomeKeyCipher
112someAES192 = SomeKeyCipher
113someAES256 = SomeKeyCipher
114
115keyCipher :: OpenPGP.SymmetricAlgorithm -> BS.ByteString -> Maybe SomeKeyCipher
116keyCipher OpenPGP.AES128 key = someAES128 <$> maybeCryptoError (cipherInit key)
117keyCipher OpenPGP.AES192 key = someAES192 <$> maybeCryptoError (cipherInit key)
118keyCipher OpenPGP.AES256 key = someAES256 <$> maybeCryptoError (cipherInit key)
119keyCipher _ _ = Nothing
120
121keyCipherSize OpenPGP.AES128 = cipherKeySize (undefined :: AES128)
122keyCipherSize OpenPGP.AES192 = cipherKeySize (undefined :: AES192)
123keyCipherSize OpenPGP.AES256 = cipherKeySize (undefined :: AES256)
124
125
126kdfParamBytes :: OpenPGP.Packet -> BL.ByteString
127kdfParamBytes 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]
136kdf :: OpenPGP.HashAlgorithm -> Cv25519.DhSecret -> Int -> BL.ByteString -> Maybe BL.ByteString
137kdf 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.
192decodeEncryptedKey :: ByteString -> Maybe (SymmetricAlgorithm, ByteString)
193decodeEncryptedKey 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
206decryptMessage :: 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]
210decryptMessage 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
123withS2K :: (forall k. (Vincent.BlockCipher k) => k -> Vincent.IV k -> LZ.ByteString -> LZ.ByteString) 123withS2K :: (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
127withS2K codec OpenPGP.AES128 s2k s = withIV $ codec (string2key s2k s :: Vincent.AES128) 127withS2K codec OpenPGP.AES128 s2k s = withIV $ codec (string2key s2k s :: Vincent.AES128)
128withS2K codec OpenPGP.AES192 s2k s = withIV $ codec (string2key s2k s :: Vincent.AES192) 128withS2K 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
131withS2K codec OpenPGP.CAST5 s2k s = withIV $ codec (string2key s2k s :: ThomasToVincent CAST5_128) 131withS2K codec OpenPGP.CAST5 s2k s = withIV $ codec (string2key s2k s :: ThomasToVincent CAST5_128)
132withS2K codec algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.withS2K" 132withS2K codec algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.withS2K"
133 133
134withS2K' :: OpenPGP.SymmetricAlgorithm -> OpenPGP.S2K -> LZ.ByteString 134withS2K' :: 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
136withS2K' OpenPGP.AES128 s2k s f = f (string2key s2k s :: Vincent.AES128) 136withS2K' OpenPGP.AES128 s2k s f = f (string2key s2k s :: Vincent.AES128)
137withS2K' OpenPGP.AES192 s2k s f = f (string2key s2k s :: Vincent.AES192) 137withS2K' 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{- 172string2key :: (Vincent.BlockCipher k) => Maybe OpenPGP.S2K -> LZ.ByteString -> k
173Data/OpenPGP/Util/DecryptSecretKey.hs:172:20: 173string2key 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-}
183string2key :: (Vincent.BlockCipher k) => OpenPGP.S2K -> LZ.ByteString -> k
184string2key 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
123library 123library
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
5import Control.Arrow
6import Control.Monad
7import Data.Binary
8import Data.Binary.Get
9import Data.ByteString (ByteString)
10import Data.Bits
11import qualified Data.ByteArray as BA
12import qualified Data.ByteString.Char8 as B8
13import qualified Data.ByteString as BS
14import qualified Data.ByteString.Lazy as BL
15import Data.Char
16import Text.Show.Pretty
17import Numeric
18import Data.Int
19
20import Data.OpenPGP.Internal
21import Data.OpenPGP.Util
22import Data.OpenPGP.Util.Base
23import Data.OpenPGP as OpenPGP
24import Crypto.Cipher.SBox
25import Data.OpenPGP.Util.Ed25519 (zeroExtend,zeroPad)
26import qualified Crypto.PubKey.Curve25519 as Cv25519
27import Crypto.Error
28import Crypto.Cipher.AES
29import Crypto.Cipher.Types
30import Data.OpenPGP.Util.DecryptSecretKey -- (withS2K, simpleUnCFB, Enciphered(..))
31
32import Data.OpenPGP.Util.Cv25519
33
34msg :: ByteString
35msg = [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
53keyring :: ByteString
54keyring =
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
89expected_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
99main = 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])