generate/store realm certificate
This commit is contained in:
parent
d9ff44b870
commit
4d007c776a
12 changed files with 301 additions and 1 deletions
63
core/src/main/java/org/keycloak/util/CertificateUtils.java
Executable file
63
core/src/main/java/org/keycloak/util/CertificateUtils.java
Executable 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;
|
||||
}
|
||||
}
|
|
@ -18,4 +18,5 @@ public class KeystoreUtil {
|
|||
trustStream.close();
|
||||
return trustStore;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in a new issue