keycloak-uncached

Merge pull request #765 from patriot1burke/master app cert

10/14/2014 8:17:26 PM

Changes

Details

diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
old mode 100644
new mode 100755
index 0b9f0f5..98eecbd
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml
@@ -32,6 +32,9 @@
         <addColumn tableName="CLIENT_SESSION">
             <column name="REALM_ID" type="VARCHAR(255)"/>
         </addColumn>
+        <addColumn tableName="REALM">
+            <column name="CERTIFICATE" type="VARCHAR(2048)"/>
+        </addColumn>
         <addPrimaryKey columnNames="CLIENT_ID, NAME" constraintName="CONSTRAINT_3C" tableName="CLIENT_ATTRIBUTES"/>
         <addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTRAINT_5E" tableName="CLIENT_SESSION_NOTE"/>
         <addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_ATTRIBUTES" constraintName="FK3C47C64BEACCA966" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index f192ecf..243479a 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -43,6 +43,7 @@ public class RealmRepresentation {
 
     protected String privateKey;
     protected String publicKey;
+    protected String certificate;
     protected RolesRepresentation roles;
     protected List<String> defaultRoles;
     protected Set<String> requiredCredentials;
@@ -220,6 +221,14 @@ public class RealmRepresentation {
         this.publicKey = publicKey;
     }
 
+    public String getCertificate() {
+        return certificate;
+    }
+
+    public void setCertificate(String certificate) {
+        this.certificate = certificate;
+    }
+
     public Boolean isPasswordCredentialGrantAllowed() {
         return passwordCredentialGrantAllowed;
     }
diff --git a/core/src/main/java/org/keycloak/util/CertificateUtils.java b/core/src/main/java/org/keycloak/util/CertificateUtils.java
new file mode 100755
index 0000000..ae18376
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/CertificateUtils.java
@@ -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;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/util/KeystoreUtil.java b/core/src/main/java/org/keycloak/util/KeystoreUtil.java
index d318a5f..76e0f1d 100755
--- a/core/src/main/java/org/keycloak/util/KeystoreUtil.java
+++ b/core/src/main/java/org/keycloak/util/KeystoreUtil.java
@@ -18,4 +18,5 @@ public class KeystoreUtil {
         trustStream.close();
         return trustStore;
     }
+
 }
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
index 658acdf..fe33ab1 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/app.js
@@ -440,6 +440,18 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'ApplicationCredentialsCtrl'
         })
+        .when('/realms/:realm/applications/:application/certificate', {
+            templateUrl : 'partials/application-keys.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                },
+                application : function(ApplicationLoader) {
+                    return ApplicationLoader();
+                }
+            },
+            controller : 'ApplicationCertificateCtrl'
+        })
         .when('/realms/:realm/applications/:application/roles', {
             templateUrl : 'partials/application-role-list.html',
             resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
index dbb8867..03dce99 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
@@ -43,6 +43,63 @@ module.controller('ApplicationCredentialsCtrl', function($scope, $location, real
     });
 });
 
