diff options
Diffstat (limited to 'src/main/ui')
-rw-r--r-- | src/main/ui/IssueScreen.java | 138 | ||||
-rw-r--r-- | src/main/ui/JCA.java | 203 | ||||
-rw-r--r-- | src/main/ui/Main.java | 4 | ||||
-rw-r--r-- | src/main/ui/MainScreen.java | 200 | ||||
-rw-r--r-- | src/main/ui/MgmtScreen.java | 170 | ||||
-rw-r--r-- | src/main/ui/Screen.java | 31 | ||||
-rw-r--r-- | src/main/ui/TemplateSetScreen.java | 131 | ||||
-rw-r--r-- | src/main/ui/TemplatesScreen.java | 108 | ||||
-rw-r--r-- | src/main/ui/UIHandler.java | 45 | ||||
-rw-r--r-- | src/main/ui/Utils.java | 44 |
10 files changed, 1049 insertions, 25 deletions
diff --git a/src/main/ui/IssueScreen.java b/src/main/ui/IssueScreen.java new file mode 100644 index 0000000..e152b0d --- /dev/null +++ b/src/main/ui/IssueScreen.java @@ -0,0 +1,138 @@ +package ui; + +import model.asn1.exceptions.ParseException; +import model.asn1.parsing.BytesReader; +import model.ca.Template; +import model.csr.CertificationRequest; +import model.pki.cert.Certificate; + +public class IssueScreen implements UIHandler { + private final JCA session; + + private Template template; + private CertificationRequest incomingCSR; + + /** + * EFFECTS: Init with the session. + */ + public IssueScreen(JCA session) { + this.session = session; + } + + /** + * EFFECTS: Set current template and CSR in use by args. + * REQUIRES: args.length = 2, args[0] instanceof CertificateRequest, args[1] instanceof Template + * MODIFIES: args[1] + */ + @Override + public void enter(Object... args) { + this.incomingCSR = (CertificationRequest) args[0]; + this.template = (Template) args[1]; + } + + @Override + public void help() { + System.out.print("show\tView the current certificate\n" + + "set\tSet properties or template\n" + + "commit\tIssue the certificate\n" + + "exit\tDiscard and go to main menu\n" + + "help\tPrint this message\n"); + } + + @Override + public void show() { + System.out.println("Requested Subject:\t" + incomingCSR.getCertificationRequestInfo().getSubject()); + System.out.println("Subject:\t" + (template.getSubject() == null + ? incomingCSR.getCertificationRequestInfo().getSubject() + : template.getSubject())); + System.out.println("Template:\t" + template.getName()); + System.out.println("Validity:\t" + template.getValidity() + " days"); + } + + @Override + public void commit() { + try { + Certificate certificate = session.getCa().signCert(incomingCSR.getCertificationRequestInfo(), template); + System.out.println(Utils.toPEM(certificate.encodeDER(), "CERTIFICATE")); + session.log("A certificate was issued."); + session.setScreen(Screen.MAIN); + } catch (Throwable e) { + System.out.println(e.getMessage()); + } + } + + private void handleIssueSetSubject(String val) { + try { + template.setSubject(val); + } catch (ParseException e) { + System.out.println(e.getMessage()); + } + } + + private void handleIssueSetValidity(String val) { + if (val == null) { + System.out.println("Cannot unset validity"); + return; + } + try { + long i = Long.parseLong(val); + if (i <= 0) { + System.out.println("Invalid validity days"); + return; + } + template.setValidity(i); + } catch (NumberFormatException ignored) { + System.out.println("Invalid validity days"); + } + } + + private void handleIssueSet(String... args) { + if (args.length != 2 && args.length != 3) { + System.out.println("Usage: set <key> <value>"); + System.out.println("Supported keys: subject validity"); + return; + } + String val = args.length == 3 ? args[2] : null; + switch (args[1]) { + case "subject": + handleIssueSetSubject(val); + break; + case "validity": + handleIssueSetValidity(val); + break; + default: + System.out.println("Unknown key"); + break; + } + } + + @Override + public void command(String... args) { + switch (args[0]) { + case "set": + handleIssueSet(args); + break; + default: + help(); + break; + } + } + + /** + * EFFECTS: Clear the certificates and return main. + * MODIFIES: this + */ + @Override + public Screen exit() { + incomingCSR = null; + template = null; + return Screen.MAIN; + } + + @Override + public String getPS1() { + return String.format("/%s/ %%", template.getSubject() == null + ? incomingCSR.getCertificationRequestInfo().getSubject() + : template.getSubject()); + } +} diff --git a/src/main/ui/JCA.java b/src/main/ui/JCA.java new file mode 100644 index 0000000..f9467ea --- /dev/null +++ b/src/main/ui/JCA.java @@ -0,0 +1,203 @@ +package ui; + +import model.asn1.exceptions.ParseException; +import model.ca.AuditLogEntry; +import model.ca.CACertificate; +import model.ca.Template; + +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.time.ZonedDateTime; +import java.util.*; + +/** + * Main program + */ +public class JCA { + /** + * The current screen. + */ + private UIHandler screen; + + /** + * Instances of the five screens; + */ + private final UIHandler mainScreen; + private final UIHandler mgmtScreen; + private final UIHandler issueScreen; + private final UIHandler templatesScreen; + private final UIHandler templateSetScreen; + + /** + * Templates + */ + private final List<Template> templates; + + /** + * The CA + */ + private final CACertificate ca; + + /** + * Audit logs + */ + private final List<AuditLogEntry> logs; + + /** + * Current user + */ + private final String user; + + /** + * EFFECTS: Init with main screen, empty templates, logs, user 'yuuta', and generate a private key with no CA cert. + * Throws {@link NoSuchAlgorithmException} when crypto issue happens. + */ + public JCA() throws NoSuchAlgorithmException { + this.mainScreen = new MainScreen(this); + this.mgmtScreen = new MgmtScreen(this); + this.issueScreen = new IssueScreen(this); + this.templatesScreen = new TemplatesScreen(this); + this.templateSetScreen = new TemplateSetScreen(this); + + setScreen(Screen.MAIN); + + this.templates = new ArrayList<>(); + this.ca = new CACertificate(); + this.logs = new ArrayList<>(); + this.user = "yuuta"; + + this.ca.generateKey(); + } + + /** + * EFFECT: Checks if the CA is installed or not (according to the desired state) and print if not matching. Returns + * true if matching. + */ + public boolean checkCA(boolean requireInstalled) { + if (requireInstalled && ca.getCertificate() == null) { + System.out.println("The CA is not installed yet"); + return false; + } else if (!requireInstalled && ca.getCertificate() != null) { + System.out.println("The CA is already installed"); + return false; + } + return true; + } + + /** + * EFFECTS: Read PEM from stdin, matched the given tag. + * Throws {@link ParseException} if the input is incorrect. + */ + public Byte[] handleInputPEM(String desiredTag) throws ParseException { + final Scanner scanner = new Scanner(System.in); + StringBuilder in = new StringBuilder(); + while (true) { + final String line = scanner.nextLine(); + in.append(line); + in.append("\n"); + if (line.matches("-----END .*-----")) { + break; + } + } + return Utils.parsePEM(Utils.byteToByte(in.toString().getBytes(StandardCharsets.UTF_8)), desiredTag); + } + + /** + * EFFECTS: Find the template based on name, or null if not found. + */ + public Template findTemplate(String name, boolean requireEnabled) { + Optional<Template> opt = templates.stream().filter(temp -> { + if (requireEnabled && !temp.isEnabled()) { + return false; + } + return temp.getName().equals(name); + }).findFirst(); + return opt.orElse(null); + } + + /** + * EFFECT: Set the current screen with optional args. Exit the program when mode is null. + * MODIFIES: this + */ + public void setScreen(Screen mode, Object... args) { + if (mode == null) { + System.exit(0); + } + switch (mode) { + case MAIN: + this.screen = mainScreen; + break; + case MGMT: + this.screen = mgmtScreen; + break; + case ISSUE: + this.screen = issueScreen; + break; + case TEMPLATES: + this.screen = templatesScreen; + break; + case TEMPLATE_SET: + this.screen = templateSetScreen; + break; + } + screen.enter(args); + } + + private void handleLine(String... args) { + if (!args[0].isBlank()) { + switch (args[0]) { + case "help": + screen.help(); + break; + case "show": + screen.show(); + break; + case "commit": + screen.commit(); + break; + case "exit": + setScreen(screen.exit()); + break; + default: + screen.command(args); + break; + } + } + printPS1(); + } + + private void printPS1() { + System.out.printf("%s@JCA %s ", user, screen.getPS1()); + } + + /** + * EFFECT: Log an action to the audit log + * MODIFIES: this + */ + public void log(String action) { + this.logs.add(new AuditLogEntry(user, ZonedDateTime.now(), action)); + } + + /** + * EFFECTS: Run the program + */ + public void run() { + printPS1(); + final Scanner scanner = new Scanner(System.in); + while (true) { + handleLine(scanner.nextLine().split(" ")); + } + } + + public List<Template> getTemplates() { + return templates; + } + + public CACertificate getCa() { + return ca; + } + + public List<AuditLogEntry> getLogs() { + return logs; + } +} diff --git a/src/main/ui/Main.java b/src/main/ui/Main.java index d80be21..1429532 100644 --- a/src/main/ui/Main.java +++ b/src/main/ui/Main.java @@ -1,7 +1,7 @@ package ui; public class Main { - public static void main(String[] args) { - + public static void main(String[] args) throws Throwable { + new JCA().run(); } } diff --git a/src/main/ui/MainScreen.java b/src/main/ui/MainScreen.java new file mode 100644 index 0000000..69cb32c --- /dev/null +++ b/src/main/ui/MainScreen.java @@ -0,0 +1,200 @@ +package ui; + +import model.asn1.ASN1Object; +import model.asn1.UtcTime; +import model.asn1.exceptions.ParseException; +import model.asn1.parsing.BytesReader; +import model.ca.Template; +import model.csr.CertificationRequest; +import model.pki.cert.Certificate; +import model.pki.crl.Reason; +import model.pki.crl.RevokedCertificate; + +import java.io.*; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Optional; + +public class MainScreen implements UIHandler { + private final JCA session; + + /** + * EFFECTS: Init with the parent session. + */ + public MainScreen(JCA session) { + this.session = session; + } + + @Override + public void help() { + System.out.print("mgmt\tView and manage the CA certificate\n" + + "issue\tIssue a certificate\n" + + "show\tList all issued certificates\n" + + "export\tExport a certificate to file (DER)\n" + + "template\tManage templates\n" + + "revoke\tRevoke a certificate\n" + + "crl\t\tSign CRL\n" + + "log\t\tView audit logs\n" + + "exit\tExit\n" + + "help\tPrint this message\n"); + } + + @Override + public void show() { + session.getCa().getSigned().forEach(cert -> { + System.out.printf("%s\t%d\t%s\n", + cert.getCertificate().getSubject().toString(), + cert.getCertificate().getSerialNumber().getLong(), + session.getCa().getRevoked().stream().anyMatch(rev -> rev.getSerialNumber().getLong() + == cert.getCertificate().getSerialNumber().getLong()) ? "REVOKED" : "OK"); + }); + } + + private CertificationRequest handleIssueInputCSR() { + try { + return new CertificationRequest(new BytesReader(session.handleInputPEM("CERTIFICATE REQUEST")), + false); + } catch (ParseException e) { + System.out.println(e.getMessage()); + return null; + } + } + + private void handleIssue(String... args) { + if (!session.checkCA(true)) { + return; + } + if (args.length <= 1) { + System.out.println("Usage: issue <template>"); + return; + } + Template tmp = session.findTemplate(args[1], true); + if (tmp == null) { + System.out.println("Cannot find the template specified"); + return; + } + CertificationRequest req = handleIssueInputCSR(); + if (req != null) { + session.setScreen(Screen.ISSUE, req, new Template(tmp.getName(), + true, + tmp.getSubject(), + tmp.getValidity())); + } + } + + /** + * EFFECTS: Find issued and not revoked certificate by serial. Return null if not found. + */ + private Certificate findCertBySerial(int serial) { + Optional<Certificate> c = session.getCa().getSigned() + .stream() + .filter(cert -> cert.getCertificate().getSerialNumber().getLong() == serial) + .findFirst(); + if (c.isEmpty()) { + System.out.println("Cannot find the certificate specified"); + return null; + } + if (session.getCa().getRevoked().stream().anyMatch(rev -> rev.getSerialNumber().getLong() == serial)) { + System.out.println("The certificate has already been revoked."); + return null; + } + return c.get(); + } + + private void handleRevoke(String... args) { + if (args.length < 3) { + System.out.println("Usage: revoke <serial> <reason>"); + return; + } + try { + final Reason reason = Reason.valueOf(args[2]); + int serial = Integer.parseInt(args[1]); + Certificate c = findCertBySerial(serial); + if (c == null) { + return; + } + session.getCa().revoke(new RevokedCertificate(ASN1Object.TAG_SEQUENCE, null, + c.getCertificate().getSerialNumber(), + new UtcTime(UtcTime.TAG, null, ZonedDateTime.now(ZoneId.of("UTC"))), reason)); + session.log("A certificate has been revoked."); + } catch (IllegalArgumentException ignored) { + System.out.println("Illegal serial number or reason"); + } + } + + private void handleExport(String... args) { + if (args.length < 3) { + System.out.println("Usage: export <serial> <path>"); + return; + } + try { + int serial = Integer.parseInt(args[1]); + Certificate c = findCertBySerial(serial); + if (c == null) { + return; + } + final File fd = new File(args[2]); + final OutputStream out = new FileOutputStream(fd); + out.write(Utils.byteToByte(c.encodeDER())); + out.close(); + } catch (IllegalArgumentException ignored) { + System.out.println("Illegal serial number or reason"); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + + private void handleCRL() { + if (!session.checkCA(true)) { + return; + } + try { + System.out.println(Utils.toPEM(session.getCa().signCRL().encodeDER(), "X509 CRL")); + session.log("A CRL was signed"); + } catch (Throwable e) { + System.out.println(e.getMessage()); + } + } + + private void handleLog() { + session.getLogs().forEach(System.out::println); + } + + @Override + public void command(String... args) { + switch (args[0]) { + case "mgmt": + session.setScreen(Screen.MGMT); + return; + case "issue": + handleIssue(args); + return; + case "revoke": + handleRevoke(args); + return; + case "export": + handleExport(args); + return; + case "template": + session.setScreen(Screen.TEMPLATES); + return; + case "crl": + handleCRL(); + return; + case "log": + handleLog(); + return; + } + help(); + } + + @Override + public Screen exit() { + return null; + } + + @Override + public String getPS1() { + return "/ %"; + } +} diff --git a/src/main/ui/MgmtScreen.java b/src/main/ui/MgmtScreen.java new file mode 100644 index 0000000..613aa50 --- /dev/null +++ b/src/main/ui/MgmtScreen.java @@ -0,0 +1,170 @@ +package ui; + +import model.asn1.ASN1Object; +import model.asn1.BitString; +import model.asn1.Bool; +import model.asn1.ObjectIdentifier; +import model.asn1.exceptions.ParseException; +import model.asn1.parsing.BytesReader; +import model.csr.CertificationRequest; +import model.pki.SubjectPublicKeyInfo; +import model.pki.cert.Certificate; +import model.pki.cert.Extension; +import model.pki.cert.TbsCertificate; + +import java.util.Arrays; +import java.util.Base64; +import java.util.BitSet; + +public class MgmtScreen implements UIHandler { + private final JCA session; + + /** + * EFFECTS: Init with the parent session. + */ + public MgmtScreen(JCA session) { + this.session = session; + } + + @Override + public void help() { + System.out.print("show\tView the public key and CA certificate\n" + + "csr\tGenerate a CSR for a upper-level CA to sign\n" + + "install\tInstall a CA certificate\n" + + "exit\tGo to main menu\n" + + "help\tPrint this message\n"); + } + + /** + * EFFECTS: Format the public key and CA + */ + @Override + public void show() { + System.out.printf("Public Key:\t%s\n", + Base64.getEncoder().encodeToString(session.getCa().getPublicKey().getEncoded())); + if (!session.checkCA(true)) { + return; + } + final TbsCertificate info = session.getCa().getCertificate().getCertificate(); + System.out.printf("Subject:\t%s\n", info.getSubject().toString()); + System.out.printf("Issuer:\t%s\n", info.getIssuer().toString()); + System.out.printf("Not Before:\t%s\n", info.getValidity().getNotBefore().getTimestamp()); + System.out.printf("Not After:\t%s\n", info.getValidity().getNotAfter().getTimestamp()); + System.out.printf("Signature:\t%s\n", + Base64.getEncoder().encodeToString(Utils.byteToByte(info.getSubjectPublicKeyInfo() + .getSubjectPublicKey().getConvertedVal()))); + } + + private void handleCSR() { + if (!session.checkCA(false)) { + return; + } + try { + CertificationRequest req = session.getCa().signCSR(); + System.out.println(Utils.toPEM(req.encodeDER(), "CERTIFICATE REQUEST")); + session.log("Signed CA CSR."); + } catch (Throwable e) { + System.out.println(e.getMessage()); + } + } + + private void validateCACertificateVersion(Certificate cert) throws ParseException { + if (cert.getCertificate().getVersion() == null + || cert.getCertificate().getVersion().getLong() != TbsCertificate.VERSION_V3) { + throw new ParseException("The input certificate must be V3"); + } + } + + private void validateCACertificatePublicKey(Certificate cert) throws ParseException { + final SubjectPublicKeyInfo expectedPKInfo = session.getCa().getCAPublicKeyInfo(); + if (!Arrays.equals(cert.getCertificate().getSubjectPublicKeyInfo().getAlgorithm().getType().getInts(), + expectedPKInfo.getAlgorithm().getType().getInts()) + || !Arrays.equals(cert.getCertificate().getSubjectPublicKeyInfo().getSubjectPublicKey().getVal(), + expectedPKInfo.getSubjectPublicKey().getVal())) { + throw new ParseException("The input certificate does not have the corresponding public key"); + } + } + + private void validateCACertificateBasicConstraints(Certificate cert) throws ParseException { + final Extension basicConstraints = cert.getCertificate().getExtension(ObjectIdentifier.OID_BASIC_CONSTRAINTS); + if (basicConstraints == null + || basicConstraints.getExtnValue().getBytes().length <= 0) { + throw new ParseException("The certificate does not have a valid basicConstraints extension."); + } + final ASN1Object basicConstraintsValue = + new ASN1Object(new BytesReader(basicConstraints.getExtnValue().getBytes()), false); + if (basicConstraintsValue.getLength() <= 0) { + throw new ParseException("The certificate does not have a valid basicConstraints extension."); + } + final ASN1Object bool = + ASN1Object.parse(new BytesReader(basicConstraintsValue.encodeValueDER()), false); + if (!(bool instanceof Bool) + || !((Bool) bool).getValue()) { + throw new ParseException("The certificate does not have a valid basicConstraints extension."); + } + } + + private void validateCACertificateKeyUsage(Certificate cert) throws ParseException { + final Extension keyUsage = cert.getCertificate().getExtension(ObjectIdentifier.OID_KEY_USAGE); + if (keyUsage == null + || keyUsage.getExtnValue().getBytes().length <= 0) { + throw new ParseException("The certificate does not have a valid keyUsage extension."); + } + final ASN1Object keyUsageValue = + ASN1Object.parse(new BytesReader(keyUsage.getExtnValue().getBytes()), false); + if (keyUsageValue.getLength() <= 0 + || !(keyUsageValue instanceof BitString)) { + throw new ParseException("The certificate does not have a valid keyUsage extension."); + } + final BitSet bitSet = BitSet.valueOf(Utils.byteToByte(((BitString) keyUsageValue).getVal())); + if (!bitSet.get(7) || !bitSet.get(2) || !bitSet.get(1)) { + throw new ParseException("The certificate does not have a valid keyUsage extension."); + } + } + + private void handleInstall() { + if (!session.checkCA(false)) { + return; + } + try { + final Byte[] in = session.handleInputPEM("CERTIFICATE"); + Certificate cert = new Certificate(new BytesReader(in), false); + validateCACertificateVersion(cert); + validateCACertificatePublicKey(cert); + validateCACertificateBasicConstraints(cert); + validateCACertificateKeyUsage(cert); + session.getCa().installCertificate(cert); + session.log("A CA certificate is installed."); + } catch (ParseException e) { + System.out.println(e.getMessage()); + } + } + + @Override + public void command(String... args) { + switch (args[0]) { + case "csr": + handleCSR(); + break; + case "install": + handleInstall(); + break; + default: + help(); + break; + } + } + + /** + * EFFECTS: Go to main menu + */ + @Override + public Screen exit() { + return Screen.MAIN; + } + + @Override + public String getPS1() { + return "/ca/ #"; + } +}
\ No newline at end of file diff --git a/src/main/ui/Screen.java b/src/main/ui/Screen.java new file mode 100644 index 0000000..31370e1 --- /dev/null +++ b/src/main/ui/Screen.java @@ -0,0 +1,31 @@ +package ui; + +/** + * The screen type + */ +public enum Screen { + /** + * Main menu (mgmt, issue, template, crl, show, revoke, log) + */ + MAIN, + + /** + * The CA management menu (show, csr, install) + */ + MGMT, + + /** + * The issue menu (show, set, commit) + */ + ISSUE, + + /** + * The templates menu (show, add, enable, disable, remove) + */ + TEMPLATES, + + /** + * The template edit menu (show, set, commit) + */ + TEMPLATE_SET +} diff --git a/src/main/ui/TemplateSetScreen.java b/src/main/ui/TemplateSetScreen.java new file mode 100644 index 0000000..9a31f50 --- /dev/null +++ b/src/main/ui/TemplateSetScreen.java @@ -0,0 +1,131 @@ +package ui; + +import model.asn1.exceptions.ParseException; +import model.ca.Template; + +public class TemplateSetScreen implements UIHandler { + private final JCA session; + + /** + * EFFECTS: Init with session. + */ + public TemplateSetScreen(JCA session) { + this.session = session; + } + + private Template template; + + @Override + public void help() { + System.out.println("show\tView the current template settings\n" + + "set\tSet key value\n" + + "commit\tSave the template\n" + + "exit\tDiscard changes\n" + + "help\tPrint this help message\n"); + } + + private void handleSetSubject(String val) { + try { + template.setSubject(val); + } catch (ParseException e) { + System.out.println(e.getMessage()); + } + } + + private void handleSetValidity(String val) { + if (val == null) { + System.out.println("Cannot unset validity"); + return; + } + try { + long i = Long.parseLong(val); + if (i <= 0) { + System.out.println("Invalid validity days"); + return; + } + template.setValidity(i); + } catch (NumberFormatException ignored) { + System.out.println("Invalid validity days"); + } + } + + private void handleSet(String... args) { + if (args.length != 2 && args.length != 3) { + System.out.println("Usage: set <key> <value>"); + System.out.println("Supported keys: subject validity"); + return; + } + String val = args.length == 3 ? args[2] : null; + switch (args[1]) { + case "subject": + handleSetSubject(val); + break; + case "validity": + handleSetValidity(val); + break; + default: + System.out.println("Unknown key"); + break; + } + } + + /** + * EFFECTS: Add the template to store and switch to templates screen. + * MODIFIES: session + */ + @Override + public void commit() { + session.getTemplates().add(template); + session.setScreen(Screen.TEMPLATES); + session.log("A new template is added."); + } + + /** + * EFFECTS: Show template info. + */ + @Override + public void show() { + System.out.println("Subject:\t" + template.getSubject()); + System.out.println("Validity:\t" + template.getValidity() + " days"); + } + + @Override + public void command(String... args) { + switch (args[0]) { + case "set": + handleSet(args); + break; + default: + case "help": + help(); + break; + } + } + + /** + * EFFECTS: Return to templates list and clear the current template in editing. + */ + @Override + public Screen exit() { + template = null; + return Screen.TEMPLATES; + } + + /** + * EFFECTS: yuuta@JCA /templates/name/ % + */ + @Override + public String getPS1() { + return String.format("/templates/%s/ %%", template.getName()); + } + + /** + * EFFECT: Edit args[0]. + * REQUIRES: args.length = 1; args[0] instanceof Template + * MODIFIES: args[0] + */ + @Override + public void enter(Object... args) { + template = (Template) args[0]; + } +}
\ No newline at end of file diff --git a/src/main/ui/TemplatesScreen.java b/src/main/ui/TemplatesScreen.java new file mode 100644 index 0000000..9b0bf3e --- /dev/null +++ b/src/main/ui/TemplatesScreen.java @@ -0,0 +1,108 @@ +package ui; + +import model.ca.Template; + +public class TemplatesScreen implements UIHandler { + private final JCA session; + + /** + * EFFECTS: Init with the session. + */ + public TemplatesScreen(JCA session) { + this.session = session; + } + + @Override + public void help() { + System.out.println("show\tList templates\n" + + "add\tCreate a new template\n" + + "enable\tEnable a template\n" + + "disable\tDisable a template\n" + + "delete\tDelete a template\n" + + "exit\tGo to main menu\n" + + "help\tPrint this message"); + } + + @Override + public void show() { + session.getTemplates().forEach(tem -> + System.out.printf("%s[%s]\t%s\t%d Days\n", + tem.getName(), + tem.isEnabled() ? "ENABLED" : "DISABLED", + tem.getSubject(), + tem.getValidity())); + } + + private void handleAdd(String... args) { + if (args.length <= 1) { + System.out.println("Usage: add <name>"); + return; + } + if (session.findTemplate(args[1], false) != null) { + System.out.println("The template already exists."); + return; + } + + session.setScreen(Screen.TEMPLATE_SET, + new Template(args[1], false, null, 30)); + } + + private void handleEnableDisable(boolean enable, String... args) { + if (args.length <= 1) { + System.out.printf("Usage: %s <template>\n", enable ? "enable" : "disable"); + return; + } + Template tmp = session.findTemplate(args[1], false); + if (tmp == null) { + System.out.println("Cannot find the template specified"); + return; + } + tmp.setEnabled(enable); + session.log("A template was enabled / disabled."); + } + + private void handleDelete(String... args) { + if (args.length <= 1) { + System.out.println("Usage: delete <template>"); + return; + } + Template tmp = session.findTemplate(args[1], true); + if (tmp == null) { + System.out.println("Cannot find the template specified"); + return; + } + session.getTemplates().remove(tmp); + session.log("A template was deleted."); + } + + @Override + public void command(String... args) { + switch (args[0]) { + case "add": + handleAdd(args); + break; + case "enable": + handleEnableDisable(true, args); + break; + case "disable": + handleEnableDisable(false, args); + break; + case "delete": + handleDelete(args); + break; + default: + help(); + break; + } + } + + @Override + public Screen exit() { + return Screen.MAIN; + } + + @Override + public String getPS1() { + return "/templates/ %"; + } +} diff --git a/src/main/ui/UIHandler.java b/src/main/ui/UIHandler.java new file mode 100644 index 0000000..f451542 --- /dev/null +++ b/src/main/ui/UIHandler.java @@ -0,0 +1,45 @@ +package ui; + +/** + * Represents a screen + */ +public interface UIHandler { + /** + * EFFECTS: Called when the screen is switched to. + */ + default void enter(Object... args) { + + } + + /** + * EFFECTS: Show objects. command() will not be called. + */ + void show(); + + /** + * EFFECTS: Commit changes and exit. command() will not be called. + */ + default void commit() { + } + + /** + * EFFECTS: Discard changes and exit. command() will not be called. Returns the next screen. + */ + Screen exit(); + + /** + * EFFECTS: Run help. command() will not be called. + */ + void help(); + + /** + * EFFECTS: Any commands rather than commit / exit / help. + * REQUIRES: args != null && args.length >= 1 + */ + void command(String... args); + + /** + * EFFECTS: Return the current PS1 prompt. + */ + String getPS1(); +} diff --git a/src/main/ui/Utils.java b/src/main/ui/Utils.java index ccb244e..3ed1300 100644 --- a/src/main/ui/Utils.java +++ b/src/main/ui/Utils.java @@ -49,19 +49,8 @@ public final class Utils { } /** - * EFFECTS: Pack the big-endian bytes into a 64bit integer. - * Throws {@link model.asn1.exceptions.ParseException} if the value is too large. - */ - public static long bytesToLong(Byte[] array) throws ParseException { - try { - return new BigInteger(byteToByte(array)).longValueExact(); - } catch (ArithmeticException ignored) { - throw new ParseException("Value is too large."); - } - } - - /** - * EFFECTS: Unpack the multibyte 64bit integer to its shortest array of byte format. + * EFFECTS: Unpack the multibyte 64bit integer to its shortest array of byte format. Remove leading empty byte + * if that number is not zero. */ public static Byte[] valToByte(long val) { byte[] v = BigInteger.valueOf(val).toByteArray(); @@ -77,15 +66,8 @@ public final class Utils { } /** - * EFFECTS: Parse the two-digit octet string into an unsigned byte, preserving leading zero and negative values. - * REQUIRES: The input octet must be a two-char string, with each char matching [0-9][A-F]. - */ - public static Byte parseByte(String octet) { - return (byte) Integer.parseInt(octet, 16); - } - - /** - * EFFECTS: Decode the input PEM file, with optional check on tags. + * EFFECTS: Decode the input PEM file, with optional check on tags. \n must present after each line, optional after + * the last. * Throws {@link ParseException} if the desiredTag is specified but the input does not have the specific tag, or * if the input does not have any tags at all (not a PEM). */ @@ -93,7 +75,7 @@ public final class Utils { final String str = new String(byteToByte(input), StandardCharsets.UTF_8); Pattern pattern = Pattern.compile("^-----BEGIN " + desiredTag - + "-----$\n^(.*)$\n^-----END " + desiredTag + "-----$", + + "-----$\n^(.*)$\n^-----END " + desiredTag + "-----$\n*", Pattern.DOTALL | Pattern.MULTILINE); final Matcher matcher = pattern.matcher(str); if (!matcher.matches()) { @@ -102,4 +84,20 @@ public final class Utils { final String b64 = matcher.group(1).replace("\n", ""); return byteToByte(Base64.getDecoder().decode(b64)); } + + /** + * EFFECTS: Base64 encode the input bytes and convert them into PEM format. + * REQUIRES: desiredTag must be upper case and not empty. + */ + public static String toPEM(Byte[] input, String tag) { + StringBuilder builder = new StringBuilder(); + builder.append("-----BEGIN "); + builder.append(tag); + builder.append("-----\n"); + builder.append(Base64.getEncoder().encodeToString(byteToByte(input))); + builder.append("\n-----END "); + builder.append(tag); + builder.append("-----"); + return builder.toString(); + } } |