| /** |
| * 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; |
| } |
| } |