001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014: * License for the specific language governing permissions and limitations under
015: * the License.
016: */
017:
018: package org.apache.harmony.tools.keytool;
019:
020: import java.io.FileNotFoundException;
021: import java.io.IOException;
022: import java.math.BigInteger;
023: import java.security.InvalidKeyException;
024: import java.security.KeyPair;
025: import java.security.KeyPairGenerator;
026: import java.security.KeyStore;
027: import java.security.KeyStoreException;
028: import java.security.NoSuchAlgorithmException;
029: import java.security.NoSuchProviderException;
030: import java.security.PrivateKey;
031: import java.security.PublicKey;
032: import java.security.Signature;
033: import java.security.SignatureException;
034: import java.security.UnrecoverableKeyException;
035: import java.security.cert.Certificate;
036: import java.security.cert.CertificateException;
037: import java.security.cert.X509Certificate;
038: import java.util.Collections;
039: import java.util.Date;
040: import java.util.Random;
041:
042: import javax.crypto.KeyGenerator;
043: import javax.crypto.SecretKey;
044:
045: import org.apache.harmony.security.provider.cert.X509CertImpl;
046: import org.apache.harmony.security.utils.AlgNameMapper;
047: import org.apache.harmony.security.x501.Name;
048: import org.apache.harmony.security.x509.AlgorithmIdentifier;
049: import org.apache.harmony.security.x509.BasicConstraints;
050: import org.apache.harmony.security.x509.Extension;
051: import org.apache.harmony.security.x509.Extensions;
052: import org.apache.harmony.security.x509.SubjectPublicKeyInfo;
053: import org.apache.harmony.security.x509.TBSCertificate;
054: import org.apache.harmony.security.x509.Validity;
055:
056: /**
057: * Class for generating keys and key pairs, wrapping them into self-signed X.509
058: * certificates.
059: */
060: public class KeyCertGenerator {
061:
062: /**
063: * Generates a key pair or a secret key. Key pair is composed of a private
064: * and a public key. Method wraps the public key into a self-signed X.509
065: * (v1, v2, v3) certificate and puts the certificate into a single-element
066: * certificate chain or signs the certificate with private key from another
067: * key entry and adds its chain to the newly generated certificate . After
068: * that the method adds to the keystore a new entry containing the generated
069: * private key and the chain. If a secret key is generated it is put into a
070: * secret key entry, with null certificate chain.
071: *
072: * @throws NoSuchAlgorithmException
073: * @throws KeyStoreException
074: * @throws KeytoolException
075: * @throws IOException
076: * @throws SignatureException
077: * @throws NoSuchProviderException
078: * @throws InvalidKeyException
079: * @throws UnrecoverableKeyException
080: * @throws CertificateException
081: */
082: static void genKey(KeytoolParameters param)
083: throws NoSuchAlgorithmException, KeyStoreException,
084: KeytoolException, InvalidKeyException,
085: NoSuchProviderException, SignatureException, IOException,
086: UnrecoverableKeyException, CertificateException {
087:
088: if (param.isSecretKey()) {
089: generateSecretKey(param);
090: } else {
091: generatePrivateKey(param);
092: }
093: }
094:
095: // Generates a key pair composed of a private and a public key, wraps the
096: // public key into a self-signed X.509 (v1, v2, v3) certificate, puts the
097: // certificate into a single-element certificate chain and adds to the
098: // keystore a new entry containing the private key and the chain.
099: private static void generatePrivateKey(KeytoolParameters param)
100: throws NoSuchAlgorithmException, KeyStoreException,
101: NoSuchProviderException, InvalidKeyException,
102: SignatureException, IOException, KeytoolException,
103: UnrecoverableKeyException, CertificateException {
104:
105: KeyStore keyStore = param.getKeyStore();
106:
107: PrivateKey issuerPrivateKey = null;
108: Certificate[] issuerCertChain = null;
109: String issuerDN = null;
110: // if the generated certificate shouldn't be self-signed
111: // but should be signed with a chain.
112: boolean selfSigned = (param.getIssuerAlias() == null);
113: if (!selfSigned) {
114: String issuerAlias = param.getIssuerAlias();
115: if (!keyStore.containsAlias(issuerAlias)) {
116: throw new KeytoolException("Certificate issuer alias <"
117: + issuerAlias + "> does not exist.");
118: }
119:
120: if (!keyStore.entryInstanceOf(issuerAlias,
121: KeyStore.PrivateKeyEntry.class)) {
122: throw new KeytoolException("Issuer alias <"
123: + issuerAlias
124: + "> is not a private key entry. ");
125: }
126: issuerCertChain = keyStore.getCertificateChain(issuerAlias);
127: issuerPrivateKey = (PrivateKey) keyStore.getKey(
128: issuerAlias, param.getIssuerPass());
129: issuerDN = ((X509Certificate) issuerCertChain[0])
130: .getSubjectX500Principal().getName();
131: }
132:
133: KeyPairGenerator kpg = null;
134: String keyAlg = param.getKeyAlg();
135: // key algorithm is DSA by default
136: if (keyAlg == null) {
137: keyAlg = "DSA";
138: }
139:
140: String sigAlgName = null;
141:
142: // if signature algorithm is not set, use a default
143: if (param.getSigAlg() != null) {
144: sigAlgName = param.getSigAlg();
145: } else if (selfSigned) {
146: if (keyAlg.equalsIgnoreCase("DSA")) {
147: sigAlgName = "SHA1withDSA";
148: } else if (keyAlg.equalsIgnoreCase("RSA")) {
149: sigAlgName = "MD5withRSA";
150: } else {
151: sigAlgName = keyAlg;
152: }
153: } else {
154: String issuerKeyAlg = issuerPrivateKey.getAlgorithm();
155: if (issuerKeyAlg.equalsIgnoreCase("DSA")) {
156: sigAlgName = "SHA1withDSA";
157: } else if (issuerKeyAlg.equalsIgnoreCase("RSA")) {
158: sigAlgName = "MD5withRSA";
159: } else {
160: sigAlgName = issuerKeyAlg;
161: }
162: }
163: // set the certificate validity period
164: // 90 days by default
165: long validity = (param.getValidity() != 0) ? param
166: .getValidity() : 90;
167:
168: // set the X.509 version to use with the certificate
169: int version = (param.getX509version() != 0) ?
170: // TBSCertificate needs 0, 1 or 2 as version in constructor (not 1,2,3);
171: // X.509 v.3 certificates by default
172: param.getX509version() - 1
173: : 2;
174:
175: // set certificate serial number
176: BigInteger serialNr;
177: if (param.getCertSerialNr() != 0) {
178: serialNr = BigInteger.valueOf(param.getCertSerialNr());
179: } else {
180: int randomInt = new Random().nextInt();
181: if (randomInt < 0) {
182: randomInt = -randomInt;
183: }
184: serialNr = BigInteger.valueOf(randomInt);
185: }
186:
187: int keySize = param.getKeySize();
188: if (param.isVerbose()) {
189: StringBuffer strBuf = new StringBuffer("Generating a "
190: + keyAlg + " key pair, key length " + keySize
191: + " bit \nand a ");
192: if (selfSigned) {
193: strBuf.append("self-signed ");
194: }
195: strBuf.append("certificate (signature algorithm is "
196: + sigAlgName + ")\n for " + param.getDName());
197: System.out.println(strBuf);
198: }
199:
200: // generate a pair of keys
201: String provider = param.getProvider();
202: String keyProvider = (param.getKeyProvider() != null) ? param
203: .getKeyProvider() : provider;
204: try {
205: if (keyProvider == null) {
206: kpg = KeyPairGenerator.getInstance(keyAlg);
207: } else {
208: kpg = KeyPairGenerator.getInstance(keyAlg, keyProvider);
209: }
210: } catch (NoSuchAlgorithmException e) {
211: throw new NoSuchAlgorithmException("The algorithm "
212: + keyAlg
213: + " is not available in current environment.");
214: } catch (NoSuchProviderException e) {
215: throw (NoSuchProviderException) new NoSuchProviderException(
216: "The provider " + keyProvider
217: + " is not found in the environment.")
218: .initCause(e);
219: }
220: // initialize the KeyPairGenerator with the key size
221: kpg.initialize(keySize);
222:
223: KeyPair keyPair = kpg.genKeyPair();
224: PrivateKey privateKey = keyPair.getPrivate();
225:
226: String subjectDN = param.getDName();
227: String sigProvider = (param.getSigProvider() != null) ? param
228: .getSigProvider() : provider;
229:
230: if (selfSigned) {
231: // generate the certificate
232: X509CertImpl x509cert = genX509CertImpl(sigAlgName,
233: version, serialNr, subjectDN, subjectDN, validity,
234: keyPair.getPublic(), privateKey, sigProvider, param
235: .isCA());
236:
237: // put the key pair with the newly created cert into the keystore
238: keyStore.setKeyEntry(param.getAlias(), privateKey, param
239: .getKeyPass(), new X509Certificate[] { x509cert });
240:
241: } else {
242: // generate the certificate
243: X509CertImpl x509cert = genX509CertImpl(sigAlgName,
244: version, serialNr, subjectDN, issuerDN, validity,
245: keyPair.getPublic(), issuerPrivateKey, sigProvider,
246: param.isCA());
247:
248: // construct the certificate chain
249: int issuerChainLength = issuerCertChain.length;
250: X509Certificate[] certChain = new X509Certificate[issuerChainLength + 1];
251: certChain[0] = x509cert;
252: System.arraycopy(issuerCertChain, 0, certChain, 1,
253: issuerChainLength);
254:
255: // put the key pair with the newly created cert into the keystore
256: keyStore.setKeyEntry(param.getAlias(), privateKey, param
257: .getKeyPass(), certChain);
258:
259: }
260:
261: param.setNeedSaveKS(true);
262: }
263:
264: /**
265: * Generates an X.509 (v1, v2, v3) self-signed certificate using a key pair
266: * associated with alias defined in param. If X.500 Distinguished Name is
267: * supplied in param it is used as both subject and issuer of the
268: * certificate. Otherwise the distinguished name associated with alias is
269: * used. Signature algorithm, validity period and certificate serial number
270: * are taken from param if defined there or from the keystore entry
271: * identified by alias.
272: *
273: * @throws NoSuchAlgorithmException
274: * @throws KeyStoreException
275: * @throws UnrecoverableKeyException
276: * @throws IOException
277: * @throws NoSuchProviderException
278: * @throws SignatureException
279: * @throws InvalidKeyException
280: * @throws KeytoolException
281: * @throws CertificateException
282: */
283: static void selfCert(KeytoolParameters param)
284: throws NoSuchAlgorithmException, KeyStoreException,
285: UnrecoverableKeyException, InvalidKeyException,
286: SignatureException, NoSuchProviderException, IOException,
287: KeytoolException, CertificateException {
288:
289: String alias = param.getAlias();
290: KeyStore keyStore = param.getKeyStore();
291:
292: if (!keyStore.entryInstanceOf(alias,
293: KeyStore.PrivateKeyEntry.class)) {
294: throw new KeytoolException(
295: "Failed to generate a certificate. " + "Entry <"
296: + alias + "> is not a private key entry");
297: }
298:
299: // get the keys and the certificate from the keystore
300: PrivateKey privateKey;
301: try {
302: privateKey = (PrivateKey) keyStore.getKey(alias, param
303: .getKeyPass());
304: } catch (NoSuchAlgorithmException e) {
305: throw new NoSuchAlgorithmException(
306: "Cannot find the algorithm to recover the key. ", e);
307: }
308:
309: // get the certificate currently associated with the alias
310: X509Certificate existing = (X509Certificate) keyStore
311: .getCertificate(alias);
312:
313: // setting certificate attributes
314: // signature algorithm name
315: String sigAlgName = (param.getSigAlg() != null) ? param
316: .getSigAlg() : existing.getSigAlgName();
317:
318: // X.500 distinguished name
319: String distinguishedName = (param.getDName() != null) ? param
320: .getDName() : existing.getSubjectDN().getName();
321:
322: // validity period. It is 90 days by default
323: long validity = (param.getValidity() != 0) ? param
324: .getValidity() : 90;
325:
326: // set the X.509 version to use with the certificate
327: int version = (param.getX509version() != 0) ?
328: // TBSCertificate needs 0, 1 or 2 as version in constructor (not 1,2,3);
329: param.getX509version() - 1
330: : 2; // X.509 v.3 certificates by default
331:
332: // set certificate serial number
333: int randomInt = new Random().nextInt();
334: if (randomInt < 0) {
335: randomInt = -randomInt;
336: }
337: BigInteger serialNr = (param.getCertSerialNr() != 0) ? BigInteger
338: .valueOf(param.getCertSerialNr())
339: : BigInteger.valueOf(randomInt);
340:
341: // generate a new certificate
342: String sigProvider = (param.getSigProvider() != null) ? param
343: .getSigProvider() : param.getProvider();
344: X509CertImpl x509cert = genX509CertImpl(sigAlgName, version,
345: serialNr, distinguishedName, distinguishedName,
346: validity, existing.getPublicKey(), privateKey,
347: sigProvider, param.isCA());
348:
349: if (param.isVerbose()) {
350: System.out.println("New self-signed certificate: ");
351: System.out.println("Version: v" + x509cert.getVersion());
352: System.out.println("Owner: "
353: + x509cert.getSubjectX500Principal());
354: System.out.println("Issuer: "
355: + x509cert.getIssuerX500Principal());
356: System.out
357: .println("Public key: " + x509cert.getPublicKey());
358: System.out.println("Signature algorithm: OID."
359: + x509cert.getSigAlgOID() + ", "
360: + x509cert.getSigAlgName());
361: System.out.println("Serial number: "
362: // String.format("%x", x509cert.getSerialNumber()));
363: // TODO: print with String.format(..) when the
364: // method is
365: // implemented, and remove Integer.toHexString(..).
366: + Integer.toHexString(x509cert.getSerialNumber()
367: .intValue()));
368:
369: System.out.println("Validity: \n From: "
370: + x509cert.getNotBefore() + "\n To: "
371: + x509cert.getNotAfter());
372: }
373:
374: // put the new certificate into the entry, associated with the alias
375: keyStore.setKeyEntry(alias, privateKey, param.getKeyPass(),
376: new X509Certificate[] { x509cert });
377: param.setNeedSaveKS(true);
378: }
379:
380: // Generates an X.509 certificate (instance of X509CertImpl class) based
381: // on given paramaters.
382: //
383: // @param sigAlgName
384: // the name of the signature algorithm to use
385: // @param version
386: // version of X.509 protocol to use. May be one of 0 (v1), 1 (v2)
387: // or 2 (v3)
388: // @param serialNr
389: // certificate serial number
390: // @param strSubjectDN
391: // X.500 Distinguished Name to use as subject
392: // @param strIssuerDN
393: // X.500 Distinguished Name to use as issuer
394: // @param validity
395: // certificate validity period in days after the current moment
396: // @param publicKey
397: // public key to wrap in certificate
398: // @param privateKey
399: // private key to sign the certificate
400: // @param provider
401: // provider name to use when generating a signature
402: // @return X.509 certificate
403: private static X509CertImpl genX509CertImpl(String sigAlgName,
404: int version, BigInteger serialNr, String strSubjectDN,
405: String strIssuerDN, long validity, PublicKey publicKey,
406: PrivateKey privateKey, String provider, boolean isCA)
407: throws InvalidKeyException, SignatureException,
408: NoSuchAlgorithmException, IOException,
409: NoSuchProviderException {
410:
411: String[] sigAlgNameAndOID = getAlgNameAndOID(sigAlgName);
412: if (sigAlgNameAndOID[0] == null || sigAlgNameAndOID[1] == null) {
413: throw new NoSuchAlgorithmException("The algorithm "
414: + sigAlgName + " is not found in the environment.");
415: }
416: sigAlgName = sigAlgNameAndOID[0];
417: String sigAlgOID = sigAlgNameAndOID[1];
418:
419: AlgorithmIdentifier algId = new AlgorithmIdentifier(sigAlgOID);
420:
421: // generate a distinguished name using the string
422: Name subjectDName = null;
423: Name issuerDName = null;
424: try {
425: subjectDName = new Name(strSubjectDN);
426:
427: if (strSubjectDN.equals(strIssuerDN)) {
428: issuerDName = subjectDName;
429: } else {
430: issuerDName = new Name(strIssuerDN);
431: }
432: } catch (IOException e) {
433: throw (IOException) new IOException(
434: "Failed to generate a distinguished name. ")
435: .initCause(e);
436: }
437:
438: // generate a SubjectPublicKeyInfo
439: SubjectPublicKeyInfo subjectPublicKeyInfo = null;
440: try {
441: subjectPublicKeyInfo = (SubjectPublicKeyInfo) SubjectPublicKeyInfo.ASN1
442: .decode(publicKey.getEncoded());
443: } catch (IOException e) {
444: throw (IOException) new IOException(
445: "Failed to decode SubjectPublicKeyInfo. ")
446: .initCause(e);
447: }
448:
449: Extensions extensions = null;
450:
451: if (version == 1 || version == 2) {
452: // generate extensions
453: extensions = new Extensions(Collections
454: .singletonList(new Extension("2.5.29.19", false,
455: new BasicConstraints(isCA,
456: Integer.MAX_VALUE))));
457: }
458: // generate the TBSCertificate to put it into the X.509 cert
459: TBSCertificate tbsCertificate = new TBSCertificate(
460: // version
461: version,
462: // serial number
463: serialNr,
464: // signature algorithm identifier
465: algId,
466: // issuer
467: issuerDName,
468: // validity
469: new Validity(new Date(System.currentTimeMillis()), // notBefore
470: new Date(System.currentTimeMillis()
471: // 86400000 milliseconds in a day
472: + validity * 86400000)), // notAfter
473: // subject
474: subjectDName,
475: // subjectPublicKeyInfo
476: subjectPublicKeyInfo,
477: // issuerUniqueID
478: null,
479: // subjectUniqueID
480: null,
481: // basic constraints
482: extensions);
483: // get the TBSCertificate encoding
484: byte[] tbsCertEncoding = tbsCertificate.getEncoded();
485:
486: // generate the signature
487: Signature sig = null;
488: try {
489: sig = (provider == null) ? Signature
490: .getInstance(sigAlgName) : Signature.getInstance(
491: sigAlgName, provider);
492: } catch (NoSuchAlgorithmException e) {
493: throw new NoSuchAlgorithmException("The algorithm "
494: + sigAlgName + " is not found in the environment.",
495: e);
496: } catch (NoSuchProviderException e) {
497: throw (NoSuchProviderException) new NoSuchProviderException(
498: "The provider " + provider
499: + " is not found in the environment.")
500: .initCause(e);
501: }
502:
503: try {
504: sig.initSign(privateKey);
505: } catch (InvalidKeyException e) {
506: throw new InvalidKeyException(
507: "The private key used to generate the signature is invalid.",
508: e);
509: }
510:
511: byte[] signatureValue = null;
512: try {
513: sig.update(tbsCertEncoding, 0, tbsCertEncoding.length);
514: signatureValue = sig.sign();
515: } catch (SignatureException e) {
516: throw new SignatureException(
517: "Failed to sign the certificate. ", e);
518: }
519:
520: // actual X.509 certificate generation
521: org.apache.harmony.security.x509.Certificate cert;
522: cert = new org.apache.harmony.security.x509.Certificate(
523: tbsCertificate, algId, signatureValue);
524: return new X509CertImpl(cert);
525: }
526:
527: // generates a secret key and puts it into a newly created secret key entry.
528: private static void generateSecretKey(KeytoolParameters param)
529: throws NoSuchAlgorithmException, NoSuchProviderException,
530: KeyStoreException, CertificateException,
531: FileNotFoundException, IOException {
532:
533: String keyAlg = param.getKeyAlg();
534: int keySize = param.getKeySize();
535:
536: if (keyAlg == null) {
537: keyAlg = "DES";
538: } else {
539: keyAlg = getAlgNameAndOID(keyAlg)[0];
540: }
541: if (param.isVerbose()) {
542: System.out.println("Generating a new secret key: ");
543: System.out.println("Algorithm: " + keyAlg);
544: System.out.println("Key size: " + keySize);
545: }
546: KeyGenerator keyGen;
547: String keyProvider = (param.getKeyProvider() != null) ? param
548: .getKeyProvider() : param.getProvider();
549: try {
550:
551: if (keyProvider == null) {
552: keyGen = KeyGenerator.getInstance(keyAlg);
553: } else {
554: keyGen = KeyGenerator.getInstance(keyAlg, keyProvider);
555: }
556: } catch (NoSuchAlgorithmException e) {
557: throw new NoSuchAlgorithmException("The algorithm "
558: + keyAlg + " is not found in the environment.", e);
559: } catch (NoSuchProviderException e) {
560: throw (NoSuchProviderException) new NoSuchProviderException(
561: "The provider " + keyProvider
562: + " is not found in the environment.")
563: .initCause(e);
564: }
565:
566: keyGen.init(keySize);
567: SecretKey key = keyGen.generateKey();
568:
569: KeyStore keyStore = param.getKeyStore();
570:
571: // keyStore.setKeyEntry(param.getAlias(), key, param.getKeyPass(),
572: // null);
573:
574: keyStore.setEntry(param.getAlias(),
575: new KeyStore.SecretKeyEntry(key),
576: new KeyStore.PasswordProtection(param.getKeyPass()));
577:
578: param.setNeedSaveKS(true);
579: }
580:
581: // Method gets algorithm name (it can be an algorithm alias, OID or standard
582: // algorithm name) and returns String array, which has standard name as the
583: // first element and OID as the second.
584: // If algName is OID and the mapping is not found in providers available,
585: // first element can be null.
586: // If algName is a standard format (not an OID) and the mapping is not found
587: // second element of the returned array can be null. If the algorithm name
588: // itself is not found algName is returned as the first element.
589: private static String[] getAlgNameAndOID(String algName)
590: throws NoSuchAlgorithmException {
591: String algOID = null;
592: String standardName = null;
593: if (AlgNameMapper.isOID(algName.toUpperCase())) {
594: // if algName is OID, remove the leading "OID." and
595: // copy it to algOID
596: algOID = AlgNameMapper.normalize(algName.toUpperCase());
597: // convert OID to a normal algorithm name.
598: standardName = AlgNameMapper.map2AlgName(algOID);
599: } else {
600: // if algName is not an OID, convert it into a standard name and
601: // get its OID and put to algOID
602: standardName = AlgNameMapper.getStandardName(algName);
603: if (standardName == null) {
604: return new String[] { algName, null };
605: }
606: algOID = AlgNameMapper.map2OID(standardName);
607: }
608: return new String[] { standardName, algOID };
609: }
610: }
|