From a4004e8f82c5d9f734b36fccdd8b8bdd5d508fa9 Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Fri, 14 Jun 2024 17:26:37 +0200 Subject: [PATCH 01/10] test branch for IDP renew. Do not merge --- pom.xml | 2 +- .../ti/i4mi/mag/xua/AuthRequestConverter.java | 15 +- .../i4mi/mag/xua/AuthResponseConverter.java | 7 +- .../i4mi/mag/xua/AuthenticationRequest.java | 2 + .../ti/i4mi/mag/xua/Iti71RouteBuilder.java | 6 +- .../ti/i4mi/mag/xua/OAuth2TokenResponse.java | 2 + .../xua/ScSAMLRenewSecurityTokenBuilder.java | 460 ++++++++++++++++++ .../ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java | 4 + .../ch/bfh/ti/i4mi/mag/xua/TokenRenew.java | 103 ++++ .../i4mi/mag/xua/TokenRenewRouteBuilder.java | 73 +++ 10 files changed, 666 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java create mode 100644 src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java create mode 100644 src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java diff --git a/pom.xml b/pom.xml index a555320b..af2dc82a 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 1.11 yyyy-MM-dd HH:mm:ss - 11 + 15 3.10.1 diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthRequestConverter.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthRequestConverter.java index 8e9c697d..e7be251e 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthRequestConverter.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthRequestConverter.java @@ -16,12 +16,15 @@ package ch.bfh.ti.i4mi.mag.xua; +import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Map; import javax.ws.rs.BadRequestException; import org.apache.camel.Body; +import org.apache.camel.ExchangeProperty; import org.apache.camel.Header; import org.apache.camel.Headers; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +34,7 @@ import org.springframework.stereotype.Component; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import net.sourceforge.plantuml.utils.Log; /** * Convert OAuth2 request to SAML Assertion Request @@ -86,7 +90,7 @@ public AuthenticationRequest buildAuthenticationRequest( return request; } - public AssertionRequest buildAssertionRequest(@Header("response_type") String responseType, @Header("oauthrequest") AuthenticationRequest request) throws AuthException { + public AssertionRequest buildAssertionRequest(@Header("response_type") String responseType, @ExchangeProperty("oauthrequest") AuthenticationRequest request) throws AuthException { if (!"code".equals(responseType)) throw new AuthException(400, "invalid_request", "response_type must be 'code'"); @@ -107,6 +111,15 @@ private String decode(String in) { public AssertionRequest buildAssertionRequestFromIdp(@Body String authorization, @Header("scope") String scope) throws AuthException { return buildAssertionRequestInternal(authorization, scope); } + + public AssertionRequest buildAssertionRequestFromToken(@Body String authorization, @Header("scope") String scope) throws AuthException { + System.out.println("BODY:"+authorization); + try { + authorization = new String(Base64.getDecoder().decode(authorization), "UTF-8"); + } catch (UnsupportedEncodingException e) {} + System.out.println("BODY2:"+authorization); + return buildAssertionRequestInternal(authorization, scope); + } private AssertionRequest buildAssertionRequestInternal(Object authorization, String scope) throws AuthException { diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthResponseConverter.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthResponseConverter.java index 46fb1313..774307f1 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthResponseConverter.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthResponseConverter.java @@ -21,6 +21,7 @@ import java.net.URLEncoder; import org.apache.camel.Body; +import org.apache.camel.ExchangeProperty; import org.apache.camel.Header; import org.apache.commons.lang.RandomStringUtils; import org.apache.cxf.binding.soap.SoapFault; @@ -37,7 +38,7 @@ public class AuthResponseConverter { @Autowired private Cache codeToToken; - public String handle(@Body String assertion, @Header("oauthrequest") AuthenticationRequest request) throws UnsupportedEncodingException { + public String handle(@Body String assertion, @ExchangeProperty("oauthrequest") AuthenticationRequest request) throws UnsupportedEncodingException { String returnurl = request.getRedirect_uri(); String state = request.getState(); @@ -54,7 +55,7 @@ public String handle(@Body String assertion, @Header("oauthrequest") Authenticat return returnurl; } - public String handleerror(@Header("oauthrequest") AuthenticationRequest request, @Body AuthException exception) throws UnsupportedEncodingException{ + public String handleerror(@ExchangeProperty("oauthrequest") AuthenticationRequest request, @Body AuthException exception) throws UnsupportedEncodingException{ String returnurl = request.getRedirect_uri(); String state = request.getState(); @@ -68,7 +69,7 @@ public String handleerror(@Header("oauthrequest") AuthenticationRequest request, return returnurl; } - public String handlesoaperror(@Header("oauthrequest") AuthenticationRequest request, @Body SoapFault exception) throws UnsupportedEncodingException{ + public String handlesoaperror(@ExchangeProperty("oauthrequest") AuthenticationRequest request, @Body SoapFault exception) throws UnsupportedEncodingException{ String returnurl = request.getRedirect_uri(); String state = request.getState(); diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthenticationRequest.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthenticationRequest.java index 6f3e039b..cf99c030 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthenticationRequest.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthenticationRequest.java @@ -39,6 +39,8 @@ public class AuthenticationRequest { private String assertion; + private String idpAssertion; + private String code_challenge; } diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/Iti71RouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/Iti71RouteBuilder.java index 36ac4843..9f0b9c49 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/Iti71RouteBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/Iti71RouteBuilder.java @@ -64,7 +64,7 @@ public void configure() throws Exception { from(String.format("servlet://%s?matchOnUriPrefix=true", AUTHORIZE_PATH)).routeId("iti71") .doTry() - .setHeader("oauthrequest").method(converter, "buildAuthenticationRequest") + .setProperty("oauthrequest").method(converter, "buildAuthenticationRequest") // end spring security session in order to prevent use of already expired // identity provider assertions cached in spring security session @@ -72,8 +72,8 @@ public void configure() throws Exception { .process(Utils.endHttpSession()) .bean(AuthRequestConverter.class, "buildAssertionRequest") - .bean(Iti40RequestGenerator.class, "buildAssertion") - + .bean(TokenRenew.class, "keepIdpAssertion") + .bean(Iti40RequestGenerator.class, "buildAssertion") .removeHeaders("*","oauthrequest") .setHeader(CxfConstants.OPERATION_NAME, constant("Issue")) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/OAuth2TokenResponse.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/OAuth2TokenResponse.java index cb013a8a..9c27f05a 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/OAuth2TokenResponse.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/OAuth2TokenResponse.java @@ -29,6 +29,8 @@ public class OAuth2TokenResponse { private String access_token; + private String refresh_token; + private String token_type; private long expires_in; diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java new file mode 100644 index 00000000..c6fe2aea --- /dev/null +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java @@ -0,0 +1,460 @@ +package ch.bfh.ti.i4mi.mag.xua; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.TrustManager; +import javax.xml.crypto.dsig.CanonicalizationMethod; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.camel.Body; +import org.apache.camel.Exchange; +import org.apache.camel.ExchangeProperty; +import org.apache.camel.Processor; +import org.apache.camel.http.common.HttpMessage; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.URI; +import org.apache.commons.httpclient.URIException; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; +import org.apache.cxf.staxutils.StaxUtils; +import org.apache.http.impl.client.HttpClients; +import org.joda.time.DateTime; +import org.opensaml.Configuration; +import org.opensaml.common.SignableSAMLObject; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.ws.soap.client.BasicSOAPMessageContext; +import org.opensaml.ws.soap.client.http.HttpClientBuilder; +import org.opensaml.ws.soap.client.http.HttpSOAPClient; +import org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory; +import org.opensaml.ws.soap.common.SOAPException; +import org.opensaml.ws.soap.soap11.Envelope; +import org.opensaml.ws.soap.soap11.Header; +import org.opensaml.ws.soap.util.SOAPHelper; +import org.opensaml.ws.wssecurity.BinarySecurityToken; +import org.opensaml.ws.wssecurity.Created; +import org.opensaml.ws.wssecurity.EncodedString; +import org.opensaml.ws.wssecurity.Expires; +import org.opensaml.ws.wssecurity.Security; +import org.opensaml.ws.wssecurity.SecurityTokenReference; +import org.opensaml.ws.wssecurity.Timestamp; +import org.opensaml.ws.wssecurity.WSSecurityConstants; +import org.opensaml.ws.wssecurity.util.WSSecurityHelper; +import org.opensaml.ws.wstrust.RenewTarget; +import org.opensaml.ws.wstrust.RequestSecurityToken; +import org.opensaml.ws.wstrust.RequestType; +import org.opensaml.ws.wstrust.TokenType; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.XMLObjectBuilderFactory; +import org.opensaml.xml.io.Marshaller; +import org.opensaml.xml.io.MarshallingException; +import org.opensaml.xml.io.Unmarshaller; +import org.opensaml.xml.io.UnmarshallingException; +import org.opensaml.xml.parse.BasicParserPool; +import org.opensaml.xml.security.CriteriaSet; +import org.opensaml.xml.security.credential.Credential; +import org.opensaml.xml.security.credential.UsageType; +import org.opensaml.xml.security.criteria.EntityIDCriteria; +import org.opensaml.xml.security.criteria.UsageCriteria; +import org.opensaml.xml.security.keyinfo.KeyInfoHelper; +import org.opensaml.xml.security.x509.BasicX509Credential; +import org.opensaml.xml.security.x509.X509Credential; + +import org.opensaml.xml.signature.DocumentInternalIDContentReference; +import org.opensaml.xml.signature.KeyInfo; +import org.opensaml.xml.signature.SignableXMLObject; +import org.opensaml.xml.signature.Signature; +import org.opensaml.xml.signature.SignatureConstants; +import org.opensaml.xml.signature.SignatureException; +import org.opensaml.xml.signature.Signer; +import org.opensaml.xml.signature.X509Data; +import org.opensaml.xml.signature.X509IssuerSerial; +import org.opensaml.xml.signature.impl.X509IssuerSerialBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.saml.context.SAMLContextProvider; +import org.springframework.security.saml.context.SAMLMessageContext; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.trust.X509KeyManager; +import org.springframework.security.saml.trust.X509TrustManager; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.opensaml.common.impl.SecureRandomIdentifierGenerator; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.security.MetadataCriteria; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import lombok.extern.slf4j.Slf4j; + +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.util.Base64; + +@Slf4j +@Component +public class ScSAMLRenewSecurityTokenBuilder { + + private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; + + private static SecureRandomIdentifierGenerator secureRandomIdGenerator; + + private String destination; + + static { + try { + secureRandomIdGenerator = new SecureRandomIdentifierGenerator(); + } catch (NoSuchAlgorithmException e) { + log.error(e.getMessage(), e); + } + } + + + @Autowired + private KeyManager keyManager; + + @Autowired + private SAMLContextProvider contextProvider; + + //@Autowired + //private HttpClient httpClient; + + @Value("${mag.iua.idp.key-alias}") + private String keyAlias; + + public Element addSecurityHeader(String input) throws XMLStreamException { + return (Element) StaxUtils.read(new StringReader(input)).getDocumentElement(); + } + + public static Processor keepRequest() { + return exchange -> { + HttpServletRequest request = exchange.getIn(HttpMessage.class).getRequest(); + HttpServletResponse response = exchange.getIn(HttpMessage.class).getResponse(); + exchange.setProperty("request", request); + exchange.setProperty("response", response); + }; + } + + public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest request, @ExchangeProperty("request") HttpServletRequest hrequest, @ExchangeProperty("response") HttpServletResponse hresponse) throws Exception { + + SAMLMessageContext context = contextProvider.getLocalAndPeerEntity(hrequest, hresponse); + + Object token = request.getSamlToken(); + + if (token instanceof String) { + token = addSecurityHeader((String) token); + } + + RequestSecurityToken renewSecurityTokenRequestMessage; + + renewSecurityTokenRequestMessage = createSAMLObject(RequestSecurityToken.class); + RequestType requestType = createSAMLObject(RequestType.class); + requestType.setValue(RequestType.RENEW); + renewSecurityTokenRequestMessage.getUnknownXMLObjects().add(requestType); + TokenType tokenType = createSAMLObject(TokenType.class); + tokenType.setValue("http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"); + renewSecurityTokenRequestMessage.getUnknownXMLObjects().add(tokenType); + + RenewTarget renewTarget = createSAMLObject(RenewTarget.class); + renewTarget.setUnknownXMLObject(unmarshall((Element) token)); + renewSecurityTokenRequestMessage.getUnknownXMLObjects().add(renewTarget); + + + Envelope envelope = createSAMLObject(Envelope.class); + // The body element contains the actual SAML message. + org.opensaml.ws.soap.soap11.Body body = createSAMLObject(org.opensaml.ws.soap.soap11.Body.class); + + String ref = randomId(); + WSSecurityHelper.addWSUId(body, ref); + + //WSSecuritySupport.addWSUId(body, securityModule.buildId()); + body.getUnknownXMLObjects().add(renewSecurityTokenRequestMessage); + envelope.setBody(body); + // Application-specific context information (for example, security or encryption information). + Header header = createSAMLObject(Header.class); + envelope.setHeader(header); + Security security = createSAMLObject(Security.class); + header.getUnknownXMLObjects().add(security); + // Security timestamp defining the lifetime of the message. + Timestamp timestamp = createSAMLObject(Timestamp.class); + timestamp.setWSUId(randomId()); + Created created = createSAMLObject(Created.class); + created.setDateTime(DateTime.now()); + timestamp.setCreated(created); + Expires expires = createSAMLObject(Expires.class); + expires.setDateTime(created.getDateTime().plusSeconds(5*60)); + timestamp.setExpires(expires); + security.getUnknownXMLObjects().add(timestamp); + + // Binary security token is the base64 encoded representation of an X.509 public certificate. + //KeyStore.PrivateKeyEntry privateKeyEntry = securityModule.findPrivateKey(); + //X509Certificate publicCertificate = (X509Certificate) privateKeyEntry.getCertificate(); + + X509Certificate publicCertificate = keyManager.getCertificate(keyAlias); + log.info("CERT NOT NULL:"+publicCertificate.toString()); + + BinarySecurityToken binarySecurityToken = createSAMLObject(BinarySecurityToken.class); + binarySecurityToken.setEncodingType(EncodedString.ENCODING_TYPE_BASE64_BINARY); + // Why not use BinarySecurityToken.setValueType()? It was not being valued in the XML by the marshaller. + binarySecurityToken.getUnknownAttributes().put(new QName("ValueType"), WSSecurityConstants.X509_V3); + binarySecurityToken.setValue(encode(publicCertificate)); + security.getUnknownXMLObjects().add(binarySecurityToken); + // Digital signature to verify the identity of the signer. + + + Signature signature = createSAMLObject(Signature.class); + Credential cred = keyManager.getCredential(keyAlias); + signature.setSigningCredential(cred); + signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512); + signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + DocumentInternalIDContentReference timestampReference = + new DocumentInternalIDContentReference(timestamp.getWSUId()); + timestampReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); + + signature.getContentReferences().add(timestampReference); + + DocumentInternalIDContentReference bodyReference = + new DocumentInternalIDContentReference(WSSecurityHelper.getWSUId(body)); + bodyReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); + signature.getContentReferences().add(bodyReference); + KeyInfo keyInfo = createSAMLObject(KeyInfo.class); + SecurityTokenReference securityTokenReference = createSAMLObject(SecurityTokenReference.class); + keyInfo.getXMLObjects().add(securityTokenReference); + + + X509Data x509Data = createSAMLObject(X509Data.class); + + x509Data.getX509IssuerSerials().add(KeyInfoHelper.buildX509IssuerSerial( + publicCertificate.getIssuerX500Principal().getName(), publicCertificate.getSerialNumber())); + securityTokenReference.getUnknownXMLObjects().add(x509Data); + + signature.setKeyInfo(keyInfo); + security.getUnknownXMLObjects().add(signature); + + marshall(envelope); + //sign(signature); + //securityModule.signObject(envelope, signature); + + // Build the W3C DOM representing the SOAP message. + Element elem = marshall(envelope); + + log.info(StaxUtils.toString(elem)); + + Envelope result = send(renewEndpointUrl, context, envelope); + NodeList lst = result.getBody().getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); + + Node node = lst.item(0); + log.debug("NODE: "+node.toString()); + + StringWriter writer = new StringWriter(); + TransformerFactory factory = TransformerFactory.newInstance(); + //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "all"); + //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "all"); + + Transformer transformer = factory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.transform(new DOMSource(node), new StreamResult(writer)); + + String xml = writer.toString(); + + return xml; + } + + public static String encode(final X509Certificate certificate) throws CertificateEncodingException { + final Base64.Encoder encoder = Base64.getEncoder(); + + final byte[] rawCrtText = certificate.getEncoded(); + final String encodedCertText = new String(encoder.encode(rawCrtText)); + + return encodedCertText; + } + + public static void sign(Signature signature) { + + try { + Signer.signObject(signature); + } catch (SignatureException e) { + throw new RuntimeException(e); + } + } + + public static T createSAMLObject(final Class clazz) { + T object = null; + try { + XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory(); + QName defaultElementName; + + try { + defaultElementName = (QName)clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null); + } catch (NoSuchFieldException e) { + defaultElementName = (QName)clazz.getDeclaredField("ELEMENT_NAME").get(null); + } + + object = (T)builderFactory.getBuilder(defaultElementName).buildObject(defaultElementName); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Could not create SAML object"); + } catch (NoSuchFieldException e) { + log.error("Exception", e); + log.error("CLASSNAME: "+clazz.getName()); + throw new IllegalArgumentException("Could not create SAML object"); + } + + return object; + } + + public Element marshall(XMLObject object) { + if (object instanceof SignableSAMLObject && ((SignableSAMLObject) object).isSigned() + && object.getDOM() != null) { + return object.getDOM(); + } else { + try { + Marshaller out = Configuration.getMarshallerFactory().getMarshaller(object); + out.marshall(object); + return object.getDOM(); + } catch (MarshallingException e) { + return null; + } + } + } + + public XMLObject unmarshall(Element input) throws UnmarshallingException { + Unmarshaller un = Configuration.getUnmarshallerFactory().getUnmarshaller(input); + return un.unmarshall(input); + } + + + public static String randomId() { + return secureRandomIdGenerator.generateIdentifier(); + } + + + + + + private Envelope send(String targetUrl, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, + MarshallingException, SignatureException, IllegalAccessException, org.opensaml.xml.security.SecurityException, URIException, MessageEncodingException { + HttpClientBuilder clientBuilder = new HttpClientBuilder(); + //clientBuilder.setHttpsProtocolSocketFactory(SSLProtocolSocketFactory.getSocketFactory()); + HttpClient httpClient = clientBuilder.buildClient(); + httpClient.setHostConfiguration(getHostConfiguration(new URI(targetUrl, "UTF-8"), context, httpClient)); + /*clientBuilder.setHttpsProtocolSocketFactory( + new TLSProtocolSocketFactory(keyManager, trustManager)); + */ + HttpSOAPClient soapClient = new HttpSOAPClient(httpClient, new BasicParserPool()); + + BasicSOAPMessageContext soapContext = new BasicSOAPMessageContext(); + soapContext.setOutboundMessage(envelope); + log.info("SEND!"); + soapClient.send(targetUrl, soapContext); + log.info("POST-SEND!"); + + Envelope soapResponse = (Envelope)soapContext.getInboundMessage(); + + return soapResponse; + } + + protected HostConfiguration getHostConfiguration(URI uri, SAMLMessageContext context, HttpClient httpClient) throws MessageEncodingException { + + try { + + HostConfiguration hc = httpClient.getHostConfiguration(); + + if (hc != null) { + // Clone configuration from the HTTP Client object + log.info("EXIST"); + hc = new HostConfiguration(hc); + } else { + // Create brand new configuration when there are no defaults + log.info("NOT EXIST"); + hc = new HostConfiguration(); + } + + if (uri.getScheme().equalsIgnoreCase("http")) { + + log.info("Using HTTP configuration"); + hc.setHost(uri); + + } else { + + log.info("Using HTTPS configuration"); + log.info("PEER="+context.getPeerEntityId()); + CriteriaSet criteriaSet = new CriteriaSet(); + //criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); + //criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); + //criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); + + X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); + + //X509KeyManager manager1 = new X509KeyManager(context.getLocalSSLCredential()); + //log.info("TLS NOT NULL:"+keyManager.getCredential("hintls").toString()); + + X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential("hintls")); + + HostnameVerifier hostnameVerifier = context.getLocalSSLHostnameVerifier(); + + ProtocolSocketFactory socketFactory = getSSLSocketFactory(context, manager, trustManager, hostnameVerifier); + Protocol protocol = new Protocol("https", socketFactory, 443); + hc.setHost(uri.getHost(), uri.getPort(), protocol); + + log.info("SET-HOST: "+uri.getHost()+" proto="+protocol.toString()); + } + + return hc; + + } catch (URIException e) { + throw new MessageEncodingException("Error parsing remote location URI", e); + } + + } + + /** + * Method returns SecureProtocolSocketFactory used to connect to create SSL connections for artifact resolution. + * By default we create instance of org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory. + * + * @param context current SAML context + * @param manager keys used for client authentication + * @param trustManager trust manager for server verification + * @param hostnameVerifier verifier for server hostname, or null + * @return socket factory + */ + protected SecureProtocolSocketFactory getSSLSocketFactory(SAMLMessageContext context, X509KeyManager manager, X509TrustManager trustManager, HostnameVerifier hostnameVerifier) { + if (isHostnameVerificationSupported()) { + return new TLSProtocolSocketFactory(manager, trustManager, hostnameVerifier); + } else { + return new TLSProtocolSocketFactory(manager, trustManager); + } + } + + /** + * Check for the latest OpenSAML library. Support for HostnameVerification was added in openws-1.5.1 and + * customers might use previous versions of OpenSAML. + * + * @return true when OpenSAML library support hostname verification + */ + protected boolean isHostnameVerificationSupported() { + try { + TLSProtocolSocketFactory.class.getConstructor(javax.net.ssl.X509KeyManager.class, javax.net.ssl.X509TrustManager.class, javax.net.ssl.HostnameVerifier.class); + return true; + } catch (NoSuchMethodException e) { + log.warn("HostnameVerification is not supported, update your OpenSAML libraries"); + return false; + } + } + +} \ No newline at end of file diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java index aed4d822..a5684775 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java @@ -95,8 +95,12 @@ public OAuth2TokenResponse handle( String encoded = Base64.getEncoder().encodeToString(assertion.getBytes("UTF-8")); log.debug("Encoded token: "+encoded); + String idpAssertion = request.getIdpAssertion(); + String encodedIdp = Base64.getEncoder().encodeToString(idpAssertion.getBytes("UTF-8")); + OAuth2TokenResponse result = new OAuth2TokenResponse(); result.setAccess_token(encoded); + result.setRefresh_token(encodedIdp); result.setExpires_in(defaultTimeout); result.setScope(request.getScope()); result.setToken_type("Bearer" /*request.getToken_type()*/); diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java new file mode 100644 index 00000000..0b737221 --- /dev/null +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java @@ -0,0 +1,103 @@ +package ch.bfh.ti.i4mi.mag.xua; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.UUID; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPElement; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; +import javax.xml.stream.XMLStreamException; + +import org.apache.camel.Body; +import org.apache.camel.ExchangeProperty; +import org.apache.camel.Header; +import org.apache.cxf.staxutils.StaxUtils; +import org.apache.xml.security.utils.XMLUtils; +import org.opensaml.xml.util.XMLHelper; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +import javax.xml.crypto.dsig.CanonicalizationMethod; +import javax.xml.namespace.QName; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; + + + +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class TokenRenew { + + private String BASE_MSG() { return """ + + + + + http://docs.oasis-open.org/ws-sx/ws-trust/200512/Renew + http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0 + + + + + """; } + + public Element addSecurityHeader(String input) throws XMLStreamException { + return (Element) StaxUtils.read(new StringReader(input)).getDocumentElement(); + } + + public SOAPMessage buildRenewRequest(@Body AssertionRequest request) throws SOAPException, IOException, XMLStreamException, AuthException { + + Object token = request.getSamlToken(); + if (token == null) throw new AuthException(400, "server_error", "No SAML token found"); + log.info(token.getClass().getSimpleName()); + if (token instanceof String && token.toString().startsWith("")) token = token.toString().substring("".length()); + log.info("Decoded IDP Token:"+token); + + MessageFactory factory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage message = factory.createMessage(new MimeHeaders(), new ByteArrayInputStream(BASE_MSG().getBytes(Charset.forName("UTF-8")))); + log.info("BASE MSG"); + // message.getSOAPHeader().addChildElement("MessageID","wsa","http://www.w3.org/2005/08/addressing").addTextNode(UUID.randomUUID().toString()); + + SOAPElement renewTarget = (SOAPElement) message.getSOAPBody().getElementsByTagNameNS("http://docs.oasis-open.org/ws-sx/ws-trust/200512", "RenewTarget").item(0); + log.info("RENEW TG"); + Element elem; + if (token instanceof String) { + elem = addSecurityHeader(token.toString()); + } else { + elem = ((Element) token); + } + Node node = message.getSOAPBody().getOwnerDocument().importNode(elem, true); + log.info("RENEW NODE"); + renewTarget.appendChild(node); + + log.info("Sending IDP Renew Request: "+message.toString()); + + message.saveChanges(); + + return message; + } + + public AssertionRequest buildAssertionRequest(@Header("assertionRequest") AssertionRequest assertionRequest, @Body String renewedIdpAssertion) { + assertionRequest.setSamlToken(renewedIdpAssertion); + return assertionRequest; + } + + public AssertionRequest keepIdpAssertion(@ExchangeProperty("oauthrequest") AuthenticationRequest authRequest, @Body AssertionRequest assertionRequest) { + String idpAssertion = XMLHelper.nodeToString((Node) assertionRequest.getSamlToken()); + authRequest.setIdpAssertion(idpAssertion); + return assertionRequest; + } +} diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java new file mode 100644 index 00000000..29a644ff --- /dev/null +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java @@ -0,0 +1,73 @@ +package ch.bfh.ti.i4mi.mag.xua; + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.cxf.common.message.CxfConstants; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class TokenRenewRouteBuilder extends RouteBuilder { + + public static final String RENEW_PATH = "renew"; + + @Value("${mag.iua.ap.url}") + private String assertionEndpointUrl; + + @Value("${mag.iua.ap.wsdl}") + private String wsdl; + + @Value("${mag.iua.ap.endpoint-name:}") + private String endpointName; + + + @Value("${mag.client-ssl.enabled}") + private boolean clientSsl; + + + @Override + public void configure() throws Exception { + + final String assertionEndpoint = String.format("cxf://%s?dataFormat=CXF_MESSAGE&wsdlURL=%s&loggingFeatureEnabled=true"+ + ((endpointName!=null && endpointName.length()>0) ? "&endpointName="+endpointName : "")+ + "&inInterceptors=#soapResponseLogger" + + "&inFaultInterceptors=#soapResponseLogger"+ + "&outInterceptors=#soapRequestLogger" + + "&outFaultInterceptors=#soapRequestLogger"+ + (clientSsl ? "&sslContextParameters=#sslContext" : ""), + assertionEndpointUrl, wsdl); + + log.info("Assertion-URL: "+assertionEndpoint); + + from(String.format("servlet://%s?httpMethodRestrict=POST&matchOnUriPrefix=true", RENEW_PATH)) + .routeId("renewEndpoint") + .process(ScSAMLRenewSecurityTokenBuilder.keepRequest()) + .doTry() + .bean(AuthRequestConverter.class, "buildAssertionRequestFromToken") + .setHeader("assertionRequest", body()) + .bean(ScSAMLRenewSecurityTokenBuilder.class, "requestRenewToken") + + .bean(AssertionExtractor.class) + .bean(TokenRenew.class, "buildAssertionRequest") + .bean(Iti40RequestGenerator.class, "buildAssertion") + .removeHeaders("*", "scope") + .setHeader(CxfConstants.OPERATION_NAME, + constant("Issue")) + .setHeader(CxfConstants.OPERATION_NAMESPACE, + constant("http://docs.oasis-open.org/ws-sx/ws-trust/200512/wsdl")) + .to(assertionEndpoint) + .bean(AssertionExtractor.class) + .bean(TokenEndpoint.class, "handleFromIdp") + + .doCatch(AuthException.class) + .setBody(simple("${exception}")) + .bean(TokenEndpoint.class, "handleError") + .setHeader(Exchange.HTTP_RESPONSE_CODE, simple("${exception.status}")) + .end() + .removeHeaders("*", Exchange.HTTP_RESPONSE_CODE) + .setHeader("Cache-Control", constant("no-store")) + .setHeader("Pragma", constant("no-cache")) + .marshal() + .json(); + } +} From 82adaa4081dd027d020bf4f2323c9904915828aa Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Sat, 15 Jun 2024 11:41:51 +0200 Subject: [PATCH 02/10] update to idp renew --- .../AssertionFromIdpTokenRouteBuilder.java | 2 +- .../xua/ScSAMLRenewSecurityTokenBuilder.java | 20 ++++++++++++++----- .../ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java | 7 ++++++- .../ch/bfh/ti/i4mi/mag/xua/TokenRenew.java | 11 +++++++++- .../i4mi/mag/xua/TokenRenewRouteBuilder.java | 5 +++-- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AssertionFromIdpTokenRouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AssertionFromIdpTokenRouteBuilder.java index 3cf542e6..8e15553f 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AssertionFromIdpTokenRouteBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AssertionFromIdpTokenRouteBuilder.java @@ -60,7 +60,7 @@ public void configure() throws Exception { // identity provider assertions cached in spring security session // this is unrelated to the IDP provider cookie set by the IDP itself .process(Utils.endHttpSession()) - + .setProperty("oauthrequest").method(TokenRenew.class, "emptyAuthRequest") .bean(AuthRequestConverter.class, "buildAssertionRequestFromIdp") .bean(Iti40RequestGenerator.class, "buildAssertion") .removeHeaders("*", "scope") diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java index c6fe2aea..b1864176 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java @@ -93,6 +93,7 @@ import org.w3c.dom.NodeList; import org.opensaml.common.impl.SecureRandomIdentifierGenerator; import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.binding.encoding.HTTPSOAP11Encoder; import org.opensaml.saml2.metadata.IDPSSODescriptor; import org.opensaml.security.MetadataCriteria; @@ -111,7 +112,8 @@ @Component public class ScSAMLRenewSecurityTokenBuilder { - private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; + //private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; + private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; private static SecureRandomIdentifierGenerator secureRandomIdGenerator; @@ -226,12 +228,13 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re DocumentInternalIDContentReference timestampReference = new DocumentInternalIDContentReference(timestamp.getWSUId()); timestampReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); - + timestampReference.setDigestAlgorithm("http://www.w3.org/2001/04/xmlenc#sha256"); signature.getContentReferences().add(timestampReference); DocumentInternalIDContentReference bodyReference = new DocumentInternalIDContentReference(WSSecurityHelper.getWSUId(body)); - bodyReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); + bodyReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); + bodyReference.setDigestAlgorithm("http://www.w3.org/2001/04/xmlenc#sha256"); signature.getContentReferences().add(bodyReference); KeyInfo keyInfo = createSAMLObject(KeyInfo.class); SecurityTokenReference securityTokenReference = createSAMLObject(SecurityTokenReference.class); @@ -248,12 +251,17 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re security.getUnknownXMLObjects().add(signature); marshall(envelope); - //sign(signature); + sign(signature); //securityModule.signObject(envelope, signature); // Build the W3C DOM representing the SOAP message. Element elem = marshall(envelope); + //context.setOutboundSAMLMessage(null); + //HTTPSOAP11Encoder encode = new HTTPSOAP11Encoder(); + //encode.encode(context); + + log.info(StaxUtils.toString(elem)); Envelope result = send(renewEndpointUrl, context, envelope); @@ -359,7 +367,9 @@ private Envelope send(String targetUrl, SAMLMessageContext context, Envelope env HttpSOAPClient soapClient = new HttpSOAPClient(httpClient, new BasicParserPool()); BasicSOAPMessageContext soapContext = new BasicSOAPMessageContext(); - soapContext.setOutboundMessage(envelope); + soapContext.setOutboundMessage(envelope); + log.info("ISSUER="+soapContext.getOutboundMessageIssuer()); + soapContext.setOutboundMessageIssuer("https://test.ahdis.ch"); log.info("SEND!"); soapClient.send(targetUrl, soapContext); log.info("POST-SEND!"); diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java index a5684775..7f836ad2 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java @@ -23,6 +23,7 @@ import java.util.Base64; import org.apache.camel.Body; +import org.apache.camel.ExchangeProperty; import org.apache.camel.Header; import org.ehcache.Cache; import org.springframework.beans.factory.annotation.Autowired; @@ -121,12 +122,16 @@ public static String sha256ThenBase64(String input) throws AuthException { } } - public OAuth2TokenResponse handleFromIdp(@Body String assertion, @Header("scope") String scope) throws UnsupportedEncodingException, AuthException { + public OAuth2TokenResponse handleFromIdp(@ExchangeProperty("oauthrequest") AuthenticationRequest authRequest, @Body String assertion, @Header("scope") String scope) throws UnsupportedEncodingException, AuthException { String encoded = Base64.getEncoder().encodeToString(assertion.getBytes("UTF-8")); OAuth2TokenResponse result = new OAuth2TokenResponse(); result.setAccess_token(encoded); + + String idpAssertion = authRequest.getIdpAssertion(); + String encodedIdp = Base64.getEncoder().encodeToString(idpAssertion.getBytes("UTF-8")); + result.setRefresh_token(encodedIdp); result.setExpires_in(defaultTimeout); result.setScope(scope); result.setToken_type("Bearer" /*request.getToken_type()*/); diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java index 0b737221..069e75c4 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java @@ -95,8 +95,17 @@ public AssertionRequest buildAssertionRequest(@Header("assertionRequest") Assert return assertionRequest; } + public AuthenticationRequest emptyAuthRequest() { + return new AuthenticationRequest(); + } + public AssertionRequest keepIdpAssertion(@ExchangeProperty("oauthrequest") AuthenticationRequest authRequest, @Body AssertionRequest assertionRequest) { - String idpAssertion = XMLHelper.nodeToString((Node) assertionRequest.getSamlToken()); + String idpAssertion; + if (assertionRequest.getSamlToken() instanceof String) { + idpAssertion = (String) assertionRequest.getSamlToken(); + } else { + idpAssertion = XMLHelper.nodeToString((Node) assertionRequest.getSamlToken()); + } authRequest.setIdpAssertion(idpAssertion); return assertionRequest; } diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java index 29a644ff..994b5e5a 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java @@ -42,13 +42,14 @@ public void configure() throws Exception { from(String.format("servlet://%s?httpMethodRestrict=POST&matchOnUriPrefix=true", RENEW_PATH)) .routeId("renewEndpoint") .process(ScSAMLRenewSecurityTokenBuilder.keepRequest()) + .setProperty("oauthrequest").method(TokenRenew.class, "emptyAuthRequest") .doTry() .bean(AuthRequestConverter.class, "buildAssertionRequestFromToken") .setHeader("assertionRequest", body()) .bean(ScSAMLRenewSecurityTokenBuilder.class, "requestRenewToken") - - .bean(AssertionExtractor.class) + .bean(TokenRenew.class, "buildAssertionRequest") + .bean(TokenRenew.class, "keepIdpAssertion") .bean(Iti40RequestGenerator.class, "buildAssertion") .removeHeaders("*", "scope") .setHeader(CxfConstants.OPERATION_NAME, From c173176ad02a2ed94591b47d165a28dedf664744 Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Sat, 15 Jun 2024 12:17:20 +0200 Subject: [PATCH 03/10] fix request sending --- .../xua/ScSAMLRenewSecurityTokenBuilder.java | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java index b1864176..59a7cc73 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java @@ -1,5 +1,6 @@ package ch.bfh.ti.i4mi.mag.xua; +import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; @@ -23,6 +24,7 @@ import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; +import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; @@ -31,7 +33,9 @@ import org.apache.http.impl.client.HttpClients; import org.joda.time.DateTime; import org.opensaml.Configuration; +import org.opensaml.common.SAMLException; import org.opensaml.common.SignableSAMLObject; +import org.opensaml.ws.message.decoder.MessageDecodingException; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.ws.soap.client.BasicSOAPMessageContext; import org.opensaml.ws.soap.client.http.HttpClientBuilder; @@ -41,6 +45,8 @@ import org.opensaml.ws.soap.soap11.Envelope; import org.opensaml.ws.soap.soap11.Header; import org.opensaml.ws.soap.util.SOAPHelper; +import org.opensaml.ws.transport.http.HttpClientInTransport; +import org.opensaml.ws.transport.http.HttpClientOutTransport; import org.opensaml.ws.wssecurity.BinarySecurityToken; import org.opensaml.ws.wssecurity.Created; import org.opensaml.ws.wssecurity.EncodedString; @@ -85,6 +91,7 @@ import org.springframework.security.saml.context.SAMLContextProvider; import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.processor.SAMLProcessor; import org.springframework.security.saml.trust.X509KeyManager; import org.springframework.security.saml.trust.X509TrustManager; import org.springframework.stereotype.Component; @@ -95,6 +102,7 @@ import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.binding.encoding.HTTPSOAP11Encoder; import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.security.MetadataCriteria; import javax.servlet.http.HttpServletRequest; @@ -112,8 +120,8 @@ @Component public class ScSAMLRenewSecurityTokenBuilder { - //private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; - private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; + private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; + //private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; private static SecureRandomIdentifierGenerator secureRandomIdGenerator; @@ -134,6 +142,9 @@ public class ScSAMLRenewSecurityTokenBuilder { @Autowired private SAMLContextProvider contextProvider; + @Autowired + SAMLProcessor processor; + //@Autowired //private HttpClient httpClient; @@ -264,6 +275,9 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re log.info(StaxUtils.toString(elem)); + //XMLObject result = send2(renewEndpointUrl, context, envelope); + //NodeList lst = result.getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); + Envelope result = send(renewEndpointUrl, context, envelope); NodeList lst = result.getBody().getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); @@ -271,10 +285,7 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re log.debug("NODE: "+node.toString()); StringWriter writer = new StringWriter(); - TransformerFactory factory = TransformerFactory.newInstance(); - //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "all"); - //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "all"); - + TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.transform(new DOMSource(node), new StreamResult(writer)); @@ -350,26 +361,35 @@ public XMLObject unmarshall(Element input) throws UnmarshallingException { public static String randomId() { return secureRandomIdGenerator.generateIdentifier(); } - - - private Envelope send(String targetUrl, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, MarshallingException, SignatureException, IllegalAccessException, org.opensaml.xml.security.SecurityException, URIException, MessageEncodingException { HttpClientBuilder clientBuilder = new HttpClientBuilder(); //clientBuilder.setHttpsProtocolSocketFactory(SSLProtocolSocketFactory.getSocketFactory()); - HttpClient httpClient = clientBuilder.buildClient(); - httpClient.setHostConfiguration(getHostConfiguration(new URI(targetUrl, "UTF-8"), context, httpClient)); - /*clientBuilder.setHttpsProtocolSocketFactory( - new TLSProtocolSocketFactory(keyManager, trustManager)); - */ + CriteriaSet criteriaSet = new CriteriaSet(); + criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); + criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); + criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); + + X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); + X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential("hintls")); + + + clientBuilder.setHttpsProtocolSocketFactory( + new TLSProtocolSocketFactory(manager, trustManager)); + + + HttpClient httpClient = clientBuilder.buildClient(); + httpClient.setHostConfiguration(getHostConfiguration(new URI(targetUrl, true, "UTF-8"), context, httpClient)); + + HttpSOAPClient soapClient = new HttpSOAPClient(httpClient, new BasicParserPool()); BasicSOAPMessageContext soapContext = new BasicSOAPMessageContext(); soapContext.setOutboundMessage(envelope); log.info("ISSUER="+soapContext.getOutboundMessageIssuer()); - soapContext.setOutboundMessageIssuer("https://test.ahdis.ch"); + //soapContext.setOutboundMessageIssuer("https://test.ahdis.ch"); log.info("SEND!"); soapClient.send(targetUrl, soapContext); log.info("POST-SEND!"); @@ -405,9 +425,9 @@ protected HostConfiguration getHostConfiguration(URI uri, SAMLMessageContext con log.info("Using HTTPS configuration"); log.info("PEER="+context.getPeerEntityId()); CriteriaSet criteriaSet = new CriteriaSet(); - //criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); - //criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); - //criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); + criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); + criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); + criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); From f7816da5d573a4b34f4b71455654886f6dde2d5c Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Sat, 15 Jun 2024 12:28:01 +0200 Subject: [PATCH 04/10] clean up --- ...ava => SAMLRenewSecurityTokenBuilder.java} | 130 ++---------------- .../ch/bfh/ti/i4mi/mag/xua/TokenRenew.java | 50 +------ .../i4mi/mag/xua/TokenRenewRouteBuilder.java | 4 +- 3 files changed, 14 insertions(+), 170 deletions(-) rename src/main/java/ch/bfh/ti/i4mi/mag/xua/{ScSAMLRenewSecurityTokenBuilder.java => SAMLRenewSecurityTokenBuilder.java} (75%) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java similarity index 75% rename from src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java rename to src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java index 59a7cc73..62815ca4 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java @@ -118,7 +118,7 @@ @Slf4j @Component -public class ScSAMLRenewSecurityTokenBuilder { +public class SAMLRenewSecurityTokenBuilder { private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; //private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; @@ -144,10 +144,7 @@ public class ScSAMLRenewSecurityTokenBuilder { @Autowired SAMLProcessor processor; - - //@Autowired - //private HttpClient httpClient; - + @Value("${mag.iua.idp.key-alias}") private String keyAlias; @@ -214,11 +211,7 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re expires.setDateTime(created.getDateTime().plusSeconds(5*60)); timestamp.setExpires(expires); security.getUnknownXMLObjects().add(timestamp); - - // Binary security token is the base64 encoded representation of an X.509 public certificate. - //KeyStore.PrivateKeyEntry privateKeyEntry = securityModule.findPrivateKey(); - //X509Certificate publicCertificate = (X509Certificate) privateKeyEntry.getCertificate(); - + X509Certificate publicCertificate = keyManager.getCertificate(keyAlias); log.info("CERT NOT NULL:"+publicCertificate.toString()); @@ -262,22 +255,13 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re security.getUnknownXMLObjects().add(signature); marshall(envelope); - sign(signature); - //securityModule.signObject(envelope, signature); + sign(signature); // Build the W3C DOM representing the SOAP message. - Element elem = marshall(envelope); - - //context.setOutboundSAMLMessage(null); - //HTTPSOAP11Encoder encode = new HTTPSOAP11Encoder(); - //encode.encode(context); - + Element elem = marshall(envelope); log.info(StaxUtils.toString(elem)); - - //XMLObject result = send2(renewEndpointUrl, context, envelope); - //NodeList lst = result.getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); - + Envelope result = send(renewEndpointUrl, context, envelope); NodeList lst = result.getBody().getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); @@ -366,7 +350,7 @@ public static String randomId() { private Envelope send(String targetUrl, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, MarshallingException, SignatureException, IllegalAccessException, org.opensaml.xml.security.SecurityException, URIException, MessageEncodingException { HttpClientBuilder clientBuilder = new HttpClientBuilder(); - //clientBuilder.setHttpsProtocolSocketFactory(SSLProtocolSocketFactory.getSocketFactory()); + CriteriaSet criteriaSet = new CriteriaSet(); criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); @@ -380,111 +364,19 @@ private Envelope send(String targetUrl, SAMLMessageContext context, Envelope env new TLSProtocolSocketFactory(manager, trustManager)); - HttpClient httpClient = clientBuilder.buildClient(); - httpClient.setHostConfiguration(getHostConfiguration(new URI(targetUrl, true, "UTF-8"), context, httpClient)); - - + HttpClient httpClient = clientBuilder.buildClient(); HttpSOAPClient soapClient = new HttpSOAPClient(httpClient, new BasicParserPool()); BasicSOAPMessageContext soapContext = new BasicSOAPMessageContext(); soapContext.setOutboundMessage(envelope); - log.info("ISSUER="+soapContext.getOutboundMessageIssuer()); - //soapContext.setOutboundMessageIssuer("https://test.ahdis.ch"); - log.info("SEND!"); + soapClient.send(targetUrl, soapContext); - log.info("POST-SEND!"); + Envelope soapResponse = (Envelope)soapContext.getInboundMessage(); return soapResponse; } - - protected HostConfiguration getHostConfiguration(URI uri, SAMLMessageContext context, HttpClient httpClient) throws MessageEncodingException { - - try { - - HostConfiguration hc = httpClient.getHostConfiguration(); - - if (hc != null) { - // Clone configuration from the HTTP Client object - log.info("EXIST"); - hc = new HostConfiguration(hc); - } else { - // Create brand new configuration when there are no defaults - log.info("NOT EXIST"); - hc = new HostConfiguration(); - } - - if (uri.getScheme().equalsIgnoreCase("http")) { - - log.info("Using HTTP configuration"); - hc.setHost(uri); - - } else { - - log.info("Using HTTPS configuration"); - log.info("PEER="+context.getPeerEntityId()); - CriteriaSet criteriaSet = new CriteriaSet(); - criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); - criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); - criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); - - X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); - - //X509KeyManager manager1 = new X509KeyManager(context.getLocalSSLCredential()); - //log.info("TLS NOT NULL:"+keyManager.getCredential("hintls").toString()); - - X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential("hintls")); - - HostnameVerifier hostnameVerifier = context.getLocalSSLHostnameVerifier(); - - ProtocolSocketFactory socketFactory = getSSLSocketFactory(context, manager, trustManager, hostnameVerifier); - Protocol protocol = new Protocol("https", socketFactory, 443); - hc.setHost(uri.getHost(), uri.getPort(), protocol); - - log.info("SET-HOST: "+uri.getHost()+" proto="+protocol.toString()); - } - - return hc; - - } catch (URIException e) { - throw new MessageEncodingException("Error parsing remote location URI", e); - } - - } - - /** - * Method returns SecureProtocolSocketFactory used to connect to create SSL connections for artifact resolution. - * By default we create instance of org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory. - * - * @param context current SAML context - * @param manager keys used for client authentication - * @param trustManager trust manager for server verification - * @param hostnameVerifier verifier for server hostname, or null - * @return socket factory - */ - protected SecureProtocolSocketFactory getSSLSocketFactory(SAMLMessageContext context, X509KeyManager manager, X509TrustManager trustManager, HostnameVerifier hostnameVerifier) { - if (isHostnameVerificationSupported()) { - return new TLSProtocolSocketFactory(manager, trustManager, hostnameVerifier); - } else { - return new TLSProtocolSocketFactory(manager, trustManager); - } - } - - /** - * Check for the latest OpenSAML library. Support for HostnameVerification was added in openws-1.5.1 and - * customers might use previous versions of OpenSAML. - * - * @return true when OpenSAML library support hostname verification - */ - protected boolean isHostnameVerificationSupported() { - try { - TLSProtocolSocketFactory.class.getConstructor(javax.net.ssl.X509KeyManager.class, javax.net.ssl.X509TrustManager.class, javax.net.ssl.HostnameVerifier.class); - return true; - } catch (NoSuchMethodException e) { - log.warn("HostnameVerification is not supported, update your OpenSAML libraries"); - return false; - } - } + } \ No newline at end of file diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java index 069e75c4..78255775 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java @@ -41,55 +41,7 @@ @Slf4j public class TokenRenew { - private String BASE_MSG() { return """ - - - - - http://docs.oasis-open.org/ws-sx/ws-trust/200512/Renew - http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0 - - - - - """; } - - public Element addSecurityHeader(String input) throws XMLStreamException { - return (Element) StaxUtils.read(new StringReader(input)).getDocumentElement(); - } - - public SOAPMessage buildRenewRequest(@Body AssertionRequest request) throws SOAPException, IOException, XMLStreamException, AuthException { - - Object token = request.getSamlToken(); - if (token == null) throw new AuthException(400, "server_error", "No SAML token found"); - log.info(token.getClass().getSimpleName()); - if (token instanceof String && token.toString().startsWith("")) token = token.toString().substring("".length()); - log.info("Decoded IDP Token:"+token); - - MessageFactory factory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); - SOAPMessage message = factory.createMessage(new MimeHeaders(), new ByteArrayInputStream(BASE_MSG().getBytes(Charset.forName("UTF-8")))); - log.info("BASE MSG"); - // message.getSOAPHeader().addChildElement("MessageID","wsa","http://www.w3.org/2005/08/addressing").addTextNode(UUID.randomUUID().toString()); - - SOAPElement renewTarget = (SOAPElement) message.getSOAPBody().getElementsByTagNameNS("http://docs.oasis-open.org/ws-sx/ws-trust/200512", "RenewTarget").item(0); - log.info("RENEW TG"); - Element elem; - if (token instanceof String) { - elem = addSecurityHeader(token.toString()); - } else { - elem = ((Element) token); - } - Node node = message.getSOAPBody().getOwnerDocument().importNode(elem, true); - log.info("RENEW NODE"); - renewTarget.appendChild(node); - - log.info("Sending IDP Renew Request: "+message.toString()); - - message.saveChanges(); - - return message; - } - + public AssertionRequest buildAssertionRequest(@Header("assertionRequest") AssertionRequest assertionRequest, @Body String renewedIdpAssertion) { assertionRequest.setSamlToken(renewedIdpAssertion); return assertionRequest; diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java index 994b5e5a..131c558a 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java @@ -41,12 +41,12 @@ public void configure() throws Exception { from(String.format("servlet://%s?httpMethodRestrict=POST&matchOnUriPrefix=true", RENEW_PATH)) .routeId("renewEndpoint") - .process(ScSAMLRenewSecurityTokenBuilder.keepRequest()) + .process(SAMLRenewSecurityTokenBuilder.keepRequest()) .setProperty("oauthrequest").method(TokenRenew.class, "emptyAuthRequest") .doTry() .bean(AuthRequestConverter.class, "buildAssertionRequestFromToken") .setHeader("assertionRequest", body()) - .bean(ScSAMLRenewSecurityTokenBuilder.class, "requestRenewToken") + .bean(SAMLRenewSecurityTokenBuilder.class, "requestRenewToken") .bean(TokenRenew.class, "buildAssertionRequest") .bean(TokenRenew.class, "keepIdpAssertion") From e7a1f632bed4dd82bbd4d278d46e2a8806ecf13c Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Wed, 19 Jun 2024 10:41:19 +0200 Subject: [PATCH 05/10] update to token renew --- .../ch/bfh/ti/i4mi/mag/xua/IDPConfig.java | 2 + .../xua/SAMLRenewSecurityTokenBuilder.java | 66 +++++++------------ 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/IDPConfig.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/IDPConfig.java index a2241989..4c309e0f 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/IDPConfig.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/IDPConfig.java @@ -29,6 +29,8 @@ public class IDPConfig { private String name; private String metadataUrl; + + private String renewUrl; private String keyAlias; diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java index 62815ca4..0f801a11 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java @@ -1,11 +1,8 @@ package ch.bfh.ti.i4mi.mag.xua; -import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.TrustManager; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; @@ -16,26 +13,15 @@ import javax.xml.transform.stream.StreamResult; import org.apache.camel.Body; -import org.apache.camel.Exchange; import org.apache.camel.ExchangeProperty; import org.apache.camel.Processor; import org.apache.camel.http.common.HttpMessage; -import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; -import org.apache.commons.httpclient.methods.PostMethod; -import org.apache.commons.httpclient.protocol.Protocol; -import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; -import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; -import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; import org.apache.cxf.staxutils.StaxUtils; -import org.apache.http.impl.client.HttpClients; import org.joda.time.DateTime; import org.opensaml.Configuration; -import org.opensaml.common.SAMLException; import org.opensaml.common.SignableSAMLObject; -import org.opensaml.ws.message.decoder.MessageDecodingException; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.ws.soap.client.BasicSOAPMessageContext; import org.opensaml.ws.soap.client.http.HttpClientBuilder; @@ -44,9 +30,6 @@ import org.opensaml.ws.soap.common.SOAPException; import org.opensaml.ws.soap.soap11.Envelope; import org.opensaml.ws.soap.soap11.Header; -import org.opensaml.ws.soap.util.SOAPHelper; -import org.opensaml.ws.transport.http.HttpClientInTransport; -import org.opensaml.ws.transport.http.HttpClientOutTransport; import org.opensaml.ws.wssecurity.BinarySecurityToken; import org.opensaml.ws.wssecurity.Created; import org.opensaml.ws.wssecurity.EncodedString; @@ -73,21 +56,16 @@ import org.opensaml.xml.security.criteria.EntityIDCriteria; import org.opensaml.xml.security.criteria.UsageCriteria; import org.opensaml.xml.security.keyinfo.KeyInfoHelper; -import org.opensaml.xml.security.x509.BasicX509Credential; import org.opensaml.xml.security.x509.X509Credential; import org.opensaml.xml.signature.DocumentInternalIDContentReference; import org.opensaml.xml.signature.KeyInfo; -import org.opensaml.xml.signature.SignableXMLObject; import org.opensaml.xml.signature.Signature; import org.opensaml.xml.signature.SignatureConstants; import org.opensaml.xml.signature.SignatureException; import org.opensaml.xml.signature.Signer; import org.opensaml.xml.signature.X509Data; -import org.opensaml.xml.signature.X509IssuerSerial; -import org.opensaml.xml.signature.impl.X509IssuerSerialBuilder; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.security.saml.context.SAMLContextProvider; import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.key.KeyManager; @@ -98,13 +76,15 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; + +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + import org.opensaml.common.impl.SecureRandomIdentifierGenerator; import org.opensaml.common.xml.SAMLConstants; -import org.opensaml.saml2.binding.encoding.HTTPSOAP11Encoder; import org.opensaml.saml2.metadata.IDPSSODescriptor; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.security.MetadataCriteria; +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -115,13 +95,13 @@ import java.security.cert.X509Certificate; import java.time.Instant; import java.util.Base64; +import java.util.Map; @Slf4j @Component public class SAMLRenewSecurityTokenBuilder { - private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; - //private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; + //private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; private static SecureRandomIdentifierGenerator secureRandomIdGenerator; @@ -144,9 +124,10 @@ public class SAMLRenewSecurityTokenBuilder { @Autowired SAMLProcessor processor; - - @Value("${mag.iua.idp.key-alias}") - private String keyAlias; + + @Resource + Map idps; + public Element addSecurityHeader(String input) throws XMLStreamException { return (Element) StaxUtils.read(new StringReader(input)).getDocumentElement(); @@ -163,10 +144,14 @@ public static Processor keepRequest() { public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest request, @ExchangeProperty("request") HttpServletRequest hrequest, @ExchangeProperty("response") HttpServletResponse hresponse) throws Exception { - SAMLMessageContext context = contextProvider.getLocalAndPeerEntity(hrequest, hresponse); - + SAMLMessageContext context = contextProvider.getLocalAndPeerEntity(hrequest, hresponse); + String local = context.getLocalExtendedMetadata().getAlias(); + IDPConfig config = idps.get(local); + if (config == null || config.getRenewUrl() == null) throw new InvalidRequestException("No 'renewUrl' configured for IDP '"+local+"' in the MAG configuration."); + String renewEndpointUrl = config.getRenewUrl(); + Object token = request.getSamlToken(); - + if (token == null) throw new InvalidRequestException("No token provided for renewal."); if (token instanceof String) { token = addSecurityHeader((String) token); } @@ -212,8 +197,8 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re timestamp.setExpires(expires); security.getUnknownXMLObjects().add(timestamp); - X509Certificate publicCertificate = keyManager.getCertificate(keyAlias); - log.info("CERT NOT NULL:"+publicCertificate.toString()); + String signKeyAlias = config.getSignKeyAlias() != null ? config.getSignKeyAlias() : config.getKeyAlias(); + X509Certificate publicCertificate = keyManager.getCertificate(signKeyAlias); BinarySecurityToken binarySecurityToken = createSAMLObject(BinarySecurityToken.class); binarySecurityToken.setEncodingType(EncodedString.ENCODING_TYPE_BASE64_BINARY); @@ -225,7 +210,7 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re Signature signature = createSAMLObject(Signature.class); - Credential cred = keyManager.getCredential(keyAlias); + Credential cred = keyManager.getCredential(signKeyAlias); signature.setSigningCredential(cred); signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512); signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); @@ -260,14 +245,13 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re // Build the W3C DOM representing the SOAP message. Element elem = marshall(envelope); - log.info(StaxUtils.toString(elem)); + log.debug(StaxUtils.toString(elem)); - Envelope result = send(renewEndpointUrl, context, envelope); + Envelope result = send(renewEndpointUrl, config, context, envelope); NodeList lst = result.getBody().getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); Node node = lst.item(0); - log.debug("NODE: "+node.toString()); - + StringWriter writer = new StringWriter(); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); @@ -347,7 +331,7 @@ public static String randomId() { } - private Envelope send(String targetUrl, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, + private Envelope send(String targetUrl, IDPConfig config, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, MarshallingException, SignatureException, IllegalAccessException, org.opensaml.xml.security.SecurityException, URIException, MessageEncodingException { HttpClientBuilder clientBuilder = new HttpClientBuilder(); @@ -357,7 +341,7 @@ private Envelope send(String targetUrl, SAMLMessageContext context, Envelope env criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); - X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential("hintls")); + X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential(config.getTlsKeyAlias())); clientBuilder.setHttpsProtocolSocketFactory( From 833151f624f68638bf6052c5846d1c37f326802c Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Fri, 14 Jun 2024 17:26:37 +0200 Subject: [PATCH 06/10] test branch for IDP renew. Do not merge --- pom.xml | 2 +- .../ti/i4mi/mag/xua/AuthRequestConverter.java | 15 +- .../i4mi/mag/xua/AuthResponseConverter.java | 7 +- .../i4mi/mag/xua/AuthenticationRequest.java | 2 + .../ti/i4mi/mag/xua/Iti71RouteBuilder.java | 6 +- .../ti/i4mi/mag/xua/OAuth2TokenResponse.java | 2 + .../xua/ScSAMLRenewSecurityTokenBuilder.java | 460 ++++++++++++++++++ .../ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java | 5 + .../ch/bfh/ti/i4mi/mag/xua/TokenRenew.java | 103 ++++ .../i4mi/mag/xua/TokenRenewRouteBuilder.java | 73 +++ 10 files changed, 667 insertions(+), 8 deletions(-) create mode 100644 src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java create mode 100644 src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java create mode 100644 src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java diff --git a/pom.xml b/pom.xml index a555320b..af2dc82a 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 1.11 yyyy-MM-dd HH:mm:ss - 11 + 15 3.10.1 diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthRequestConverter.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthRequestConverter.java index 8e9c697d..e7be251e 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthRequestConverter.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthRequestConverter.java @@ -16,12 +16,15 @@ package ch.bfh.ti.i4mi.mag.xua; +import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Map; import javax.ws.rs.BadRequestException; import org.apache.camel.Body; +import org.apache.camel.ExchangeProperty; import org.apache.camel.Header; import org.apache.camel.Headers; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +34,7 @@ import org.springframework.stereotype.Component; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import net.sourceforge.plantuml.utils.Log; /** * Convert OAuth2 request to SAML Assertion Request @@ -86,7 +90,7 @@ public AuthenticationRequest buildAuthenticationRequest( return request; } - public AssertionRequest buildAssertionRequest(@Header("response_type") String responseType, @Header("oauthrequest") AuthenticationRequest request) throws AuthException { + public AssertionRequest buildAssertionRequest(@Header("response_type") String responseType, @ExchangeProperty("oauthrequest") AuthenticationRequest request) throws AuthException { if (!"code".equals(responseType)) throw new AuthException(400, "invalid_request", "response_type must be 'code'"); @@ -107,6 +111,15 @@ private String decode(String in) { public AssertionRequest buildAssertionRequestFromIdp(@Body String authorization, @Header("scope") String scope) throws AuthException { return buildAssertionRequestInternal(authorization, scope); } + + public AssertionRequest buildAssertionRequestFromToken(@Body String authorization, @Header("scope") String scope) throws AuthException { + System.out.println("BODY:"+authorization); + try { + authorization = new String(Base64.getDecoder().decode(authorization), "UTF-8"); + } catch (UnsupportedEncodingException e) {} + System.out.println("BODY2:"+authorization); + return buildAssertionRequestInternal(authorization, scope); + } private AssertionRequest buildAssertionRequestInternal(Object authorization, String scope) throws AuthException { diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthResponseConverter.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthResponseConverter.java index 46fb1313..774307f1 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthResponseConverter.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthResponseConverter.java @@ -21,6 +21,7 @@ import java.net.URLEncoder; import org.apache.camel.Body; +import org.apache.camel.ExchangeProperty; import org.apache.camel.Header; import org.apache.commons.lang.RandomStringUtils; import org.apache.cxf.binding.soap.SoapFault; @@ -37,7 +38,7 @@ public class AuthResponseConverter { @Autowired private Cache codeToToken; - public String handle(@Body String assertion, @Header("oauthrequest") AuthenticationRequest request) throws UnsupportedEncodingException { + public String handle(@Body String assertion, @ExchangeProperty("oauthrequest") AuthenticationRequest request) throws UnsupportedEncodingException { String returnurl = request.getRedirect_uri(); String state = request.getState(); @@ -54,7 +55,7 @@ public String handle(@Body String assertion, @Header("oauthrequest") Authenticat return returnurl; } - public String handleerror(@Header("oauthrequest") AuthenticationRequest request, @Body AuthException exception) throws UnsupportedEncodingException{ + public String handleerror(@ExchangeProperty("oauthrequest") AuthenticationRequest request, @Body AuthException exception) throws UnsupportedEncodingException{ String returnurl = request.getRedirect_uri(); String state = request.getState(); @@ -68,7 +69,7 @@ public String handleerror(@Header("oauthrequest") AuthenticationRequest request, return returnurl; } - public String handlesoaperror(@Header("oauthrequest") AuthenticationRequest request, @Body SoapFault exception) throws UnsupportedEncodingException{ + public String handlesoaperror(@ExchangeProperty("oauthrequest") AuthenticationRequest request, @Body SoapFault exception) throws UnsupportedEncodingException{ String returnurl = request.getRedirect_uri(); String state = request.getState(); diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthenticationRequest.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthenticationRequest.java index 6f3e039b..cf99c030 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthenticationRequest.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AuthenticationRequest.java @@ -39,6 +39,8 @@ public class AuthenticationRequest { private String assertion; + private String idpAssertion; + private String code_challenge; } diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/Iti71RouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/Iti71RouteBuilder.java index 36ac4843..9f0b9c49 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/Iti71RouteBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/Iti71RouteBuilder.java @@ -64,7 +64,7 @@ public void configure() throws Exception { from(String.format("servlet://%s?matchOnUriPrefix=true", AUTHORIZE_PATH)).routeId("iti71") .doTry() - .setHeader("oauthrequest").method(converter, "buildAuthenticationRequest") + .setProperty("oauthrequest").method(converter, "buildAuthenticationRequest") // end spring security session in order to prevent use of already expired // identity provider assertions cached in spring security session @@ -72,8 +72,8 @@ public void configure() throws Exception { .process(Utils.endHttpSession()) .bean(AuthRequestConverter.class, "buildAssertionRequest") - .bean(Iti40RequestGenerator.class, "buildAssertion") - + .bean(TokenRenew.class, "keepIdpAssertion") + .bean(Iti40RequestGenerator.class, "buildAssertion") .removeHeaders("*","oauthrequest") .setHeader(CxfConstants.OPERATION_NAME, constant("Issue")) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/OAuth2TokenResponse.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/OAuth2TokenResponse.java index cb013a8a..9c27f05a 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/OAuth2TokenResponse.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/OAuth2TokenResponse.java @@ -29,6 +29,8 @@ public class OAuth2TokenResponse { private String access_token; + private String refresh_token; + private String token_type; private long expires_in; diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java new file mode 100644 index 00000000..c6fe2aea --- /dev/null +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java @@ -0,0 +1,460 @@ +package ch.bfh.ti.i4mi.mag.xua; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.TrustManager; +import javax.xml.crypto.dsig.CanonicalizationMethod; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.camel.Body; +import org.apache.camel.Exchange; +import org.apache.camel.ExchangeProperty; +import org.apache.camel.Processor; +import org.apache.camel.http.common.HttpMessage; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.URI; +import org.apache.commons.httpclient.URIException; +import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; +import org.apache.cxf.staxutils.StaxUtils; +import org.apache.http.impl.client.HttpClients; +import org.joda.time.DateTime; +import org.opensaml.Configuration; +import org.opensaml.common.SignableSAMLObject; +import org.opensaml.ws.message.encoder.MessageEncodingException; +import org.opensaml.ws.soap.client.BasicSOAPMessageContext; +import org.opensaml.ws.soap.client.http.HttpClientBuilder; +import org.opensaml.ws.soap.client.http.HttpSOAPClient; +import org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory; +import org.opensaml.ws.soap.common.SOAPException; +import org.opensaml.ws.soap.soap11.Envelope; +import org.opensaml.ws.soap.soap11.Header; +import org.opensaml.ws.soap.util.SOAPHelper; +import org.opensaml.ws.wssecurity.BinarySecurityToken; +import org.opensaml.ws.wssecurity.Created; +import org.opensaml.ws.wssecurity.EncodedString; +import org.opensaml.ws.wssecurity.Expires; +import org.opensaml.ws.wssecurity.Security; +import org.opensaml.ws.wssecurity.SecurityTokenReference; +import org.opensaml.ws.wssecurity.Timestamp; +import org.opensaml.ws.wssecurity.WSSecurityConstants; +import org.opensaml.ws.wssecurity.util.WSSecurityHelper; +import org.opensaml.ws.wstrust.RenewTarget; +import org.opensaml.ws.wstrust.RequestSecurityToken; +import org.opensaml.ws.wstrust.RequestType; +import org.opensaml.ws.wstrust.TokenType; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.XMLObjectBuilderFactory; +import org.opensaml.xml.io.Marshaller; +import org.opensaml.xml.io.MarshallingException; +import org.opensaml.xml.io.Unmarshaller; +import org.opensaml.xml.io.UnmarshallingException; +import org.opensaml.xml.parse.BasicParserPool; +import org.opensaml.xml.security.CriteriaSet; +import org.opensaml.xml.security.credential.Credential; +import org.opensaml.xml.security.credential.UsageType; +import org.opensaml.xml.security.criteria.EntityIDCriteria; +import org.opensaml.xml.security.criteria.UsageCriteria; +import org.opensaml.xml.security.keyinfo.KeyInfoHelper; +import org.opensaml.xml.security.x509.BasicX509Credential; +import org.opensaml.xml.security.x509.X509Credential; + +import org.opensaml.xml.signature.DocumentInternalIDContentReference; +import org.opensaml.xml.signature.KeyInfo; +import org.opensaml.xml.signature.SignableXMLObject; +import org.opensaml.xml.signature.Signature; +import org.opensaml.xml.signature.SignatureConstants; +import org.opensaml.xml.signature.SignatureException; +import org.opensaml.xml.signature.Signer; +import org.opensaml.xml.signature.X509Data; +import org.opensaml.xml.signature.X509IssuerSerial; +import org.opensaml.xml.signature.impl.X509IssuerSerialBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.saml.context.SAMLContextProvider; +import org.springframework.security.saml.context.SAMLMessageContext; +import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.trust.X509KeyManager; +import org.springframework.security.saml.trust.X509TrustManager; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.opensaml.common.impl.SecureRandomIdentifierGenerator; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.security.MetadataCriteria; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import lombok.extern.slf4j.Slf4j; + +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.util.Base64; + +@Slf4j +@Component +public class ScSAMLRenewSecurityTokenBuilder { + + private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; + + private static SecureRandomIdentifierGenerator secureRandomIdGenerator; + + private String destination; + + static { + try { + secureRandomIdGenerator = new SecureRandomIdentifierGenerator(); + } catch (NoSuchAlgorithmException e) { + log.error(e.getMessage(), e); + } + } + + + @Autowired + private KeyManager keyManager; + + @Autowired + private SAMLContextProvider contextProvider; + + //@Autowired + //private HttpClient httpClient; + + @Value("${mag.iua.idp.key-alias}") + private String keyAlias; + + public Element addSecurityHeader(String input) throws XMLStreamException { + return (Element) StaxUtils.read(new StringReader(input)).getDocumentElement(); + } + + public static Processor keepRequest() { + return exchange -> { + HttpServletRequest request = exchange.getIn(HttpMessage.class).getRequest(); + HttpServletResponse response = exchange.getIn(HttpMessage.class).getResponse(); + exchange.setProperty("request", request); + exchange.setProperty("response", response); + }; + } + + public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest request, @ExchangeProperty("request") HttpServletRequest hrequest, @ExchangeProperty("response") HttpServletResponse hresponse) throws Exception { + + SAMLMessageContext context = contextProvider.getLocalAndPeerEntity(hrequest, hresponse); + + Object token = request.getSamlToken(); + + if (token instanceof String) { + token = addSecurityHeader((String) token); + } + + RequestSecurityToken renewSecurityTokenRequestMessage; + + renewSecurityTokenRequestMessage = createSAMLObject(RequestSecurityToken.class); + RequestType requestType = createSAMLObject(RequestType.class); + requestType.setValue(RequestType.RENEW); + renewSecurityTokenRequestMessage.getUnknownXMLObjects().add(requestType); + TokenType tokenType = createSAMLObject(TokenType.class); + tokenType.setValue("http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"); + renewSecurityTokenRequestMessage.getUnknownXMLObjects().add(tokenType); + + RenewTarget renewTarget = createSAMLObject(RenewTarget.class); + renewTarget.setUnknownXMLObject(unmarshall((Element) token)); + renewSecurityTokenRequestMessage.getUnknownXMLObjects().add(renewTarget); + + + Envelope envelope = createSAMLObject(Envelope.class); + // The body element contains the actual SAML message. + org.opensaml.ws.soap.soap11.Body body = createSAMLObject(org.opensaml.ws.soap.soap11.Body.class); + + String ref = randomId(); + WSSecurityHelper.addWSUId(body, ref); + + //WSSecuritySupport.addWSUId(body, securityModule.buildId()); + body.getUnknownXMLObjects().add(renewSecurityTokenRequestMessage); + envelope.setBody(body); + // Application-specific context information (for example, security or encryption information). + Header header = createSAMLObject(Header.class); + envelope.setHeader(header); + Security security = createSAMLObject(Security.class); + header.getUnknownXMLObjects().add(security); + // Security timestamp defining the lifetime of the message. + Timestamp timestamp = createSAMLObject(Timestamp.class); + timestamp.setWSUId(randomId()); + Created created = createSAMLObject(Created.class); + created.setDateTime(DateTime.now()); + timestamp.setCreated(created); + Expires expires = createSAMLObject(Expires.class); + expires.setDateTime(created.getDateTime().plusSeconds(5*60)); + timestamp.setExpires(expires); + security.getUnknownXMLObjects().add(timestamp); + + // Binary security token is the base64 encoded representation of an X.509 public certificate. + //KeyStore.PrivateKeyEntry privateKeyEntry = securityModule.findPrivateKey(); + //X509Certificate publicCertificate = (X509Certificate) privateKeyEntry.getCertificate(); + + X509Certificate publicCertificate = keyManager.getCertificate(keyAlias); + log.info("CERT NOT NULL:"+publicCertificate.toString()); + + BinarySecurityToken binarySecurityToken = createSAMLObject(BinarySecurityToken.class); + binarySecurityToken.setEncodingType(EncodedString.ENCODING_TYPE_BASE64_BINARY); + // Why not use BinarySecurityToken.setValueType()? It was not being valued in the XML by the marshaller. + binarySecurityToken.getUnknownAttributes().put(new QName("ValueType"), WSSecurityConstants.X509_V3); + binarySecurityToken.setValue(encode(publicCertificate)); + security.getUnknownXMLObjects().add(binarySecurityToken); + // Digital signature to verify the identity of the signer. + + + Signature signature = createSAMLObject(Signature.class); + Credential cred = keyManager.getCredential(keyAlias); + signature.setSigningCredential(cred); + signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512); + signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + DocumentInternalIDContentReference timestampReference = + new DocumentInternalIDContentReference(timestamp.getWSUId()); + timestampReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); + + signature.getContentReferences().add(timestampReference); + + DocumentInternalIDContentReference bodyReference = + new DocumentInternalIDContentReference(WSSecurityHelper.getWSUId(body)); + bodyReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); + signature.getContentReferences().add(bodyReference); + KeyInfo keyInfo = createSAMLObject(KeyInfo.class); + SecurityTokenReference securityTokenReference = createSAMLObject(SecurityTokenReference.class); + keyInfo.getXMLObjects().add(securityTokenReference); + + + X509Data x509Data = createSAMLObject(X509Data.class); + + x509Data.getX509IssuerSerials().add(KeyInfoHelper.buildX509IssuerSerial( + publicCertificate.getIssuerX500Principal().getName(), publicCertificate.getSerialNumber())); + securityTokenReference.getUnknownXMLObjects().add(x509Data); + + signature.setKeyInfo(keyInfo); + security.getUnknownXMLObjects().add(signature); + + marshall(envelope); + //sign(signature); + //securityModule.signObject(envelope, signature); + + // Build the W3C DOM representing the SOAP message. + Element elem = marshall(envelope); + + log.info(StaxUtils.toString(elem)); + + Envelope result = send(renewEndpointUrl, context, envelope); + NodeList lst = result.getBody().getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); + + Node node = lst.item(0); + log.debug("NODE: "+node.toString()); + + StringWriter writer = new StringWriter(); + TransformerFactory factory = TransformerFactory.newInstance(); + //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "all"); + //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "all"); + + Transformer transformer = factory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.transform(new DOMSource(node), new StreamResult(writer)); + + String xml = writer.toString(); + + return xml; + } + + public static String encode(final X509Certificate certificate) throws CertificateEncodingException { + final Base64.Encoder encoder = Base64.getEncoder(); + + final byte[] rawCrtText = certificate.getEncoded(); + final String encodedCertText = new String(encoder.encode(rawCrtText)); + + return encodedCertText; + } + + public static void sign(Signature signature) { + + try { + Signer.signObject(signature); + } catch (SignatureException e) { + throw new RuntimeException(e); + } + } + + public static T createSAMLObject(final Class clazz) { + T object = null; + try { + XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory(); + QName defaultElementName; + + try { + defaultElementName = (QName)clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null); + } catch (NoSuchFieldException e) { + defaultElementName = (QName)clazz.getDeclaredField("ELEMENT_NAME").get(null); + } + + object = (T)builderFactory.getBuilder(defaultElementName).buildObject(defaultElementName); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Could not create SAML object"); + } catch (NoSuchFieldException e) { + log.error("Exception", e); + log.error("CLASSNAME: "+clazz.getName()); + throw new IllegalArgumentException("Could not create SAML object"); + } + + return object; + } + + public Element marshall(XMLObject object) { + if (object instanceof SignableSAMLObject && ((SignableSAMLObject) object).isSigned() + && object.getDOM() != null) { + return object.getDOM(); + } else { + try { + Marshaller out = Configuration.getMarshallerFactory().getMarshaller(object); + out.marshall(object); + return object.getDOM(); + } catch (MarshallingException e) { + return null; + } + } + } + + public XMLObject unmarshall(Element input) throws UnmarshallingException { + Unmarshaller un = Configuration.getUnmarshallerFactory().getUnmarshaller(input); + return un.unmarshall(input); + } + + + public static String randomId() { + return secureRandomIdGenerator.generateIdentifier(); + } + + + + + + private Envelope send(String targetUrl, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, + MarshallingException, SignatureException, IllegalAccessException, org.opensaml.xml.security.SecurityException, URIException, MessageEncodingException { + HttpClientBuilder clientBuilder = new HttpClientBuilder(); + //clientBuilder.setHttpsProtocolSocketFactory(SSLProtocolSocketFactory.getSocketFactory()); + HttpClient httpClient = clientBuilder.buildClient(); + httpClient.setHostConfiguration(getHostConfiguration(new URI(targetUrl, "UTF-8"), context, httpClient)); + /*clientBuilder.setHttpsProtocolSocketFactory( + new TLSProtocolSocketFactory(keyManager, trustManager)); + */ + HttpSOAPClient soapClient = new HttpSOAPClient(httpClient, new BasicParserPool()); + + BasicSOAPMessageContext soapContext = new BasicSOAPMessageContext(); + soapContext.setOutboundMessage(envelope); + log.info("SEND!"); + soapClient.send(targetUrl, soapContext); + log.info("POST-SEND!"); + + Envelope soapResponse = (Envelope)soapContext.getInboundMessage(); + + return soapResponse; + } + + protected HostConfiguration getHostConfiguration(URI uri, SAMLMessageContext context, HttpClient httpClient) throws MessageEncodingException { + + try { + + HostConfiguration hc = httpClient.getHostConfiguration(); + + if (hc != null) { + // Clone configuration from the HTTP Client object + log.info("EXIST"); + hc = new HostConfiguration(hc); + } else { + // Create brand new configuration when there are no defaults + log.info("NOT EXIST"); + hc = new HostConfiguration(); + } + + if (uri.getScheme().equalsIgnoreCase("http")) { + + log.info("Using HTTP configuration"); + hc.setHost(uri); + + } else { + + log.info("Using HTTPS configuration"); + log.info("PEER="+context.getPeerEntityId()); + CriteriaSet criteriaSet = new CriteriaSet(); + //criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); + //criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); + //criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); + + X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); + + //X509KeyManager manager1 = new X509KeyManager(context.getLocalSSLCredential()); + //log.info("TLS NOT NULL:"+keyManager.getCredential("hintls").toString()); + + X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential("hintls")); + + HostnameVerifier hostnameVerifier = context.getLocalSSLHostnameVerifier(); + + ProtocolSocketFactory socketFactory = getSSLSocketFactory(context, manager, trustManager, hostnameVerifier); + Protocol protocol = new Protocol("https", socketFactory, 443); + hc.setHost(uri.getHost(), uri.getPort(), protocol); + + log.info("SET-HOST: "+uri.getHost()+" proto="+protocol.toString()); + } + + return hc; + + } catch (URIException e) { + throw new MessageEncodingException("Error parsing remote location URI", e); + } + + } + + /** + * Method returns SecureProtocolSocketFactory used to connect to create SSL connections for artifact resolution. + * By default we create instance of org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory. + * + * @param context current SAML context + * @param manager keys used for client authentication + * @param trustManager trust manager for server verification + * @param hostnameVerifier verifier for server hostname, or null + * @return socket factory + */ + protected SecureProtocolSocketFactory getSSLSocketFactory(SAMLMessageContext context, X509KeyManager manager, X509TrustManager trustManager, HostnameVerifier hostnameVerifier) { + if (isHostnameVerificationSupported()) { + return new TLSProtocolSocketFactory(manager, trustManager, hostnameVerifier); + } else { + return new TLSProtocolSocketFactory(manager, trustManager); + } + } + + /** + * Check for the latest OpenSAML library. Support for HostnameVerification was added in openws-1.5.1 and + * customers might use previous versions of OpenSAML. + * + * @return true when OpenSAML library support hostname verification + */ + protected boolean isHostnameVerificationSupported() { + try { + TLSProtocolSocketFactory.class.getConstructor(javax.net.ssl.X509KeyManager.class, javax.net.ssl.X509TrustManager.class, javax.net.ssl.HostnameVerifier.class); + return true; + } catch (NoSuchMethodException e) { + log.warn("HostnameVerification is not supported, update your OpenSAML libraries"); + return false; + } + } + +} \ No newline at end of file diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java index 6d3381e1..f1986fb4 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java @@ -121,9 +121,14 @@ public OAuth2TokenResponse handle( String encoded = Base64.getEncoder().encodeToString(assertion.getBytes("UTF-8")); log.debug("Encoded token: "+encoded); + String idpAssertion = request.getIdpAssertion(); + String encodedIdp = Base64.getEncoder().encodeToString(idpAssertion.getBytes("UTF-8")); + OAuth2TokenResponse result = new OAuth2TokenResponse(); result.setAccess_token(encoded); + result.setExpires_in(this.computeExpiresInFromNotOnOrAfter(assertion)); // In seconds + result.setRefresh_token(encodedIdp); result.setScope(request.getScope()); result.setToken_type("Bearer" /*request.getToken_type()*/); return result; diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java new file mode 100644 index 00000000..0b737221 --- /dev/null +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java @@ -0,0 +1,103 @@ +package ch.bfh.ti.i4mi.mag.xua; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.UUID; + +import javax.xml.soap.MessageFactory; +import javax.xml.soap.MimeHeaders; +import javax.xml.soap.SOAPConstants; +import javax.xml.soap.SOAPElement; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; +import javax.xml.stream.XMLStreamException; + +import org.apache.camel.Body; +import org.apache.camel.ExchangeProperty; +import org.apache.camel.Header; +import org.apache.cxf.staxutils.StaxUtils; +import org.apache.xml.security.utils.XMLUtils; +import org.opensaml.xml.util.XMLHelper; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +import javax.xml.crypto.dsig.CanonicalizationMethod; +import javax.xml.namespace.QName; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; + + + +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class TokenRenew { + + private String BASE_MSG() { return """ + + + + + http://docs.oasis-open.org/ws-sx/ws-trust/200512/Renew + http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0 + + + + + """; } + + public Element addSecurityHeader(String input) throws XMLStreamException { + return (Element) StaxUtils.read(new StringReader(input)).getDocumentElement(); + } + + public SOAPMessage buildRenewRequest(@Body AssertionRequest request) throws SOAPException, IOException, XMLStreamException, AuthException { + + Object token = request.getSamlToken(); + if (token == null) throw new AuthException(400, "server_error", "No SAML token found"); + log.info(token.getClass().getSimpleName()); + if (token instanceof String && token.toString().startsWith("")) token = token.toString().substring("".length()); + log.info("Decoded IDP Token:"+token); + + MessageFactory factory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); + SOAPMessage message = factory.createMessage(new MimeHeaders(), new ByteArrayInputStream(BASE_MSG().getBytes(Charset.forName("UTF-8")))); + log.info("BASE MSG"); + // message.getSOAPHeader().addChildElement("MessageID","wsa","http://www.w3.org/2005/08/addressing").addTextNode(UUID.randomUUID().toString()); + + SOAPElement renewTarget = (SOAPElement) message.getSOAPBody().getElementsByTagNameNS("http://docs.oasis-open.org/ws-sx/ws-trust/200512", "RenewTarget").item(0); + log.info("RENEW TG"); + Element elem; + if (token instanceof String) { + elem = addSecurityHeader(token.toString()); + } else { + elem = ((Element) token); + } + Node node = message.getSOAPBody().getOwnerDocument().importNode(elem, true); + log.info("RENEW NODE"); + renewTarget.appendChild(node); + + log.info("Sending IDP Renew Request: "+message.toString()); + + message.saveChanges(); + + return message; + } + + public AssertionRequest buildAssertionRequest(@Header("assertionRequest") AssertionRequest assertionRequest, @Body String renewedIdpAssertion) { + assertionRequest.setSamlToken(renewedIdpAssertion); + return assertionRequest; + } + + public AssertionRequest keepIdpAssertion(@ExchangeProperty("oauthrequest") AuthenticationRequest authRequest, @Body AssertionRequest assertionRequest) { + String idpAssertion = XMLHelper.nodeToString((Node) assertionRequest.getSamlToken()); + authRequest.setIdpAssertion(idpAssertion); + return assertionRequest; + } +} diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java new file mode 100644 index 00000000..29a644ff --- /dev/null +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java @@ -0,0 +1,73 @@ +package ch.bfh.ti.i4mi.mag.xua; + +import org.apache.camel.Exchange; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.cxf.common.message.CxfConstants; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class TokenRenewRouteBuilder extends RouteBuilder { + + public static final String RENEW_PATH = "renew"; + + @Value("${mag.iua.ap.url}") + private String assertionEndpointUrl; + + @Value("${mag.iua.ap.wsdl}") + private String wsdl; + + @Value("${mag.iua.ap.endpoint-name:}") + private String endpointName; + + + @Value("${mag.client-ssl.enabled}") + private boolean clientSsl; + + + @Override + public void configure() throws Exception { + + final String assertionEndpoint = String.format("cxf://%s?dataFormat=CXF_MESSAGE&wsdlURL=%s&loggingFeatureEnabled=true"+ + ((endpointName!=null && endpointName.length()>0) ? "&endpointName="+endpointName : "")+ + "&inInterceptors=#soapResponseLogger" + + "&inFaultInterceptors=#soapResponseLogger"+ + "&outInterceptors=#soapRequestLogger" + + "&outFaultInterceptors=#soapRequestLogger"+ + (clientSsl ? "&sslContextParameters=#sslContext" : ""), + assertionEndpointUrl, wsdl); + + log.info("Assertion-URL: "+assertionEndpoint); + + from(String.format("servlet://%s?httpMethodRestrict=POST&matchOnUriPrefix=true", RENEW_PATH)) + .routeId("renewEndpoint") + .process(ScSAMLRenewSecurityTokenBuilder.keepRequest()) + .doTry() + .bean(AuthRequestConverter.class, "buildAssertionRequestFromToken") + .setHeader("assertionRequest", body()) + .bean(ScSAMLRenewSecurityTokenBuilder.class, "requestRenewToken") + + .bean(AssertionExtractor.class) + .bean(TokenRenew.class, "buildAssertionRequest") + .bean(Iti40RequestGenerator.class, "buildAssertion") + .removeHeaders("*", "scope") + .setHeader(CxfConstants.OPERATION_NAME, + constant("Issue")) + .setHeader(CxfConstants.OPERATION_NAMESPACE, + constant("http://docs.oasis-open.org/ws-sx/ws-trust/200512/wsdl")) + .to(assertionEndpoint) + .bean(AssertionExtractor.class) + .bean(TokenEndpoint.class, "handleFromIdp") + + .doCatch(AuthException.class) + .setBody(simple("${exception}")) + .bean(TokenEndpoint.class, "handleError") + .setHeader(Exchange.HTTP_RESPONSE_CODE, simple("${exception.status}")) + .end() + .removeHeaders("*", Exchange.HTTP_RESPONSE_CODE) + .setHeader("Cache-Control", constant("no-store")) + .setHeader("Pragma", constant("no-cache")) + .marshal() + .json(); + } +} From eee80160207eb4edf91dd2f50b7ca4471cd2ad5d Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Sat, 15 Jun 2024 11:41:51 +0200 Subject: [PATCH 07/10] update to idp renew --- .../AssertionFromIdpTokenRouteBuilder.java | 2 +- .../xua/ScSAMLRenewSecurityTokenBuilder.java | 20 ++++++++++++++----- .../ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java | 11 +++++++--- .../ch/bfh/ti/i4mi/mag/xua/TokenRenew.java | 11 +++++++++- .../i4mi/mag/xua/TokenRenewRouteBuilder.java | 5 +++-- 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AssertionFromIdpTokenRouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AssertionFromIdpTokenRouteBuilder.java index 3cf542e6..8e15553f 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/AssertionFromIdpTokenRouteBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/AssertionFromIdpTokenRouteBuilder.java @@ -60,7 +60,7 @@ public void configure() throws Exception { // identity provider assertions cached in spring security session // this is unrelated to the IDP provider cookie set by the IDP itself .process(Utils.endHttpSession()) - + .setProperty("oauthrequest").method(TokenRenew.class, "emptyAuthRequest") .bean(AuthRequestConverter.class, "buildAssertionRequestFromIdp") .bean(Iti40RequestGenerator.class, "buildAssertion") .removeHeaders("*", "scope") diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java index c6fe2aea..b1864176 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java @@ -93,6 +93,7 @@ import org.w3c.dom.NodeList; import org.opensaml.common.impl.SecureRandomIdentifierGenerator; import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.binding.encoding.HTTPSOAP11Encoder; import org.opensaml.saml2.metadata.IDPSSODescriptor; import org.opensaml.security.MetadataCriteria; @@ -111,7 +112,8 @@ @Component public class ScSAMLRenewSecurityTokenBuilder { - private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; + //private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; + private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; private static SecureRandomIdentifierGenerator secureRandomIdGenerator; @@ -226,12 +228,13 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re DocumentInternalIDContentReference timestampReference = new DocumentInternalIDContentReference(timestamp.getWSUId()); timestampReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); - + timestampReference.setDigestAlgorithm("http://www.w3.org/2001/04/xmlenc#sha256"); signature.getContentReferences().add(timestampReference); DocumentInternalIDContentReference bodyReference = new DocumentInternalIDContentReference(WSSecurityHelper.getWSUId(body)); - bodyReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); + bodyReference.getTransforms().add(CanonicalizationMethod.EXCLUSIVE); + bodyReference.setDigestAlgorithm("http://www.w3.org/2001/04/xmlenc#sha256"); signature.getContentReferences().add(bodyReference); KeyInfo keyInfo = createSAMLObject(KeyInfo.class); SecurityTokenReference securityTokenReference = createSAMLObject(SecurityTokenReference.class); @@ -248,12 +251,17 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re security.getUnknownXMLObjects().add(signature); marshall(envelope); - //sign(signature); + sign(signature); //securityModule.signObject(envelope, signature); // Build the W3C DOM representing the SOAP message. Element elem = marshall(envelope); + //context.setOutboundSAMLMessage(null); + //HTTPSOAP11Encoder encode = new HTTPSOAP11Encoder(); + //encode.encode(context); + + log.info(StaxUtils.toString(elem)); Envelope result = send(renewEndpointUrl, context, envelope); @@ -359,7 +367,9 @@ private Envelope send(String targetUrl, SAMLMessageContext context, Envelope env HttpSOAPClient soapClient = new HttpSOAPClient(httpClient, new BasicParserPool()); BasicSOAPMessageContext soapContext = new BasicSOAPMessageContext(); - soapContext.setOutboundMessage(envelope); + soapContext.setOutboundMessage(envelope); + log.info("ISSUER="+soapContext.getOutboundMessageIssuer()); + soapContext.setOutboundMessageIssuer("https://test.ahdis.ch"); log.info("SEND!"); soapClient.send(targetUrl, soapContext); log.info("POST-SEND!"); diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java index f1986fb4..c88d3019 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenEndpoint.java @@ -27,6 +27,7 @@ import java.util.Base64; import org.apache.camel.Body; +import org.apache.camel.ExchangeProperty; import org.apache.camel.Header; import org.ehcache.Cache; import org.opensaml.Configuration; @@ -148,14 +149,18 @@ public static String sha256ThenBase64(String input) throws AuthException { } } - public OAuth2TokenResponse handleFromIdp(@Body String assertion, @Header("scope") String scope) - throws UnsupportedEncodingException, XMLParserException, UnmarshallingException { - + + public OAuth2TokenResponse handleFromIdp(@ExchangeProperty("oauthrequest") AuthenticationRequest authRequest, @Body String assertion, @Header("scope") String scope) throws UnsupportedEncodingException, AuthException, XMLParserException, UnmarshallingException { String encoded = Base64.getEncoder().encodeToString(assertion.getBytes("UTF-8")); OAuth2TokenResponse result = new OAuth2TokenResponse(); result.setAccess_token(encoded); result.setExpires_in(this.computeExpiresInFromNotOnOrAfter(assertion)); // In seconds + + String idpAssertion = authRequest.getIdpAssertion(); + String encodedIdp = Base64.getEncoder().encodeToString(idpAssertion.getBytes("UTF-8")); + result.setRefresh_token(encodedIdp); + result.setScope(scope); result.setToken_type("Bearer" /*request.getToken_type()*/); return result; diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java index 0b737221..069e75c4 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java @@ -95,8 +95,17 @@ public AssertionRequest buildAssertionRequest(@Header("assertionRequest") Assert return assertionRequest; } + public AuthenticationRequest emptyAuthRequest() { + return new AuthenticationRequest(); + } + public AssertionRequest keepIdpAssertion(@ExchangeProperty("oauthrequest") AuthenticationRequest authRequest, @Body AssertionRequest assertionRequest) { - String idpAssertion = XMLHelper.nodeToString((Node) assertionRequest.getSamlToken()); + String idpAssertion; + if (assertionRequest.getSamlToken() instanceof String) { + idpAssertion = (String) assertionRequest.getSamlToken(); + } else { + idpAssertion = XMLHelper.nodeToString((Node) assertionRequest.getSamlToken()); + } authRequest.setIdpAssertion(idpAssertion); return assertionRequest; } diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java index 29a644ff..994b5e5a 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java @@ -42,13 +42,14 @@ public void configure() throws Exception { from(String.format("servlet://%s?httpMethodRestrict=POST&matchOnUriPrefix=true", RENEW_PATH)) .routeId("renewEndpoint") .process(ScSAMLRenewSecurityTokenBuilder.keepRequest()) + .setProperty("oauthrequest").method(TokenRenew.class, "emptyAuthRequest") .doTry() .bean(AuthRequestConverter.class, "buildAssertionRequestFromToken") .setHeader("assertionRequest", body()) .bean(ScSAMLRenewSecurityTokenBuilder.class, "requestRenewToken") - - .bean(AssertionExtractor.class) + .bean(TokenRenew.class, "buildAssertionRequest") + .bean(TokenRenew.class, "keepIdpAssertion") .bean(Iti40RequestGenerator.class, "buildAssertion") .removeHeaders("*", "scope") .setHeader(CxfConstants.OPERATION_NAME, From 4e26d4b27c8cd4f777457b17b974b951792602db Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Sat, 15 Jun 2024 12:17:20 +0200 Subject: [PATCH 08/10] fix request sending --- .../xua/ScSAMLRenewSecurityTokenBuilder.java | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java index b1864176..59a7cc73 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java @@ -1,5 +1,6 @@ package ch.bfh.ti.i4mi.mag.xua; +import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; @@ -23,6 +24,7 @@ import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; +import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; @@ -31,7 +33,9 @@ import org.apache.http.impl.client.HttpClients; import org.joda.time.DateTime; import org.opensaml.Configuration; +import org.opensaml.common.SAMLException; import org.opensaml.common.SignableSAMLObject; +import org.opensaml.ws.message.decoder.MessageDecodingException; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.ws.soap.client.BasicSOAPMessageContext; import org.opensaml.ws.soap.client.http.HttpClientBuilder; @@ -41,6 +45,8 @@ import org.opensaml.ws.soap.soap11.Envelope; import org.opensaml.ws.soap.soap11.Header; import org.opensaml.ws.soap.util.SOAPHelper; +import org.opensaml.ws.transport.http.HttpClientInTransport; +import org.opensaml.ws.transport.http.HttpClientOutTransport; import org.opensaml.ws.wssecurity.BinarySecurityToken; import org.opensaml.ws.wssecurity.Created; import org.opensaml.ws.wssecurity.EncodedString; @@ -85,6 +91,7 @@ import org.springframework.security.saml.context.SAMLContextProvider; import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.key.KeyManager; +import org.springframework.security.saml.processor.SAMLProcessor; import org.springframework.security.saml.trust.X509KeyManager; import org.springframework.security.saml.trust.X509TrustManager; import org.springframework.stereotype.Component; @@ -95,6 +102,7 @@ import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.binding.encoding.HTTPSOAP11Encoder; import org.opensaml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.security.MetadataCriteria; import javax.servlet.http.HttpServletRequest; @@ -112,8 +120,8 @@ @Component public class ScSAMLRenewSecurityTokenBuilder { - //private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; - private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; + private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; + //private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; private static SecureRandomIdentifierGenerator secureRandomIdGenerator; @@ -134,6 +142,9 @@ public class ScSAMLRenewSecurityTokenBuilder { @Autowired private SAMLContextProvider contextProvider; + @Autowired + SAMLProcessor processor; + //@Autowired //private HttpClient httpClient; @@ -264,6 +275,9 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re log.info(StaxUtils.toString(elem)); + //XMLObject result = send2(renewEndpointUrl, context, envelope); + //NodeList lst = result.getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); + Envelope result = send(renewEndpointUrl, context, envelope); NodeList lst = result.getBody().getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); @@ -271,10 +285,7 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re log.debug("NODE: "+node.toString()); StringWriter writer = new StringWriter(); - TransformerFactory factory = TransformerFactory.newInstance(); - //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "all"); - //factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "all"); - + TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.transform(new DOMSource(node), new StreamResult(writer)); @@ -350,26 +361,35 @@ public XMLObject unmarshall(Element input) throws UnmarshallingException { public static String randomId() { return secureRandomIdGenerator.generateIdentifier(); } - - - private Envelope send(String targetUrl, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, MarshallingException, SignatureException, IllegalAccessException, org.opensaml.xml.security.SecurityException, URIException, MessageEncodingException { HttpClientBuilder clientBuilder = new HttpClientBuilder(); //clientBuilder.setHttpsProtocolSocketFactory(SSLProtocolSocketFactory.getSocketFactory()); - HttpClient httpClient = clientBuilder.buildClient(); - httpClient.setHostConfiguration(getHostConfiguration(new URI(targetUrl, "UTF-8"), context, httpClient)); - /*clientBuilder.setHttpsProtocolSocketFactory( - new TLSProtocolSocketFactory(keyManager, trustManager)); - */ + CriteriaSet criteriaSet = new CriteriaSet(); + criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); + criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); + criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); + + X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); + X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential("hintls")); + + + clientBuilder.setHttpsProtocolSocketFactory( + new TLSProtocolSocketFactory(manager, trustManager)); + + + HttpClient httpClient = clientBuilder.buildClient(); + httpClient.setHostConfiguration(getHostConfiguration(new URI(targetUrl, true, "UTF-8"), context, httpClient)); + + HttpSOAPClient soapClient = new HttpSOAPClient(httpClient, new BasicParserPool()); BasicSOAPMessageContext soapContext = new BasicSOAPMessageContext(); soapContext.setOutboundMessage(envelope); log.info("ISSUER="+soapContext.getOutboundMessageIssuer()); - soapContext.setOutboundMessageIssuer("https://test.ahdis.ch"); + //soapContext.setOutboundMessageIssuer("https://test.ahdis.ch"); log.info("SEND!"); soapClient.send(targetUrl, soapContext); log.info("POST-SEND!"); @@ -405,9 +425,9 @@ protected HostConfiguration getHostConfiguration(URI uri, SAMLMessageContext con log.info("Using HTTPS configuration"); log.info("PEER="+context.getPeerEntityId()); CriteriaSet criteriaSet = new CriteriaSet(); - //criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); - //criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); - //criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); + criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); + criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); + criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); From 02516cc316a7546747ee64eaa0ecb171d05b59ba Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Sat, 15 Jun 2024 12:28:01 +0200 Subject: [PATCH 09/10] clean up --- ...ava => SAMLRenewSecurityTokenBuilder.java} | 130 ++---------------- .../ch/bfh/ti/i4mi/mag/xua/TokenRenew.java | 50 +------ .../i4mi/mag/xua/TokenRenewRouteBuilder.java | 4 +- 3 files changed, 14 insertions(+), 170 deletions(-) rename src/main/java/ch/bfh/ti/i4mi/mag/xua/{ScSAMLRenewSecurityTokenBuilder.java => SAMLRenewSecurityTokenBuilder.java} (75%) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java similarity index 75% rename from src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java rename to src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java index 59a7cc73..62815ca4 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/ScSAMLRenewSecurityTokenBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java @@ -118,7 +118,7 @@ @Slf4j @Component -public class ScSAMLRenewSecurityTokenBuilder { +public class SAMLRenewSecurityTokenBuilder { private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; //private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; @@ -144,10 +144,7 @@ public class ScSAMLRenewSecurityTokenBuilder { @Autowired SAMLProcessor processor; - - //@Autowired - //private HttpClient httpClient; - + @Value("${mag.iua.idp.key-alias}") private String keyAlias; @@ -214,11 +211,7 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re expires.setDateTime(created.getDateTime().plusSeconds(5*60)); timestamp.setExpires(expires); security.getUnknownXMLObjects().add(timestamp); - - // Binary security token is the base64 encoded representation of an X.509 public certificate. - //KeyStore.PrivateKeyEntry privateKeyEntry = securityModule.findPrivateKey(); - //X509Certificate publicCertificate = (X509Certificate) privateKeyEntry.getCertificate(); - + X509Certificate publicCertificate = keyManager.getCertificate(keyAlias); log.info("CERT NOT NULL:"+publicCertificate.toString()); @@ -262,22 +255,13 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re security.getUnknownXMLObjects().add(signature); marshall(envelope); - sign(signature); - //securityModule.signObject(envelope, signature); + sign(signature); // Build the W3C DOM representing the SOAP message. - Element elem = marshall(envelope); - - //context.setOutboundSAMLMessage(null); - //HTTPSOAP11Encoder encode = new HTTPSOAP11Encoder(); - //encode.encode(context); - + Element elem = marshall(envelope); log.info(StaxUtils.toString(elem)); - - //XMLObject result = send2(renewEndpointUrl, context, envelope); - //NodeList lst = result.getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); - + Envelope result = send(renewEndpointUrl, context, envelope); NodeList lst = result.getBody().getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); @@ -366,7 +350,7 @@ public static String randomId() { private Envelope send(String targetUrl, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, MarshallingException, SignatureException, IllegalAccessException, org.opensaml.xml.security.SecurityException, URIException, MessageEncodingException { HttpClientBuilder clientBuilder = new HttpClientBuilder(); - //clientBuilder.setHttpsProtocolSocketFactory(SSLProtocolSocketFactory.getSocketFactory()); + CriteriaSet criteriaSet = new CriteriaSet(); criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); @@ -380,111 +364,19 @@ private Envelope send(String targetUrl, SAMLMessageContext context, Envelope env new TLSProtocolSocketFactory(manager, trustManager)); - HttpClient httpClient = clientBuilder.buildClient(); - httpClient.setHostConfiguration(getHostConfiguration(new URI(targetUrl, true, "UTF-8"), context, httpClient)); - - + HttpClient httpClient = clientBuilder.buildClient(); HttpSOAPClient soapClient = new HttpSOAPClient(httpClient, new BasicParserPool()); BasicSOAPMessageContext soapContext = new BasicSOAPMessageContext(); soapContext.setOutboundMessage(envelope); - log.info("ISSUER="+soapContext.getOutboundMessageIssuer()); - //soapContext.setOutboundMessageIssuer("https://test.ahdis.ch"); - log.info("SEND!"); + soapClient.send(targetUrl, soapContext); - log.info("POST-SEND!"); + Envelope soapResponse = (Envelope)soapContext.getInboundMessage(); return soapResponse; } - - protected HostConfiguration getHostConfiguration(URI uri, SAMLMessageContext context, HttpClient httpClient) throws MessageEncodingException { - - try { - - HostConfiguration hc = httpClient.getHostConfiguration(); - - if (hc != null) { - // Clone configuration from the HTTP Client object - log.info("EXIST"); - hc = new HostConfiguration(hc); - } else { - // Create brand new configuration when there are no defaults - log.info("NOT EXIST"); - hc = new HostConfiguration(); - } - - if (uri.getScheme().equalsIgnoreCase("http")) { - - log.info("Using HTTP configuration"); - hc.setHost(uri); - - } else { - - log.info("Using HTTPS configuration"); - log.info("PEER="+context.getPeerEntityId()); - CriteriaSet criteriaSet = new CriteriaSet(); - criteriaSet.add(new EntityIDCriteria(context.getPeerEntityId())); - criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); - criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); - - X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); - - //X509KeyManager manager1 = new X509KeyManager(context.getLocalSSLCredential()); - //log.info("TLS NOT NULL:"+keyManager.getCredential("hintls").toString()); - - X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential("hintls")); - - HostnameVerifier hostnameVerifier = context.getLocalSSLHostnameVerifier(); - - ProtocolSocketFactory socketFactory = getSSLSocketFactory(context, manager, trustManager, hostnameVerifier); - Protocol protocol = new Protocol("https", socketFactory, 443); - hc.setHost(uri.getHost(), uri.getPort(), protocol); - - log.info("SET-HOST: "+uri.getHost()+" proto="+protocol.toString()); - } - - return hc; - - } catch (URIException e) { - throw new MessageEncodingException("Error parsing remote location URI", e); - } - - } - - /** - * Method returns SecureProtocolSocketFactory used to connect to create SSL connections for artifact resolution. - * By default we create instance of org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory. - * - * @param context current SAML context - * @param manager keys used for client authentication - * @param trustManager trust manager for server verification - * @param hostnameVerifier verifier for server hostname, or null - * @return socket factory - */ - protected SecureProtocolSocketFactory getSSLSocketFactory(SAMLMessageContext context, X509KeyManager manager, X509TrustManager trustManager, HostnameVerifier hostnameVerifier) { - if (isHostnameVerificationSupported()) { - return new TLSProtocolSocketFactory(manager, trustManager, hostnameVerifier); - } else { - return new TLSProtocolSocketFactory(manager, trustManager); - } - } - - /** - * Check for the latest OpenSAML library. Support for HostnameVerification was added in openws-1.5.1 and - * customers might use previous versions of OpenSAML. - * - * @return true when OpenSAML library support hostname verification - */ - protected boolean isHostnameVerificationSupported() { - try { - TLSProtocolSocketFactory.class.getConstructor(javax.net.ssl.X509KeyManager.class, javax.net.ssl.X509TrustManager.class, javax.net.ssl.HostnameVerifier.class); - return true; - } catch (NoSuchMethodException e) { - log.warn("HostnameVerification is not supported, update your OpenSAML libraries"); - return false; - } - } + } \ No newline at end of file diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java index 069e75c4..78255775 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenew.java @@ -41,55 +41,7 @@ @Slf4j public class TokenRenew { - private String BASE_MSG() { return """ - - - - - http://docs.oasis-open.org/ws-sx/ws-trust/200512/Renew - http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0 - - - - - """; } - - public Element addSecurityHeader(String input) throws XMLStreamException { - return (Element) StaxUtils.read(new StringReader(input)).getDocumentElement(); - } - - public SOAPMessage buildRenewRequest(@Body AssertionRequest request) throws SOAPException, IOException, XMLStreamException, AuthException { - - Object token = request.getSamlToken(); - if (token == null) throw new AuthException(400, "server_error", "No SAML token found"); - log.info(token.getClass().getSimpleName()); - if (token instanceof String && token.toString().startsWith("")) token = token.toString().substring("".length()); - log.info("Decoded IDP Token:"+token); - - MessageFactory factory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL); - SOAPMessage message = factory.createMessage(new MimeHeaders(), new ByteArrayInputStream(BASE_MSG().getBytes(Charset.forName("UTF-8")))); - log.info("BASE MSG"); - // message.getSOAPHeader().addChildElement("MessageID","wsa","http://www.w3.org/2005/08/addressing").addTextNode(UUID.randomUUID().toString()); - - SOAPElement renewTarget = (SOAPElement) message.getSOAPBody().getElementsByTagNameNS("http://docs.oasis-open.org/ws-sx/ws-trust/200512", "RenewTarget").item(0); - log.info("RENEW TG"); - Element elem; - if (token instanceof String) { - elem = addSecurityHeader(token.toString()); - } else { - elem = ((Element) token); - } - Node node = message.getSOAPBody().getOwnerDocument().importNode(elem, true); - log.info("RENEW NODE"); - renewTarget.appendChild(node); - - log.info("Sending IDP Renew Request: "+message.toString()); - - message.saveChanges(); - - return message; - } - + public AssertionRequest buildAssertionRequest(@Header("assertionRequest") AssertionRequest assertionRequest, @Body String renewedIdpAssertion) { assertionRequest.setSamlToken(renewedIdpAssertion); return assertionRequest; diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java index 994b5e5a..131c558a 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/TokenRenewRouteBuilder.java @@ -41,12 +41,12 @@ public void configure() throws Exception { from(String.format("servlet://%s?httpMethodRestrict=POST&matchOnUriPrefix=true", RENEW_PATH)) .routeId("renewEndpoint") - .process(ScSAMLRenewSecurityTokenBuilder.keepRequest()) + .process(SAMLRenewSecurityTokenBuilder.keepRequest()) .setProperty("oauthrequest").method(TokenRenew.class, "emptyAuthRequest") .doTry() .bean(AuthRequestConverter.class, "buildAssertionRequestFromToken") .setHeader("assertionRequest", body()) - .bean(ScSAMLRenewSecurityTokenBuilder.class, "requestRenewToken") + .bean(SAMLRenewSecurityTokenBuilder.class, "requestRenewToken") .bean(TokenRenew.class, "buildAssertionRequest") .bean(TokenRenew.class, "keepIdpAssertion") From 11d0d82a65f7eeedebcebbc0506cbf7f4864a725 Mon Sep 17 00:00:00 2001 From: Alexander-Kreutz Date: Wed, 19 Jun 2024 10:41:19 +0200 Subject: [PATCH 10/10] update to token renew --- .../ch/bfh/ti/i4mi/mag/xua/IDPConfig.java | 2 + .../xua/SAMLRenewSecurityTokenBuilder.java | 66 +++++++------------ 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/IDPConfig.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/IDPConfig.java index a2241989..4c309e0f 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/IDPConfig.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/IDPConfig.java @@ -29,6 +29,8 @@ public class IDPConfig { private String name; private String metadataUrl; + + private String renewUrl; private String keyAlias; diff --git a/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java b/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java index 62815ca4..0f801a11 100644 --- a/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java +++ b/src/main/java/ch/bfh/ti/i4mi/mag/xua/SAMLRenewSecurityTokenBuilder.java @@ -1,11 +1,8 @@ package ch.bfh.ti.i4mi.mag.xua; -import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.TrustManager; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; @@ -16,26 +13,15 @@ import javax.xml.transform.stream.StreamResult; import org.apache.camel.Body; -import org.apache.camel.Exchange; import org.apache.camel.ExchangeProperty; import org.apache.camel.Processor; import org.apache.camel.http.common.HttpMessage; -import org.apache.commons.httpclient.HostConfiguration; import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; -import org.apache.commons.httpclient.methods.PostMethod; -import org.apache.commons.httpclient.protocol.Protocol; -import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; -import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; -import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; import org.apache.cxf.staxutils.StaxUtils; -import org.apache.http.impl.client.HttpClients; import org.joda.time.DateTime; import org.opensaml.Configuration; -import org.opensaml.common.SAMLException; import org.opensaml.common.SignableSAMLObject; -import org.opensaml.ws.message.decoder.MessageDecodingException; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.ws.soap.client.BasicSOAPMessageContext; import org.opensaml.ws.soap.client.http.HttpClientBuilder; @@ -44,9 +30,6 @@ import org.opensaml.ws.soap.common.SOAPException; import org.opensaml.ws.soap.soap11.Envelope; import org.opensaml.ws.soap.soap11.Header; -import org.opensaml.ws.soap.util.SOAPHelper; -import org.opensaml.ws.transport.http.HttpClientInTransport; -import org.opensaml.ws.transport.http.HttpClientOutTransport; import org.opensaml.ws.wssecurity.BinarySecurityToken; import org.opensaml.ws.wssecurity.Created; import org.opensaml.ws.wssecurity.EncodedString; @@ -73,21 +56,16 @@ import org.opensaml.xml.security.criteria.EntityIDCriteria; import org.opensaml.xml.security.criteria.UsageCriteria; import org.opensaml.xml.security.keyinfo.KeyInfoHelper; -import org.opensaml.xml.security.x509.BasicX509Credential; import org.opensaml.xml.security.x509.X509Credential; import org.opensaml.xml.signature.DocumentInternalIDContentReference; import org.opensaml.xml.signature.KeyInfo; -import org.opensaml.xml.signature.SignableXMLObject; import org.opensaml.xml.signature.Signature; import org.opensaml.xml.signature.SignatureConstants; import org.opensaml.xml.signature.SignatureException; import org.opensaml.xml.signature.Signer; import org.opensaml.xml.signature.X509Data; -import org.opensaml.xml.signature.X509IssuerSerial; -import org.opensaml.xml.signature.impl.X509IssuerSerialBuilder; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.security.saml.context.SAMLContextProvider; import org.springframework.security.saml.context.SAMLMessageContext; import org.springframework.security.saml.key.KeyManager; @@ -98,13 +76,15 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; + +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + import org.opensaml.common.impl.SecureRandomIdentifierGenerator; import org.opensaml.common.xml.SAMLConstants; -import org.opensaml.saml2.binding.encoding.HTTPSOAP11Encoder; import org.opensaml.saml2.metadata.IDPSSODescriptor; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.security.MetadataCriteria; +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -115,13 +95,13 @@ import java.security.cert.X509Certificate; import java.time.Instant; import java.util.Base64; +import java.util.Map; @Slf4j @Component public class SAMLRenewSecurityTokenBuilder { - private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; - //private String renewEndpointUrl = "https://test.ahdis.ch/eprik-cara/camel/hin/ahdis/saml/2.0/renewassertion"; + //private String renewEndpointUrl = "https://samlservices.test.epr.fed.hin.ch/saml/2.0/renewassertion"; private static SecureRandomIdentifierGenerator secureRandomIdGenerator; @@ -144,9 +124,10 @@ public class SAMLRenewSecurityTokenBuilder { @Autowired SAMLProcessor processor; - - @Value("${mag.iua.idp.key-alias}") - private String keyAlias; + + @Resource + Map idps; + public Element addSecurityHeader(String input) throws XMLStreamException { return (Element) StaxUtils.read(new StringReader(input)).getDocumentElement(); @@ -163,10 +144,14 @@ public static Processor keepRequest() { public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest request, @ExchangeProperty("request") HttpServletRequest hrequest, @ExchangeProperty("response") HttpServletResponse hresponse) throws Exception { - SAMLMessageContext context = contextProvider.getLocalAndPeerEntity(hrequest, hresponse); - + SAMLMessageContext context = contextProvider.getLocalAndPeerEntity(hrequest, hresponse); + String local = context.getLocalExtendedMetadata().getAlias(); + IDPConfig config = idps.get(local); + if (config == null || config.getRenewUrl() == null) throw new InvalidRequestException("No 'renewUrl' configured for IDP '"+local+"' in the MAG configuration."); + String renewEndpointUrl = config.getRenewUrl(); + Object token = request.getSamlToken(); - + if (token == null) throw new InvalidRequestException("No token provided for renewal."); if (token instanceof String) { token = addSecurityHeader((String) token); } @@ -212,8 +197,8 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re timestamp.setExpires(expires); security.getUnknownXMLObjects().add(timestamp); - X509Certificate publicCertificate = keyManager.getCertificate(keyAlias); - log.info("CERT NOT NULL:"+publicCertificate.toString()); + String signKeyAlias = config.getSignKeyAlias() != null ? config.getSignKeyAlias() : config.getKeyAlias(); + X509Certificate publicCertificate = keyManager.getCertificate(signKeyAlias); BinarySecurityToken binarySecurityToken = createSAMLObject(BinarySecurityToken.class); binarySecurityToken.setEncodingType(EncodedString.ENCODING_TYPE_BASE64_BINARY); @@ -225,7 +210,7 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re Signature signature = createSAMLObject(Signature.class); - Credential cred = keyManager.getCredential(keyAlias); + Credential cred = keyManager.getCredential(signKeyAlias); signature.setSigningCredential(cred); signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512); signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); @@ -260,14 +245,13 @@ public String requestRenewToken(@Body ch.bfh.ti.i4mi.mag.xua.AssertionRequest re // Build the W3C DOM representing the SOAP message. Element elem = marshall(envelope); - log.info(StaxUtils.toString(elem)); + log.debug(StaxUtils.toString(elem)); - Envelope result = send(renewEndpointUrl, context, envelope); + Envelope result = send(renewEndpointUrl, config, context, envelope); NodeList lst = result.getBody().getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion","Assertion"); Node node = lst.item(0); - log.debug("NODE: "+node.toString()); - + StringWriter writer = new StringWriter(); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); @@ -347,7 +331,7 @@ public static String randomId() { } - private Envelope send(String targetUrl, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, + private Envelope send(String targetUrl, IDPConfig config, SAMLMessageContext context, Envelope envelope) throws SOAPException, CertificateEncodingException, MarshallingException, SignatureException, IllegalAccessException, org.opensaml.xml.security.SecurityException, URIException, MessageEncodingException { HttpClientBuilder clientBuilder = new HttpClientBuilder(); @@ -357,7 +341,7 @@ private Envelope send(String targetUrl, SAMLMessageContext context, Envelope env criteriaSet.add(new UsageCriteria(UsageType.UNSPECIFIED)); X509TrustManager trustManager = new X509TrustManager(criteriaSet, context.getLocalSSLTrustEngine()); - X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential("hintls")); + X509KeyManager manager = new X509KeyManager((X509Credential) keyManager.getCredential(config.getTlsKeyAlias())); clientBuilder.setHttpsProtocolSocketFactory(