diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,13 @@
 See also http://pvp.haskell.org/faq
 
+0.6.2.4
+-------
+
+* Allow `tar-0.6`
+* Drop support for GHC < 7.8 in favor of `PatternSynonyms`
+* Drop flags `base48`, `mtl21`, `old-directory` and support for GHC 7.8, `mtl < 2.2` and `directory < 1.2`
+* Tested with GHC 7.10 - 9.8
+
 0.6.2.3
 -------
 
diff --git a/hackage-security.cabal b/hackage-security.cabal
--- a/hackage-security.cabal
+++ b/hackage-security.cabal
@@ -1,6 +1,6 @@
 cabal-version:       1.12
 name:                hackage-security
-version:             0.6.2.3
+version:             0.6.2.4
 
 synopsis:            Hackage security library
 description:         The hackage security library provides both server and
@@ -31,9 +31,18 @@
 build-type:          Simple
 
 tested-with:
-  GHC==9.4.1, GHC==9.2.4, GHC==9.0.2,
-  GHC==8.10.7, GHC==8.8.4, GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2,
-  GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2
+  GHC == 9.8.1
+  GHC == 9.6.3
+  GHC == 9.4.8
+  GHC == 9.2.8
+  GHC == 9.0.2
+  GHC == 8.10.7
+  GHC == 8.8.4
+  GHC == 8.6.5
+  GHC == 8.4.4
+  GHC == 8.2.2
+  GHC == 8.0.2
+  GHC == 7.10.3
 
 extra-source-files:
   ChangeLog.md
@@ -42,10 +51,6 @@
   type: git
   location: https://github.com/haskell/hackage-security.git
 
-flag base48
-  description: Are we using @base@ 4.8 or later?
-  manual: False
-
 flag use-network-uri
   description: Are we using @network-uri@?
   manual: False
@@ -55,16 +60,6 @@
   manual: False
   default: False
 
-flag old-directory
-  description: Use @directory@ < 1.2 and @old-time@
-  manual:      False
-  default:     False
-
-flag mtl21
-  description: Use @mtl@ < 2.2 and @mtl-compat@
-  manual:      False
-  default:     False
-
 flag lukko
   description: Use @lukko@ for file-locking, otherwise use @GHC.IO.Handle.Lock@
   manual:      True
@@ -117,44 +112,36 @@
                        Hackage.Security.Util.TypedEmbedded
                        MyPrelude
   -- We support ghc 7.4 (bundled with Cabal 1.14) and up
-  build-depends:       base              >= 4.5     && < 4.18,
+  build-depends:       base              >= 4.8     && < 4.20,
+                         -- PatternSynonyms are only available since GHC 7.8 (base 4.7)
                        base16-bytestring >= 0.1.1   && < 1.1,
                        base64-bytestring >= 1.0     && < 1.3,
-                       bytestring        >= 0.9     && < 0.12,
-                       containers        >= 0.4     && < 0.7,
+                       bytestring        >= 0.9     && < 0.13,
+                       containers        >= 0.4     && < 0.8,
+                       cryptohash-sha256 >= 0.11    && < 0.12,
+                       directory         >= 1.2     && < 1.4,
                        ed25519           >= 0.0     && < 0.1,
                        filepath          >= 1.2     && < 1.5,
+                       mtl               >= 2.2     && < 2.4,
                        parsec            >= 3.1     && < 3.2,
                        pretty            >= 1.0     && < 1.2,
-                       cryptohash-sha256 >= 0.11    && < 0.12,
                        -- 0.4.2 introduces TarIndex, 0.4.4 introduces more
                        -- functionality, 0.5.0 changes type of serialise
-                       tar               >= 0.5     && < 0.6,
-                       template-haskell  >= 2.7     && < 2.20,
+                       tar               >= 0.5     && < 0.7,
+                       template-haskell  >= 2.7     && < 2.22,
                        time              >= 1.2     && < 1.13,
                        transformers      >= 0.3     && < 0.7,
                        zlib              >= 0.5     && < 0.7,
                        -- whatever versions are bundled with ghc:
                        ghc-prim
-  if flag(old-directory)
-    build-depends:     directory  >= 1.1.0.2 && < 1.2,
-                       old-time   >= 1 &&       < 1.2
-  else
-    build-depends:     directory  >= 1.2 && < 1.4
 
