From 4d903cf80c4f2f8a5d6b22964ed23d8584fdbed4 Mon Sep 17 00:00:00 2001 From: Yuuta Liang Date: Thu, 23 Nov 2023 08:41:56 +0800 Subject: Add QR code Signed-off-by: Yuuta Liang --- README.md | 3 ++ src/main/ui/MainUI.java | 16 ++++++++- src/main/ui/widgets/QRPanel.java | 77 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/main/ui/widgets/QRPanel.java diff --git a/README.md b/README.md index 1040e4b..802448a 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,9 @@ container, upon each successful sign. 8. Periodically publish CRLs. On the "CA" tab, click on the "CRL" button on the top right, and select a path for the CRL. The database automatically saves after issuing a new CRL. +The CA public key will also be rendered as a QR code on the "CA" tab, for easier transfer between computers. The encoded +data is PEM-encoded PKCS#1 public key. + ## Author Yuuta Liang diff --git a/src/main/ui/MainUI.java b/src/main/ui/MainUI.java index 9637d79..34cc224 100644 --- a/src/main/ui/MainUI.java +++ b/src/main/ui/MainUI.java @@ -92,6 +92,7 @@ public class MainUI extends JFrame { private final JButton buttonGenPrivKey = btn("Generate", 'G', this::onGeneratePrivateKey); private final JButton buttonInstallCA = btn("Install", 'I', this::onInstallCA); private final JButton buttonGenCSR = btn("CSR", this::onSignCSR); + private final QRPanel panelQR = new QRPanel(256); private final JToolBar toolbarCA = new JToolBar(); private final JButton buttonCAToolbarCRL = btn("CRL", "publisher.png", this::onCRL); @@ -192,6 +193,17 @@ public class MainUI extends JFrame { renderRefresh(); } + /** + * EFFECTS: Set the QR code data to either null (no public key) or PEM-encoded PKCS#1 public key. + */ + private void renderQRPublicKey() { + if (ca.getPublicKey() == null) { + panelQR.setData(null); + return; + } + panelQR.setData(Utils.toPEM(Utils.byteToByte(ca.getPublicKey().getEncoded()), "PUBLIC KEY")); + } + // -----END HELPER METHODS----- // -----BEGIN DATA OBSERVERS----- @@ -327,7 +339,8 @@ public class MainUI extends JFrame { panelTabCA.add(buttonGenPrivKey, new GCBuilder().gridXY(1, 1).fill(HORIZONTAL).build()); panelTabCA.add(buttonInstallCA, new GCBuilder().gridXY(1, 2).fill(HORIZONTAL).build()); panelTabCA.add(buttonGenCSR, new GCBuilder().gridXY(2, 2).fill(HORIZONTAL).build()); - panelTabCA.add(new JPanel(), new GCBuilder().gridXY(3, 3).expandXY().fill(BOTH).build()); + panelTabCA.add(panelQR, new GCBuilder().gridY(3).fill(HORIZONTAL).build()); + panelTabCA.add(new JPanel(), new GCBuilder().gridXY(4, 4).expandXY().fill(BOTH).build()); tabbedPane.addTab("Certs", panelCertsTab = defView(scrTbl(tableCerts), "No issued certs")); tabbedPane.addTab("Templates", panelTmpTab = defView(scrTbl(tableTemplates), "No templates")); @@ -339,6 +352,7 @@ public class MainUI extends JFrame { * MODIFIES: this */ private void renderCAPage() { + renderQRPublicKey(); if (ca.getPublicKey() == null) { labelPrivateKey.setText("Private key not installed"); } else { diff --git a/src/main/ui/widgets/QRPanel.java b/src/main/ui/widgets/QRPanel.java new file mode 100644 index 0000000..d72d0d0 --- /dev/null +++ b/src/main/ui/widgets/QRPanel.java @@ -0,0 +1,77 @@ +package ui.widgets; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; + +import javax.swing.*; +import java.awt.*; +import java.util.Collections; + +import static com.google.zxing.EncodeHintType.ERROR_CORRECTION; +import static com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.L; + +/** + * A fixed-size panel displaying a QR code. + */ +public class QRPanel extends JPanel { + private final int size; + + private BitMatrix data; + + /** + * EFFECTS: Init with the specific rectangular size. Min size = max size = preferred size. + */ + public QRPanel(int size) { + super(); + setSize(new Dimension(size, size)); + setMaximumSize(new Dimension(size, size)); + setMinimumSize(new Dimension(size, size)); + setPreferredSize(new Dimension(size, size)); + this.size = size; + } + + /** + * EFFECTS: Draw the QR code. Leave white if no data is set. + * MODIFIES: this + */ + @Override + protected void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, size, size); + + if (data == null) { + return; + } + + graphics.setColor(Color.BLACK); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + if (data.get(i, j)) { + graphics.fillRect(i, j, 1, 1); + } + } + } + } + + /** + * EFFECTS: Set data and repaint. Nullable. + * MODIFIES: this + */ + public void setData(String data) { + if (data == null) { + this.data = null; + invalidate(); + return; + } + try { + this.data = new QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, size, size, + Collections.singletonMap(ERROR_CORRECTION, L)); + } catch (WriterException e) { + e.printStackTrace(); + } + invalidate(); + } +} -- cgit v1.2.3