KEYCLOAK-8996: Provide a way to set a responder certificate in OCSP/X509 Authenticator

This commit is contained in:
rmartinc 2018-12-03 12:24:25 +01:00 committed by Marek Posolda
parent e843d84f6e
commit 231db059b2
12 changed files with 255 additions and 15 deletions

View file

@ -378,7 +378,7 @@ public final class OCSPUtils {
}
if (signingCert != null) {
if (signingCert.equals(issuerCertificate)) {
logger.log(Level.INFO, "OCSP response is signed by the target\'s Issuing CA");
logger.log(Level.INFO, "OCSP response is signed by the target''s Issuing CA");
} else if (responderCertificate != null && signingCert.equals(responderCertificate)) {
// https://www.ietf.org/rfc/rfc2560.txt
// 2.6 OCSP Signature Authority Delegation
@ -390,7 +390,7 @@ public final class OCSPUtils {
// extension and is issued by the CA that issued the certificate in
// question."
if (!signingCert.getIssuerX500Principal().equals(issuerCertificate.getSubjectX500Principal())) {
logger.log(Level.INFO, "Signer certificate's Issuer: {0}\nIssuer certificate's Subject: {1}",
logger.log(Level.INFO, "Signer certificate''s Issuer: {0}\nIssuer certificate''s Subject: {1}",
new Object[] {signingCert.getIssuerX500Principal().getName(), issuerCertificate.getSubjectX500Principal().getName()});
throw new CertPathValidatorException("Responder\'s certificate is not authorized to sign OCSP responses");
}
@ -401,7 +401,7 @@ public final class OCSPUtils {
throw new CertPathValidatorException("Responder\'s certificate not valid for signing OCSP responses");
}
} catch (CertificateParsingException e) {
logger.log(Level.FINE, "Failed to get certificate's extended key usage extension\n{0}", e.getMessage());
logger.log(Level.FINE, "Failed to get certificate''s extended key usage extension\n{0}", e.getMessage());
}
if (date == null) {
signingCert.checkValidity();

View file

@ -52,6 +52,11 @@ public class ProviderConfigProperty {
public static final String CLIENT_LIST_TYPE="ClientList";
public static final String PASSWORD="Password";
/**
* textarea field
*/
public static final String TEXT_TYPE="Text";
protected String name;
protected String label;
protected String helpText;

View file

@ -54,6 +54,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
public static final String ENABLE_CRLDP = "x509-cert-auth.crldp-checking-enabled";
public static final String CRL_RELATIVE_PATH = "x509-cert-auth.crl-relative-path";
public static final String OCSPRESPONDER_URI = "x509-cert-auth.ocsp-responder-uri";
public static final String OCSPRESPONDER_CERTIFICATE = "x509-cert-auth.ocsp-responder-certificate";
public static final String MAPPING_SOURCE_SELECTION = "x509-cert-auth.mapping-source-selection";
public static final String MAPPING_SOURCE_CERT_SUBJECTDN = "Match SubjectDN using regular expression";
public static final String MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL = "Subject's e-mail";
@ -93,6 +94,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
.cRLDPEnabled(config.getCRLDistributionPointEnabled())
.cRLrelativePath(config.getCRLRelativePath())
.oCSPEnabled(config.getOCSPEnabled())
.oCSPResponseCertificate(config.getOCSPResponderCertificate())
.oCSPResponderURI(config.getOCSPResponder());
}
}

View file

@ -48,6 +48,7 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_CN;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_CERT_SUBJECTDN_EMAIL;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.MAPPING_SOURCE_SELECTION;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.OCSPRESPONDER_CERTIFICATE;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.OCSPRESPONDER_URI;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.REGULAR_EXPRESSION;
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.USERNAME_EMAIL_MAPPER;
@ -55,6 +56,7 @@ import static org.keycloak.authentication.authenticators.x509.AbstractX509Client
import static org.keycloak.authentication.authenticators.x509.AbstractX509ClientCertificateAuthenticator.USER_MAPPER_SELECTION;
import static org.keycloak.provider.ProviderConfigProperty.BOOLEAN_TYPE;
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
import static org.keycloak.provider.ProviderConfigProperty.TEXT_TYPE;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
@ -155,6 +157,12 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
ocspResponderUri.setLabel("OCSP Responder Uri");
ocspResponderUri.setHelpText("Clients use OCSP Responder Uri to check certificate revocation status.");
ProviderConfigProperty ocspResponderCert = new ProviderConfigProperty();
ocspResponderCert.setType(TEXT_TYPE);
ocspResponderCert.setName(OCSPRESPONDER_CERTIFICATE);
ocspResponderCert.setLabel("OCSP Responder Certificate");
ocspResponderCert.setHelpText("Optional certificate used by the responder to sign the responses. The certificate should be in PEM format without BEGIN and END tags. It is only used if the OCSP Responder URI is set. By default, the certificate of the OCSP responder is that of the issuer of the certificate being validated or one with the OCSPSigning extension and also issued by the same CA. This option identifies the certificate of the OCSP responder when the defaults do not apply.");
ProviderConfigProperty keyUsage = new ProviderConfigProperty();
keyUsage.setType(STRING_TYPE);
keyUsage.setName(CERTIFICATE_KEY_USAGE);
@ -182,6 +190,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen
cRLRelativePath,
oCspCheckingEnabled,
ocspResponderUri,
ocspResponderCert,
keyUsage,
extendedKeyUsage,
identityConfirmationPageDisallowed);

View file

@ -51,6 +51,8 @@ import java.util.List;
import java.util.Set;
import java.util.LinkedList;
import java.util.ArrayList;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
/**
* @author <a href="mailto:pnalyvayko@agi.com">Peter Nalyvayko</a>
@ -149,8 +151,11 @@ public class CertificateValidator {
public static class BouncyCastleOCSPChecker extends OCSPChecker {
private final String responderUri;
BouncyCastleOCSPChecker(String responderUri) {
private final X509Certificate responderCert;
BouncyCastleOCSPChecker(String responderUri, X509Certificate responderCert) {
this.responderUri = responderUri;
this.responderCert = responderCert;
}
@Override
@ -177,12 +182,13 @@ public class CertificateValidator {
String message = String.format("Unable to check certificate revocation status using OCSP.\n%s", e.getMessage());
throw new CertPathValidatorException(message, e);
}
logger.tracef("Responder URI \"%s\" will be used to verify revocation status of the certificate using OCSP", uri.toString());
logger.tracef("Responder URI \"%s\" will be used to verify revocation status of the certificate using OCSP with responderCert=%s",
uri.toString(), responderCert);
// Obtains the revocation status of a certificate using OCSP.
// OCSP responder's certificate is assumed to be the issuer's certificate
// certificate.
// responderUri overrides the contents (if any) of the certificate's AIA extension
ocspRevocationStatus = OCSPUtils.check(cert, issuerCertificate, uri, null, null);
ocspRevocationStatus = OCSPUtils.check(cert, issuerCertificate, uri, responderCert, null);
}
return ocspRevocationStatus;
}
@ -529,6 +535,7 @@ public class CertificateValidator {
CRLLoaderImpl _crlLoader;
boolean _ocspEnabled;
String _responderUri;
X509Certificate _responderCert;
public CertificateValidatorBuilder() {
_extendedKeyUsage = new LinkedList<>();
@ -675,6 +682,21 @@ public class CertificateValidator {
}
public class GotOCSP {
public GotOCSP oCSPResponseCertificate(String responderCert) {
if (responderCert != null && !responderCert.isEmpty()) {
try {
_responderCert = XMLSignatureUtil.getX509CertificateFromKeyInfoString(responderCert);
_responderCert.checkValidity();
} catch(CertificateException e) {
logger.warnf("Ignoring invalid certificate: %s", _responderCert);
_responderCert = null;
} catch (ProcessingException e) {
throw new RuntimeException(e);
}
}
return new GotOCSP();
}
public CertificateValidatorBuilder oCSPResponderURI(String responderURI) {
_responderUri = responderURI;
return _parent;
@ -699,7 +721,8 @@ public class CertificateValidator {
_crlLoader = new CRLFileLoader("");
}
return new CertificateValidator(certs, _keyUsageBits, _extendedKeyUsage,
_crlCheckingEnabled, _crldpEnabled, _crlLoader, _ocspEnabled, new BouncyCastleOCSPChecker(_responderUri));
_crlCheckingEnabled, _crldpEnabled, _crlLoader, _ocspEnabled,
new BouncyCastleOCSPChecker(_responderUri, _responderCert));
}
}

View file

@ -142,6 +142,19 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel {
return this;
}
public String getOCSPResponderCertificate() {
return getConfig().getOrDefault(OCSPRESPONDER_CERTIFICATE, null);
}
public X509AuthenticatorConfigModel setOCSPResponderCertificate(String responderCert) {
if (responderCert != null) {
getConfig().put(OCSPRESPONDER_CERTIFICATE, responderCert);
} else {
getConfig().remove(OCSPRESPONDER_CERTIFICATE);
}
return this;
}
public MappingSourceType getMappingSourceType() {
return MappingSourceType.parse(getConfig().getOrDefault(MAPPING_SOURCE_SELECTION, MAPPING_SOURCE_CERT_SUBJECTDN));
}

View file

@ -68,9 +68,13 @@ import io.undertow.util.Headers;
final class OcspHandler implements HttpHandler {
private static final String OCSP_RESPONDER_CERT_PATH = "/client-auth-test/intermediate-ca.crt";
// certificates created by the same ca than the client certs
public static final String OCSP_RESPONDER_CERT_PATH = "/client-auth-test/intermediate-ca.crt";
public static final String OCSP_RESPONDER_KEYPAIR_PATH = "/client-auth-test/intermediate-ca.key";
private static final String OCSP_RESPONDER_KEYPAIR_PATH = "/client-auth-test/intermediate-ca.key";
// certificates specific for responderCert
public static final String OCSP_RESPONDER_CERT_PATH_SPECIFIC = "/client-auth-test/intermediate-ca-2.crt";
public static final String OCSP_RESPONDER_KEYPAIR_PATH_SPECIFIC = "/client-auth-test/intermediate-ca-2.key";
// add any certificates that the OCSP responder needs to know about in the tests here
private static final Map<BigInteger, CertificateStatus> REVOKED_CERTIFICATES_STATUS = ImmutableMap
@ -82,9 +86,10 @@ final class OcspHandler implements HttpHandler {
private final AsymmetricKeyParameter privateKey;
OcspHandler() throws OperatorCreationException, GeneralSecurityException, IOException {
public OcspHandler(String responderCertPath, String responderKeyPath)
throws OperatorCreationException, GeneralSecurityException, IOException {
final Certificate certificate = CertificateFactory.getInstance("X509")
.generateCertificate(X509OCSPResponderTest.class.getResourceAsStream(OCSP_RESPONDER_CERT_PATH));
.generateCertificate(X509OCSPResponderTest.class.getResourceAsStream(responderCertPath));
chain = new X509CertificateHolder[] {new X509CertificateHolder(certificate.getEncoded())};
@ -92,7 +97,7 @@ final class OcspHandler implements HttpHandler {
subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(publicKey);
final InputStream keyPairStream = X509OCSPResponderTest.class.getResourceAsStream(OCSP_RESPONDER_KEYPAIR_PATH);
final InputStream keyPairStream = X509OCSPResponderTest.class.getResourceAsStream(responderKeyPath);
try (final PEMParser keyPairReader = new PEMParser(new InputStreamReader(keyPairStream))) {
final PEMKeyPair keyPairPem = (PEMKeyPair) keyPairReader.readObject();

View file

@ -0,0 +1,130 @@
/*
* Copyright 2018 Analytical Graphics, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.testsuite.x509;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.testsuite.util.OAuthClient;
import javax.ws.rs.core.Response;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USERNAME_EMAIL;
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL;
import io.undertow.Undertow;
import io.undertow.server.handlers.BlockingHandler;
/**
* Verifies Certificate revocation using OCSP responder but specifying specific
* responder cert.
* The tests rely on an OCSP responder service listening
* for OCSP requests on http://localhost:8888
* @author rmartinc
* @version $Revision: 1 $
* @since 11/2/2016
*/
public class X509OCSPResponderSpecificCertTest extends AbstractX509AuthenticationTest {
private static final String OCSP_RESPONDER_HOST = "localhost";
private static final int OCSP_RESPONDER_PORT = 8888;
private Undertow ocspResponder;
@Test
public void loginFailedInvalidResponderOnOCSPResponderRevocationCheck() throws Exception {
X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel()
.setOCSPEnabled(true)
.setMappingSourceType(SUBJECTDN_EMAIL)
.setUserIdentityMapperType(USERNAME_EMAIL);
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig());
String cfgId = createConfig(directGrantExecution.getId(), cfg);
Assert.assertNotNull(cfgId);
oauth.clientId("resource-owner");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);
assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatusCode());
assertEquals("invalid_request", response.getError());
Assert.assertThat(response.getErrorDescription(), containsString("Responder's certificate is not authorized to sign OCSP responses"));
}
@Test
public void loginFailedOnOCSPResponderRevocationCheck() throws Exception {
X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel()
.setOCSPEnabled(true)
.setOCSPResponder("http://" + OCSP_RESPONDER_HOST + ":" + OCSP_RESPONDER_PORT + "/oscp")
.setOCSPResponderCertificate(
"MIIDSDCCAjACCQDutBlh01xKxDANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJV\n"
+ "UzELMAkGA1UECBMCTUExETAPBgNVBAcTCFdlc3R3b3JkMRAwDgYDVQQKEwdSZWQg\n"
+ "SGF0MREwDwYDVQQLEwhLZXljbG9hazESMBAGA1UEAxMJbG9jYWxob3N0MCAXDTE4\n"
+ "MTEyOTE1MzYxNFoYDzMwMTgwNDAxMTUzNjE0WjBkMQswCQYDVQQGEwJVUzELMAkG\n"
+ "A1UECAwCTUExDzANBgNVBAcMBkJvc3RvbjEQMA4GA1UECgwHUmVkIEhhdDERMA8G\n"
+ "A1UECwwIS2V5Y2xvYWsxEjAQBgNVBAMMCUtleWNsb2FrMjCCASIwDQYJKoZIhvcN\n"
+ "AQEBBQADggEPADCCAQoCggEBALxnRdlqot+p3fAZ8BPt/ZeytVy2ZFXd7zG8jVCu\n"
+ "j/u/IqrN9fE7esdaiZYEwMvaTPKG7pAxb5NlRgWsE8UfNNN9a0GCp3wPJsmj4Lfx\n"
+ "K7LmH9QLtq+K7Ap5UGXXRNU0BZqcDMznMaIz04N4DimX5uGAwFQCy/NM5yUP2iOa\n"
+ "dPlB6ECpLavHDT09rMSVl5RgLQLx/TeX7pT4IW7kbpdTVI02rzE90O72riK61c6P\n"
+ "Q9Zb6bGSaNZwfNGIVQ8u6AVimLJx66p3BNP+3kEfg7xvXkw6UcaXM5LlMVcxi7cr\n"
+ "Se1k2UR95gPwJC1AVVPUPqHVb3Ix/wLl7GGhHcuBLPODF0cCAwEAATANBgkqhkiG\n"
+ "9w0BAQsFAAOCAQEADX2znEyqJZHGWLazrSHFn9Rn1mREqH+OCRq38ymz3tCNyVhs\n"
+ "OTSO1t6Fo1PP4RvlxB6gd4BYH7/cSCsO00s/OjPf8ptqz59TQAmCIM0+dwQuyxKO\n"
+ "gq55nWpy5gbqf/zqQiWsMXW5nkMVEMUvf1qbKx6xYP61B83vZh+t65LZh7meG6S5\n"
+ "B5qT6nDGKN7C8AHuxHHJjpgvYL8kNb47fASTYdHzW57Yi92NrkmAq4PCEt6FQTkX\n"
+ "WybC/Il6hS0jPdR2ExNV9ykKJrNGGhiwg3C8sf97/Kf+qQgRK8wQIdT88g81aJaG\n"
+ "qpwfJXd9AZO7DdDJdZ75lR9N1YSnhSq3Ur6IJg==")
.setMappingSourceType(SUBJECTDN_EMAIL)
.setUserIdentityMapperType(USERNAME_EMAIL);
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig());
String cfgId = createConfig(directGrantExecution.getId(), cfg);
Assert.assertNotNull(cfgId);
oauth.clientId("resource-owner");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);
assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatusCode());
assertEquals("invalid_request", response.getError());
Assert.assertThat(response.getErrorDescription(), containsString("Certificate's been revoked."));
}
@Before
public void startOCSPResponder() throws Exception {
ocspResponder = Undertow.builder().addHttpListener(OCSP_RESPONDER_PORT, OCSP_RESPONDER_HOST)
.setHandler(new BlockingHandler(new OcspHandler(
OcspHandler.OCSP_RESPONDER_CERT_PATH_SPECIFIC,
OcspHandler.OCSP_RESPONDER_KEYPAIR_PATH_SPECIFIC))
).build();
ocspResponder.start();
}
@After
public void stopOCSPResponder() {
ocspResponder.stop();
}
}

