Java tutorial
/* * Copyright "Open Digital Education", 2015 * * This program is published by "Open Digital Education". * You must indicate the name of the software and the company in any production /contribution * using the software and indicate on the home page of the software industry in question, * "powered by Open Digital Education" with a reference to the website: https://opendigitaleducation.com/. * * This program is free software, licensed under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, version 3 of the License. * * You can redistribute this application and/or modify it since you respect the terms of the GNU Affero General Public License. * If you modify the source code and then use this modified source code in your creation, you must make available the source code of your modifications. * * You should have received a copy of the GNU Affero General Public License along with the software. * If not, please see : <http://www.gnu.org/licenses/>. Full compliance requires reading the terms of this license and following its directives. */ package org.entcore.auth.security; import fr.wseduc.webutils.Either; import fr.wseduc.webutils.data.ZLib; import io.vertx.core.json.Json; import org.entcore.auth.services.SamlServiceProvider; import org.entcore.auth.services.SamlServiceProviderFactory; import org.entcore.auth.services.SamlVectorService; import org.entcore.auth.services.impl.DefaultServiceProviderFactory; import org.entcore.auth.services.impl.FrEduVecteurService; import org.entcore.common.neo4j.Neo4j; import org.joda.time.DateTime; import org.opensaml.DefaultBootstrap; import org.opensaml.common.SAMLObject; import org.opensaml.common.SAMLVersion; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.core.*; import org.opensaml.saml2.core.impl.*; import org.opensaml.saml2.encryption.Decrypter; import org.opensaml.saml2.metadata.*; import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.security.MetadataCredentialResolver; import org.opensaml.security.MetadataCriteria; import org.opensaml.security.SAMLSignatureProfileValidator; import org.opensaml.xml.Configuration; import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.encryption.InlineEncryptedKeyResolver; import org.opensaml.xml.io.MarshallingException; import org.opensaml.xml.parse.BasicParserPool; import org.opensaml.xml.schema.XSString; import org.opensaml.xml.schema.impl.XSStringBuilder; import org.opensaml.xml.security.CriteriaSet; 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.KeyInfoCredentialResolver; import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver; import org.opensaml.xml.security.x509.BasicX509Credential; import org.opensaml.xml.signature.Signature; import org.opensaml.xml.signature.SignatureConstants; import org.opensaml.xml.signature.SignatureTrustEngine; import org.opensaml.xml.signature.Signer; import org.opensaml.xml.signature.impl.ExplicitKeySignatureTrustEngine; import org.opensaml.xml.signature.impl.SignatureBuilder; import org.opensaml.xml.util.XMLHelper; import org.opensaml.xml.validation.ValidationException; import io.vertx.core.Handler; import io.vertx.core.eventbus.EventBus; import io.vertx.core.eventbus.Message; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import org.vertx.java.busmods.BusModBase; import org.w3c.dom.Element; import java.io.*; import java.net.URLEncoder; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; import static fr.wseduc.webutils.Server.getEventBus; import static fr.wseduc.webutils.Utils.isNotEmpty; public class SamlValidator extends BusModBase implements Handler<Message<JsonObject>> { private final Map<String, SignatureTrustEngine> signatureTrustEngineMap = new HashMap<>(); private final Map<String, EntityDescriptor> entityDescriptorMap = new HashMap<>(); private SPSSODescriptor spSSODescriptor; private RSAPrivateKey privateKey; private String issuer; private SamlVectorService samlVectorService; private Neo4j neo4j; private SamlServiceProviderFactory spFactory; private void debug(String message) { if (logger.isDebugEnabled()) { logger.debug(message); } } @Override public void start() { final EventBus eb = getEventBus(vertx); super.start(); spFactory = new DefaultServiceProviderFactory(config.getJsonObject("saml-services-providers")); String neo4jConfig = (String) vertx.sharedData().getLocalMap("server").get("neo4jConfig"); if (neo4jConfig != null) { neo4j = Neo4j.getSpecificInstance(); neo4j.init(vertx, new JsonObject(neo4jConfig)); } try { DefaultBootstrap.bootstrap(); String path = config.getString("saml-metadata-folder"); if (path == null || path.trim().isEmpty()) { logger.error("Metadata folder not found."); return; } issuer = config.getString("saml-issuer"); if (issuer == null || issuer.trim().isEmpty()) { logger.error("Empty issuer"); return; } for (String f : vertx.fileSystem().readDirBlocking(path)) { loadSignatureTrustEngine(f); } loadPrivateKey(config.getString("saml-private-key")); vertx.eventBus().localConsumer("saml", this); } catch (ConfigurationException | MetadataProviderException | InvalidKeySpecException | NoSuchAlgorithmException e) { logger.error("Error loading SamlValidator.", e); } } private void loadPrivateKey(String path) throws NoSuchAlgorithmException, InvalidKeySpecException { logger.info("loadPrivateKey : " + path); if (path != null && !path.trim().isEmpty() && vertx.fileSystem().existsBlocking(path)) { byte[] encodedPrivateKey = vertx.fileSystem().readFileBlocking(path).getBytes(); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey); privateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec); } } @Override public void handle(Message<JsonObject> message) { final String action = message.body().getString("action", ""); final String response = message.body().getString("response"); final String idp = message.body().getString("IDP"); if (!"generate-slo-request".equals(action) && !"generate-authn-request".equals(action) && !"generate-saml-response".equals(action) && (response == null || response.trim().isEmpty())) { sendError(message, "invalid.response"); return; } try { switch (action) { case "generate-authn-request": String sp = message.body().getString("SP"); String acs = message.body().getString("acs"); boolean sign = message.body().getBoolean("AuthnRequestsSigned", false); if (message.body().getBoolean("SimpleSPEntityID", false)) { sendOK(message, generateSimpleSPEntityIDRequest(idp, sp)); } else { sendOK(message, generateAuthnRequest(idp, sp, acs, sign)); } break; case "generate-saml-response": String serviceProvider = message.body().getString("SP"); String userId = message.body().getString("userId"); String nameid = message.body().getString("nameid"); String host = message.body().getString("host"); spSSODescriptor = getSSODescriptor(serviceProvider); generateSAMLResponse(serviceProvider, userId, nameid, host, message); break; case "validate-signature": sendOK(message, new JsonObject().put("valid", validateSignature(response))); break; case "decrypt-assertion": sendOK(message, new JsonObject().put("assertion", decryptAssertion(response))); break; case "validate-signature-decrypt": final JsonObject res = new JsonObject(); if (validateSignature(response)) { res.put("valid", true).put("assertion", decryptAssertion(response)); } else { res.put("valid", false).put("assertion", (String) null); } sendOK(message, res); break; case "generate-slo-request": String sessionIndex = message.body().getString("SessionIndex"); String nameID = message.body().getString("NameID"); sendOK(message, new JsonObject().put("slo", generateSloRequest(nameID, sessionIndex, idp))); break; default: sendError(message, "invalid.action"); } } catch (Exception e) { sendError(message, e.getMessage(), e); } } /** * Build SAMLResponse and convert it in base64 * * @param serviceProvider serviceProvider name qualifier * @param userId neo4j userID * @param nameId ameId value * @param message message * * * @throws SignatureException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws UnsupportedEncodingException * @throws MarshallingException */ public void generateSAMLResponse(final String serviceProvider, final String userId, final String nameId, final String host, final Message<JsonObject> message) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, MarshallingException { logger.info("start generating SAMLResponse"); logger.info("SP : " + serviceProvider); final JsonObject idp = config.getJsonObject("saml-entng-idp-nq"); String entngIdpNameQualifierTMP = null; if (idp.containsKey(serviceProvider)) { entngIdpNameQualifierTMP = idp.getString(serviceProvider); } else if (idp.containsKey("default")) { entngIdpNameQualifierTMP = idp.getString(serviceProvider); } final String entngIdpNameQualifier = entngIdpNameQualifierTMP; if (entngIdpNameQualifier == null) { String error = "entngIdpNameQualifier can not be null. You must specify it in auth configuration (saml-entng-idp-nq properties)"; logger.error(error); JsonObject jsonObject = new JsonObject().put("error", error); sendOK(message, jsonObject); } logger.info("entngIdpNameQualifier : " + entngIdpNameQualifier); // -- get spSSODescriptor from serviceProvider id -- if (spSSODescriptor == null) { String error = "error SSODescriptor not found for serviceProvider : " + serviceProvider; logger.error(error); JsonObject jsonObject = new JsonObject().put("error", error); sendOK(message, jsonObject); } // --- TAG Issuer --- final Issuer idpIssuer = createIssuer(entngIdpNameQualifier); // --- TAG Status --- final Status status = createStatus(); final AssertionConsumerService assertionConsumerService = spSSODescriptor .getDefaultAssertionConsumerService(); if (assertionConsumerService == null) { String error = "error : AssertionConsumerService not found"; logger.error(error); sendError(message, error); } // --- TAG AttributeStatement --- createVectors(userId, host, new Handler<Either<String, JsonArray>>() { @Override public void handle(Either<String, JsonArray> event) { if (event.isRight()) { LinkedHashMap<String, List<String>> attributes = new LinkedHashMap<String, List<String>>(); JsonArray vectors = event.right().getValue(); if (vectors == null || vectors.size() == 0) { String error = "error building vectors for user " + userId; logger.error(error); sendError(message, error); } else { for (int i = 0; i < vectors.size(); i++) { List<String> vectorsValue = new ArrayList<>(); String vectorType = ""; JsonObject vectorsJsonObject = (vectors.getJsonObject(i)); for (Iterator<String> iter = (vectors.getJsonObject(i)).fieldNames().iterator(); iter .hasNext();) { vectorType = iter.next(); if (attributes.containsKey(vectorType)) { vectorsValue = attributes.get(vectorType); } vectorsValue.add(((JsonObject) vectorsJsonObject).getString(vectorType)); } attributes.put(vectorType, vectorsValue); } } AttributeStatement attributeStatement = createAttributeStatement(attributes); // --- TAG Assertion --- Assertion assertion = null; try { assertion = generateAssertion(entngIdpNameQualifier, serviceProvider, nameId, assertionConsumerService.getLocation(), userId); } catch (Exception e) { logger.error(e.getMessage(), e); sendError(message, e.getMessage(), e); } if (assertion == null) { String error = "error building assertion"; logger.error(error); sendError(message, error); } assertion.getAttributeStatements().add(attributeStatement); // -- attribute Destination (acs) -- String destination = assertionConsumerService.getLocation(); // --- Build response -- Response response = createResponse(new DateTime(), idpIssuer, status, assertion, destination); Signature signature = null; try { signature = createSignature(); } catch (Throwable e) { logger.error(e.getMessage(), e); sendError(message, e.getMessage()); } //response.setSignature(signature); assertion.setSignature(signature); ResponseMarshaller marshaller = new ResponseMarshaller(); Element element = null; try { element = marshaller.marshall(response); } catch (MarshallingException e) { logger.error(e.getMessage(), e); sendError(message, e.getMessage(), e); } if (signature != null) { try { Signer.signObject(signature); } catch (org.opensaml.xml.signature.SignatureException e) { logger.error(e.getMessage(), e); sendError(message, e.getMessage(), e); } } StringWriter rspWrt = new StringWriter(); XMLHelper.writeNode(element, rspWrt); debug("response : " + rspWrt.toString()); JsonObject jsonObject = new JsonObject(); String base64Response = Base64.getEncoder().encodeToString(rspWrt.toString().getBytes()); //, Base64.DONT_BREAK_LINES); debug("base64Response : " + base64Response); jsonObject.put("SAMLResponse64", base64Response); jsonObject.put("destination", destination); sendOK(message, jsonObject); } else { String error = "error bulding vectors for user " + userId + " :"; logger.error(error); logger.error(event.left().getValue()); sendError(message, error); } } }); } /** * Create Success status * * @return the status */ private Status createStatus() { StatusCodeBuilder statusCodeBuilder = new StatusCodeBuilder(); StatusCode statusCode = statusCodeBuilder.buildObject(); statusCode.setValue(StatusCode.SUCCESS_URI); StatusBuilder statusBuilder = new StatusBuilder(); Status status = statusBuilder.buildObject(); status.setStatusCode(statusCode); return status; } /** * Create signature using private key and public cert specified in file conf * @return the signature * @throws Throwable */ private Signature createSignature() throws Throwable { SignatureBuilder builder = new SignatureBuilder(); Signature signature = builder.buildObject(); // create public key (cert) portion of credential String publicKeyPath = config.getString("saml-public-key"); FileInputStream inStream = new FileInputStream(publicKeyPath); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cer = (X509Certificate) cf.generateCertificate(inStream); inStream.close(); // create credential and initialize BasicX509Credential credential = new BasicX509Credential(); credential.setEntityCertificate(cer); //credential.setPublicKey(cer.getPublicKey()); credential.setPrivateKey(privateKey); signature.setSigningCredential(credential); signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1); signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); return signature; } /** * Build the java response * @param issueDate date of generation * @param issuer issuer (must be the same as the nameid->namequalifier) * @param status the status * @param assertion the assertion * @param destination the acs location * @return the java Response */ private Response createResponse(final DateTime issueDate, Issuer issuer, Status status, Assertion assertion, String destination) { ResponseBuilder responseBuilder = new ResponseBuilder(); Response response = responseBuilder.buildObject(); // ID must not be a number response.setID("ENT_" + UUID.randomUUID().toString()); response.setIssueInstant(issueDate); response.setVersion(SAMLVersion.VERSION_20); response.setIssuer(issuer); response.setStatus(status); response.setDestination(destination); response.getAssertions().add(assertion); return response; } /** * Build the java assertion * * @param idp identity provider name qualifier * @param serviceProvider service provider name qualifier * @param nameId nameId value * @param recipient recipient of the assertion (SP Assertion Consumer Service) * @param userId user id neo4j * @return the java assertion * * @throws UnsupportedEncodingException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws SignatureException */ private Assertion generateAssertion(String idp, String serviceProvider, String nameId, String recipient, String userId) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { debug("start generating assertion"); debug("IDP : " + idp); debug("SP : " + serviceProvider); // Init assertion AssertionBuilder assertionBuilder = new AssertionBuilder(); // --- TAG Assertion --- final Assertion assertion = assertionBuilder.buildObject(); // attribut ID // ID must not be a number assertion.setID("ENT_" + UUID.randomUUID().toString()); debug("Assertion ID : " + assertion.getID()); // attribut IssueInstant DateTime authenticationTime = new DateTime(); assertion.setIssueInstant(authenticationTime); debug("IssueInstant : " + assertion.getIssueInstant()); // --- TAG Issuer --- Issuer issuer = createIssuer(idp); assertion.setIssuer(issuer); // --- TAG Subject --- Subject subject = createSubject(nameId, 5, idp, serviceProvider, recipient); assertion.setSubject(subject); // --- TAG Conditions --- ConditionsBuilder conditionsBuilder = new ConditionsBuilder(); Conditions conditions = conditionsBuilder.buildObject(); conditions.setNotBefore(authenticationTime); DateTime notOnOrAfter = new DateTime(); notOnOrAfter = notOnOrAfter.plusDays(1); conditions.setNotOnOrAfter(notOnOrAfter); AudienceRestriction audienceRestriction = new AudienceRestrictionBuilder().buildObject(); Audience issuerAudience = new AudienceBuilder().buildObject(); issuerAudience.setAudienceURI(serviceProvider); audienceRestriction.getAudiences().add(issuerAudience); conditions.getAudienceRestrictions().add(audienceRestriction); assertion.setConditions(conditions); // --- TAG AuthnStatement --- AuthnStatement authnStatement = createAuthnStatement(authenticationTime); authnStatement.setSessionIndex(assertion.getID()); assertion.getAuthnStatements().add(authnStatement); return assertion; } /** * Build vector(s) representating user according to this profile * * @param userId userId neo4j * @param handler handler containing results */ private void createVectors(String userId, final String host, final Handler<Either<String, JsonArray>> handler) { debug("create user Vector(s)"); // browse supported type vector required by the service provider logger.info("createVectors init "); List<AttributeConsumingService> AttributesCS = spSSODescriptor.getAttributeConsumingServices(); if (AttributesCS.size() > 0) { HashMap<String, List<String>> attributes = new HashMap<String, List<String>>(); final JsonArray jsonArrayResult = new fr.wseduc.webutils.collections.JsonArray(); for (final AttributeConsumingService attributeConsumingService : AttributesCS) { for (RequestedAttribute requestedAttribute : attributeConsumingService.getRequestAttributes()) { String vectorName = requestedAttribute.getName(); if (vectorName.equals("FrEduVecteur")) { samlVectorService = new FrEduVecteurService(neo4j); samlVectorService.getVectors(userId, new Handler<Either<String, JsonArray>>() { @Override public void handle(Either<String, JsonArray> stringJsonArrayEither) { if (stringJsonArrayEither.isRight()) { JsonArray jsonArrayResultTemp = ((JsonArray) stringJsonArrayEither.right() .getValue()); for (int i = 0; i < jsonArrayResultTemp.size(); i++) { jsonArrayResult.add(jsonArrayResultTemp.getValue(i)); } // add FrEduUrlRetour vector for (RequestedAttribute requestedAttribute : attributeConsumingService .getRequestAttributes()) { String vectorName = requestedAttribute.getName(); if (vectorName.equals("FrEduUrlRetour")) { JsonObject vectorRetour = new JsonObject().put("FrEduUrlRetour", host); jsonArrayResult.add(vectorRetour); } } handler.handle(new Either.Right<String, JsonArray>(jsonArrayResult)); } } }); } else if (requestedAttribute.isRequired() && vectorName.equals("FrEduUrlRetour")) { String error = "vector " + vectorName + " not implemented yet"; logger.error(error); handler.handle(new Either.Left<String, JsonArray>(error)); } else if (vectorName.equals("mail")) { String error = "vector " + vectorName + " not implemented yet"; logger.error(error); handler.handle(new Either.Left<String, JsonArray>(error)); } else { if (requestedAttribute.isRequired()) { String error = "vector " + vectorName + " not supported for user " + userId; logger.error(error); handler.handle(new Either.Left<String, JsonArray>(error)); } else { logger.debug("vector " + vectorName + " don't have to be supported."); } } } } } else { String SPid = ((EntityDescriptor) spSSODescriptor.getParent()).getEntityID(); if (SPid.isEmpty() || SPid == null) { logger.error("Service Providor ID is null or empty"); handler.handle(new Either.Left<String, JsonArray>("Service Providor ID is null or empty")); } else { SamlServiceProvider sp = spFactory.serviceProvider(SPid); sp.generate(eb, userId, handler); } } } /** * Build attribute statement with specified vectors. * * @param attributes attributes containing vectors * * @return the attributeStatement */ private AttributeStatement createAttributeStatement(HashMap<String, List<String>> attributes) { // create authenticationstatement object AttributeStatementBuilder attributeStatementBuilder = new AttributeStatementBuilder(); AttributeStatement attributeStatement = attributeStatementBuilder.buildObject(); AttributeBuilder attributeBuilder = new AttributeBuilder(); if (attributes != null) { for (Map.Entry<String, List<String>> entry : attributes.entrySet()) { Attribute attribute = attributeBuilder.buildObject(); attribute.setName(entry.getKey()); attribute.setFriendlyName(entry.getKey()); attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:basic"); for (String value : entry.getValue()) { XSStringBuilder stringBuilder = new XSStringBuilder(); XSString attributeValue = stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); attributeValue.setValue(value); attribute.getAttributeValues().add(attributeValue); } attributeStatement.getAttributes().add(attribute); } } return attributeStatement; } /** * Returns SPSSODescriptor according to service provider name qualifier * @param serviceProvider service provider name qualifier * @return the SPSSODescriptor if exists null otherwise * */ private SPSSODescriptor getSSODescriptor(String serviceProvider) { EntityDescriptor entityDescriptor = entityDescriptorMap.get(serviceProvider); if (entityDescriptor == null) { return null; } SPSSODescriptor spSSODescriptor = entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS); return spSSODescriptor; } private AuthnStatement createAuthnStatement(final DateTime issueDate) { debug("createAuthnStatement with issueDate : " + issueDate); // create authcontextclassref object AuthnContextClassRefBuilder classRefBuilder = new AuthnContextClassRefBuilder(); AuthnContextClassRef classRef = classRefBuilder.buildObject(); classRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"); // create authcontext object AuthnContextBuilder authContextBuilder = new AuthnContextBuilder(); AuthnContext authnContext = authContextBuilder.buildObject(); authnContext.setAuthnContextClassRef(classRef); // create authenticationstatement object AuthnStatementBuilder authStatementBuilder = new AuthnStatementBuilder(); AuthnStatement authnStatement = authStatementBuilder.buildObject(); authnStatement.setAuthnInstant(issueDate); authnStatement.setAuthnContext(authnContext); return authnStatement; } private Issuer createIssuer(final String issuerName) { debug("createIssuer : " + issuerName); // create Issuer object IssuerBuilder issuerBuilder = new IssuerBuilder(); Issuer issuer = issuerBuilder.buildObject(); issuer.setValue(issuerName); return issuer; } private Subject createSubject(String nameIdValue, Integer samlAssertionDays, String idpNameQualifier, String spNameQualifier, String recipient) { debug("createSubject for nameid : " + nameIdValue); debug("idpNameQualifier : " + idpNameQualifier); debug("spNameQualifier : " + spNameQualifier); DateTime currentDate = new DateTime(); if (samlAssertionDays != null) currentDate = currentDate.plusDays(samlAssertionDays); // create name element NameIDBuilder nameIdBuilder = new NameIDBuilder(); NameID nameId = nameIdBuilder.buildObject(); nameId.setValue(nameIdValue); nameId.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"); nameId.setNameQualifier(idpNameQualifier); nameId.setSPNameQualifier(spNameQualifier); SubjectConfirmationDataBuilder dataBuilder = new SubjectConfirmationDataBuilder(); SubjectConfirmationData subjectConfirmationData = dataBuilder.buildObject(); subjectConfirmationData.setNotOnOrAfter(currentDate); subjectConfirmationData.setRecipient(recipient); SubjectConfirmationBuilder subjectConfirmationBuilder = new SubjectConfirmationBuilder(); SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject(); subjectConfirmation.setMethod("urn:oasis:names:tc:SAML:2.0:cm:bearer"); subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData); // create subject element SubjectBuilder subjectBuilder = new SubjectBuilder(); Subject subject = subjectBuilder.buildObject(); subject.setNameID(nameId); subject.getSubjectConfirmations().add(subjectConfirmation); return subject; } private JsonObject generateSimpleSPEntityIDRequest(String idp, String sp) { return new JsonObject().put("authn-request", getAuthnRequestUri(idp) + "?SPEntityID=" + sp) .put("relay-state", SamlUtils.SIMPLE_RS); } private JsonObject generateAuthnRequest(String idp, String sp, String acs, boolean sign) throws NoSuchFieldException, IllegalAccessException, MarshallingException, IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { final String id = "ENT_" + UUID.randomUUID().toString(); //Create an issuer Object Issuer issuer = new IssuerBuilder().buildObject("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer", "samlp"); issuer.setValue(sp); final NameIDPolicy nameIdPolicy = new NameIDPolicyBuilder().buildObject(); nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"); //nameIdPolicy.setSPNameQualifier(""); nameIdPolicy.setAllowCreate(true); //Create AuthnContextClassRef AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder(); AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder .buildObject("urn:oasis:names:tc:SAML:2.0:assertion", "AuthnContextClassRef", "saml"); authnContextClassRef .setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"); RequestedAuthnContext requestedAuthnContext = new RequestedAuthnContextBuilder().buildObject(); requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT); requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef); final AuthnRequest authRequest = new AuthnRequestBuilder() .buildObject("urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequest", "samlp"); authRequest.setForceAuthn(false); authRequest.setIsPassive(false); authRequest.setIssueInstant(new DateTime()); authRequest.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"); authRequest.setAssertionConsumerServiceURL(acs); authRequest.setAssertionConsumerServiceIndex(1); authRequest.setAttributeConsumingServiceIndex(1); authRequest.setIssuer(issuer); authRequest.setNameIDPolicy(nameIdPolicy); authRequest.setRequestedAuthnContext(requestedAuthnContext); authRequest.setID(id); authRequest.setVersion(SAMLVersion.VERSION_20); final String anr = SamlUtils.marshallAuthnRequest(authRequest) .replaceFirst("<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>\n", ""); final String rs = UUID.randomUUID().toString(); String queryString = "SAMLRequest=" + URLEncoder.encode(ZLib.deflateAndEncode(anr), "UTF-8") + "&RelayState=" + rs; if (sign) { queryString = sign(queryString); } return new JsonObject().put("id", id).put("relay-state", rs).put("authn-request", getAuthnRequestUri(idp) + "?" + queryString); } private String sign(String c) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException { final String content = c + "&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1"; java.security.Signature sign = java.security.Signature.getInstance("SHA1withRSA"); sign.initSign(privateKey); sign.update(content.getBytes("UTF-8")); return content + "&Signature=" + URLEncoder.encode(Base64.getEncoder().encodeToString(sign.sign()), "UTF-8"); //, Base64.DONT_BREAK_LINES), "UTF-8"); } private String generateSloRequest(String nameID, String sessionIndex, String idp) throws Exception { NameID nId = SamlUtils.unmarshallNameId(nameID); NameID nameId = SamlUtils.buildSAMLObjectWithDefaultName(NameID.class); nameId.setFormat(nId.getFormat()); nameId.setValue(nId.getValue()); String spIssuer = this.issuer; if (isNotEmpty(nId.getNameQualifier())) { nameId.setNameQualifier(nId.getNameQualifier()); } if (isNotEmpty(nId.getSPNameQualifier())) { nameId.setSPNameQualifier(nId.getSPNameQualifier()); spIssuer = nId.getSPNameQualifier(); } LogoutRequest logoutRequest = SamlUtils.buildSAMLObjectWithDefaultName(LogoutRequest.class); logoutRequest.setID("ENT_" + UUID.randomUUID().toString()); String sloUri = getLogoutUri(idp); logoutRequest.setDestination(sloUri); logoutRequest.setIssueInstant(new DateTime()); Issuer issuer = SamlUtils.buildSAMLObjectWithDefaultName(Issuer.class); issuer.setValue(spIssuer); logoutRequest.setIssuer(issuer); SessionIndex sessionIndexElement = SamlUtils.buildSAMLObjectWithDefaultName(SessionIndex.class); sessionIndexElement.setSessionIndex(sessionIndex); logoutRequest.getSessionIndexes().add(sessionIndexElement); logoutRequest.setNameID(nameId); String lr = SamlUtils.marshallLogoutRequest(logoutRequest) .replaceFirst("<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>\n", ""); String queryString = "SAMLRequest=" + URLEncoder.encode(ZLib.deflateAndEncode(lr), "UTF-8") + "&RelayState=" + config.getString("saml-slo-relayState", "NULL"); queryString = sign(queryString); if (logger.isDebugEnabled()) { logger.debug("lr : " + lr); logger.debug("querystring : " + queryString); } return sloUri + "?" + queryString; } private String getAuthnRequestUri(String idp) { String ssoServiceURI = null; EntityDescriptor entityDescriptor = entityDescriptorMap.get(idp); if (entityDescriptor != null) { for (SingleSignOnService ssos : entityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) .getSingleSignOnServices()) { if (ssos.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { ssoServiceURI = ssos.getLocation(); } } } return ssoServiceURI; } private String getLogoutUri(String idp) { String sloServiceURI = null; EntityDescriptor entityDescriptor = entityDescriptorMap.get(idp); if (entityDescriptor != null) { for (SingleLogoutService sls : entityDescriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) .getSingleLogoutServices()) { if (sls.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { sloServiceURI = sls.getLocation(); } } } return sloServiceURI; } public boolean validateSignature(String assertion) throws Exception { final Response response = SamlUtils.unmarshallResponse(assertion); final SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator(); Signature signature = response.getSignature(); if (signature == null) { if (response.getAssertions() != null && !response.getAssertions().isEmpty()) { for (Assertion a : response.getAssertions()) { signature = a.getSignature(); } } else if (response.getEncryptedAssertions() != null && !response.getEncryptedAssertions().isEmpty()) { Assertion a = decryptAssertion(response); if (a != null) { signature = a.getSignature(); } } else { logger.error("Assertions not founds."); throw new ValidationException("Assertions not founds."); } } if (signature == null) { logger.error("Signature not found."); throw new ValidationException("Signature not found."); } profileValidator.validate(signature); SignatureTrustEngine sigTrustEngine = getSignatureTrustEngine(response); CriteriaSet criteriaSet = new CriteriaSet(); criteriaSet.add(new EntityIDCriteria(SamlUtils.getIssuer(response))); criteriaSet.add(new MetadataCriteria(IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS)); criteriaSet.add(new UsageCriteria(UsageType.SIGNING)); return sigTrustEngine.validate(signature, criteriaSet); } private void loadSignatureTrustEngine(String filePath) throws MetadataProviderException { logger.info(filePath); FilesystemMetadataProvider metadataProvider = new FilesystemMetadataProvider(new File(filePath)); metadataProvider.setParserPool(new BasicParserPool()); metadataProvider.initialize(); MetadataCredentialResolver metadataCredResolver = new MetadataCredentialResolver(metadataProvider); KeyInfoCredentialResolver keyInfoCredResolver = Configuration.getGlobalSecurityConfiguration() .getDefaultKeyInfoCredentialResolver(); EntityDescriptor entityDescriptor = (EntityDescriptor) metadataProvider.getMetadata(); String entityID = entityDescriptor.getEntityID(); entityDescriptorMap.put(entityID, entityDescriptor); signatureTrustEngineMap.put(entityID, new ExplicitKeySignatureTrustEngine(metadataCredResolver, keyInfoCredResolver)); } private SignatureTrustEngine getSignatureTrustEngine(Response response) { // IDP Arena urn:fi:ac-paris:ent:1.0 // IDP Aten urn:fi:ac-paris:ts:1.0 String issuer = SamlUtils.getIssuer(response); debug("getSignatureTrustEngine from issuer : " + issuer); return signatureTrustEngineMap.get(issuer); } private String decryptAssertion(String response) throws Exception { return SamlUtils.marshallAssertion(decryptAssertion(SamlUtils.unmarshallResponse(response))); } private Assertion decryptAssertion(Response response) throws Exception { EncryptedAssertion encryptedAssertion; if (response.getEncryptedAssertions() != null && response.getEncryptedAssertions().size() == 1) { encryptedAssertion = response.getEncryptedAssertions().get(0); } else { throw new ValidationException("Encrypted Assertion not found."); } BasicX509Credential decryptionCredential = new BasicX509Credential(); decryptionCredential.setPrivateKey(privateKey); Decrypter decrypter = new Decrypter(null, new StaticKeyInfoCredentialResolver(decryptionCredential), new InlineEncryptedKeyResolver()); decrypter.setRootInNewDocument(true); Assertion assertion = decrypter.decrypt(encryptedAssertion); if (assertion != null && assertion.getSubject() != null && assertion.getSubject().getEncryptedID() != null) { SAMLObject s = decrypter.decrypt(assertion.getSubject().getEncryptedID()); if (s instanceof BaseID) { assertion.getSubject().setBaseID((BaseID) s); } else if (s instanceof NameID) { assertion.getSubject().setNameID((NameID) s); } assertion.getSubject().setEncryptedID(null); } if (assertion != null && assertion.getAttributeStatements() != null) { for (AttributeStatement statement : assertion.getAttributeStatements()) { for (EncryptedAttribute ea : statement.getEncryptedAttributes()) { Attribute a = decrypter.decrypt(ea); statement.getAttributes().add(a); } statement.getEncryptedAttributes().clear(); } } return assertion; } }