Fix for KEYCLOAK-18914 (#9355)

Closed #9382 

Co-authored-by: Hans-Christian Halfbrodt <hc-github42@halfbrodt.org>
This commit is contained in:
Hans-Christian Halfbrodt 2022-01-06 18:05:50 +01:00 committed by GitHub
parent 1cebd79f1c
commit d9d77fe1f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 235 additions and 1159 deletions

View file

@ -33,6 +33,7 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AssertionType;
@ -57,7 +58,6 @@ import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException; import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.Base64;
import org.keycloak.saml.common.util.DocumentUtil; import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature; import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder; import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;

View file

@ -1,5 +1,7 @@
package org.keycloak.common.util; package org.keycloak.common.util;
import java.io.IOException;
/** /**
* <p>Encodes and decodes to and from Base64 notation.</p> * <p>Encodes and decodes to and from Base64 notation.</p>
* <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p> * <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p>
@ -35,6 +37,10 @@ package org.keycloak.common.util;
* Change Log: * Change Log:
* </p> * </p>
* <ul> * <ul>
* <li>v2.3.8 - Fixed automatic gzip decoding, based on the content,
* as this may lead to unexpected behaviour. Request either gzipped
* or non gzipped decoding as excepted. Automatic encoding is especially
* problematic with generated input (see KEYCLOAK-18914 for a detailed case).</li>
* <li>v2.3.7 - Fixed subtle bug when base 64 input stream contained the * <li>v2.3.7 - Fixed subtle bug when base 64 input stream contained the
* value 01111111, which is an invalid base 64 character but should not * value 01111111, which is an invalid base 64 character but should not
* throw an ArrayIndexOutOfBoundsException either. Led to discovery of * throw an ArrayIndexOutOfBoundsException either. Led to discovery of
@ -76,7 +82,7 @@ package org.keycloak.common.util;
* <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>.</li> * <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>.</li>
* <li><em>Throws exceptions instead of returning null values.</em> Because some operations * <li><em>Throws exceptions instead of returning null values.</em> Because some operations
* (especially those that may permit the GZIP option) use IO streams, there * (especially those that may permit the GZIP option) use IO streams, there
* is a possiblity of an java.io.IOException being thrown. After some discussion and * is a possibility of an java.io.IOException being thrown. After some discussion and
* thought, I've changed the behavior of the methods to throw java.io.IOExceptions * thought, I've changed the behavior of the methods to throw java.io.IOExceptions
* rather than return null if ever there's an error. I think this is more * rather than return null if ever there's an error. I think this is more
* appropriate, though it will require some changes to your code. Sorry, * appropriate, though it will require some changes to your code. Sorry,
@ -167,9 +173,8 @@ public class Base64
/** Specify that data should be gzip-compressed in second bit. Value is two. */ /** Specify that data should be gzip-compressed in second bit. Value is two. */
public final static int GZIP = 2; public final static int GZIP = 2;
/** Specify that gzipped data should <em>not</em> be automatically gunzipped. */ /** Specify that data should be gunzipped. */
public final static int DONT_GUNZIP = 4; public final static int GUNZIP = 4;
/** Do break lines when encoding. Value is 8. */ /** Do break lines when encoding. Value is 8. */
public final static int DO_BREAK_LINES = 8; public final static int DO_BREAK_LINES = 8;
@ -179,7 +184,7 @@ public class Base64
* in Section 4 of RFC3548: * in Section 4 of RFC3548:
* <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. * <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>.
* It is important to note that data encoded this way is <em>not</em> officially valid Base64, * It is important to note that data encoded this way is <em>not</em> officially valid Base64,
* or at the very least should not be called Base64 without also specifying that is * or at the very least should not be called Base64 without also specifying that it
* was encoded using the URL- and Filename-safe dialect. * was encoded using the URL- and Filename-safe dialect.
*/ */
public final static int URL_SAFE = 16; public final static int URL_SAFE = 16;
@ -476,7 +481,7 @@ public class Base64
* anywhere along their length by specifying * anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>. * <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays * This method does not check to make sure your arrays
* are large enough to accomodate <var>srcOffset</var> + 3 for * are large enough to accommodate <var>srcOffset</var> + 3 for
* the <var>source</var> array or <var>destOffset</var> + 4 for * the <var>source</var> array or <var>destOffset</var> + 4 for
* the <var>destination</var> array. * the <var>destination</var> array.
* The actual number of significant bytes in your array is * The actual number of significant bytes in your array is
@ -548,7 +553,7 @@ public class Base64
* writing it to the <code>encoded</code> ByteBuffer. * writing it to the <code>encoded</code> ByteBuffer.
* This is an experimental feature. Currently it does not * This is an experimental feature. Currently it does not
* pass along any options (such as {@link #DO_BREAK_LINES} * pass along any options (such as {@link #DO_BREAK_LINES}
* or {@link #GZIP}. * or {@link #GZIP}).
* *
* @param raw input buffer * @param raw input buffer
* @param encoded output buffer * @param encoded output buffer
@ -733,7 +738,7 @@ public class Base64
* Example options:<pre> * Example options:<pre>
* GZIP: gzip-compresses object before encoding it. * GZIP: gzip-compresses object before encoding it.
* DO_BREAK_LINES: break lines at 76 characters * DO_BREAK_LINES: break lines at 76 characters
* <i>Note: Technically, this makes your encoding non-compliant.</i> * <i>Note: Technically, without line break your encoding may become non-compliant (see rfc2045 and rfc4648).</i>
* </pre> * </pre>
* <p> * <p>
* Example: <code>encodeBytes( myData, Base64.GZIP )</code> or * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
@ -1115,15 +1120,8 @@ public class Base64
* @return decoded data * @return decoded data
* @since 2.3.1 * @since 2.3.1
*/ */
public static byte[] decode( byte[] source ) public static byte[] decode( byte[] source ) throws java.io.IOException {
throws java.io.IOException { return decode( source, 0, source.length, Base64.NO_OPTIONS );
byte[] decoded = null;
// try {
decoded = decode( source, 0, source.length, Base64.NO_OPTIONS );
// } catch( java.io.IOException ex ) {
// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
// }
return decoded;
} }
@ -1231,9 +1229,9 @@ public class Base64
* detecting gzip-compressed data and decompressing it. * detecting gzip-compressed data and decompressing it.
* *
* @param s the string to decode * @param s the string to decode
* @param options encode options such as URL_SAFE * @param options decode options such as URL_SAFE or GUNZIP
* @return the decoded data * @return the decoded data
* @throws java.io.IOException if there is an error * @throws java.io.IOException if there is an error (invalid character in source string or gunzip error)
* @throws NullPointerException if <tt>s</tt> is null * @throws NullPointerException if <tt>s</tt> is null
* @since 1.4 * @since 1.4
*/ */
@ -1257,46 +1255,41 @@ public class Base64
// Check to see if it's gzip-compressed // Check to see if it's gzip-compressed
// GZIP Magic Two-Byte Number: 0x8b1f (35615) // GZIP Magic Two-Byte Number: 0x8b1f (35615)
boolean dontGunzip = (options & DONT_GUNZIP) != 0; boolean doGunzip = (options & GUNZIP) != 0;
if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { if( (bytes != null) && (bytes.length >= 4) && doGunzip ) {
int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { if( java.util.zip.GZIPInputStream.GZIP_MAGIC != head ) {
java.io.ByteArrayInputStream bais = null; throw new IOException("Provided data has no GZIP magic header.");
java.util.zip.GZIPInputStream gzis = null; }
java.io.ByteArrayOutputStream baos = null; java.io.ByteArrayInputStream bais = null;
byte[] buffer = new byte[2048]; java.util.zip.GZIPInputStream gzis = null;
int length = 0; java.io.ByteArrayOutputStream baos = null;
byte[] buffer = new byte[2048];
int length = 0;
try { try {
baos = new java.io.ByteArrayOutputStream(); baos = new java.io.ByteArrayOutputStream();
bais = new java.io.ByteArrayInputStream( bytes ); bais = new java.io.ByteArrayInputStream( bytes );
gzis = new java.util.zip.GZIPInputStream( bais ); gzis = new java.util.zip.GZIPInputStream( bais );
while( ( length = gzis.read( buffer ) ) >= 0 ) { while( ( length = gzis.read( buffer ) ) >= 0 ) {
baos.write(buffer,0,length); baos.write(buffer,0,length);
} // end while: reading input } // end while: reading input
// No error? Get new bytes. // No error? Get new bytes.
bytes = baos.toByteArray(); bytes = baos.toByteArray();
} // end try } // end try
catch( java.io.IOException e ) { catch( java.io.IOException e ) {
if (e.getMessage().equals("Unsupported compression method")) { throw new IOException("Failed to gunzip", e);
System.out.println("Base64 decoding: Ignoring GZIP header and just returning originally-decoded bytes."); // Better to log as debug, but jboss logging not available in the module :/ } // end catch
} else { finally {
e.printStackTrace(); try{ baos.close(); } catch( Exception e ){}
} try{ gzis.close(); } catch( Exception e ){}
try{ bais.close(); } catch( Exception e ){}
} // end finally
// Just return originally-decoded bytes
} // end catch
finally {
try{ baos.close(); } catch( Exception e ){}
try{ gzis.close(); } catch( Exception e ){}
try{ bais.close(); } catch( Exception e ){}
} // end finally
} // end if: gzipped
} // end if: bytes.length >= 2 } // end if: bytes.length >= 2
return bytes; return bytes;

View file

@ -31,8 +31,7 @@ public class Base64Url {
public static byte[] decode(String s) { public static byte[] decode(String s) {
s = encodeBase64UrlToBase64(s); s = encodeBase64UrlToBase64(s);
try { try {
// KEYCLOAK-2479 : Avoid to try gzip decoding as for some objects, it may display exception to STDERR. And we know that object wasn't encoded as GZIP return Base64.decode(s);
return Base64.decode(s, Base64.DONT_GUNZIP);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View file

@ -0,0 +1,135 @@
package org.keycloak.common.util;
import org.hamcrest.MatcherAssert;
import org.junit.Test;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
/**
* Test for BASE64 decode implementation.
*
* @author <a href="mailto:keycloak-hc@halfbrodt.org">Hans-Christian Halfbrodt</a>
*/
public class Base64DecodeTest {
@Test
public void decode_simple() throws IOException {
// high level string variant
final String testData = "test data";
final String encoded = "dGVzdCBkYXRh";
final String decoded = new String(Base64.decode(encoded));
assertThat(decoded, equalTo(testData));
// low level byte array variant
final byte[] encoded2 = encoded.getBytes();
final String decoded2 = new String(Base64.decode(encoded2));
assertThat(decoded2, equalTo(testData));
}
@Test
public void decode_doNotUseGzip() throws IOException {
// Input has gzip magic byte by coincidence and should not be gunzipped. (KEYCLOAK-18914)
// high level string variant
final byte[] testData = new byte[]{
31, -117, 8, -56, 1, 1, 1, 1, 1, 1, 43, 73, 45, 46,
81, 72, 73, 44, 73, 4, 1, -78, -82, 8, -45, 9, 1, 1, 1};
final String encoded = "H4sIyAEBAQEBAStJLS5RSEksSQQBsq4I0wkBAQE=";
final byte[] decoded = Base64.decode(encoded);
assertThat(decoded, equalTo(testData));
// low level byte array variant
final byte[] encoded2 = encoded.getBytes();
final byte[] decoded2 = Base64.decode(encoded2);
assertThat(decoded2, equalTo(testData));
}
@Test
public void decode_gzip() throws IOException {
// high level string variant
final String testData = "test data";
final String encoded = "H4sIAAAAAAAAACtJLS5RSEksSQQAsq4I0wkAAAA=";
final String decoded = new String(Base64.decode(encoded, Base64.GUNZIP));
assertThat(decoded, equalTo(testData));
// low level byte array variant
// specified to ignore gunzip option (see javadoc)
final byte[] expected2 = new byte[]{
31, -117, 8, 0, 0, 0, 0, 0, 0, 0, 43, 73,
45, 46, 81, 72, 73, 44, 73, 4, 0, -78, -82,
8, -45, 9, 0, 0, 0};
final byte[] encoded2 = encoded.getBytes();
final byte[] decoded2 = Base64.decode(encoded2, 0, encoded2.length, Base64.GUNZIP);
assertThat(decoded2, equalTo(expected2));
}
@Test
public void decode_empty() throws IOException {
final byte[] result = Base64.decode("");
assertThat(result, equalTo(new byte[0]));
final byte[] result2 = Base64.decode(new byte[0]);
assertThat(result2, equalTo(new byte[0]));
try {
Base64.decode(" ");
MatcherAssert.assertThat("Exception excepted", false);
} catch (final Exception e) {
assertThat(e, instanceOf(IllegalArgumentException.class));
}
try {
Base64.decode(" ".getBytes());
MatcherAssert.assertThat("Exception excepted", false);
} catch (final Exception e) {
assertThat(e, instanceOf(IllegalArgumentException.class));
}
try {
Base64.decode((String) null);
MatcherAssert.assertThat("Exception excepted", false);
} catch (final Exception e) {
assertThat(e, instanceOf(NullPointerException.class));
}
try {
Base64.decode((byte[]) null);
MatcherAssert.assertThat("Exception excepted", false);
} catch (final Exception e) {
assertThat(e, instanceOf(NullPointerException.class));
}
}
@Test
public void decode_lowLevelInvalidParams() {
try {
Base64.decode(null, 0, 1, Base64.NO_OPTIONS);
MatcherAssert.assertThat("Exception excepted", false);
} catch (final Exception e){
assertThat(e, instanceOf(NullPointerException.class));
}
try {
Base64.decode(new byte[2], 0, 1, Base64.NO_OPTIONS);
MatcherAssert.assertThat("Exception excepted", false);
} catch (final Exception e){
assertThat(e, instanceOf(IllegalArgumentException.class));
}
try {
Base64.decode(new byte[8], 0, 10, Base64.NO_OPTIONS);
MatcherAssert.assertThat("Exception excepted", false);
} catch (final Exception e){
assertThat(e, instanceOf(IllegalArgumentException.class));
}
try {
Base64.decode(new byte[8], 5, 5, Base64.NO_OPTIONS);
MatcherAssert.assertThat("Exception excepted", false);
} catch (final Exception e){
assertThat(e, instanceOf(IllegalArgumentException.class));
}
}
}

View file

@ -17,9 +17,9 @@
package org.keycloak.dom.xmlsec.w3.xmldsig; package org.keycloak.dom.xmlsec.w3.xmldsig;
import org.keycloak.common.util.Base64;
import org.keycloak.saml.common.constants.WSTrustConstants; import org.keycloak.saml.common.constants.WSTrustConstants;
import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.Base64;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.KeyFactory; import java.security.KeyFactory;
@ -201,14 +201,13 @@ public class DSAKeyValueType implements KeyValueType {
* @throws org.keycloak.saml.common.exceptions.ProcessingException * @throws org.keycloak.saml.common.exceptions.ProcessingException
*/ */
public DSAPublicKey convertToPublicKey() throws ProcessingException { public DSAPublicKey convertToPublicKey() throws ProcessingException {
BigInteger BigY, BigP, BigQ, BigG;
BigY = new BigInteger(1, massage(Base64.decode(new String(y))));
BigP = new BigInteger(1, massage(Base64.decode(new String(p))));
BigQ = new BigInteger(1, massage(Base64.decode(new String(q))));
BigG = new BigInteger(1, massage(Base64.decode(new String(g))));
try { try {
BigInteger BigY = new BigInteger(1, massage(Base64.decode(new String(y))));
BigInteger BigP = new BigInteger(1, massage(Base64.decode(new String(p))));
BigInteger BigQ = new BigInteger(1, massage(Base64.decode(new String(q))));
BigInteger BigG = new BigInteger(1, massage(Base64.decode(new String(g))));
KeyFactory dsaKeyFactory = KeyFactory.getInstance("dsa"); KeyFactory dsaKeyFactory = KeyFactory.getInstance("dsa");
DSAPublicKeySpec kspec = new DSAPublicKeySpec(BigY, BigP, BigQ, BigG); DSAPublicKeySpec kspec = new DSAPublicKeySpec(BigY, BigP, BigQ, BigG);
return (DSAPublicKey) dsaKeyFactory.generatePublic(kspec); return (DSAPublicKey) dsaKeyFactory.generatePublic(kspec);
@ -225,14 +224,13 @@ public class DSAKeyValueType implements KeyValueType {
* @throws ProcessingException * @throws ProcessingException
*/ */
public DSAPrivateKey convertToPrivateKey() throws ProcessingException { public DSAPrivateKey convertToPrivateKey() throws ProcessingException {
BigInteger BigY, BigP, BigQ, BigG;
BigY = new BigInteger(1, massage(Base64.decode(new String(y))));
BigP = new BigInteger(1, massage(Base64.decode(new String(p))));
BigQ = new BigInteger(1, massage(Base64.decode(new String(q))));
BigG = new BigInteger(1, massage(Base64.decode(new String(g))));
try { try {
BigInteger BigY = new BigInteger(1, massage(Base64.decode(new String(y))));
BigInteger BigP = new BigInteger(1, massage(Base64.decode(new String(p))));
BigInteger BigQ = new BigInteger(1, massage(Base64.decode(new String(q))));
BigInteger BigG = new BigInteger(1, massage(Base64.decode(new String(g))));
KeyFactory dsaKeyFactory = KeyFactory.getInstance("dsa"); KeyFactory dsaKeyFactory = KeyFactory.getInstance("dsa");
DSAPrivateKeySpec kspec = new DSAPrivateKeySpec(BigY, BigP, BigQ, BigG); DSAPrivateKeySpec kspec = new DSAPrivateKeySpec(BigY, BigP, BigQ, BigG);
return (DSAPrivateKey) dsaKeyFactory.generatePrivate(kspec); return (DSAPrivateKey) dsaKeyFactory.generatePrivate(kspec);
@ -300,4 +298,4 @@ public class DSAKeyValueType implements KeyValueType {
} }
return byteArray; return byteArray;
} }
} }

View file

@ -17,9 +17,9 @@
package org.keycloak.dom.xmlsec.w3.xmldsig; package org.keycloak.dom.xmlsec.w3.xmldsig;
import org.keycloak.common.util.Base64;
import org.keycloak.saml.common.constants.WSTrustConstants; import org.keycloak.saml.common.constants.WSTrustConstants;
import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.Base64;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.KeyFactory; import java.security.KeyFactory;
@ -97,10 +97,9 @@ public class RSAKeyValueType implements KeyValueType {
* @throws org.keycloak.saml.common.exceptions.ProcessingException * @throws org.keycloak.saml.common.exceptions.ProcessingException
*/ */
public RSAPublicKey convertToPublicKey() throws ProcessingException { public RSAPublicKey convertToPublicKey() throws ProcessingException {
BigInteger bigModulus = new BigInteger(1, massage(Base64.decode(new String(modulus))));
BigInteger bigEx = new BigInteger(1, massage(Base64.decode(new String(exponent))));
try { try {
BigInteger bigModulus = new BigInteger(1, massage(Base64.decode(new String(modulus))));
BigInteger bigEx = new BigInteger(1, massage(Base64.decode(new String(exponent))));
KeyFactory rsaKeyFactory = KeyFactory.getInstance("rsa"); KeyFactory rsaKeyFactory = KeyFactory.getInstance("rsa");
RSAPublicKeySpec kspec = new RSAPublicKeySpec(bigModulus, bigEx); RSAPublicKeySpec kspec = new RSAPublicKeySpec(bigModulus, bigEx);
return (RSAPublicKey) rsaKeyFactory.generatePublic(kspec); return (RSAPublicKey) rsaKeyFactory.generatePublic(kspec);
@ -117,10 +116,9 @@ public class RSAKeyValueType implements KeyValueType {
* @throws ProcessingException * @throws ProcessingException
*/ */
public RSAPrivateKey convertToPrivateKey() throws ProcessingException { public RSAPrivateKey convertToPrivateKey() throws ProcessingException {
BigInteger bigModulus = new BigInteger(1, massage(Base64.decode(new String(modulus))));
BigInteger bigEx = new BigInteger(1, massage(Base64.decode(new String(exponent))));
try { try {
BigInteger bigModulus = new BigInteger(1, massage(Base64.decode(new String(modulus))));
BigInteger bigEx = new BigInteger(1, massage(Base64.decode(new String(exponent))));
KeyFactory rsaKeyFactory = KeyFactory.getInstance("rsa"); KeyFactory rsaKeyFactory = KeyFactory.getInstance("rsa");
RSAPrivateKeySpec kspec = new RSAPrivateKeySpec(bigModulus, bigEx); RSAPrivateKeySpec kspec = new RSAPrivateKeySpec(bigModulus, bigEx);
return (RSAPrivateKey) rsaKeyFactory.generatePrivate(kspec); return (RSAPrivateKey) rsaKeyFactory.generatePrivate(kspec);
@ -160,4 +158,4 @@ public class RSAKeyValueType implements KeyValueType {
} }
return byteArray; return byteArray;
} }
} }

View file

@ -43,7 +43,12 @@ public class SAMLRequestParser {
public static SAMLDocumentHolder parseRequestRedirectBinding(String samlMessage) { public static SAMLDocumentHolder parseRequestRedirectBinding(String samlMessage) {
InputStream is; InputStream is;
is = RedirectBindingUtil.base64DeflateDecode(samlMessage); try {
is = RedirectBindingUtil.base64DeflateDecode(samlMessage);
} catch (IOException e) {
logger.samlBase64DecodingError(e);
return null;
}
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
String message = null; String message = null;
try { try {
@ -105,7 +110,13 @@ public class SAMLRequestParser {
} }
public static SAMLDocumentHolder parseResponseRedirectBinding(String samlMessage) { public static SAMLDocumentHolder parseResponseRedirectBinding(String samlMessage) {
InputStream is = RedirectBindingUtil.base64DeflateDecode(samlMessage); InputStream is;
try {
is = RedirectBindingUtil.base64DeflateDecode(samlMessage);
} catch (IOException e) {
logger.samlBase64DecodingError(e);
return null;
}
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
String message = null; String message = null;
try { try {

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.saml.processing.core.saml.v2.util; package org.keycloak.saml.processing.core.saml.v2.util;
import org.keycloak.common.util.Base64;
import org.keycloak.dom.xmlsec.w3.xmldsig.DSAKeyValueType; import org.keycloak.dom.xmlsec.w3.xmldsig.DSAKeyValueType;
import org.keycloak.dom.xmlsec.w3.xmldsig.KeyValueType; import org.keycloak.dom.xmlsec.w3.xmldsig.KeyValueType;
import org.keycloak.dom.xmlsec.w3.xmldsig.RSAKeyValueType; import org.keycloak.dom.xmlsec.w3.xmldsig.RSAKeyValueType;
@ -24,14 +25,8 @@ import org.keycloak.saml.common.PicketLinkLogger;
import org.keycloak.saml.common.PicketLinkLoggerFactory; import org.keycloak.saml.common.PicketLinkLoggerFactory;
import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLConstants; import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.WSTrustConstants;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.util.Base64;
import org.keycloak.saml.processing.core.constants.PicketLinkFederationConstants; import org.keycloak.saml.processing.core.constants.PicketLinkFederationConstants;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
@ -220,4 +215,4 @@ public class SignatureUtil {
throw logger.signatureUnknownAlgo(algo); throw logger.signatureUnknownAlgo(algo);
return sig; return sig;
} }
} }

View file

@ -16,6 +16,7 @@
*/ */
package org.keycloak.saml.processing.core.util; package org.keycloak.saml.processing.core.util;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.dom.xmlsec.w3.xmldsig.DSAKeyValueType; import org.keycloak.dom.xmlsec.w3.xmldsig.DSAKeyValueType;
import org.keycloak.dom.xmlsec.w3.xmldsig.KeyValueType; import org.keycloak.dom.xmlsec.w3.xmldsig.KeyValueType;
@ -28,7 +29,6 @@ import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.WSTrustConstants; import org.keycloak.saml.common.constants.WSTrustConstants;
import org.keycloak.saml.common.exceptions.ParsingException; import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.Base64;
import org.keycloak.saml.common.util.DocumentUtil; import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.common.util.StringUtil; import org.keycloak.saml.common.util.StringUtil;
import org.keycloak.saml.common.util.SystemPropertiesUtil; import org.keycloak.saml.common.util.SystemPropertiesUtil;
@ -519,7 +519,7 @@ public class XMLSignatureUtil {
DOMValidateContext valContext = new DOMValidateContext(validationKeySelector, signatureNode); DOMValidateContext valContext = new DOMValidateContext(validationKeySelector, signatureNode);
XMLSignature signature = fac.unmarshalXMLSignature(valContext); XMLSignature signature = fac.unmarshalXMLSignature(valContext);
boolean coreValidity = signature.validate(valContext); boolean coreValidity = signature.validate(valContext);
if (! coreValidity) { if (! coreValidity) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
boolean sv = signature.getSignatureValue().validate(valContext); boolean sv = signature.getSignatureValue().validate(valContext);
@ -758,4 +758,4 @@ public class XMLSignatureUtil {
return keyInfoFactory.newKeyInfo(items); return keyInfoFactory.newKeyInfo(items);
} }
} }

View file

@ -16,10 +16,10 @@
*/ */
package org.keycloak.saml.processing.web.util; package org.keycloak.saml.processing.web.util;
import org.keycloak.common.util.Base64;
import org.keycloak.saml.common.PicketLinkLogger; import org.keycloak.saml.common.PicketLinkLogger;
import org.keycloak.saml.common.PicketLinkLoggerFactory; import org.keycloak.saml.common.PicketLinkLoggerFactory;
import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.util.Base64;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
@ -43,7 +43,7 @@ public class PostBindingUtil {
* @return * @return
*/ */
public static String base64Encode(String stringToEncode) throws IOException { public static String base64Encode(String stringToEncode) throws IOException {
return Base64.encodeBytes(stringToEncode.getBytes(GeneralConstants.SAML_CHARSET), Base64.DONT_BREAK_LINES); return Base64.encodeBytes(stringToEncode.getBytes(GeneralConstants.SAML_CHARSET));
} }
/** /**
@ -57,7 +57,12 @@ public class PostBindingUtil {
if (encodedString == null) if (encodedString == null)
throw logger.nullArgumentError("encodedString"); throw logger.nullArgumentError("encodedString");
return Base64.decode(encodedString); try {
return Base64.decode(encodedString);
} catch (Exception e) {
logger.error(e);
throw logger.invalidArgumentError("base64 decode failed: " + e.getMessage());
}
} }
/** /**
@ -87,4 +92,4 @@ public class PostBindingUtil {
return escaped.toString(); return escaped.toString();
} }
} }

View file

@ -16,8 +16,8 @@
*/ */
package org.keycloak.saml.processing.web.util; package org.keycloak.saml.processing.web.util;
import org.keycloak.common.util.Base64;
import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.util.Base64;
import org.keycloak.saml.common.util.StringUtil; import org.keycloak.saml.common.util.StringUtil;
import org.keycloak.saml.processing.api.util.DeflateUtil; import org.keycloak.saml.processing.api.util.DeflateUtil;
@ -70,7 +70,7 @@ public class RedirectBindingUtil {
* @throws IOException * @throws IOException
*/ */
public static String base64Encode(byte[] stringToEncode) throws IOException { public static String base64Encode(byte[] stringToEncode) throws IOException {
return Base64.encodeBytes(stringToEncode, Base64.DONT_BREAK_LINES); return Base64.encodeBytes(stringToEncode);
} }
/** /**
@ -83,7 +83,7 @@ public class RedirectBindingUtil {
* @throws IOException * @throws IOException
*/ */
public static String base64URLEncode(byte[] stringToEncode) throws IOException { public static String base64URLEncode(byte[] stringToEncode) throws IOException {
String base64Request = Base64.encodeBytes(stringToEncode, Base64.DONT_BREAK_LINES); String base64Request = Base64.encodeBytes(stringToEncode);
return urlEncode(base64Request); return urlEncode(base64Request);
} }
@ -139,7 +139,7 @@ public class RedirectBindingUtil {
*/ */
public static String deflateBase64Encode(byte[] stringToEncode) throws IOException { public static String deflateBase64Encode(byte[] stringToEncode) throws IOException {
byte[] deflatedMsg = DeflateUtil.encode(stringToEncode); byte[] deflatedMsg = DeflateUtil.encode(stringToEncode);
return Base64.encodeBytes(deflatedMsg, Base64.DONT_BREAK_LINES); return Base64.encodeBytes(deflatedMsg);
} }
/** /**
@ -162,8 +162,10 @@ public class RedirectBindingUtil {
* @param encodedString * @param encodedString
* *
* @return * @return
*
* @throws IOException
*/ */
public static InputStream base64DeflateDecode(String encodedString) { public static InputStream base64DeflateDecode(String encodedString) throws IOException {
byte[] base64decodedMsg = Base64.decode(encodedString); byte[] base64decodedMsg = Base64.decode(encodedString);
return DeflateUtil.decode(base64decodedMsg); return DeflateUtil.decode(base64decodedMsg);
} }
@ -229,4 +231,4 @@ public class RedirectBindingUtil {
return this; return this;
} }
} }
} }