You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by pl...@apache.org on 2015/09/24 09:07:10 UTC

[2/2] directory-kerby git commit: DIRKRB-415 Consolidate and merge the existing kerberos-pkinit implementation in directory to Kerby.

DIRKRB-415 Consolidate and merge the existing kerberos-pkinit implementation in directory to Kerby.


Project: http://git-wip-us.apache.org/repos/asf/directory-kerby/repo
Commit: http://git-wip-us.apache.org/repos/asf/directory-kerby/commit/c98ab976
Tree: http://git-wip-us.apache.org/repos/asf/directory-kerby/tree/c98ab976
Diff: http://git-wip-us.apache.org/repos/asf/directory-kerby/diff/c98ab976

Branch: refs/heads/pkinit-support
Commit: c98ab976a7f384959fe2dd3107b325afbe23d844
Parents: 42bcec8
Author: plusplusjiajia <ji...@intel.com>
Authored: Thu Sep 24 15:12:24 2015 +0800
Committer: plusplusjiajia <ji...@intel.com>
Committed: Thu Sep 24 15:12:24 2015 +0800

----------------------------------------------------------------------
 kerby-kerb/kerb-client/pom.xml                  |  10 +
 .../preauth/pkinit/ClientConfiguration.java     | 149 ++++++++++
 .../kerb/client/preauth/pkinit/DhClient.java    | 135 +++++++++
 .../kerb/client/preauth/pkinit/DhGroup.java     | 142 ++++++++++
 .../kerb/client/preauth/pkinit/DhServer.java    | 142 ++++++++++
 .../preauth/pkinit/EnvelopedDataEngine.java     | 130 +++++++++
 .../client/preauth/pkinit/OctetString2Key.java  | 103 +++++++
 .../preauth/pkinit/ServerConfiguration.java     | 153 ++++++++++
 .../client/preauth/pkinit/SignedDataEngine.java | 205 ++++++++++++++
 .../pkinit/certs/CertificateChainFactory.java   | 283 +++++++++++++++++++
 .../pkinit/certs/EndEntityGenerator.java        | 257 +++++++++++++++++
 .../pkinit/certs/IntermediateCaGenerator.java   | 116 ++++++++
 .../preauth/pkinit/certs/KeyPairSpec.java       | 114 ++++++++
 .../pkinit/certs/TrustAnchorGenerator.java      | 110 +++++++
 .../pkinit/CertificateChainFactoryTest.java     | 119 ++++++++
 .../kerb/client/preauth/pkinit/DhGroupTest.java |  55 ++++
 .../preauth/pkinit/DhKeyAgreementTest.java      | 109 +++++++
 .../preauth/pkinit/EnvelopedDataEngineTest.java | 133 +++++++++
 .../preauth/pkinit/OctetString2KeyTest.java     | 230 +++++++++++++++
 .../preauth/pkinit/SignedDataEngineTest.java    | 135 +++++++++
 .../certs/CertificateChainFactoryTest.java      | 116 ++++++++
 21 files changed, 2946 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/pom.xml
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/pom.xml b/kerby-kerb/kerb-client/pom.xml
index d75eaea..fbad8cc 100644
--- a/kerby-kerb/kerb-client/pom.xml
+++ b/kerby-kerb/kerb-client/pom.xml
@@ -46,5 +46,15 @@
       <artifactId>kerb-util</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcprov-jdk15</artifactId>