+module.controller('ApplicationCertificateCtrl', function($scope, $location, $http, realm, application,
+                                                         ApplicationCertificate, ApplicationCertificateGenerate,
+                                                         ApplicationCertificateDownload, Notifications) {
+    $scope.realm = realm;
+    $scope.application = application;
+    var jks = {
+        keyAlias: application.name,
+        realmAlias: realm.realm
+    };
+
+    $scope.jks = jks;
+
+    var keyInfo = ApplicationCertificate.get({ realm : realm.realm, application : application.id },
+        function() {
+            $scope.keyInfo = keyInfo;
+        }
+    );
+
+    $scope.generate = function() {
+        var keyInfo = ApplicationCertificateGenerate.generate({ realm : realm.realm, application : application.id },
+            function() {
+                Notifications.success('Client keypair and cert has been changed.');
+                $scope.keyInfo = keyInfo;
+            },
+            function() {
+                Notifications.error("Client keypair and cert was not changed due to a problem.");
+            }
+        );
+    };
+
+    $scope.downloadJKS = function() {
+        $http({
+            url: authUrl + '/admin/realms/' + realm.realm + '/applications-by-id/' + application.id + '/certificates/download',
+            method: 'POST',
+            responseType: 'arraybuffer',
+            data: $scope.jks,
+            headers: {
+                'Content-Type': 'application/json',
+                'Accept': 'application/octet-stream'
+            }
+        }).success(function(data){
+            var blob = new Blob([data], {
+                type: 'application/octet-stream'
+            });
+            saveAs(blob, 'keystore' + '.jks');
+        }).error(function(){
+            Notifications.error("Error downloading.");
+        });
+    }
+
+    $scope.$watch(function() {
+        return $location.path();
+    }, function() {
+        $scope.path = $location.path().substring(1).split("/");
+    });
+});
+
 module.controller('ApplicationSessionsCtrl', function($scope, realm, sessionCount, application,
                                                       ApplicationUserSessions) {
     $scope.realm = realm;
@@ -339,12 +396,21 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, 
     $scope.save = function() {
         if ($scope.samlServerSignature == true) {
             $scope.application.attributes["samlServerSignature"] = "true";
+        } else {
+            $scope.application.attributes["samlServerSignature"] = "false";
+
         }
         if ($scope.samlClientSignature == true) {
             $scope.application.attributes["samlClientSignature"] = "true";
+        } else {
+            $scope.application.attributes["samlClientSignature"] = "false";
+
         }
         if ($scope.samlServerEncrypt == true) {
             $scope.application.attributes["samlServerEncrypt"] = "true";
+        } else {
+            $scope.application.attributes["samlServerEncrypt"] = "false";
+
         }
 
         $scope.application.protocol = $scope.protocol;
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
index fec1e99..b87310b 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js
@@ -693,7 +693,37 @@ module.factory('ApplicationPushRevocation', function($resource) {
     });
 });
 
+module.factory('ApplicationCertificate', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/applications-by-id/:application/certificates', {
+            realm : '@realm',
+            application : "@application"
+        });
+});
+
+module.factory('ApplicationCertificateGenerate', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/applications-by-id/:application/certificates/generate', {
+            realm : '@realm',
+            application : "@application"
+        },
+        {
+            generate : {
+                method : 'POST'
+            }
+        });
+});
 
