sign artifact resolve-code + post binding

This commit is contained in:
Bernaldo Mihasi 2022-05-25 10:30:59 +03:00
parent 6c36253f09
commit ff6d068ddd
10 changed files with 233 additions and 54 deletions

View File

@ -0,0 +1,52 @@
package eu.eudat.controllers;
import eu.eudat.logic.security.CustomAuthenticationProvider;
import eu.eudat.logic.security.validators.TokenValidatorFactoryImpl;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.models.data.login.LoginInfo;
import eu.eudat.models.data.principal.PrincipalModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@CrossOrigin
@RequestMapping(value = {"/api/auth/saml2"})
public class Saml2PostBinding extends BaseController {
private CustomAuthenticationProvider customAuthenticationProvider;
@Autowired
public Saml2PostBinding(ApiContext apiContext, CustomAuthenticationProvider customAuthenticationProvider) {
super(apiContext);
this.customAuthenticationProvider = customAuthenticationProvider;
}
@RequestMapping(method = RequestMethod.POST, value = {"/postBinding"}, consumes = "application/x-www-form-urlencoded")
public @ResponseBody
ResponseEntity<Void> verify(@RequestParam(value = "SAMLResponse") String SAMLResponse, @RequestParam(value = "RelayState") String RelayState) throws GeneralSecurityException {
Map<String, String> map = Arrays.stream(RelayState.split("&")).map(s -> s.split("=")).collect(Collectors.toMap(e -> e[0], e -> e[1]));
LoginInfo loginInfo = new LoginInfo();
loginInfo.setTicket(SAMLResponse);
loginInfo.setProvider(TokenValidatorFactoryImpl.LoginProvider.CONFIGURABLE.getValue());
Map<String, String> providerId = new HashMap<>();
providerId.put("configurableLoginId", map.get("configurableLoginId"));
loginInfo.setData(providerId);
PrincipalModel principal = this.customAuthenticationProvider.authenticate(loginInfo);
return ResponseEntity.status(HttpStatus.FOUND).header(HttpHeaders.LOCATION, "http://localhost:4200/login/external/saml?token=" + principal.getToken().toString()).build();
}
}

View File

@ -80,11 +80,16 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
private String keyPassword;
private boolean responseSigned;
private boolean assertionSigned;
private boolean signatureRequired;
private String signatureKeyAlias;
private String signaturePath;
private String signatureKeyStorePassword;
private String signatureKeyPassword;
private SAML2UsingFormat usingFormat;
private Map<String, SAML2AttributeType> attributeTypes;
private Map<String, String> configurableUserFromAttributes;
private String binding;
//private String assertionConsumerServiceUrl;
private String assertionConsumerServiceUrl;
public String getSpEntityId() {
return spEntityId;
@ -177,6 +182,41 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
this.assertionSigned = assertionSigned;
}
public boolean isSignatureRequired() {
return signatureRequired;
}
public void setSignatureRequired(boolean signatureRequired) {
this.signatureRequired = signatureRequired;
}
public String getSignatureKeyAlias() {
return signatureKeyAlias;
}
public void setSignatureKeyAlias(String signatureKeyAlias) {
this.signatureKeyAlias = signatureKeyAlias;
}
public String getSignaturePath() {
return signaturePath;
}
public void setSignaturePath(String signaturePath) {
this.signaturePath = signaturePath;
}
public String getSignatureKeyStorePassword() {
return signatureKeyStorePassword;
}
public void setSignatureKeyStorePassword(String signatureKeyStorePassword) {
this.signatureKeyStorePassword = signatureKeyStorePassword;
}
public String getSignatureKeyPassword() {
return signatureKeyPassword;
}
public void setSignatureKeyPassword(String signatureKeyPassword) {
this.signatureKeyPassword = signatureKeyPassword;
}
public SAML2UsingFormat getUsingFormat() {
return usingFormat;
}
@ -205,11 +245,11 @@ public class Saml2ConfigurableProvider extends ConfigurableProvider {
this.binding = binding;
}
// public String getAssertionConsumerServiceUrl() {
// return assertionConsumerServiceUrl;
// }
// public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
// this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
// }
public String getAssertionConsumerServiceUrl() {
return assertionConsumerServiceUrl;
}
public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
}
}

