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 /Data | |
parent | 76bf7e08bccbb1a3a689068016b8a9c29d1e060e (diff) |
Cv25519 encryption.
Diffstat (limited to 'Data')
-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 |
4 files changed, 287 insertions, 52 deletions
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 |