Skip to content

Commit ab07e6e

Browse files
authored
hackage-security: Expose more details in insufficient signatures error (#296)
The exception type now provides the set of acceptable key IDs as well as which keys signed the artifact. This has been very useful while debugging signing issues in the `head.hackage` infrastructure.
2 parents c77bfa2 + ef7e3fc commit ab07e6e

File tree

1 file changed

+28
-10
lines changed
  • hackage-security/src/Hackage/Security/Trusted

1 file changed

+28
-10
lines changed

hackage-security/src/Hackage/Security/Trusted/TCB.hs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ newtype SignaturesVerified a = SignaturesVerified { signaturesVerified :: a }
167167
-- | Errors thrown during role validation
168168
data VerificationError =
169169
-- | Not enough signatures signed with the appropriate keys
170-
VerificationErrorSignatures TargetPath
170+
VerificationErrorSignatures TargetPath -- what were we verifying?
171+
Integer -- threshold
172+
[KeyId] -- trusted keys
173+
[KeyId] -- found signing keys
171174

172175
-- | The file is expired
173176
| VerificationErrorExpired TargetPath
@@ -218,9 +221,16 @@ instance Show RootUpdated where show = pretty
218221
instance Exception RootUpdated
219222
#endif
220223

224+
indentedLines :: [String] -> String
225+
indentedLines = unlines . map (" " ++)
226+
221227
instance Pretty VerificationError where
222-
pretty (VerificationErrorSignatures file) =
223-
pretty file ++ " does not have enough signatures signed with the appropriate keys"
228+
pretty (VerificationErrorSignatures file threshold trusted sigs) =
229+
pretty file ++ " does not have enough signatures signed with the appropriate keys\n"
230+
++ "Expected at least " ++ show threshold ++ " signatures from:\n"
231+
++ indentedLines (map keyIdString trusted)
232+
++ "Found signatures from:\n"
233+
++ indentedLines (map keyIdString sigs)
224234
pretty (VerificationErrorExpired file) =
225235
pretty file ++ " is expired"
226236
pretty (VerificationErrorVersion file) =
@@ -235,7 +245,7 @@ instance Pretty VerificationError where
235245
"Could not deserialize " ++ pretty file ++ ": " ++ pretty err
236246
pretty (VerificationErrorLoop es) =
237247
"Verification loop. Errors in order:\n"
238-
++ unlines (map ((" " ++) . either pretty pretty) es)
248+
++ indentedLines (map (either pretty pretty) es)
239249

240250
instance Pretty RootUpdated where
241251
pretty RootUpdated = "Root information updated"
@@ -291,15 +301,20 @@ verifyRole' (trusted -> RoleSpec{roleSpecThreshold = KeyThreshold threshold, ..}
291301
-- was invalid we would already have thrown an error constructing Signed.
292302
-- (Similarly, if two signatures were made by the same key, the FromJSON
293303
-- instance for Signatures would have thrown an error.)
294-
unless (length (filter isRoleSpecKey sigs) >= fromIntegral threshold) $
295-
throwError $ VerificationErrorSignatures targetPath
304+
let nSigs = length (filter isRoleSpecKey sigs)
305+
unless (nSigs >= fromIntegral threshold) $
306+
throwError $ VerificationErrorSignatures targetPath (fromIntegral threshold) trustedKeys signingKeys
296307

297308
-- Everything is A-OK!
298309
return $ SignaturesVerified signed
299310

300311
isRoleSpecKey :: Signature -> Bool
301312
isRoleSpecKey Signature{..} = signatureKey `elem` roleSpecKeys
302313

314+
trustedKeys, signingKeys :: [KeyId]
315+
trustedKeys = map someKeyId roleSpecKeys
316+
signingKeys = map (someKeyId . signatureKey) sigs
317+
303318
-- | Variation on 'verifyRole' that uses key IDs rather than keys
304319
--
305320
-- This is used during the bootstrap process.
@@ -314,9 +329,12 @@ verifyFingerprints fingerprints
314329
(KeyThreshold threshold)
315330
targetPath
316331
Signed{signatures = Signatures sigs, ..} =
317-
if length (filter isTrustedKey sigs) >= fromIntegral threshold
332+
if length (filter isTrustedKey signingKeys) >= fromIntegral threshold
318333
then Right $ SignaturesVerified signed
319-
else Left $ VerificationErrorSignatures targetPath
334+
else Left $ VerificationErrorSignatures targetPath (fromIntegral threshold) fingerprints signingKeys
320335
where
321-
isTrustedKey :: Signature -> Bool
322-
isTrustedKey Signature{..} = someKeyId signatureKey `elem` fingerprints
336+
signingKeys :: [KeyId]
337+
signingKeys = map (someKeyId . signatureKey) sigs
338+
339+
isTrustedKey :: KeyId -> Bool
340+
isTrustedKey key = key `elem` fingerprints

0 commit comments

Comments
 (0)