saml signatures
This commit is contained in:
parent
4e41041469
commit
4750b22b6d
12 changed files with 430 additions and 248 deletions
|
@ -8,7 +8,6 @@ import org.picketlink.common.exceptions.ConfigurationException;
|
|||
import org.picketlink.common.exceptions.ProcessingException;
|
||||
import org.picketlink.common.util.DocumentUtil;
|
||||
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
|
||||
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
|
||||
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
|
||||
import org.picketlink.identity.federation.core.saml.v2.factories.JBossSAMLAuthnResponseFactory;
|
||||
import org.picketlink.identity.federation.core.saml.v2.holders.IDPInfoHolder;
|
||||
|
@ -43,7 +42,7 @@ import static org.picketlink.common.util.StringUtil.isNotNull;
|
|||
* @author Anil.Saldhana@redhat.com
|
||||
* @author bburke@redhat.com
|
||||
*/
|
||||
public class SAML2PostBindingResponseBuilder {
|
||||
public class SALM2PostBindingLoginResponseBuilder extends SAML2PostBindingBuilder<SALM2PostBindingLoginResponseBuilder> {
|
||||
protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
|
||||
|
||||
protected List<String> roles = new LinkedList<String>();
|
||||
|
@ -51,188 +50,68 @@ public class SAML2PostBindingResponseBuilder {
|
|||
protected boolean multiValuedRoles;
|
||||
protected boolean disableAuthnStatement;
|
||||
protected String requestID;
|
||||
protected String responseIssuer;
|
||||
protected String authMethod;
|
||||
protected String relayState;
|
||||
protected String destination;
|
||||
protected String requestIssuer;
|
||||
protected Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
|
||||
|
||||
public SAML2PostBindingResponseBuilder attributes(Map<String, Object> attributes) {
|
||||
public SALM2PostBindingLoginResponseBuilder attributes(Map<String, Object> attributes) {
|
||||
this.attributes = attributes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder attribute(String name, Object value) {
|
||||
public SALM2PostBindingLoginResponseBuilder attribute(String name, Object value) {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder requestID(String requestID) {
|
||||
public SALM2PostBindingLoginResponseBuilder requestID(String requestID) {
|
||||
this.requestID =requestID;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder requestIssuer(String requestIssuer) {
|
||||
public SALM2PostBindingLoginResponseBuilder requestIssuer(String requestIssuer) {
|
||||
this.requestIssuer =requestIssuer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder responseIssuer(String issuer) {
|
||||
this.responseIssuer = issuer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder roles(List<String> roles) {
|
||||
public SALM2PostBindingLoginResponseBuilder roles(List<String> roles) {
|
||||
this.roles = roles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder roles(String... roles) {
|
||||
public SALM2PostBindingLoginResponseBuilder roles(String... roles) {
|
||||
for (String role : roles) {
|
||||
this.roles.add(role);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder authMethod(String authMethod) {
|
||||
public SALM2PostBindingLoginResponseBuilder authMethod(String authMethod) {
|
||||
this.authMethod = authMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder userPrincipal(String userPrincipal) {
|
||||
public SALM2PostBindingLoginResponseBuilder userPrincipal(String userPrincipal) {
|
||||
this.userPrincipal = userPrincipal;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder relayState(String relayState) {
|
||||
this.relayState = relayState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder destination(String destination) {
|
||||
this.destination = destination;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder multiValuedRoles(boolean multiValuedRoles) {
|
||||
public SALM2PostBindingLoginResponseBuilder multiValuedRoles(boolean multiValuedRoles) {
|
||||
this.multiValuedRoles = multiValuedRoles;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SAML2PostBindingResponseBuilder disableAuthnStatement(boolean disableAuthnStatement) {
|
||||
public SALM2PostBindingLoginResponseBuilder disableAuthnStatement(boolean disableAuthnStatement) {
|
||||
this.disableAuthnStatement = disableAuthnStatement;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Response buildErrorResponse(String status) throws ConfigurationException, ProcessingException, IOException {
|
||||
Document doc = getErrorResponse(status);
|
||||
return buildResponse(doc);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public Document getErrorResponse(String status) {
|
||||
Document samlResponse = null;
|
||||
ResponseType responseType = null;
|
||||
|
||||
SAML2Response saml2Response = new SAML2Response();
|
||||
|
||||
// Create a response type
|
||||
String id = IDGenerator.create("ID_");
|
||||
|
||||
IssuerInfoHolder issuerHolder = new IssuerInfoHolder(responseIssuer);
|
||||
issuerHolder.setStatusCode(status);
|
||||
|
||||
IDPInfoHolder idp = new IDPInfoHolder();
|
||||
idp.setNameIDFormatValue(null);
|
||||
idp.setNameIDFormat(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get());
|
||||
|
||||
SPInfoHolder sp = new SPInfoHolder();
|
||||
sp.setResponseDestinationURI(destination);
|
||||
|
||||
responseType = saml2Response.createResponseType(id);
|
||||
responseType.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status));
|
||||
responseType.setDestination(destination);
|
||||
|
||||
// Lets see how the response looks like
|
||||
if (logger.isTraceEnabled()) {
|
||||
StringWriter sw = new StringWriter();
|
||||
try {
|
||||
saml2Response.marshall(responseType, sw);
|
||||
} catch (ProcessingException e) {
|
||||
logger.trace(e);
|
||||
}
|
||||
logger.trace("SAML Response Document: " + sw.toString());
|
||||
}
|
||||
|
||||
/*
|
||||
if (supportSignature) {
|
||||
try {
|
||||
SAML2Signature ss = new SAML2Signature();
|
||||
samlResponse = ss.sign(responseType, keyManager.getSigningKeyPair());
|
||||
} catch (Exception e) {
|
||||
logger.trace(e);
|
||||
throw new RuntimeException(logger.signatureError(e));
|
||||
}
|
||||
} else
|
||||
try {
|
||||
samlResponse = saml2Response.convert(responseType);
|
||||
} catch (Exception e) {
|
||||
logger.trace(e);
|
||||
}
|
||||
*/
|
||||
|
||||
return samlResponse;
|
||||
}
|
||||
|
||||
public Response buildLoginResponse() throws ConfigurationException, ProcessingException, IOException {
|
||||
Document responseDoc = getResponse();
|
||||
return buildResponse(responseDoc);
|
||||
}
|
||||
|
||||
protected Response buildResponse(Document responseDoc) throws ProcessingException, ConfigurationException, IOException {
|
||||
byte[] responseBytes = DocumentUtil.getDocumentAsString(responseDoc).getBytes("UTF-8");
|
||||
String samlResponse = PostBindingUtil.base64Encode(new String(responseBytes));
|
||||
|
||||
if (destination == null) {
|
||||
throw logger.nullValueError("Destination is null");
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
String key = GeneralConstants.SAML_RESPONSE_KEY;
|
||||
builder.append("<HTML>");
|
||||
builder.append("<HEAD>");
|
||||
|
||||
builder.append("<TITLE>HTTP Post Binding Response (Response)</TITLE>");
|
||||
builder.append("</HEAD>");
|
||||
builder.append("<BODY Onload=\"document.forms[0].submit()\">");
|
||||
|
||||
builder.append("<FORM METHOD=\"POST\" ACTION=\"" + destination + "\">");
|
||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + key + "\"" + " VALUE=\"" + samlResponse + "\"/>");
|
||||
|
||||
if (isNotNull(relayState)) {
|
||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"RelayState\" " + "VALUE=\"" + relayState + "\"/>");
|
||||
}
|
||||
|
||||
builder.append("<NOSCRIPT>");
|
||||
builder.append("<P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.</P>");
|
||||
builder.append("<INPUT TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
|
||||
builder.append("</NOSCRIPT>");
|
||||
|
||||
builder.append("</FORM></BODY></HTML>");
|
||||
|
||||
String str = builder.toString();
|
||||
|
||||
CacheControl cacheControl = new CacheControl();
|
||||
cacheControl.setNoCache(true);
|
||||
return Response.ok(str, MediaType.TEXT_HTML_TYPE)
|
||||
.header("Pragma", "no-cache")
|
||||
.header("Cache-Control", "no-cache, no-store").build();
|
||||
}
|
||||
|
||||
public Document getResponse() throws ConfigurationException, ProcessingException {
|
||||
|
||||
Document samlResponseDocument = null;
|
||||
|
@ -295,6 +174,10 @@ public class SAML2PostBindingResponseBuilder {
|
|||
throw logger.samlAssertionMarshallError(e);
|
||||
}
|
||||
|
||||
if (signed) {
|
||||
signDocument(samlResponseDocument);
|
||||
}
|
||||
return samlResponseDocument;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.picketlink.common.constants.GeneralConstants;
|
||||
import org.picketlink.common.exceptions.ConfigurationException;
|
||||
import org.picketlink.common.exceptions.ProcessingException;
|
||||
import org.picketlink.common.util.DocumentUtil;
|
||||
import org.picketlink.identity.federation.web.util.PostBindingUtil;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.ws.rs.core.CacheControl;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static org.picketlink.common.util.StringUtil.isNotNull;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SAML2PostBindingBuilder<T extends SAML2PostBindingBuilder> {
|
||||
protected KeyPair signingKeyPair;
|
||||
protected X509Certificate signingCertificate;
|
||||
protected boolean signed;
|
||||
protected String signatureDigestMethod;
|
||||
protected String signatureMethod;
|
||||
protected String relayState;
|
||||
protected String destination;
|
||||
protected String responseIssuer;
|
||||
|
||||
public T sign(KeyPair keyPair) {
|
||||
this.signingKeyPair = keyPair;
|
||||
this.signed = true;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T sign(PrivateKey privateKey, PublicKey publicKey) {
|
||||
this.signingKeyPair = new KeyPair(publicKey, privateKey);
|
||||
this.signed = true;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T sign(KeyPair keyPair, X509Certificate cert) {
|
||||
this.signingKeyPair = keyPair;
|
||||
this.signingCertificate = cert;
|
||||
this.signed = true;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T signatureDigestMethod(String method) {
|
||||
this.signatureDigestMethod = method;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T signatureMethod(String method) {
|
||||
this.signatureMethod = method;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T sign(PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) {
|
||||
this.signingKeyPair = new KeyPair(publicKey, privateKey);
|
||||
this.signingCertificate = cert;
|
||||
this.signed = true;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T destination(String destination) {
|
||||
this.destination = destination;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T responseIssuer(String issuer) {
|
||||
this.responseIssuer = issuer;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T relayState(String relayState) {
|
||||
this.relayState = relayState;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected void signDocument(Document samlDocument) throws ProcessingException {
|
||||
SamlProtocolUtils.signDocument(samlDocument, signingKeyPair, signatureMethod, signatureDigestMethod, signingCertificate);
|
||||
}
|
||||
|
||||
protected Response buildResponse(Document responseDoc) throws ProcessingException, ConfigurationException, IOException {
|
||||
byte[] responseBytes = DocumentUtil.getDocumentAsString(responseDoc).getBytes("UTF-8");
|
||||
String samlResponse = PostBindingUtil.base64Encode(new String(responseBytes));
|
||||
|
||||
if (destination == null) {
|
||||
throw SALM2PostBindingLoginResponseBuilder.logger.nullValueError("Destination is null");
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
String key = GeneralConstants.SAML_RESPONSE_KEY;
|
||||
builder.append("<HTML>");
|
||||
builder.append("<HEAD>");
|
||||
|
||||
builder.append("<TITLE>HTTP Post Binding Response (Response)</TITLE>");
|
||||
builder.append("</HEAD>");
|
||||
builder.append("<BODY Onload=\"document.forms[0].submit()\">");
|
||||
|
||||
builder.append("<FORM METHOD=\"POST\" ACTION=\"" + destination + "\">");
|
||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"" + key + "\"" + " VALUE=\"" + samlResponse + "\"/>");
|
||||
|
||||
if (isNotNull(relayState)) {
|
||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"RelayState\" " + "VALUE=\"" + relayState + "\"/>");
|
||||
}
|
||||
|
||||
builder.append("<NOSCRIPT>");
|
||||
builder.append("<P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.</P>");
|
||||
builder.append("<INPUT TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
|
||||
builder.append("</NOSCRIPT>");
|
||||
|
||||
builder.append("</FORM></BODY></HTML>");
|
||||
|
||||
String str = builder.toString();
|
||||
|
||||
CacheControl cacheControl = new CacheControl();
|
||||
cacheControl.setNoCache(true);
|
||||
return Response.ok(str, MediaType.TEXT_HTML_TYPE)
|
||||
.header("Pragma", "no-cache")
|
||||
.header("Cache-Control", "no-cache, no-store").build();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||
import org.picketlink.common.exceptions.ConfigurationException;
|
||||
import org.picketlink.common.exceptions.ProcessingException;
|
||||
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
|
||||
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
|
||||
import org.picketlink.identity.federation.core.saml.v2.factories.JBossSAMLAuthnResponseFactory;
|
||||
import org.picketlink.identity.federation.core.saml.v2.holders.IDPInfoHolder;
|
||||
import org.picketlink.identity.federation.core.saml.v2.holders.IssuerInfoHolder;
|
||||
import org.picketlink.identity.federation.core.saml.v2.holders.SPInfoHolder;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SAML2PostBindingErrorResponseBuilder extends SAML2PostBindingBuilder<SAML2PostBindingErrorResponseBuilder> {
|
||||
|
||||
public Document getErrorResponse(String status) throws ProcessingException {
|
||||
Document samlResponse = null;
|
||||
ResponseType responseType = null;
|
||||
|
||||
SAML2Response saml2Response = new SAML2Response();
|
||||
|
||||
// Create a response type
|
||||
String id = IDGenerator.create("ID_");
|
||||
|
||||
IssuerInfoHolder issuerHolder = new IssuerInfoHolder(responseIssuer);
|
||||
issuerHolder.setStatusCode(status);
|
||||
|
||||
IDPInfoHolder idp = new IDPInfoHolder();
|
||||
idp.setNameIDFormatValue(null);
|
||||
idp.setNameIDFormat(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get());
|
||||
|
||||
SPInfoHolder sp = new SPInfoHolder();
|
||||
sp.setResponseDestinationURI(destination);
|
||||
|
||||
responseType = saml2Response.createResponseType(id);
|
||||
responseType.setStatus(JBossSAMLAuthnResponseFactory.createStatusTypeForResponder(status));
|
||||
responseType.setDestination(destination);
|
||||
|
||||
if (signed) {
|
||||
signDocument(samlResponse);
|
||||
}
|
||||
return samlResponse;
|
||||
}
|
||||
|
||||
public Response buildErrorResponse(String status) throws ConfigurationException, ProcessingException, IOException {
|
||||
Document doc = getErrorResponse(status);
|
||||
return buildResponse(doc);
|
||||
}}
|
|
@ -0,0 +1,69 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||
import org.picketlink.common.exceptions.ConfigurationException;
|
||||
import org.picketlink.common.exceptions.ParsingException;
|
||||
import org.picketlink.common.exceptions.ProcessingException;
|
||||
import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
|
||||
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
|
||||
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
|
||||
import org.picketlink.identity.federation.core.saml.v2.factories.JBossSAMLAuthnResponseFactory;
|
||||
import org.picketlink.identity.federation.core.saml.v2.holders.IDPInfoHolder;
|
||||
import org.picketlink.identity.federation.core.saml.v2.holders.IssuerInfoHolder;
|
||||
import org.picketlink.identity.federation.core.saml.v2.holders.SPInfoHolder;
|
||||
import org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil;
|
||||
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
|
||||
import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
|
||||
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
|
||||
import org.picketlink.identity.federation.web.util.PostBindingUtil;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.KeyPair;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SAML2PostBindingLogoutResponseBuilder extends SAML2PostBindingBuilder<SAML2PostBindingLogoutResponseBuilder> {
|
||||
protected String userPrincipal;
|
||||
|
||||
public SAML2PostBindingLogoutResponseBuilder userPrincipal(String userPrincipal) {
|
||||
this.userPrincipal = userPrincipal;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String buildRequestString() {
|
||||
try {
|
||||
Document logoutRequestDocument = new SAML2Request().convert(createLogoutRequest());
|
||||
if (signed) {
|
||||
signDocument(logoutRequestDocument);
|
||||
}
|
||||
byte[] responseBytes = DocumentUtil.getDocumentAsString(logoutRequestDocument).getBytes("UTF-8");
|
||||
return PostBindingUtil.base64Encode(new String(responseBytes));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private LogoutRequestType createLogoutRequest() throws ConfigurationException {
|
||||
LogoutRequestType lort = new SAML2Request().createLogoutRequest(responseIssuer);
|
||||
|
||||
NameIDType nameID = new NameIDType();
|
||||
nameID.setValue(userPrincipal);
|
||||
//Deal with NameID Format
|
||||
String nameIDFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
|
||||
nameID.setFormat(URI.create(nameIDFormat));
|
||||
lort.setNameID(nameID);
|
||||
|
||||
long assertionValidity = PicketLinkCoreSTS.instance().getConfiguration().getIssuedTokenTimeout();
|
||||
|
||||
lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionValidity));
|
||||
lort.setDestination(URI.create(destination));
|
||||
return lort;
|
||||
}
|
||||
}
|
|
@ -43,6 +43,5 @@ public class SAMLRequestParser {
|
|||
logger.samlBase64DecodingError(e);
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,34 +20,19 @@ import org.keycloak.services.resources.RealmsResource;
|
|||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.picketlink.common.constants.GeneralConstants;
|
||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||
import org.picketlink.common.exceptions.ConfigurationException;
|
||||
import org.picketlink.common.exceptions.ParsingException;
|
||||
import org.picketlink.common.exceptions.ProcessingException;
|
||||
import org.picketlink.common.util.StringUtil;
|
||||
import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
|
||||
import org.picketlink.identity.federation.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||
import org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil;
|
||||
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
|
||||
import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
|
||||
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
||||
import org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler;
|
||||
import org.picketlink.identity.federation.web.util.PostBindingUtil;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SamlLogin implements LoginProtocol {
|
||||
protected static final Logger logger = Logger.getLogger(SamlLogin.class);
|
||||
public class SalmProtocol implements LoginProtocol {
|
||||
protected static final Logger logger = Logger.getLogger(SalmProtocol.class);
|
||||
public static final String LOGIN_PROTOCOL = "saml";
|
||||
public static final String SAML_BINDING = "saml_binding";
|
||||
public static final String SAML_POST_BINDING = "post";
|
||||
|
@ -61,19 +46,19 @@ public class SamlLogin implements LoginProtocol {
|
|||
|
||||
|
||||
@Override
|
||||
public SamlLogin setSession(KeycloakSession session) {
|
||||
public SalmProtocol setSession(KeycloakSession session) {
|
||||
this.session = session;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlLogin setRealm(RealmModel realm) {
|
||||
public SalmProtocol setRealm(RealmModel realm) {
|
||||
this.realm = realm;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SamlLogin setUriInfo(UriInfo uriInfo) {
|
||||
public SalmProtocol setUriInfo(UriInfo uriInfo) {
|
||||
this.uriInfo = uriInfo;
|
||||
return this;
|
||||
}
|
||||
|
@ -93,14 +78,10 @@ public class SamlLogin implements LoginProtocol {
|
|||
}
|
||||
|
||||
protected Response getErrorResponse(ClientSessionModel clientSession, String status) {
|
||||
String relayState = clientSession.getNote(GeneralConstants.RELAY_STATE);
|
||||
String redirectUri = clientSession.getRedirectUri();
|
||||
SAML2PostBindingResponseBuilder builder = new SAML2PostBindingResponseBuilder();
|
||||
String responseIssuer = getResponseIssuer(realm);
|
||||
builder .relayState(relayState)
|
||||
.destination(redirectUri)
|
||||
.responseIssuer(responseIssuer)
|
||||
.requestIssuer(clientSession.getClient().getClientId());
|
||||
SAML2PostBindingErrorResponseBuilder builder = new SAML2PostBindingErrorResponseBuilder()
|
||||
.relayState(clientSession.getNote(GeneralConstants.RELAY_STATE))
|
||||
.destination(clientSession.getRedirectUri())
|
||||
.responseIssuer(getResponseIssuer(realm));
|
||||
try {
|
||||
return builder.buildErrorResponse(status);
|
||||
} catch (Exception e) {
|
||||
|
@ -111,7 +92,7 @@ public class SamlLogin implements LoginProtocol {
|
|||
@Override
|
||||
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
|
||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||
if (SamlLogin.SAML_POST_BINDING.equals(clientSession.getNote(SamlLogin.SAML_BINDING))) {
|
||||
if (SalmProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SalmProtocol.SAML_BINDING))) {
|
||||
return postBinding(userSession, clientSession);
|
||||
}
|
||||
throw new RuntimeException("still need to implement redirect binding");
|
||||
|
@ -123,7 +104,7 @@ public class SamlLogin implements LoginProtocol {
|
|||
String redirectUri = clientSession.getRedirectUri();
|
||||
String responseIssuer = getResponseIssuer(realm);
|
||||
|
||||
SAML2PostBindingResponseBuilder builder = new SAML2PostBindingResponseBuilder();
|
||||
SALM2PostBindingLoginResponseBuilder builder = new SALM2PostBindingLoginResponseBuilder();
|
||||
builder.requestID(requestID)
|
||||
.relayState(relayState)
|
||||
.destination(redirectUri)
|
||||
|
@ -140,7 +121,10 @@ public class SamlLogin implements LoginProtocol {
|
|||
builder.roles(roleModel.getName());
|
||||
}
|
||||
}
|
||||
|
||||
ClientModel client = clientSession.getClient();
|
||||
if (requiresRealmSignature(client)) {
|
||||
builder.sign(realm.getPrivateKey(), realm.getPublicKey());
|
||||
}
|
||||
try {
|
||||
return builder.buildLoginResponse();
|
||||
} catch (Exception e) {
|
||||
|
@ -149,7 +133,11 @@ public class SamlLogin implements LoginProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
public void initClaims(SAML2PostBindingResponseBuilder builder, ClientModel model, UserModel user) {
|
||||
private boolean requiresRealmSignature(ClientModel client) {
|
||||
return "true".equals(client.getAttribute("samlServerSignature"));
|
||||
}
|
||||
|
||||
public void initClaims(SALM2PostBindingLoginResponseBuilder builder, ClientModel model, UserModel user) {
|
||||
if (ClaimMask.hasEmail(model.getAllowedClaimsMask())) {
|
||||
builder.attribute(X500SAMLProfileConstants.EMAIL_ADDRESS.getFriendlyName(), user.getEmail());
|
||||
}
|
||||
|
@ -172,15 +160,18 @@ public class SamlLogin implements LoginProtocol {
|
|||
ApplicationModel app = (ApplicationModel)client;
|
||||
if (app.getManagementUrl() == null) return;
|
||||
|
||||
SAML2PostBindingLogoutResponseBuilder logoutBuilder = new SAML2PostBindingLogoutResponseBuilder()
|
||||
.userPrincipal(userSession.getUser().getUsername())
|
||||
.destination(client.getClientId());
|
||||
if (requiresRealmSignature(client)) {
|
||||
logoutBuilder.sign(realm.getPrivateKey(), realm.getPublicKey());
|
||||
}
|
||||
String logoutRequestString = null;
|
||||
try {
|
||||
LogoutRequestType logoutRequest = createLogoutRequest(userSession.getUser(), client);
|
||||
Document logoutRequestDocument = new SAML2Request().convert(logoutRequest);
|
||||
|
||||
byte[] responseBytes = DocumentUtil.getDocumentAsString(logoutRequestDocument).getBytes("UTF-8");
|
||||
logoutRequestString = PostBindingUtil.base64Encode(new String(responseBytes));
|
||||
logoutRequestString = logoutBuilder.buildRequestString();
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to send saml logout", e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
@ -219,24 +210,6 @@ public class SamlLogin implements LoginProtocol {
|
|||
|
||||
}
|
||||
|
||||
private LogoutRequestType createLogoutRequest(UserModel user, ClientModel client) throws ConfigurationException, ProcessingException {
|
||||
LogoutRequestType lort = new SAML2Request().createLogoutRequest(getResponseIssuer(realm));
|
||||
|
||||
NameIDType nameID = new NameIDType();
|
||||
nameID.setValue(user.getUsername());
|
||||
//Deal with NameID Format
|
||||
String nameIDFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
|
||||
nameID.setFormat(URI.create(nameIDFormat));
|
||||
lort.setNameID(nameID);
|
||||
|
||||
long assertionValidity = PicketLinkCoreSTS.instance().getConfiguration().getIssuedTokenTimeout();
|
||||
|
||||
lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionValidity));
|
||||
lort.setDestination(URI.create(client.getClientId()));
|
||||
return lort;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
|
@ -13,7 +13,7 @@ import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SamlLoginFactory implements LoginProtocolFactory {
|
||||
public class SamlProtocolFactory implements LoginProtocolFactory {
|
||||
|
||||
@Override
|
||||
public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
|
||||
|
@ -22,7 +22,7 @@ public class SamlLoginFactory implements LoginProtocolFactory {
|
|||
|
||||
@Override
|
||||
public LoginProtocol create(KeycloakSession session) {
|
||||
return new SamlLogin().setSession(session);
|
||||
return new SalmProtocol().setSession(session);
|
||||
}
|
||||
|
||||
@Override
|
|
@ -0,0 +1,66 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.util.PemUtils;
|
||||
import org.picketlink.common.exceptions.ProcessingException;
|
||||
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SamlProtocolUtils {
|
||||
|
||||
public static void verifyPostBindingSignature(ClientModel client, Document document) throws VerificationException {
|
||||
if (!"true".equals(client.getAttribute("samlClientSignature"))) {
|
||||
return;
|
||||
}
|
||||
SAML2Signature saml2Signature = new SAML2Signature();
|
||||
String publicKeyPem = client.getAttribute(ClientModel.PUBLIC_KEY);
|
||||
if (publicKeyPem == null) throw new VerificationException("Client does not have a public key.");
|
||||
PublicKey publicKey = null;
|
||||
try {
|
||||
publicKey = PemUtils.decodePublicKey(publicKeyPem);
|
||||
} catch (Exception e) {
|
||||
throw new VerificationException("Could not decode public key", e);
|
||||
}
|
||||
try {
|
||||
if (!saml2Signature.validate(document, publicKey)) {
|
||||
throw new VerificationException("Invalid signature on document");
|
||||
}
|
||||
} catch (ProcessingException e) {
|
||||
throw new VerificationException("Error validating signature", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void signDocument(Document samlDocument, KeyPair signingKeyPair, String signatureMethod, String signatureDigestMethod, X509Certificate signingCertificate) throws ProcessingException {
|
||||
SAML2Signature samlSignature = new SAML2Signature();
|
||||
|
||||
if (signatureMethod != null) {
|
||||
samlSignature.setSignatureMethod(signatureMethod);
|
||||
}
|
||||
|
||||
if (signatureDigestMethod != null) {
|
||||
samlSignature.setDigestMethod(signatureDigestMethod);
|
||||
}
|
||||
|
||||
Node nextSibling = samlSignature.getNextSiblingOfIssuer(samlDocument);
|
||||
|
||||
samlSignature.setNextSibling(nextSibling);
|
||||
|
||||
if (signingCertificate != null) {
|
||||
samlSignature.setX509Certificate(signingCertificate);
|
||||
}
|
||||
|
||||
samlSignature.signSAMLDocument(samlDocument, signingKeyPair);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
package org.keycloak.protocol.saml;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.jboss.resteasy.spi.HttpResponse;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
|
@ -27,15 +25,14 @@ import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder
|
|||
import org.picketlink.identity.federation.saml.v2.SAML2Object;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.RequestAbstractType;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
@ -45,8 +42,6 @@ import javax.ws.rs.core.UriBuilder;
|
|||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.ext.Providers;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Resource class for the oauth/openid connect token service
|
||||
|
@ -137,24 +132,7 @@ public class SamlService {
|
|||
|
||||
SAML2Object samlObject = documentHolder.getSamlObject();
|
||||
|
||||
if (samlObject instanceof AuthnRequestType) {
|
||||
event.event(EventType.LOGIN);
|
||||
// Get the SAML Request Message
|
||||
AuthnRequestType requestAbstractType = (AuthnRequestType) samlObject;
|
||||
return loginRequest(relayState, requestAbstractType);
|
||||
} else if (samlObject instanceof LogoutRequestType) {
|
||||
event.event(EventType.LOGOUT);
|
||||
LogoutRequestType requestAbstractType = (LogoutRequestType) samlObject;
|
||||
return logoutRequest(relayState, requestAbstractType);
|
||||
|
||||
} else {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
}
|
||||
}
|
||||
|
||||
protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType) {
|
||||
RequestAbstractType requestAbstractType = (RequestAbstractType)samlObject;
|
||||
String issuer = requestAbstractType.getIssuer().getValue();
|
||||
ClientModel client = realm.findClient(issuer);
|
||||
|
||||
|
@ -176,6 +154,32 @@ public class SamlService {
|
|||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
|
||||
}
|
||||
|
||||
try {
|
||||
SamlProtocolUtils.verifyPostBindingSignature(client, documentHolder.getSamlDocument());
|
||||
} catch (VerificationException e) {
|
||||
logger.error("request validation failed", e);
|
||||
event.error(Errors.INVALID_CLIENT);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid requester.");
|
||||
}
|
||||
if (samlObject instanceof AuthnRequestType) {
|
||||
event.event(EventType.LOGIN);
|
||||
// Get the SAML Request Message
|
||||
AuthnRequestType authn = (AuthnRequestType) samlObject;
|
||||
return loginRequest(relayState, authn, client);
|
||||
} else if (samlObject instanceof LogoutRequestType) {
|
||||
event.event(EventType.LOGOUT);
|
||||
LogoutRequestType logout = (LogoutRequestType) samlObject;
|
||||
return logoutRequest(logout, client);
|
||||
|
||||
} else {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
|
||||
}
|
||||
}
|
||||
|
||||
protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
|
||||
|
||||
URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
|
||||
String redirect = OpenIDConnectService.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
|
||||
|
||||
|
@ -186,10 +190,10 @@ public class SamlService {
|
|||
|
||||
|
||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||
clientSession.setAuthMethod(SamlLogin.LOGIN_PROTOCOL);
|
||||
clientSession.setAuthMethod(SalmProtocol.LOGIN_PROTOCOL);
|
||||
clientSession.setRedirectUri(redirect);
|
||||
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
|
||||
clientSession.setNote(SamlLogin.SAML_BINDING, SamlLogin.SAML_POST_BINDING);
|
||||
clientSession.setNote(SalmProtocol.SAML_BINDING, SalmProtocol.SAML_POST_BINDING);
|
||||
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
||||
clientSession.setNote("REQUEST_ID", requestAbstractType.getID());
|
||||
|
||||
|
@ -212,28 +216,7 @@ public class SamlService {
|
|||
return forms.createLogin();
|
||||
}
|
||||
|
||||
protected Response logoutRequest(String relayState, LogoutRequestType requestAbstractType) {
|
||||
String issuer = requestAbstractType.getIssuer().getValue();
|
||||
ClientModel client = realm.findClient(issuer);
|
||||
|
||||
if (client == null) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
|
||||
}
|
||||
|
||||
if (!client.isEnabled()) {
|
||||
event.error(Errors.CLIENT_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
|
||||
}
|
||||
if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login");
|
||||
}
|
||||
if (client.isDirectGrantsOnly()) {
|
||||
event.error(Errors.NOT_ALLOWED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
|
||||
}
|
||||
|
||||
protected Response logoutRequest(LogoutRequestType requestAbstractType, ClientModel client) {
|
||||
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
|
||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
|
||||
if (authResult != null) {
|
||||
|
|
|
@ -1 +1 @@
|
|||
org.keycloak.protocol.saml.SamlLoginFactory
|
||||
org.keycloak.protocol.saml.SamlProtocolFactory
|
|
@ -157,11 +157,11 @@ public class AccountTest {
|
|||
});
|
||||
}
|
||||
|
||||
// @Test
|
||||
// @Ignore
|
||||
// public void runit() throws Exception {
|
||||
// Thread.sleep(10000000);
|
||||
// }
|
||||
@Test
|
||||
@Ignore
|
||||
public void runit() throws Exception {
|
||||
Thread.sleep(10000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnToAppFromQueryParam() {
|
||||
|
|
|
@ -32,11 +32,30 @@
|
|||
"name": "http://localhost:8080/sales-post/",
|
||||
"enabled": true,
|
||||
"fullScopeAllowed": true,
|
||||
"protocol": "saml",
|
||||
"baseUrl": "http://localhost:8080/sales-post",
|
||||
"adminUrl": "http://localhost:8080/sales-post",
|
||||
"redirectUris": [
|
||||
"http://localhost:8080/sales-post/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "http://localhost:8080/sales-post-sig/",
|
||||
"enabled": true,
|
||||
"protocol": "saml",
|
||||
"fullScopeAllowed": true,
|
||||
"baseUrl": "http://localhost:8080/sales-post-sig",
|
||||
"adminUrl": "http://localhost:8080/sales-post-sig",
|
||||
"redirectUris": [
|
||||
"http://localhost:8080/sales-post-sig/*"
|
||||
],
|
||||
"attributes": {
|
||||
"samlServerSignature": "true",
|
||||
"samlClientSignature": "true",
|
||||
"privateKey": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQAB",
|
||||
"X509Certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw=="
|
||||
}
|
||||
}
|
||||
],
|
||||
"roles" : {
|
||||
|
|
Loading…
Reference in a new issue