diff options
author | Joe Crayne <joe@jerkface.net> | 2020-05-10 14:05:42 -0400 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2020-05-19 11:59:10 -0400 |
commit | cee4108007b2a384f54b4a8943a612fdc3c9f260 (patch) | |
tree | 3f2f476b6bd5af89da0014d97baa39ca67cea7b9 | |
parent | 4bfc6b028527f43d9ae48d24dc2afb5f0db7ad1e (diff) |
Parse v5 secret key packets (draft-ietf-openpgp-rfc4880bis-09).
-rw-r--r-- | Data/OpenPGP.hs | 96 | ||||
-rw-r--r-- | Data/OpenPGP/Util/Gen.hs | 1 |
2 files changed, 80 insertions, 17 deletions
diff --git a/Data/OpenPGP.hs b/Data/OpenPGP.hs index 16fcd1e..598fe75 100644 --- a/Data/OpenPGP.hs +++ b/Data/OpenPGP.hs | |||
@@ -39,6 +39,7 @@ module Data.OpenPGP ( | |||
39 | s2k, | 39 | s2k, |
40 | signature, | 40 | signature, |
41 | signature_type, | 41 | signature_type, |
42 | aead_algorithm, | ||
42 | symmetric_algorithm, | 43 | symmetric_algorithm, |
43 | timestamp, | 44 | timestamp, |
44 | trailer, | 45 | trailer, |
@@ -53,6 +54,7 @@ module Data.OpenPGP ( | |||
53 | string2key, | 54 | string2key, |
54 | HashAlgorithm(..), | 55 | HashAlgorithm(..), |
55 | KeyAlgorithm(..), | 56 | KeyAlgorithm(..), |
57 | AEADAlgorithm(..), | ||
56 | SymmetricAlgorithm(..), | 58 | SymmetricAlgorithm(..), |
57 | CompressionAlgorithm(..), | 59 | CompressionAlgorithm(..), |
58 | RevocationCode(..), | 60 | RevocationCode(..), |
@@ -245,6 +247,7 @@ data Packet = | |||
245 | key :: [(Char,MPI)], | 247 | key :: [(Char,MPI)], |
246 | s2k_useage :: Word8, | 248 | s2k_useage :: Word8, |
247 | s2k :: S2K, -- ^ This is meaningless if symmetric_algorithm == Unencrypted | 249 | s2k :: S2K, -- ^ This is meaningless if symmetric_algorithm == Unencrypted |
250 | aead_algorithm :: Maybe AEADAlgorithm, | ||
248 | symmetric_algorithm :: SymmetricAlgorithm, | 251 | symmetric_algorithm :: SymmetricAlgorithm, |
249 | encrypted_data :: B.ByteString, | 252 | encrypted_data :: B.ByteString, |
250 | is_subkey :: Bool | 253 | is_subkey :: Bool |
@@ -588,13 +591,14 @@ put_packet (SecretKeyPacket { version = version, timestamp = timestamp, | |||
588 | put_packet p@(PublicKeyPacket { version = v, timestamp = timestamp, | 591 | put_packet p@(PublicKeyPacket { version = v, timestamp = timestamp, |
589 | key_algorithm = algorithm, key = key, | 592 | key_algorithm = algorithm, key = key, |
590 | is_subkey = is_subkey }) | 593 | is_subkey = is_subkey }) |
591 | | v == 3 = | 594 | = case v of |
595 | 3 -> | ||
592 | final (B.concat $ [ | 596 | final (B.concat $ [ |
593 | B.singleton 3, encode timestamp, | 597 | B.singleton 3, encode timestamp, |
594 | encode v3_days, | 598 | encode v3_days, |
595 | encode algorithm | 599 | encode algorithm |
596 | ] ++ material) | 600 | ] ++ material) |
597 | | v == 4 = | 601 | _ -> |
598 | final (B.concat $ [ | 602 | final (B.concat $ [ |
599 | B.singleton 4, encode timestamp, encode algorithm | 603 | B.singleton 4, encode timestamp, encode algorithm |
600 | ] ++ material) | 604 | ] ++ material) |
@@ -757,24 +761,66 @@ parse_packet 5 = do | |||
757 | }) <- parse_packet 6 | 761 | }) <- parse_packet 6 |
758 | s2k_useage <- get :: Get Word8 | 762 | s2k_useage <- get :: Get Word8 |
759 | let k = SecretKeyPacket version timestamp algorithm key s2k_useage | 763 | let k = SecretKeyPacket version timestamp algorithm key s2k_useage |
760 | (symmetric_algorithm, s2k) <- case () of | 764 | optlen1 <- if version == 5 |
761 | _ | s2k_useage `elem` [255, 254] -> (,) <$> get <*> get | 765 | then do |
762 | _ | s2k_useage > 0 -> | 766 | len <- get |
767 | return $ isolate (fromIntegral (len :: Word8)) | ||
768 | else return id | ||
769 | ((symmetric_algorithm, aead, s2k),iv) <- optlen1 $ do | ||
770 | symdata <- case () of | ||
771 | _ | s2k_useage `elem` [255, 254] -> do | ||
772 | sym <- get | ||
773 | s2k <- get | ||
774 | return (sym,Nothing,s2k) | ||
775 | _ | s2k_useage == 253 -> do | ||
776 | sym <- get | ||
777 | aead <- get :: Get AEADAlgorithm | ||
778 | s2k <- get | ||
779 | return (sym,Just aead,s2k) | ||
780 | _ | s2k_useage > 0 -> do | ||
763 | -- s2k_useage is symmetric_type in this case | 781 | -- s2k_useage is symmetric_type in this case |
764 | (,) <$> localGet get (encode s2k_useage) <*> pure (SimpleS2K MD5) | 782 | sym <- localGet get (encode s2k_useage) |
783 | return $ (sym, Nothing, SimpleS2K MD5) | ||
765 | _ -> | 784 | _ -> |
766 | return (Unencrypted, S2K 100 B.empty) | 785 | return (Unencrypted, Nothing, S2K 100 B.empty) |
786 | iv <- if version==5 then getRemainingByteString | ||
787 | else return mempty -- For now, empty. It will be read later. | ||
788 | return (symdata,iv) | ||
789 | let -- We make an isolate5 utility to avoid running isolate for v4 keys. | ||
790 | -- The reason for this is because apparently, Data.Serialize.isolate | ||
791 | -- documents the unfortunate requirement: | ||
792 | -- "The action is required to consume all the bytes that it is isolated to." | ||
793 | -- This prevents using maxBound or some other large default isolation. | ||
794 | isolate5 | version == 5 = isolate | ||
795 | | otherwise = const id | ||
796 | optlen2 <- if version == 5 | ||
797 | then fromIntegral <$> (get :: Get Word32) | ||
798 | else return 0 -- Value is not used. | ||
767 | if symmetric_algorithm /= Unencrypted then do { | 799 | if symmetric_algorithm /= Unencrypted then do { |
768 | encrypted <- getRemainingByteString; | 800 | isolate5 optlen2 $ do |
769 | return (k s2k symmetric_algorithm encrypted False) | 801 | encrypted <- getRemainingByteString; |
802 | return (k s2k aead symmetric_algorithm (iv <> encrypted) False) | ||
770 | } else do | 803 | } else do |
771 | skey <- foldM (\m f -> do | 804 | (skey,skeybs) <- isolate5 optlen2 $ case known_secret_key_fields algorithm of |
772 | mpi <- get :: Get MPI | 805 | Just fs -> do |
773 | return $ (f,mpi):m) [] (secret_key_fields algorithm) | 806 | skey <- foldM (\m f -> do |
774 | chk <- get | 807 | mpi <- get :: Get MPI |
775 | when (checksum (B.concat $ map (encode . snd) skey) /= chk) $ | 808 | return $ (f,mpi):m) |
776 | fail "Checksum verification failed for unencrypted secret key" | 809 | [] |
777 | return ((k s2k symmetric_algorithm B.empty False) {key = key ++ skey}) | 810 | fs |
811 | return (skey, B.concat $ map (encode . snd) skey) | ||
812 | Nothing -> do | ||
813 | keybs <- getRemainingByteString | ||
814 | return ([('R', MPI $ getBigNum $ toStrictBS keybs)], keybs) | ||
815 | let (sumcnt0, csum) = checksumForKey s2k_useage | ||
816 | sumcnt = fromIntegral sumcnt0 :: Int | ||
817 | -- Note: Spec draft-ietf-openpgp-rfc4880bis-09.txt is unclear. | ||
818 | -- I think checksum should be excluded from the optlen2 count | ||
819 | -- when s2k_useage is 0 so I am excluding it. | ||
820 | chk <- getByteString (fromIntegral sumcnt) | ||
821 | when (csum skeybs /= chk) $ | ||
822 | fail "Checksum verification failed for unencrypted secret key" | ||
823 | return ((k s2k aead symmetric_algorithm B.empty False) {key = key ++ skey}) | ||
778 | -- PublicKeyPacket, http://tools.ietf.org/html/rfc4880#section-5.5.2 | 824 | -- PublicKeyPacket, http://tools.ietf.org/html/rfc4880#section-5.5.2 |
779 | parse_packet 6 = do | 825 | parse_packet 6 = do |
780 | version <- get :: Get Word8 | 826 | version <- get :: Get Word8 |
@@ -1047,6 +1093,22 @@ instance BINARY_CLASS SymmetricAlgorithm where | |||
1047 | put = put . enum_to_word8 | 1093 | put = put . enum_to_word8 |
1048 | get = fmap enum_from_word8 get | 1094 | get = fmap enum_from_word8 get |
1049 | 1095 | ||
1096 | data AEADAlgorithm = EAX | OCB | AEADAlgorithm Word8 | ||
1097 | deriving (Show, Read, Eq, Ord) | ||
1098 | |||
1099 | instance Enum AEADAlgorithm where | ||
1100 | toEnum 1 = EAX | ||
1101 | toEnum 2 = OCB | ||
1102 | toEnum x = AEADAlgorithm (fromIntegral x) | ||
1103 | fromEnum EAX = 1 | ||
1104 | fromEnum OCB = 2 | ||
1105 | fromEnum (AEADAlgorithm x) = fromIntegral x | ||
1106 | |||
1107 | instance BINARY_CLASS AEADAlgorithm where | ||
1108 | put = put . enum_to_word8 | ||
1109 | get = fmap enum_from_word8 get | ||
1110 | |||
1111 | |||
1050 | data CompressionAlgorithm = Uncompressed | ZIP | ZLIB | BZip2 | CompressionAlgorithm Word8 | 1112 | data CompressionAlgorithm = Uncompressed | ZIP | ZLIB | BZip2 | CompressionAlgorithm Word8 |
1051 | deriving (Show, Read, Eq) | 1113 | deriving (Show, Read, Eq) |
1052 | 1114 | ||
@@ -1109,7 +1171,7 @@ data SignatureOver = | |||
1109 | -- To get the signed-over bytes | 1171 | -- To get the signed-over bytes |
1110 | instance BINARY_CLASS SignatureOver where | 1172 | instance BINARY_CLASS SignatureOver where |
1111 | put (DataSignature (LiteralDataPacket {content = c}) _) = | 1173 | put (DataSignature (LiteralDataPacket {content = c}) _) = |
1112 | putSomeByteString c | 1174 | putSomeByteString c -- TODO: This is incorrect for v5 signatures. See section 5.10. |
1113 | put (KeySignature k _) = mapM_ putSomeByteString (fingerprint_material k) | 1175 | put (KeySignature k _) = mapM_ putSomeByteString (fingerprint_material k) |
1114 | put (SubkeySignature k s _) = mapM_ (mapM_ putSomeByteString) | 1176 | put (SubkeySignature k s _) = mapM_ (mapM_ putSomeByteString) |
1115 | [fingerprint_material k, fingerprint_material s] | 1177 | [fingerprint_material k, fingerprint_material s] |
diff --git a/Data/OpenPGP/Util/Gen.hs b/Data/OpenPGP/Util/Gen.hs index 713e909..4e629cd 100644 --- a/Data/OpenPGP/Util/Gen.hs +++ b/Data/OpenPGP/Util/Gen.hs | |||
@@ -143,6 +143,7 @@ buildPacket alg stamp fields = | |||
143 | key = fields :: [(Char,MPI)], | 143 | key = fields :: [(Char,MPI)], |
144 | s2k_useage = 0 :: Word8, | 144 | s2k_useage = 0 :: Word8, |
145 | s2k = S2K 100 B.empty :: S2K, -- Unencrypted so meaningless | 145 | s2k = S2K 100 B.empty :: S2K, -- Unencrypted so meaningless |
146 | aead_algorithm = Nothing :: Maybe AEADAlgorithm, | ||
146 | symmetric_algorithm = Unencrypted :: SymmetricAlgorithm, | 147 | symmetric_algorithm = Unencrypted :: SymmetricAlgorithm, |
147 | encrypted_data = B.empty :: B.ByteString, | 148 | encrypted_data = B.empty :: B.ByteString, |
148 | is_subkey = True :: Bool | 149 | is_subkey = True :: Bool |