summaryrefslogtreecommitdiff
path: root/Data/OpenPGP/Util/Cv25519.hs
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 /Data/OpenPGP/Util/Cv25519.hs
parent76bf7e08bccbb1a3a689068016b8a9c29d1e060e (diff)
Cv25519 encryption.
Diffstat (limited to 'Data/OpenPGP/Util/Cv25519.hs')
-rw-r--r--Data/OpenPGP/Util/Cv25519.hs231
1 files changed, 231 insertions, 0 deletions
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