refactor signing of saml2 authentication request

This commit is contained in:
Bernaldo Mihasi 2023-06-21 11:51:26 +03:00
parent 1a2a93a95f
commit b6505cda4e
6 changed files with 112 additions and 44 deletions

View File

@ -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."));

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -0,0 +1,6 @@
export interface AuthnRequestModel {
authnRequestXml: string;
relayState: string;
algorithm: string;
signature: string;
}

View File

@ -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 });
} }
} }

View File

@ -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'])