generate/store realm certificate

This commit is contained in:
Bill Burke 2014-10-11 10:49:04 -04:00
parent d9ff44b870
commit 4d007c776a
12 changed files with 301 additions and 1 deletions

View file

@ -0,0 +1,63 @@
package org.keycloak.util;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
import javax.security.auth.x500.X500Principal;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class CertificateUtils {
public static X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert, String subject) throws Exception {
X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
X500Principal subjectName = new X500Principal("CN=" + subject);
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
certGen.setSerialNumber(serialNumber);
certGen.setIssuerDN(caCert.getSubjectX500Principal());
certGen.setNotBefore(new Date(System.currentTimeMillis() - 100000));
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR, 10);
certGen.setNotAfter(calendar.getTime());
certGen.setSubjectDN(subjectName);
certGen.setPublicKey(keyPair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
new AuthorityKeyIdentifierStructure(caCert));
certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
new SubjectKeyIdentifierStructure(keyPair.getPublic()));
X509Certificate cert = certGen.generate(caPrivateKey, "BC"); // note: private key of CA
return cert;
}
public static X509Certificate generateV1SelfSignedCertificate(KeyPair keyPair, String subject) throws Exception {
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
X500Principal subjectPrincipal = new X500Principal("CN=" + subject);
certGen.setSerialNumber(serialNumber);
certGen.setIssuerDN(subjectPrincipal);
certGen.setNotBefore(new Date(System.currentTimeMillis() - 100000));
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR, 10);
certGen.setNotAfter(calendar.getTime());
certGen.setSubjectDN(subjectPrincipal);
certGen.setPublicKey(keyPair.getPublic());
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
X509Certificate cert = certGen.generate(keyPair.getPrivate(), "BC");
return cert;
}
}

View file

@ -18,4 +18,5 @@ public class KeystoreUtil {
trustStream.close();
return trustStore;
}
}

View file

