aboutsummaryrefslogtreecommitdiff
path: root/src/main/ui/MgmtScreen.java
blob: 1957c7ef19dfbb0b35580630920ce2d268a379b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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;

/**
 * Manage the private key and CA certificate. It can print the public key, generate CSR, and install CA cert.
 */
public class MgmtScreen implements UIHandler {
    private final JCA session;

    /**
     * EFFECTS: Init with the parent session.
     */
    public MgmtScreen(JCA session) {
        this.session = session;
    }

    /**
     * EFFECTS: Print help
     */
    @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())));
    }

    /**
     * EFFECT: Generate a CSR
     */
    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());
        }
    }

    /**
     * EFFECTS: Throw {@link ParseException} if the incoming cert is not v3.
     */
    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");
        }
    }

    /**
     * EFFECTS: Throw {@link ParseException} if the incoming cert does not have the matching public key.
     */
    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");
        }
    }

    /**
     * EFFECTS: Throw {@link ParseException} if the incoming cert does not have cA = true in its basicConstraints.
     */
    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.");
        }
    }

    /**
     * EFFECTS: Throw {@link ParseException} if the incoming cert does not have valid key usages.
     */
    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.");
        }
    }

    /**
     * EFFECTS: Handle the 'install' command. Read incoming certificate and validate it.
     */
    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());
        }
    }

    /**
     * EFFECTS: Handle commands.
     */
    @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;
    }

    /**
     * EFFECTS: return "/ca/ #"
     */
    @Override
    public String getPS1() {
        return "/ca/ #";
    }
}