From 30dda180eb6afb1b20f0cadc96a2369484607e2a Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Wed, 6 Nov 2019 15:32:39 +0000 Subject: [PATCH] Add support for comments in certificates, bump certificate version --- .../muwire/core/filecert/Certificate.groovy | 45 ++++++++++++++--- .../core/filecert/CertificateManager.groovy | 8 +++- .../main/java/com/muwire/core/Constants.java | 2 +- .../gui/CertificateControlController.groovy | 17 +++++++ .../gui/FetchCertificatesController.groovy | 13 +++++ .../com/muwire/gui/SearchTabController.groovy | 3 +- .../muwire/gui/CertificateControlModel.groovy | 2 + .../muwire/gui/FetchCertificatesModel.groovy | 1 + .../com/muwire/gui/ShowCommentModel.groovy | 3 +- .../muwire/gui/CertificateControlView.groovy | 48 +++++++++++++++++++ .../muwire/gui/FetchCertificatesView.groovy | 17 ++++++- .../com/muwire/gui/ShowCommentView.groovy | 4 +- 12 files changed, 149 insertions(+), 14 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/filecert/Certificate.groovy b/core/src/main/groovy/com/muwire/core/filecert/Certificate.groovy index 860db990..d91a9c5d 100644 --- a/core/src/main/groovy/com/muwire/core/filecert/Certificate.groovy +++ b/core/src/main/groovy/com/muwire/core/filecert/Certificate.groovy @@ -14,7 +14,7 @@ import net.i2p.data.SigningPublicKey class Certificate { private final byte version private final InfoHash infoHash - private final Name name + private final Name name, comment private final long timestamp private final Persona issuer private final byte[] sig @@ -23,7 +23,7 @@ class Certificate { Certificate(InputStream is) { version = (byte) (is.read() & 0xFF) - if (version != Constants.FILE_CERT_VERSION) + if (version > Constants.FILE_CERT_VERSION) throw new IOException("Unknown version $version") DataInputStream dis = new DataInputStream(is) @@ -35,19 +35,29 @@ class Certificate { name = new Name(dis) - issuer = new Persona(is) + issuer = new Persona(dis) + if (version == 2) { + byte present = (byte)(dis.read() & 0xFF) + if (present != 0) { + comment = new Name(dis) + } + } sig = new byte[Constants.SIG_TYPE.getSigLen()] dis.readFully(sig) - if (!verify(version, infoHash, name, timestamp, issuer, sig)) + if (!verify(version, infoHash, name, timestamp, issuer, comment, sig)) throw new InvalidSignatureException("certificate for $name.name from ${issuer.getHumanReadableName()} didn't verify") } - Certificate(InfoHash infoHash, String name, long timestamp, Persona issuer, SigningPrivateKey spk) { + Certificate(InfoHash infoHash, String name, long timestamp, Persona issuer, String comment, SigningPrivateKey spk) { this.version = Constants.FILE_CERT_VERSION this.infoHash = infoHash this.name = new Name(name) + if (comment != null) + this.comment = new Name(comment) + else + this.comment = null this.timestamp = timestamp this.issuer = issuer @@ -59,6 +69,12 @@ class Certificate { daos.write(infoHash.getRoot()) this.name.write(daos) issuer.write(daos) + if (this.comment == null) { + daos.write((byte) 0) + } else { + daos.write((byte) 1) + this.comment.write(daos) + } daos.close() byte[] payload = baos.toByteArray() @@ -66,7 +82,7 @@ class Certificate { this.sig = signature.getData() } - private static boolean verify(byte version, InfoHash infoHash, Name name, long timestamp, Persona issuer, byte[] sig) { + private static boolean verify(byte version, InfoHash infoHash, Name name, long timestamp, Persona issuer, Name comment, byte[] sig) { ByteArrayOutputStream baos = new ByteArrayOutputStream() DataOutputStream daos = new DataOutputStream(baos) daos.write(version) @@ -74,6 +90,14 @@ class Certificate { daos.write(infoHash.getRoot()) name.write(daos) issuer.write(daos) + if (version == 2) { + if (comment == null) { + daos.write((byte)0) + } else { + daos.write((byte)1) + comment.write(daos) + } + } daos.close() byte [] payload = baos.toByteArray() @@ -91,6 +115,12 @@ class Certificate { daos.write(infoHash.getRoot()) name.write(daos) issuer.write(daos) + if (comment == null) + daos.write((byte) 0) + else { + daos.write((byte) 1) + comment.write(daos) + } daos.write(sig) daos.close() @@ -114,6 +144,7 @@ class Certificate { infoHash == other.infoHash && timestamp == other.timestamp && name == other.name && - issuer == other.issuer + issuer == other.issuer && + comment == other.comment } } diff --git a/core/src/main/groovy/com/muwire/core/filecert/CertificateManager.groovy b/core/src/main/groovy/com/muwire/core/filecert/CertificateManager.groovy index a3a92cb2..bfd2cc29 100644 --- a/core/src/main/groovy/com/muwire/core/filecert/CertificateManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filecert/CertificateManager.groovy @@ -8,6 +8,7 @@ import com.muwire.core.InfoHash import com.muwire.core.InvalidSignatureException import com.muwire.core.Name import com.muwire.core.Persona +import com.muwire.core.util.DataUtil import groovy.util.logging.Log import net.i2p.data.Base64 @@ -72,7 +73,12 @@ class CertificateManager { InfoHash infoHash = e.sharedFile.getInfoHash() String name = e.sharedFile.getFile().getName() long timestamp = System.currentTimeMillis() - Certificate cert = new Certificate(infoHash, name, timestamp, me, spk) + + String comment = null + if (e.sharedFile.getComment() != null) + comment = DataUtil.readi18nString(Base64.decode(e.sharedFile.getComment())) + + Certificate cert = new Certificate(infoHash, name, timestamp, me, comment, spk) if (addToMaps(cert)) { diff --git a/core/src/main/java/com/muwire/core/Constants.java b/core/src/main/java/com/muwire/core/Constants.java index e2101b0b..f29ff146 100644 --- a/core/src/main/java/com/muwire/core/Constants.java +++ b/core/src/main/java/com/muwire/core/Constants.java @@ -4,7 +4,7 @@ import net.i2p.crypto.SigType; public class Constants { public static final byte PERSONA_VERSION = (byte)1; - public static final byte FILE_CERT_VERSION = (byte)1; + public static final byte FILE_CERT_VERSION = (byte)2; public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519; public static final int MAX_HEADER_SIZE = 0x1 << 14; diff --git a/gui/griffon-app/controllers/com/muwire/gui/CertificateControlController.groovy b/gui/griffon-app/controllers/com/muwire/gui/CertificateControlController.groovy index ace363b9..d41d1ce2 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/CertificateControlController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/CertificateControlController.groovy @@ -6,6 +6,23 @@ import griffon.inject.MVCMember import griffon.metadata.ArtifactProviderFor import javax.annotation.Nonnull +import com.muwire.core.filecert.Certificate + @ArtifactProviderFor(GriffonController) class CertificateControlController { + @MVCMember @Nonnull + CertificateControlModel model + @MVCMember @Nonnull + CertificateControlView view + + @ControllerAction + void showComment() { + Certificate cert = view.getSelectedSertificate() + if (cert == null || cert.comment == null) + return + + def params = [:] + params['text'] = cert.comment.name + mvcGroup.createMVCGroup("show-comment", params) + } } \ No newline at end of file diff --git a/gui/griffon-app/controllers/com/muwire/gui/FetchCertificatesController.groovy b/gui/griffon-app/controllers/com/muwire/gui/FetchCertificatesController.groovy index e2f534d8..f1ba25e3 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/FetchCertificatesController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/FetchCertificatesController.groovy @@ -63,6 +63,19 @@ class FetchCertificatesController { JOptionPane.showMessageDialog(null, "Certificates imported.") } + @ControllerAction + void showComment() { + def selectedCerts = view.selectedCertificates() + if (selectedCerts == null || selectedCerts.size() != 1) + return + + String comment = selectedCerts[0].comment.name + def params = [:] + params['text'] = comment + params['name'] = "Certificate Comment" + mvcGroup.createMVCGroup("show-comment", params) + } + @ControllerAction void dismiss() { view.dialog.setVisible(false) diff --git a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy index 38663961..1421f032 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy @@ -117,7 +117,8 @@ class SearchTabController { String groupId = Base64.encode(event.infohash.getRoot()) Map params = new HashMap<>() - params['result'] = event + params['text'] = event.comment + params['name'] = event.name mvcGroup.createMVCGroup("show-comment", groupId, params) } diff --git a/gui/griffon-app/models/com/muwire/gui/CertificateControlModel.groovy b/gui/griffon-app/models/com/muwire/gui/CertificateControlModel.groovy index 0914376f..8befb540 100644 --- a/gui/griffon-app/models/com/muwire/gui/CertificateControlModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/CertificateControlModel.groovy @@ -13,6 +13,8 @@ class CertificateControlModel { Core core + @Observable boolean showCommentActionEnabled + void mvcGroupInit(Map args) { users.addAll(core.certificateManager.byIssuer.keySet()) } diff --git a/gui/griffon-app/models/com/muwire/gui/FetchCertificatesModel.groovy b/gui/griffon-app/models/com/muwire/gui/FetchCertificatesModel.groovy index bb31cb28..3a7630e0 100644 --- a/gui/griffon-app/models/com/muwire/gui/FetchCertificatesModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/FetchCertificatesModel.groovy @@ -16,6 +16,7 @@ class FetchCertificatesModel { @Observable int certificateCount @Observable boolean importActionEnabled + @Observable boolean showCommentActionEnabled def certificates = [] } \ No newline at end of file diff --git a/gui/griffon-app/models/com/muwire/gui/ShowCommentModel.groovy b/gui/griffon-app/models/com/muwire/gui/ShowCommentModel.groovy index 08b597f1..5f00cebc 100644 --- a/gui/griffon-app/models/com/muwire/gui/ShowCommentModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/ShowCommentModel.groovy @@ -8,5 +8,6 @@ import griffon.metadata.ArtifactProviderFor @ArtifactProviderFor(GriffonModel) class ShowCommentModel { - UIResultEvent result + String name + String text } \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/CertificateControlView.groovy b/gui/griffon-app/views/com/muwire/gui/CertificateControlView.groovy index 14f06ed5..6aeea5e0 100644 --- a/gui/griffon-app/views/com/muwire/gui/CertificateControlView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/CertificateControlView.groovy @@ -6,6 +6,8 @@ import griffon.metadata.ArtifactProviderFor import net.i2p.data.Base64 import javax.swing.JDialog +import javax.swing.JMenuItem +import javax.swing.JPopupMenu import javax.swing.ListSelectionModel import javax.swing.SwingConstants @@ -13,6 +15,8 @@ import com.muwire.core.Persona import com.muwire.core.filecert.Certificate import java.awt.BorderLayout +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent import java.awt.event.WindowAdapter import java.awt.event.WindowEvent @@ -24,6 +28,8 @@ class CertificateControlView { FactoryBuilderSupport builder @MVCMember @Nonnull CertificateControlModel model + @MVCMember @Nonnull + CertificateControlController controller def mainFrame def dialog @@ -31,6 +37,7 @@ class CertificateControlView { def usersTable def certsTable def lastUsersSortEvent + def lastCertsSortEvent void initUI() { mainFrame = application.windowManager.findWindow("main-frame") @@ -57,6 +64,7 @@ class CertificateControlView { tableModel(list : model.certificates) { closureColumn(header : "File Name", type : String, read : {it.name.name}) closureColumn(header : "Hash", type : String, read : {Base64.encode(it.infoHash.getRoot())}) + closureColumn(header : "Comment", preferredWidth : 20, type : Boolean, read : {it.comment != null}) closureColumn(header : "Timestamp", type : String, read : { def date = new Date(it.timestamp) date.toString() @@ -65,6 +73,9 @@ class CertificateControlView { } } } + panel (constraints : BorderLayout.SOUTH) { + button(text : "Show Comment", enabled : bind {model.showCommentActionEnabled}, showCommentAction) + } } } @@ -84,6 +95,24 @@ class CertificateControlView { certsTable.model.fireTableDataChanged() }) + certsTable.rowSorter.addRowSorterListener({evt -> lastCertsSortEvent = evt}) + selectionModel = certsTable.getSelectionModel() + selectionModel.addListSelectionListener({ + Certificate c = getSelectedSertificate() + model.showCommentActionEnabled = c != null && c.comment != null + }) + + certsTable.addMouseListener(new MouseAdapter() { + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) + showMenu(e) + } + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) + showMenu(e) + } + }) + dialog.getContentPane().add(panel) dialog.pack() dialog.setLocationRelativeTo(mainFrame) @@ -105,4 +134,23 @@ class CertificateControlView { model.users[selectedRow] } + Certificate getSelectedSertificate() { + int [] selectedRows = certsTable.getSelectedRows() + if (selectedRows.length != 1) + return null + if (lastCertsSortEvent != null) + selectedRows[0] = certsTable.rowSorter.convertRowIndexToModel(selectedRows[0]) + model.certificates[selectedRows[0]] + } + + private void showMenu(MouseEvent e) { + if (!model.showCommentActionEnabled) + return + JPopupMenu menu = new JPopupMenu() + JMenuItem showComment = new JMenuItem("Show Comment") + showComment.addActionListener({controller.showComment()}) + menu.add(showComment) + menu.show(e.getComponent(), e.getX(), e.getY()) + } + } \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/FetchCertificatesView.groovy b/gui/griffon-app/views/com/muwire/gui/FetchCertificatesView.groovy index be661081..4e1216d3 100644 --- a/gui/griffon-app/views/com/muwire/gui/FetchCertificatesView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/FetchCertificatesView.groovy @@ -57,11 +57,13 @@ class FetchCertificatesView { def date = new Date(it.timestamp) date.toString() }) + closureColumn(header : "Comments", preferredWidth: 20, type : Boolean, read :{it.comment != null}) } } } panel(constraints : BorderLayout.SOUTH) { button(text : "Import", enabled : bind {model.importActionEnabled}, importCertificatesAction) + button(text : "Show Comment", enabled : bind {model.showCommentActionEnabled}, showCommentAction) button(text : "Dismiss", dismissAction) } } @@ -74,6 +76,14 @@ class FetchCertificatesView { selectionModel.addListSelectionListener({ int[] rows = certsTable.getSelectedRows() model.importActionEnabled = rows.length > 0 + + if (rows.length == 1) { + if (lastSortEvent != null) + rows[0] = certsTable.rowSorter.convertRowIndexToModel(rows[0]) + model.showCommentActionEnabled = model.certificates[rows[0]].comment != null + } else + model.showCommentActionEnabled = false + }) certsTable.addMouseListener(new MouseAdapter() { @@ -94,7 +104,12 @@ class FetchCertificatesView { JMenuItem importItem = new JMenuItem("Import") importItem.addActionListener({controller.importCertificates()}) menu.add(importItem) - menu.showing(e.getComponent(), e.getX(), e.getY()) + if (model.showCommentActionEnabled) { + JMenuItem showComment = new JMenuItem("Show Comment") + showComment.addActionListener({controller.showComment()}) + menu.add(showComment) + } + menu.show(e.getComponent(), e.getX(), e.getY()) } def selectedCertificates() { diff --git a/gui/griffon-app/views/com/muwire/gui/ShowCommentView.groovy b/gui/griffon-app/views/com/muwire/gui/ShowCommentView.groovy index 4c4cd9ea..d7790fd6 100644 --- a/gui/griffon-app/views/com/muwire/gui/ShowCommentView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/ShowCommentView.groovy @@ -29,7 +29,7 @@ class ShowCommentView { void initUI() { mainFrame = application.windowManager.findWindow("main-frame") - dialog = new JDialog(mainFrame, model.result.name, true) + dialog = new JDialog(mainFrame, model.name, true) dialog.setResizable(true) @@ -37,7 +37,7 @@ class ShowCommentView { borderLayout() panel (constraints : BorderLayout.CENTER) { scrollPane { - textArea(text : model.result.comment, rows : 20, columns : 100, editable : false, lineWrap : true, wrapStyleWord : true) + textArea(text : model.text, rows : 20, columns : 100, editable : false, lineWrap : true, wrapStyleWord : true) } } panel (constraints : BorderLayout.SOUTH) {