From ab3a680a85bee47dcfcc2335c8e1f9948a10cbd4 Mon Sep 17 00:00:00 2001 From: datb-com <48554223+datb-com@users.noreply.github.com> Date: Wed, 8 Jan 2020 20:11:32 +0000 Subject: [PATCH 01/20] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 87164cb..4a0bf64 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ # Dead Simple SAML 2.0 Client +This is coveooss/saml-client with some crude alterations to allow more flexibility for setting keys where they aren't held in files. + This library implements a very simple SAML 2.0 client that allows retrieving an authenticated identity from a compliant identity provider, using the HTTP POST binding. It is based on the OpenSAML library, and only provides the necessary glue code to make it work in a basic scenario. This is by no means a complete implementation supporting all the nitty gritty SAML details, but it does perform the basic task of generating requests and validating responses. It's useful if you need to authenticate with SAML but don't want to bring in an uber large framework such as Spring Security. @@ -113,4 +115,4 @@ To add the keys : ```java // To add the keys (is needed only if you have encrypted assertion or if you want to sign documents) client.setSPKeys(publicKeyPath,privateKeyPath); -``` \ No newline at end of file +``` From fd06350090289fedd573eaad29f0ebbce07620be Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 8 Jan 2020 21:14:54 +0000 Subject: [PATCH 02/20] Added InputStream variant to setSPKeys(). --- src/main/java/com/coveo/saml/SamlClient.java | 67 ++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index a74acaa..4e3daff 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -2,6 +2,7 @@ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -641,6 +642,22 @@ public void setSPKeys(String publicKey, String privateKey) throws SamlException spCredential = new BasicX509Credential(cert, pk); } + /** + * Set service provider keys from InputStreams. + * + * @param publicKey the public key + * @param privateKey the private key + * @throws SamlException if publicKey and privateKey don't form a valid credential + */ + public void setSPKeys(InputStream publicKey, InputStream privateKey) throws SamlException { + if (publicKey == null || privateKey == null) { + throw new SamlException("No credentials provided"); + } + PrivateKey pk = loadPrivateKey(privateKey); + X509Certificate cert = loadCertificate(publicKey); + spCredential = new BasicX509Credential(cert, pk); + } + /** * Gets attributes from the IDP Response * @@ -912,6 +929,22 @@ private X509Certificate loadCertificate(String filename) throws SamlException { } } + /** + * Load an X.509 certificate + * @param bis An input stream providing the bytes of the key. + * */ + private X509Certificate loadCertificate(InputStream bis) throws SamlException { + try { + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + return (X509Certificate) cf.generateCertificate(bis); + + } catch (Exception e) { + throw new SamlException("Couldn't load public key", e); + } + } + /** * Load a PKCS8 key * @param filename The path of the key @@ -932,6 +965,40 @@ private PrivateKey loadPrivateKey(String filename) throws SamlException { } } + /** + * Load a PKCS8 key + * @param bis An input stream providing the bytes of the key. + * */ + private PrivateKey loadPrivateKey(InputStream bis) throws SamlException { + try //(RandomAccessFile raf = new RandomAccessFile(filename, "r")) + { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + int totalBytes = 0; + byte[] onekay = new byte[1024]; + int bytesRead = 0; + while (true) { + bytesRead = bis.read(onekay); + + if (bytesRead == -1) break; + + if (bytesRead > 0) baos.write(onekay, 0, bytesRead); + } + bis.close(); + baos.flush(); + baos.close(); + + PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(baos.toByteArray()); + KeyFactory kf = KeyFactory.getInstance("RSA"); + + return kf.generatePrivate(kspec); + + } catch (Exception e) { + throw new SamlException("Couldn't load private key", e); + } + } + private StringWriter marshallXmlObject(XMLObject object) throws MarshallingException { StringWriter stringWriter = new StringWriter(); Marshaller marshaller = From 35844380628b1f0845359ef7d7616197f91f4c45 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Tue, 19 May 2020 14:10:04 +0100 Subject: [PATCH 03/20] pull from coveooss/saml-client (4.0.1SNAPSHOT) --- pom.xml | 12 ++++++------ src/main/java/com/coveo/saml/SamlClient.java | 15 ++++++++------- src/test/java/com/coveo/saml/SamlClientTest.java | 13 +++++++++++++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 6d12880..9215104 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.coveo saml-client - 4.0.0-SNAPSHOT + 4.0.1-SNAPSHOT jar ${project.groupId}:${project.artifactId} @@ -57,7 +57,7 @@ 1.8 1.8 - 3.4.3 + 3.4.5 @@ -85,7 +85,7 @@ org.bouncycastle bcprov-jdk15on - 1.61 + 1.65 @@ -96,12 +96,12 @@ org.slf4j slf4j-api - 1.7.19 + 1.7.30 org.apache.commons commons-lang3 - 3.4 + 3.10 commons-io @@ -116,7 +116,7 @@ junit junit - 4.12 + 4.13 test diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 4e3daff..354a8ef 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -643,19 +643,17 @@ public void setSPKeys(String publicKey, String privateKey) throws SamlException } /** - * Set service provider keys from InputStreams. + * Set service provider keys. * - * @param publicKey the public key + * @param certificate the certificate * @param privateKey the private key * @throws SamlException if publicKey and privateKey don't form a valid credential */ - public void setSPKeys(InputStream publicKey, InputStream privateKey) throws SamlException { - if (publicKey == null || privateKey == null) { + public void setSPKeys(X509Certificate certificate, PrivateKey privateKey) throws SamlException { + if (certificate == null || privateKey == null) { throw new SamlException("No credentials provided"); } - PrivateKey pk = loadPrivateKey(privateKey); - X509Certificate cert = loadCertificate(publicKey); - spCredential = new BasicX509Credential(cert, pk); + spCredential = new BasicX509Credential(certificate, privateKey); } /** @@ -903,6 +901,9 @@ private void decodeEncryptedAssertion(Response response) throws DecryptionExcept null, new StaticKeyInfoCredentialResolver(spCredential), new InlineEncryptedKeyResolver()); + + decrypter.setRootInNewDocument(true); + // Decrypt the assertion. Assertion decryptedAssertion = decrypter.decrypt(encryptedAssertion); // Add the assertion diff --git a/src/test/java/com/coveo/saml/SamlClientTest.java b/src/test/java/com/coveo/saml/SamlClientTest.java index be68070..3a21681 100644 --- a/src/test/java/com/coveo/saml/SamlClientTest.java +++ b/src/test/java/com/coveo/saml/SamlClientTest.java @@ -28,6 +28,8 @@ public class SamlClientTest { new DateTime(2018, 8, 16, 6, 54, 0, DateTimeZone.UTC); private static final String AN_ENCODED_RESPONSE = "PHNhbWxwOlJlc3BvbnNlIElEPSJfMTEzMjlhZjQtYTdkMC00MDkwLTg3N2QtYTJkNWNlYWRlZWU0IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNi0wMy0yMVQxNjo1MDo0Ny4zOTlaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9sb2NhbGhvc3Q6ODQ0My9yZXN0L3NlYXJjaC9sb2dpbi9hZGZzIiBDb25zZW50PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y29uc2VudDp1bnNwZWNpZmllZCIgSW5SZXNwb25zZVRvPSJ6ZjE3MDkyNGItZjVlYy00Y2I1LWE5YWUtMmFiMmNmZDcxNGQzIiB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj48SXNzdWVyIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwOi8vYWRmczAxLmRldi5jb3Zlby5jb20vYWRmcy9zZXJ2aWNlcy90cnVzdDwvSXNzdWVyPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIgLz48L3NhbWxwOlN0YXR1cz48QXNzZXJ0aW9uIElEPSJfYTg4MGU1M2QtMTVhMC00ZDNiLTk5NDEtZWExMWY4MTBhODhkIiBJc3N1ZUluc3RhbnQ9IjIwMTYtMDMtMjFUMTY6NTA6NDcuMzk5WiIgVmVyc2lvbj0iMi4wIiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+PElzc3Vlcj5odHRwOi8vYWRmczAxLmRldi5jb3Zlby5jb20vYWRmcy9zZXJ2aWNlcy90cnVzdDwvSXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIiAvPjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2IiAvPjxkczpSZWZlcmVuY2UgVVJJPSIjX2E4ODBlNTNkLTE1YTAtNGQzYi05OTQxLWVhMTFmODEwYTg4ZCI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIiAvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiIC8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiIC8+PGRzOkRpZ2VzdFZhbHVlPlRlbzBFdk5kU1BLUVZsV0R4bVJ1RlBPU3pFS0ROYU5TMzllejIybGJDdVU9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPnFXOW1wK2tPNTdvK2k3cUJ5RXhsQmZUYnlnTHVENjU2N3RibWlodXFOQ3lxNnZQbFp4WW9XQkIybHpYR2VmaDh6cTRYOStHcWtxMXhSVElDemNNUmhjYTZPaVF2eWd3NWQzZ2NtLzh3bG9remFZQmJDeHdzTUFpNUMwMk4xb3hqTXZsU2xOdkUzN0piMXI5cDdyOGZNeEJreVBwUFFDa3RRYnFLUXk3TTBvWmhQaVpjMVRpMXZ3c0xvbWJVc3hCVzl0RzJ5WTlKVU9QK2dKak82SStUV2IrS0lzWTBGS21pN1hXK3dmSDNpaTI0RTFUVkh5LzYvandtUzlhVjJrZ2RjVXNvN3FYZVpQZ2JsTy9JM2VaQzBHQUp1bFErcEtjS2V2ZEd2c2JWM25HQmY0M3BZcnVzRTM1ZXo1WTRBdFNiNjRUaE1mT1I1c3lER0lpTkEzL29IZz09PC9kczpTaWduYXR1cmVWYWx1ZT48S2V5SW5mbyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQzVqQ0NBYzZnQXdJQkFnSVFjd0Y4ektkZ2hMRkRKWUtNdW5heGpqQU5CZ2txaGtpRzl3MEJBUXNGQURBdU1Td3dLZ1lEVlFRREV5TkJSRVpUSUZOcFoyNXBibWNnTFNCaFpHWnpNREV1WkdWMkxtTnZkbVZ2TG1OdmJUQWdGdzB4TkRBME1UUXhOVEF6TkRaYUdBOHlNVEUwTURNeU1URTFNRE0wTmxvd0xqRXNNQ29HQTFVRUF4TWpRVVJHVXlCVGFXZHVhVzVuSUMwZ1lXUm1jekF4TG1SbGRpNWpiM1psYnk1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDenJidEhBMklMcnpUTmsrU2ZEd1dVaG42Rk1uVDFFZUFianNYaDVwd3NCZmUwOGhobDJXTWZIWktGZlNVNk1wRk0xVDdlNERjSDNINldibmY2WTNUeG82aVI2ZWpRaExxWVdPQlNTSFM0T0hXeE1hY3o2MUViOFc1MXhwOW9DZnpocmFJSnJJeFhKcXJFVzhZVkZObmtrUTg0UUxYZVpPT3RWUnE0UTJ5azNOUE56RUF6aVlUazRoK01WQlJ5SUwvaFFjcTcrRGVhTE0weDRUZnY4c1VHVU9QQThjMEVybXNFVURrS3pxM242dENCZG05SEFielVWcU5FenBQcEs0T0ovR01zdHFyeXF0dStPYzJ4ZERMMVZZTVhZU1ZzbHJIRFc1b2ZWTGlML3kyQS9BMXpBNmRHbExOZm1WV1JwOGJIS0ZpVlJabTFrKzlmYzNicHdnSVJBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFGSWN1M09FQ1JEY1paT3BWaUNFRy9vRWU2UDJaWUZBSlJOUVNiS3cvUWhQOWlJVDJwbGJod3p0S0ZzckFoSTZmOXI4a2VDNnhmVitnMzdRWHJyL2dWVTR5SGIyUFQ4YjllYStuWStWM2FNeCt5RlF3K1djd3A0U1k3cTRMTnc0RVA4aHR6ejEzZnRiTTh0SUN1bytneGpLQ2FjZkhVZmFIOUZqUWRTUExrejNWZGZiSTVrbUdFc1RCVzEvQzBNR2cwc2o1MnkwM1BFYWxQQ09oRmNla01nU1hPdmh2enN0WkhFaENBaEtlbkdaME9iQ0I5RHZhUHFzN3ZiUlBtTUdFVjJwbUU0MHVqRlRORHBzNUVTaCs5MFk5Slh1U2lUVEpLTnB2K1ZhRmIyQnAyOWZuWXR3SGVXQXBWeXppdENsQlZqbFN5Z3l0dGliTjl0d2xMOXFNVnc9PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L0tleUluZm8+PC9kczpTaWduYXR1cmU+PFN1YmplY3Q+PE5hbWVJRD5tbGFwb3J0ZUBjb3Zlby5jb208L05hbWVJRD48U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBJblJlc3BvbnNlVG89InpmMTcwOTI0Yi1mNWVjLTRjYjUtYTlhZS0yYWIyY2ZkNzE0ZDMiIE5vdE9uT3JBZnRlcj0iMjAxNi0wMy0yMVQxNjo1NTo0Ny4zOTlaIiBSZWNpcGllbnQ9Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMvcmVzdC9zZWFyY2gvbG9naW4vYWRmcyIgLz48L1N1YmplY3RDb25maXJtYXRpb24+PC9TdWJqZWN0PjxDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNi0wMy0yMVQxNjo1MDo0Ny4zODNaIiBOb3RPbk9yQWZ0ZXI9IjIwMTYtMDMtMjFUMTc6NTA6NDcuMzgzWiI+PEF1ZGllbmNlUmVzdHJpY3Rpb24+PEF1ZGllbmNlPmh0dHBzOi8vbG9jYWxob3N0Ojg0NDM8L0F1ZGllbmNlPjwvQXVkaWVuY2VSZXN0cmljdGlvbj48L0NvbmRpdGlvbnM+PEF0dHJpYnV0ZVN0YXRlbWVudD48QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL3VwbiI+PEF0dHJpYnV0ZVZhbHVlPm1sYXBvcnRlQGNvdmVvLmNvbTwvQXR0cmlidXRlVmFsdWU+PC9BdHRyaWJ1dGU+PC9BdHRyaWJ1dGVTdGF0ZW1lbnQ+PEF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNi0wMy0yMVQwOTo0NjoxNy4yMzFaIiBTZXNzaW9uSW5kZXg9Il9hODgwZTUzZC0xNWEwLTRkM2ItOTk0MS1lYTExZjgxMGE4OGQiPjxBdXRobkNvbnRleHQ+PEF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9BdXRobkNvbnRleHRDbGFzc1JlZj48L0F1dGhuQ29udGV4dD48L0F1dGhuU3RhdGVtZW50PjwvQXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+"; + private static final String AN_ENCODED_RESPONSE_WITH_SIGNED_AND_ENCRYPTED_ASSERTION = + "PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfMTEzMjlhZjQtYTdkMC00MDkwLTg3N2QtYTJkNWNlYWRlZWU0IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNi0wMy0yMVQxNjo1MDo0Ny4zOTlaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9sb2NhbGhvc3Q6ODQ0My9yZXN0L3NlYXJjaC9sb2dpbi9hZGZzIiBDb25zZW50PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y29uc2VudDp1bnNwZWNpZmllZCIgSW5SZXNwb25zZVRvPSJ6ZjE3MDkyNGItZjVlYy00Y2I1LWE5YWUtMmFiMmNmZDcxNGQzIj48c2FtbDpJc3N1ZXIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPm15aWRlbnRpZmllcjwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMjU2LWNiYyIvPjxkc2lnOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PHhlbmM6RW5jcnlwdGVkS2V5Pjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNyc2EtMV81Ii8+PHhlbmM6Q2lwaGVyRGF0YT48eGVuYzpDaXBoZXJWYWx1ZT5HcTg2dzVvbWUzQ2RtdVNBNnVGTXNGVk1Vc3NEMU1YSWk1UkY1RWt2dkhFQ0RRQWlzdVYyM2RzRkRIWHlGWmlNTEhqWXl1SFdaWDdlTlExOFY3UUJCM0o4ZnZ6d2RkMjNSbDIzYkEwcENnb2pza1dFQ2VpR3hVNFp0ck9CLzYxWDIvYlNWOCttQ3FidUFFR3Q4WE0yREVBQkFiMUpMUGJZcWpZbXRWU3pCNUNRNTVTL0xqeGZDR2V5dHhHUzV6NS80QVpwb004cVVhUnhVa0k2NkpBS01CRjhzUzkyd3g5NGM0U1AzSlFpamFVM3BpQm1wOUhSUitlL2YrbkZjUWZ3Y0s1MUl2WndURHUzcGIrbFRBVjRuVDhNYzFYdjU1aWtON3ArNG9UbXl2UXB3T2JUV1JpT2tmS2UwM2VlVFNQcXVmcE5HTUlCbUx2emlDZmFXdmloUEE9PTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHNpZzpLZXlJbmZvPjx4ZW5jOkNpcGhlckRhdGE+PHhlbmM6Q2lwaGVyVmFsdWU+MlJpcjljUDBIdWc2VTdpWkNlb3JDVmdXT0cvVE1qMGE2Rlc2UzcrNkI4bVZCSFBrNGE5UUNjRHVBZ2xoQVU2V1E5a1lxeDdEK1g1T0FPaytwOUhjR3d0WlZNNUQ0aTRDVGFTWDB1Q050TVNQUWZEOTNOdE0xWGYvT2tiWE1oSnRSRm01Q3JtT3NSVDkvUEdFNjNYc2V5S3lXR0duTlljOFpxUi9MV2NJVjM1K0diVC9yTDd0U21lUVpmWWMxMFZXWHNrN202eEVJR1lQNXVVOXZzWVZuN3lUOGJ6N3FUeFNTSVlNQVF3endUQjRVMVZQblA0YlZ4ckQ4MU9FRUZmN2wzUjZ4MUlXZmhFQzNwUDVFSFBoZFJaODFNZGZWLzlxUWVlN3FuL0V3L0psdlZ5MkVBMkpxSkZ1MUMrdWJlTk8vdXJrMVJXczA1WnhuVlFweXhvSVRQU0lKQjJIeWwvRFRvNzJEZWNnVjhrYmxKMDJnY2VZOXFtT2k4WDc2dTNhblQ0RFl3M2VhM3NDUGsyUXllUGc2U2NXcG1LSFhsbVZpb1dhcnl6Wkx3ak1aRDVwMWRTVGV0VXBGOGsrQnNkQkJNdXY0eTBaYzJVcjRzUWo5V0xLVVZlemQ3Vm9uRUpUUjBqUVgyK0NsZnNwQ3BsSEVyaXhZVTBEZ0s3MXFOTWJDa3BFMnRPWFRLaXFtRkttWDE1QitubjM2bWJNa1FLM3NkZyt4Q1Fyc2Nmc3JhK1lJRlltdmdENlllV3J5TUFrTXpvS2hxQ0QwQklONU15eEwwd1ZKRUxza2UrRy9TRXpjL056cU4yQ3QzbjlQMFlNWUptRHpBSjVOUXhqK21VNElvMmJzN1dTV2pYVEZYd3ZieUF0WFB1bWpGVUZFcnB4Zk5xaG9iNG1xci8vZGpScGx4K0lDekgveitJSlNRYmVQa1hYaVN3WFdxaFREMUo2K0VzenpkWkJlTE96ZHdrOG9LdG5zSWRzMjVhYTc1eTA5Y2Y0Y0psNUNpYVhiQTB2MERudXZBQlo2TzJlZG5ocXlaSWlFQUpwSWFnNkVwN1E3SkFXaFF3YUlpUi9sOU9DWi9oWng3dUdEbXcybGdyMURiYzlHWWQxTndDR2M2QmlhcXUyU0dhSWkzSkRLK1Y1QXN5ems1aFBxUlBSdG9pM3Bha0doNjcxVHplaW55c1dVUzlJdC9XNm5oSHlnUTNmM1Nxd3JVSkRZVkZndnBPVjM1cWZuSGE0QWhhQlczeXNTdEphOGNDT1lzTWk4WVBSVUdGZytNY05CamFvK3VzZTdEM2NWMVl1UTJLVHZadlo2MlM2TXFEclFoT2lKMGdVTVdNaWlQOUNsc0VTNjcwYUVPeTQ5R21jOXBqK01FS0ZDbkVvTTY5REZxaFhjS0UvbzJFRFhqc3lDc3MzMmxXRTdYekRoWkdLa0FobndKemdSR3RJRXlVVkFMUVhTcGdwMnF0Wm9UdjJUT0tVMG5kOWRkMUNBZFMzRmR3SzJyYVo3eGZ4K2ZKRy96ZnJIUDlvenR1R1gwd2Z4RkFCU0ZibHlsWTkydlF0WWNiVXpFd3o1MENoMVVqei8rZXFqcUVMaUhoYlVmSTFkbHhhdVZjNGRId29FMDhiejI2SG90VGt0a05qclRQSGZaWW5scit3UVF4aGR0TmVSeFl0MHVjSUczYlUrT2J4V3pHWFp3eWRPRzJDSnNQSlNDV24ybE1rNmJqWG45NUk2YWljaFM4Rjh2T3R2U0REZUlwY2xvZEFRUEJpa1dib0VCbi95bWlQWjZpVzhPNW5Ga1FQVWhBSFRBZ1AxWWRHUkZEa1JBeUFmaFluWEdHQnRDRmx3bk0xM0xLU2ZlbkpMRGhUa040R2pzR3VocEVSNHcyVEVKRUNDOHFwamJuWEVseWNzV0JJYjdRS1dPeU0rM2tEWmtOR01iV0gzYUhSVXNNRm94dDhYZDhiYUpjZ1FMa2xYakIrUzljY2ZsZkhRSjMvcmFaNU5LNnkxemZ1Z1JNNkNyTFZtQ21MRzU3WjUvdVp4dHZpZmJSYjBwWWNUcWtiV3NvUjJzN2FJN0VpWXdBRi9FbG5SWTNHVVQxNHhjaUNwZG1SYXIwaTZudnN5UUdmb3ZmbU5pL1l5Q29Zby9LQ1d2SThUZFFucFJVQ083cVdCdUJrQUhna1JrczVNamIxcHRhZmNvTGphTHpvaEVadDZ0SUNxZHVNaytKUXNQMzNvSy9XSEdKZkI2ZzNXR1NlUlhJU1g3TU52blBjVkdpciszczZ3TG5JUDFDQzVsa0V0VmRhWjE3OVNEL0NRNU51YkxscHRDcms4a1FrdzVnYUpGMFFUSWhsRFFHS0xzTkVrS2NwZWZ0RU9xRHB2REZSYlFmelhJWkN0TUtpWVR0RHJPYkhsT1J4TU1vaXJIbytDZmhkOUVnMzcxWGFRWmpJWXlUQzFTNkdQRVBsUHMwSEFyZ3RQQ2NVYms3WnRxdmc2YWRETnQxejgyRjg0SldZK2l5WGJyck5uQ1VXZ01uZnVwVDRnNVlaT2x5d2ZLZnhJMHZyQ1k3bkk0U1hQc3dYakNTZnkzR09MTGVGK2FRaWgrN2hmdEJlR0lnbndxK3lidmREdFJpNjE4WkY5TGpnOU5YdmpSZHlEc2RoMzRWK1BIOHAvdWVqSFd2VXdWVUlQcXJxMEhzbldCTWdobzNyakR6VWt0UnBwR0cwdFZXSWhrYllteW9YVUFpVnQ3NFpkM2tnNlRLdTlLK0trbDRYemdxL0dobUtGWEc4WHkzK3BuUGd3UVhXdkNybmhKUGRIRzlhZTZJQzM4amE0bTg2VFVwZ2Nzc2lFRHZZRzUybjhlb0dneUZObEFJUEJSN0xjOFpYN2UreFExU29xRnI5Sm12YTV2Z2ZtRTBvbFZHdytuQ3hjOUdNeU8zU0ZnVkUySmpoUktRMDBuYit1QWFQb0hDSXcxcml3QjdxUGtaMnZVQzQ3N0FiNEY0Y3UvampSczZuRWV6ZGlXUWVyaDM2NE5OSTJXeFRPeEpmdkdxZTlOeG1kYVdKeGtlVi9PbHUwYWZ4b0VDTTl4NFZESndaQVdKcDIzWjF5WFBuNTdWTXJaTi8wR3N2YlkvdndIM2dHYS9QYVpyamJsajc5eW9uY3R0WTRhaTF4N0tCK0tibFlBVkVyQXNMZjJ5amg1MzJqd1dsbm5oYmdiVFEvVHo5VmJBejZzSStHbXBsQk01ZjN5UFJ6bUxObjRNY3QzSWpOL21DT1hwbkUrV0l1WkR3V2k4c0kvTXdudjYxcjY2MDM4dWpWNCtJQmNPa0Y2SUVpN0pkaVJZMnErUHpUMVN3OFFoZlQ2ZVdjak9rSjRHNTFZY1ZIUFNRenZoQ05FSThwakU4RkEydUEwKzZhbHdBRkxkSGFZUzZJc2ZuWlNaS2pLeUZKTjhlc01Yd3BNY3o2WjRwUzZDR01tL2RJaFREdjNYTU12SUZ3MmdpelVOTnhtTk9QVEJoTUdyTk1BTUdaYTh1M3NLMDVtZUxSaDc0djFPREwwN1FGSXRTaGNqZGYrY3lPczVzelZna3pxQ0ZzNjkraG81RDI3bTM0OXduQlNZdTEwaDZiNEY5bWpacmhsbXJNb0xaWnhSMGtsWlA1d1FYakgvVVA3Q0M3a0hBSmtHbm5Yd2c1UkNtMnpJc2REWWRTOW9nM1pUVUY3M0xTakY3ZWZmWEVZZkdEWG05aUc2eUgza010VkxhSEdTdVRXTGlSdXFJdmlCc1Bya3B3RnJwMnVXY1E1ekFmVW1GQjJDOGZzeFlPMTJZMTBnY2tuQjRzbjFSK1RYaXBsbTZyNEZFQzI5bmpmUTRUUHNYc2NYYWkzbmQrUmN2THlMdy9XZEJxZ3I0NEZhaS9TUldyMXJrcnRyZWhzaWJ5ODFXU1JtUnNKcHM0WUhQME5TREg4cGloWXFUSzh5bjNSeFNORWtOS08vMWd0SEFtdnRoZE1weGRudXVEa1pkVnRWWDNsVHJUWTJldXJIbDVCakg5RXgyQnJ2aVI3aWxVZVVXUEJicWw5SWZwR0pPRGZDYUt2QVZlSEhtYzVSVUdBMGZzRWZVYytvTzZZTVAyc1NKa01WRGJWSlBHZFdHejJJSzArb1hnZXlvY1RiZllFbElTbnUrTFVid1RVbFFycEQzZURYVGhhRUhUK1lYQTlCWmJZeW9aMzJUdEU4bVpQK3dud0drM01aVDJLcTZNZ1dhNmpsMVl4UkF5YzBDVFdPY1VlL2FTMWtPS0RQMlg2OUZKS1hBVzdMbzRMTHk4Mm0wWkE3K3FYb085N2ZWdHdyM0pNc0NsWGhKY1kzaC96UHFhQUVxbk9zQzg2MnczT2thbmNpd29KWWlhdnJXZVpFUWVwSW9sYWtPRyt1RHFDYmRtUVBQak1SZC9lMGttcytrWURZRlUzVW91ZGxPS2ttTEVuWitaWUp3NDB0ZUdCdUxjamhGenkvcW1NT24rR0p1QitSUER3QmJTRXo3ZHhCVHFvZUZHTkdnKzhPZkptWlVVSFBrdlJLa2hEQTlLVWQzVEZGRlVpemlTeUVDajJJUjZZTzBtOGVYdkdJQVVkdDRHallTOFhqTlRwSVJ5cGJOTDd6SmU3WGtYUEJXcDFqMjhzRHlJZkI1aXhwWllVemh1N0ZtWUpid1Q4NUFQdmtzRTNISzRSNnd6U2pwRVJicXo4eHRUWnN6RE4wSHE1M05LdXIwcFpLQytZR2d3djZpWXFKdlZkQ01XV25JSG5vb0YvWVl5ZHNka01ybzBsd3NXR2kxYXBhVnlGdFhHVnA1ZkRDWXJjYmFJNXE5WUUzQmE1d3M4MzRyaU16amJjOE1NMWMyOURMZkVxRUk1ck9xR0svZ1lzRGxsSmtITUpIdlhEcFlCVFo4ZDlvTDlLZmlYQUlNYjFCZE80UzlMMDFvVGt2ZnlxSExRd1dNN0ZIUDBGWWp6emdEL1FrNms4cVN5bnZOanFKbGw1MzlvbzMyNUIzUTA5cS9mSis4R0xSWTJCZDhQNkFrT1ltVkwyU1NhWjJadnBUMDlCckN1Mko2REY4QnpOYkpYWXB1Tzk3T0RPdkJwQVpacy9WNVloQm9lSlNGenV4Z3hYSzVuYU5tTUFQMTBKVlRWcnA1UktLRjJYQ3FiSisxSDJyOVhOQ1VzTTBDRWdRWGg3d09wSzhLczBxWkdISUtUNnhGV2dsT2d3SHZnVVdaYlpZbGxFU3gzS09jSTEyYXJOTkJzVk1abUJqQXp2ZTA3UzU3bWxkdktzU1JBZzdZcTR4cUptL2dpUWxpOU5hT240NnZIc2c0ZVN6R2ZyVDdQZGxRRWthcW1xem1jSjZOMTRPT0UzcDZ5UnVFUkNVeEtzbm5WZGdEMVVsaVhsZG1zcDlpMmZ3VkxMZUJBS2NOdFFYdnZPek9POWV4ZHhFNzdqZU1FNDZJU0gvTkVhMlpycVYyWVJCcFhWU2YvTHlXSjRJem56Nm9vdEJZWHltZi9tMm1MZThRRncrVDQzUG1EZFJWQlJXTG5pR1MwMnVmSDhBMU1jNm5ERDdiQWNyQStqNyt4TzJ4NitwQi9tZW5tMjNKUCs3YWZEWDNyWTBDVksvUGRFcS9PU3ZyOU9ETVZQSUtSNThTaUhzUmNtSTVXQ0hoT3hnZjJWNEtmamdaNXVzd2ozU1N0UXdxYkxtbi9FZGdSc2FERkN3YVpCNGZGUFE2cVYvdmlwbkFWU0d0Vk0zT1JNMStpQ2VFQUN4eEdjV2FXZU5ZYldlWFRUWWRjV1JDR3Q4cVFWYzIwUmRPWWhsS2lPZUVDejZrRzNCVzZBZW5wTnRrdXVIRklKQ3VqRFFWUXRYSGtwMzBpc1FWcnltRjJNY3VZMm0zZmhCYndvN3gxalFXZmsrd09UdTRTNFYvUXZ2M2VVVUJpeFR1UDRYaVg4Tml2anloR0RmS0FiN0FyTXBjNERXWk1zb29NNVFGRWNJejN1UXRGS1Y0WXZBMHZxVFM5Y08wNElLR21BVU0vVFNuSGZ2clJDZGEvTm9Ga0x3Lzh3UnNJUHU4Umtaa1FSWG9GdFI3c092NVRURGtiMTNGM0xLbEJaQlIwM2l6c1ZXQTVCbFplT2xvUEpNV3ltdUhzQytkbzIrY2d6bk9jOTNMcVEwd3dObWtvdHNuK29ndDRFdStHV2VIYS9rY2pDRGl5QXlPdmc1aUZKM0QyYXI1ejBuYnJSMnRuOVBHdTJzODRFTTJacUlnWkVGY1Q3TmpUQkpSTW9yMUxtdC9vVFU2R2hqNDNURkhLQXpBWEZyeHlqbnFxVnJzM2RFWm1KYzhMMzI2ME54WjE5WlRURk45RHg5Y3M2bXlyYkdjS000PTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkRGF0YT48L3NhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+"; private static final String AN_ENCODED_RESPONSE_WITHOUT_SIGNATURE = "PHNhbWxwOlJlc3BvbnNlIElEPSJfMTEzMjlhZjQtYTdkMC00MDkwLTg3N2QtYTJkNWNlYWRlZWU0IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNi0wMy0yMVQxNjo1MDo0Ny4zOTlaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9sb2NhbGhvc3Q6ODQ0My9yZXN0L3NlYXJjaC9sb2dpbi9hZGZzIiBDb25zZW50PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y29uc2VudDp1bnNwZWNpZmllZCIgSW5SZXNwb25zZVRvPSJ6ZjE3MDkyNGItZjVlYy00Y2I1LWE5YWUtMmFiMmNmZDcxNGQzIiB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj48SXNzdWVyIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwOi8vYWRmczAxLmRldi5jb3Zlby5jb20vYWRmcy9zZXJ2aWNlcy90cnVzdDwvSXNzdWVyPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIgLz48L3NhbWxwOlN0YXR1cz48QXNzZXJ0aW9uIElEPSJfYTg4MGU1M2QtMTVhMC00ZDNiLTk5NDEtZWExMWY4MTBhODhkIiBJc3N1ZUluc3RhbnQ9IjIwMTYtMDMtMjFUMTY6NTA6NDcuMzk5WiIgVmVyc2lvbj0iMi4wIiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+PElzc3Vlcj5odHRwOi8vYWRmczAxLmRldi5jb3Zlby5jb20vYWRmcy9zZXJ2aWNlcy90cnVzdDwvSXNzdWVyPjxTdWJqZWN0PjxOYW1lSUQ+bWxhcG9ydGVAY292ZW8uY29tPC9OYW1lSUQ+PFN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJ6ZjE3MDkyNGItZjVlYy00Y2I1LWE5YWUtMmFiMmNmZDcxNGQzIiBOb3RPbk9yQWZ0ZXI9IjIwMTYtMDMtMjFUMTY6NTU6NDcuMzk5WiIgUmVjaXBpZW50PSJodHRwczovL2xvY2FsaG9zdDo4NDQzL3Jlc3Qvc2VhcmNoL2xvZ2luL2FkZnMiIC8+PC9TdWJqZWN0Q29uZmlybWF0aW9uPjwvU3ViamVjdD48Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTYtMDMtMjFUMTY6NTA6NDcuMzgzWiIgTm90T25PckFmdGVyPSIyMDE2LTAzLTIxVDE3OjUwOjQ3LjM4M1oiPjxBdWRpZW5jZVJlc3RyaWN0aW9uPjxBdWRpZW5jZT5odHRwczovL2xvY2FsaG9zdDo4NDQzPC9BdWRpZW5jZT48L0F1ZGllbmNlUmVzdHJpY3Rpb24+PC9Db25kaXRpb25zPjxBdHRyaWJ1dGVTdGF0ZW1lbnQ+PEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy91cG4iPjxBdHRyaWJ1dGVWYWx1ZT5tbGFwb3J0ZUBjb3Zlby5jb208L0F0dHJpYnV0ZVZhbHVlPjwvQXR0cmlidXRlPjwvQXR0cmlidXRlU3RhdGVtZW50PjxBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTYtMDMtMjFUMDk6NDY6MTcuMjMxWiIgU2Vzc2lvbkluZGV4PSJfYTg4MGU1M2QtMTVhMC00ZDNiLTk5NDEtZWExMWY4MTBhODhkIj48QXV0aG5Db250ZXh0PjxBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZFByb3RlY3RlZFRyYW5zcG9ydDwvQXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9BdXRobkNvbnRleHQ+PC9BdXRoblN0YXRlbWVudD48L0Fzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPgo="; private static final String AN_ENCODED_RESPONSE_WITH_INVALID_RESPONSE_SIGNATURE = @@ -245,6 +247,17 @@ public void decodeAndValidateSamlResponseRejectsNowBeforeNotBefore() throws Thro assertEquals("mlaporte@coveo.com", response.getNameID()); } + @Test + public void decodeAndValidateSamlResponseWithEncryptedSignedAssertion() throws Throwable { + SamlClient client = getKeyCloakClient(true); + client.setDateTimeNow(ASSERTION_DATE); + + SamlResponse response = + client.decodeAndValidateSamlResponse( + AN_ENCODED_RESPONSE_WITH_SIGNED_AND_ENCRYPTED_ASSERTION, "POST"); + assertEquals("mlaporte@coveo.com", response.getNameID()); + } + // Test for https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html @Test public void itIsNotVulnerableToXXEAttacks() throws Throwable { From ca423e5d5cf82599300e7a81b8a253d776d465b8 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 20 May 2020 12:33:39 +0100 Subject: [PATCH 04/20] Added support for Redirect binding for SSO. --- .gitignore | 3 +- src/main/java/com/coveo/saml/SamlClient.java | 134 ++++++++++++++++++- 2 files changed, 129 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index d140809..6774b28 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ target .project .classpath -.settings \ No newline at end of file +.settings +.DS_Store/ diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 354a8ef..52b5d3c 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -11,8 +11,11 @@ import java.io.RandomAccessFile; import java.io.Reader; import java.io.StringWriter; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -26,6 +29,8 @@ import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; @@ -295,6 +300,12 @@ public SamlResponse decodeAndValidateSamlResponse(String encodedResponse, String */ public void redirectToIdentityProvider(HttpServletResponse response, String relayState) throws IOException, SamlException { + + if (samlBinding == SamlClient.SamlIdpBinding.Redirect) { + response.sendRedirect(getRedirectURL(relayState)); + return; + } + Map values = new HashMap<>(); values.put("SAMLRequest", getSamlRequest()); if (relayState != null) { @@ -304,6 +315,67 @@ public void redirectToIdentityProvider(HttpServletResponse response, String rela BrowserUtils.postUsingBrowser(identityProviderUrl, response, values); } + public String getRedirectURL(String relayState) throws IOException, SamlException { + //From https://medium.com/@sagarag/reloading-saml-saml-basics-b8999995c73e + //- RelayState is supported but should be limited to 80 bytes and it should be appended to the URL with “RelayState=value” format. + //- The SAML request is first compressed using DEFLATE compression mechanism and then encoded using Base64 finally it again need to be URL encoded. + //- For the request signing, it should use DSAWithSHA or RSAWithSHA algorithms. + //- HTTP Cache should not be used with this binding. + //- SAML processors should not use HTTP error codes instead SAML error coded need to be used. + // + //Following are the URL parameter names supported in this binding. + //- SAMLRequest — SAML request message + //- RelayState — RelayState value + //nb. these are additional to POST... + //- SAMLEncoding — indicate the SAML encoding mechanism + //- SigAlg — Signature algorithm identifier + //- Signature — Base64 encoded signature value + // + //Example: https://localhost:9443/samlsso?SAMLRequest=nZPBjpskDyneY4s...&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=VoTUhZQVI1y... + // + //From: https://www.oasis-open.org/committees/download.php/35387/sstc-saml-bindings-errata-2.0-wd-05-diff.pdf + //1. Any signature on the SAML protocol message, including the XML element itself,MUST be removed. + // Note that if the content of the message includes another signature, such as asigned SAML assertion, this embedded signature is not removed. + // However, the length of such amessage after encoding essentially precludes using this mechanism. + // Thus SAML protocolmessages that contain signed content SHOULD NOT be encoded using this mechanism. + //2. The DEFLATE compression mechanism, as specified in [RFC1951] is then applied to the entireremaining XML content of the original SAML protocol message. + //3. The compressed data is subsequently base64-encoded according to the rules specified in IETFRFC 2045 [RFC2045]. + // Linefeeds or other whitespace MUST be removed from the result. + //4. The base-64 encoded data is then URL-encoded, and added to the URL as a query stringparameter which MUST be named SAMLRequest (if the message is a SAML request) or + // SAMLResponse (if the message is a SAML response). + //5. If RelayState data is to accompany the SAML protocol message, it MUST be URL-encoded and placed in an additional query string parameter named RelayState. + //6. If the original SAML protocol message was signed using an XML digital signature, a new signaturecovering the encoded data as specified above MUST be attached using the rules stated below. + // + //SAMLRequest=value&RelayState=value&SigAlg=value "Finally, note that if there is no RelayState value, the entire parameter should be omitted from thesignature computation (and not included as an empty parameter name)." + // + StringBuilder sb = new StringBuilder(); + sb.append("SAMLRequest=").append(URLEncoder.encode(getSamlRequest(SamlClient.SamlIdpBinding.Redirect), "UTF-8")); + + if (relayState != null) + sb.append("&RelayState=").append(URLEncoder.encode(relayState, "UTF-8")); + + sb.append("&SigAlg=").append( "http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1" ); + + byte[] bytesToSign = sb.toString().getBytes("UTF-8"); + try { + java.security.Signature sig = java.security.Signature.getInstance("SHA1withRSA"); + sig.initSign(spCredential.getPrivateKey()); + sig.update(bytesToSign); + byte[] signature = sig.sign(); + sb.append("&Signature=") + .append(URLEncoder.encode(Base64.encodeBase64String(signature), "UTF-8")); + } catch (NoSuchAlgorithmException | InvalidKeyException | java.security.SignatureException e) { + throw new SamlException("Unsupported algorithm. " + e); + } + + sb.append("&SAMLEncoding=") + .append( + URLEncoder.encode( + "urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE", "UTF-8")); //optional + + return identityProviderUrl + "?" + sb.toString(); + } + /** * Processes a POST containing the SAML response. * @@ -706,9 +778,11 @@ private RequestAbstractType getBasicSamlRequest(QName defaultElementName) { /** Convert a SAML request to a base64-encoded String * * @param request The request to encode + * @param binding Whether we are doing a POST or Redirect * @throws SamlException if marshalling the request fails * */ - private String marshallAndEncodeSamlObject(RequestAbstractType request) throws SamlException { + private String marshallAndEncodeSamlObject(RequestAbstractType request, SamlIdpBinding binding) + throws SamlException { StringWriter stringWriter; try { stringWriter = marshallXmlObject(request); @@ -716,18 +790,52 @@ private String marshallAndEncodeSamlObject(RequestAbstractType request) throws S throw new SamlException("Error while marshalling SAML request to XML", e); } - logger.trace("Issuing SAML request: " + stringWriter.toString()); + String requestStr = stringWriter.toString(); + logger.trace("Issuing SAML request: " + requestStr); - return Base64.encodeBase64String(stringWriter.toString().getBytes(StandardCharsets.UTF_8)); + byte[] requestBytes; + if (binding == SamlClient.SamlIdpBinding.Redirect) { + try { + //For a Redirect we compress (deflate) the bytes before we Base64 them + InputStream input = new ByteArrayInputStream(requestStr.getBytes("UTF-8")); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + Deflater def = new Deflater(1, true); + DeflaterOutputStream compresser = new DeflaterOutputStream(output, def); + int len; + byte[] oneKay = new byte[1024]; + while ((len = input.read(oneKay)) != -1) { + compresser.write(oneKay, 0, len); + } + compresser.flush(); + compresser.finish(); + requestBytes = output.toByteArray(); + } catch (IOException e) { + throw new SamlException("Failed to deflate request for redirect.", e); + } + } else { + //For a POST we just Base64 the bytes + requestBytes = requestStr.getBytes(StandardCharsets.UTF_8); + } + return Base64.encodeBase64String(requestBytes); } /** - * Builds an encoded SAML request. + * Builds an encoded SAML request for the POST binding. * * @return The base-64 encoded SAML request. * @throws SamlException thrown if an unexpected error occurs. */ public String getSamlRequest() throws SamlException { + return getSamlRequest(SamlClient.SamlIdpBinding.POST); + } + + /** + * Builds an encoded SAML request. + * + * @return The base-64 encoded SAML request. + * @throws SamlException thrown if an unexpected error occurs. + */ + public String getSamlRequest(SamlIdpBinding binding) throws SamlException { AuthnRequest request = (AuthnRequest) getBasicSamlRequest(AuthnRequest.DEFAULT_ELEMENT_NAME); request.setProtocolBinding( @@ -739,13 +847,16 @@ public String getSamlRequest() throws SamlException { nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); request.setNameIDPolicy(nameIDPolicy); - signSAMLObject(request); + if (binding != SamlClient.SamlIdpBinding.Redirect) + signSAMLObject(request); - return marshallAndEncodeSamlObject(request); + return marshallAndEncodeSamlObject(request, binding); } /** * Gets the encoded logout request. + * + * Assumes use of POST. * * @param nameId the name id * @return the logout request @@ -760,7 +871,7 @@ public String getLogoutRequest(String nameId) throws SamlException { signSAMLObject(request); - return marshallAndEncodeSamlObject(request); + return marshallAndEncodeSamlObject(request, SamlClient.SamlIdpBinding.POST); } /** * Gets saml logout response. @@ -1055,4 +1166,13 @@ private void signSAMLObject(SignableSAMLObject samlObject) throws SamlException } } } + + /** + * Get the response issuer. + * + * @return + */ + public String getResponseIssuer() { + return responseIssuer; + } } From 3b9e773fede3ea79abc38690dd8caa8ebd6999cf Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 3 Jun 2020 10:29:31 +0100 Subject: [PATCH 05/20] Identifies as 4.0.1SNAPSHOTJ2 --- src/main/java/com/coveo/saml/SamlClient.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 52b5d3c..20d6d24 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -349,12 +349,13 @@ public String getRedirectURL(String relayState) throws IOException, SamlExceptio //SAMLRequest=value&RelayState=value&SigAlg=value "Finally, note that if there is no RelayState value, the entire parameter should be omitted from thesignature computation (and not included as an empty parameter name)." // StringBuilder sb = new StringBuilder(); - sb.append("SAMLRequest=").append(URLEncoder.encode(getSamlRequest(SamlClient.SamlIdpBinding.Redirect), "UTF-8")); + sb.append("SAMLRequest=") + .append(URLEncoder.encode(getSamlRequest(SamlClient.SamlIdpBinding.Redirect), "UTF-8")); if (relayState != null) sb.append("&RelayState=").append(URLEncoder.encode(relayState, "UTF-8")); - sb.append("&SigAlg=").append( "http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1" ); + sb.append("&SigAlg=").append("http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1"); byte[] bytesToSign = sb.toString().getBytes("UTF-8"); try { @@ -847,15 +848,14 @@ public String getSamlRequest(SamlIdpBinding binding) throws SamlException { nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); request.setNameIDPolicy(nameIDPolicy); - if (binding != SamlClient.SamlIdpBinding.Redirect) - signSAMLObject(request); + if (binding != SamlClient.SamlIdpBinding.Redirect) signSAMLObject(request); return marshallAndEncodeSamlObject(request, binding); } /** * Gets the encoded logout request. - * + * * Assumes use of POST. * * @param nameId the name id @@ -1168,9 +1168,9 @@ private void signSAMLObject(SignableSAMLObject samlObject) throws SamlException } /** - * Get the response issuer. - * - * @return + * Get the response issuer. + * + * @return */ public String getResponseIssuer() { return responseIssuer; From 8a9f57c98db129ff27e04a249d37ac35c40bcf17 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Thu, 4 Jun 2020 10:16:57 +0100 Subject: [PATCH 06/20] SSO Redirect now uses SHA256 instead of SHA1 --- src/main/java/com/coveo/saml/SamlClient.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 20d6d24..7b431df 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -355,11 +355,13 @@ public String getRedirectURL(String relayState) throws IOException, SamlExceptio if (relayState != null) sb.append("&RelayState=").append(URLEncoder.encode(relayState, "UTF-8")); - sb.append("&SigAlg=").append("http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1"); + sb.append("&SigAlg=") + .append( + "http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256"); //http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 byte[] bytesToSign = sb.toString().getBytes("UTF-8"); try { - java.security.Signature sig = java.security.Signature.getInstance("SHA1withRSA"); + java.security.Signature sig = java.security.Signature.getInstance("SHA256withRSA"); sig.initSign(spCredential.getPrivateKey()); sig.update(bytesToSign); byte[] signature = sig.sign(); From f615c6220c55b57997b29daeba4ffc6fffcc20a7 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 21 Dec 2022 14:01:48 +0000 Subject: [PATCH 07/20] A270669 Updated our changes to SamlClient to align with changes from coveooss master. --- src/main/java/com/coveo/saml/SamlClient.java | 21 ++++++-------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 364b33c..93e5160 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -288,7 +288,12 @@ public SamlResponse decodeAndValidateSamlResponse(String encodedResponse, String throw new SamlException("Cannot decrypt the assertion", e); } //Validate the response (Assertion / Signature / Schema) - ValidatorUtils.validate(response, responseIssuer, credentials, this.now, notBeforeSkew); + ValidatorUtils.validate( + response, + responseIssuer, + credentials, + this.now, + notBeforeSkew); //If this fails with REQUESTER = "urn:oasis:names:tc:SAML:2.0:status:Requester"; then the IDP is saying that the request was wrong. Assertion assertion = response.getAssertions().get(0); return new SamlResponse(assertion); @@ -779,20 +784,6 @@ public void clearAdditionalSPKeys() throws SamlException { additionalSpCredentials = new ArrayList<>(); } - /** - * Set service provider keys. - * - * @param certificate the certificate - * @param privateKey the private key - * @throws SamlException if publicKey and privateKey don't form a valid credential - */ - public void setSPKeys(X509Certificate certificate, PrivateKey privateKey) throws SamlException { - if (certificate == null || privateKey == null) { - throw new SamlException("No credentials provided"); - } - spCredential = new BasicX509Credential(certificate, privateKey); - } - /** * Gets attributes from the IDP Response * From 44208a83ca06261994674244d386d51f54d47ca3 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Tue, 5 Sep 2023 16:14:45 +0100 Subject: [PATCH 08/20] A275378 Support use of RetrievalMethod for the EncryptionKey for encrypted assertions. --- src/main/java/com/coveo/saml/SamlClient.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 93e5160..40d6ba1 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -104,6 +104,9 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.xml.BasicParserPool; import net.shibboleth.utilities.java.support.xml.XMLParserException; +import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; public class SamlClient { private static final Logger logger = LoggerFactory.getLogger(SamlClient.class); @@ -1070,13 +1073,19 @@ private void decodeEncryptedAssertion(Response response) throws DecryptionExcept if (!additionalSpCredentials.isEmpty()) { resolverChain.add(new CollectionKeyInfoCredentialResolver(additionalSpCredentials)); - } - + } + + //4.1.2-datb-2 A275378 If encrypted assertions use RetrievalMethod to refer to an EncryptedKey using a URI within the + //document then SimpleRetrievalMethodEncryptedKeyResolver is needed to resolve these. + List ekResolverList = new ArrayList<>(); + ekResolverList.add( new InlineEncryptedKeyResolver() ); + ekResolverList.add( new SimpleRetrievalMethodEncryptedKeyResolver() ); + Decrypter decrypter = new Decrypter( null, new ChainingKeyInfoCredentialResolver(resolverChain), - new InlineEncryptedKeyResolver()); + new ChainingEncryptedKeyResolver( ekResolverList ) ); decrypter.setRootInNewDocument(true); From 96cca77e32ac83cdab103291441c5a84d9435054 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 23 Oct 2024 14:35:00 +0100 Subject: [PATCH 09/20] C281958 setForceAuthn() --- src/main/java/com/coveo/saml/SamlClient.java | 21 ++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 40d6ba1..e1a6a9f 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -132,6 +132,7 @@ public enum SamlIdpBinding { private SamlIdpBinding samlBinding; private BasicX509Credential spCredential; private List additionalSpCredentials = new ArrayList<>(); + private boolean forceAuthn; //2.34.1 C281958 /** * Returns the url where SAML requests should be posted. @@ -906,6 +907,9 @@ public String getSamlRequest(SamlIdpBinding binding) throws SamlException { nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); request.setNameIDPolicy(nameIDPolicy); + //2.34.1 C281958 + if (forceAuthn) request.setForceAuthn(true); + if (binding != SamlClient.SamlIdpBinding.Redirect) signSAMLObject(request); return marshallAndEncodeSamlObject(request, binding); @@ -1073,14 +1077,14 @@ private void decodeEncryptedAssertion(Response response) throws DecryptionExcept if (!additionalSpCredentials.isEmpty()) { resolverChain.add(new CollectionKeyInfoCredentialResolver(additionalSpCredentials)); - } + } - //4.1.2-datb-2 A275378 If encrypted assertions use RetrievalMethod to refer to an EncryptedKey using a URI within the - //document then SimpleRetrievalMethodEncryptedKeyResolver is needed to resolve these. + //4.1.2-datb-2 A275378 If encrypted assertions use RetrievalMethod to refer to an EncryptedKey using a URI within the + //document then SimpleRetrievalMethodEncryptedKeyResolver is needed to resolve these. List ekResolverList = new ArrayList<>(); ekResolverList.add( new InlineEncryptedKeyResolver() ); ekResolverList.add( new SimpleRetrievalMethodEncryptedKeyResolver() ); - + Decrypter decrypter = new Decrypter( null, @@ -1249,4 +1253,13 @@ private void signSAMLObject(SignableSAMLObject samlObject) throws SamlException public String getResponseIssuer() { return responseIssuer; } + + /** + * Set whether to set the forceAuthn flag on any SamlRequests. + * + * 2.34.1 C281958 + */ + public void setForceAuthn(boolean forceAuthn) { + this.forceAuthn = forceAuthn; + } } From 565ce2657fe1b1707c79fcea89ca779cb4d70389 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Thu, 24 Oct 2024 13:38:35 +0100 Subject: [PATCH 10/20] 4.1.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2f3a229..7f82a4d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.coveo saml-client - 4.1.2 + 4.1.3 jar ${project.groupId}:${project.artifactId} From beb5105ef9c62106687b4c1fc2ddaf7cef9cfe68 Mon Sep 17 00:00:00 2001 From: datb-com <48554223+datb-com@users.noreply.github.com> Date: Wed, 8 Jan 2020 20:11:32 +0000 Subject: [PATCH 11/20] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e915c6b..8093bd5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ # Dead Simple SAML 2.0 Client +This is coveooss/saml-client with some crude alterations to allow more flexibility for setting keys where they aren't held in files. + This library implements a very simple SAML 2.0 client that allows retrieving an authenticated identity from a compliant identity provider, using the HTTP POST binding. It is based on the OpenSAML library, and only provides the necessary glue code to make it work in a basic scenario. This is by no means a complete implementation supporting all the nitty gritty SAML details, but it does perform the basic task of generating requests and validating responses. It's useful if you need to authenticate with SAML but don't want to bring in an uber large framework such as Spring Security. From 6bd8a1b63fa389c8e60f061bc45c222f2ea45f1f Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 8 Jan 2020 21:14:54 +0000 Subject: [PATCH 12/20] Added InputStream variant to setSPKeys(). --- src/main/java/com/coveo/saml/SamlClient.java | 67 ++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 3eb3c27..eff3542 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -2,6 +2,7 @@ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -688,6 +689,22 @@ public void clearAdditionalSPKeys() { additionalSpCredentials = new ArrayList<>(); } + /** + * Set service provider keys from InputStreams. + * + * @param publicKey the public key + * @param privateKey the private key + * @throws SamlException if publicKey and privateKey don't form a valid credential + */ + public void setSPKeys(InputStream publicKey, InputStream privateKey) throws SamlException { + if (publicKey == null || privateKey == null) { + throw new SamlException("No credentials provided"); + } + PrivateKey pk = loadPrivateKey(privateKey); + X509Certificate cert = loadCertificate(publicKey); + spCredential = new BasicX509Credential(cert, pk); + } + /** * Gets attributes from the IDP Response * @@ -975,6 +992,22 @@ private X509Certificate loadCertificate(String filename) throws SamlException { } } + /** + * Load an X.509 certificate + * @param bis An input stream providing the bytes of the key. + * */ + private X509Certificate loadCertificate(InputStream bis) throws SamlException { + try { + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + return (X509Certificate) cf.generateCertificate(bis); + + } catch (Exception e) { + throw new SamlException("Couldn't load public key", e); + } + } + /** * Load a PKCS8 key * @param filename The path of the key @@ -995,6 +1028,40 @@ private PrivateKey loadPrivateKey(String filename) throws SamlException { } } + /** + * Load a PKCS8 key + * @param bis An input stream providing the bytes of the key. + * */ + private PrivateKey loadPrivateKey(InputStream bis) throws SamlException { + try //(RandomAccessFile raf = new RandomAccessFile(filename, "r")) + { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + int totalBytes = 0; + byte[] onekay = new byte[1024]; + int bytesRead = 0; + while (true) { + bytesRead = bis.read(onekay); + + if (bytesRead == -1) break; + + if (bytesRead > 0) baos.write(onekay, 0, bytesRead); + } + bis.close(); + baos.flush(); + baos.close(); + + PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(baos.toByteArray()); + KeyFactory kf = KeyFactory.getInstance("RSA"); + + return kf.generatePrivate(kspec); + + } catch (Exception e) { + throw new SamlException("Couldn't load private key", e); + } + } + private StringWriter marshallXmlObject(XMLObject object) throws MarshallingException { StringWriter stringWriter = new StringWriter(); Marshaller marshaller = From 741401b49e6f35acf11601c7228c9fe9b7f32cb1 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Tue, 19 May 2020 14:10:04 +0100 Subject: [PATCH 13/20] pull from coveooss/saml-client (4.0.1SNAPSHOT) --- src/main/java/com/coveo/saml/SamlClient.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index eff3542..d7a2bdb 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -690,19 +690,17 @@ public void clearAdditionalSPKeys() { } /** - * Set service provider keys from InputStreams. + * Set service provider keys. * - * @param publicKey the public key + * @param certificate the certificate * @param privateKey the private key * @throws SamlException if publicKey and privateKey don't form a valid credential */ - public void setSPKeys(InputStream publicKey, InputStream privateKey) throws SamlException { - if (publicKey == null || privateKey == null) { + public void setSPKeys(X509Certificate certificate, PrivateKey privateKey) throws SamlException { + if (certificate == null || privateKey == null) { throw new SamlException("No credentials provided"); } - PrivateKey pk = loadPrivateKey(privateKey); - X509Certificate cert = loadCertificate(publicKey); - spCredential = new BasicX509Credential(cert, pk); + spCredential = new BasicX509Credential(certificate, privateKey); } /** From 0c5e5c5a8c8c6a2a99fb3a40247961a102de8f7f Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 20 May 2020 12:33:39 +0100 Subject: [PATCH 14/20] Added support for Redirect binding for SSO. --- .gitignore | 3 +- src/main/java/com/coveo/saml/SamlClient.java | 131 ++++++++++++++++++- 2 files changed, 127 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index d140809..6774b28 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ target .project .classpath -.settings \ No newline at end of file +.settings +.DS_Store/ diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index d7a2bdb..8ecd0f1 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -11,8 +11,11 @@ import java.io.RandomAccessFile; import java.io.Reader; import java.io.StringWriter; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -27,6 +30,8 @@ import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; @@ -299,6 +304,12 @@ public SamlResponse decodeAndValidateSamlResponse(String encodedResponse, String */ public void redirectToIdentityProvider(HttpServletResponse response, String relayState) throws IOException, SamlException { + + if (samlBinding == SamlClient.SamlIdpBinding.Redirect) { + response.sendRedirect(getRedirectURL(relayState)); + return; + } + Map values = new HashMap<>(); values.put("SAMLRequest", getSamlRequest()); if (relayState != null) { @@ -308,6 +319,67 @@ public void redirectToIdentityProvider(HttpServletResponse response, String rela BrowserUtils.postUsingBrowser(identityProviderUrl, response, values); } + public String getRedirectURL(String relayState) throws IOException, SamlException { + //From https://medium.com/@sagarag/reloading-saml-saml-basics-b8999995c73e + //- RelayState is supported but should be limited to 80 bytes and it should be appended to the URL with “RelayState=value” format. + //- The SAML request is first compressed using DEFLATE compression mechanism and then encoded using Base64 finally it again need to be URL encoded. + //- For the request signing, it should use DSAWithSHA or RSAWithSHA algorithms. + //- HTTP Cache should not be used with this binding. + //- SAML processors should not use HTTP error codes instead SAML error coded need to be used. + // + //Following are the URL parameter names supported in this binding. + //- SAMLRequest — SAML request message + //- RelayState — RelayState value + //nb. these are additional to POST... + //- SAMLEncoding — indicate the SAML encoding mechanism + //- SigAlg — Signature algorithm identifier + //- Signature — Base64 encoded signature value + // + //Example: https://localhost:9443/samlsso?SAMLRequest=nZPBjpskDyneY4s...&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1&Signature=VoTUhZQVI1y... + // + //From: https://www.oasis-open.org/committees/download.php/35387/sstc-saml-bindings-errata-2.0-wd-05-diff.pdf + //1. Any signature on the SAML protocol message, including the XML element itself,MUST be removed. + // Note that if the content of the message includes another signature, such as asigned SAML assertion, this embedded signature is not removed. + // However, the length of such amessage after encoding essentially precludes using this mechanism. + // Thus SAML protocolmessages that contain signed content SHOULD NOT be encoded using this mechanism. + //2. The DEFLATE compression mechanism, as specified in [RFC1951] is then applied to the entireremaining XML content of the original SAML protocol message. + //3. The compressed data is subsequently base64-encoded according to the rules specified in IETFRFC 2045 [RFC2045]. + // Linefeeds or other whitespace MUST be removed from the result. + //4. The base-64 encoded data is then URL-encoded, and added to the URL as a query stringparameter which MUST be named SAMLRequest (if the message is a SAML request) or + // SAMLResponse (if the message is a SAML response). + //5. If RelayState data is to accompany the SAML protocol message, it MUST be URL-encoded and placed in an additional query string parameter named RelayState. + //6. If the original SAML protocol message was signed using an XML digital signature, a new signaturecovering the encoded data as specified above MUST be attached using the rules stated below. + // + //SAMLRequest=value&RelayState=value&SigAlg=value "Finally, note that if there is no RelayState value, the entire parameter should be omitted from thesignature computation (and not included as an empty parameter name)." + // + StringBuilder sb = new StringBuilder(); + sb.append("SAMLRequest=").append(URLEncoder.encode(getSamlRequest(SamlClient.SamlIdpBinding.Redirect), "UTF-8")); + + if (relayState != null) + sb.append("&RelayState=").append(URLEncoder.encode(relayState, "UTF-8")); + + sb.append("&SigAlg=").append( "http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1" ); + + byte[] bytesToSign = sb.toString().getBytes("UTF-8"); + try { + java.security.Signature sig = java.security.Signature.getInstance("SHA1withRSA"); + sig.initSign(spCredential.getPrivateKey()); + sig.update(bytesToSign); + byte[] signature = sig.sign(); + sb.append("&Signature=") + .append(URLEncoder.encode(Base64.encodeBase64String(signature), "UTF-8")); + } catch (NoSuchAlgorithmException | InvalidKeyException | java.security.SignatureException e) { + throw new SamlException("Unsupported algorithm. " + e); + } + + sb.append("&SAMLEncoding=") + .append( + URLEncoder.encode( + "urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE", "UTF-8")); //optional + + return identityProviderUrl + "?" + sb.toString(); + } + /** * Processes a POST containing the SAML response. * @@ -753,9 +825,11 @@ private RequestAbstractType getBasicSamlRequest(QName defaultElementName) { /** Convert a SAML request to a base64-encoded String * * @param request The request to encode + * @param binding Whether we are doing a POST or Redirect * @throws SamlException if marshalling the request fails * */ - private String marshallAndEncodeSamlObject(RequestAbstractType request) throws SamlException { + private String marshallAndEncodeSamlObject(RequestAbstractType request, SamlIdpBinding binding) + throws SamlException { StringWriter stringWriter; try { stringWriter = marshallXmlObject(request); @@ -765,16 +839,49 @@ private String marshallAndEncodeSamlObject(RequestAbstractType request) throws S logger.trace("Issuing SAML request: " + stringWriter); - return Base64.encodeBase64String(stringWriter.toString().getBytes(StandardCharsets.UTF_8)); + byte[] requestBytes; + if (binding == SamlClient.SamlIdpBinding.Redirect) { + try { + //For a Redirect we compress (deflate) the bytes before we Base64 them + InputStream input = new ByteArrayInputStream(requestStr.getBytes("UTF-8")); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + Deflater def = new Deflater(1, true); + DeflaterOutputStream compresser = new DeflaterOutputStream(output, def); + int len; + byte[] oneKay = new byte[1024]; + while ((len = input.read(oneKay)) != -1) { + compresser.write(oneKay, 0, len); + } + compresser.flush(); + compresser.finish(); + requestBytes = output.toByteArray(); + } catch (IOException e) { + throw new SamlException("Failed to deflate request for redirect.", e); + } + } else { + //For a POST we just Base64 the bytes + requestBytes = requestStr.getBytes(StandardCharsets.UTF_8); + } + return Base64.encodeBase64String(requestBytes); } /** - * Builds an encoded SAML request. + * Builds an encoded SAML request for the POST binding. * * @return The base-64 encoded SAML request. * @throws SamlException thrown if an unexpected error occurs. */ public String getSamlRequest() throws SamlException { + return getSamlRequest(SamlClient.SamlIdpBinding.POST); + } + + /** + * Builds an encoded SAML request. + * + * @return The base-64 encoded SAML request. + * @throws SamlException thrown if an unexpected error occurs. + */ + public String getSamlRequest(SamlIdpBinding binding) throws SamlException { AuthnRequest request = (AuthnRequest) getBasicSamlRequest(AuthnRequest.DEFAULT_ELEMENT_NAME); request.setProtocolBinding( @@ -786,13 +893,16 @@ public String getSamlRequest() throws SamlException { nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); request.setNameIDPolicy(nameIDPolicy); - signSAMLObject(request); + if (binding != SamlClient.SamlIdpBinding.Redirect) + signSAMLObject(request); - return marshallAndEncodeSamlObject(request); + return marshallAndEncodeSamlObject(request, binding); } /** * Gets the encoded logout request. + * + * Assumes use of POST. * * @param nameId the name id * @return the logout request @@ -807,7 +917,7 @@ public String getLogoutRequest(String nameId) throws SamlException { signSAMLObject(request); - return marshallAndEncodeSamlObject(request); + return marshallAndEncodeSamlObject(request, SamlClient.SamlIdpBinding.POST); } /** * Gets saml logout response. @@ -1115,4 +1225,13 @@ private void signSAMLObject(SignableSAMLObject samlObject) throws SamlException } } } + + /** + * Get the response issuer. + * + * @return + */ + public String getResponseIssuer() { + return responseIssuer; + } } From b1437d1944f9f2f8c6778b79d2019852cccad0d1 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 3 Jun 2020 10:29:31 +0100 Subject: [PATCH 15/20] Identifies as 4.0.1SNAPSHOTJ2 --- src/main/java/com/coveo/saml/SamlClient.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 8ecd0f1..b9ef702 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -353,12 +353,13 @@ public String getRedirectURL(String relayState) throws IOException, SamlExceptio //SAMLRequest=value&RelayState=value&SigAlg=value "Finally, note that if there is no RelayState value, the entire parameter should be omitted from thesignature computation (and not included as an empty parameter name)." // StringBuilder sb = new StringBuilder(); - sb.append("SAMLRequest=").append(URLEncoder.encode(getSamlRequest(SamlClient.SamlIdpBinding.Redirect), "UTF-8")); + sb.append("SAMLRequest=") + .append(URLEncoder.encode(getSamlRequest(SamlClient.SamlIdpBinding.Redirect), "UTF-8")); if (relayState != null) sb.append("&RelayState=").append(URLEncoder.encode(relayState, "UTF-8")); - sb.append("&SigAlg=").append( "http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1" ); + sb.append("&SigAlg=").append("http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1"); byte[] bytesToSign = sb.toString().getBytes("UTF-8"); try { @@ -893,15 +894,14 @@ public String getSamlRequest(SamlIdpBinding binding) throws SamlException { nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); request.setNameIDPolicy(nameIDPolicy); - if (binding != SamlClient.SamlIdpBinding.Redirect) - signSAMLObject(request); + if (binding != SamlClient.SamlIdpBinding.Redirect) signSAMLObject(request); return marshallAndEncodeSamlObject(request, binding); } /** * Gets the encoded logout request. - * + * * Assumes use of POST. * * @param nameId the name id @@ -1227,9 +1227,9 @@ private void signSAMLObject(SignableSAMLObject samlObject) throws SamlException } /** - * Get the response issuer. - * - * @return + * Get the response issuer. + * + * @return */ public String getResponseIssuer() { return responseIssuer; From 1b8b4bd0dd9dcb37bac5fe8110bbcfdede06a5e3 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Thu, 4 Jun 2020 10:16:57 +0100 Subject: [PATCH 16/20] SSO Redirect now uses SHA256 instead of SHA1 --- src/main/java/com/coveo/saml/SamlClient.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index b9ef702..fbcdafe 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -359,11 +359,13 @@ public String getRedirectURL(String relayState) throws IOException, SamlExceptio if (relayState != null) sb.append("&RelayState=").append(URLEncoder.encode(relayState, "UTF-8")); - sb.append("&SigAlg=").append("http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1"); + sb.append("&SigAlg=") + .append( + "http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256"); //http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 byte[] bytesToSign = sb.toString().getBytes("UTF-8"); try { - java.security.Signature sig = java.security.Signature.getInstance("SHA1withRSA"); + java.security.Signature sig = java.security.Signature.getInstance("SHA256withRSA"); sig.initSign(spCredential.getPrivateKey()); sig.update(bytesToSign); byte[] signature = sig.sign(); From 27ed46338c2eaf8e3c8981323d52a3cf5db835e5 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 21 Dec 2022 14:01:48 +0000 Subject: [PATCH 17/20] A270669 Updated our changes to SamlClient to align with changes from coveooss master. --- src/main/java/com/coveo/saml/SamlClient.java | 21 ++++++-------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index fbcdafe..36f4f07 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -288,7 +288,12 @@ public SamlResponse decodeAndValidateSamlResponse(String encodedResponse, String throw new SamlException("Cannot decrypt the assertion", e); } //Validate the response (Assertion / Signature / Schema) - ValidatorUtils.validate(response, responseIssuer, credentials, this.now, notBeforeSkew); + ValidatorUtils.validate( + response, + responseIssuer, + credentials, + this.now, + notBeforeSkew); //If this fails with REQUESTER = "urn:oasis:names:tc:SAML:2.0:status:Requester"; then the IDP is saying that the request was wrong. Assertion assertion = response.getAssertions().get(0); return new SamlResponse(assertion); @@ -764,20 +769,6 @@ public void clearAdditionalSPKeys() { additionalSpCredentials = new ArrayList<>(); } - /** - * Set service provider keys. - * - * @param certificate the certificate - * @param privateKey the private key - * @throws SamlException if publicKey and privateKey don't form a valid credential - */ - public void setSPKeys(X509Certificate certificate, PrivateKey privateKey) throws SamlException { - if (certificate == null || privateKey == null) { - throw new SamlException("No credentials provided"); - } - spCredential = new BasicX509Credential(certificate, privateKey); - } - /** * Gets attributes from the IDP Response * From a616350cce50565015e5291333a2cf383d34ab40 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Tue, 5 Sep 2023 16:14:45 +0100 Subject: [PATCH 18/20] A275378 Support use of RetrievalMethod for the EncryptionKey for encrypted assertions. --- src/main/java/com/coveo/saml/SamlClient.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 36f4f07..9688cd6 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -104,6 +104,9 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.xml.BasicParserPool; import net.shibboleth.utilities.java.support.xml.XMLParserException; +import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver; +import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver; public class SamlClient { private static final Logger logger = LoggerFactory.getLogger(SamlClient.class); @@ -1057,13 +1060,19 @@ private void decodeEncryptedAssertion(Response response) throws DecryptionExcept if (!additionalSpCredentials.isEmpty()) { resolverChain.add(new CollectionKeyInfoCredentialResolver(additionalSpCredentials)); - } - + } + + //4.1.2-datb-2 A275378 If encrypted assertions use RetrievalMethod to refer to an EncryptedKey using a URI within the + //document then SimpleRetrievalMethodEncryptedKeyResolver is needed to resolve these. + List ekResolverList = new ArrayList<>(); + ekResolverList.add( new InlineEncryptedKeyResolver() ); + ekResolverList.add( new SimpleRetrievalMethodEncryptedKeyResolver() ); + Decrypter decrypter = new Decrypter( null, new ChainingKeyInfoCredentialResolver(resolverChain), - new InlineEncryptedKeyResolver()); + new ChainingEncryptedKeyResolver( ekResolverList ) ); decrypter.setRootInNewDocument(true); From 6776a4a76fb4ab56e9f271e9469e32148da936f3 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Wed, 23 Oct 2024 14:35:00 +0100 Subject: [PATCH 19/20] C281958 setForceAuthn() --- src/main/java/com/coveo/saml/SamlClient.java | 21 ++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 9688cd6..377fbbe 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -132,6 +132,7 @@ public enum SamlIdpBinding { private final SamlIdpBinding samlBinding; private BasicX509Credential spCredential; private List additionalSpCredentials = new ArrayList<>(); + private boolean forceAuthn; //2.34.1 C281958 /** * Returns the url where SAML requests should be posted. @@ -890,6 +891,9 @@ public String getSamlRequest(SamlIdpBinding binding) throws SamlException { nameIDPolicy.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); request.setNameIDPolicy(nameIDPolicy); + //2.34.1 C281958 + if (forceAuthn) request.setForceAuthn(true); + if (binding != SamlClient.SamlIdpBinding.Redirect) signSAMLObject(request); return marshallAndEncodeSamlObject(request, binding); @@ -1060,14 +1064,14 @@ private void decodeEncryptedAssertion(Response response) throws DecryptionExcept if (!additionalSpCredentials.isEmpty()) { resolverChain.add(new CollectionKeyInfoCredentialResolver(additionalSpCredentials)); - } + } - //4.1.2-datb-2 A275378 If encrypted assertions use RetrievalMethod to refer to an EncryptedKey using a URI within the - //document then SimpleRetrievalMethodEncryptedKeyResolver is needed to resolve these. + //4.1.2-datb-2 A275378 If encrypted assertions use RetrievalMethod to refer to an EncryptedKey using a URI within the + //document then SimpleRetrievalMethodEncryptedKeyResolver is needed to resolve these. List ekResolverList = new ArrayList<>(); ekResolverList.add( new InlineEncryptedKeyResolver() ); ekResolverList.add( new SimpleRetrievalMethodEncryptedKeyResolver() ); - + Decrypter decrypter = new Decrypter( null, @@ -1236,4 +1240,13 @@ private void signSAMLObject(SignableSAMLObject samlObject) throws SamlException public String getResponseIssuer() { return responseIssuer; } + + /** + * Set whether to set the forceAuthn flag on any SamlRequests. + * + * 2.34.1 C281958 + */ + public void setForceAuthn(boolean forceAuthn) { + this.forceAuthn = forceAuthn; + } } From d1cf136e1ecc8553cb88f60d8d2271915e43ee54 Mon Sep 17 00:00:00 2001 From: Jason Dore Date: Tue, 16 Sep 2025 13:31:04 +0100 Subject: [PATCH 20/20] merge with com.coveo 5.0.1 snapshot --- pom.xml | 2 +- .../java/com/coveo/saml/MetadataUtils.java | 281 +++++++++--------- src/main/java/com/coveo/saml/SamlClient.java | 9 +- .../com/coveo/saml/MetadataUtilsTest.java | 79 ++--- .../com/coveo/saml/samltest.id/idp.xml | 122 ++++++++ .../com/coveo/saml/samltest.id/sp.xml | 128 ++++++++ 6 files changed, 439 insertions(+), 182 deletions(-) create mode 100644 src/test/resources/com/coveo/saml/samltest.id/idp.xml create mode 100644 src/test/resources/com/coveo/saml/samltest.id/sp.xml diff --git a/pom.xml b/pom.xml index 39d3015..a31f51d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.coveo saml-client - 5.0.1-SNAPSHOT + 5.0.1 jar ${project.groupId}:${project.artifactId} diff --git a/src/main/java/com/coveo/saml/MetadataUtils.java b/src/main/java/com/coveo/saml/MetadataUtils.java index ac7ac4a..17d8a60 100644 --- a/src/main/java/com/coveo/saml/MetadataUtils.java +++ b/src/main/java/com/coveo/saml/MetadataUtils.java @@ -33,146 +33,147 @@ import org.slf4j.LoggerFactory; import org.w3c.dom.Document; - public class MetadataUtils { - private static final Logger logger = LoggerFactory.getLogger(SamlClient.class); - - public static String generateSpMetadata(String entityId, String assertionConsumerServiceURL, String logoutServiceURL) { - return generateSpMetadata(entityId, assertionConsumerServiceURL, logoutServiceURL, null); - } - - public static String generateSpMetadata(String entityId, String assertionConsumerServiceURL, String singleLogoutServiceURL, X509Certificate certificate) { - try { - InitializationService.initialize(); - - EntityDescriptor spEntityDescriptor = createSAMLObject(EntityDescriptor.class); - if (spEntityDescriptor == null) { - return null; - } - spEntityDescriptor.setEntityID(entityId); - SPSSODescriptor spSSODescriptor = createSAMLObject(SPSSODescriptor.class); - if (spSSODescriptor == null) { - return null; - } - - spSSODescriptor.setWantAssertionsSigned(false); - spSSODescriptor.setAuthnRequestsSigned(false); - - if (certificate != null) { - - spSSODescriptor.setWantAssertionsSigned(true); - spSSODescriptor.setAuthnRequestsSigned(true); - - X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory(); - keyInfoGeneratorFactory.setEmitEntityCertificate(true); - KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance(); - - KeyDescriptor encKeyDescriptor = createSAMLObject(KeyDescriptor.class); - if (encKeyDescriptor == null) { - return null; - } - - encKeyDescriptor.setUse(UsageType.ENCRYPTION); - - Credential credential = new BasicX509Credential(certificate); - - try { - encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential)); - } - catch (Exception e) { - logger.error("Error while creating credentials", e); - } - spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor); - - KeyDescriptor signKeyDescriptor = createSAMLObject(KeyDescriptor.class); - if (signKeyDescriptor == null) { - return null; - } - - signKeyDescriptor.setUse(UsageType.SIGNING); // Set usage - - try { - signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential)); - } - catch (SecurityException e) { - logger.error("Error while creating credentials", e); - } - spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor); - } - - SingleLogoutService singleLogoutService = createSAMLObject(SingleLogoutService.class); - if (singleLogoutService == null) { - return null; - } - singleLogoutService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); - singleLogoutService.setLocation(singleLogoutServiceURL); - spSSODescriptor.getSingleLogoutServices().add(singleLogoutService); - - NameIDFormat nameIDFormat = createSAMLObject(NameIDFormat.class); - if (nameIDFormat == null) { - return null; - } - - nameIDFormat.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); - spSSODescriptor.getNameIDFormats().add(nameIDFormat); - - AssertionConsumerService assertionConsumerService = createSAMLObject(AssertionConsumerService.class); - if (assertionConsumerService == null) { - return null; - } - assertionConsumerService.setIndex(1); - assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); - - assertionConsumerService.setLocation(assertionConsumerServiceURL); - spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService); - - spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); - - spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor); - - DocumentBuilder builder; - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - - builder = factory.newDocumentBuilder(); - Document document = builder.newDocument(); - Marshaller out = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(spEntityDescriptor); - out.marshall(spEntityDescriptor, document); - - TransformerFactory transformerfactory = TransformerFactory.newInstance(); - transformerfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); - Transformer transformer = transformerfactory.newTransformer(); - StringWriter stringWriter = new StringWriter(); - StreamResult streamResult = new StreamResult(stringWriter); - DOMSource source = new DOMSource(document); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); - transformer.transform(source, streamResult); - stringWriter.close(); - - return stringWriter.toString(); - } - catch (Exception e) { - logger.error("Error while generation SP metadata", e); - return null; - } - - } - - public static T createSAMLObject(final Class clazz) { - XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory(); - - QName defaultElementName = null; - try { - defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null); - } - catch (Exception e) { - logger.error("Error while creating SAML object", e); - return null; - } - T object = (T) builderFactory.getBuilder(defaultElementName).buildObject(defaultElementName); - - return object; - } + private static final Logger logger = LoggerFactory.getLogger(SamlClient.class); + + public static String generateSpMetadata( + String entityId, String assertionConsumerServiceURL, String logoutServiceURL) { + return generateSpMetadata(entityId, assertionConsumerServiceURL, logoutServiceURL, null); + } + + public static String generateSpMetadata( + String entityId, + String assertionConsumerServiceURL, + String singleLogoutServiceURL, + X509Certificate certificate) { + try { + InitializationService.initialize(); + + EntityDescriptor spEntityDescriptor = createSAMLObject(EntityDescriptor.class); + if (spEntityDescriptor == null) { + return null; + } + spEntityDescriptor.setEntityID(entityId); + SPSSODescriptor spSSODescriptor = createSAMLObject(SPSSODescriptor.class); + if (spSSODescriptor == null) { + return null; + } + + spSSODescriptor.setWantAssertionsSigned(false); + spSSODescriptor.setAuthnRequestsSigned(false); + + if (certificate != null) { + + spSSODescriptor.setWantAssertionsSigned(true); + spSSODescriptor.setAuthnRequestsSigned(true); + + X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory(); + keyInfoGeneratorFactory.setEmitEntityCertificate(true); + KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance(); + + KeyDescriptor encKeyDescriptor = createSAMLObject(KeyDescriptor.class); + if (encKeyDescriptor == null) { + return null; + } + + encKeyDescriptor.setUse(UsageType.ENCRYPTION); + + Credential credential = new BasicX509Credential(certificate); + + try { + encKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential)); + } catch (Exception e) { + logger.error("Error while creating credentials", e); + } + spSSODescriptor.getKeyDescriptors().add(encKeyDescriptor); + + KeyDescriptor signKeyDescriptor = createSAMLObject(KeyDescriptor.class); + if (signKeyDescriptor == null) { + return null; + } + + signKeyDescriptor.setUse(UsageType.SIGNING); // Set usage + + try { + signKeyDescriptor.setKeyInfo(keyInfoGenerator.generate(credential)); + } catch (SecurityException e) { + logger.error("Error while creating credentials", e); + } + spSSODescriptor.getKeyDescriptors().add(signKeyDescriptor); + } + + SingleLogoutService singleLogoutService = createSAMLObject(SingleLogoutService.class); + if (singleLogoutService == null) { + return null; + } + singleLogoutService.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + singleLogoutService.setLocation(singleLogoutServiceURL); + spSSODescriptor.getSingleLogoutServices().add(singleLogoutService); + + NameIDFormat nameIDFormat = createSAMLObject(NameIDFormat.class); + if (nameIDFormat == null) { + return null; + } + + nameIDFormat.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"); + spSSODescriptor.getNameIDFormats().add(nameIDFormat); + + AssertionConsumerService assertionConsumerService = + createSAMLObject(AssertionConsumerService.class); + if (assertionConsumerService == null) { + return null; + } + assertionConsumerService.setIndex(1); + assertionConsumerService.setBinding(SAMLConstants.SAML2_POST_BINDING_URI); + + assertionConsumerService.setLocation(assertionConsumerServiceURL); + spSSODescriptor.getAssertionConsumerServices().add(assertionConsumerService); + + spSSODescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS); + + spEntityDescriptor.getRoleDescriptors().add(spSSODescriptor); + + DocumentBuilder builder; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + + builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + Marshaller out = + XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(spEntityDescriptor); + out.marshall(spEntityDescriptor, document); + + TransformerFactory transformerfactory = TransformerFactory.newInstance(); + transformerfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + Transformer transformer = transformerfactory.newTransformer(); + StringWriter stringWriter = new StringWriter(); + StreamResult streamResult = new StreamResult(stringWriter); + DOMSource source = new DOMSource(document); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + transformer.transform(source, streamResult); + stringWriter.close(); + + return stringWriter.toString(); + } catch (Exception e) { + logger.error("Error while generation SP metadata", e); + return null; + } + } + + public static T createSAMLObject(final Class clazz) { + XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory(); + + QName defaultElementName = null; + try { + defaultElementName = (QName) clazz.getDeclaredField("DEFAULT_ELEMENT_NAME").get(null); + } catch (Exception e) { + logger.error("Error while creating SAML object", e); + return null; + } + T object = (T) builderFactory.getBuilder(defaultElementName).buildObject(defaultElementName); + + return object; + } } diff --git a/src/main/java/com/coveo/saml/SamlClient.java b/src/main/java/com/coveo/saml/SamlClient.java index 377fbbe..a735f04 100644 --- a/src/main/java/com/coveo/saml/SamlClient.java +++ b/src/main/java/com/coveo/saml/SamlClient.java @@ -840,6 +840,7 @@ private String marshallAndEncodeSamlObject(RequestAbstractType request, SamlIdpB byte[] requestBytes; if (binding == SamlClient.SamlIdpBinding.Redirect) { try { + String requestStr = stringWriter.toString(); //For a Redirect we compress (deflate) the bytes before we Base64 them InputStream input = new ByteArrayInputStream(requestStr.getBytes("UTF-8")); ByteArrayOutputStream output = new ByteArrayOutputStream(); @@ -858,7 +859,7 @@ private String marshallAndEncodeSamlObject(RequestAbstractType request, SamlIdpB } } else { //For a POST we just Base64 the bytes - requestBytes = requestStr.getBytes(StandardCharsets.UTF_8); + requestBytes = stringWriter.toString().getBytes(StandardCharsets.UTF_8); } return Base64.encodeBase64String(requestBytes); } @@ -1069,14 +1070,14 @@ private void decodeEncryptedAssertion(Response response) throws DecryptionExcept //4.1.2-datb-2 A275378 If encrypted assertions use RetrievalMethod to refer to an EncryptedKey using a URI within the //document then SimpleRetrievalMethodEncryptedKeyResolver is needed to resolve these. List ekResolverList = new ArrayList<>(); - ekResolverList.add( new InlineEncryptedKeyResolver() ); - ekResolverList.add( new SimpleRetrievalMethodEncryptedKeyResolver() ); + ekResolverList.add(new InlineEncryptedKeyResolver()); + ekResolverList.add(new SimpleRetrievalMethodEncryptedKeyResolver()); Decrypter decrypter = new Decrypter( null, new ChainingKeyInfoCredentialResolver(resolverChain), - new ChainingEncryptedKeyResolver( ekResolverList ) ); + new ChainingEncryptedKeyResolver(ekResolverList)); decrypter.setRootInNewDocument(true); diff --git a/src/test/java/com/coveo/saml/MetadataUtilsTest.java b/src/test/java/com/coveo/saml/MetadataUtilsTest.java index fe20d3f..f013a32 100644 --- a/src/test/java/com/coveo/saml/MetadataUtilsTest.java +++ b/src/test/java/com/coveo/saml/MetadataUtilsTest.java @@ -10,43 +10,48 @@ import org.junit.Test; - public class MetadataUtilsTest { - @Test - public void generateSpMetadata_AllNull() { - String metadata = MetadataUtils.generateSpMetadata(null, null, null); - assertEquals( - " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ", - metadata.replace("\r\n", "").replace("\n", "")); - } - - @Test - public void generateSpMetadata_AllFields() { - String metadata = MetadataUtils.generateSpMetadata("testSp", "http://localhost:8080/consume", "http://localhost:8080/logout"); - assertEquals( - " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ", - metadata.replace("\r\n", "").replace("\n", "")); - } - - @Test - public void generateSpMetadata_AllFieldsAndCertificat() { - Certificate cert = null; - try { - InputStream keyStoreInputStream = this.getClass().getResourceAsStream("/com/coveo/saml/test.p12"); - - KeyStore keystore = KeyStore.getInstance("PKCS12"); - keystore.load(keyStoreInputStream, "test".toCharArray()); - cert = keystore.getCertificate("tester"); - } - catch (Exception e) { - fail(); - } - - String metadata = MetadataUtils.generateSpMetadata("testSp", "http://localhost:8080/consume", "http://localhost:8080/logout", (X509Certificate) cert); - assertEquals( - " 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 urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ", - metadata.replace("\r\n", "").replace("\n", "")); - } - + @Test + public void generateSpMetadata_AllNull() { + String metadata = MetadataUtils.generateSpMetadata(null, null, null); + assertEquals( + " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ", + metadata.replace("\r\n", "").replace("\n", "")); + } + + @Test + public void generateSpMetadata_AllFields() { + String metadata = + MetadataUtils.generateSpMetadata( + "testSp", "http://localhost:8080/consume", "http://localhost:8080/logout"); + assertEquals( + " urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ", + metadata.replace("\r\n", "").replace("\n", "")); + } + + @Test + public void generateSpMetadata_AllFieldsAndCertificat() { + Certificate cert = null; + try { + InputStream keyStoreInputStream = + this.getClass().getResourceAsStream("/com/coveo/saml/test.p12"); + + KeyStore keystore = KeyStore.getInstance("PKCS12"); + keystore.load(keyStoreInputStream, "test".toCharArray()); + cert = keystore.getCertificate("tester"); + } catch (Exception e) { + fail(); + } + + String metadata = + MetadataUtils.generateSpMetadata( + "testSp", + "http://localhost:8080/consume", + "http://localhost:8080/logout", + (X509Certificate) cert); + assertEquals( + " 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 urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified ", + metadata.replace("\r\n", "").replace("\n", "")); + } } diff --git a/src/test/resources/com/coveo/saml/samltest.id/idp.xml b/src/test/resources/com/coveo/saml/samltest.id/idp.xml new file mode 100644 index 0000000..861829a --- /dev/null +++ b/src/test/resources/com/coveo/saml/samltest.id/idp.xml @@ -0,0 +1,122 @@ + + + + + + + + + samltest.id + + + + SAMLtest IdP + A free and basic IdP for testing SAML deployments + https://samltest.id/saml/logo.png + + + + + + + +MIIDETCCAfmgAwIBAgIUZRpDhkNKl5eWtJqk0Bu1BgTTargwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwHhcNMTgwODI0MjExNDEwWhcNMzgw +ODI0MjExNDEwWjAWMRQwEgYDVQQDDAtzYW1sdGVzdC5pZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAJrh9/PcDsiv3UeL8Iv9rf4WfLPxuOm9W6aCntEA +8l6c1LQ1Zyrz+Xa/40ZgP29ENf3oKKbPCzDcc6zooHMji2fBmgXp6Li3fQUzu7yd ++nIC2teejijVtrNLjn1WUTwmqjLtuzrKC/ePoZyIRjpoUxyEMJopAd4dJmAcCq/K +k2eYX9GYRlqvIjLFoGNgy2R4dWwAKwljyh6pdnPUgyO/WjRDrqUBRFrLQJorR2kD +c4seZUbmpZZfp4MjmWMDgyGM1ZnR0XvNLtYeWAyt0KkSvFoOMjZUeVK/4xR74F8e +8ToPqLmZEg9ZUx+4z2KjVK00LpdRkH9Uxhh03RQ0FabHW6UCAwEAAaNXMFUwHQYD +VR0OBBYEFJDbe6uSmYQScxpVJhmt7PsCG4IeMDQGA1UdEQQtMCuCC3NhbWx0ZXN0 +LmlkhhxodHRwczovL3NhbWx0ZXN0LmlkL3NhbWwvaWRwMA0GCSqGSIb3DQEBCwUA +A4IBAQBNcF3zkw/g51q26uxgyuy4gQwnSr01Mhvix3Dj/Gak4tc4XwvxUdLQq+jC +cxr2Pie96klWhY/v/JiHDU2FJo9/VWxmc/YOk83whvNd7mWaNMUsX3xGv6AlZtCO +L3JhCpHjiN+kBcMgS5jrtGgV1Lz3/1zpGxykdvS0B4sPnFOcaCwHe2B9SOCWbDAN +JXpTjz1DmJO4ImyWPJpN1xsYKtm67Pefxmn0ax0uE2uuzq25h0xbTkqIQgJzyoE/ +DPkBFK1vDkMfAW11dQ0BXatEnW7Gtkc0lh2/PIbHWj4AzxYMyBf5Gy6HSVOftwjC +voQR2qr2xJBixsg+MIORKtmKHLfU + + + + + + + + + +MIIDEjCCAfqgAwIBAgIVAMECQ1tjghafm5OxWDh9hwZfxthWMA0GCSqGSIb3DQEB +CwUAMBYxFDASBgNVBAMMC3NhbWx0ZXN0LmlkMB4XDTE4MDgyNDIxMTQwOVoXDTM4 +MDgyNDIxMTQwOVowFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC0Z4QX1NFKs71ufbQwoQoW7qkNAJRIANGA4iM0 +ThYghul3pC+FwrGv37aTxWXfA1UG9njKbbDreiDAZKngCgyjxj0uJ4lArgkr4AOE +jj5zXA81uGHARfUBctvQcsZpBIxDOvUUImAl+3NqLgMGF2fktxMG7kX3GEVNc1kl +bN3dfYsaw5dUrw25DheL9np7G/+28GwHPvLb4aptOiONbCaVvh9UMHEA9F7c0zfF +/cL5fOpdVa54wTI0u12CsFKt78h6lEGG5jUs/qX9clZncJM7EFkN3imPPy+0HC8n +spXiH/MZW8o2cqWRkrw3MzBZW3Ojk5nQj40V6NUbjb7kfejzAgMBAAGjVzBVMB0G +A1UdDgQWBBQT6Y9J3Tw/hOGc8PNV7JEE4k2ZNTA0BgNVHREELTArggtzYW1sdGVz +dC5pZIYcaHR0cHM6Ly9zYW1sdGVzdC5pZC9zYW1sL2lkcDANBgkqhkiG9w0BAQsF +AAOCAQEASk3guKfTkVhEaIVvxEPNR2w3vWt3fwmwJCccW98XXLWgNbu3YaMb2RSn +7Th4p3h+mfyk2don6au7Uyzc1Jd39RNv80TG5iQoxfCgphy1FYmmdaSfO8wvDtHT +TNiLArAxOYtzfYbzb5QrNNH/gQEN8RJaEf/g/1GTw9x/103dSMK0RXtl+fRs2nbl +D1JJKSQ3AdhxK/weP3aUPtLxVVJ9wMOQOfcy02l+hHMb6uAjsPOpOVKqi3M8XmcU +ZOpx4swtgGdeoSpeRyrtMvRwdcciNBp9UZome44qZAYH1iqrpmmjsfI9pJItsgWu +3kXPjhSfj1AJGR1l9JGvJrHki1iHTA== + + + + + + + + + +MIIDEjCCAfqgAwIBAgIVAPVbodo8Su7/BaHXUHykx0Pi5CFaMA0GCSqGSIb3DQEB +CwUAMBYxFDASBgNVBAMMC3NhbWx0ZXN0LmlkMB4XDTE4MDgyNDIxMTQwOVoXDTM4 +MDgyNDIxMTQwOVowFjEUMBIGA1UEAwwLc2FtbHRlc3QuaWQwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCQb+1a7uDdTTBBFfwOUun3IQ9nEuKM98SmJDWa +MwM877elswKUTIBVh5gB2RIXAPZt7J/KGqypmgw9UNXFnoslpeZbA9fcAqqu28Z4 +sSb2YSajV1ZgEYPUKvXwQEmLWN6aDhkn8HnEZNrmeXihTFdyr7wjsLj0JpQ+VUlc +4/J+hNuU7rGYZ1rKY8AA34qDVd4DiJ+DXW2PESfOu8lJSOteEaNtbmnvH8KlwkDs +1NvPTsI0W/m4SK0UdXo6LLaV8saIpJfnkVC/FwpBolBrRC/Em64UlBsRZm2T89ca +uzDee2yPUvbBd5kLErw+sC7i4xXa2rGmsQLYcBPhsRwnmBmlAgMBAAGjVzBVMB0G +A1UdDgQWBBRZ3exEu6rCwRe5C7f5QrPcAKRPUjA0BgNVHREELTArggtzYW1sdGVz +dC5pZIYcaHR0cHM6Ly9zYW1sdGVzdC5pZC9zYW1sL2lkcDANBgkqhkiG9w0BAQsF +AAOCAQEABZDFRNtcbvIRmblnZItoWCFhVUlq81ceSQddLYs8DqK340//hWNAbYdj +WcP85HhIZnrw6NGCO4bUipxZXhiqTA/A9d1BUll0vYB8qckYDEdPDduYCOYemKkD +dmnHMQWs9Y6zWiYuNKEJ9mf3+1N8knN/PK0TYVjVjXAf2CnOETDbLtlj6Nqb8La3 +sQkYmU+aUdopbjd5JFFwbZRaj6KiHXHtnIRgu8sUXNPrgipUgZUOVhP0C0N5OfE4 +JW8ZBrKgQC/6vJ2rSa9TlzI6JAa5Ww7gMXMP9M+cJUNQklcq+SBnTK8G+uBHgPKR +zBDsMIEzRtQZm4GIoHJae4zmnCekkQ== + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/com/coveo/saml/samltest.id/sp.xml b/src/test/resources/com/coveo/saml/samltest.id/sp.xml new file mode 100644 index 0000000..8914613 --- /dev/null +++ b/src/test/resources/com/coveo/saml/samltest.id/sp.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JD Test SP + A free and basic SP for testing SAML deployments + https://samltest.id/saml/logo.png + + + + + + + + +MIIDujCCAqKgAwIBAgIJAJHfaVlpD2MLMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNV +BAYTAkZSMQowCAYDVQQIDAEtMQowCAYDVQQHDAEtMQowCAYDVQQKDAEtMQowCAYD +VQQLDAEtMRQwEgYDVQQDDAtzYW1sLWNsaWVudDEdMBsGCSqGSIb3DQEJARYObm9A +YWRkcmVzcy5jb20wHhcNMTgwOTIwMTMyOTM1WhcNMTkwOTIwMTMyOTM1WjByMQsw +CQYDVQQGEwJGUjEKMAgGA1UECAwBLTEKMAgGA1UEBwwBLTEKMAgGA1UECgwBLTEK +MAgGA1UECwwBLTEUMBIGA1UEAwwLc2FtbC1jbGllbnQxHTAbBgkqhkiG9w0BCQEW +Dm5vQGFkZHJlc3MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +r4KWtZ++3+wF+P5vHBus58gPhM5z2eH3bCmYYMtz/sdssjzYs7gvDP4fCaQy2mma +ntMzJaYdLA08SON49yy7lcRK40E4MQ+DvksPS64bMwjRvLyN96ANKxJnoUejL6uI +86yGwH09ad/J/2GC0kmGgEctxZ60QHVqK5Bn4Yv/RoXC5jskbEIrbACJ8rwa7/8z +1rM8r1tsm7jy/8NqsHvFQ0IfB8N27zqdrC77XsaYZWHJzBbyaSC0AnAOOXXMOdoL +YpsaeXw5nZ3knwKY5PyEODuCv/5rOoJkpqdN5zX06yZ7uubQ1+o3cHyV/UErZmk7 +oPLvMezc/lwtUmHBvY0LMQIDAQABo1MwUTAdBgNVHQ4EFgQUoU2D/5UEJ1CiB8eH +OOb7p0OUSJkwHwYDVR0jBBgwFoAUoU2D/5UEJ1CiB8eHOOb7p0OUSJkwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAE6c4YayrDUcXNcK20CUD0xSe +5Pf75gKBSf7s8C2EEk4kQ4W1yc5dG7pnCGhKNhOpeYWgTEHnFyb1311wDuhW2+a6 +uWLLkOr2R2qALPhPlMQVXKzA3smp8TydqFat1TTylrTo+lBmIWXBB9LczuqPpYKh +JOcJpDQW7J/asKpHnh+Jd7eMGs8PzI1sDoXEb3JuzMC1OQW/o750+IybAC3YH4jH +0uKlcKvIfC3puO99+Et3cZqgH0CMmsdy1KA8intCRcvIutm6upjZs4IFS+jC2mMn +EXZmiWfP5l4t8OPE0g5jdSuomDHAK63LNiCps4GVRXMtihRqN5j1+RGetfSUQA== + + + + + + + + + +MIIDujCCAqKgAwIBAgIJAJHfaVlpD2MLMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNV +BAYTAkZSMQowCAYDVQQIDAEtMQowCAYDVQQHDAEtMQowCAYDVQQKDAEtMQowCAYD +VQQLDAEtMRQwEgYDVQQDDAtzYW1sLWNsaWVudDEdMBsGCSqGSIb3DQEJARYObm9A +YWRkcmVzcy5jb20wHhcNMTgwOTIwMTMyOTM1WhcNMTkwOTIwMTMyOTM1WjByMQsw +CQYDVQQGEwJGUjEKMAgGA1UECAwBLTEKMAgGA1UEBwwBLTEKMAgGA1UECgwBLTEK +MAgGA1UECwwBLTEUMBIGA1UEAwwLc2FtbC1jbGllbnQxHTAbBgkqhkiG9w0BCQEW +Dm5vQGFkZHJlc3MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +r4KWtZ++3+wF+P5vHBus58gPhM5z2eH3bCmYYMtz/sdssjzYs7gvDP4fCaQy2mma +ntMzJaYdLA08SON49yy7lcRK40E4MQ+DvksPS64bMwjRvLyN96ANKxJnoUejL6uI +86yGwH09ad/J/2GC0kmGgEctxZ60QHVqK5Bn4Yv/RoXC5jskbEIrbACJ8rwa7/8z +1rM8r1tsm7jy/8NqsHvFQ0IfB8N27zqdrC77XsaYZWHJzBbyaSC0AnAOOXXMOdoL +YpsaeXw5nZ3knwKY5PyEODuCv/5rOoJkpqdN5zX06yZ7uubQ1+o3cHyV/UErZmk7 +oPLvMezc/lwtUmHBvY0LMQIDAQABo1MwUTAdBgNVHQ4EFgQUoU2D/5UEJ1CiB8eH +OOb7p0OUSJkwHwYDVR0jBBgwFoAUoU2D/5UEJ1CiB8eHOOb7p0OUSJkwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAE6c4YayrDUcXNcK20CUD0xSe +5Pf75gKBSf7s8C2EEk4kQ4W1yc5dG7pnCGhKNhOpeYWgTEHnFyb1311wDuhW2+a6 +uWLLkOr2R2qALPhPlMQVXKzA3smp8TydqFat1TTylrTo+lBmIWXBB9LczuqPpYKh +JOcJpDQW7J/asKpHnh+Jd7eMGs8PzI1sDoXEb3JuzMC1OQW/o750+IybAC3YH4jH +0uKlcKvIfC3puO99+Et3cZqgH0CMmsdy1KA8intCRcvIutm6upjZs4IFS+jC2mMn +EXZmiWfP5l4t8OPE0g5jdSuomDHAK63LNiCps4GVRXMtihRqN5j1+RGetfSUQA== + + + + + + + + + + + + + + + + + + + + + + + + + + + +