-  if flag(mtl21)
-    build-depends:     mtl        >= 2.1     && < 2.2,
-                       mtl-compat >= 0.2     && < 0.3
-  else
-    build-depends:     mtl        >= 2.2     && < 2.4
-
   if flag(lukko)
     build-depends:     lukko      >= 0.1     && < 0.2
   else
     build-depends:     base       >= 4.10
 
   if flag(Cabal-syntax) && impl(ghc >= 8.2)
-    build-depends: Cabal-syntax >= 3.7 && < 3.10
+    build-depends: Cabal-syntax >= 3.7 && < 3.12
   else
     build-depends: Cabal        >= 1.14    && < 1.26
                              || >= 2.0     && < 2.6
@@ -175,6 +162,7 @@
                        NamedFieldPuns
                        NoImplicitPrelude
                        NoMonomorphismRestriction
+                       PatternSynonyms
                        RankNTypes
                        RecordWildCards
                        ScopedTypeVariables
@@ -183,10 +171,14 @@
                        TypeFamilies
                        TypeOperators
                        ViewPatterns
-  other-extensions:    BangPatterns
+  other-extensions:
+                       AllowAmbiguousTypes
+                       BangPatterns
                        CPP
                        OverlappingInstances
                        PackageImports
+                       RoleAnnotations
+                       StaticPointers
                        UndecidableInstances
 
   -- use the new stage1/cross-compile-friendly DeriveLift extension for GHC 8.0+
@@ -197,11 +189,6 @@
 
   ghc-options:         -Wall
 
-  if flag(base48)
-    build-depends: base >= 4.8
-  else
-    build-depends: base < 4.8, old-locale == 1.0.*
-
   -- The URI type got split out off the network package after version 2.5, and
   -- moved to a separate network-uri package. Since we don't need the rest of
   -- network here, it would suffice to rely only on network-uri:
@@ -241,13 +228,6 @@
   else
     build-depends: network     >= 2.5 && < 2.6
 
-  if impl(ghc >= 7.8)
-     other-extensions: RoleAnnotations
-
-  if impl(ghc >= 7.10)
-     other-extensions: AllowAmbiguousTypes
-                       StaticPointers
-
 test-suite TestSuite
   type:                exitcode-stdio-1.0
   main-is:             TestSuite.hs
@@ -271,8 +251,8 @@
                        zlib
 
   if flag(Cabal-syntax) && impl(ghc >= 8.2)
-    build-depends: Cabal        >= 3.7 && < 3.10,
-                   Cabal-syntax >= 3.7 && < 3.10
+    build-depends: Cabal        >= 3.7 && < 3.12,
+                   Cabal-syntax >= 3.7 && < 3.12
   else
     build-depends: Cabal        >= 1.14    && < 1.26
                              || >= 2.0     && < 2.6
@@ -280,11 +260,11 @@
                    Cabal-syntax <  3.7
 
   -- dependencies exclusive to test-suite
-  build-depends:       tasty            >= 1.2 && < 1.5,
+  build-depends:       tasty            >= 1.2 && < 1.6,
                        tasty-hunit      == 0.10.*,
                        tasty-quickcheck == 0.10.*,
                        QuickCheck       >= 2.11 && <2.15,
-                       aeson            == 1.4.* || == 1.5.* || == 2.0.* || == 2.1.*,
+                       aeson            >= 1.4 && < 1.6 || >= 2.0 && < 2.3,
                        vector           >= 0.12 && <0.14,
                        unordered-containers >=0.2.8.0 && <0.3,
                        temporary        >= 1.2 && < 1.4
diff --git a/src/Hackage/Security/Client/Repository/Cache.hs b/src/Hackage/Security/Client/Repository/Cache.hs
--- a/src/Hackage/Security/Client/Repository/Cache.hs
+++ b/src/Hackage/Security/Client/Repository/Cache.hs
@@ -20,7 +20,7 @@
 import Control.Monad
 import Control.Monad.IO.Class
 import Data.Maybe
-import Codec.Archive.Tar (Entries(..))
+import Codec.Archive.Tar (Entries, pattern Done, pattern Fail, pattern Next)
 import Codec.Archive.Tar.Index (TarIndex, IndexBuilder, TarEntryOffset)
 import qualified Codec.Archive.Tar       as Tar
 import qualified Codec.Archive.Tar.Index as TarIndex
