refactor signing of saml2 authentication request
This commit is contained in:
parent
1a2a93a95f
commit
b6505cda4e
|
@ -5,6 +5,7 @@ import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.sam
|
||||||
import eu.eudat.logic.security.validators.configurableProvider.Saml2SSOUtils;
|
import eu.eudat.logic.security.validators.configurableProvider.Saml2SSOUtils;
|
||||||
import eu.eudat.logic.services.ApiContext;
|
import eu.eudat.logic.services.ApiContext;
|
||||||
import eu.eudat.models.data.helpers.responses.ResponseItem;
|
import eu.eudat.models.data.helpers.responses.ResponseItem;
|
||||||
|
import eu.eudat.models.data.saml2.AuthnRequestModel;
|
||||||
import eu.eudat.types.ApiMessageCode;
|
import eu.eudat.types.ApiMessageCode;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
@ -56,14 +57,14 @@ public class Saml2MetadataController extends BaseController {
|
||||||
}
|
}
|
||||||
@RequestMapping(method = RequestMethod.GET, value = {"authnRequest/{configurableProviderId}"})
|
@RequestMapping(method = RequestMethod.GET, value = {"authnRequest/{configurableProviderId}"})
|
||||||
public @ResponseBody
|
public @ResponseBody
|
||||||
ResponseEntity<ResponseItem<String>> getAuthnRequest(@PathVariable String configurableProviderId) {
|
ResponseEntity getAuthnRequest(@PathVariable String configurableProviderId) {
|
||||||
Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider) this.configLoader.getConfigurableProviders().getProviders().stream()
|
Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider) this.configLoader.getConfigurableProviders().getProviders().stream()
|
||||||
.filter(prov -> prov.getConfigurableLoginId().equals(configurableProviderId))
|
.filter(prov -> prov.getConfigurableLoginId().equals(configurableProviderId))
|
||||||
.findFirst().orElse(null);
|
.findFirst().orElse(null);
|
||||||
if (saml2ConfigurableProvider != null) {
|
if (saml2ConfigurableProvider != null) {
|
||||||
try {
|
try {
|
||||||
String authnRequestXml = Saml2SSOUtils.getAuthnRequest(saml2ConfigurableProvider);
|
AuthnRequestModel authnRequest = Saml2SSOUtils.getAuthnRequest(saml2ConfigurableProvider);
|
||||||
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<String>().status(ApiMessageCode.SUCCESS_MESSAGE).message("Created").payload(authnRequestXml));
|
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<AuthnRequestModel>().status(ApiMessageCode.SUCCESS_MESSAGE).message("Created").payload(authnRequest));
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem<String>().status(ApiMessageCode.ERROR_MESSAGE).message("Failed to create authentication request."));
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem<String>().status(ApiMessageCode.ERROR_MESSAGE).message("Failed to create authentication request."));
|
||||||
|
|
|
@ -3,6 +3,7 @@ package eu.eudat.logic.security.validators.configurableProvider;
|
||||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.CertificateInfo;
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.CertificateInfo;
|
||||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
|
||||||
import eu.eudat.logic.utilities.builders.XmlBuilder;
|
import eu.eudat.logic.utilities.builders.XmlBuilder;
|
||||||
|
import eu.eudat.models.data.saml2.AuthnRequestModel;
|
||||||
import jakarta.xml.soap.*;
|
import jakarta.xml.soap.*;
|
||||||
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
|
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
|
||||||
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
|
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
|
||||||
|
@ -34,7 +35,6 @@ import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||||
import org.opensaml.core.xml.io.*;
|
import org.opensaml.core.xml.io.*;
|
||||||
import org.opensaml.core.xml.schema.*;
|
import org.opensaml.core.xml.schema.*;
|
||||||
import org.opensaml.saml.common.SAMLObject;
|
import org.opensaml.saml.common.SAMLObject;
|
||||||
import org.opensaml.saml.common.SAMLObjectContentReference;
|
|
||||||
import org.opensaml.saml.common.SAMLVersion;
|
import org.opensaml.saml.common.SAMLVersion;
|
||||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||||
import org.opensaml.saml.criterion.EntityRoleCriterion;
|
import org.opensaml.saml.criterion.EntityRoleCriterion;
|
||||||
|
@ -64,8 +64,6 @@ import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
|
||||||
import org.opensaml.xmlsec.signature.KeyInfo;
|
import org.opensaml.xmlsec.signature.KeyInfo;
|
||||||
import org.opensaml.xmlsec.signature.Signature;
|
import org.opensaml.xmlsec.signature.Signature;
|
||||||
import org.opensaml.xmlsec.signature.X509Data;
|
import org.opensaml.xmlsec.signature.X509Data;
|
||||||
import org.opensaml.xmlsec.signature.impl.SignatureBuilder;
|
|
||||||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
|
||||||
import org.opensaml.xmlsec.signature.support.SignatureValidator;
|
import org.opensaml.xmlsec.signature.support.SignatureValidator;
|
||||||
import org.opensaml.xmlsec.signature.support.Signer;
|
import org.opensaml.xmlsec.signature.support.Signer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -88,6 +86,7 @@ import javax.xml.transform.TransformerFactory;
|
||||||
import javax.xml.transform.dom.DOMSource;
|
import javax.xml.transform.dom.DOMSource;
|
||||||
import javax.xml.transform.stream.StreamResult;
|
import javax.xml.transform.stream.StreamResult;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
|
@ -794,47 +793,68 @@ public class Saml2SSOUtils {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getAuthnRequest(Saml2ConfigurableProvider provider) throws Exception {
|
public static AuthnRequestModel getAuthnRequest(Saml2ConfigurableProvider provider) throws Exception {
|
||||||
|
|
||||||
AuthnRequest authnRequest = buildAuthnRequest(provider);
|
AuthnRequest authnRequest = buildAuthnRequest(provider);
|
||||||
|
String relayState = "spId=" + provider.getSpEntityId() + "&configurableLoginId=" + provider.getConfigurableLoginId();
|
||||||
|
|
||||||
String authnRequestXml = null;
|
String authnRequestXml = null;
|
||||||
DocumentBuilder builder;
|
String signatureBase64 = null;
|
||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
||||||
try {
|
try {
|
||||||
Signature signature = (Signature) buildXMLObject(Signature.DEFAULT_ELEMENT_NAME);
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
if(provider.isAuthnRequestsSigned()){
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
|
||||||
Credential credential = getCredential(provider.getSigningCert());
|
|
||||||
signature.setSigningCredential(credential);
|
|
||||||
signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
|
|
||||||
signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
|
|
||||||
|
|
||||||
X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory();
|
|
||||||
keyInfoGeneratorFactory.setEmitEntityCertificate(true);
|
|
||||||
KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance();
|
|
||||||
signature.setKeyInfo(keyInfoGenerator.generate(getCredential(provider.getSigningCert())));
|
|
||||||
|
|
||||||
authnRequest.setSignature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder = factory.newDocumentBuilder();
|
|
||||||
Document document = builder.newDocument();
|
Document document = builder.newDocument();
|
||||||
Marshaller out = registry.getMarshallerFactory().getMarshaller(authnRequest);
|
Marshaller out = registry.getMarshallerFactory().getMarshaller(authnRequest);
|
||||||
out.marshall(authnRequest, document);
|
out.marshall(authnRequest, document);
|
||||||
|
|
||||||
if(provider.isAuthnRequestsSigned()) {
|
|
||||||
Signer.signObject(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
authnRequestXml = XmlBuilder.generateXml(document);
|
authnRequestXml = XmlBuilder.generateXml(document);
|
||||||
|
|
||||||
|
if(provider.isAuthnRequestsSigned()) {
|
||||||
|
signatureBase64 = buildSignature(authnRequestXml, relayState, provider.getSigningCert());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (MarshallingException | ParserConfigurationException e) {
|
catch (MarshallingException | ParserConfigurationException e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
return authnRequestXml;
|
|
||||||
|
|
||||||
|
AuthnRequestModel authnRequestModel = new AuthnRequestModel();
|
||||||
|
authnRequestModel.setAuthnRequestXml(authnRequestXml);
|
||||||
|
authnRequestModel.setRelayState(relayState);
|
||||||
|
authnRequestModel.setAlgorithm("http://www.w3.org/2000/09/xmldsig#rsa-sha1");
|
||||||
|
authnRequestModel.setSignature(signatureBase64);
|
||||||
|
return authnRequestModel;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildSignature(String authnRequest, String relayState, CertificateInfo signingCertInfo) throws Exception{
|
||||||
|
|
||||||
|
KeyStore ks = (signingCertInfo.getKeyFormat().getType().equals("JKS")) ? KeyStore.getInstance("JKS") : KeyStore.getInstance("PKCS12");
|
||||||
|
String archivePassword = signingCertInfo.getKeystorePassword();
|
||||||
|
char[] pwdArray = (archivePassword != null) ? archivePassword.toCharArray() : "changeit".toCharArray();
|
||||||
|
ks.load(new FileInputStream(signingCertInfo.getKeystorePath()), pwdArray);
|
||||||
|
PrivateKey pk = (PrivateKey) ks.getKey(signingCertInfo.getAlias(), signingCertInfo.getPassword().toCharArray());
|
||||||
|
|
||||||
|
String signAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
|
||||||
|
String message = "SAMLRequest=" + URLEncoder.encode(authnRequest, "UTF-8")
|
||||||
|
+ "&RelayState=" + URLEncoder.encode(relayState, "UTF-8")
|
||||||
|
+ "&SigAlg=" + URLEncoder.encode(signAlgorithm, "UTF-8");
|
||||||
|
|
||||||
|
String signature = null;
|
||||||
|
try{
|
||||||
|
signature = new String(org.apache.commons.codec.binary.Base64.encodeBase64(sign(message, pk)), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
catch(InvalidKeyException | SignatureException | NoSuchAlgorithmException e){
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] sign(String message, PrivateKey key) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
|
||||||
|
java.security.Signature instance = java.security.Signature.getInstance("SHA1withRSA");
|
||||||
|
instance.initSign(key);
|
||||||
|
instance.update(message.getBytes());
|
||||||
|
return instance.sign();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AuthnRequest buildAuthnRequest(Saml2ConfigurableProvider provider) throws Exception {
|
private static AuthnRequest buildAuthnRequest(Saml2ConfigurableProvider provider) throws Exception {
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package eu.eudat.models.data.saml2;
|
||||||
|
|
||||||
|
public class AuthnRequestModel {
|
||||||
|
|
||||||
|
String authnRequestXml;
|
||||||
|
String relayState;
|
||||||
|
String algorithm;
|
||||||
|
String signature;
|
||||||
|
|
||||||
|
public AuthnRequestModel() {}
|
||||||
|
|
||||||
|
public AuthnRequestModel(String authnRequestXml, String relayState, String algorithm, String signature) {
|
||||||
|
this.authnRequestXml = authnRequestXml;
|
||||||
|
this.relayState = relayState;
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
this.signature = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthnRequestXml() {
|
||||||
|
return authnRequestXml;
|
||||||
|
}
|
||||||
|
public void setAuthnRequestXml(String authnRequestXml) {
|
||||||
|
this.authnRequestXml = authnRequestXml;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRelayState() {
|
||||||
|
return relayState;
|
||||||
|
}
|
||||||
|
public void setRelayState(String relayState) {
|
||||||
|
this.relayState = relayState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return algorithm;
|
||||||
|
}
|
||||||
|
public void setAlgorithm(String algorithm) {
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
public void setSignature(String signature) {
|
||||||
|
this.signature = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface AuthnRequestModel {
|
||||||
|
authnRequestXml: string;
|
||||||
|
relayState: string;
|
||||||
|
algorithm: string;
|
||||||
|
signature: string;
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { ConfigurationService } from './configuration/configuration.service';
|
||||||
import { BaseHttpService } from './http/base-http.service';
|
import { BaseHttpService } from './http/base-http.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { BaseComponent } from '@common/base/base.component';
|
import { BaseComponent } from '@common/base/base.component';
|
||||||
|
import { AuthnRequestModel } from '../model/saml2/AuthnRequestModel';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SamlLoginService extends BaseComponent {
|
export class SamlLoginService extends BaseComponent {
|
||||||
|
@ -27,8 +28,8 @@ export class SamlLoginService extends BaseComponent {
|
||||||
return routeParams.has('spId') ? routeParams.get('spId') : '';
|
return routeParams.has('spId') ? routeParams.get('spId') : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthnRequest(configurableLoginId: string): Observable<string> {
|
getAuthnRequest(configurableLoginId: string): Observable<AuthnRequestModel> {
|
||||||
return this.http.get<string>(this.actionUrl + 'authnRequest/' + configurableLoginId, { headers: this.headers });
|
return this.http.get<AuthnRequestModel>(this.actionUrl + 'authnRequest/' + configurableLoginId, { headers: this.headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -90,25 +90,18 @@ export class ConfigurableLoginComponent extends BaseComponent implements OnInit
|
||||||
this.samlLoginService.getAuthnRequest(provider.configurableLoginId).pipe(takeUntil(this._destroyed))
|
this.samlLoginService.getAuthnRequest(provider.configurableLoginId).pipe(takeUntil(this._destroyed))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
authenticationRequest => {
|
authenticationRequest => {
|
||||||
const uint = new Uint8Array(authenticationRequest.length);
|
const uint = new Uint8Array(authenticationRequest.authnRequestXml.length);
|
||||||
for (let i = 0, j = authenticationRequest.length; i < j; ++i) {
|
for (let i = 0, j = authenticationRequest.authnRequestXml.length; i < j; ++i) {
|
||||||
uint[i] = authenticationRequest.charCodeAt(i);
|
uint[i] = authenticationRequest.authnRequestXml.charCodeAt(i);
|
||||||
}
|
}
|
||||||
const base64String = btoa(pk.deflateRaw(uint, { to: 'string' }));
|
const base64String = btoa(pk.deflateRaw(uint, { to: 'string' }));
|
||||||
const relayState = this.buildRelayState(provider.spEntityId, provider.configurableLoginId);
|
const url = provider.idpUrl + '?SAMLRequest=' + encodeURIComponent(base64String) + '&RelayState=' + encodeURIComponent(authenticationRequest.relayState) + '&SigAlg=' + encodeURIComponent(authenticationRequest.algorithm) + '&Signature=' + encodeURIComponent(authenticationRequest.signature);
|
||||||
const url = provider.idpUrl + '?RelayState=' + relayState + '&SAMLRequest=' + encodeURIComponent(base64String);
|
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildRelayState(spId: string, configurableLoginId: string): string {
|
|
||||||
let uri = 'spId=' + spId;
|
|
||||||
uri += '&configurableLoginId=' + configurableLoginId;
|
|
||||||
return encodeURIComponent(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public configurableLoginUser(code: string, state: string) {
|
public configurableLoginUser(code: string, state: string) {
|
||||||
if (state !== (<Oauth2ConfigurableProvider>this.provider).state) {
|
if (state !== (<Oauth2ConfigurableProvider>this.provider).state) {
|
||||||
this.router.navigate(['/login'])
|
this.router.navigate(['/login'])
|
||||||
|
|
Loading…
Reference in New Issue