View file

@ -76,7 +76,9 @@ public class X509OCSPResponderTest extends AbstractX509AuthenticationTest {
@Before
public void startOCSPResponder() throws Exception {
ocspResponder = Undertow.builder().addHttpListener(OCSP_RESPONDER_PORT, OCSP_RESPONDER_HOST)
.setHandler(new BlockingHandler(new OcspHandler())).build();
.setHandler(new BlockingHandler(
new OcspHandler(OcspHandler.OCSP_RESPONDER_CERT_PATH, OcspHandler.OCSP_RESPONDER_KEYPAIR_PATH))
).build();
ocspResponder.start();
}

View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDSDCCAjACCQDutBlh01xKxDANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJV
UzELMAkGA1UECBMCTUExETAPBgNVBAcTCFdlc3R3b3JkMRAwDgYDVQQKEwdSZWQg
SGF0MREwDwYDVQQLEwhLZXljbG9hazESMBAGA1UEAxMJbG9jYWxob3N0MCAXDTE4
MTEyOTE1MzYxNFoYDzMwMTgwNDAxMTUzNjE0WjBkMQswCQYDVQQGEwJVUzELMAkG
A1UECAwCTUExDzANBgNVBAcMBkJvc3RvbjEQMA4GA1UECgwHUmVkIEhhdDERMA8G
A1UECwwIS2V5Y2xvYWsxEjAQBgNVBAMMCUtleWNsb2FrMjCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALxnRdlqot+p3fAZ8BPt/ZeytVy2ZFXd7zG8jVCu
j/u/IqrN9fE7esdaiZYEwMvaTPKG7pAxb5NlRgWsE8UfNNN9a0GCp3wPJsmj4Lfx
K7LmH9QLtq+K7Ap5UGXXRNU0BZqcDMznMaIz04N4DimX5uGAwFQCy/NM5yUP2iOa
dPlB6ECpLavHDT09rMSVl5RgLQLx/TeX7pT4IW7kbpdTVI02rzE90O72riK61c6P
Q9Zb6bGSaNZwfNGIVQ8u6AVimLJx66p3BNP+3kEfg7xvXkw6UcaXM5LlMVcxi7cr
Se1k2UR95gPwJC1AVVPUPqHVb3Ix/wLl7GGhHcuBLPODF0cCAwEAATANBgkqhkiG
9w0BAQsFAAOCAQEADX2znEyqJZHGWLazrSHFn9Rn1mREqH+OCRq38ymz3tCNyVhs
OTSO1t6Fo1PP4RvlxB6gd4BYH7/cSCsO00s/OjPf8ptqz59TQAmCIM0+dwQuyxKO
gq55nWpy5gbqf/zqQiWsMXW5nkMVEMUvf1qbKx6xYP61B83vZh+t65LZh7meG6S5
B5qT6nDGKN7C8AHuxHHJjpgvYL8kNb47fASTYdHzW57Yi92NrkmAq4PCEt6FQTkX
WybC/Il6hS0jPdR2ExNV9ykKJrNGGhiwg3C8sf97/Kf+qQgRK8wQIdT88g81aJaG
qpwfJXd9AZO7DdDJdZ75lR9N1YSnhSq3Ur6IJg==
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAvGdF2Wqi36nd8BnwE+39l7K1XLZkVd3vMbyNUK6P+78iqs31
8Tt6x1qJlgTAy9pM8obukDFvk2VGBawTxR80031rQYKnfA8myaPgt/ErsuYf1Au2
r4rsCnlQZddE1TQFmpwMzOcxojPTg3gOKZfm4YDAVALL80znJQ/aI5p0+UHoQKkt
q8cNPT2sxJWXlGAtAvH9N5fulPghbuRul1NUjTavMT3Q7vauIrrVzo9D1lvpsZJo
1nB80YhVDy7oBWKYsnHrqncE0/7eQR+DvG9eTDpRxpczkuUxVzGLtytJ7WTZRH3m
A/AkLUBVU9Q+odVvcjH/AuXsYaEdy4Es84MXRwIDAQABAoIBAQCTcxPUm8OK3qvm
zLX7MwiEoAWW4NxX45DfUR0cJvJi6W0dVoIEYTOvL+l8Mo4dPOV57iZpTXdwWRNb
Pxhd7xVt67t0kue3jTgjGZG3BBXaNoZ0cxJwAn7Hl7hXbAnf/o3gOf8+ojZTJtKE
EwKqhMrac+SSG3o5GVfRxr1HngusQxJvT5amKoG01jILMPJ5ihXKnxKLSxUQL18Z
UsCYiWBtcFpp06frjqke44E06YTDoUMMzuQJ1sLmJvbVkNgJNSZVAu719TZ5MNcu
4ioIxzpHVnjfSsDwzyO8/NV72WTwXqHppluGVtPU3iOECk3KUflQVe7qxDUmw0e7
vrIe9o5JAoGBAPB+Dl/ObNtMgAVE4rEGl4pUIumXCblc/odGdWxLQwCmtPYborTU
IZWLDmZ426P9iIhd95a/2T4XkioMcwbz/I8HKJn8dQ6yRSpHrUhruQFxK0FXIWMG
6e3BM9OE+0hyCiJysZ6SA8DEiyw33xHGyMoeFIRjisxfng+JIHC4J7gtAoGBAMiN
Wq7uTSnz74a4W7E4oJXDJLf330bqdeoxNXJZ/ToTxbpUzudsIUhCj8zhpPEugh8H
cMBOYzvY0oqZH4tFK68Moc6Swp3r54y3Lqg/ON2QaarIQReZA0/dCr0wUkp5Kla1
fKecUPf8iPopaOKqFmzeDHuh10ZYSVQM1LW3FCHDAoGBALnznkWyJTWD91EbrHTY
9f+9gqk/YgNzxlLuxgiQQSSVwkKxp6NOixFITkApnxs0BIf8sF5dxSpfq2Lb8W0k
5tno3RoDvLc6XISOm4vtz0UwLhP9vREG3YY650nb/zhLBU8RhAlqOYCp+DqekNr7
63FsxMyA82eHX33/t5ewrPWZAoGBAKuC762wkH8zZYHyQkBCbFsHo2xNaD5unt7k
rI+Z1A0KAcxDYRrbB2+TtQUDaBr2qkM9Crj7kLQKzqvHHgnt4tj2VZ6jNhr2N3TI
5/bs8oXRTfzG+5vhuMphACEhpQ6ZppK27N8uuLQL3V0Lcu59hWYfU8+bbc50DGn7
+yFYa7gfAoGBANatpQuu9CwP3/tKkDN8jzDo7n824celL8ANt6CflU0HGW3IKtRN
8E4UVfpuSjU1+KQ4a07wqXMHmY3lIdZa4UOut8Ug/os8+NydlhxuQufFjdtjPYP2
Mq7zrVhebbk+X+vuZdsV6VUr+Xx4n6BpxZ7nf3q0UtrVmHbDVg7QfKjR
-----END RSA PRIVATE KEY-----

View file

@ -2,7 +2,7 @@
<div data-ng-repeat="option in properties" class="form-group" data-ng-controller="ProviderConfigCtrl">
<label class="col-md-2 control-label">{{:: option.label | translate}}</label>
<div class="col-md-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role' || option.type == 'ClientList' || option.type == 'Password' || option.type=='Script'">
<div class="col-md-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role' || option.type == 'ClientList' || option.type == 'Password' || option.type=='Script' || option.type=='Text'">
<input class="form-control" type="text" data-ng-model="config[ option.name ]" >
</div>
<div class="col-md-6" data-ng-show="option.type == 'Password'">
@ -38,6 +38,10 @@
</div>
</div>
<div class="col-md-6" data-ng-show="option.type == 'Text'">
<textarea class="form-control" data-ng-model="config[ option.name ]"/>
</div>
<kc-tooltip>{{:: option.helpText | translate}}</kc-tooltip>
</div>
</div>
</div>