diff options
author | Joe Crayne <joe@jerkface.net> | 2019-10-13 00:02:36 -0400 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2019-11-09 15:58:34 -0500 |
commit | 36601fd1d12270d1215f55e43fc8c075815fb638 (patch) | |
tree | 5632e708157340387ebcac8af50a6e364fdb487a /Data | |
parent | bb3a9e181638fa881e2bcd8425f10cfb365533f5 (diff) |
Ed25519 support.
Diffstat (limited to 'Data')
-rw-r--r-- | Data/OpenPGP.hs | 75 | ||||
-rw-r--r-- | Data/OpenPGP/Internal.hs | 42 | ||||
-rw-r--r-- | Data/OpenPGP/Util/Base.hs | 1 | ||||
-rw-r--r-- | Data/OpenPGP/Util/Ed25519.hs | 51 | ||||
-rw-r--r-- | Data/OpenPGP/Util/Verify.hs | 4 |
5 files changed, 135 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 | ||
70 | import Control.Applicative | 72 | import Control.Applicative |
@@ -80,6 +82,7 @@ import Data.Maybe | |||
80 | import Data.Monoid | 82 | import Data.Monoid |
81 | import Data.OpenPGP.Internal | 83 | import Data.OpenPGP.Internal |
82 | import Data.Word | 84 | import Data.Word |
85 | import GHC.Stack | ||
83 | import Numeric | 86 | import Numeric |
84 | 87 | ||
85 | #ifdef CEREAL | 88 | #ifdef CEREAL |
@@ -345,8 +348,8 @@ public_key_fields RSA_S = public_key_fields RSA | |||
345 | public_key_fields ELGAMAL = ['p', 'g', 'y'] | 348 | public_key_fields ELGAMAL = ['p', 'g', 'y'] |
346 | public_key_fields DSA = ['p', 'q', 'g', 'y'] | 349 | public_key_fields DSA = ['p', 'q', 'g', 'y'] |
347 | public_key_fields ECDSA = ['c','l','x', 'y', 'f'] | 350 | public_key_fields ECDSA = ['c','l','x', 'y', 'f'] |
348 | public_key_fields Ed25519 = ['c','l','x', 'y', 'f'] | 351 | public_key_fields Ed25519 = ['c','l','x', 'y', 'n', 'f'] |
349 | public_key_fields ECC = ['c','l','x', 'y', 'f'] | 352 | public_key_fields ECC = ['c','l','x', 'y', 'n', 'f', 'e'] |
350 | public_key_fields alg = error ("Unknown key fields for "++show alg) -- Nothing in the spec. Maybe empty | 353 | public_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'] | |||
359 | secret_key_fields ECDSA = ['d'] | 362 | secret_key_fields ECDSA = ['d'] |
360 | secret_key_fields alg = error ("Unkown secret fields for "++show alg) -- Nothing in the spec. Maybe empty | 363 | secret_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 |
366 | signature_packet_start :: Packet -> B.ByteString | 371 | signature_packet_start :: Packet -> B.ByteString |
@@ -420,19 +425,28 @@ eccOID SecretKeyPacket { key = k } = lookup 'c' k >>= \(MPI oid) -> Just (snd $ | |||
420 | eccOID _ = Nothing | 425 | eccOID _ = Nothing |
421 | 426 | ||
422 | encode_public_key_material :: Packet -> [B.ByteString] | 427 | encode_public_key_material :: Packet -> [B.ByteString] |
423 | encode_public_key_material k | key_algorithm k `elem` [ECDSA,Ed25519] = do | 428 | encode_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 ] | ||
436 | encode_public_key_material k = map (encode . (key k !)) (public_key_fields $ key_algorithm k) | 450 | encode_public_key_material k = map (encode . (key k !)) (public_key_fields $ key_algorithm k) |
437 | 451 | ||
438 | decode_public_key_material :: KeyAlgorithm -> Get [(Char,MPI)] | 452 | decode_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)] | ||
452 | decode_public_key_material ECC = do | 470 | decode_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))] | ||
469 | decode_public_key_material algorithm = mapM (\f -> fmap ((,)f) get) (public_key_fields algorithm) | 495 | decode_public_key_material algorithm = mapM (\f -> fmap ((,)f) get) (public_key_fields algorithm) |
470 | 496 | ||
471 | put_packet :: Packet -> (B.ByteString, Word8) | 497 | put_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 | ||
18 | decode_s2k_count :: Word8 -> Word32 | 18 | decode_s2k_count :: Word8 -> Word32 |
19 | decode_s2k_count c = (16 + (fromIntegral c .&. 15)) `shiftL` | 19 | decode_s2k_count c = shiftL (16 + (fromIntegral c .&. 15)) |
20 | ((fromIntegral c `shiftR` 4) + 6) | 20 | ((fromIntegral c `shiftR` 4) + 6) |
21 | 21 | ||
22 | encode_s2k_count :: Word32 -> Word8 | 22 | encode_s2k_count :: Word32 -> Word8 |
23 | encode_s2k_count iterations | 23 | encode_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 | {- |
35 | getBigNum :: BS.ByteString -> Integer | 35 | getBigNum :: 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 | ||
73 | integerToBS :: Integer -> BS.ByteString | 74 | integerToBS :: Integer -> BS.ByteString |
74 | integerToBS i = BS.unsafeCreate (I# (word2Int# (sizeInBaseInteger i 256#))) $ \ptr -> do | 75 | integerToBS 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 | {- | ||
81 | getBigNumLE :: BS.ByteString -> Integer | ||
82 | getBigNumLE 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 | ||
90 | integerToLE :: Integer -> BS.ByteString | ||
91 | integerToLE 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 | |||
85 | find_key = OpenPGP.find_key (show . fingerprint) | 85 | find_key = OpenPGP.find_key (show . fingerprint) |
86 | 86 | ||
87 | 87 | ||
88 | |||
89 | keyParam :: Char -> OpenPGP.Packet -> Integer | 88 | keyParam :: Char -> OpenPGP.Packet -> Integer |
90 | keyParam c k = fromJustMPI $ lookup c (OpenPGP.key k) | 89 | keyParam 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 @@ | |||
1 | module Data.OpenPGP.Util.Ed25519 where | ||
2 | |||
3 | import Crypto.Error | ||
4 | import qualified Crypto.PubKey.Ed25519 as Ed25519 | ||
5 | import Data.OpenPGP.Internal -- (integerToBS,integerToLE,getBigNumLE) | ||
6 | import qualified Data.OpenPGP as OpenPGP | ||
7 | import Crypto.ECC.Edwards25519 | ||
8 | |||
9 | import qualified Data.ByteArray as BA | ||
10 | import Control.Monad | ||
11 | import qualified Data.ByteString as BS | ||
12 | import qualified Data.ByteString.Lazy as BL | ||
13 | import Data.List | ||
14 | import Data.Int | ||
15 | import Data.Word | ||
16 | import Data.OpenPGP.Util.Base | ||
17 | |||
18 | import Text.Printf | ||
19 | import Numeric | ||
20 | import Data.Char | ||
21 | import System.IO | ||
22 | |||
23 | import Foreign.Ptr | ||
24 | import System.IO.Unsafe | ||
25 | |||
26 | import Crypto.Cipher.SBox | ||
27 | |||
28 | ed25519Key :: OpenPGP.Packet -> Maybe Ed25519.PublicKey | ||
29 | ed25519Key k = case Ed25519.publicKey $ integerToBS $ keyParam 'n' k of | ||
30 | CryptoPassed ed25519 -> Just ed25519 | ||
31 | CryptoFailed err -> Nothing | ||
32 | |||
33 | ed25519sig 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 | |||
43 | ed25519Verify :: OpenPGP.Packet -> BS.ByteString -> OpenPGP.Packet -> Maybe Bool | ||
44 | ed25519Verify 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 | ||
21 | import Data.OpenPGP.Util.Base | 21 | import Data.OpenPGP.Util.Base |
22 | import Data.OpenPGP.Util.Ed25519 | ||
22 | 23 | ||
23 | 24 | ||
24 | dsaKey :: OpenPGP.Packet -> Vincent.DSA.PublicKey | 25 | dsaKey :: OpenPGP.Packet -> Vincent.DSA.PublicKey |
@@ -43,6 +44,7 @@ verify :: | |||
43 | verify keys over = | 44 | verify 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 | |||