View File

@ -9,7 +9,7 @@ public class Saml2ConfigurableProviderModel extends ConfigurableProviderModel {
private String spEntityId;
private String idpUrl;
private String binding;
//private String assertionConsumerServiceUrl;
private String assertionConsumerServiceUrl;
public String getSpEntityId() {
return spEntityId;
@ -32,12 +32,12 @@ public class Saml2ConfigurableProviderModel extends ConfigurableProviderModel {
this.binding = binding;
}
// public String getAssertionConsumerServiceUrl() {
// return assertionConsumerServiceUrl;
// }
// public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
// this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
// }
public String getAssertionConsumerServiceUrl() {
return assertionConsumerServiceUrl;
}
public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
}
@Override
public Saml2ConfigurableProviderModel fromDataModel(ConfigurableProvider entity) {
@ -49,7 +49,7 @@ public class Saml2ConfigurableProviderModel extends ConfigurableProviderModel {
model.setSpEntityId(((Saml2ConfigurableProvider)entity).getSpEntityId());
model.setIdpUrl(((Saml2ConfigurableProvider)entity).getIdpUrl());
model.setBinding(((Saml2ConfigurableProvider)entity).getBinding());
//model.setAssertionConsumerServiceUrl(((Saml2ConfigurableProvider)entity).getAssertionConsumerServiceUrl());
model.setAssertionConsumerServiceUrl(((Saml2ConfigurableProvider)entity).getAssertionConsumerServiceUrl());
return model;
}

View File

@ -67,7 +67,7 @@ public class ConfigurableProviderTokenValidator implements TokenValidator {
Assertion saml2Assertion = null;
try {
Saml2ConfigurableProvider saml2Provider = (Saml2ConfigurableProvider)configurableProvider;
if(saml2Provider.getBinding().equals("Redirect"))
if(saml2Provider.getBinding().equals("Redirect") || saml2Provider.getBinding().equals("Post"))
saml2Assertion = Saml2SSOUtils.processResponse(credentials.getTicket(), saml2Provider);
else if(saml2Provider.getBinding().equals("Artifact"))
saml2Assertion = Saml2SSOUtils.processArtifactResponse(credentials.getTicket(), saml2Provider);

View File

@ -20,17 +20,18 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.signature.XMLSignature;
import org.opensaml.core.config.ConfigurationService;
import org.opensaml.core.config.InitializationException;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.XMLObjectBuilder;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.opensaml.core.xml.io.*;
import org.opensaml.core.xml.schema.*;
import org.opensaml.saml.common.SAMLObject;
import org.opensaml.saml.common.SAMLObjectBuilder;
import org.opensaml.saml.common.SAMLVersion;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.criterion.EntityRoleCriterion;
@ -47,7 +48,7 @@ import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.security.x509.X509Credential;
import org.opensaml.soap.common.SOAPObjectBuilder;
import org.opensaml.security.x509.impl.KeyStoreX509CredentialAdapter;
import org.opensaml.soap.soap11.Body;
import org.opensaml.soap.soap11.Envelope;
import org.opensaml.xml.util.Base64;
@ -55,7 +56,11 @@ import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
import org.opensaml.xmlsec.encryption.EncryptedKey;
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
import org.opensaml.xmlsec.signature.KeyInfo;
import org.opensaml.xmlsec.signature.Signature;
import org.opensaml.xmlsec.signature.X509Data;
import org.opensaml.xmlsec.signature.support.SignatureValidator;
import org.opensaml.xmlsec.signature.support.Signer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@ -67,6 +72,7 @@ import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.SAXException;
import javax.crypto.SecretKey;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@ -78,6 +84,7 @@ import java.io.*;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.*;
@ -144,6 +151,17 @@ public class Saml2SSOUtils {
}
}
private static XMLObject buildXMLObject(QName objectQName) throws Exception {
doBootstrap();
XMLObjectBuilder builder = registry.getBuilderFactory().getBuilder(objectQName);
if (builder == null) {
throw new Exception("Unable to retrieve builder for object QName " + objectQName);
}
return builder.buildObject(objectQName.getNamespaceURI(), objectQName.getLocalPart(), objectQName.getPrefix());
}
public static String getAttributeName(Attribute attribute, Saml2ConfigurableProvider.SAML2UsingFormat usingFormat){
String friendlyName = attribute.getFriendlyName();
String name = attribute.getName();
@ -231,7 +249,7 @@ public class Saml2SSOUtils {
doBootstrap();
if (artifactString != null){
ArtifactResolve artifactResolve = generateArtifactResolveReq(artifactString, saml2Provider.getSpEntityId());
ArtifactResolve artifactResolve = generateArtifactResolveReq(artifactString, saml2Provider);
ArtifactResponse artifactResponse = sendArtifactResolveRequest(artifactResolve, saml2Provider.getIdpArtifactUrl());
Response saml2Response = (Response)artifactResponse.getMessage();
return processSSOResponse(saml2Response, saml2Provider);
@ -242,34 +260,27 @@ public class Saml2SSOUtils {
}
private static ArtifactResolve generateArtifactResolveReq(String samlArtReceived, String spEntityId) {
private static ArtifactResolve generateArtifactResolveReq(String samlArtReceived, Saml2ConfigurableProvider saml2Provider) throws Exception {
ArtifactResolve artifactResolve = createArtifactResolveObject(samlArtReceived, spEntityId);
// if (config.isEnableArtifactResolveSigning()) {
// artifactResolve = signArtifactResolveReq(artifactResolve);
// }
ArtifactResolve artifactResolve = createArtifactResolveObject(samlArtReceived, saml2Provider.getSpEntityId());
if (saml2Provider.isSignatureRequired()) {
signArtifactResolveReq(artifactResolve, saml2Provider);
}
return artifactResolve;
}
private static ArtifactResolve createArtifactResolveObject(String samlArtReceived, String spEntityId) {
private static ArtifactResolve createArtifactResolveObject(String samlArtReceived, String spEntityId) throws Exception {
XMLObjectBuilderFactory builderFactory = registry.getBuilderFactory();
SAMLObjectBuilder<ArtifactResolve> artifactResolveBuilder =
(SAMLObjectBuilder<ArtifactResolve>) builderFactory.getBuilder(ArtifactResolve.DEFAULT_ELEMENT_NAME);
ArtifactResolve artifactResolve = artifactResolveBuilder.buildObject();
ArtifactResolve artifactResolve = (ArtifactResolve)buildXMLObject(ArtifactResolve.DEFAULT_ELEMENT_NAME);
artifactResolve.setVersion(SAMLVersion.VERSION_20);
artifactResolve.setID(UUID.randomUUID().toString());
artifactResolve.setIssueInstant(Instant.now());
SAMLObjectBuilder<Artifact> artifactBuilder =
(SAMLObjectBuilder<Artifact>) builderFactory.getBuilder(Artifact.DEFAULT_ELEMENT_NAME);
Artifact artifact = artifactBuilder.buildObject();
Artifact artifact = (Artifact)buildXMLObject(Artifact.DEFAULT_ELEMENT_NAME);
artifact.setValue(samlArtReceived);
SAMLObjectBuilder<Issuer> issuerBuilder = (SAMLObjectBuilder<Issuer>) builderFactory.getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
Issuer issuer = issuerBuilder.buildObject();
Issuer issuer = (Issuer)buildXMLObject(Issuer.DEFAULT_ELEMENT_NAME);
issuer.setValue(spEntityId);
artifactResolve.setIssuer(issuer);
@ -279,6 +290,57 @@ public class Saml2SSOUtils {
}
private static void signArtifactResolveReq(ArtifactResolve artifactResolve, Saml2ConfigurableProvider saml2Provider) throws Exception {
try {
KeyStore ks = KeyStore.getInstance("JKS");
String archivePassword = saml2Provider.getSignatureKeyStorePassword();
char[] pwdArray = (archivePassword != null) ? archivePassword.toCharArray() : "changeit".toCharArray();
ks.load(new FileInputStream(saml2Provider.getSignaturePath()), pwdArray);
X509Credential cred = new KeyStoreX509CredentialAdapter(ks, saml2Provider.getSignatureKeyAlias(), saml2Provider.getSignatureKeyPassword().toCharArray());
Signature signature = setSignatureRaw(XMLSignature.ALGO_ID_SIGNATURE_RSA, cred);
artifactResolve.setSignature(signature);
List<Signature> signatureList = new ArrayList<>();
signatureList.add(signature);
MarshallerFactory marshallerFactory = registry.getMarshallerFactory();
Marshaller marshaller = marshallerFactory.getMarshaller(artifactResolve);
marshaller.marshall(artifactResolve);
org.apache.xml.security.Init.init();
Signer.signObjects(signatureList);
} catch (Exception e) {
throw new Exception("Error while signing the SAML Request message", e);
}
}
private static Signature setSignatureRaw(String signatureAlgorithm, X509Credential cred) throws Exception {
Signature signature = (Signature)buildXMLObject(Signature.DEFAULT_ELEMENT_NAME);
signature.setSigningCredential(cred);
signature.setSignatureAlgorithm(signatureAlgorithm);
signature.setCanonicalizationAlgorithm(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
try {
KeyInfo keyInfo = (KeyInfo)buildXMLObject(KeyInfo.DEFAULT_ELEMENT_NAME);
X509Data data = (X509Data)buildXMLObject(X509Data.DEFAULT_ELEMENT_NAME);
org.opensaml.xmlsec.signature.X509Certificate cert =
(org.opensaml.xmlsec.signature.X509Certificate) buildXMLObject(
org.opensaml.xmlsec.signature.X509Certificate.DEFAULT_ELEMENT_NAME);
String value = org.apache.commons.codec.binary.Base64.encodeBase64String(cred.getEntityCertificate().getEncoded());
cert.setValue(value);
data.getX509Certificates().add(cert);
keyInfo.getX509Datas().add(data);
signature.setKeyInfo(keyInfo);
return signature;
} catch (CertificateEncodingException e) {
throw new Exception("Error getting certificate", e);
}
}
private static ArtifactResponse sendArtifactResolveRequest(ArtifactResolve artifactResolve, String idpArtifactUrl) throws Exception {
Envelope envelope = buildSOAPMessage(artifactResolve);
@ -297,17 +359,10 @@ public class Saml2SSOUtils {
}
private static Envelope buildSOAPMessage(SAMLObject samlMessage) {
private static Envelope buildSOAPMessage(SAMLObject samlMessage) throws Exception {
XMLObjectBuilderFactory builderFactory = registry.getBuilderFactory();
SOAPObjectBuilder<Envelope> envBuilder = (SOAPObjectBuilder<Envelope>) builderFactory.getBuilder(
Envelope.DEFAULT_ELEMENT_NAME);
Envelope envelope = envBuilder.buildObject();
SOAPObjectBuilder<Body> bodyBuilder = (SOAPObjectBuilder<Body>) builderFactory.getBuilder(
Body.DEFAULT_ELEMENT_NAME);
Body body = bodyBuilder.buildObject();
Envelope envelope = (Envelope)buildXMLObject(Envelope.DEFAULT_ELEMENT_NAME);
Body body = (Body)buildXMLObject(Body.DEFAULT_ELEMENT_NAME);
body.getUnknownXMLObjects().add(samlMessage);
envelope.setBody(body);
return envelope;
@ -459,10 +514,16 @@ public class Saml2SSOUtils {
doBootstrap();
if (saml2SSOResponse != null) {
byte[] decodedResponse = Base64.decode(saml2SSOResponse);
ByteArrayInputStream bytesIn = new ByteArrayInputStream(decodedResponse);
InflaterInputStream inflater = new InflaterInputStream(bytesIn, new Inflater(true));
String response = new BufferedReader(new InputStreamReader(inflater, StandardCharsets.UTF_8))
.lines().collect(Collectors.joining("\n"));
String response;
if(!saml2Provider.getBinding().equals("Post")){
ByteArrayInputStream bytesIn = new ByteArrayInputStream(decodedResponse);
InflaterInputStream inflater = new InflaterInputStream(bytesIn, new Inflater(true));
response = new BufferedReader(new InputStreamReader(inflater, StandardCharsets.UTF_8))
.lines().collect(Collectors.joining("\n"));
}
else{
response = new String(decodedResponse);
}
Response saml2Response = (Response) Saml2SSOUtils.unmarshall(response);
return processSSOResponse(saml2Response, saml2Provider);

View File

@ -4,4 +4,5 @@ export class Saml2ConfigurableProvider extends ConfigurableProvider{
spEntityId: string;
idpUrl: string;
binding: string;
assertionConsumerServiceUrl: string;
}

View File

@ -181,4 +181,23 @@ export class AuthService extends BaseService {
})
);
}
public getUserFromToken(token: string): Observable<Principal> {
this.actionUrl = this.configurationService.server + 'auth/';
const url = this.actionUrl + 'me';
let headers = this.headers;
headers = headers.set('AuthToken', token);
return this.http.post(url, null, { headers: headers }).pipe(
map((res: any) => {
const princ = this.current(res.payload);
princ.expiresAt = new Date(princ.expiresAt);
return princ;
}),
catchError((error: any) => {
this.clear();
const princ = this.current();
this.router.navigate(['/login']);
return observableOf<Principal>(princ);
}));
}
}

View File

@ -24,17 +24,18 @@ export class SamlLoginService {
return routeParams.has('spId') ? routeParams.get('spId') : '';
}
getSamlLoginUrl(spEntityID: string, idpUrl: string, binding: string, configurableLoginId: string) {
getSamlLoginUrl(spEntityID: string, idpUrl: string, binding: string, assertionConsumerServiceUrl: string, configurableLoginId: string) {
const now = new Date();
let protocolBinding = '';
switch (binding) {
case "Redirect": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" '; break;
case "Artifact": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" '; break;
case "Post": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Post" '; break;
case "Post": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" '; break;
}
const authenticationRequest = '<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_' + Guid.create() + '" Version="2.0" ' +
'IssueInstant="' + now.toISOString() + '" ' +
protocolBinding +
'AssertionConsumerServiceUrl="' + assertionConsumerServiceUrl + '" ' +
'Destination="' + idpUrl + '">' +
'<saml2:Issuer>' + spEntityID + '</saml2:Issuer>' +
'</saml2p:AuthnRequest>';

View File

@ -86,7 +86,7 @@ export class ConfigurableLoginComponent extends BaseComponent implements OnInit
}
else if(this.provider.type === ConfigurableProviderType.Saml2){
let provider = this.provider as Saml2ConfigurableProvider;
window.location.href = this.samlLoginService.getSamlLoginUrl(provider.spEntityId, provider.idpUrl, provider.binding, provider.configurableLoginId);
window.location.href = this.samlLoginService.getSamlLoginUrl(provider.spEntityId, provider.idpUrl, provider.binding, provider.assertionConsumerServiceUrl, provider.configurableLoginId);
}
}

View File

@ -36,8 +36,13 @@ export class SamlResponseLoginComponent extends BaseComponent implements OnInit
} else if (routeParams.SAMLResponse) {
samlResponse = routeParams.SAMLResponse;
}
else if(routeParams.token){
this.authService.getUserFromToken(routeParams.token).pipe(takeUntil(this._destroyed))
.subscribe((result) => this.onAuthenticateSuccess(), (error) => this.onAuthenticateError(error));
return;
}
if (samlResponse == null) return;
if (samlResponse == null) this.router.navigate(['/login']);
const spId = this.samlLoginService.resolveSpId(routeParams.RelayState);
const configurableLoginId = this.samlLoginService.resolveConfigurableLoginId(routeParams.RelayState);