@ -4,6 +4,7 @@ import org.keycloak.enums.SslRequired;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -96,6 +97,11 @@ public interface RealmModel extends RoleContainerModel {
void setPublicKey(PublicKey publicKey);
X509Certificate getCertificate();
void setCertificate(X509Certificate certificate);
String getCertificatePem();
void setCertificatePem(String certificate);
PrivateKey getPrivateKey();
void setPrivateKey(PrivateKey privateKey);

View file

@ -40,6 +40,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private String publicKeyPem;
private String privateKeyPem;
private String certificatePem;
private String loginTheme;
private String accountTheme;
@ -381,4 +382,12 @@ public class RealmEntity extends AbstractIdentifiableEntity {
public void setUserFederationProviders(List<UserFederationProviderEntity> userFederationProviders) {
this.userFederationProviders = userFederationProviders;
}
public String getCertificatePem() {
return certificatePem;
}
public void setCertificatePem(String certificatePem) {
this.certificatePem = certificatePem;
}
}

View file

@ -12,6 +12,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.util.CertificateUtils;
import org.keycloak.util.PemUtils;
import java.io.IOException;
@ -22,6 +23,7 @@ import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Set;
import java.util.UUID;
@ -51,6 +53,19 @@ public final class KeycloakModelUtils {
}
}
public static X509Certificate getCertificate(String cert) {
if (cert != null) {
try {
return PemUtils.decodeCertificate(cert);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
public static PrivateKey getPrivateKey(String privateKeyPem) {
if (privateKeyPem != null) {
try {
@ -75,6 +90,19 @@ public final class KeycloakModelUtils {
return PemUtils.removeBeginEnd(s);
}
public static String getPemFromCertificate(X509Certificate certificate) {
StringWriter writer = new StringWriter();
PEMWriter pemWriter = new PEMWriter(writer);
try {
pemWriter.writeObject(certificate);
pemWriter.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
String s = writer.toString();
return PemUtils.removeBeginEnd(s);
}
public static void generateRealmKeys(RealmModel realm) {
KeyPair keyPair = null;
try {
@ -84,6 +112,23 @@ public final class KeycloakModelUtils {
}
realm.setPrivateKey(keyPair.getPrivate());
realm.setPublicKey(keyPair.getPublic());
X509Certificate certificate = null;
try {
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
realm.setCertificate(certificate);
}
public static void generateRealmCertificate(RealmModel realm) {
X509Certificate certificate = null;
try {
certificate = CertificateUtils.generateV1SelfSignedCertificate(new KeyPair(realm.getPublicKey(), realm.getPrivateKey()), realm.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
realm.setCertificate(certificate);
}
public static UserCredentialModel generateSecret(ClientModel app) {

View file

@ -15,6 +15,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@ -33,6 +34,7 @@ public class RealmAdapter implements RealmModel {
protected RealmCache cache;
protected volatile transient PublicKey publicKey;
protected volatile transient PrivateKey privateKey;
protected volatile transient X509Certificate certificate;
public RealmAdapter(CachedRealm cached, CacheRealmProvider cacheSession) {
this.cached = cached;
@ -331,6 +333,33 @@ public class RealmAdapter implements RealmModel {
setPublicKeyPem(publicKeyPem);
}
@Override
public X509Certificate getCertificate() {
if (certificate != null) return certificate;
certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
return certificate;
}
@Override
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
String certPem = KeycloakModelUtils.getPemFromCertificate(certificate);
setCertificatePem(certPem);
}
@Override
public String getCertificatePem() {
if (updated != null) return updated.getCertificatePem();
return cached.getCertificatePem();
}
@Override
public void setCertificatePem(String certificate) {
getDelegateForUpdate();
updated.setCertificatePem(certificate);
}
@Override
public PrivateKey getPrivateKey() {
if (privateKey != null) return privateKey;
@ -345,6 +374,8 @@ public class RealmAdapter implements RealmModel {
setPrivateKeyPem(privateKeyPem);
}
@Override
public List<RequiredCredentialModel> getRequiredCredentials() {

View file

@ -56,6 +56,7 @@ public class CachedRealm {
private String publicKeyPem;
private String privateKeyPem;
private String certificatePem;
private String loginTheme;
private String accountTheme;
@ -113,6 +114,7 @@ public class CachedRealm {
publicKeyPem = model.getPublicKeyPem();
privateKeyPem = model.getPrivateKeyPem();
certificatePem = model.getCertificatePem();
loginTheme = model.getLoginTheme();
accountTheme = model.getAccountTheme();
@ -328,4 +330,8 @@ public class CachedRealm {
public List<UserFederationProviderModel> getUserFederationProviders() {
return userFederationProviders;
}
public String getCertificatePem() {
return certificatePem;
}
}

View file

@ -23,6 +23,7 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -44,6 +45,7 @@ public class RealmAdapter implements RealmModel {
protected EntityManager em;
protected volatile transient PublicKey publicKey;
protected volatile transient PrivateKey privateKey;
protected volatile transient X509Certificate certificate;
protected KeycloakSession session;
private PasswordPolicy passwordPolicy;
@ -367,6 +369,32 @@ public class RealmAdapter implements RealmModel {
em.flush();
}
@Override
public X509Certificate getCertificate() {
if (certificate != null) return certificate;
certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
return certificate;
}
@Override
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
String certificatePem = KeycloakModelUtils.getPemFromCertificate(certificate);
setCertificatePem(certificatePem);
}
@Override
public String getCertificatePem() {
return realm.getCertificatePem();
}
@Override
public void setCertificatePem(String certificate) {
realm.setCertificatePem(certificate);
}
@Override
public String getPrivateKeyPem() {
return realm.getPrivateKeyPem();

View file

@ -80,6 +80,8 @@ public class RealmEntity {
protected String publicKeyPem;
@Column(name="PRIVATE_KEY", length = 2048)
protected String privateKeyPem;
@Column(name="CERTIFICATE", length = 2048)
protected String certificatePem;
@Column(name="LOGIN_THEME")
protected String loginTheme;
@ -432,5 +434,13 @@ public class RealmEntity {
public void setAttributes(Collection<RealmAttributeEntity> attributes) {
this.attributes = attributes;
}
public String getCertificatePem() {
return certificatePem;
}
public void setCertificatePem(String certificatePem) {
this.certificatePem = certificatePem;
}
}

View file

@ -25,6 +25,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -49,6 +50,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
protected volatile transient PublicKey publicKey;
protected volatile transient PrivateKey privateKey;
protected volatile transient X509Certificate certificate;
private volatile transient PasswordPolicy passwordPolicy;
private volatile transient KeycloakSession session;
@ -350,6 +352,33 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
updateRealm();
}
@Override
public X509Certificate getCertificate() {
if (certificate != null) return certificate;
certificate = KeycloakModelUtils.getCertificate(getCertificatePem());
return certificate;
}
@Override
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
String certificatePem = KeycloakModelUtils.getPemFromCertificate(certificate);
setCertificatePem(certificatePem);
}
@Override
public String getCertificatePem() {
return realm.getCertificatePem();
}
@Override
public void setCertificatePem(String certificate) {
realm.setCertificatePem(certificate);
}
@Override
public String getPrivateKeyPem() {
return realm.getPrivateKeyPem();

View file

@ -94,7 +94,6 @@ public class ApplicationResource {
public Response update(final ApplicationRepresentation rep) {
auth.requireManage();
ApplicationManager applicationManager = new ApplicationManager(new RealmManager(session));
try {
RepresentationToModel.updateApplication(rep, application);
return Response.noContent().build();

View file

@ -0,0 +1,73 @@
package org.keycloak.services.resources.admin;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
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.x509.X509V1CertificateGenerator;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import javax.security.auth.x500.X500Principal;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.StreamingOutput;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ClientCertificateResource {
protected RealmModel realm;
private RealmAuth auth;
protected ClientModel client;
protected KeycloakSession session;
public ClientCertificateResource(RealmModel realm, RealmAuth auth, ClientModel client, KeycloakSession session) {
this.realm = realm;
this.auth = auth;
this.client = client;
this.session = session;
}
@POST
public void generate() {
auth.requireManage();
}
@GET
@Path("/download/jks")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public StreamingOutput getJavaKeyStore(@QueryParam("realmCertificate") @DefaultValue("true") boolean realmCertificate) {
auth.requireView();
return null;
}
}