openpgp-Crypto (empty) → 0.3
raw patch · 16 files changed
+349/−0 lines, 16 filesdep +Cryptodep +HUnitdep +QuickChecksetup-changedbinary-added
Dependencies added: Crypto, HUnit, QuickCheck, base, binary, bytestring, containers, openpgp, test-framework, test-framework-hunit, test-framework-quickcheck2, utf8-string
Files
- COPYING +13/−0
- Data/OpenPGP/Crypto.hs +183/−0
- README +9/−0
- Setup.hs +3/−0
- openpgp-Crypto.cabal +74/−0
- tests/data/000001-006.public_key binary
- tests/data/000016-006.public_key binary
- tests/data/000027-006.public_key binary
- tests/data/000035-006.public_key binary
- tests/data/compressedsig-bzip2.gpg binary
- tests/data/compressedsig-zlib.gpg binary
- tests/data/compressedsig.gpg binary
- tests/data/uncompressed-ops-dsa-sha384.txt.gpg binary
- tests/data/uncompressed-ops-dsa.gpg binary
- tests/data/uncompressed-ops-rsa.gpg binary
- tests/suite.hs +67/−0
+ COPYING view
@@ -0,0 +1,13 @@+Copyright © 2011, Stephen Paul Weber <singpolyma.net>++Permission to use, copy, modify, and/or distribute this software for any+purpose with or without fee is hereby granted, provided that the above+copyright notice and this permission notice appear in all copies.++THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ Data/OpenPGP/Crypto.hs view
@@ -0,0 +1,183 @@+-- | This is a wrapper around <http://hackage.haskell.org/package/Crypto>+-- that currently does fingerprint generation and signature verification.+--+-- The recommended way to import this module is:+--+-- > import qualified Data.OpenPGP.Crypto as OpenPGP+module Data.OpenPGP.Crypto (sign, verify, fingerprint) where++import Numeric+import Data.Word+import Data.Char+import Data.List (find)+import Data.Map ((!))+import qualified Data.ByteString.Lazy as LZ+import qualified Data.ByteString.Lazy.UTF8 as LZ (fromString)++import Data.Binary+import Codec.Utils (fromOctets)+import qualified Codec.Encryption.RSA as RSA+import qualified Data.Digest.MD5 as MD5+import qualified Data.Digest.SHA1 as SHA1+import qualified Data.Digest.SHA256 as SHA256+import qualified Data.Digest.SHA384 as SHA384+import qualified Data.Digest.SHA512 as SHA512++import qualified Data.OpenPGP as OpenPGP++-- | Generate a key fingerprint from a PublicKeyPacket or SecretKeyPacket+-- <http://tools.ietf.org/html/rfc4880#section-12.2>+fingerprint :: OpenPGP.Packet -> String+fingerprint p+ | OpenPGP.version p == 4 =+ map toUpper $ (`showHex` "") $ SHA1.toInteger $ SHA1.hash $+ LZ.unpack (LZ.concat (OpenPGP.fingerprint_material p))+ | OpenPGP.version p `elem` [2, 3] =+ map toUpper $ foldr (pad `oo` showHex) "" $+ MD5.hash $ LZ.unpack (LZ.concat (OpenPGP.fingerprint_material p))+ | otherwise = error "Unsupported Packet version or type in fingerprint"+ where+ oo = (.) . (.)+ pad s | odd $ length s = '0':s+ | otherwise = s++find_key :: OpenPGP.Message -> String -> Maybe OpenPGP.Packet+find_key (OpenPGP.Message (x@(OpenPGP.PublicKeyPacket {}):xs)) keyid =+ find_key_ x xs keyid+find_key (OpenPGP.Message (x@(OpenPGP.SecretKeyPacket {}):xs)) keyid =+ find_key_ x xs keyid+find_key (OpenPGP.Message (_:xs)) keyid =+ find_key (OpenPGP.Message xs) keyid+find_key _ _ = Nothing++find_key_ :: OpenPGP.Packet -> [OpenPGP.Packet] -> String -> Maybe OpenPGP.Packet+find_key_ x xs keyid+ | thisid == keyid = Just x+ | otherwise = find_key (OpenPGP.Message xs) keyid+ where+ thisid = reverse $ take (length keyid) (reverse (fingerprint x))++keyfield_as_octets :: OpenPGP.Packet -> Char -> [Word8]+keyfield_as_octets k f =+ LZ.unpack $ LZ.drop 2 (encode (k' ! f))+ where k' = OpenPGP.key k++-- http://tools.ietf.org/html/rfc3447#page-43+emsa_pkcs1_v1_5_hash_padding :: OpenPGP.HashAlgorithm -> [Word8]+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]+emsa_pkcs1_v1_5_hash_padding OpenPGP.SHA1 = [0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14]+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]+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]+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]+emsa_pkcs1_v1_5_hash_padding _ =+ error "Unsupported HashAlgorithm in emsa_pkcs1_v1_5_hash_padding."++hash :: OpenPGP.HashAlgorithm -> [Word8] -> [Word8]+hash OpenPGP.MD5 = MD5.hash+hash OpenPGP.SHA1 = drop 2 . LZ.unpack . encode . OpenPGP.MPI . SHA1.toInteger . SHA1.hash+hash OpenPGP.SHA256 = SHA256.hash+hash OpenPGP.SHA384 = SHA384.hash+hash OpenPGP.SHA512 = SHA512.hash+hash _ = error "Unsupported HashAlgorithm in hash."++emsa_pkcs1_v1_5_encode :: [Word8] -> Int -> OpenPGP.HashAlgorithm -> [Word8]+emsa_pkcs1_v1_5_encode m emLen algo =+ [0, 1] ++ replicate (emLen - length t - 3) 0xff ++ [0] ++ t+ where t = emsa_pkcs1_v1_5_hash_padding algo ++ hash algo m++-- | Verify a message signature. Only supports RSA keys for now.+verify :: OpenPGP.Message -- ^ Keys that may have made the signature+ -> OpenPGP.Message -- ^ LiteralData message to verify+ -> Int -- ^ Index of signature to verify (0th, 1st, etc)+ -> Bool+verify keys message sigidx =+ encoded == RSA.encrypt (n, e) raw_sig+ where+ raw_sig = LZ.unpack $ LZ.drop 2 $ encode (OpenPGP.signature sig)+ encoded = emsa_pkcs1_v1_5_encode signature_over+ (length n) (OpenPGP.hash_algorithm sig)+ signature_over = LZ.unpack $ dta `LZ.append` OpenPGP.trailer sig+ (n, e) = (keyfield_as_octets k 'n', keyfield_as_octets k 'e')+ Just k = find_key keys issuer+ Just issuer = OpenPGP.signature_issuer sig+ sig = sigs !! sigidx+ (sigs, (OpenPGP.LiteralDataPacket {OpenPGP.content = dta}):_) =+ OpenPGP.signatures_and_data message++-- | Sign data or key/userID pair. Only supports RSA keys for now.+sign :: OpenPGP.Message -- ^ SecretKeys, one of which will be used+ -> OpenPGP.Message -- ^ Message containing data or key to sign, and optional signature packet+ -> OpenPGP.HashAlgorithm -- ^ HashAlgorithm to use in signature+ -> String -- ^ KeyID of key to choose or @[]@ for first+ -> Integer -- ^ Timestamp for signature (unless sig supplied)+ -> OpenPGP.Packet+sign keys message hsh keyid timestamp =+ -- WARNING: this style of update is unsafe on most fields+ -- it is safe on signature and hash_head, though+ sig {+ OpenPGP.signature = OpenPGP.MPI $ toNum final,+ OpenPGP.hash_head = toNum $ take 2 final+ }+ where+ -- toNum has explicit param so that it can remain polymorphic+ toNum l = fromOctets (256::Integer) l+ final = dropWhile (==0) $ RSA.decrypt (n, d) encoded+ encoded = emsa_pkcs1_v1_5_encode dta (length n) hsh+ (n, d) = (keyfield_as_octets k 'n', keyfield_as_octets k 'd')+ dta = LZ.unpack $ case signOver of {+ OpenPGP.LiteralDataPacket {OpenPGP.content = c} -> c;+ _ -> LZ.concat $ OpenPGP.fingerprint_material signOver ++ [+ LZ.singleton 0xB4,+ encode (fromIntegral (length firstUserID) :: Word32),+ LZ.fromString firstUserID+ ]+ } `LZ.append` OpenPGP.trailer sig+ sig = findSigOrDefault (find OpenPGP.isSignaturePacket m)++ -- Either a SignaturePacket was found, or we need to make one+ findSigOrDefault (Just s) = OpenPGP.signaturePacket+ (OpenPGP.version s)+ (OpenPGP.signature_type s)+ OpenPGP.RSA -- force key and hash algorithm+ hsh+ (OpenPGP.hashed_subpackets s)+ (OpenPGP.unhashed_subpackets s)+ (OpenPGP.hash_head s)+ (OpenPGP.signature s)+ findSigOrDefault Nothing = OpenPGP.signaturePacket+ 4+ defaultStype+ OpenPGP.RSA+ hsh+ ([+ -- Do we really need to pass in timestamp just for the default?+ OpenPGP.SignatureCreationTimePacket $ fromIntegral timestamp,+ OpenPGP.IssuerPacket keyid'+ ] ++ (case signOver of+ OpenPGP.LiteralDataPacket {} -> []+ _ -> [] -- TODO: OpenPGP.KeyFlagsPacket [0x01, 0x02]+ ))+ []+ undefined+ undefined++ keyid' = reverse $ take 16 $ reverse $ fingerprint k+ Just k = find_key keys keyid++ Just (OpenPGP.UserIDPacket firstUserID) = find isUserID m++ defaultStype = case signOver of+ OpenPGP.LiteralDataPacket {OpenPGP.format = f} ->+ if f == 'b' then 0x00 else 0x01+ _ -> 0x13++ Just signOver = find isSignable m+ OpenPGP.Message m = message++ isSignable (OpenPGP.LiteralDataPacket {}) = True+ isSignable (OpenPGP.PublicKeyPacket {}) = True+ isSignable (OpenPGP.SecretKeyPacket {}) = True+ isSignable _ = False++ isUserID (OpenPGP.UserIDPacket {}) = True+ isUserID _ = False
+ README view
@@ -0,0 +1,9 @@+This is a wrapper around <http://hackage.haskell.org/package/Crypto>+that currently does fingerprint generation, signature generation, and+signature verification (for RSA keys only).++It is indended to be used with <http://hackage.haskell.org/openpgp>++It is intended that you use qualified imports with this library.++> import qualified Data.OpenPGP.Crypto as OpenPGP
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple++main = defaultMain
+ openpgp-Crypto.cabal view
@@ -0,0 +1,74 @@+name: openpgp-Crypto+version: 0.3+cabal-version: >= 1.8+license: OtherLicense+license-file: COPYING+category: Cryptography+copyright: © 2011-2012 Stephen Paul Weber+author: Stephen Paul Weber <singpolyma@singpolyma.net>+maintainer: Stephen Paul Weber <singpolyma@singpolyma.net>+stability: experimental+tested-with: GHC == 7.0.3+synopsis: Implementation of cryptography for use with OpenPGP using the Crypto library+homepage: http://github.com/singpolyma/OpenPGP-Crypto+bug-reports: http://github.com/singpolyma/OpenPGP-Haskell/issues+build-type: Simple+description:+ This is a wrapper around <http://hackage.haskell.org/package/Crypto>+ that currently does fingerprint generation, signature generation, and+ signature verification (for RSA keys only).+ .+ It is indended to be used with <http://hackage.haskell.org/openpgp>+ .+ It is intended that you use qualified imports with this library.+ .+ > import qualified Data.OpenPGP.Crypto as OpenPGP++extra-source-files:+ README,+ tests/suite.hs,+ tests/data/000001-006.public_key,+ tests/data/000016-006.public_key,+ tests/data/000027-006.public_key,+ tests/data/000035-006.public_key,+ tests/data/uncompressed-ops-dsa.gpg+ tests/data/uncompressed-ops-dsa-sha384.txt.gpg+ tests/data/uncompressed-ops-rsa.gpg+ tests/data/compressedsig.gpg+ tests/data/compressedsig-zlib.gpg+ tests/data/compressedsig-bzip2.gpg++library+ exposed-modules:+ Data.OpenPGP.Crypto++ build-depends:+ base == 4.*,+ containers,+ bytestring,+ utf8-string,+ binary,+ openpgp,+ Crypto++test-suite tests+ type: exitcode-stdio-1.0+ main-is: tests/suite.hs++ build-depends:+ base == 4.*,+ containers,+ bytestring,+ utf8-string,+ binary,+ openpgp,+ Crypto,+ HUnit,+ QuickCheck >= 2.4.1.1,+ test-framework,+ test-framework-hunit,+ test-framework-quickcheck2++source-repository head+ type: git+ location: git://github.com/singpolyma/OpenPGP-Crypto.git
+ tests/data/000001-006.public_key view
binary file changed (absent → 171 bytes)
+ tests/data/000016-006.public_key view
binary file changed (absent → 1201 bytes)
+ tests/data/000027-006.public_key view
binary file changed (absent → 421 bytes)
+ tests/data/000035-006.public_key view
binary file changed (absent → 143 bytes)
+ tests/data/compressedsig-bzip2.gpg view
binary file changed (absent → 442 bytes)
+ tests/data/compressedsig-zlib.gpg view
binary file changed (absent → 322 bytes)
+ tests/data/compressedsig.gpg view
binary file changed (absent → 324 bytes)
+ tests/data/uncompressed-ops-dsa-sha384.txt.gpg view
binary file changed (absent → 150 bytes)
+ tests/data/uncompressed-ops-dsa.gpg view
binary file changed (absent → 150 bytes)
+ tests/data/uncompressed-ops-rsa.gpg view
binary file changed (absent → 236 bytes)
+ tests/suite.hs view
@@ -0,0 +1,67 @@+import Test.Framework (defaultMain, testGroup, Test)+import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck2+import Test.QuickCheck+import Test.HUnit hiding (Test)++import Data.Binary+import qualified Data.OpenPGP as OpenPGP+import qualified Data.OpenPGP.Crypto as OpenPGP+import qualified Data.ByteString.Lazy as LZ+import qualified Data.ByteString.Lazy.UTF8 as LZ (fromString)++instance Arbitrary OpenPGP.HashAlgorithm where+ arbitrary = elements [OpenPGP.MD5, OpenPGP.SHA1, OpenPGP.SHA256, OpenPGP.SHA384, OpenPGP.SHA512]++testFingerprint :: FilePath -> String -> Assertion+testFingerprint fp kf = do+ bs <- LZ.readFile $ "tests/data/" ++ fp+ let (OpenPGP.Message [packet]) = decode bs+ assertEqual ("for " ++ fp) kf (OpenPGP.fingerprint packet)++testVerifyMessage :: FilePath -> FilePath -> Assertion+testVerifyMessage keyring message = do+ keys <- fmap decode $ LZ.readFile $ "tests/data/" ++ keyring+ m <- fmap decode $ LZ.readFile $ "tests/data/" ++ message+ let verification = OpenPGP.verify keys m 0+ assertEqual (keyring ++ " for " ++ message) True verification++prop_sign_and_verify :: OpenPGP.Message -> String -> OpenPGP.HashAlgorithm -> String -> String -> Bool+prop_sign_and_verify secring kid halgo filename msg =+ let+ m = OpenPGP.LiteralDataPacket {+ OpenPGP.format = 'u',+ OpenPGP.filename = filename,+ OpenPGP.timestamp = 12341234,+ OpenPGP.content = LZ.fromString msg+ }+ sig = OpenPGP.sign secring (OpenPGP.Message [m]) halgo kid 12341234+ in+ OpenPGP.verify secring (OpenPGP.Message [m,sig]) 0++tests :: OpenPGP.Message -> [Test]+tests secring =+ [+ testGroup "Fingerprint" [+ testCase "000001-006.public_key" (testFingerprint "000001-006.public_key" "421F28FEAAD222F856C8FFD5D4D54EA16F87040E"),+ testCase "000016-006.public_key" (testFingerprint "000016-006.public_key" "AF95E4D7BAC521EE9740BED75E9F1523413262DC"),+ testCase "000027-006.public_key" (testFingerprint "000027-006.public_key" "1EB20B2F5A5CC3BEAFD6E5CB7732CF988A63EA86"),+ testCase "000035-006.public_key" (testFingerprint "000035-006.public_key" "CB7933459F59C70DF1C3FBEEDEDC3ECF689AF56D")+ ],+ testGroup "Message verification" [+ --testCase "uncompressed-ops-dsa" (testVerifyMessage "pubring.gpg" "uncompressed-ops-dsa.gpg"),+ --testCase "uncompressed-ops-dsa-sha384" (testVerifyMessage "pubring.gpg" "uncompressed-ops-dsa-sha384.txt.gpg"),+ testCase "uncompressed-ops-rsa" (testVerifyMessage "pubring.gpg" "uncompressed-ops-rsa.gpg"),+ testCase "compressedsig" (testVerifyMessage "pubring.gpg" "compressedsig.gpg"),+ testCase "compressedsig-zlib" (testVerifyMessage "pubring.gpg" "compressedsig-zlib.gpg"),+ testCase "compressedsig-bzip2" (testVerifyMessage "pubring.gpg" "compressedsig-bzip2.gpg")+ ],+ testGroup "Signing" [+ testProperty "Crypto signatures verify" (prop_sign_and_verify secring "FEF8AFA0F661C3EE")+ ]+ ]++main :: IO ()+main = do+ secring <- fmap decode $ LZ.readFile "tests/data/secring.gpg"+ defaultMain (tests secring)