keycloak-memoizeit
Changes
forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js 66(+66 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/application-keys.html 78(+78 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-keys.html 14(+11 -3)
forms/common-themes/src/main/resources/theme/admin/base/resources/templates/kc-navigation-application.html 3(+2 -1)
Details
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/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/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index f0b677e..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
@@ -131,6 +131,29 @@ public final class KeycloakModelUtils {
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) {
UserCredentialModel secret = UserCredentialModel.generateSecret();
app.setSecret(secret.getValue());
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/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java
index 420ec6d..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
@@ -117,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
index fe68c5b..84aac7e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientCertificateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientCertificateResource.java
@@ -1,39 +1,32 @@
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.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.security.auth.x500.X500Principal;
-import javax.ws.rs.DefaultValue;
+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.QueryParam;
+import javax.ws.rs.WebApplicationException;
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.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.Security;
-import java.security.SignatureException;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
-import java.util.Calendar;
-import java.util.Date;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -52,21 +45,173 @@ public class ClientCertificateResource {
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
- public void generate() {
+ @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;
+ }
}
- @GET
- @Path("/download/jks")
+ @POST
+ @NoCache
+ @Path("/download")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
- public StreamingOutput getJavaKeyStore(@QueryParam("realmCertificate") @DefaultValue("true") boolean realmCertificate) {
+ @Consumes(MediaType.APPLICATION_JSON)
+ public byte[] getJavaKeyStore(final KeyStoreConfig config) {
auth.requireView();
- return null;
+ 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>();