parent
89028613d8
commit
8ce10df6da
19 changed files with 810 additions and 217 deletions
|
@ -42,14 +42,7 @@
|
||||||
</keycloak.osgi.import>
|
</keycloak.osgi.import>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcpkix-jdk15on</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed 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.keycloak.common.crypto;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface CertificateUtilsProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates version 3 {@link java.security.cert.X509Certificate}.
|
||||||
|
*
|
||||||
|
* @param keyPair the key pair
|
||||||
|
* @param caPrivateKey the CA private key
|
||||||
|
* @param caCert the CA certificate
|
||||||
|
* @param subject the subject name
|
||||||
|
*
|
||||||
|
* @return the x509 certificate
|
||||||
|
*
|
||||||
|
* @throws Exception the exception
|
||||||
|
*/
|
||||||
|
public X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert,
|
||||||
|
String subject) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate version 1 self signed {@link java.security.cert.X509Certificate}..
|
||||||
|
*
|
||||||
|
* @param caKeyPair the CA key pair
|
||||||
|
* @param subject the subject name
|
||||||
|
*
|
||||||
|
* @return the x509 certificate
|
||||||
|
*
|
||||||
|
* @throws Exception the exception
|
||||||
|
*/
|
||||||
|
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject);
|
||||||
|
|
||||||
|
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject, BigInteger serialNumber);
|
||||||
|
|
||||||
|
}
|
|
@ -25,4 +25,22 @@ public interface CryptoProvider {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
<T> T getAlgorithmProvider(Class<T> clazz, String algorithm);
|
<T> T getAlgorithmProvider(Class<T> clazz, String algorithm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get CertificateUtils implementation. Returned implementation can be dependent according to if we have
|
||||||
|
* non-fips bouncycastle or fips bouncycastle on the classpath.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public CertificateUtilsProvider getCertificateUtils();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get PEMUtils implementation. Returned implementation can be dependent according to if we have
|
||||||
|
* non-fips bouncycastle or fips bouncycastle on the classpath.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public PemUtilsProvider getPemUtils();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
170
common/src/main/java/org/keycloak/common/crypto/PemUtilsProvider.java
Executable file
170
common/src/main/java/org/keycloak/common/crypto/PemUtilsProvider.java
Executable file
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed 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.keycloak.common.crypto;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Base64;
|
||||||
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.common.util.DerUtils;
|
||||||
|
import org.keycloak.common.util.PemException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility classes to extract PublicKey, PrivateKey, and X509Certificate from openssl generated PEM files
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public abstract class PemUtilsProvider {
|
||||||
|
|
||||||
|
public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
|
||||||
|
public static final String END_CERT = "-----END CERTIFICATE-----";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a X509 Certificate from a PEM string
|
||||||
|
*
|
||||||
|
* @param cert
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public X509Certificate decodeCertificate(String cert) {
|
||||||
|
if (cert == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] der = pemToDer(cert);
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
||||||
|
return DerUtils.decodeCertificate(bis);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PemException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a Public Key from a PEM string
|
||||||
|
*
|
||||||
|
* @param pem
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public PublicKey decodePublicKey(String pem) {
|
||||||
|
return decodePublicKey(pem, "RSA");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a Public Key from a PEM string
|
||||||
|
* @param pem The pem encoded pblic key
|
||||||
|
* @param type The type of the key (RSA, EC,...)
|
||||||
|
* @return The public key or null
|
||||||
|
*/
|
||||||
|
public PublicKey decodePublicKey(String pem, String type) {
|
||||||
|
if (pem == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] der = pemToDer(pem);
|
||||||
|
return DerUtils.decodePublicKey(der, type);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PemException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a Private Key from a PEM string
|
||||||
|
*
|
||||||
|
* @param pem
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public PrivateKey decodePrivateKey(String pem) {
|
||||||
|
if (pem == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] der = pemToDer(pem);
|
||||||
|
return DerUtils.decodePrivateKey(der);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PemException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a Key to a PEM string
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public String encodeKey(Key key) {
|
||||||
|
return encode(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a X509 Certificate to a PEM string
|
||||||
|
*
|
||||||
|
* @param certificate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String encodeCertificate(Certificate certificate) {
|
||||||
|
return encode(certificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] pemToDer(String pem) {
|
||||||
|
try {
|
||||||
|
pem = removeBeginEnd(pem);
|
||||||
|
return Base64.decode(pem);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new PemException(ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String removeBeginEnd(String pem) {
|
||||||
|
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
|
||||||
|
pem = pem.replaceAll("-----END (.*)----", "");
|
||||||
|
pem = pem.replaceAll("\r\n", "");
|
||||||
|
pem = pem.replaceAll("\n", "");
|
||||||
|
return pem.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateThumbprint(String[] certChain, String encoding) throws NoSuchAlgorithmException{
|
||||||
|
return Base64Url.encode(generateThumbprintBytes(certChain, encoding));
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] generateThumbprintBytes(String[] certChain, String encoding) throws NoSuchAlgorithmException {
|
||||||
|
return MessageDigest.getInstance(encoding).digest(pemToDer(certChain[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String encode(Object obj);
|
||||||
|
|
||||||
|
}
|
|
@ -17,38 +17,24 @@
|
||||||
|
|
||||||
package org.keycloak.common.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
|
||||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
|
||||||
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
|
||||||
import org.bouncycastle.asn1.x509.Extension;
|
|
||||||
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
|
||||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
|
||||||
import org.bouncycastle.cert.X509CertificateHolder;
|
|
||||||
import org.bouncycastle.cert.X509v1CertificateBuilder;
|
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
|
||||||
import org.bouncycastle.operator.ContentSigner;
|
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
import org.keycloak.common.crypto.CryptoIntegration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
|
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
|
|
||||||
* @version $Revision: 2 $
|
|
||||||
*/
|
*/
|
||||||
public class CertificateUtils {
|
public class CertificateUtils {
|
||||||
|
|
||||||
|
static {
|
||||||
|
CryptoIntegration.init(ClassLoader.getSystemClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates version 3 {@link java.security.cert.X509Certificate}.
|
* Generates version 3 {@link java.security.cert.X509Certificate}.
|
||||||
*
|
*
|
||||||
|
@ -61,57 +47,10 @@ public class CertificateUtils {
|
||||||
*
|
*
|
||||||
* @throws Exception the exception
|
* @throws Exception the exception
|
||||||
*/
|
*/
|
||||||
public static X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert,
|
public static X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey,
|
||||||
String subject) throws Exception {
|
X509Certificate caCert, String subject) throws Exception {
|
||||||
try {
|
return CryptoIntegration.getProvider().getCertificateUtils().generateV3Certificate(keyPair, caPrivateKey,
|
||||||
X500Name subjectDN = new X500Name("CN=" + subject);
|
caCert, subject);
|
||||||
|
|
||||||
// Serial Number
|
|
||||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
|
||||||
BigInteger serialNumber = BigInteger.valueOf(Math.abs(random.nextInt()));
|
|
||||||
|
|
||||||
// Validity
|
|
||||||
Date notBefore = new Date(System.currentTimeMillis());
|
|
||||||
Date notAfter = new Date(System.currentTimeMillis() + (((1000L * 60 * 60 * 24 * 30)) * 12) * 3);
|
|
||||||
|
|
||||||
// SubjectPublicKeyInfo
|
|
||||||
SubjectPublicKeyInfo subjPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
|
|
||||||
|
|
||||||
X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(new X500Name(caCert.getSubjectDN().getName()),
|
|
||||||
serialNumber, notBefore, notAfter, subjectDN, subjPubKeyInfo);
|
|
||||||
|
|
||||||
JcaX509ExtensionUtils x509ExtensionUtils = new JcaX509ExtensionUtils();
|
|
||||||
|
|
||||||
// Subject Key Identifier
|
|
||||||
certGen.addExtension(Extension.subjectKeyIdentifier, false,
|
|
||||||
x509ExtensionUtils.createSubjectKeyIdentifier(subjPubKeyInfo));
|
|
||||||
|
|
||||||
// Authority Key Identifier
|
|
||||||
certGen.addExtension(Extension.authorityKeyIdentifier, false,
|
|
||||||
x509ExtensionUtils.createAuthorityKeyIdentifier(subjPubKeyInfo));
|
|
||||||
|
|
||||||
// Key Usage
|
|
||||||
certGen.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign
|
|
||||||
| KeyUsage.cRLSign));
|
|
||||||
|
|
||||||
// Extended Key Usage
|
|
||||||
KeyPurposeId[] EKU = new KeyPurposeId[2];
|
|
||||||
EKU[0] = KeyPurposeId.id_kp_emailProtection;
|
|
||||||
EKU[1] = KeyPurposeId.id_kp_serverAuth;
|
|
||||||
|
|
||||||
certGen.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(EKU));
|
|
||||||
|
|
||||||
// Basic Constraints
|
|
||||||
certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));
|
|
||||||
|
|
||||||
// Content Signer
|
|
||||||
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BouncyIntegration.PROVIDER).build(caPrivateKey);
|
|
||||||
|
|
||||||
// Certificate
|
|
||||||
return new JcaX509CertificateConverter().setProvider(BouncyIntegration.PROVIDER).getCertificate(certGen.build(sigGen));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Error creating X509v3Certificate.", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,42 +64,11 @@ public class CertificateUtils {
|
||||||
* @throws Exception the exception
|
* @throws Exception the exception
|
||||||
*/
|
*/
|
||||||
public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) {
|
public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) {
|
||||||
return generateV1SelfSignedCertificate(caKeyPair, subject, BigInteger.valueOf(System.currentTimeMillis()));
|
return CryptoIntegration.getProvider().getCertificateUtils().generateV1SelfSignedCertificate(caKeyPair, subject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject, BigInteger serialNumber) {
|
public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject, BigInteger serialNumber) {
|
||||||
try {
|
return CryptoIntegration.getProvider().getCertificateUtils().generateV1SelfSignedCertificate(caKeyPair, subject, serialNumber);
|
||||||
X500Name subjectDN = new X500Name("CN=" + subject);
|
|
||||||
Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
calendar.add(Calendar.YEAR, 10);
|
|
||||||
Date validityEndDate = new Date(calendar.getTime().getTime());
|
|
||||||
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(caKeyPair.getPublic().getEncoded());
|
|
||||||
|
|
||||||
X509v1CertificateBuilder builder = new X509v1CertificateBuilder(subjectDN, serialNumber, validityStartDate,
|
|
||||||
validityEndDate, subjectDN, subPubKeyInfo);
|
|
||||||
X509CertificateHolder holder = builder.build(createSigner(caKeyPair.getPrivate()));
|
|
||||||
|
|
||||||
return new JcaX509CertificateConverter().getCertificate(holder);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Error creating X509v1Certificate.", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the content signer for generation of Version 1 {@link java.security.cert.X509Certificate}.
|
|
||||||
*
|
|
||||||
* @param privateKey the private key
|
|
||||||
*
|
|
||||||
* @return the content signer
|
|
||||||
*/
|
|
||||||
public static ContentSigner createSigner(PrivateKey privateKey) {
|
|
||||||
try {
|
|
||||||
JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
|
|
||||||
.setProvider(BouncyIntegration.PROVIDER);
|
|
||||||
return signerBuilder.build(privateKey);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Could not create content signer.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,31 +17,28 @@
|
||||||
|
|
||||||
package org.keycloak.common.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
import org.keycloak.common.crypto.CryptoIntegration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility classes to extract PublicKey, PrivateKey, and X509Certificate from openssl generated PEM files
|
* Utility classes to extract PublicKey, PrivateKey, and X509Certificate from openssl generated PEM files
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public final class PemUtils {
|
public class PemUtils {
|
||||||
|
|
||||||
public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
|
public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
|
||||||
public static final String END_CERT = "-----END CERTIFICATE-----";
|
public static final String END_CERT = "-----END CERTIFICATE-----";
|
||||||
|
|
||||||
private PemUtils() {
|
static {
|
||||||
|
CryptoIntegration.init(ClassLoader.getSystemClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,18 +49,9 @@ public final class PemUtils {
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static X509Certificate decodeCertificate(String cert) {
|
public static X509Certificate decodeCertificate(String cert) {
|
||||||
if (cert == null) {
|
return CryptoIntegration.getProvider().getPemUtils().decodeCertificate(cert);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] der = pemToDer(cert);
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
|
||||||
return DerUtils.decodeCertificate(bis);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new PemException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a Public Key from a PEM string
|
* Decode a Public Key from a PEM string
|
||||||
|
@ -73,7 +61,7 @@ public final class PemUtils {
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static PublicKey decodePublicKey(String pem) {
|
public static PublicKey decodePublicKey(String pem) {
|
||||||
return decodePublicKey(pem, "RSA");
|
return CryptoIntegration.getProvider().getPemUtils().decodePublicKey(pem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,18 +70,10 @@ public final class PemUtils {
|
||||||
* @param type The type of the key (RSA, EC,...)
|
* @param type The type of the key (RSA, EC,...)
|
||||||
* @return The public key or null
|
* @return The public key or null
|
||||||
*/
|
*/
|
||||||
public static PublicKey decodePublicKey(String pem, String type) {
|
public static PublicKey decodePublicKey(String pem, String type){
|
||||||
if (pem == null) {
|
return CryptoIntegration.getProvider().getPemUtils().decodePublicKey(pem, type);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] der = pemToDer(pem);
|
|
||||||
return DerUtils.decodePublicKey(der, type);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new PemException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a Private Key from a PEM string
|
* Decode a Private Key from a PEM string
|
||||||
|
@ -102,18 +82,10 @@ public final class PemUtils {
|
||||||
* @return
|
* @return
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static PrivateKey decodePrivateKey(String pem) {
|
public static PrivateKey decodePrivateKey(String pem){
|
||||||
if (pem == null) {
|
return CryptoIntegration.getProvider().getPemUtils().decodePrivateKey(pem);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] der = pemToDer(pem);
|
|
||||||
return DerUtils.decodePrivateKey(der);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new PemException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a Key to a PEM string
|
* Encode a Key to a PEM string
|
||||||
|
@ -122,8 +94,8 @@ public final class PemUtils {
|
||||||
* @return
|
* @return
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public static String encodeKey(Key key) {
|
public static String encodeKey(Key key){
|
||||||
return encode(key);
|
return CryptoIntegration.getProvider().getPemUtils().encodeKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -132,51 +104,20 @@ public final class PemUtils {
|
||||||
* @param certificate
|
* @param certificate
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String encodeCertificate(Certificate certificate) {
|
public static String encodeCertificate(Certificate certificate){
|
||||||
return encode(certificate);
|
return CryptoIntegration.getProvider().getPemUtils().encodeCertificate(certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String encode(Object obj) {
|
public static byte[] pemToDer(String pem){
|
||||||
if (obj == null) {
|
return CryptoIntegration.getProvider().getPemUtils().pemToDer(pem);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
public static String removeBeginEnd(String pem){
|
||||||
StringWriter writer = new StringWriter();
|
return CryptoIntegration.getProvider().getPemUtils().removeBeginEnd(pem);
|
||||||
JcaPEMWriter pemWriter = new JcaPEMWriter(writer);
|
|
||||||
pemWriter.writeObject(obj);
|
|
||||||
pemWriter.flush();
|
|
||||||
pemWriter.close();
|
|
||||||
String s = writer.toString();
|
|
||||||
return PemUtils.removeBeginEnd(s);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new PemException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] pemToDer(String pem) {
|
public static String generateThumbprint(String[] certChain, String encoding) throws NoSuchAlgorithmException{
|
||||||
try {
|
return CryptoIntegration.getProvider().getPemUtils().generateThumbprint(certChain, encoding);
|
||||||
pem = removeBeginEnd(pem);
|
|
||||||
return Base64.decode(pem);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new PemException(ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String removeBeginEnd(String pem) {
|
|
||||||
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
|
|
||||||
pem = pem.replaceAll("-----END (.*)----", "");
|
|
||||||
pem = pem.replaceAll("\r\n", "");
|
|
||||||
pem = pem.replaceAll("\n", "");
|
|
||||||
return pem.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String generateThumbprint(String[] certChain, String encoding) throws NoSuchAlgorithmException {
|
|
||||||
return Base64Url.encode(generateThumbprintBytes(certChain, encoding));
|
|
||||||
}
|
|
||||||
|
|
||||||
static byte[] generateThumbprintBytes(String[] certChain, String encoding) throws NoSuchAlgorithmException {
|
|
||||||
return MessageDigest.getInstance(encoding).digest(pemToDer(certChain[0]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed 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.keycloak.crypto.def;
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
|
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||||
|
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
||||||
|
import org.bouncycastle.asn1.x509.Extension;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
|
import org.bouncycastle.cert.X509v1CertificateBuilder;
|
||||||
|
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||||
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
import org.keycloak.common.util.BouncyIntegration;
|
||||||
|
import org.keycloak.common.crypto.CertificateUtilsProvider;
|
||||||
|
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
|
||||||
|
* @version $Revision: 2 $
|
||||||
|
*/
|
||||||
|
public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates version 3 {@link java.security.cert.X509Certificate}.
|
||||||
|
*
|
||||||
|
* @param keyPair the key pair
|
||||||
|
* @param caPrivateKey the CA private key
|
||||||
|
* @param caCert the CA certificate
|
||||||
|
* @param subject the subject name
|
||||||
|
*
|
||||||
|
* @return the x509 certificate
|
||||||
|
*
|
||||||
|
* @throws Exception the exception
|
||||||
|
*/
|
||||||
|
public X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert,
|
||||||
|
String subject) throws Exception {
|
||||||
|
try {
|
||||||
|
X500Name subjectDN = new X500Name("CN=" + subject);
|
||||||
|
|
||||||
|
// Serial Number
|
||||||
|
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
BigInteger serialNumber = BigInteger.valueOf(Math.abs(random.nextInt()));
|
||||||
|
|
||||||
|
// Validity
|
||||||
|
Date notBefore = new Date(System.currentTimeMillis());
|
||||||
|
Date notAfter = new Date(System.currentTimeMillis() + (((1000L * 60 * 60 * 24 * 30)) * 12) * 3);
|
||||||
|
|
||||||
|
// SubjectPublicKeyInfo
|
||||||
|
SubjectPublicKeyInfo subjPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
|
||||||
|
|
||||||
|
X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(new X500Name(caCert.getSubjectDN().getName()),
|
||||||
|
serialNumber, notBefore, notAfter, subjectDN, subjPubKeyInfo);
|
||||||
|
|
||||||
|
JcaX509ExtensionUtils x509ExtensionUtils = new JcaX509ExtensionUtils();
|
||||||
|
|
||||||
|
// Subject Key Identifier
|
||||||
|
certGen.addExtension(Extension.subjectKeyIdentifier, false,
|
||||||
|
x509ExtensionUtils.createSubjectKeyIdentifier(subjPubKeyInfo));
|
||||||
|
|
||||||
|
// Authority Key Identifier
|
||||||
|
certGen.addExtension(Extension.authorityKeyIdentifier, false,
|
||||||
|
x509ExtensionUtils.createAuthorityKeyIdentifier(subjPubKeyInfo));
|
||||||
|
|
||||||
|
// Key Usage
|
||||||
|
certGen.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign
|
||||||
|
| KeyUsage.cRLSign));
|
||||||
|
|
||||||
|
// Extended Key Usage
|
||||||
|
KeyPurposeId[] EKU = new KeyPurposeId[2];
|
||||||
|
EKU[0] = KeyPurposeId.id_kp_emailProtection;
|
||||||
|
EKU[1] = KeyPurposeId.id_kp_serverAuth;
|
||||||
|
|
||||||
|
certGen.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(EKU));
|
||||||
|
|
||||||
|
// Basic Constraints
|
||||||
|
certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));
|
||||||
|
|
||||||
|
// Content Signer
|
||||||
|
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BouncyIntegration.PROVIDER).build(caPrivateKey);
|
||||||
|
|
||||||
|
// Certificate
|
||||||
|
return new JcaX509CertificateConverter().setProvider(BouncyIntegration.PROVIDER).getCertificate(certGen.build(sigGen));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error creating X509v3Certificate.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate version 1 self signed {@link java.security.cert.X509Certificate}..
|
||||||
|
*
|
||||||
|
* @param caKeyPair the CA key pair
|
||||||
|
* @param subject the subject name
|
||||||
|
*
|
||||||
|
* @return the x509 certificate
|
||||||
|
*
|
||||||
|
* @throws Exception the exception
|
||||||
|
*/
|
||||||
|
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) {
|
||||||
|
return generateV1SelfSignedCertificate(caKeyPair, subject, BigInteger.valueOf(System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject, BigInteger serialNumber) {
|
||||||
|
try {
|
||||||
|
X500Name subjectDN = new X500Name("CN=" + subject);
|
||||||
|
Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.add(Calendar.YEAR, 10);
|
||||||
|
Date validityEndDate = new Date(calendar.getTime().getTime());
|
||||||
|
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(caKeyPair.getPublic().getEncoded());
|
||||||
|
|
||||||
|
X509v1CertificateBuilder builder = new X509v1CertificateBuilder(subjectDN, serialNumber, validityStartDate,
|
||||||
|
validityEndDate, subjectDN, subPubKeyInfo);
|
||||||
|
X509CertificateHolder holder = builder.build(createSigner(caKeyPair.getPrivate()));
|
||||||
|
|
||||||
|
return new JcaX509CertificateConverter().getCertificate(holder);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error creating X509v1Certificate.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the content signer for generation of Version 1 {@link java.security.cert.X509Certificate}.
|
||||||
|
*
|
||||||
|
* @param privateKey the private key
|
||||||
|
*
|
||||||
|
* @return the content signer
|
||||||
|
*/
|
||||||
|
private ContentSigner createSigner(PrivateKey privateKey) {
|
||||||
|
try {
|
||||||
|
JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
|
||||||
|
.setProvider(BouncyIntegration.PROVIDER);
|
||||||
|
return signerBuilder.build(privateKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Could not create content signer.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
crypto/default/src/main/java/org/keycloak/crypto/def/BCPemUtilsProvider.java
Executable file
60
crypto/default/src/main/java/org/keycloak/crypto/def/BCPemUtilsProvider.java
Executable file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed 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.keycloak.crypto.def;
|
||||||
|
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||||
|
import org.keycloak.common.util.PemException;
|
||||||
|
import org.keycloak.common.crypto.PemUtilsProvider;
|
||||||
|
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes Key or Certificates to PEM format string
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:david.anderson@redhat.com">David Anderson</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class BCPemUtilsProvider extends PemUtilsProvider {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode object to JCA PEM String using BC libraries
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
* @return The encoded PEM string
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String encode(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
JcaPEMWriter pemWriter = new JcaPEMWriter(writer);
|
||||||
|
pemWriter.writeObject(obj);
|
||||||
|
pemWriter.flush();
|
||||||
|
pemWriter.close();
|
||||||
|
String s = writer.toString();
|
||||||
|
return removeBeginEnd(s);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PemException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,6 +9,8 @@ import java.util.function.Supplier;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.keycloak.common.crypto.CryptoProvider;
|
import org.keycloak.common.crypto.CryptoProvider;
|
||||||
import org.keycloak.common.crypto.CryptoProviderTypes;
|
import org.keycloak.common.crypto.CryptoProviderTypes;
|
||||||
|
import org.keycloak.common.crypto.CertificateUtilsProvider;
|
||||||
|
import org.keycloak.common.crypto.PemUtilsProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
@ -35,4 +37,15 @@ public class DefaultCryptoProvider implements CryptoProvider {
|
||||||
}
|
}
|
||||||
return clazz.cast(o);
|
return clazz.cast(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CertificateUtilsProvider getCertificateUtils() {
|
||||||
|
return new BCCertificateUtilsProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PemUtilsProvider getPemUtils() {
|
||||||
|
return new BCPemUtilsProvider();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.keycloak.crypto.def.test;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class PemUtilsBCTest {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateThumbprintSha1() throws NoSuchAlgorithmException {
|
||||||
|
String[] test = new String[] {"abcdefg"};
|
||||||
|
String encoded = org.keycloak.common.util.PemUtils.generateThumbprint(test, "SHA-1");
|
||||||
|
assertEquals(27, encoded.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateThumbprintSha256() throws NoSuchAlgorithmException {
|
||||||
|
String[] test = new String[] {"abcdefg"};
|
||||||
|
String encoded = org.keycloak.common.util.PemUtils.generateThumbprint(test, "SHA-256");
|
||||||
|
assertEquals(43, encoded.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed 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.keycloak.crypto.fips;
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
|
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||||
|
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
||||||
|
import org.bouncycastle.asn1.x509.Extension;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
|
import org.bouncycastle.cert.X509v1CertificateBuilder;
|
||||||
|
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||||
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
import org.keycloak.common.util.BouncyIntegration;
|
||||||
|
import org.keycloak.common.crypto.CertificateUtilsProvider;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
|
||||||
|
* @version $Revision: 2 $
|
||||||
|
*/
|
||||||
|
public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates version 3 {@link java.security.cert.X509Certificate}.
|
||||||
|
*
|
||||||
|
* @param keyPair the key pair
|
||||||
|
* @param caPrivateKey the CA private key
|
||||||
|
* @param caCert the CA certificate
|
||||||
|
* @param subject the subject name
|
||||||
|
*
|
||||||
|
* @return the x509 certificate
|
||||||
|
*
|
||||||
|
* @throws Exception the exception
|
||||||
|
*/
|
||||||
|
public X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert,
|
||||||
|
String subject) throws Exception {
|
||||||
|
try {
|
||||||
|
X500Name subjectDN = new X500Name("CN=" + subject);
|
||||||
|
|
||||||
|
// Serial Number
|
||||||
|
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||||
|
BigInteger serialNumber = BigInteger.valueOf(Math.abs(random.nextInt()));
|
||||||
|
|
||||||
|
// Validity
|
||||||
|
Date notBefore = new Date(System.currentTimeMillis());
|
||||||
|
Date notAfter = new Date(System.currentTimeMillis() + (((1000L * 60 * 60 * 24 * 30)) * 12) * 3);
|
||||||
|
|
||||||
|
// SubjectPublicKeyInfo
|
||||||
|
SubjectPublicKeyInfo subjPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
|
||||||
|
|
||||||
|
X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(new X500Name(caCert.getSubjectDN().getName()),
|
||||||
|
serialNumber, notBefore, notAfter, subjectDN, subjPubKeyInfo);
|
||||||
|
|
||||||
|
JcaX509ExtensionUtils x509ExtensionUtils = new JcaX509ExtensionUtils();
|
||||||
|
|
||||||
|
// Subject Key Identifier
|
||||||
|
certGen.addExtension(Extension.subjectKeyIdentifier, false,
|
||||||
|
x509ExtensionUtils.createSubjectKeyIdentifier(subjPubKeyInfo));
|
||||||
|
|
||||||
|
// Authority Key Identifier
|
||||||
|
certGen.addExtension(Extension.authorityKeyIdentifier, false,
|
||||||
|
x509ExtensionUtils.createAuthorityKeyIdentifier(subjPubKeyInfo));
|
||||||
|
|
||||||
|
// Key Usage
|
||||||
|
certGen.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign
|
||||||
|
| KeyUsage.cRLSign));
|
||||||
|
|
||||||
|
// Extended Key Usage
|
||||||
|
KeyPurposeId[] EKU = new KeyPurposeId[2];
|
||||||
|
EKU[0] = KeyPurposeId.id_kp_emailProtection;
|
||||||
|
EKU[1] = KeyPurposeId.id_kp_serverAuth;
|
||||||
|
|
||||||
|
certGen.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(EKU));
|
||||||
|
|
||||||
|
// Basic Constraints
|
||||||
|
certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));
|
||||||
|
|
||||||
|
// Content Signer
|
||||||
|
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BouncyIntegration.PROVIDER).build(caPrivateKey);
|
||||||
|
|
||||||
|
// Certificate
|
||||||
|
return new JcaX509CertificateConverter().setProvider(BouncyIntegration.PROVIDER).getCertificate(certGen.build(sigGen));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error creating X509v3Certificate.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate version 1 self signed {@link java.security.cert.X509Certificate}..
|
||||||
|
*
|
||||||
|
* @param caKeyPair the CA key pair
|
||||||
|
* @param subject the subject name
|
||||||
|
*
|
||||||
|
* @return the x509 certificate
|
||||||
|
*
|
||||||
|
* @throws Exception the exception
|
||||||
|
*/
|
||||||
|
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) {
|
||||||
|
return generateV1SelfSignedCertificate(caKeyPair, subject, BigInteger.valueOf(System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject, BigInteger serialNumber) {
|
||||||
|
try {
|
||||||
|
X500Name subjectDN = new X500Name("CN=" + subject);
|
||||||
|
Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.add(Calendar.YEAR, 10);
|
||||||
|
Date validityEndDate = new Date(calendar.getTime().getTime());
|
||||||
|
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(caKeyPair.getPublic().getEncoded());
|
||||||
|
|
||||||
|
X509v1CertificateBuilder builder = new X509v1CertificateBuilder(subjectDN, serialNumber, validityStartDate,
|
||||||
|
validityEndDate, subjectDN, subPubKeyInfo);
|
||||||
|
X509CertificateHolder holder = builder.build(createSigner(caKeyPair.getPrivate()));
|
||||||
|
|
||||||
|
return new JcaX509CertificateConverter().getCertificate(holder);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error creating X509v1Certificate.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the content signer for generation of Version 1 {@link java.security.cert.X509Certificate}.
|
||||||
|
*
|
||||||
|
* @param privateKey the private key
|
||||||
|
*
|
||||||
|
* @return the content signer
|
||||||
|
*/
|
||||||
|
private ContentSigner createSigner(PrivateKey privateKey) {
|
||||||
|
try {
|
||||||
|
JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
|
||||||
|
.setProvider(BouncyIntegration.PROVIDER);
|
||||||
|
return signerBuilder.build(privateKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Could not create content signer.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed 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.keycloak.crypto.fips;
|
||||||
|
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||||
|
import org.keycloak.common.util.PemException;
|
||||||
|
import org.keycloak.common.crypto.PemUtilsProvider;
|
||||||
|
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes Key or Certificates to PEM format string
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:david.anderson@redhat.com">David Anderson</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class BCFIPSPemUtilsProvider extends PemUtilsProvider {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode object to JCA PEM String using BC FIPS libraries
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
* @return The encoded PEM string
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected String encode(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
JcaPEMWriter pemWriter = new JcaPEMWriter(writer);
|
||||||
|
pemWriter.writeObject(obj);
|
||||||
|
pemWriter.flush();
|
||||||
|
pemWriter.close();
|
||||||
|
String s = writer.toString();
|
||||||
|
return removeBeginEnd(s);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PemException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ import java.util.function.Supplier;
|
||||||
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
|
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
|
||||||
import org.keycloak.common.crypto.CryptoProvider;
|
import org.keycloak.common.crypto.CryptoProvider;
|
||||||
import org.keycloak.common.crypto.CryptoProviderTypes;
|
import org.keycloak.common.crypto.CryptoProviderTypes;
|
||||||
|
import org.keycloak.common.crypto.CertificateUtilsProvider;
|
||||||
|
import org.keycloak.common.crypto.PemUtilsProvider;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,4 +41,14 @@ public class FIPS1402Provider implements CryptoProvider {
|
||||||
}
|
}
|
||||||
return clazz.cast(o);
|
return clazz.cast(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CertificateUtilsProvider getCertificateUtils() {
|
||||||
|
return new BCFIPSCertificateUtilsProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PemUtilsProvider getPemUtils() {
|
||||||
|
return new BCFIPSPemUtilsProvider();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,13 @@
|
||||||
package org.keycloak.common.util;
|
package org.keycloak.crypto.fips.test;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class PemUtilsTest {
|
public class PemUtilsBCFIPSTest {
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGenerateThumbprintBytesSha1() throws NoSuchAlgorithmException {
|
|
||||||
String[] test = new String[] {"abcdefg"};
|
|
||||||
byte[] digest = PemUtils.generateThumbprintBytes(test, "SHA-1");
|
|
||||||
assertEquals(20, digest.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGenerateThumbprintBytesSha256() throws NoSuchAlgorithmException {
|
|
||||||
String[] test = new String[] {"abcdefg"};
|
|
||||||
byte[] digest = PemUtils.generateThumbprintBytes(test, "SHA-256");
|
|
||||||
assertEquals(32, digest.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenerateThumbprintSha1() throws NoSuchAlgorithmException {
|
public void testGenerateThumbprintSha1() throws NoSuchAlgorithmException {
|
|
@ -24,7 +24,6 @@
|
||||||
<artifact name="${org.keycloak:keycloak-common}"/>
|
<artifact name="${org.keycloak:keycloak-common}"/>
|
||||||
</resources>
|
</resources>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<module name="org.bouncycastle" />
|
|
||||||
<module name="javax.api"/>
|
<module name="javax.api"/>
|
||||||
<module name="javax.activation.api"/>
|
<module name="javax.activation.api"/>
|
||||||
<module name="org.jboss.logging"/>
|
<module name="org.jboss.logging"/>
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<module name="org.bouncycastle" />
|
|
||||||
<module name="javax.api"/>
|
<module name="javax.api"/>
|
||||||
<module name="javax.activation.api"/>
|
<module name="javax.activation.api"/>
|
||||||
<module name="org.jboss.logging"/>
|
<module name="org.jboss.logging"/>
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<module name="org.bouncycastle" />
|
|
||||||
<module name="javax.api"/>
|
<module name="javax.api"/>
|
||||||
<module name="javax.activation.api"/>
|
<module name="javax.activation.api"/>
|
||||||
<module name="org.jboss.logging"/>
|
<module name="org.jboss.logging"/>
|
||||||
|
|
|
@ -114,6 +114,16 @@
|
||||||
<artifactId>keycloak-common</artifactId>
|
<artifactId>keycloak-common</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- FIXME: Adding BC since removed from common, this will need to be removed -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Test -->
|
<!-- Test -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
|
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
|
||||||
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
||||||
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
Loading…
Reference in a new issue