Skip to content

Commit

Permalink
Basic implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Vzor- committed Sep 9, 2024
1 parent 7330141 commit 0b16615
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 97 deletions.
120 changes: 93 additions & 27 deletions src/qz/auth/Certificate.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@
import java.nio.file.Path;
import java.security.*;
import java.security.cert.*;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;

Expand All @@ -48,6 +45,38 @@ public enum Algorithm {
}
}

public enum Field {
ORGANIZATION("Organization"),
COMMON_NAME("Common Name"),
TRUSTED("Trusted"),
VALID_FROM("Valid From"),
VALID_TO("Valid To"),
FINGERPRINT("Fingerprint"),
VALID("Valid");

String description;

// List of fields needed to construct a new cert.
public static final Field[] saveFields = new Field[] {FINGERPRINT, COMMON_NAME, ORGANIZATION, VALID_FROM, VALID_TO, VALID};
public static final Field[] displayFields = new Field[] {ORGANIZATION, COMMON_NAME, TRUSTED, VALID_FROM, VALID_TO, FINGERPRINT};
Field(String description) {
this.description = description;
}

@Override
public String toString() {
return description;
}

public String getDescription() {
return description;
}

public static int size() {
return values().length;
}
}

public static ArrayList<Certificate> rootCAs = new ArrayList<>();
public static Certificate builtIn;
private static CertPathValidator validator;
Expand All @@ -56,8 +85,6 @@ public enum Algorithm {
// id-at-description used for storing renewal information
private static ASN1ObjectIdentifier RENEWAL_OF = new ASN1ObjectIdentifier("2.5.4.13");

public static final String[] saveFields = new String[] {"fingerprint", "commonName", "organization", "validFrom", "validTo", "valid"};

public static final String SPONSORED_CN_PREFIX = "Sponsored:";

// Valid date range allows UI to only show "Expired" text for valid certificates
Expand Down Expand Up @@ -85,13 +112,13 @@ public enum Algorithm {
public static final Certificate UNKNOWN;

static {
HashMap<String,String> map = new HashMap<>();
map.put("fingerprint", "UNKNOWN REQUEST");
map.put("commonName", "An anonymous request");
map.put("organization", "Unknown");
map.put("validFrom", UNKNOWN_MIN.toString());
map.put("validTo", UNKNOWN_MAX.toString());
map.put("valid", "false");
HashMap<Field,String> map = new HashMap<>();
map.put(Field.FINGERPRINT, "UNKNOWN REQUEST");
map.put(Field.COMMON_NAME, "An anonymous request");
map.put(Field.ORGANIZATION, "Unknown");
map.put(Field.VALID_FROM, UNKNOWN_MIN.toString());
map.put(Field.VALID_TO, UNKNOWN_MAX.toString());
map.put(Field.VALID, "false");
UNKNOWN = Certificate.loadCertificate(map);
}

Expand Down Expand Up @@ -315,16 +342,16 @@ private Certificate() {}
/**
* Used to rebuild a certificate for the 'Saved Sites' screen without having to decrypt the certificates again
*/
public static Certificate loadCertificate(HashMap<String,String> data) {
public static Certificate loadCertificate(HashMap<Field,String> data) {
Certificate cert = new Certificate();

cert.fingerprint = data.get("fingerprint");
cert.commonName = data.get("commonName");
cert.organization = data.get("organization");
cert.fingerprint = data.get(Field.FINGERPRINT);
cert.commonName = data.get(Field.COMMON_NAME);
cert.organization = data.get(Field.ORGANIZATION);

try {
cert.validFrom = Instant.from(LocalDateTime.from(dateParse.parse(data.get("validFrom"))).atZone(ZoneOffset.UTC));
cert.validTo = Instant.from(LocalDateTime.from(dateParse.parse(data.get("validTo"))).atZone(ZoneOffset.UTC));
cert.validFrom = Instant.from(LocalDateTime.from(dateParse.parse(data.get(Field.VALID_FROM))).atZone(ZoneOffset.UTC));
cert.validTo = Instant.from(LocalDateTime.from(dateParse.parse(data.get(Field.VALID_TO))).atZone(ZoneOffset.UTC));
}
catch(DateTimeException e) {
cert.validFrom = UNKNOWN_MIN;
Expand All @@ -333,7 +360,7 @@ public static Certificate loadCertificate(HashMap<String,String> data) {
log.warn("Unable to parse certificate date: {}", e.getMessage());
}

cert.valid = Boolean.parseBoolean(data.get("valid"));
cert.valid = Boolean.parseBoolean(data.get(Field.VALID));

return cert;
}
Expand Down Expand Up @@ -405,6 +432,30 @@ private static boolean existsInAnyFile(String fingerprint, File... files) {
return false;
}

public String get(Field field) {
return get(field, null);
}

public String get(Field field, Object optArg) {
switch(field) {
case ORGANIZATION:
return getOrganization();
case COMMON_NAME:
return getCommonName();
case TRUSTED:
return String.valueOf(isTrusted());
case VALID_FROM:
return getValidFrom((TimeZone)optArg) + " " + ((TimeZone)optArg).getDisplayName(false, TimeZone.SHORT);
case VALID_TO:
return getValidTo((TimeZone)optArg) + " " + ((TimeZone)optArg).getDisplayName(false, TimeZone.SHORT);
case FINGERPRINT:
return getFingerprint();
case VALID:
return String.valueOf(isValid());
default:
return null;
}
}

public String getFingerprint() {
return fingerprint;
Expand All @@ -419,16 +470,31 @@ public String getOrganization() {
}

public String getValidFrom() {
if (validFrom.isAfter(UNKNOWN_MIN)) {
return dateFormat.format(validFrom.atZone(ZoneOffset.UTC));
} else {
return "Not Provided";
}
return formatDate(validFrom, null);
}

public String getValidFrom(TimeZone timeZone) {
return formatDate(validFrom, timeZone);
}

public String getValidTo() {
if (validTo.isBefore(UNKNOWN_MAX)) {
return dateFormat.format(validTo.atZone(ZoneOffset.UTC));
return formatDate(validTo, null);
}

public String getValidTo(TimeZone timeZone) {
return formatDate(validTo, timeZone);
}

public String formatDate(Instant date, TimeZone timeZone) {
ZoneId zoneId;
if (timeZone == null) {
zoneId = ZoneOffset.UTC;
} else {
zoneId = timeZone.toZoneId();
}

if (date.isBefore(UNKNOWN_MAX)) {
return dateFormat.format(date.atZone(zoneId));
} else {
return "Not Provided";
}
Expand Down
6 changes: 3 additions & 3 deletions src/qz/ui/SiteManagerDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -554,10 +554,10 @@ public ArrayList<CertificateDisplay> readCertificates(ArrayList<CertificateDispl
if (line.startsWith("#")) { continue; } //treat these lines as comments
String[] data = line.split("\\t");

if (data.length == Certificate.saveFields.length) {
HashMap<String,String> dataMap = new HashMap<>();
if (data.length == Certificate.Field.saveFields.length) {
HashMap<Certificate.Field,String> dataMap = new HashMap<>();
for(int i = 0; i < data.length; i++) {
dataMap.put(Certificate.saveFields[i], data[i]);
dataMap.put(Certificate.Field.saveFields[i], data[i]);
}

CertificateDisplay certificate = new CertificateDisplay(Certificate.loadCertificate(dataMap), local);
Expand Down
125 changes: 58 additions & 67 deletions src/qz/ui/component/CertificateTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,74 +7,53 @@

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.TimeZone;

/**
* Created by Tres on 2/22/2015.
* Displays Certificate information in a JTable
*/
public class CertificateTable extends DisplayTable implements Themeable {

/**
* Certificate fields to be displayed (and the corresponding function to Reflect upon)
*/
enum CertificateField {
ORGANIZATION("Organization", "getOrganization"),
COMMON_NAME("Common Name", "getCommonName"),
TRUSTED("Trusted", "isTrusted"),
VALID_FROM("Valid From", "getValidFrom"),
VALID_TO("Valid To", "getValidTo"),
FINGERPRINT("Fingerprint", "getFingerprint");

String description;
String callBack;

CertificateField(String description, String callBack) {
this.description = description;
this.callBack = callBack;
}

/**
* Returns the <code>String</code> value associated with this certificate field
*
* @return Certificate field such as "commonName"
*/
public String getValue(Certificate cert) {
if (cert == null) {
return "";
}

Reflect reflect = Reflect.on(cert).call(callBack);
Object value = reflect == null? null:reflect.get();
if (value == null) {
return "";
}
return value.toString();
}

@Override
public String toString() {
return description;
}

public String getDescription() {
return description;
}

public static int size() {
return values().length;
}
}

private Certificate cert;

private static Certificate cert;
private static final TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
private Instant warn;
private Instant now;
private boolean useLocalTimezone = false;

public CertificateTable(IconCache iconCache) {
super(iconCache);
setDefaultRenderer(Object.class, new CertificateTableCellRenderer());
addMouseListener(new MouseAdapter() {
int lastRow = -1;
int lastCol = -1;

@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
JTable target = (JTable)e.getSource();
int row = target.getSelectedRow();
int col = target.getSelectedColumn();
// Only trigger after the cell is click AND highlighted. This make copying text easier.
if (row == lastRow && col == lastCol) {
Certificate.Field rowKey = (Certificate.Field)target.getValueAt(row, 0);
if (rowKey == Certificate.Field.VALID_TO || rowKey == Certificate.Field.VALID_FROM) {
useLocalTimezone = !useLocalTimezone;
refreshComponents();
row = -1;
col = -1;
}
}
lastRow = row;
lastCol = col;
}

public void mouseClicked(MouseEvent e) {
}
});

}

public void setCertificate(Certificate cert) {
Expand All @@ -96,11 +75,12 @@ public void refreshComponents() {
removeRows();

// First Column
for(CertificateField field : CertificateField.values()) {
if(field.equals(CertificateField.TRUSTED) && !Certificate.isTrustBuiltIn()) {
for(Certificate.Field field : Certificate.Field.displayFields) {
if(field.equals(Certificate.Field.TRUSTED) && !Certificate.isTrustBuiltIn()) {
continue; // Remove "Verified by" text; uncertain in strict mode
}
model.addRow(new Object[] {field, field.getValue(cert)});
TimeZone timeZone = useLocalTimezone ? TimeZone.getDefault() : utcTimeZone;
model.addRow(new Object[] {field, cert.get(field, timeZone)});
}

repaint();
Expand All @@ -113,10 +93,9 @@ public void refresh() {
}

public void autoSize() {
super.autoSize(CertificateField.size(), 2);
super.autoSize(Certificate.Field.displayFields.length, 2);
}


/** Custom cell renderer for JTable to allow colors and styles not directly available in a JTable */
private class CertificateTableCellRenderer extends StyledTableCellRenderer {

Expand All @@ -125,8 +104,23 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);

// First Column
if (value instanceof CertificateField) {
label = stylizeLabel(STATUS_NORMAL, label, isSelected);
if (value instanceof Certificate.Field) {
switch((Certificate.Field)value) {
case VALID_FROM:
boolean futureExpiration = cert.getValidFromDate().isAfter(now);
label = stylizeLabel(futureExpiration? STATUS_WARNING:STATUS_NORMAL, label, isSelected, "future inception");
break;
case VALID_TO:
boolean expiresSoon = cert.getValidToDate().isBefore(warn);
boolean expired = cert.getValidToDate().isBefore(now);
String reason = expired? "expired":(expiresSoon? "expires soon":null);

label = stylizeLabel(expiresSoon || expired? STATUS_WARNING:STATUS_NORMAL, label, isSelected, reason);
break;
default:
label = stylizeLabel(STATUS_NORMAL, label, isSelected);
break;
}
if (iconCache != null) {
label.setIcon(iconCache.getIcon(IconCache.Icon.FIELD_ICON));
}
Expand All @@ -136,7 +130,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole
// Second Column
if (cert == null || col < 1) { return stylizeLabel(STATUS_NORMAL, label, isSelected); }

CertificateField field = (CertificateField)table.getValueAt(row, col - 1);
Certificate.Field field = (Certificate.Field)table.getValueAt(row, col - 1);
if (field == null) { return stylizeLabel(STATUS_NORMAL, label, isSelected); }
switch(field) {
case TRUSTED:
Expand All @@ -153,17 +147,14 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole
return stylizeLabel(!cert.isValid()? STATUS_WARNING:STATUS_TRUSTED, label, isSelected);
case VALID_FROM:
boolean futureExpiration = cert.getValidFromDate().isAfter(now);
return stylizeLabel(futureExpiration? STATUS_WARNING:STATUS_NORMAL, label, isSelected, "future inception");
return stylizeLabel(futureExpiration? STATUS_WARNING:STATUS_NORMAL, label, isSelected);
case VALID_TO:
boolean expiresSoon = cert.getValidToDate().isBefore(warn);
boolean expired = cert.getValidToDate().isBefore(now);
String reason = expired? "expired":(expiresSoon? "expires soon":null);
return stylizeLabel(expiresSoon || expired? STATUS_WARNING:STATUS_NORMAL, label, isSelected, reason);
return stylizeLabel(expiresSoon || expired? STATUS_WARNING:STATUS_NORMAL, label, isSelected);
default:
return stylizeLabel(STATUS_NORMAL, label, isSelected);
}
}

}

}

0 comments on commit 0b16615

Please sign in to comment.