summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2019-10-13 00:02:36 -0400
committerJoe Crayne <joe@jerkface.net>2019-11-09 15:58:34 -0500
commit36601fd1d12270d1215f55e43fc8c075815fb638 (patch)
tree5632e708157340387ebcac8af50a6e364fdb487a
parentbb3a9e181638fa881e2bcd8425f10cfb365533f5 (diff)
Ed25519 support.
-rw-r--r--Data/OpenPGP.hs75
-rw-r--r--Data/OpenPGP/Internal.hs42
-rw-r--r--Data/OpenPGP/Util/Base.hs1
-rw-r--r--Data/OpenPGP/Util/Ed25519.hs51
-rw-r--r--Data/OpenPGP/Util/Verify.hs4
-rw-r--r--openpgp-util.cabal1
6 files changed, 136 insertions, 38 deletions
diff --git a/Data/OpenPGP.hs b/Data/OpenPGP.hs
index 35298c8..4627d4e 100644
--- a/Data/OpenPGP.hs
+++ b/Data/OpenPGP.hs
@@ -64,7 +64,9 @@ module Data.OpenPGP (
64 signature_issuer, 64 signature_issuer,
65 public_key_fields, 65 public_key_fields,
66 secret_key_fields, 66 secret_key_fields,
67 eccOID 67 eccOID,
68 encode_public_key_material,
69 decode_public_key_material
68) where 70) where
69 71
70import Control.Applicative 72import Control.Applicative
@@ -80,6 +82,7 @@ import Data.Maybe
80import Data.Monoid 82import Data.Monoid
81import Data.OpenPGP.Internal 83import Data.OpenPGP.Internal
82import Data.Word 84import Data.Word
85import GHC.Stack
83import Numeric 86import Numeric
84 87
85#ifdef CEREAL 88#ifdef CEREAL
@@ -345,8 +348,8 @@ public_key_fields RSA_S = public_key_fields RSA
345public_key_fields ELGAMAL = ['p', 'g', 'y'] 348public_key_fields ELGAMAL = ['p', 'g', 'y']
346public_key_fields DSA = ['p', 'q', 'g', 'y'] 349public_key_fields DSA = ['p', 'q', 'g', 'y']
347public_key_fields ECDSA = ['c','l','x', 'y', 'f'] 350public_key_fields ECDSA = ['c','l','x', 'y', 'f']
348public_key_fields Ed25519 = ['c','l','x', 'y', 'f'] 351public_key_fields Ed25519 = ['c','l','x', 'y', 'n', 'f']
349public_key_fields ECC = ['c','l','x', 'y', 'f'] 352public_key_fields ECC = ['c','l','x', 'y', 'n', 'f', 'e']
350public_key_fields alg = error ("Unknown key fields for "++show alg) -- Nothing in the spec. Maybe empty 353public_key_fields alg = error ("Unknown key fields for "++show alg) -- Nothing in the spec. Maybe empty
351 354
352-- http://tools.ietf.org/html/rfc4880#section-5.5.3 355-- http://tools.ietf.org/html/rfc4880#section-5.5.3
@@ -359,8 +362,10 @@ secret_key_fields DSA = ['x']
359secret_key_fields ECDSA = ['d'] 362secret_key_fields ECDSA = ['d']
360secret_key_fields alg = error ("Unkown secret fields for "++show alg) -- Nothing in the spec. Maybe empty 363secret_key_fields alg = error ("Unkown secret fields for "++show alg) -- Nothing in the spec. Maybe empty
361 364
362(!) :: (Eq k) => [(k,v)] -> k -> v 365(!) :: (HasCallStack, Show k, Eq k) => [(k,v)] -> k -> v
363(!) xs k = let Just x = lookup k xs in x 366(!) xs k = case lookup k xs of
367 Just v -> v
368 Nothing -> error ("Missing field "++show k++" at "++prettyCallStack callStack)
364 369
365-- Need this seperate for trailer calculation 370-- Need this seperate for trailer calculation
366signature_packet_start :: Packet -> B.ByteString 371signature_packet_start :: Packet -> B.ByteString
@@ -420,19 +425,28 @@ eccOID SecretKeyPacket { key = k } = lookup 'c' k >>= \(MPI oid) -> Just (snd $
420eccOID _ = Nothing 425eccOID _ = Nothing
421 426
422encode_public_key_material :: Packet -> [B.ByteString] 427encode_public_key_material :: Packet -> [B.ByteString]
423encode_public_key_material k | key_algorithm k `elem` [ECDSA,Ed25519] = do 428encode_public_key_material k | key_algorithm k `elem` [ECDSA,Ed25519,ECC] = do
424 -- http://tools.ietf.org/html/rfc6637 429 -- http://tools.ietf.org/html/rfc6637
425 c <- maybeToList $ lookup 'c' (key k) 430 c <- maybeToList $ lookup 'c' (key k)
426 MPI l <- maybeToList $ lookup 'l' (key k) 431 MPI l <- maybeToList $ lookup 'l' (key k)
427 MPI x <- maybeToList $ lookup 'x' (key k)
428 MPI y <- maybeToList $ lookup 'y' (key k)
429 MPI flag <- maybeToList $ lookup 'f' (key k) 432 MPI flag <- maybeToList $ lookup 'f' (key k)
430 let (bitlen,oid) = B.splitAt 2 (encode c) 433 let (bitlen,oid) = B.splitAt 2 (encode c)
431 len16 = decode bitlen :: Word16 434 len16 = decode bitlen :: Word16
432 (fullbytes,rembits) = len16 `quotRem` 8 435 (fullbytes,rembits) = len16 `quotRem` 8
433 len8 = fromIntegral (fullbytes + if rembits/=0 then 1 else 0) :: Word8 436 len8 = fromIntegral (fullbytes + if rembits/=0 then 1 else 0) :: Word8
434 xy = flag*(4^l) + x*(2^l) + y -- flag could be 0x04 or 0x40 437 eccstuff = case lookup 'e' (key k) of
435 [ len8 `B.cons` oid, encode (MPI xy) ] 438 Just stuff -> encode stuff
439 Nothing -> B.empty
440 case flag of
441 0x40 -> do
442 MPI n <- maybeToList $ lookup 'n' (key k)
443 let xy = flag*(4^l) + n
444 [ len8 `B.cons` oid, encode (MPI xy), eccstuff ]
445 _ -> do
446 MPI x <- maybeToList $ lookup 'x' (key k)
447 MPI y <- maybeToList $ lookup 'y' (key k)
448 let xy = flag*(4^l) + x*(2^l) + y
449 [ len8 `B.cons` oid, encode (MPI xy), eccstuff ]
436encode_public_key_material k = map (encode . (key k !)) (public_key_fields $ key_algorithm k) 450encode_public_key_material k = map (encode . (key k !)) (public_key_fields $ key_algorithm k)
437 451
438decode_public_key_material :: KeyAlgorithm -> Get [(Char,MPI)] 452decode_public_key_material :: KeyAlgorithm -> Get [(Char,MPI)]
@@ -442,30 +456,42 @@ decode_public_key_material algorithm | algorithm `elem` [ECDSA, Ed25519] = do
442 oidbytes <- getSomeByteString (fromIntegral oidlen) 456 oidbytes <- getSomeByteString (fromIntegral oidlen)
443 let mpiFromBytes bytes = MPI (getBigNum $ B.toStrict bytes) 457 let mpiFromBytes bytes = MPI (getBigNum $ B.toStrict bytes)
444 oid = mpiFromBytes oidbytes 458 oid = mpiFromBytes oidbytes
445 MPI xy <- get 459 MPI fxy <- get
446 let integerBytesize i = fromIntegral $ LZ.length (encode (MPI i)) - 2 460 let integerBytesize i = fromIntegral $ LZ.length (encode (MPI i)) - 2
447 width = ( integerBytesize xy - 1 ) `div` 2 461 width = ( integerBytesize fxy - 1 ) `div` 2
448 (fx,y) = xy `quotRem` (256^width)
449 (flag,x) = fx `quotRem` (256^width)
450 l = width*8 462 l = width*8
451 return [('c',oid), ('l',MPI l), ('x',MPI x), ('y',MPI y), ('f',MPI flag)] 463 (flag,xy) = fxy `quotRem` (256^(2*width))
464 return $ case flag of
465 0x40 -> [('c',oid), ('l',MPI l), ('n',MPI xy), ('f',MPI flag)]
466 _ -> let (x,y) = xy `quotRem` (256^width)
467 -- (fx,y) = xy `quotRem` (256^width)
468 -- (flag,x) = fx `quotRem` (256^width)
469 in [('c',oid), ('l',MPI l), ('x',MPI x), ('y',MPI y), ('f',MPI flag)]
452decode_public_key_material ECC = do 470decode_public_key_material ECC = do
453 -- http://tools.ietf.org/html/rfc6637 (9) Algorithm-Specific Fields for ECDH keys: 471 -- http://tools.ietf.org/html/rfc6637 (9) Algorithm-Specific Fields for ECDH keys:
454 oidlen <- get :: Get Word8 472 oidlen <- get :: Get Word8
455 oidbytes <- getSomeByteString (fromIntegral oidlen) 473 oidbytes <- getSomeByteString (fromIntegral oidlen)
456 let mpiFromBytes bytes = MPI (getBigNum $ B.toStrict bytes) 474 let mpiFromBytes bytes = MPI (getBigNum $ B.toStrict bytes)
457 oid = mpiFromBytes oidbytes 475 oid = mpiFromBytes oidbytes
458 MPI xy <- get 476 MPI fxy <- get
459 flen <- get :: Get Word8 477 eccstuff <- get :: Get Word32
460 one <- get :: Get Word8 478 {- eccstuff is 4 one-byte fields:
461 hashid <- get :: Get Word8 479 flen <- get :: Get Word8
462 algoid <- get :: Get Word8 480 one <- get :: Get Word8 -- always 0x01
481 hashid <- get :: Get Word8
482 algoid <- get :: Get Word8
483 -}
463 let integerBytesize i = fromIntegral $ LZ.length (encode (MPI i)) - 2 484 let integerBytesize i = fromIntegral $ LZ.length (encode (MPI i)) - 2
464 width = ( integerBytesize xy - 1 ) `div` 2 485 width = ( integerBytesize fxy - 1 ) `div` 2
465 (fx,y) = xy `quotRem` (256^width)
466 (flag,x) = fx `quotRem` (256^width)
467 l = width*8 486 l = width*8
468 return [('c',oid), ('l',MPI l), ('x',MPI x), ('y',MPI y), ('f',MPI flag)] 487 (flag,xy) = fxy `quotRem` (256^(2*width))
488 result = case flag of
489 0x40 -> [('c',oid), ('l',MPI l), ('n',MPI xy), ('f',MPI flag)]
490 _ -> let (x,y) = xy `quotRem` (256^width)
491 -- (fx,y) = xy `quotRem` (256^width)
492 -- (flag,x) = fx `quotRem` (256^width)
493 in [('c',oid), ('l',MPI l), ('x',MPI x), ('y',MPI y), ('f',MPI flag)]
494 return $ result ++ [('e',MPI (fromIntegral eccstuff))]
469decode_public_key_material algorithm = mapM (\f -> fmap ((,)f) get) (public_key_fields algorithm) 495decode_public_key_material algorithm = mapM (\f -> fmap ((,)f) get) (public_key_fields algorithm)
470 496
471put_packet :: Packet -> (B.ByteString, Word8) 497put_packet :: Packet -> (B.ByteString, Word8)
@@ -1130,6 +1156,7 @@ data SignatureSubpacket =
1130 hash :: B.ByteString 1156 hash :: B.ByteString
1131 } | 1157 } |
1132 EmbeddedSignaturePacket Packet | 1158 EmbeddedSignaturePacket Packet |
1159 -- TODO: IssuerFingerprintPacket (tag=33)
1133 UnsupportedSignatureSubpacket Word8 B.ByteString 1160 UnsupportedSignatureSubpacket Word8 B.ByteString
1134 deriving (Show, Read, Eq) 1161 deriving (Show, Read, Eq)
1135 1162
diff --git a/Data/OpenPGP/Internal.hs b/Data/OpenPGP/Internal.hs
index 175a62e..f9a8803 100644
--- a/Data/OpenPGP/Internal.hs
+++ b/Data/OpenPGP/Internal.hs
@@ -16,20 +16,20 @@ import System.IO.Unsafe
16 16
17 17
18decode_s2k_count :: Word8 -> Word32 18decode_s2k_count :: Word8 -> Word32
19decode_s2k_count c = (16 + (fromIntegral c .&. 15)) `shiftL` 19decode_s2k_count c = shiftL (16 + (fromIntegral c .&. 15))
20 ((fromIntegral c `shiftR` 4) + 6) 20 ((fromIntegral c `shiftR` 4) + 6)
21 21
22encode_s2k_count :: Word32 -> Word8 22encode_s2k_count :: Word32 -> Word8
23encode_s2k_count iterations 23encode_s2k_count iterations
24 | iterations >= 65011712 = 255 24 | iterations >= 65011712 = 255
25 | decode_s2k_count result < iterations = result+1 25 | decode_s2k_count result < iterations = result+1
26 | otherwise = result 26 | otherwise = result
27 where 27 where
28 result = fromIntegral $ (fromIntegral c `shiftL` 4) .|. (count - 16) 28 result = fromIntegral $ (fromIntegral c `shiftL` 4) .|. (count - 16)
29 (count, c) = encode_s2k_count' (iterations `shiftR` 6) (0::Word8) 29 (count, c) = encode_s2k_count' (iterations `shiftR` 6) (0::Word8)
30 encode_s2k_count' count c 30 encode_s2k_count' count c
31 | count < 32 = (count, c) 31 | count < 32 = (count, c)
32 | otherwise = encode_s2k_count' (count `shiftR` 1) (c+1) 32 | otherwise = encode_s2k_count' (count `shiftR` 1) (c+1)
33 33
34{- 34{-
35getBigNum :: BS.ByteString -> Integer 35getBigNum :: BS.ByteString -> Integer
@@ -70,8 +70,26 @@ putBigNum i = ( (fromIntegral (BS.length bytes) - 1) * 8 + sigBit
70 sigBit = fromIntegral $ 8 - countLeadingZeros (BS.index bytes 0) 70 sigBit = fromIntegral $ 8 - countLeadingZeros (BS.index bytes 0)
71 bytes = integerToBS i 71 bytes = integerToBS i
72 72
73-- big-endian
73integerToBS :: Integer -> BS.ByteString 74integerToBS :: Integer -> BS.ByteString
74integerToBS i = BS.unsafeCreate (I# (word2Int# (sizeInBaseInteger i 256#))) $ \ptr -> do 75integerToBS i = BS.unsafeCreate (I# (word2Int# (sizeInBaseInteger i 256#))) $ \ptr -> do
75 let Ptr addr = ptr 76 let Ptr addr = ptr
76 cnt <- exportIntegerToAddr i addr 1# 77 cnt <- exportIntegerToAddr i addr 1# -- 1# for big-endian (use 0# for little-endian)
77 return () 78 return ()
79
80{-
81getBigNumLE :: BS.ByteString -> Integer
82getBigNumLE bytes = unsafeDupablePerformIO $
83 let (fptr,offset,len) = BS.toForeignPtr bytes
84 in withForeignPtr fptr $ \ptr -> do
85 let Ptr addr = ptr `plusPtr` offset :: Ptr Word64
86 I# n = len
87 importIntegerFromAddr addr (int2Word# n) 0#
88
89-- little-endian
90integerToLE :: Integer -> BS.ByteString
91integerToLE i = BS.unsafeCreate (I# (word2Int# (sizeInBaseInteger i 256#))) $ \ptr -> do
92 let Ptr addr = ptr
93 cnt <- exportIntegerToAddr i addr 0#
94 return ()
95-}
diff --git a/Data/OpenPGP/Util/Base.hs b/Data/OpenPGP/Util/Base.hs
index 5b3e159..0c888ca 100644
--- a/Data/OpenPGP/Util/Base.hs
+++ b/Data/OpenPGP/Util/Base.hs
@@ -85,7 +85,6 @@ find_key :: OpenPGP.Message -> String -> Maybe OpenPGP.Packet
85find_key = OpenPGP.find_key (show . fingerprint) 85find_key = OpenPGP.find_key (show . fingerprint)
86 86
87 87
88
89keyParam :: Char -> OpenPGP.Packet -> Integer 88keyParam :: Char -> OpenPGP.Packet -> Integer
90keyParam c k = fromJustMPI $ lookup c (OpenPGP.key k) 89keyParam c k = fromJustMPI $ lookup c (OpenPGP.key k)
91 where 90 where
diff --git a/Data/OpenPGP/Util/Ed25519.hs b/Data/OpenPGP/Util/Ed25519.hs
new file mode 100644
index 0000000..ed277c8
--- /dev/null
+++ b/Data/OpenPGP/Util/Ed25519.hs
@@ -0,0 +1,51 @@
1module Data.OpenPGP.Util.Ed25519 where
2
3import Crypto.Error
4import qualified Crypto.PubKey.Ed25519 as Ed25519
5import Data.OpenPGP.Internal -- (integerToBS,integerToLE,getBigNumLE)
6import qualified Data.OpenPGP as OpenPGP
7import Crypto.ECC.Edwards25519
8
9import qualified Data.ByteArray as BA
10import Control.Monad
11import qualified Data.ByteString as BS
12import qualified Data.ByteString.Lazy as BL
13import Data.List
14import Data.Int
15import Data.Word
16import Data.OpenPGP.Util.Base
17
18import Text.Printf
19import Numeric
20import Data.Char
21import System.IO
22
23import Foreign.Ptr
24import System.IO.Unsafe
25
26import Crypto.Cipher.SBox
27
28ed25519Key :: OpenPGP.Packet -> Maybe Ed25519.PublicKey
29ed25519Key k = case Ed25519.publicKey $ integerToBS $ keyParam 'n' k of
30 CryptoPassed ed25519 -> Just ed25519
31 CryptoFailed err -> Nothing
32
33ed25519sig sig =
34 let [OpenPGP.MPI r,OpenPGP.MPI s] = OpenPGP.signature sig
35 -- rbs = BS.pack $ take 32 $ rbytes r ++ repeat 0
36 -- sbs = BS.pack $ take 32 $ rbytes s ++ repeat 0
37 rbs = let r' = integerToBS r in BS.replicate (32 - BS.length r') 0 <> r'
38 sbs = let s' = integerToBS s in BS.replicate (32 - BS.length s') 0 <> s'
39 in case Ed25519.signature (rbs <> sbs) of
40 CryptoPassed sig -> Just sig
41 CryptoFailed err -> Nothing
42
43ed25519Verify :: OpenPGP.Packet -> BS.ByteString -> OpenPGP.Packet -> Maybe Bool
44ed25519Verify sig over k = do
45 let hashbs = hashBySymbol (OpenPGP.hash_algorithm sig) $ BL.fromChunks [over]
46 guard $ 0x2B06010401DA470F01 == keyParam 'c' k -- Only Ed25519 curve.
47 k' <- ed25519Key k -- SecretKeyPacket ???
48 sig' <- ed25519sig sig
49 let result = Ed25519.verify k' hashbs sig'
50 Just result
51
diff --git a/Data/OpenPGP/Util/Verify.hs b/Data/OpenPGP/Util/Verify.hs
index fd83485..5eea260 100644
--- a/Data/OpenPGP/Util/Verify.hs
+++ b/Data/OpenPGP/Util/Verify.hs
@@ -19,6 +19,7 @@ import Crypto.PubKey.HashDescr
19#endif 19#endif
20 20
21import Data.OpenPGP.Util.Base 21import Data.OpenPGP.Util.Base
22import Data.OpenPGP.Util.Ed25519
22 23
23 24
24dsaKey :: OpenPGP.Packet -> Vincent.DSA.PublicKey 25dsaKey :: OpenPGP.Packet -> Vincent.DSA.PublicKey
@@ -43,6 +44,7 @@ verify ::
43verify keys over = 44verify keys over =
44 over {OpenPGP.signatures_over = mapMaybe (uncurry $ verifyOne keys) sigs} 45 over {OpenPGP.signatures_over = mapMaybe (uncurry $ verifyOne keys) sigs}
45 where 46 where
47 sigs :: [(OpenPGP.Packet,BS.ByteString)]
46 sigs = map (\s -> (s, toStrictBS $ encode over `LZ.append` OpenPGP.trailer s)) 48 sigs = map (\s -> (s, toStrictBS $ encode over `LZ.append` OpenPGP.trailer s))
47 (OpenPGP.signatures_over over) 49 (OpenPGP.signatures_over over)
48 50
@@ -52,6 +54,7 @@ verifyOne keys sig over = fmap (const sig) $ maybeKey >>= verification >>= guard
52 verification = case OpenPGP.key_algorithm sig of 54 verification = case OpenPGP.key_algorithm sig of
53 OpenPGP.DSA -> dsaVerify 55 OpenPGP.DSA -> dsaVerify
54 OpenPGP.ECDSA -> ecdsaVerify 56 OpenPGP.ECDSA -> ecdsaVerify
57 OpenPGP.Ed25519 -> ed25519Verify sig over
55 alg | alg `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> rsaVerify 58 alg | alg `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> rsaVerify
56 | otherwise -> const Nothing 59 | otherwise -> const Nothing
57 60
@@ -97,4 +100,3 @@ verifyOne keys sig over = fmap (const sig) $ maybeKey >>= verification >>= guard
97 hash_algo = OpenPGP.hash_algorithm sig 100 hash_algo = OpenPGP.hash_algorithm sig
98 maybeKey = OpenPGP.signature_issuer sig >>= find_key keys 101 maybeKey = OpenPGP.signature_issuer sig >>= find_key keys
99 -- in trace ("maybeKey="++show (fmap OpenPGP.key_algorithm r)) r 102 -- in trace ("maybeKey="++show (fmap OpenPGP.key_algorithm r)) r
100
diff --git a/openpgp-util.cabal b/openpgp-util.cabal
index 33b5ff6..b0d7f53 100644
--- a/openpgp-util.cabal
+++ b/openpgp-util.cabal
@@ -136,6 +136,7 @@ library
136 Data.OpenPGP.Util.Sign 136 Data.OpenPGP.Util.Sign
137 Data.OpenPGP.Util.Gen 137 Data.OpenPGP.Util.Gen
138 Data.OpenPGP.Util.DecryptSecretKey 138 Data.OpenPGP.Util.DecryptSecretKey
139 Data.OpenPGP.Util.Ed25519
139 build-depends: 140 build-depends:
140 base == 4.*, 141 base == 4.*,
141 transformers, 142 transformers,