+      <version>1.38</version>
+    </dependency>
+    <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcmail-jdk15</artifactId>
+      <version>1.38</version>
+    </dependency>
   </dependencies>
 </project>

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/ClientConfiguration.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/ClientConfiguration.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/ClientConfiguration.java
new file mode 100644
index 0000000..5350c02
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/ClientConfiguration.java
@@ -0,0 +1,149 @@
+/*
+ *  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;
+
+
+import javax.crypto.spec.DHParameterSpec;
+
+
+/**
+ * Client configuration settings.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class ClientConfiguration
+{
+    /**
+     * The location of the user certificate.
+     */
+    private String certificatePath;
+
+    /**
+     * The CMS types to use.
+     */
+    private String cmsType;
+
+    /**
+     * Whether or not to use Diffie-Hellman.  The alternative is the "public key"
+     * method.
+     */
+    private boolean isDhUsed = true;
+
+    /**
+     * The Diffie-Hellman group to use.
+     */
+    private DHParameterSpec dhGroup = DhGroup.MODP_GROUP2;
+
+    /**
+     * Whether or not to reuse Diffie-Hellman keys.
+     */
+    private boolean isDhKeysReused;
+
+
+    /**
+     * @return the certificatePath
+     */
+    public String getCertificatePath()
+    {
+        return certificatePath;
+    }
+
+
+    /**
+     * @param certificatePath the certificatePath to set
+     */
+    public void setCertificatePath( String certificatePath )
+    {
+        this.certificatePath = certificatePath;
+    }
+
+
+    /**
+     * @return the cmsType
+     */
+    public String getCmsType()
+    {
+        return cmsType;
+    }
+
+
+    /**
+     * @param cmsType the cmsType to set
+     */
+    public void setCmsType( String cmsType )
+    {
+        this.cmsType = cmsType;
+    }
+
+
+    /**
+     * @return the isDhUsed
+     */
+    public boolean isDhUsed()
+    {
+        return isDhUsed;
+    }
+
+
+    /**
+     * @param isDhUsed the isDhUsed to set
+     */
+    public void setDhUsed( boolean isDhUsed )
+    {
+        this.isDhUsed = isDhUsed;
+    }
+
+
+    /**
+     * @return the dhGroup
+     */
+    public DHParameterSpec getDhGroup()
+    {
+        return dhGroup;
+    }
+
+
+    /**
+     * @param dhGroup the dhGroup to set
+     */
+    public void setDhGroup( DHParameterSpec dhGroup )
+    {
+        this.dhGroup = dhGroup;
+    }
+
+
+    /**
+     * @return the isDhKeysReused
+     */
+    public boolean isDhKeysReused()
+    {
+        return isDhKeysReused;
+    }
+
+
+    /**
+     * @param isDhKeysReused the isDhKeysReused to set
+     */
+    public void setDhKeysReused( boolean isDhKeysReused )
+    {
+        this.isDhKeysReused = isDhKeysReused;
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhClient.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhClient.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhClient.java
new file mode 100644
index 0000000..20ca0c6
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhClient.java
@@ -0,0 +1,135 @@
+/*
+ *  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;
+
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.X509EncodedKeySpec;
+
+
+/**
+ * The client-side of Diffie-Hellman key agreement for Kerberos PKINIT.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+class DhClient
+{
+    private static AlgorithmParameterSpec AES_IV = new IvParameterSpec( new byte[16] );
+
+    private KeyAgreement clientKeyAgree;
+    private SecretKey clientAesKey;
+
+
+    byte[] init( DHParameterSpec dhParamSpec ) throws Exception
+    {
+        // The client creates its own DH key pair, using the DH parameters from above.
+        KeyPairGenerator clientKpairGen = KeyPairGenerator.getInstance( "DH" );
+        clientKpairGen.initialize( dhParamSpec );
+        KeyPair clientKpair = clientKpairGen.generateKeyPair();
+
+        // The client creates and initializes its DH KeyAgreement object.
+        clientKeyAgree = KeyAgreement.getInstance( "DH" );
+        clientKeyAgree.init( clientKpair.getPrivate() );
+
+        // The client encodes its public key, and sends it over to the server.
+        return clientKpair.getPublic().getEncoded();
+    }
+
+
+    void doPhase( byte[] serverPubKeyEnc ) throws Exception
+    {
+        /*
+         * The client uses the server's public key for the first (and only) phase
+         * of its version of the DH protocol.  Before it can do so, it has to
+         * instantiate a DH public key from the server's encoded key material.
+         */
+        KeyFactory clientKeyFac = KeyFactory.getInstance( "DH" );
+        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec( serverPubKeyEnc );
+        PublicKey serverPubKey = clientKeyFac.generatePublic( x509KeySpec );
+
+        clientKeyAgree.doPhase( serverPubKey, true );
+    }
+
+
+    byte[] generateKey( byte[] clientDhNonce, byte[] serverDhNonce )
+    {
+        // ZZ length will be same as public key.
+        byte[] dhSharedSecret = clientKeyAgree.generateSecret();
+        byte[] x = dhSharedSecret;
+
+        if ( ( clientDhNonce != null && clientDhNonce.length > 0 )
+            && ( serverDhNonce != null && serverDhNonce.length > 0 ) )
+        {
+            x = concatenateBytes( dhSharedSecret, clientDhNonce );
+            x = concatenateBytes( x, serverDhNonce );
+        }
+
+        byte[] secret = OctetString2Key.kTruncate( dhSharedSecret.length, x );
+        clientAesKey = new SecretKeySpec( secret, 0, 16, "AES" );
+
+        return clientAesKey.getEncoded();
+    }
+
+
+    /**
+     * Decrypt using AES in CTS mode.
+     * 
+     * @param cipherText
+     * @return
+     * @throws Exception
+     */
+    byte[] decryptAes( byte[] cipherText ) throws Exception
+    {
+        // Use the secret key to encrypt/decrypt data.
+        Cipher serverCipher = Cipher.getInstance( "AES/CTS/NoPadding" );
+        serverCipher.init( Cipher.DECRYPT_MODE, clientAesKey, AES_IV );
+
+        return serverCipher.doFinal( cipherText );
+    }
+
+
+    byte[] concatenateBytes( byte[] array1, byte[] array2 )
+    {
+        byte concatenatedBytes[] = new byte[array1.length + array2.length];
+
+        for ( int i = 0; i < array1.length; i++ )
+        {
+            concatenatedBytes[i] = array1[i];
+        }
+
+        for ( int j = array1.length; j < concatenatedBytes.length; j++ )
+        {
+            concatenatedBytes[j] = array2[j - array1.length];
+        }
+
+        return concatenatedBytes;
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhGroup.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhGroup.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhGroup.java
new file mode 100644
index 0000000..6fb0f51
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhGroup.java
@@ -0,0 +1,142 @@
+/*
+ *  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;
+
+
+import javax.crypto.spec.DHParameterSpec;
+import java.math.BigInteger;
+
+
+/**
+ * "When using the Diffie-Hellman key agreement method, implementations MUST
+ * support Oakley 1024-bit Modular Exponential (MODP) well-known group 2
+ * [RFC2412] and Oakley 2048-bit MODP well-known group 14 [RFC3526] and
+ * SHOULD support Oakley 4096-bit MODP well-known group 16 [RFC3526]."
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class DhGroup
+{
+    /**
+     * From:
+     * The OAKLEY Key Determination Protocol
+     * http://www.ietf.org/rfc/rfc2412.txt
+     * 
+     * Well-Known Group 2:  A 1024 bit prime
+     * This group is assigned id 2 (two).
+     * The prime is 2^1024 - 2^960 - 1 + 2^64 * { [2^894 pi] + 129093 }.
+     * The generator is 2 (decimal)
+     */
+    public static final DHParameterSpec MODP_GROUP2;
+
+    static
+    {
+        StringBuffer sb = new StringBuffer();
+        sb.append( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" );
+        sb.append( "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" );
+        sb.append( "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" );
+        sb.append( "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" );
+        sb.append( "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" );
+        sb.append( "FFFFFFFFFFFFFFFF" );
+
+        BigInteger prime = new BigInteger( sb.toString(), 16 );
+        BigInteger generator = BigInteger.valueOf( 2 );
+
+        MODP_GROUP2 = new DHParameterSpec( prime, generator );
+    }
+
+    /**
+     * From:
+     * More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)
+     * http://www.ietf.org/rfc/rfc3526.txt
+     * 
+     * 2048-bit MODP Group
+     * This group is assigned id 14.
+     * This prime is: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }
+     * The generator is: 2.
+     */
+    public static final DHParameterSpec MODP_GROUP14;
+
+    static
+    {
+        StringBuffer sb = new StringBuffer();
+        sb.append( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" );
+        sb.append( "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" );
+        sb.append( "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" );
+        sb.append( "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" );
+        sb.append( "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" );
+        sb.append( "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" );
+        sb.append( "83655D23DCA3AD961C62F356208552BB9ED529077096966D" );
+        sb.append( "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" );
+        sb.append( "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" );
+        sb.append( "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" );
+        sb.append( "15728E5A8AACAA68FFFFFFFFFFFFFFFF" );
+
+        BigInteger prime = new BigInteger( sb.toString(), 16 );
+        BigInteger generator = BigInteger.valueOf( 2 );
+
+        MODP_GROUP14 = new DHParameterSpec( prime, generator );
+    }
+
+    /**
+     * From:
+     * More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)
+     * http://www.ietf.org/rfc/rfc3526.txt
+     * 
+     * 4096-bit MODP Group
+     * This group is assigned id 16.
+     * This prime is: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }
+     * The generator is: 2.
+     */
+    public static final DHParameterSpec MODP_GROUP16;
+
+    static
+    {
+        StringBuffer sb = new StringBuffer();
+        sb.append( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" );
+        sb.append( "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" );
+        sb.append( "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" );
+        sb.append( "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" );
+        sb.append( "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" );
+        sb.append( "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" );
+        sb.append( "83655D23DCA3AD961C62F356208552BB9ED529077096966D" );
+        sb.append( "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" );
+        sb.append( "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" );
+        sb.append( "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" );
+        sb.append( "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" );
+        sb.append( "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" );
+        sb.append( "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" );
+        sb.append( "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" );
+        sb.append( "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" );
+        sb.append( "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" );
+        sb.append( "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" );
+        sb.append( "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" );
+        sb.append( "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" );
+        sb.append( "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" );
+        sb.append( "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" );
+        sb.append( "FFFFFFFFFFFFFFFF" );
+
+        BigInteger prime = new BigInteger( sb.toString(), 16 );
+        BigInteger generator = BigInteger.valueOf( 2 );
+
+        MODP_GROUP16 = new DHParameterSpec( prime, generator );
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhServer.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhServer.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhServer.java
new file mode 100644
index 0000000..a2e4a27
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/DhServer.java
@@ -0,0 +1,142 @@
+/*
+ *  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;
+
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.SecretKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+
+/**
+ * The server-side of Diffie-Hellman key agreement for Kerberos PKINIT.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+class DhServer
+{
+    private static AlgorithmParameterSpec AES_IV = new IvParameterSpec( new byte[16] );
+
+    private KeyAgreement serverKeyAgree;
+    private SecretKey serverAesKey;
+
+
+    byte[] initAndDoPhase( byte[] clientPubKeyEnc ) throws Exception
+    {
+        /*
+         * The server has received the client's public key in encoded format.  The
+         * server instantiates a DH public key from the encoded key material.
+         */
+        KeyFactory serverKeyFac = KeyFactory.getInstance( "DH" );
+        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec( clientPubKeyEnc );
+        PublicKey clientPubKey = serverKeyFac.generatePublic( x509KeySpec );
+
+        /*
+         * The server gets the DH parameters associated with the client's public
+         * key.  The server must use the same parameters when it generates its own key pair.
+         */
+        DHParameterSpec dhParamSpec = ( ( DHPublicKey ) clientPubKey ).getParams();
+
+        // The server creates its own DH key pair.
+        KeyPairGenerator serverKpairGen = KeyPairGenerator.getInstance( "DH" );
+        serverKpairGen.initialize( dhParamSpec );
+        KeyPair serverKpair = serverKpairGen.generateKeyPair();
+
+        // The server creates and initializes its DH KeyAgreement object.
+        serverKeyAgree = KeyAgreement.getInstance( "DH" );
+        serverKeyAgree.init( serverKpair.getPrivate() );
+
+        /*
+         * The server uses the client's public key for the only phase of its
+         * side of the DH protocol.
+         */
+        serverKeyAgree.doPhase( clientPubKey, true );
+
+        // The server encodes its public key, and sends it over to the client.
+        return serverKpair.getPublic().getEncoded();
+    }
+
+
+    byte[] generateKey( byte[] clientDhNonce, byte[] serverDhNonce )
+    {
+        // ZZ length will be same as public key.
+        byte[] dhSharedSecret = serverKeyAgree.generateSecret();
+        byte[] x = dhSharedSecret;
+
+        if ( ( clientDhNonce != null && clientDhNonce.length > 0 )
+            && ( serverDhNonce != null && serverDhNonce.length > 0 ) )
+        {
+            x = concatenateBytes( dhSharedSecret, clientDhNonce );
+            x = concatenateBytes( x, serverDhNonce );
+        }
+
+        byte[] secret = OctetString2Key.kTruncate( dhSharedSecret.length, x );
+        serverAesKey = new SecretKeySpec( secret, 0, 16, "AES" );
+
+        return serverAesKey.getEncoded();
+    }
+
+
+    /**
+     * Encrypt using AES in CTS mode.
+     *
+     * @param cleartext
+     * @return The cipher text.
+     * @throws Exception
+     */
+    byte[] encryptAes( byte[] clearText ) throws Exception
+    {
+        // Use the secret key to encrypt/decrypt data.
+        Cipher serverCipher = Cipher.getInstance( "AES/CTS/NoPadding" );
+        serverCipher.init( Cipher.ENCRYPT_MODE, serverAesKey, AES_IV );
+
+        return serverCipher.doFinal( clearText );
+    }
+
+
+    byte[] concatenateBytes( byte[] array1, byte[] array2 )
+    {
+        byte concatenatedBytes[] = new byte[array1.length + array2.length];
+
+        for ( int i = 0; i < array1.length; i++ )
+        {
+            concatenatedBytes[i] = array1[i];
+        }
+
+        for ( int j = array1.length; j < concatenatedBytes.length; j++ )
+        {
+            concatenatedBytes[j] = array2[j - array1.length];
+        }
+
+        return concatenatedBytes;
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/EnvelopedDataEngine.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/EnvelopedDataEngine.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/EnvelopedDataEngine.java
new file mode 100644
index 0000000..523711e
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/EnvelopedDataEngine.java
@@ -0,0 +1,130 @@
+/*
+ *  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;
+
+
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.KeyTransRecipientInformation;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+
+/**
+ * Encapsulates working with PKINIT enveloped data structures.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class EnvelopedDataEngine
+{
+    /**
+     * Uses a certificate to encrypt data in a CMS EnvelopedData structure and
+     * returns the encoded EnvelopedData as bytes.
+     * 
+     * 'encKeyPack' contains a CMS type ContentInfo encoded according to [RFC3852].
+     * The contentType field of the type ContentInfo is id-envelopedData (1.2.840.113549.1.7.3).
+     * The content field is an EnvelopedData. The contentType field for the type
+     * EnvelopedData is id-signedData (1.2.840.113549.1.7.2).
+     * 
+     * @param dataToEnvelope
+     * @param certificate
+     * @return The EnvelopedData bytes.
+     * @throws NoSuchAlgorithmException
+     * @throws IOException
+     * @throws CMSException
+     * @throws NoSuchProviderException
+     */
+    public static byte[] getEnvelopedReplyKeyPack( byte[] dataToEnvelope, X509Certificate certificate )
+        throws NoSuchAlgorithmException, IOException, CMSException, NoSuchProviderException
+    {
+        CMSProcessableByteArray content = new CMSProcessableByteArray( dataToEnvelope );
+        String algorithm = CMSEnvelopedDataGenerator.DES_EDE3_CBC;
+
+        CMSEnvelopedDataGenerator envelopeGenerator = new CMSEnvelopedDataGenerator();
+        envelopeGenerator.addKeyTransRecipient( certificate );
+        CMSEnvelopedData envdata = envelopeGenerator.generate( content, algorithm, "BC" );
+
+        return envdata.getEncoded();
+    }
+
+
+    /**
+     * Uses a private key to decrypt data in a CMS EnvelopedData structure and
+     * returns the recovered (decrypted) data bytes.
+     *
+     * @param envelopedDataBytes
+     * @param certificate
+     * @param privateKey
+     * @return The recovered (decrypted) data bytes.
+     * @throws NoSuchProviderException
+     * @throws InvalidAlgorithmParameterException
+     * @throws CMSException
+     * @throws NoSuchAlgorithmException
+     * @throws CertStoreException
+     */
+    @SuppressWarnings("unchecked")
+    public static byte[] getUnenvelopedData( byte[] envelopedDataBytes, X509Certificate certificate,
+        PrivateKey privateKey ) throws NoSuchProviderException, InvalidAlgorithmParameterException, CMSException,
+        NoSuchAlgorithmException, CertStoreException
+    {
+        CMSEnvelopedData envelopedData = new CMSEnvelopedData( envelopedDataBytes );
+
+        // Set up to iterate through the recipients.
+        RecipientInformationStore recipients = envelopedData.getRecipientInfos();
+        CertStore certStore = CertStore.getInstance( "Collection", new CollectionCertStoreParameters( Collections
+            .singleton( certificate ) ), "BC" );
+        Iterator<RecipientInformation> it = recipients.getRecipients().iterator();
+
+        while ( it.hasNext() )
+        {
+            RecipientInformation recipient = it.next();
+            if ( recipient instanceof KeyTransRecipientInformation )
+            {
+                // Match the recipient ID.
+                Collection<? extends Certificate> matches = certStore.getCertificates( recipient.getRID() );
+
+                if ( !matches.isEmpty() )
+                {
+                    // Decrypt the data.
+                    return recipient.getContent( privateKey, "BC" );
+                }
+            }
+        }
+
+        return new byte[0];
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/OctetString2Key.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/OctetString2Key.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/OctetString2Key.java
new file mode 100644
index 0000000..ccfab38
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/OctetString2Key.java
@@ -0,0 +1,103 @@
+/*
+ *  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;
+
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+
+/**
+ * From RFC 4556:
+ * 
+ * Define the function octetstring2key() as follows:
+ * 
+ *      octetstring2key(x) == random-to-key(K-truncate(
+ *                               SHA1(0x00 | x) |
+ *                               SHA1(0x01 | x) |
+ *                               SHA1(0x02 | x) |
+ *                               ...
+ *                               ))
+ * 
+ * where x is an octet string; | is the concatenation operator; 0x00,
+ * 0x01, 0x02, etc. are each represented as a single octet; random-
+ * to-key() is an operation that generates a protocol key from a
+ * bitstring of length K; and K-truncate truncates its input to the
+ * first K bits.  Both K and random-to-key() are as defined in the
+ * kcrypto profile [RFC3961] for the enctype of the AS reply key.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class OctetString2Key
+{
+    /**
+     * Performs the function K-truncate to generate the AS reply key k.
+     *
+     * @param k
+     * @param x
+     * @return The AS reply key value.
+     */
+    public static byte[] kTruncate( int k, byte[] x )
+    {
+        int numberOfBytes = k / 8;
+        byte[] result = new byte[numberOfBytes];
+
+        int count = 0;
+        byte[] filler = calculateIntegrity( ( byte ) count, x );
+
+        int position = 0;
+
+        for ( int i = 0; i < numberOfBytes; i++ )
+        {
+            if ( position < filler.length )
+            {
+                result[i] = filler[position];
+                position++;
+            }
+            else
+            {
+                count++;
+                filler = calculateIntegrity( ( byte ) count, x );
+                position = 0;
+                result[i] = filler[position];
+                position++;
+            }
+        }
+
+        return result;
+    }
+
+
+    private static byte[] calculateIntegrity( byte count, byte[] data )
+    {
+        try
+        {
+            MessageDigest digester = MessageDigest.getInstance( "SHA1" );
+            digester.update( count );
+
+            return digester.digest( data );
+        }
+        catch ( NoSuchAlgorithmException nsae )
+        {
+            return new byte[0];
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/ServerConfiguration.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/ServerConfiguration.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/ServerConfiguration.java
new file mode 100644
index 0000000..44635d9
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/ServerConfiguration.java
@@ -0,0 +1,153 @@
+/*
+ *  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;
+
+import org.apache.kerby.kerberos.kerb.spec.KerberosTime;
+
+import javax.crypto.spec.DHParameterSpec;
+
+/**
+ * Server configuration settings.
+ * 
+ * TODO - Whether to use user cert vs. SAN binding.
+ * TODO - What trusted roots to use.
+ * TODO - The minimum allowed enc_types.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class ServerConfiguration
+{
+    /**
+     * Whether or not to use Diffie-Hellman.  The alternative is the "public key"
+     * method.
+     */
+    private boolean isDhUsed;
+
+    /**
+     * The Diffie-Hellman group to use.
+     */
+    private DHParameterSpec dhGroup = DhGroup.MODP_GROUP2;
+
+    /**
+     * Whether or not to reuse Diffie-Hellman keys.
+     */
+    private boolean isDhKeysReused;
+
+    /**
+     * The length of time Diffie-Hellman keys can be reused.
+     */
+    private long dhKeyExpiration = KerberosTime.DAY;
+
+    /**
+     * The length of the Diffie-Hellman nonces.
+     */
+    private int dhNonceLength = 32;
+
+
+    /**
+     * @return the isDhUsed
+     */
+    public boolean isDhUsed()
+    {
+        return isDhUsed;
+    }
+
+
+    /**
+     * @param isDhUsed the isDhUsed to set
+     */
+    public void setDhUsed( boolean isDhUsed )
+    {
+        this.isDhUsed = isDhUsed;
+    }
+
+
+    /**
+     * @return the dhGroup
+     */
+    public DHParameterSpec getDhGroup()
+    {
+        return dhGroup;
+    }
+
+
+    /**
+     * @param dhGroup the dhGroup to set
+     */
+    public void setDhGroup( DHParameterSpec dhGroup )
+    {
+        this.dhGroup = dhGroup;
+    }
+
+
+    /**
+     * @return the isDhKeysReused
+     */
+    public boolean isDhKeysReused()
+    {
+        return isDhKeysReused;
+    }
+
+
+    /**
+     * @param isDhKeysReused the isDhKeysReused to set
+     */
+    public void setDhKeysReused( boolean isDhKeysReused )
+    {
+        this.isDhKeysReused = isDhKeysReused;
+    }
+
+
+    /**
+     * @return the dhKeyExpiration
+     */
+    public long getDhKeyExpiration()
+    {
+        return dhKeyExpiration;
+    }
+
+
+    /**
+     * @param dhKeyExpiration the dhKeyExpiration to set
+     */
+    public void setDhKeyExpiration( long dhKeyExpiration )
+    {
+        this.dhKeyExpiration = dhKeyExpiration;
+    }
+
+
+    /**
+     * @return the dhNonceLength
+     */
+    public int getDhNonceLength()
+    {
+        return dhNonceLength;
+    }
+
+
+    /**
+     * @param dhNonceLength the dhNonceLength to set
+     */
+    public void setDhNonceLength( int dhNonceLength )
+    {
+        this.dhNonceLength = dhNonceLength;
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/SignedDataEngine.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/SignedDataEngine.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/SignedDataEngine.java
new file mode 100644
index 0000000..5a5a09a
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/SignedDataEngine.java
@@ -0,0 +1,205 @@
+/*
+ *  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;
+
+
+import org.apache.kerby.kerberos.kerb.spec.pa.pkinit.AuthPack;
+import org.apache.kerby.kerberos.kerb.spec.pa.pkinit.KdcDHKeyInfo;
+import org.apache.kerby.kerberos.kerb.spec.pa.pkinit.ReplyKeyPack;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSSignedGenerator;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+
+/**
+ * Encapsulates working with PKINIT signed data structures.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class SignedDataEngine {
+    private static final String ID_PKINIT_AUTHDATA = "1.3.6.1.5.2.3.1";
+    private static final String ID_PKINIT_DHKEYDATA = "1.3.6.1.5.2.3.2";
+    private static final String ID_PKINIT_RKEYDATA = "1.3.6.1.5.2.3.3";
+
+
+    /**
+     * Uses a private key to sign data in a CMS SignedData structure and returns
+     * the encoded CMS SignedData as bytes.
+     * <p/>
+     * 'signedAuthPack' contains a CMS type ContentInfo encoded according to [RFC3852].
+     * The contentType field of the type ContentInfo is id-signedData (1.2.840.113549.1.7.2),
+     * and the content field is a SignedData.
+     * <p/>
+     * The eContentType field for the type SignedData is id-pkinit-authData (1.3.6.1.5.2.3.1),
+     * and the eContent field contains the DER encoding of the type AuthPack.
+     *
+     * @param privateKey
+     * @param certificate
+     * @param authPack
+     * @return The CMS SignedData bytes.
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     * @throws InvalidAlgorithmParameterException
+     * @throws CertStoreException
+     * @throws CMSException
+     * @throws IOException
+     */
+    public static byte[] getSignedAuthPack(PrivateKey privateKey, X509Certificate certificate, AuthPack authPack)
+        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
+        CertStoreException, CMSException, IOException {
+        return getSignedData(privateKey, certificate, authPack.encode(), ID_PKINIT_AUTHDATA);
+    }
+
+
+    /**
+     * Uses a private key to sign data in a CMS SignedData structure and returns
+     * the encoded CMS SignedData as bytes.
+     * <p/>
+     * 'dhSignedData' contains a CMS type ContentInfo encoded according to [RFC3852].
+     * The contentType field of the type ContentInfo is id-signedData (1.2.840.113549.1.7.2),
+     * and the content field is a SignedData.
+     * <p/>
+     * The eContentType field for the type SignedData is id-pkinit-DHKeyData (1.3.6.1.5.2.3.2),
+     * and the eContent field contains the DER encoding of the type KDCDHKeyInfo.
+     *
+     * @param privateKey
+     * @param certificate
+     * @param kdcDhKeyInfo
+     * @return The CMS SignedData bytes.
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     * @throws InvalidAlgorithmParameterException
+     * @throws CertStoreException
+     * @throws CMSException
+     * @throws IOException
+     */
+    public static byte[] getSignedKdcDhKeyInfo(PrivateKey privateKey, X509Certificate certificate,
+                                               KdcDHKeyInfo kdcDhKeyInfo) throws NoSuchAlgorithmException, NoSuchProviderException,
+        InvalidAlgorithmParameterException, CertStoreException, CMSException, IOException {
+        return getSignedData(privateKey, certificate, kdcDhKeyInfo.encode(), ID_PKINIT_DHKEYDATA);
+    }
+
+
+    /**
+     * Uses a private key to sign data in a CMS SignedData structure and returns
+     * the encoded CMS SignedData as bytes.
+     * <p/>
+     * Selected when public key encryption is used.
+     * <p/>
+     * The eContentType field for the inner type SignedData (when unencrypted) is
+     * id-pkinit-rkeyData (1.3.6.1.5.2.3.3) and the eContent field contains the
+     * DER encoding of the type ReplyKeyPack.
+     *
+     * @param privateKey
+     * @param certificate
+     * @param replyKeyPack
+     * @return The CMS SignedData bytes.
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     * @throws InvalidAlgorithmParameterException
+     * @throws CertStoreException
+     * @throws CMSException
+     * @throws IOException
+     */
+    public static byte[] getSignedReplyKeyPack(PrivateKey privateKey, X509Certificate certificate,
+                                               ReplyKeyPack replyKeyPack) throws NoSuchAlgorithmException, NoSuchProviderException,
+        InvalidAlgorithmParameterException, CertStoreException, CMSException, IOException {
+        return getSignedData(privateKey, certificate, replyKeyPack.encode(), ID_PKINIT_RKEYDATA);
+    }
+
+
+    static byte[] getSignedData(PrivateKey privateKey, X509Certificate certificate, byte[] dataToSign,
+                                String eContentType) throws NoSuchAlgorithmException, NoSuchProviderException,
+        InvalidAlgorithmParameterException, CertStoreException, CMSException, IOException {
+        CMSSignedDataGenerator signedGenerator = new CMSSignedDataGenerator();
+        signedGenerator.addSigner(privateKey, certificate, CMSSignedGenerator.DIGEST_SHA1);
+
+        Collection<X509Certificate> certList = Collections.singletonList(certificate);
+
+        CertStore certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), "BC");
+        signedGenerator.addCertificatesAndCRLs(certStore);
+
+        CMSProcessableByteArray cmsByteArray = new CMSProcessableByteArray(dataToSign);
+        CMSSignedData signedData = signedGenerator.generate(eContentType, cmsByteArray, true, "BC");
+
+        return signedData.getEncoded();
+    }
+
+
+    /**
+     * Validates a CMS SignedData using the public key corresponding to the private
+     * key used to sign the structure.
+     *
+     * @param signedData
+     * @return true if the signature is valid.
+     * @throws Exception
+     */
+    @SuppressWarnings("unchecked")
+    public static boolean validateSignedData(CMSSignedData signedData) throws Exception {
+        CertStore certs = signedData.getCertificatesAndCRLs("Collection", "BC");
+
+        SignerInformationStore signers = signedData.getSignerInfos();
+        Collection<SignerInformation> c = signers.getSigners();
+        Iterator<SignerInformation> it = c.iterator();
+
+        while (it.hasNext()) {
+            final SignerInformation signer = it.next();
+
+            Collection<? extends Certificate> certCollection = certs.getCertificates( signer.getSID() );
+            /*Collection<? extends Certificate> certCollection = certs
+                .getCertificates(new CertSelector() {
+                    @Override
+                    public boolean match(Certificate cert) {
+                        return false; // check cert and signer
+                    }
+                });
+            */
+            Iterator<? extends Certificate> certIt = certCollection.iterator();
+
+            X509Certificate cert = ( X509Certificate ) certIt.next();
+
+            if ( signer.verify( cert.getPublicKey(), "BC" ) )
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/CertificateChainFactory.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/CertificateChainFactory.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/CertificateChainFactory.java
new file mode 100644
index 0000000..0eac8c6
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/CertificateChainFactory.java
@@ -0,0 +1,283 @@
+/*
+ *  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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+
+/**
+ * Factory for dynamically generating certificate chains.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class CertificateChainFactory {
+    /**
+     * The logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(CertificateChainFactory.class);
+
+    private static int TRUST_ANCHOR_LEVEL = 2;
+
+    private static int INTERMEDIATE_LEVEL = 1;
+
+    private static int END_ENTITY_LEVEL = 0;
+
+    private static SecureRandom secureRandom = new SecureRandom();
+
+    private static String container = "C=US, ST=Maryland, L=Forest Hill, O=Apache Software Foundation, OU=Apache Directory, CN=";
+
+    private static boolean isGenerated = false;
+
+    private static boolean isInitialized = false;
+
+    private static X509Certificate[] clientChain;
+
+    private static X509Certificate[] kdcChain;
+
+    private static PrivateKey clientPrivateKey;
+
+    private static PrivateKey kdcPrivateKey;
+
+
+    public static X509Certificate[] getKdcChain() throws Exception {
+        init();
+
+        return kdcChain;
+    }
+
+
+    public static X509Certificate[] getClientChain() throws Exception {
+        init();
+
+        return clientChain;
+    }
+
+
+    public static PrivateKey getKdcPrivateKey() throws Exception {
+        init();
+
+        return kdcPrivateKey;
+    }
+
+
+    public static PrivateKey getClientPrivateKey() throws Exception {
+        init();
+
+        return clientPrivateKey;
+    }
+
+
+    private static void init() throws Exception {
+        if (!isInitialized) {
+            initClientChain();
+            initKdcChain();
+            isInitialized = true;
+        }
+    }
+
+
+    private static void initClientChain() throws Exception {
+        // Make trust anchor.
+        String friendlyName = "Test Root CA";
+        String dn = container + friendlyName;
+        int validityDays = 730;
+
+        KeyPair keyPair = getKeyPair(TRUST_ANCHOR_LEVEL);
+        PrivateKey trustAnchorPrivateKey = keyPair.getPrivate();
+        PublicKey trustAnchorPublicKey = keyPair.getPublic();
+
+        X509Certificate trustAnchorCert = TrustAnchorGenerator.generate(trustAnchorPublicKey, trustAnchorPrivateKey,
+            dn, validityDays, friendlyName);
+
+        trustAnchorCert.checkValidity();
+        trustAnchorCert.verify(trustAnchorPublicKey);
+
+        logger.debug("Generated cert for friendly name '{}', valid for {} days.", friendlyName, validityDays);
+
+        // Make intermediate client CA.
+        friendlyName = "Client Test CA 1";
+        dn = container + friendlyName;
+        validityDays = 365;
+
+        keyPair = getKeyPair(INTERMEDIATE_LEVEL);
+        PrivateKey clientCaPrivateKey = keyPair.getPrivate();
+        PublicKey clientCaPublicKey = keyPair.getPublic();
+
+        X509Certificate clientCaCert = IntermediateCaGenerator.generate(trustAnchorCert, trustAnchorPrivateKey,
+            clientCaPublicKey, dn, validityDays, friendlyName);
+
+        clientCaCert.checkValidity();
+        clientCaCert.verify(trustAnchorPublicKey);
+
+        logger.debug("Generated cert for friendly name '{}', valid for {} days.", friendlyName, validityDays);
+
+        // Make client certificate.
+        friendlyName = "hnelson@EXAMPLE.COM UPN";
+        dn = container + friendlyName;
+        validityDays = 30;
+
+        keyPair = getKeyPair(END_ENTITY_LEVEL);
+        clientPrivateKey = keyPair.getPrivate();
+        PublicKey clientPublicKey = keyPair.getPublic();
+
+        X509Certificate clientCert = EndEntityGenerator.generate(clientCaCert, clientCaPrivateKey, clientPublicKey,
+            dn, validityDays, friendlyName);
+
+        clientCert.checkValidity();
+        clientCert.verify(clientCaPublicKey);
+
+        logger.debug("Generated cert for friendly name '{}', valid for {} days.", friendlyName, validityDays);
+
+        // Build client chain.
+        clientChain = new X509Certificate[3];
+
+        clientChain[2] = trustAnchorCert;
+        clientChain[1] = clientCaCert;
+        clientChain[0] = clientCert;
+    }
+
+
+    private static void initKdcChain() throws Exception {
+        // Make trust anchor.
+        String friendlyName = "Test Root CA";
+        String dn = container + friendlyName;
+        int validityDays = 730;
+
+        KeyPair keyPair = getKeyPair(TRUST_ANCHOR_LEVEL);
+        PrivateKey trustAnchorPrivateKey = keyPair.getPrivate();
+        PublicKey trustAnchorPublicKey = keyPair.getPublic();
+
+        X509Certificate trustAnchorCert = TrustAnchorGenerator.generate(trustAnchorPublicKey, trustAnchorPrivateKey,
+            dn, validityDays, friendlyName);
+
+        trustAnchorCert.checkValidity();
+        trustAnchorCert.verify(trustAnchorPublicKey);
+
+        logger.debug("Generated cert for friendly name '{}', valid for {} days.", friendlyName, validityDays);
+
+        // Make intermediate KDC CA.
+        friendlyName = "KDC Test CA 1";
+        dn = container + friendlyName;
+        validityDays = 365;
+
+        keyPair = getKeyPair(INTERMEDIATE_LEVEL);
+        PrivateKey kdcCaPrivateKey = keyPair.getPrivate();
+        PublicKey kdcCaPublicKey = keyPair.getPublic();
+
+        X509Certificate kdcCaCert = IntermediateCaGenerator.generate(trustAnchorCert, trustAnchorPrivateKey,
+            kdcCaPublicKey, dn, validityDays, friendlyName);
+
+        kdcCaCert.checkValidity();
+        kdcCaCert.verify(trustAnchorPublicKey);
+
+        logger.debug("Generated cert for friendly name '{}', valid for {} days.", friendlyName, validityDays);
+
+        // Make KDC certificate.
+        friendlyName = "krbtgt/EXAMPLE.COM@EXAMPLE.COM KDC";
+        dn = container + friendlyName;
+        validityDays = 30;
+
+        keyPair = getKeyPair(END_ENTITY_LEVEL);
+        kdcPrivateKey = keyPair.getPrivate();
+        PublicKey kdcPublicKey = keyPair.getPublic();
+
+        X509Certificate kdcCert = EndEntityGenerator.generate(kdcCaCert, kdcCaPrivateKey, kdcPublicKey, dn,
+            validityDays, friendlyName);
+
+        kdcCert.checkValidity();
+        kdcCert.verify(kdcCaPublicKey);
+
+        logger.debug("Generated cert for friendly name '{}', valid for {} days.", friendlyName, validityDays);
+
+        // Build KDC chain.
+        kdcChain = new X509Certificate[3];
+
+        kdcChain[2] = trustAnchorCert;
+        kdcChain[1] = kdcCaCert;
+        kdcChain[0] = kdcCert;
+    }
+
+
+    /**
+     * Get a key pair for the new certificate.  Depending on the static constant
+     * 'isGenerated', these key pairs can be dynamically generated (slower) or
+     * built from static constant values (faster).
+     *
+     * @param level
+     * @return The key pair.
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     * @throws InvalidKeySpecException
+     */
+    private static KeyPair getKeyPair(int level) throws NoSuchAlgorithmException, NoSuchProviderException,
+        InvalidKeySpecException {
+        if (isGenerated) {
+            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+            keyGen.initialize(1024, secureRandom);
+            return keyGen.generateKeyPair();
+        } else {
+            return getStaticKeyPair(level);
+        }
+    }
+
+
+    /**
+     * Get a key pair generated using static key values.  This is much faster than
+     * dynamically generating key values.
+     *
+     * @param level
+     * @return The static key pair.
+     * @throws NoSuchAlgorithmException
+     * @throws NoSuchProviderException
+     * @throws InvalidKeySpecException
+     */
+    private static KeyPair getStaticKeyPair(int level) throws NoSuchAlgorithmException, NoSuchProviderException,
+        InvalidKeySpecException {
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
+
+        switch (level) {
+            case 2:
+                PrivateKey caPrivKey = keyFactory.generatePrivate(KeyPairSpec.caPrivKeySpec);
+                PublicKey caPubKey = keyFactory.generatePublic(KeyPairSpec.caPubKeySpec);
+                return new KeyPair(caPubKey, caPrivKey);
+            case 1:
+                PrivateKey intPrivKey = keyFactory.generatePrivate(KeyPairSpec.intPrivKeySpec);
+                PublicKey intPubKey = keyFactory.generatePublic(KeyPairSpec.intPubKeySpec);
+                return new KeyPair(intPubKey, intPrivKey);
+            case 0:
+            default:
+                PrivateKey privKey = keyFactory.generatePrivate(KeyPairSpec.privKeySpec);
+                PublicKey pubKey = keyFactory.generatePublic(KeyPairSpec.pubKeySpec);
+                return new KeyPair(pubKey, privKey);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/EndEntityGenerator.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/EndEntityGenerator.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/EndEntityGenerator.java
new file mode 100644
index 0000000..1836273
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/EndEntityGenerator.java
@@ -0,0 +1,257 @@
+/*
+ *  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.apache.kerby.kerberos.kerb.spec.pa.pkinit.Krb5PrincipalName;
+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.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.crypto.DataLengthException;
+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 org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
+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$
+ */
+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 SubjectKeyIdentifierStructure( publicKey ) );
+
+        // 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 );
+
+        Krb5PrincipalName principalName = new Krb5PrincipalName();
+        ASN1EncodableVector pkinitSanVector = new ASN1EncodableVector();
+        pkinitSanVector.add( ID_PKINIT_SAN );
+        pkinitSanVector.add( new DERTaggedObject( 0, new DERSequence()));
+        DERSequence pkinitSan = new DERSequence( pkinitSanVector );
+
+        String dnsName = "localhost";
+
+        ASN1EncodableVector sanVector = new ASN1EncodableVector();
+        sanVector.add( new GeneralName( GeneralName.otherName, pkinitSan ) );
+        sanVector.add( new GeneralName( GeneralName.dNSName, dnsName ) );
+        DERSequence san = new DERSequence( sanVector );
+
+        GeneralNames sanGeneralNames = new GeneralNames( san );
+
+        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 SubjectKeyIdentifierStructure(
+            publicKey ) );
+
+        return cert;
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/IntermediateCaGenerator.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/IntermediateCaGenerator.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/IntermediateCaGenerator.java
new file mode 100644
index 0000000..4364647
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/IntermediateCaGenerator.java
@@ -0,0 +1,116 @@
+/*
+ *  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.DERBMPString;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.crypto.DataLengthException;
+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 org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
+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 "intermediate CA" certificate programmatically.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class IntermediateCaGenerator
+{
+    /**
+     * Create 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 SubjectKeyIdentifierStructure( publicKey ) );
+
+        certGen.addExtension( X509Extensions.BasicConstraints, true, new BasicConstraints( 0 ) );
+
+        certGen.addExtension( X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(
+            issuerCert ) );
+
+        certGen.addExtension( X509Extensions.KeyUsage, true, new KeyUsage( KeyUsage.digitalSignature
+            | KeyUsage.keyCertSign | KeyUsage.cRLSign ) );
+
+        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 SubjectKeyIdentifierStructure(
+            publicKey ) );
+
+        return cert;
+    }
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/KeyPairSpec.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/KeyPairSpec.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/KeyPairSpec.java
new file mode 100644
index 0000000..12cf8f9
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/KeyPairSpec.java
@@ -0,0 +1,114 @@
+/*
+ *  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 java.math.BigInteger;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+
+/**
+ * Specifications for asymmetric key pairs.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+class KeyPairSpec
+{
+    // End-entity keys.
+    static RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(
+        new BigInteger(
+            "b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7",
+            16 ), new BigInteger( "11", 16 ) );
+
+    static RSAPrivateCrtKeySpec privKeySpec = new RSAPrivateCrtKeySpec(
+        new BigInteger(
+            "b4a7e46170574f16a97082b22be58b6a2a629798419be12872a4bdba626cfae9900f76abfb12139dce5de56564fab2b6543165a040c606887420e33d91ed7ed7",
+            16 ),
+        new BigInteger( "11", 16 ),
+        new BigInteger(
+            "9f66f6b05410cd503b2709e88115d55daced94d1a34d4e32bf824d0dde6028ae79c5f07b580f5dce240d7111f7ddb130a7945cd7d957d1920994da389f490c89",
+            16 ), new BigInteger( "c0a0758cdf14256f78d4708c86becdead1b50ad4ad6c5c703e2168fbf37884cb", 16 ),
+        new BigInteger( "f01734d7960ea60070f1b06f2bb81bfac48ff192ae18451d5e56c734a5aab8a5", 16 ), new BigInteger(
+            "b54bb9edff22051d9ee60f9351a48591b6500a319429c069a3e335a1d6171391", 16 ), new BigInteger(
+            "d3d83daf2a0cecd3367ae6f8ae1aeb82e9ac2f816c6fc483533d8297dd7884cd", 16 ), new BigInteger(
+            "b8f52fc6f38593dabb661d3f50f8897f8106eee68b1bce78a95b132b4e5b5d19", 16 ) );
+
+    // Intermediate keys.
+    static RSAPublicKeySpec intPubKeySpec = new RSAPublicKeySpec(
+        new BigInteger(
+            "8de0d113c5e736969c8d2b047a243f8fe18edad64cde9e842d3669230ca486f7cfdde1f8eec54d1905fff04acc85e61093e180cadc6cea407f193d44bb0e9449b8dbb49784cd9e36260c39e06a947299978c6ed8300724e887198cfede20f3fbde658fa2bd078be946a392bd349f2b49c486e20c405588e306706c9017308e69",
+            16 ), new BigInteger( "ffff", 16 ) );
+
+    static RSAPrivateCrtKeySpec intPrivKeySpec = new RSAPrivateCrtKeySpec(
+        new BigInteger(
+            "8de0d113c5e736969c8d2b047a243f8fe18edad64cde9e842d3669230ca486f7cfdde1f8eec54d1905fff04acc85e61093e180cadc6cea407f193d44bb0e9449b8dbb49784cd9e36260c39e06a947299978c6ed8300724e887198cfede20f3fbde658fa2bd078be946a392bd349f2b49c486e20c405588e306706c9017308e69",
+            16 ),
+        new BigInteger( "ffff", 16 ),
+        new BigInteger(
+            "7deb1b194a85bcfd29cf871411468adbc987650903e3bacc8338c449ca7b32efd39ffc33bc84412fcd7df18d23ce9d7c25ea910b1ae9985373e0273b4dca7f2e0db3b7314056ac67fd277f8f89cf2fd73c34c6ca69f9ba477143d2b0e2445548aa0b4a8473095182631da46844c356f5e5c7522eb54b5a33f11d730ead9c0cff",
+            16 ),
+        new BigInteger(
+            "ef4cede573cea47f83699b814de4302edb60eefe426c52e17bd7870ec7c6b7a24fe55282ebb73775f369157726fcfb988def2b40350bdca9e5b418340288f649",
+            16 ),
+        new BigInteger(
+            "97c7737d1b9a0088c3c7b528539247fd2a1593e7e01cef18848755be82f4a45aa093276cb0cbf118cb41117540a78f3fc471ba5d69f0042274defc9161265721",
+            16 ),
+        new BigInteger(
+            "6c641094e24d172728b8da3c2777e69adfd0839085be7e38c7c4a2dd00b1ae969f2ec9d23e7e37090fcd449a40af0ed463fe1c612d6810d6b4f58b7bfa31eb5f",
+            16 ),
+        new BigInteger(
+            "70b7123e8e69dfa76feb1236d0a686144b00e9232ed52b73847e74ef3af71fb45ccb24261f40d27f98101e230cf27b977a5d5f1f15f6cf48d5cb1da2a3a3b87f",
+            16 ),
+        new BigInteger(
+            "e38f5750d97e270996a286df2e653fd26c242106436f5bab0f4c7a9e654ce02665d5a281f2c412456f2d1fa26586ef04a9adac9004ca7f913162cb28e13bf40d",
+            16 ) );
+
+    // Trust anchor keys.
+    static RSAPublicKeySpec caPubKeySpec = new RSAPublicKeySpec(
+        new BigInteger(
+            "b259d2d6e627a768c94be36164c2d9fc79d97aab9253140e5bf17751197731d6f7540d2509e7b9ffee0a70a6e26d56e92d2edd7f85aba85600b69089f35f6bdbf3c298e05842535d9f064e6b0391cb7d306e0a2d20c4dfb4e7b49a9640bdea26c10ad69c3f05007ce2513cee44cfe01998e62b6c3637d3fc0391079b26ee36d5",
+            16 ), new BigInteger( "11", 16 ) );
+
+    static RSAPrivateCrtKeySpec caPrivKeySpec = new RSAPrivateCrtKeySpec(
+        new BigInteger(
+            "b259d2d6e627a768c94be36164c2d9fc79d97aab9253140e5bf17751197731d6f7540d2509e7b9ffee0a70a6e26d56e92d2edd7f85aba85600b69089f35f6bdbf3c298e05842535d9f064e6b0391cb7d306e0a2d20c4dfb4e7b49a9640bdea26c10ad69c3f05007ce2513cee44cfe01998e62b6c3637d3fc0391079b26ee36d5",
+            16 ),
+        new BigInteger( "11", 16 ),
+        new BigInteger(
+            "92e08f83cc9920746989ca5034dcb384a094fb9c5a6288fcc4304424ab8f56388f72652d8fafc65a4b9020896f2cde297080f2a540e7b7ce5af0b3446e1258d1dd7f245cf54124b4c6e17da21b90a0ebd22605e6f45c9f136d7a13eaac1c0f7487de8bd6d924972408ebb58af71e76fd7b012a8d0e165f3ae2e5077a8648e619",
+            16 ),
+        new BigInteger(
+            "f75e80839b9b9379f1cf1128f321639757dba514642c206bbbd99f9a4846208b3e93fbbe5e0527cc59b1d4b929d9555853004c7c8b30ee6a213c3d1bb7415d03",
+            16 ),
+        new BigInteger(
+            "b892d9ebdbfc37e397256dd8a5d3123534d1f03726284743ddc6be3a709edb696fc40c7d902ed804c6eee730eee3d5b20bf6bd8d87a296813c87d3b3cc9d7947",
+            16 ),
+        new BigInteger(
+            "1d1a2d3ca8e52068b3094d501c9a842fec37f54db16e9a67070a8b3f53cc03d4257ad252a1a640eadd603724d7bf3737914b544ae332eedf4f34436cac25ceb5",
+            16 ),
+        new BigInteger(
+            "6c929e4e81672fef49d9c825163fec97c4b7ba7acb26c0824638ac22605d7201c94625770984f78a56e6e25904fe7db407099cad9b14588841b94f5ab498dded",
+            16 ),
+        new BigInteger(
+            "dae7651ee69ad1d081ec5e7188ae126f6004ff39556bde90e0b870962fa7b926d070686d8244fe5a9aa709a95686a104614834b0ada4b10f53197a5cb4c97339",
+            16 ) );
+}

http://git-wip-us.apache.org/repos/asf/directory-kerby/blob/c98ab976/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/TrustAnchorGenerator.java
----------------------------------------------------------------------
diff --git a/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/TrustAnchorGenerator.java b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/TrustAnchorGenerator.java
new file mode 100644
index 0000000..f3152d9
--- /dev/null
+++ b/kerby-kerb/kerb-client/src/main/java/org/apache/kerby/kerberos/kerb/client/preauth/pkinit/certs/TrustAnchorGenerator.java
@@ -0,0 +1,110 @@
+/*
+ *  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.DERBMPString;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.X509Extensions;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.jce.X509Principal;
+import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.x509.X509V3CertificateGenerator;
+import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
+
+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 "trust anchor" certificate programmatically.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$, $Date$
+ */
+public class TrustAnchorGenerator
+{
+    /**
+     * Create CA certificate.
+     * 
+     * @param publicKey 
+     * @param privateKey 
+     * @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( PublicKey publicKey, PrivateKey privateKey, 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() ) );
+
+        X509Principal x509Principal = new X509Principal( dn );
+        certGen.setIssuerDN( x509Principal );
+        certGen.setSubjectDN( x509Principal );
+
+        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 SubjectKeyIdentifierStructure( publicKey ) );
+
+        certGen.addExtension( X509Extensions.BasicConstraints, true, new BasicConstraints( 1 ) );
+
+        certGen.addExtension( X509Extensions.KeyUsage, true, new KeyUsage( KeyUsage.digitalSignature
+            | KeyUsage.keyCertSign | KeyUsage.cRLSign ) );
+
+        X509Certificate cert = certGen.generate( privateKey );
+
+        PKCS12BagAttributeCarrier bagAttr = ( PKCS12BagAttributeCarrier ) cert;
+
+        bagAttr.setBagAttribute( PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString( friendlyName ) );
+        bagAttr.setBagAttribute( PKCSObjectIdentifiers.pkcs_9_at_localKeyId, new SubjectKeyIdentifierStructure(
+            publicKey ) );
+
+        return cert;
+    }
+}