+module.factory('ApplicationCertificateDownload', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/applications-by-id/:application/certificates/download', {
+        realm : '@realm',
+        application : "@application"
+    },
+        {
+            download : {
+                method : 'POST',
+                responseType: 'arraybuffer'
+            }
+        });
+});
 
 module.factory('Application', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/applications-by-id/:application', {
@@ -783,6 +813,21 @@ module.factory('OAuthClientCredentials', function($resource) {
 
 });
 
+module.factory('OAuthCertificate', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/oauth-clients-by-id/:oauth/certificates', {
+        realm : '@realm',
+        oauth : '@oauth'
+    });
+});
+
+module.factory('OAuthCertificateDownload', function($resource) {
+    return $resource(authUrl + '/admin/realms/:realm/oauth-clients-by-id/:oauth/certificates/download', {
+        realm : '@realm',
+        oauth : '@oauth'
+    });
+});
+
+
 module.factory('OAuthClientRealmScopeMapping', function($resource) {
     return $resource(authUrl + '/admin/realms/:realm/oauth-clients-by-id/:oauth/scope-mappings/realm', {
         realm : '@realm',
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-keys.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-keys.html
new file mode 100755
index 0000000..c722e52
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-keys.html
@@ -0,0 +1,78 @@
+<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
+<div id="content-area" class="col-sm-9" role="main">
+    <kc-navigation-application></kc-navigation-application>
+    <div id="content">
+        <ol class="breadcrumb" data-ng-hide="create">
+            <li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
+            <li><a href="#/realms/{{realm.realm}}/applications/{{application.id}}">{{application.name}}</a></li>
+            <li class="active">Keys</li>
+        </ol>
+        <h2><span>{{application.name}}</span> Key Pair and Certificate <span tooltip-placement="right" tooltip="Application's key pair and certificate.  Used for more confidential interaction between application and auth server." class="fa fa-info-circle"></span></h2>
+        <form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageRealm">
+            <fieldset class="form-group col-sm-10" data-ng-hide="!keyInfo.privateKey">
+                <legend collapsed><span class="text">Java Keystore Download</span>  <span tooltip-placement="right" tooltip="Client key pair, cert, and realm certificate will be stuffed into a Java keystore that you can use in your applications." class="fa fa-info-circle"></span></legend>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="keyAlias">Key Alias</label>
+                    <div class="col-sm-4">
+                        <input class="form-control" type="text" id="keyAlias" name="keyAlias" data-ng-model="jks.keyAlias" autofocus required>
+                    </div>
+                </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="keyPassword">Key Password</label>
+                    <div class="col-sm-4">
+                        <input class="form-control" type="text" id="keyPassword" name="keyPassword" data-ng-model="jks.keyPassword" autofocus required>
+                    </div>
+                </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="realmAlias">Realm Certificate Alias</label>
+                    <div class="col-sm-4">
+                        <input class="form-control" type="text" id="realmAlias" name="realmAlias" data-ng-model="jks.realmAlias" autofocus required>
+                    </div>
+                </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="storePassword">Store Password</label>
+                    <div class="col-sm-4">
+                        <input class="form-control" type="text" id="storePassword" name="storePassword" data-ng-model="jks.storePassword" autofocus required>
+                    </div>
+               </div>
+                <div class="form-group" data-ng-show="access.manageRealm">
+                    <div class="pull-right">
+                        <button class="btn btn-primary" type="submit" data-ng-click="downloadJKS()">Download</button>
+                    </div>
+                </div>
+            </fieldset>
+            <fieldset class="form-group col-sm-10">
+                <legend><span class="text">Keys and Certificate</span>  <span tooltip-placement="right" tooltip="Keys and cert in PEM format." class="fa fa-info-circle"></span></legend>
+                <div class="form-group" data-ng-hide="!keyInfo.privateKey">
+                    <label class="col-sm-2 control-label" for="publicKey">Private key</label>
+
+                    <div class="col-sm-10">
+                        <textarea type="text" id="Private" name="publicKey" class="form-control" rows="5"
+                                  kc-select-action="click" readonly>{{keyInfo.privateKey}}</textarea>
+                    </div>
+                </div>
+                <div class="form-group" data-ng-hide="!keyInfo.privateKey">
+                    <label class="col-sm-2 control-label" for="publicKey">Public key</label>
+
+                    <div class="col-sm-10">
+                        <textarea type="text" id="publicKey" name="publicKey" class="form-control" rows="5"
+                                  kc-select-action="click" readonly>{{keyInfo.publicKey}}</textarea>
+                    </div>
+                </div>
+                <div class="form-group" data-ng-hide="!keyInfo.privateKey">
+                    <label class="col-sm-2 control-label" for="publicKey">Certificate</label>
+
+                    <div class="col-sm-10">
+                        <textarea type="text" id="certificate" name="certificate" class="form-control" rows="5"
+                                  kc-select-action="click" readonly>{{keyInfo.certificate}}</textarea>
+                    </div>
+                </div>
+                <div class="form-group" data-ng-show="access.manageRealm">
+                    <div class="pull-right">
+                        <button class="btn btn-primary" type="submit" data-ng-click="generate()">Generate new keys</button>
+                    </div>
+                </div>
+            </fieldset>
+        </form>
+    </div>
+</div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-keys.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-keys.html
index f8a2805..7da6bc6 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-keys.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-keys.html
@@ -5,6 +5,9 @@
     <div id="content">
         <h2><span>{{realm.realm}}</span> Realm Public Key <span tooltip-placement="right" tooltip="Realm's public key.  This is used to verify any signed tokens or documents created by the realm." class="fa fa-info-circle"></span></h2>
         <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
+            <div class="pull-right form-actions" data-ng-show="access.manageRealm">
+                <button class="btn btn-primary btn-lg" type="submit" data-ng-click="generate()">Generate new keys</button>
+            </div>
             <fieldset class="border-top">
                 <div class="form-group">
                     <label class="col-sm-2 control-label" for="publicKey">Public key</label>
@@ -14,10 +17,15 @@
                                   kc-select-action="click" readonly>{{realm.publicKey}}</textarea>
                     </div>
                 </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="publicKey">Certificate</label>
+
+                    <div class="col-sm-10">
+                        <textarea type="text" id="certificate" name="certificate" class="form-control" rows="5"
+                                  kc-select-action="click" readonly>{{realm.certificate}}</textarea>
+                    </div>
+                </div>
             </fieldset>
-            <div class="pull-right form-actions" data-ng-show="access.manageRealm">
-                <button class="btn btn-primary btn-lg" type="submit" data-ng-click="generate()">Generate new keys</button>
-            </div>
         </form>
     </div>
 </div>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html
index 7df8e71..d89d91c 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html
@@ -1,10 +1,11 @@
 <ul class="nav nav-tabs nav-tabs-pf"  data-ng-show="!create">
     <li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}">Settings</a></li>
     <li ng-class="{active: path[4] == 'credentials'}" data-ng-show="!application.bearerOnly && !application.publicClient && application.protocol != 'saml'"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/credentials">Credentials</a></li>
+    <li ng-class="{active: path[4] == 'certificate'}" data-ng-show="application.protocol == 'saml' && application.attributes['samlClientSignature'] == 'true'"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/certificate">Application Keys</a></li>
     <li ng-class="{active: path[4] == 'roles'}"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/roles">Roles</a></li>
     <li ng-class="{active: path[4] == 'claims'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/claims">Claims</a></li>
     <li ng-class="{active: path[4] == 'scope-mappings'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/scope-mappings">Scope</a></li>
     <li ng-class="{active: path[4] == 'revocation'}"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/revocation">Revocation</a></li>
     <li ng-class="{active: path[4] == 'sessions'}" data-ng-show="!application.bearerOnly"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/sessions">Sessions</a></li>
-    <li ng-class="{active: path[4] == 'installation'}"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/installation">Installation</a></li>
+    <li ng-class="{active: path[4] == 'installation'}" data-ng-show="application.protocol != 'saml'"><a href="#/realms/{{realm.realm}}/applications/{{application.id}}/installation">Installation</a></li>
 </ul>
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index 61e1574..cba47d9 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -8,6 +8,13 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public interface ClientModel {
+
+    // COMMON ATTRIBUTES
+
+    String PRIVATE_KEY = "privateKey";
+    String PUBLIC_KEY = "publicKey";
+    String X509CERTIFICATE = "X509Certificate";
+
     /**
      * Internal database key
      *
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index aa06025..4371e26 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -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;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index d9fbb97..d1895ef 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -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);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index 3e8695c..574a76a 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -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,46 @@ 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 void generateClientKeyPairCertificate(ClientModel client) {
+        KeyPair keyPair = null;
+        try {
+            keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+        X509Certificate certificate = null;
+        try {
+            certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, client.getClientId());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        String privateKeyPem = KeycloakModelUtils.getPemFromKey(keyPair.getPrivate());
+        String publicKeyPem = KeycloakModelUtils.getPemFromKey(keyPair.getPublic());
+        String certPem = KeycloakModelUtils.getPemFromCertificate(certificate);
+
+        client.setAttribute(ClientModel.PRIVATE_KEY, privateKeyPem);
+        client.setAttribute(ClientModel.PUBLIC_KEY, publicKeyPem);
+        client.setAttribute(ClientModel.X509CERTIFICATE, certPem);
+
     }
 
     public static UserCredentialModel generateSecret(ClientModel app) {
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 0e78fdd..0baa697 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -86,6 +86,11 @@ public class ModelToRepresentation {
         rep.setSslRequired(realm.getSslRequired().name().toLowerCase());
         rep.setPublicKey(realm.getPublicKeyPem());
         rep.setPrivateKey(realm.getPrivateKeyPem());
+        String privateKeyPem = realm.getPrivateKeyPem();
+        if (realm.getCertificatePem() == null && privateKeyPem != null) {
+            KeycloakModelUtils.generateRealmCertificate(realm);
+        }
+        rep.setCertificate(realm.getCertificatePem());
         rep.setPasswordCredentialGrantAllowed(realm.isPasswordCredentialGrantAllowed());
         rep.setRegistrationAllowed(realm.isRegistrationAllowed());
         rep.setRememberMe(realm.isRememberMe());
@@ -114,8 +119,6 @@ public class ModelToRepresentation {
             rep.setPasswordPolicy(realm.getPasswordPolicy().toString());
         }
 
-        ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APP);
-
         List<String> defaultRoles = realm.getDefaultRoles();
         if (!defaultRoles.isEmpty()) {
             List<String> roleStrings = new ArrayList<String>();
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 9d30bce..2d45360 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -84,6 +84,11 @@ public class RepresentationToModel {
             newRealm.setPrivateKeyPem(rep.getPrivateKey());
             newRealm.setPublicKeyPem(rep.getPublicKey());
         }
+        if (rep.getCertificate() == null) {
+            KeycloakModelUtils.generateRealmCertificate(newRealm);
+        } else {
+            newRealm.setCertificatePem(rep.getCertificate());
+        }
         if (rep.getLoginTheme() != null) newRealm.setLoginTheme(rep.getLoginTheme());
         if (rep.getAccountTheme() != null) newRealm.setAccountTheme(rep.getAccountTheme());
         if (rep.getAdminTheme() != null) newRealm.setAdminTheme(rep.getAdminTheme());
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 04ca843..d96a9ea 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -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;
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index eeeda26..d471b4a 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -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;
@@ -332,6 +334,33 @@ public class RealmAdapter implements RealmModel {
     }
 
     @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;
         privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
@@ -345,6 +374,8 @@ public class RealmAdapter implements RealmModel {
         setPrivateKeyPem(privateKeyPem);
     }
 
+
+
     @Override
     public List<RequiredCredentialModel> getRequiredCredentials() {
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 8429aac..bcb5ad9 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -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;
+    }
 }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index c382ae8..5578b88 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -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;
 
@@ -368,6 +370,32 @@ public class RealmAdapter implements RealmModel {
     }
 
     @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();
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 9b9d436..ba51c13 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -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;
@@ -351,6 +353,33 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @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();
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
index e804161..15a2773 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
@@ -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();
@@ -118,6 +117,11 @@ public class ApplicationResource {
         return ModelToRepresentation.toRepresentation(application);
     }
 
+    @Path("certificates")
+    public ClientCertificateResource getCertficateResource() {
+        return new ClientCertificateResource(realm, auth, application, session);
+    }
+
 
     /**
      * Return keycloak.json file for this application to be used to configure the adapter of that application.
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsByIdResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsByIdResource.java
index f20b73d..fc93f01 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsByIdResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsByIdResource.java
@@ -16,4 +16,10 @@ public class ApplicationsByIdResource extends ApplicationsResource {
     protected ApplicationModel getApplicationByPathParam(String id) {
         return realm.getApplicationById(id);
     }
+
+    @Override
+    protected String getApplicationPath(ApplicationModel applicationModel) {
+        return applicationModel.getId();
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java
index c96dcd7..e47e002 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationsResource.java
@@ -88,12 +88,16 @@ public class ApplicationsResource {
 
         try {
             ApplicationModel applicationModel = RepresentationToModel.createApplication(realm, rep, true);
-            return Response.created(uriInfo.getAbsolutePathBuilder().path(applicationModel.getName()).build()).build();
+            return Response.created(uriInfo.getAbsolutePathBuilder().path(getApplicationPath(applicationModel)).build()).build();
         } catch (ModelDuplicateException e) {
             return Flows.errors().exists("Application " + rep.getName() + " already exists");
         }
     }
 
+    protected String getApplicationPath(ApplicationModel applicationModel) {
+        return applicationModel.getName();
+    }
+
     /**
      * Base path for managing a specific application.
      *
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientCertificateResource.java
new file mode 100755
index 0000000..84aac7e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientCertificateResource.java
@@ -0,0 +1,218 @@
+package org.keycloak.services.resources.admin;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.BadRequestException;
+import org.jboss.resteasy.spi.NotAcceptableException;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.util.PemUtils;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.StreamingOutput;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+/**
+ * @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;
+    }
+
+    public static class ClientKeyPairInfo {
+        protected String privateKey;
+        protected String publicKey;
+        protected String certificate;
+
+        public String getPrivateKey() {
+            return privateKey;
+        }
+
+        public void setPrivateKey(String privateKey) {
+            this.privateKey = privateKey;
+        }
+
+        public String getPublicKey() {
+            return publicKey;
+        }
+
+        public void setPublicKey(String publicKey) {
+            this.publicKey = publicKey;
+        }
+
+        public String getCertificate() {
+            return certificate;
+        }
+
+        public void setCertificate(String certificate) {
+            this.certificate = certificate;
+        }
+    }
+
+    @GET
+    @NoCache
+    @Produces(MediaType.APPLICATION_JSON)
+    public ClientKeyPairInfo getKeyInfo() {
+        ClientKeyPairInfo info = new ClientKeyPairInfo();
+        info.setCertificate(client.getAttribute(ClientModel.X509CERTIFICATE));
+        info.setPrivateKey(client.getAttribute(ClientModel.PRIVATE_KEY));
+        info.setPublicKey(client.getAttribute(ClientModel.PUBLIC_KEY));
+        return info;
+    }
+
+
+    @POST
+    @NoCache
+    @Path("generate")
+    @Produces(MediaType.APPLICATION_JSON)
+    public ClientKeyPairInfo generate() {
+        auth.requireManage();
+
+        KeycloakModelUtils.generateClientKeyPairCertificate(client);
+        ClientKeyPairInfo info = new ClientKeyPairInfo();
+        info.setCertificate(client.getAttribute(ClientModel.X509CERTIFICATE));
+        info.setPrivateKey(client.getAttribute(ClientModel.PRIVATE_KEY));
+        info.setPublicKey(client.getAttribute(ClientModel.PUBLIC_KEY));
+        return info;
+    }
+
+    public static class KeyStoreConfig {
+        protected Boolean realmCertificate;
+        protected String storePassword;
+        protected String keyPassword;
+        protected String keyAlias;
+        protected String realmAlias;
+        protected String format;
+
+        public Boolean isRealmCertificate() {
+            return realmCertificate;
+        }
+
+        public void setRealmCertificate(Boolean realmCertificate) {
+            this.realmCertificate = realmCertificate;
+        }
+
+        public String getStorePassword() {
+            return storePassword;
+        }
+
+        public void setStorePassword(String storePassword) {
+            this.storePassword = storePassword;
+        }
+
+        public String getKeyPassword() {
+            return keyPassword;
+        }
+
+        public void setKeyPassword(String keyPassword) {
+            this.keyPassword = keyPassword;
+        }
+
+        public String getKeyAlias() {
+            return keyAlias;
+        }
+
+        public void setKeyAlias(String keyAlias) {
+            this.keyAlias = keyAlias;
+        }
+
+        public String getRealmAlias() {
+            return realmAlias;
+        }
+
+        public void setRealmAlias(String realmAlias) {
+            this.realmAlias = realmAlias;
+        }
+
+        public String getFormat() {
+            return format;
+        }
+
+        public void setFormat(String format) {
+            this.format = format;
+        }
+    }
+
+    @POST
+    @NoCache
+    @Path("/download")
+    @Produces(MediaType.APPLICATION_OCTET_STREAM)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public byte[] getJavaKeyStore(final KeyStoreConfig config) {
+        auth.requireView();
+        if (config.getFormat() != null && !config.getFormat().equals("jks")) {
+            throw new NotAcceptableException("Only support jks format.");
+        }
+        if (client.getAttribute(ClientModel.PRIVATE_KEY) == null) {
+            throw new NotFoundException("keypair not generated for client");
+        }
+        if (config.getKeyPassword() == null) {
+            throw new BadRequestException("Need to specify a key password for jks download");
+        }
+        if (config.getStorePassword() == null) {
+            throw new BadRequestException("Need to specify a store password for jks download");
+        }
+        final KeyStore keyStore;
+        try {
+            keyStore = KeyStore.getInstance("JKS");
+            keyStore.load(null, null);
+            String keyAlias = config.getKeyAlias();
+            if (keyAlias == null) keyAlias = client.getClientId();
+            PrivateKey privateKey = PemUtils.decodePrivateKey(client.getAttribute(ClientModel.PRIVATE_KEY));
+            X509Certificate clientCert = PemUtils.decodeCertificate(client.getAttribute(ClientModel.X509CERTIFICATE));
+
+
+            Certificate[] chain =  {clientCert};
+
+            keyStore.setKeyEntry(keyAlias, privateKey, config.getKeyPassword().trim().toCharArray(), chain);
+
+            if (config.isRealmCertificate() == null || config.isRealmCertificate().booleanValue()) {
+                X509Certificate certificate = realm.getCertificate();
+                if (certificate == null) {
+                    KeycloakModelUtils.generateRealmCertificate(realm);
+                    certificate = realm.getCertificate();
+                }
+                String certificateAlias = config.getRealmAlias();
+                if (certificateAlias == null) certificateAlias = realm.getName();
+                keyStore.setCertificateEntry(certificateAlias, certificate);
+
+            }
+            ByteArrayOutputStream stream = new ByteArrayOutputStream();
+            keyStore.store(stream, config.getStorePassword().trim().toCharArray());
+            stream.flush();
+            stream.close();
+            byte[] rtn = stream.toByteArray();
+            return rtn;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java
index aa857a0..4a03d08 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientResource.java
@@ -72,6 +72,13 @@ public class OAuthClientResource  {
         return new ClaimResource(oauthClient, auth);
     }
 
+    @Path("certificates")
+    public ClientCertificateResource getCertficateResource() {
+        return new ClientCertificateResource(realm, auth, oauthClient, session);
+    }
+
+
+
     /**
      * Update the oauth client
      *
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsByIdResource.java b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsByIdResource.java
index e479827..b7cfb1a 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsByIdResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsByIdResource.java
@@ -35,8 +35,14 @@ public class OAuthClientsByIdResource extends OAuthClientsResource {
         super(realm, auth, session);
     }
 
+    @Override
     protected OAuthClientModel getOAuthClientModel(String id) {
         return realm.getOAuthClientById(id);
     }
 
+    @Override
+    protected String getClientPath(OAuthClientModel oauth) {
+        return oauth.getId();
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsResource.java
index 0f15347..3d57d42 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/OAuthClientsResource.java
@@ -90,12 +90,16 @@ public class OAuthClientsResource {
 
         try {
             OAuthClientModel oauth = RepresentationToModel.createOAuthClient(rep, realm);
-            return Response.created(uriInfo.getAbsolutePathBuilder().path(oauth.getClientId()).build()).build();
+            return Response.created(uriInfo.getAbsolutePathBuilder().path(getClientPath(oauth)).build()).build();
         } catch (ModelDuplicateException e) {
             return Flows.errors().exists("Client " + rep.getName() + " already exists");
         }
     }
 
+    protected String getClientPath(OAuthClientModel oauth) {
+        return oauth.getClientId();
+    }
+
     /**
      * Base path to manage one specific oauth client
      *
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
index 5fe7262..2346bd8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
@@ -5,6 +5,7 @@ import org.junit.Test;
 import org.keycloak.enums.SslRequired;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 
@@ -24,8 +25,7 @@ public class ModelTest extends AbstractModelTest {
         realm.setPasswordPolicy(new PasswordPolicy("length"));
         realm.setAccessCodeLifespan(1001);
         realm.setAccessCodeLifespanUserAction(1002);
-        realm.setPublicKeyPem("0234234");
-        realm.setPrivateKeyPem("1234234");
+        KeycloakModelUtils.generateRealmKeys(realm);
         realm.addDefaultRole("default-role");
 
         HashMap<String, String> smtp = new HashMap<String,String>();