diff options
author | Stephen Paul Weber <singpolyma@singpolyma.net> | 2012-04-25 17:19:07 -0500 |
---|---|---|
committer | Stephen Paul Weber <singpolyma@singpolyma.net> | 2012-04-25 17:19:07 -0500 |
commit | 6b743222684f2b8151dfbdef42f0dc890e590c41 (patch) | |
tree | 77b5ae031d2b863f03399ba9a5b56ba4478d2ab2 /Data | |
parent | 7b3232778f284dd4dd3a6f3287bcbe1fbe10b010 (diff) |
Split OpenPGP.Crypto out into a seperate package
Diffstat (limited to 'Data')
-rw-r--r-- | Data/OpenPGP/Crypto.hs | 183 |
1 files changed, 0 insertions, 183 deletions
diff --git a/Data/OpenPGP/Crypto.hs b/Data/OpenPGP/Crypto.hs deleted file mode 100644 index 54fb81e..0000000 --- a/Data/OpenPGP/Crypto.hs +++ /dev/null | |||
@@ -1,183 +0,0 @@ | |||
1 | -- | This is a wrapper around <http://hackage.haskell.org/package/Crypto> | ||
2 | -- that currently does fingerprint generation and signature verification. | ||
3 | -- | ||
4 | -- The recommended way to import this module is: | ||
5 | -- | ||
6 | -- > import qualified Data.OpenPGP.Crypto as OpenPGP | ||
7 | module Data.OpenPGP.Crypto (sign, verify, fingerprint) where | ||
8 | |||
9 | import Numeric | ||
10 | import Data.Word | ||
11 | import Data.Char | ||
12 | import Data.List (find) | ||
13 | import Data.Map ((!)) | ||
14 | import qualified Data.ByteString.Lazy as LZ | ||
15 | import qualified Data.ByteString.Lazy.UTF8 as LZ (fromString) | ||
16 | |||
17 | import Data.Binary | ||
18 | import Codec.Utils (fromOctets) | ||
19 | import qualified Codec.Encryption.RSA as RSA | ||
20 | import qualified Data.Digest.MD5 as MD5 | ||
21 | import qualified Data.Digest.SHA1 as SHA1 | ||
22 | import qualified Data.Digest.SHA256 as SHA256 | ||
23 | import qualified Data.Digest.SHA384 as SHA384 | ||
24 | import qualified Data.Digest.SHA512 as SHA512 | ||
25 | |||
26 | import qualified Data.OpenPGP as OpenPGP | ||
27 | |||
28 | -- | Generate a key fingerprint from a PublicKeyPacket or SecretKeyPacket | ||
29 | -- <http://tools.ietf.org/html/rfc4880#section-12.2> | ||
30 | fingerprint :: OpenPGP.Packet -> String | ||
31 | fingerprint p | ||
32 | | OpenPGP.version p == 4 = | ||
33 | map toUpper $ (`showHex` "") $ SHA1.toInteger $ SHA1.hash $ | ||
34 | LZ.unpack (LZ.concat (OpenPGP.fingerprint_material p)) | ||
35 | | OpenPGP.version p `elem` [2, 3] = | ||
36 | map toUpper $ foldr (pad `oo` showHex) "" $ | ||
37 | MD5.hash $ LZ.unpack (LZ.concat (OpenPGP.fingerprint_material p)) | ||
38 | | otherwise = error "Unsupported Packet version or type in fingerprint" | ||
39 | where | ||
40 | oo = (.) . (.) | ||
41 | pad s | odd $ length s = '0':s | ||
42 | | otherwise = s | ||
43 | |||
44 | find_key :: OpenPGP.Message -> String -> Maybe OpenPGP.Packet | ||
45 | find_key (OpenPGP.Message (x@(OpenPGP.PublicKeyPacket {}):xs)) keyid = | ||
46 | find_key_ x xs keyid | ||
47 | find_key (OpenPGP.Message (x@(OpenPGP.SecretKeyPacket {}):xs)) keyid = | ||
48 | find_key_ x xs keyid | ||
49 | find_key (OpenPGP.Message (_:xs)) keyid = | ||
50 | find_key (OpenPGP.Message xs) keyid | ||
51 | find_key _ _ = Nothing | ||
52 | |||
53 | find_key_ :: OpenPGP.Packet -> [OpenPGP.Packet] -> String -> Maybe OpenPGP.Packet | ||
54 | find_key_ x xs keyid | ||
55 | | thisid == keyid = Just x | ||
56 | | otherwise = find_key (OpenPGP.Message xs) keyid | ||
57 | where | ||
58 | thisid = reverse $ take (length keyid) (reverse (fingerprint x)) | ||
59 | |||
60 | keyfield_as_octets :: OpenPGP.Packet -> Char -> [Word8] | ||
61 | keyfield_as_octets k f = | ||
62 | LZ.unpack $ LZ.drop 2 (encode (k' ! f)) | ||
63 | where k' = OpenPGP.key k | ||
64 | |||
65 | -- http://tools.ietf.org/html/rfc3447#page-43 | ||
66 | emsa_pkcs1_v1_5_hash_padding :: OpenPGP.HashAlgorithm -> [Word8] | ||
67 | emsa_pkcs1_v1_5_hash_padding OpenPGP.MD5 = [0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10] | ||
68 | emsa_pkcs1_v1_5_hash_padding OpenPGP.SHA1 = [0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14] | ||
69 | emsa_pkcs1_v1_5_hash_padding OpenPGP.SHA256 = [0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20] | ||
70 | emsa_pkcs1_v1_5_hash_padding OpenPGP.SHA384 = [0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30] | ||
71 | emsa_pkcs1_v1_5_hash_padding OpenPGP.SHA512 = [0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40] | ||
72 | emsa_pkcs1_v1_5_hash_padding _ = | ||
73 | error "Unsupported HashAlgorithm in emsa_pkcs1_v1_5_hash_padding." | ||
74 | |||
75 | hash :: OpenPGP.HashAlgorithm -> [Word8] -> [Word8] | ||
76 | hash OpenPGP.MD5 = MD5.hash | ||
77 | hash OpenPGP.SHA1 = drop 2 . LZ.unpack . encode . OpenPGP.MPI . SHA1.toInteger . SHA1.hash | ||
78 | hash OpenPGP.SHA256 = SHA256.hash | ||
79 | hash OpenPGP.SHA384 = SHA384.hash | ||
80 | hash OpenPGP.SHA512 = SHA512.hash | ||
81 | hash _ = error "Unsupported HashAlgorithm in hash." | ||
82 | |||
83 | emsa_pkcs1_v1_5_encode :: [Word8] -> Int -> OpenPGP.HashAlgorithm -> [Word8] | ||
84 | emsa_pkcs1_v1_5_encode m emLen algo = | ||
85 | [0, 1] ++ replicate (emLen - length t - 3) 0xff ++ [0] ++ t | ||
86 | where t = emsa_pkcs1_v1_5_hash_padding algo ++ hash algo m | ||
87 | |||
88 | -- | Verify a message signature. Only supports RSA keys for now. | ||
89 | verify :: OpenPGP.Message -- ^ Keys that may have made the signature | ||
90 | -> OpenPGP.Message -- ^ LiteralData message to verify | ||
91 | -> Int -- ^ Index of signature to verify (0th, 1st, etc) | ||
92 | -> Bool | ||
93 | verify keys message sigidx = | ||
94 | encoded == RSA.encrypt (n, e) raw_sig | ||
95 | where | ||
96 | raw_sig = LZ.unpack $ LZ.drop 2 $ encode (OpenPGP.signature sig) | ||
97 | encoded = emsa_pkcs1_v1_5_encode signature_over | ||
98 | (length n) (OpenPGP.hash_algorithm sig) | ||
99 | signature_over = LZ.unpack $ dta `LZ.append` OpenPGP.trailer sig | ||
100 | (n, e) = (keyfield_as_octets k 'n', keyfield_as_octets k 'e') | ||
101 | Just k = find_key keys issuer | ||
102 | Just issuer = OpenPGP.signature_issuer sig | ||
103 | sig = sigs !! sigidx | ||
104 | (sigs, (OpenPGP.LiteralDataPacket {OpenPGP.content = dta}):_) = | ||
105 | OpenPGP.signatures_and_data message | ||
106 | |||
107 | -- | Sign data or key/userID pair. Only supports RSA keys for now. | ||
108 | sign :: OpenPGP.Message -- ^ SecretKeys, one of which will be used | ||
109 | -> OpenPGP.Message -- ^ Message containing data or key to sign, and optional signature packet | ||
110 | -> OpenPGP.HashAlgorithm -- ^ HashAlgorithm to use in signature | ||
111 | -> String -- ^ KeyID of key to choose or @[]@ for first | ||
112 | -> Integer -- ^ Timestamp for signature (unless sig supplied) | ||
113 | -> OpenPGP.Packet | ||
114 | sign keys message hsh keyid timestamp = | ||
115 | -- WARNING: this style of update is unsafe on most fields | ||
116 | -- it is safe on signature and hash_head, though | ||
117 | sig { | ||
118 | OpenPGP.signature = OpenPGP.MPI $ toNum final, | ||
119 | OpenPGP.hash_head = toNum $ take 2 final | ||
120 | } | ||
121 | where | ||
122 | -- toNum has explicit param so that it can remain polymorphic | ||
123 | toNum l = fromOctets (256::Integer) l | ||
124 | final = dropWhile (==0) $ RSA.decrypt (n, d) encoded | ||
125 | encoded = emsa_pkcs1_v1_5_encode dta (length n) hsh | ||
126 | (n, d) = (keyfield_as_octets k 'n', keyfield_as_octets k 'd') | ||
127 | dta = LZ.unpack $ case signOver of { | ||
128 | OpenPGP.LiteralDataPacket {OpenPGP.content = c} -> c; | ||
129 | _ -> LZ.concat $ OpenPGP.fingerprint_material signOver ++ [ | ||
130 | LZ.singleton 0xB4, | ||
131 | encode (fromIntegral (length firstUserID) :: Word32), | ||
132 | LZ.fromString firstUserID | ||
133 | ] | ||
134 | } `LZ.append` OpenPGP.trailer sig | ||
135 | sig = findSigOrDefault (find OpenPGP.isSignaturePacket m) | ||
136 | |||
137 | -- Either a SignaturePacket was found, or we need to make one | ||
138 | findSigOrDefault (Just s) = OpenPGP.signaturePacket | ||
139 | (OpenPGP.version s) | ||
140 | (OpenPGP.signature_type s) | ||
141 | OpenPGP.RSA -- force key and hash algorithm | ||
142 | hsh | ||
143 | (OpenPGP.hashed_subpackets s) | ||
144 | (OpenPGP.unhashed_subpackets s) | ||
145 | (OpenPGP.hash_head s) | ||
146 | (OpenPGP.signature s) | ||
147 | findSigOrDefault Nothing = OpenPGP.signaturePacket | ||
148 | 4 | ||
149 | defaultStype | ||
150 | OpenPGP.RSA | ||
151 | hsh | ||
152 | ([ | ||
153 | -- Do we really need to pass in timestamp just for the default? | ||
154 | OpenPGP.SignatureCreationTimePacket $ fromIntegral timestamp, | ||
155 | OpenPGP.IssuerPacket keyid' | ||
156 | ] ++ (case signOver of | ||
157 | OpenPGP.LiteralDataPacket {} -> [] | ||
158 | _ -> [] -- TODO: OpenPGP.KeyFlagsPacket [0x01, 0x02] | ||
159 | )) | ||
160 | [] | ||
161 | undefined | ||
162 | undefined | ||
163 | |||
164 | keyid' = reverse $ take 16 $ reverse $ fingerprint k | ||
165 | Just k = find_key keys keyid | ||
166 | |||
167 | Just (OpenPGP.UserIDPacket firstUserID) = find isUserID m | ||
168 | |||
169 | defaultStype = case signOver of | ||
170 | OpenPGP.LiteralDataPacket {OpenPGP.format = f} -> | ||
171 | if f == 'b' then 0x00 else 0x01 | ||
172 | _ -> 0x13 | ||
173 | |||
174 | Just signOver = find isSignable m | ||
175 | OpenPGP.Message m = message | ||
176 | |||
177 | isSignable (OpenPGP.LiteralDataPacket {}) = True | ||
178 | isSignable (OpenPGP.PublicKeyPacket {}) = True | ||
179 | isSignable (OpenPGP.SecretKeyPacket {}) = True | ||
180 | isSignable _ = False | ||
181 | |||
182 | isUserID (OpenPGP.UserIDPacket {}) = True | ||
183 | isUserID _ = False | ||