aboutsummaryrefslogtreecommitdiffstats
path: root/Resources/old extended service/src/main/java/PgpToX509.java
blob: d83575baded358322b9ec77a9450aacf9ac79c30 (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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/*
 * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
 * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.sufficientlysecure.keychain.pgp;

import org.spongycastle.asn1.DERObjectIdentifier;
import org.spongycastle.asn1.x509.AuthorityKeyIdentifier;
import org.spongycastle.asn1.x509.BasicConstraints;
import org.spongycastle.asn1.x509.GeneralName;
import org.spongycastle.asn1.x509.GeneralNames;
import org.spongycastle.asn1.x509.SubjectKeyIdentifier;
import org.spongycastle.asn1.x509.X509Extensions;
import org.spongycastle.asn1.x509.X509Name;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.x509.X509V3CertificateGenerator;
import org.spongycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.spongycastle.x509.extension.SubjectKeyIdentifierStructure;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.Log;

import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Vector;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;

public class PgpToX509 {
    public static final String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge";
    public static final String DN_COMMON_PART_OU = "OpenPGP Keychain cert";

    /**
     * Creates a self-signed certificate from a public and private key. The (critical) key-usage
     * extension is set up with: digital signature, non-repudiation, key-encipherment, key-agreement
     * and certificate-signing. The (non-critical) Netscape extension is set up with: SSL client and
     * S/MIME. A URI subjectAltName may also be set up.
     *
     * @param pubKey         public key
     * @param privKey        private key
     * @param subject        subject (and issuer) DN for this certificate, RFC 2253 format preferred.
     * @param startDate      date from which the certificate will be valid (defaults to current date and time
     *                       if null)
     * @param endDate        date until which the certificate will be valid (defaults to current date and time
     *                       if null) *
     * @param subjAltNameURI URI to be placed in subjectAltName
     * @return self-signed certificate
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws NoSuchAlgorithmException
     * @throws IllegalStateException
     * @throws NoSuchProviderException
     * @throws CertificateException
     * @throws Exception
     * @author Bruno Harbulot
     */
    public static X509Certificate createSelfSignedCert(
        PublicKey pubKey, PrivateKey privKey, X509Name subject, Date startDate, Date endDate,
        String subjAltNameURI)
        throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
            SignatureException, CertificateException, NoSuchProviderException {

        X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();

        certGenerator.reset();
        /*
         * Sets up the subject distinguished name. Since it's a self-signed certificate, issuer and
         * subject are the same.
         */
        certGenerator.setIssuerDN(subject);
        certGenerator.setSubjectDN(subject);

        /*
         * Sets up the validity dates.
         */
        if (startDate == null) {
            startDate = new Date(System.currentTimeMillis());
        }
        certGenerator.setNotBefore(startDate);
        if (endDate == null) {
            endDate = new Date(startDate.getTime() + (365L * 24L * 60L * 60L * 1000L));
            Log.d(Constants.TAG, "end date is=" + DateFormat.getDateInstance().format(endDate));
        }

        certGenerator.setNotAfter(endDate);

        /*
         * The serial-number of this certificate is 1. It makes sense because it's self-signed.
         */
        certGenerator.setSerialNumber(BigInteger.ONE);
        /*
         * Sets the public-key to embed in this certificate.
         */
        certGenerator.setPublicKey(pubKey);
        /*
         * Sets the signature algorithm.
         */
        String pubKeyAlgorithm = pubKey.getAlgorithm();
        if (pubKeyAlgorithm.equals("DSA")) {
            certGenerator.setSignatureAlgorithm("SHA1WithDSA");
        } else if (pubKeyAlgorithm.equals("RSA")) {
            certGenerator.setSignatureAlgorithm("SHA1WithRSAEncryption");
        } else {
            RuntimeException re = new RuntimeException("Algorithm not recognised: "
                    + pubKeyAlgorithm);
            Log.e(Constants.TAG, re.getMessage(), re);
            throw re;
        }

        /*
         * Adds the Basic Constraint (CA: true) extension.
         */
        certGenerator.addExtension(X509Extensions.BasicConstraints, true,
                new BasicConstraints(true));

        /*
         * Adds the subject key identifier extension.
         */
        SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(pubKey);
        certGenerator
                .addExtension(X509Extensions.SubjectKeyIdentifier, false, subjectKeyIdentifier);

        /*
         * Adds the authority key identifier extension.
         */
        AuthorityKeyIdentifier authorityKeyIdentifier = new AuthorityKeyIdentifierStructure(pubKey);
        certGenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
                authorityKeyIdentifier);

        /*
         * Adds the subject alternative-name extension.
         */
        if (subjAltNameURI != null) {
            GeneralNames subjectAltNames = new GeneralNames(new GeneralName(
                    GeneralName.uniformResourceIdentifier, subjAltNameURI));
            certGenerator.addExtension(X509Extensions.SubjectAlternativeName, false,
                    subjectAltNames);
        }

        /*
         * Creates and sign this certificate with the private key corresponding to the public key of
         * the certificate (hence the name "self-signed certificate").
         */
        X509Certificate cert = certGenerator.generate(privKey);

        /*
         * Checks that this certificate has indeed been correctly signed.
         */
        cert.verify(pubKey);

        return cert;
    }

    /**
     * Creates a self-signed certificate from a PGP Secret Key.
     *
     * @param pgpSecKey      PGP Secret Key (from which one can extract the public and private
     *                       keys and other attributes).
     * @param pgpPrivKey     PGP Private Key corresponding to the Secret Key (password callbacks
     *                       should be done before calling this method)
     * @param subjAltNameURI optional URI to embed in the subject alternative-name
     * @return self-signed certificate
     * @throws PGPException
     * @throws NoSuchProviderException
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws CertificateException
     * @author Bruno Harbulot
     */
    public static X509Certificate createSelfSignedCert(
        PGPSecretKey pgpSecKey, PGPPrivateKey pgpPrivKey, String subjAltNameURI)
        throws PGPException, NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException,
            SignatureException, CertificateException {
        // get public key from secret key
        PGPPublicKey pgpPubKey = pgpSecKey.getPublicKey();

        // LOGGER.info("Key ID: " + Long.toHexString(pgpPubKey.getKeyID() & 0xffffffffL));

        /*
         * The X.509 Name to be the subject DN is prepared. The CN is extracted from the Secret Key
         * user ID.
         */
        Vector<DERObjectIdentifier> x509NameOids = new Vector<DERObjectIdentifier>();
        Vector<String> x509NameValues = new Vector<String>();

        x509NameOids.add(X509Name.O);
        x509NameValues.add(DN_COMMON_PART_O);

        x509NameOids.add(X509Name.OU);
        x509NameValues.add(DN_COMMON_PART_OU);

        for (@SuppressWarnings("unchecked")
             Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserIDs(); it.hasNext(); ) {
            Object attrib = it.next();
            x509NameOids.add(X509Name.CN);
            x509NameValues.add("CryptoCall");
            // x509NameValues.add(attrib.toString());
        }

        /*
         * Currently unused.
         */
        Log.d(Constants.TAG, "User attributes: ");
        for (@SuppressWarnings("unchecked")
             Iterator<Object> it = (Iterator<Object>) pgpSecKey.getUserAttributes(); it.hasNext(); ) {
            Object attrib = it.next();
            Log.d(Constants.TAG, " - " + attrib + " -- " + attrib.getClass());
        }

        X509Name x509name = new X509Name(x509NameOids, x509NameValues);

        Log.d(Constants.TAG, "Subject DN: " + x509name);

        /*
         * To check the signature from the certificate on the recipient side, the creation time
         * needs to be embedded in the certificate. It seems natural to make this creation time be
         * the "not-before" date of the X.509 certificate. Unlimited PGP keys have a validity of 0
         * second. In this case, the "not-after" date will be the same as the not-before date. This
         * is something that needs to be checked by the service receiving this certificate.
         */
        Date creationTime = pgpPubKey.getCreationTime();
        Log.d(Constants.TAG,
                "pgp pub key creation time=" + DateFormat.getDateInstance().format(creationTime));
        Log.d(Constants.TAG, "pgp valid seconds=" + pgpPubKey.getValidSeconds());
        Date validTo = null;
        if (pgpPubKey.getValidSeconds() > 0) {
            validTo = new Date(creationTime.getTime() + 1000L * pgpPubKey.getValidSeconds());
        }

        X509Certificate selfSignedCert = createSelfSignedCert(
                pgpPubKey.getKey(Constants.BOUNCY_CASTLE_PROVIDER_NAME), pgpPrivKey.getKey(),
                x509name, creationTime, validTo, subjAltNameURI);

        return selfSignedCert;
    }

    /**
     * This is a password callback handler that will fill in a password automatically. Useful to
     * configure passwords in advance, but should be used with caution depending on how much you
     * allow passwords to be stored within your application.
     *
     * @author Bruno Harbulot.
     */
    public static final class PredefinedPasswordCallbackHandler implements CallbackHandler {

        private char[] mPassword;
        private String mPrompt;

        public PredefinedPasswordCallbackHandler(String password) {
            this(password == null ? null : password.toCharArray(), null);
        }

        public PredefinedPasswordCallbackHandler(char[] password) {
            this(password, null);
        }

        public PredefinedPasswordCallbackHandler(String password, String prompt) {
            this(password == null ? null : password.toCharArray(), prompt);
        }

        public PredefinedPasswordCallbackHandler(char[] password, String prompt) {
            this.mPassword = password;
            this.mPrompt = prompt;
        }

        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
            for (Callback callback : callbacks) {
                if (callback instanceof PasswordCallback) {
                    PasswordCallback pwCallback = (PasswordCallback) callback;
                    if ((this.mPrompt == null) || (this.mPrompt.equals(pwCallback.getPrompt()))) {
                        pwCallback.setPassword(this.mPassword);
                    }
                } else {
                    throw new UnsupportedCallbackException(callback, "Unrecognised callback.");
                }
            }
        }

        protected final Object clone() throws CloneNotSupportedException {
            throw new CloneNotSupportedException();
        }
    }
}