diff --git a/src/Hackage/Security/Trusted/TCB.hs b/src/Hackage/Security/Trusted/TCB.hs
--- a/src/Hackage/Security/Trusted/TCB.hs
+++ b/src/Hackage/Security/Trusted/TCB.hs
@@ -167,7 +167,10 @@
 -- | Errors thrown during role validation
 data VerificationError =
      -- | Not enough signatures signed with the appropriate keys
-     VerificationErrorSignatures TargetPath
+     VerificationErrorSignatures TargetPath -- what were we verifying?
+                                 Integer    -- threshold
+                                 [KeyId]    -- trusted keys
+                                 [KeyId]    -- found signing keys
 
      -- | The file is expired
    | VerificationErrorExpired TargetPath
@@ -218,9 +221,16 @@
 instance Exception RootUpdated
 #endif
 
+indentedLines :: [String] -> String
+indentedLines = unlines . map ("  " ++)
+
 instance Pretty VerificationError where
-  pretty (VerificationErrorSignatures file) =
-      pretty file ++ " does not have enough signatures signed with the appropriate keys"
+  pretty (VerificationErrorSignatures file threshold trusted sigs) =
+      pretty file ++ " does not have enough signatures signed with the appropriate keys\n"
+   ++ "Expected at least " ++ show threshold  ++ " signatures from:\n"
+   ++ indentedLines (map keyIdString trusted)
+   ++ "Found signatures from:\n"
+   ++ indentedLines (map keyIdString sigs)
   pretty (VerificationErrorExpired file) =
       pretty file ++ " is expired"
   pretty (VerificationErrorVersion file) =
@@ -235,7 +245,7 @@
       "Could not deserialize " ++ pretty file ++ ": " ++ pretty err
   pretty (VerificationErrorLoop es) =
       "Verification loop. Errors in order:\n"
-   ++ unlines (map (("  " ++) . either pretty pretty) es)
+   ++ indentedLines (map (either pretty pretty) es)
 
 instance Pretty RootUpdated where
   pretty RootUpdated = "Root information updated"
@@ -291,8 +301,9 @@
       -- was invalid we would already have thrown an error constructing Signed.
       -- (Similarly, if two signatures were made by the same key, the FromJSON
       -- instance for Signatures would have thrown an error.)
-      unless (length (filter isRoleSpecKey sigs) >= fromIntegral threshold) $
-        throwError $ VerificationErrorSignatures targetPath
+      let nSigs = length (filter isRoleSpecKey sigs)
+      unless (nSigs >= fromIntegral threshold) $
+        throwError $ VerificationErrorSignatures targetPath (fromIntegral threshold) trustedKeys signingKeys
 
       -- Everything is A-OK!
       return $ SignaturesVerified signed
@@ -300,6 +311,10 @@
     isRoleSpecKey :: Signature -> Bool
     isRoleSpecKey Signature{..} = signatureKey `elem` roleSpecKeys
 
+    trustedKeys, signingKeys :: [KeyId]
+    trustedKeys = map someKeyId roleSpecKeys
+    signingKeys = map (someKeyId . signatureKey) sigs
+
 -- | Variation on 'verifyRole' that uses key IDs rather than keys
 --
 -- This is used during the bootstrap process.
@@ -314,9 +329,12 @@
                    (KeyThreshold threshold)
                    targetPath
                    Signed{signatures = Signatures sigs, ..} =
-    if length (filter isTrustedKey sigs) >= fromIntegral threshold
+    if length (filter isTrustedKey signingKeys) >= fromIntegral threshold
       then Right $ SignaturesVerified signed
-      else Left $ VerificationErrorSignatures targetPath
+      else Left $ VerificationErrorSignatures targetPath (fromIntegral threshold) fingerprints signingKeys
   where
-    isTrustedKey :: Signature -> Bool
-    isTrustedKey Signature{..} = someKeyId signatureKey `elem` fingerprints
+    signingKeys :: [KeyId]
+    signingKeys = map (someKeyId . signatureKey) sigs
+
+    isTrustedKey :: KeyId -> Bool
+    isTrustedKey key = key `elem` fingerprints
