From 6ef88d84b7e0358b602506e5f9f9d20a439b7ff3 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Mon, 5 Oct 2026 09:53:10 +0200 Subject: [PATCH] support inline signed manifests in the download verifier by reading hashes from the verified signature content --- drongo | 2 +- .../control/DownloadVerifierDialog.java | 39 ++++++++++++++----- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/drongo b/drongo index 00aa6ec0..3a60bd64 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 00aa6ec07e95860fb118adc5cb399d8b3d670fae +Subproject commit 3a60bd6453f5428fc81ad3822c35bfae04c581cc diff --git a/src/main/java/com/sparrowwallet/sparrow/control/DownloadVerifierDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/DownloadVerifierDialog.java index 3efed6a9..9f112967 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/DownloadVerifierDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/DownloadVerifierDialog.java @@ -312,7 +312,8 @@ public class DownloadVerifierDialog extends Dialog { return; } - PGPVerificationResult result = pgpVerifyService.getValue(); + SignedManifest signedManifest = pgpVerifyService.getValue(); + PGPVerificationResult result = signedManifest.result(); String message = result.userId() + " on " + signatureDateFormat.format(result.signatureTimestamp()) + (result.expired() ? " (key expired)" : ""); signedBy.setText(message); @@ -332,7 +333,7 @@ public class DownloadVerifierDialog extends Dialog { releaseVerified.setGraphic(GlyphUtils.getSuccessGlyph()); releaseLink.setText(release.get().getName()); } else { - verifyManifest(verification); + verifyManifest(verification, signedManifest.content()); } }); pgpVerifyService.setOnFailed(event -> { @@ -374,10 +375,9 @@ public class DownloadVerifierDialog extends Dialog { releaseLink.setText(""); } - private void verifyManifest(long verification) { + private void verifyManifest(long verification, byte[] manifestContent) { File releaseFile = release.get(); if(releaseFile != null && releaseFile.exists()) { - File manifestFile = manifest.get(); hashService = new FileSha256Service(releaseFile); hashService.setOnRunning(event -> { if(verification != verificationCount) { @@ -398,7 +398,11 @@ public class DownloadVerifierDialog extends Dialog { String calculatedHash = hashService.getValue(); try { - Map manifestMap = getManifest(manifestFile); + if(manifestContent == null || manifestContent.length > MAX_VALID_MANIFEST_SIZE) { + throw new InvalidManifestException("Manifest file is larger than " + (MAX_VALID_MANIFEST_SIZE / 1024) + "KB"); + } + + Map manifestMap = getManifest(new ByteArrayInputStream(manifestContent)); String manifestHash = getManifestHash(releaseFile.getName(), manifestMap); if(calculatedHash.equalsIgnoreCase(manifestHash)) { releaseHash.setText("Matched manifest hash"); @@ -775,7 +779,9 @@ public class DownloadVerifierDialog extends Dialog { } } - private static class PGPVerifyService extends Service { + private record SignedManifest(PGPVerificationResult result, byte[] content) { } + + private static class PGPVerifyService extends Service { private final File signature; private final File manifest; private final File publicKey; @@ -787,15 +793,30 @@ public class DownloadVerifierDialog extends Dialog { } @Override - protected Task createTask() { + protected Task createTask() { return new Task<>() { - protected PGPVerificationResult call() throws IOException, PGPVerificationException { + protected SignedManifest call() throws IOException, PGPVerificationException { boolean detachedSignature = !manifest.equals(signature); + //Retain at most one byte more than a valid manifest, so the content of a manifest that is too large can be rejected without holding all of it + ByteArrayOutputStream signedContent = manifest.length() > MAX_VALID_MANIFEST_SIZE ? null : new ByteArrayOutputStream(); + OutputStream signedContentStream = signedContent == null ? null : new OutputStream() { + @Override + public void write(int b) { + write(new byte[] { (byte)b }, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) { + signedContent.write(b, off, (int)Math.max(0, Math.min(len, MAX_VALID_MANIFEST_SIZE + 1 - signedContent.size()))); + } + }; + try(InputStream publicKeyStream = publicKey == null ? null : new FileInputStream(publicKey); InputStream contentStream = new BufferedInputStream(new FileInputStream(manifest)); InputStream detachedSignatureStream = detachedSignature ? new FileInputStream(signature) : null) { - return PGPUtils.verify(publicKeyStream, contentStream, detachedSignatureStream); + PGPVerificationResult result = PGPUtils.verify(publicKeyStream, contentStream, detachedSignatureStream, signedContentStream); + return new SignedManifest(result, signedContent == null ? null : signedContent.toByteArray()); } } };