blob: e2bf201312284cd8dc7a3dd9182e2247838689ed [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.kerby.kerberos.kerb.client.preauth.pkinit.certs;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.GeneralNamesBuilder;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
/**
* Generates an X.509 "end entity" certificate programmatically.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$, $Date$
*/
@SuppressWarnings({"PMD.UnusedPrivateField"})
public class EndEntityGenerator {
/**
* id-pkinit-san OBJECT IDENTIFIER ::=
* { iso(1) org(3) dod(6) internet(1) security(5) kerberosv5(2) x509SanAN (2) }
*/
private static final DERObjectIdentifier ID_PKINIT_SAN = new DERObjectIdentifier("1.3.6.1.5.2.2");
/**
* id-pkinit-KPClientAuth OBJECT IDENTIFIER ::=
* { iso(1) org(3) dod(6) internet(1) security(5) kerberosv5(2) pkinit(3) keyPurposeClientAuth(4) }
* -- PKINIT client authentication.
* -- Key usage bits that MUST be consistent:
* -- digitalSignature.
*/
private static final DERObjectIdentifier ID_PKINIT_KPCLIENTAUTH = new DERObjectIdentifier("1.3.6.1.5.2.3.4");
/**
* id-pkinit-KPKdc OBJECT IDENTIFIER ::=
* { iso(1) org(3) dod(6) internet(1) security(5) kerberosv5(2) pkinit(3) keyPurposeKdc(5) }
* -- Signing KDC responses.
* -- Key usage bits that MUST be consistent:
* -- digitalSignature.
*/
private static final DERObjectIdentifier ID_PKINIT_KPKDC = new DERObjectIdentifier("1.3.6.1.5.2.3.5");
private static final DERObjectIdentifier ID_MS_KP_SC_LOGON = new DERObjectIdentifier("1.3.6.1.4.1.311.20.2.2");
private static final DERObjectIdentifier ID_MS_SAN_SC_LOGON_UPN = new DERObjectIdentifier("1.3.6.1.4.1.311.20.2.3");
/**
* Generate certificate.
*
* @param issuerCert
* @param issuerPrivateKey
* @param publicKey
* @param dn
* @param validityDays
* @param friendlyName
* @return The certificate.
* @throws InvalidKeyException
* @throws SecurityException
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws DataLengthException
* @throws CertificateException
*/
public static X509Certificate generate(X509Certificate issuerCert, PrivateKey issuerPrivateKey,
PublicKey publicKey, String dn, int validityDays,
String friendlyName)
throws InvalidKeyException, SecurityException, SignatureException,
NoSuchAlgorithmException, DataLengthException, CertificateException {
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
// Set certificate attributes.
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
certGen.setIssuerDN(PrincipalUtil.getSubjectX509Principal(issuerCert));
certGen.setSubjectDN(new X509Principal(dn));
certGen.setNotBefore(new Date());
Calendar expiry = Calendar.getInstance();
expiry.add(Calendar.DAY_OF_YEAR, validityDays);
certGen.setNotAfter(expiry.getTime());
certGen.setPublicKey(publicKey);
certGen.setSignatureAlgorithm("SHA1WithRSAEncryption");
certGen
.addExtension(X509Extensions.SubjectKeyIdentifier, false,
new SubjectKeyIdentifier(getDigest(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()))));
// MAY set BasicConstraints=false or not at all.
certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
new AuthorityKeyIdentifierStructure(issuerCert));
certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature
| KeyUsage.keyEncipherment | KeyUsage.dataEncipherment));
ASN1EncodableVector keyPurposeVector = new ASN1EncodableVector();
keyPurposeVector.add(KeyPurposeId.id_kp_smartcardlogon);
//keyPurposeVector.add( KeyPurposeId.id_kp_serverAuth );
DERSequence keyPurposeOids = new DERSequence(keyPurposeVector);
// If critical, will throw unsupported EKU.
certGen.addExtension(X509Extensions.ExtendedKeyUsage, false, keyPurposeOids);
ASN1EncodableVector pkinitSanVector = new ASN1EncodableVector();
pkinitSanVector.add(ID_PKINIT_SAN);
pkinitSanVector.add(new DERTaggedObject(0, new DERSequence()));
DERSequence pkinitSan = new DERSequence(pkinitSanVector);
String dnsName = "localhost";
GeneralName name1 = new GeneralName(GeneralName.otherName, pkinitSan);
GeneralName name2 = new GeneralName(GeneralName.dNSName, dnsName);
GeneralNamesBuilder genNamesBuilder = new GeneralNamesBuilder();
genNamesBuilder.addName(name1);
genNamesBuilder.addName(name2);
GeneralNames sanGeneralNames = genNamesBuilder.build();
certGen.addExtension(X509Extensions.SubjectAlternativeName, true, sanGeneralNames);
/*
* The KDC MAY require the presence of an Extended Key Usage (EKU) KeyPurposeId
* [RFC3280] id-pkinit-KPClientAuth in the extensions field of the client's
* X.509 certificate.
*/
/*
* The digitalSignature key usage bit [RFC3280] MUST be asserted when the
* intended purpose of the client's X.509 certificate is restricted with
* the id-pkinit-KPClientAuth EKU.
*/
/*
* KDCs implementing this requirement SHOULD also accept the EKU KeyPurposeId
* id-ms-kp-sc-logon (1.3.6.1.4.1.311.20.2.2) as meeting the requirement, as
* there are a large number of X.509 client certificates deployed for use
* with PKINIT that have this EKU.
*/
// KDC
/*
* In addition, unless the client can otherwise verify that the public key
* used to verify the KDC's signature is bound to the KDC of the target realm,
* the KDC's X.509 certificate MUST contain a Subject Alternative Name extension
* [RFC3280] carrying an AnotherName whose type-id is id-pkinit-san (as defined
* in Section 3.2.2) and whose value is a KRB5PrincipalName that matches the
* name of the TGS of the target realm (as defined in Section 7.3 of [RFC4120]).
*/
/*
* Unless the client knows by some other means that the KDC certificate is
* intended for a Kerberos KDC, the client MUST require that the KDC certificate
* contains the EKU KeyPurposeId [RFC3280] id-pkinit-KPKdc.
*/
/*
* The digitalSignature key usage bit [RFC3280] MUST be asserted when the
* intended purpose of the KDC's X.509 certificate is restricted with the
* id-pkinit-KPKdc EKU.
*/
/*
* If the KDC certificate contains the Kerberos TGS name encoded as an id-pkinit-san
* SAN, this certificate is certified by the issuing CA as a KDC certificate,
* therefore the id-pkinit-KPKdc EKU is not required.
*/
/*
* KDC certificates issued by Windows 2000 Enterprise CAs contain a dNSName
* SAN with the DNS name of the host running the KDC, and the id-kp-serverAuth
* EKU [RFC3280].
*/
/*
* KDC certificates issued by Windows 2003 Enterprise CAs contain a dNSName
* SAN with the DNS name of the host running the KDC, the id-kp-serverAuth
* EKU, and the id-ms-kp-sc-logon EKU.
*/
/*
* RFC: KDC certificates with id-pkinit-san SAN as specified in this RFC.
*
* MS: dNSName SAN containing the domain name of the KDC
* id-pkinit-KPKdc EKU
* id-kp-serverAuth EKU.
*/
/*
* Client certificates accepted by Windows 2000 and Windows 2003 Server KDCs
* must contain an id-ms-san-sc-logon-upn (1.3.6.1.4.1.311.20.2.3) SAN and
* the id-ms-kp-sc-logon EKU. The id-ms-san-sc-logon-upn SAN contains a
* UTF8-encoded string whose value is that of the Directory Service attribute
* UserPrincipalName of the client account object, and the purpose of including
* the id-ms-san-sc-logon-upn SAN in the client certificate is to validate
* the client mapping (in other words, the client's public key is bound to
* the account that has this UserPrincipalName value).
*/
X509Certificate cert = certGen.generate(issuerPrivateKey);
PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier) cert;
bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(friendlyName));
bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
new SubjectKeyIdentifier(getDigest(SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()))));
return cert;
}
private static byte[] getDigest(SubjectPublicKeyInfo spki) {
Digest digest = new SHA1Digest();
byte[] resBuf = new byte[digest.getDigestSize()];
byte[] bytes = spki.getPublicKeyData().getBytes();
digest.update(bytes, 0, bytes.length);
digest.doFinal(resBuf, 0);
return resBuf;
}
}