From 69699431b1fd62e4997333909b73bf21d5ef076f Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Tue, 24 Nov 2009 09:31:03 +0000 Subject: [PATCH] SEC-1303: Added internal Hex and Base64 classes, and moved commons-codec dependency to test scope --- config/pom.xml | 1 + core/pom.xml | 4 - .../encoding/LdapShaPasswordEncoder.java | 11 +- .../encoding/Md4PasswordEncoder.java | 16 +- .../MessageDigestPasswordEncoder.java | 12 +- .../security/core/codec/Base64.java | 649 ++++++++++++++++++ .../security/core/codec/Hex.java | 37 + .../KeyBasedPersistenceTokenService.java | 50 +- .../core/token/Sha512DigestUtils.java | 14 +- ...ationSimpleHttpInvokerRequestExecutor.java | 15 +- .../security/util/EncryptionUtils.java | 10 +- pom.xml | 5 - web/pom.xml | 6 + .../expression/WebSecurityExpressionRoot.java | 11 +- .../AbstractRememberMeServices.java | 16 +- ...ersistentTokenBasedRememberMeServices.java | 6 +- .../TokenBasedRememberMeServices.java | 16 +- .../www/BasicAuthenticationFilter.java | 4 +- .../authentication/www/DigestAuthUtils.java | 34 +- .../www/DigestAuthenticationEntryPoint.java | 11 +- .../www/DigestAuthenticationFilter.java | 31 +- 21 files changed, 832 insertions(+), 127 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/core/codec/Base64.java create mode 100644 core/src/main/java/org/springframework/security/core/codec/Hex.java diff --git a/config/pom.xml b/config/pom.xml index c87adfc1d4..185887ea3e 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -59,6 +59,7 @@ org.springframework spring-web + true org.apache.directory.server diff --git a/core/pom.xml b/core/pom.xml index 6ae11d1bd6..f33e4b6981 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -69,10 +69,6 @@ commons-logging commons-logging - - commons-codec - commons-codec - commons-collections commons-collections diff --git a/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java b/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java index 7df8005392..695931acb1 100644 --- a/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java @@ -16,13 +16,12 @@ package org.springframework.security.authentication.encoding; -import org.apache.commons.codec.binary.Base64; - -import org.springframework.util.Assert; - import java.io.UnsupportedEncodingException; import java.security.MessageDigest; +import org.springframework.security.core.codec.Base64; +import org.springframework.util.Assert; + /** * A version of {@link ShaPasswordEncoder} which supports Ldap SHA and SSHA (salted-SHA) encodings. The values are @@ -103,13 +102,13 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { prefix = forceLowerCasePrefix ? SSHA_PREFIX_LC : SSHA_PREFIX; } - return prefix + new String(Base64.encodeBase64(hash)); + return prefix + new String(Base64.encode(hash)); } private byte[] extractSalt(String encPass) { String encPassNoLabel = encPass.substring(6); - byte[] hashAndSalt = Base64.decodeBase64(encPassNoLabel.getBytes()); + byte[] hashAndSalt = Base64.decode(encPassNoLabel.getBytes()); int saltLength = hashAndSalt.length - SHA_LENGTH; byte[] salt = new byte[saltLength]; System.arraycopy(hashAndSalt, SHA_LENGTH, salt, 0, saltLength); diff --git a/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java b/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java index 1fbd84b58c..b9d99260c7 100644 --- a/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java @@ -16,8 +16,8 @@ package org.springframework.security.authentication.encoding; import java.io.UnsupportedEncodingException; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.binary.Hex; +import org.springframework.security.core.codec.Base64; +import org.springframework.security.core.codec.Hex; /** * MD4 implementation of PasswordEncoder. @@ -45,7 +45,7 @@ public class Md4PasswordEncoder extends BaseDigestPasswordEncoder { */ public String encodePassword(String rawPass, Object salt) { String saltedPass = mergePasswordAndSalt(rawPass, salt, false); - + byte[] passBytes; try { @@ -53,16 +53,16 @@ public class Md4PasswordEncoder extends BaseDigestPasswordEncoder { } catch (UnsupportedEncodingException e) { throw new IllegalStateException("UTF-8 not supported!"); } - + Md4 md4 = new Md4(); md4.update(passBytes, 0, passBytes.length); - + byte[] resBuf = md4.digest(); if (getEncodeHashAsBase64()) { - return new String(Base64.encodeBase64(resBuf)); + return new String(Base64.encode(resBuf)); } else { - return new String(Hex.encodeHex(resBuf)); + return new String(Hex.encode(resBuf)); } } @@ -84,4 +84,4 @@ public class Md4PasswordEncoder extends BaseDigestPasswordEncoder { public String getAlgorithm() { return "MD4"; } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java b/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java index 5e10708622..827e9ec24d 100644 --- a/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java @@ -1,12 +1,12 @@ package org.springframework.security.authentication.encoding; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.binary.Hex; - import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import org.springframework.security.core.codec.Base64; +import org.springframework.security.core.codec.Hex; + /** * Base for digest password encoders. *

This class can be used stand-alone, or one of the subclasses can be used for compatiblity and convenience. @@ -74,7 +74,7 @@ public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder { MessageDigest messageDigest = getMessageDigest(); byte[] digest; - + try { digest = messageDigest.digest(saltedPass.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { @@ -82,9 +82,9 @@ public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder { } if (getEncodeHashAsBase64()) { - return new String(Base64.encodeBase64(digest)); + return new String(Base64.encode(digest)); } else { - return new String(Hex.encodeHex(digest)); + return new String(Hex.encode(digest)); } } diff --git a/core/src/main/java/org/springframework/security/core/codec/Base64.java b/core/src/main/java/org/springframework/security/core/codec/Base64.java new file mode 100644 index 0000000000..839ceb3656 --- /dev/null +++ b/core/src/main/java/org/springframework/security/core/codec/Base64.java @@ -0,0 +1,649 @@ +package org.springframework.security.core.codec; + + +/** + * Base64 encoder which is a reduced version of Robert Harder's public domain implementation. + * See http://iharder.net/base64 for more information. + *

+ * For internal use only. + * + * @author Luke Taylor + * @version $Id$ + * @since 3.0 + */ +public final class Base64 { + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding in first bit. Value is one. */ + public final static int ENCODE = 1; + + + /** Specify decoding in first bit. Value is zero. */ + public final static int DECODE = 0; + + /** Do break lines when encoding. Value is 8. */ + public final static int DO_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * It is important to note that data encoded this way is not officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + public final static int ORDERED = 32; + + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte)'='; + + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte)'\n'; + + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding + + +/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ + private final static byte[] _STANDARD_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] _STANDARD_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9,-9,-9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + +/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private final static byte[] _URL_SAFE_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' + }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private final static byte[] _URL_SAFE_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + + +/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but someone requested it, + * and it is described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + private final static byte[] _ORDERED_ALPHABET = { + (byte)'-', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', + (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'_', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' + }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private final static byte[] _ORDERED_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' + 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' + 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + + public static byte[] decode(byte[] bytes) { + return decode(bytes, 0, bytes.length, NO_OPTIONS); + } + + public static byte[] encode(byte[] bytes) { + return encodeBytesToBytes(bytes, 0, bytes.length, NO_OPTIONS); + } + + public static boolean isBase64(byte[] bytes) { + try { + decode(bytes); + } catch (InvalidBase64CharacterException e) { + return false; + } + return true; + } + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URLSAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getAlphabet( int options ) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_ALPHABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_ALPHABET; + } else { + return _STANDARD_ALPHABET; + } + } + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URL_SAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet( int options ) { + if( (options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_DECODABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_DECODABET; + } else { + return _STANDARD_DECODABET; + } + } + + +/* ******** E N C O D I N G M E T H O D S ******** */ + + /** + *

Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes.

+ *

This is the lowest level of the encoding methods with + * all possible parameters.

+ * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset, int options ) { + + byte[] ALPHABET = getAlphabet( options ); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) + | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) + | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); + + switch( numSigBytes ) + { + case 3: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; + return destination; + + case 2: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + case 1: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = EQUALS_SIGN; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + default: + return destination; + } + } + + + /** + * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns + * a byte array instead of instantiating a String. This is more efficient + * if you're working with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 2.3.1 + */ + private static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) { + + if( source == null ){ + throw new NullPointerException( "Cannot serialize a null array." ); + } // end if: null + + if( off < 0 ){ + throw new IllegalArgumentException( "Cannot have negative offset: " + off ); + } // end if: off < 0 + + if( len < 0 ){ + throw new IllegalArgumentException( "Cannot have length offset: " + len ); + } // end if: len < 0 + + if( off + len > source.length ){ + throw new IllegalArgumentException( + String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); + } // end if: off < 0 + + boolean breakLines = (options & DO_BREAK_LINES) > 0; + + //int len43 = len * 4 / 3; + //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + // Try to determine more precisely how big the array needs to be. + // If we get it right, we don't have to do an array copy, and + // we save a bunch of memory. + int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding + if( breakLines ){ + encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters + } + byte[] outBuff = new byte[ encLen ]; + + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for( ; d < len2; d+=3, e+=4 ) { + encode3to4( source, d+off, 3, outBuff, e, options ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) + { + outBuff[e+4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if( d < len ) { + encode3to4( source, d+off, len - d, outBuff, e, options ); + e += 4; + } // end if: some padding needed + + + // Only resize array if we didn't guess it right. + if( e < outBuff.length - 1 ){ + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff,0, finalOut,0,e); + //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); + return finalOut; + } else { + //System.err.println("No need to resize array."); + return outBuff; + } + } + + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + *

This is the lowest level of the decoding methods with + * all possible parameters.

+ * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param options alphabet type is pulled from this (standard, url-safe, ordered) + * @return the number of decoded bytes converted + * @throws NullPointerException if source or destination arrays are null + * @throws IllegalArgumentException if srcOffset or destOffset are invalid + * or there is not enough room in the array. + * @since 1.3 + */ + private static int decode4to3( + byte[] source, int srcOffset, + byte[] destination, int destOffset, int options ) { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Source array was null." ); + } // end if + if( destination == null ){ + throw new NullPointerException( "Destination array was null." ); + } // end if + if( srcOffset < 0 || srcOffset + 3 >= source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); + } // end if + if( destOffset < 0 || destOffset +2 >= destination.length ){ + throw new IllegalArgumentException( String.format( + "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); + } // end if + + + byte[] DECODABET = getDecodabet( options ); + + // Example: Dk== + if( source[ srcOffset + 2] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + return 1; + } + + // Example: DkL= + else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); + return 2; + } + + // Example: DkLE + else { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) + | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); + + + destination[ destOffset ] = (byte)( outBuff >> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); + destination[ destOffset + 2 ] = (byte)( outBuff ); + + return 3; + } + } + + /** + * Low-level access to decoding ASCII characters in + * the form of a byte array. Ignores GUNZIP option, if + * it's set. This is not generally a recommended method, + * although it is used internally as part of the decoding process. + * Special case: if len = 0, an empty array is returned. Still, + * if you need more speed and reduced memory footprint (and aren't + * gzipping), consider this method. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @param options Can specify options such as alphabet type to use + * @return decoded data + * @throws IllegalArgumentException If bogus characters exist in source data + */ + private static byte[] decode( byte[] source, int off, int len, int options ) { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Cannot decode null source array." ); + } // end if + if( off < 0 || off + len > source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); + } // end if + + if( len == 0 ){ + return new byte[0]; + }else if( len < 4 ){ + throw new IllegalArgumentException( + "Base64-encoded string must have at least four characters, but length specified was " + len ); + } // end if + + byte[] DECODABET = getDecodabet( options ); + + int len34 = len * 3 / 4; // Estimate on array size + byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output + int outBuffPosn = 0; // Keep track of where we're writing + + byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space + int b4Posn = 0; // Keep track of four byte input buffer + int i = 0; // Source array counter + byte sbiCrop = 0; // Low seven bits (ASCII) of input + byte sbiDecode = 0; // Special value from DECODABET + + for( i = off; i < off+len; i++ ) { // Loop through source + + sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits + sbiDecode = DECODABET[ sbiCrop ]; // Special value + + // White space, Equals sign, or legit Base64 character + // Note the values such as -5 and -9 in the + // DECODABETs at the top of the file. + if( sbiDecode >= WHITE_SPACE_ENC ) { + if( sbiDecode >= EQUALS_SIGN_ENC ) { + b4[ b4Posn++ ] = sbiCrop; // Save non-whitespace + if( b4Posn > 3 ) { // Time to decode? + outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if( sbiCrop == EQUALS_SIGN ) { + break; + } + } + } + } + else { + // There's a bad input character in the Base64 stream. + throw new InvalidBase64CharacterException( String.format( + "Bad Base64 input character '%c' in array position %d", source[i], i ) ); + } + } + + byte[] out = new byte[ outBuffPosn ]; + System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + return out; + } +} + + +class InvalidBase64CharacterException extends IllegalArgumentException { + + InvalidBase64CharacterException(String message) { + super(message); + } +} diff --git a/core/src/main/java/org/springframework/security/core/codec/Hex.java b/core/src/main/java/org/springframework/security/core/codec/Hex.java new file mode 100644 index 0000000000..4dd7548263 --- /dev/null +++ b/core/src/main/java/org/springframework/security/core/codec/Hex.java @@ -0,0 +1,37 @@ +package org.springframework.security.core.codec; + +/** + * Hex data encoder. Converts byte arrays (such as those obtained from message digests) + * into hexadecimal string representation. + *

+ * For internal use only. + * + * @author Luke Taylor + * @version $Id$ + * @since 3.0 + */ +public final class Hex { + + private static final char[] HEX = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + public static char[] encode(byte[] bytes) { + final int nBytes = bytes.length; + char[] result = new char[2*nBytes]; + + int j = 0; + for (int i=0; i < nBytes; i++) { + // Char for top 4 bits + result[j++] = HEX[(0xF0 & bytes[i]) >>> 4 ]; + // Bottom 4 + result[j++] = HEX[(0x0F & bytes[i])]; + } + + return result; + } + +// public static byte[] decode(char[] hex) { +// +// } +} diff --git a/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java b/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java index fd85ac86de..f07ba572e7 100644 --- a/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java +++ b/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java @@ -4,31 +4,31 @@ import java.io.UnsupportedEncodingException; import java.security.SecureRandom; import java.util.Date; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.binary.Hex; import org.springframework.beans.factory.InitializingBean; +import org.springframework.security.core.codec.Base64; +import org.springframework.security.core.codec.Hex; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Basic implementation of {@link TokenService} that is compatible with clusters and across machine restarts, * without requiring database persistence. - * + * *

* Keys are produced in the format: *

- * + * *

* Base64(creationTime + ":" + hex(pseudoRandomNumber) + ":" + extendedInformation + ":" + * Sha512Hex(creationTime + ":" + hex(pseudoRandomNumber) + ":" + extendedInformation + ":" + serverSecret) ) *

- * + * *

* In the above, creationTime, tokenKey and extendedInformation * are equal to that stored in {@link Token}. The Sha512Hex includes the same payload, * plus a serverSecret. *

- * + * *

* The serverSecret varies every millisecond. It relies on two static server-side secrets. The first * is a password, and the second is a server integer. Both of these must remain the same for any issued keys @@ -39,7 +39,7 @@ import org.springframework.util.StringUtils; * to the computed hash). Recall that framework features depending on token services should reject tokens * that are relatively old in any event. *

- * + * *

* A further consideration of this class is the requirement for cryptographically strong pseudo-random numbers. * To this end, the use of {@link SecureRandomFactoryBean} is recommended to inject the property. @@ -48,7 +48,7 @@ import org.springframework.util.StringUtils; *

* This implementation uses UTF-8 encoding internally for string manipulation. *

- * + * * @author Ben Alex * */ @@ -57,7 +57,7 @@ public class KeyBasedPersistenceTokenService implements TokenService, Initializi private String serverSecret; private Integer serverInteger; private SecureRandom secureRandom; - + public Token allocateToken(String extendedInformation) { Assert.notNull(extendedInformation, "Must provided non-null extendedInformation (but it can be empty)"); long creationTime = new Date().getTime(); @@ -68,8 +68,8 @@ public class KeyBasedPersistenceTokenService implements TokenService, Initializi // Compute key String sha512Hex = Sha512DigestUtils.shaHex(content + ":" + serverSecret); String keyPayload = content + ":" + sha512Hex; - String key = convertToString(Base64.encodeBase64(convertToBytes(keyPayload))); - + String key = convertToString(Base64.encode(convertToBytes(keyPayload))); + return new DefaultToken(key, creationTime, extendedInformation); } @@ -77,19 +77,19 @@ public class KeyBasedPersistenceTokenService implements TokenService, Initializi if (key == null || "".equals(key)) { return null; } - String[] tokens = StringUtils.delimitedListToStringArray(convertToString(Base64.decodeBase64(convertToBytes(key))), ":"); + String[] tokens = StringUtils.delimitedListToStringArray(convertToString(Base64.decode(convertToBytes(key))), ":"); Assert.isTrue(tokens.length >= 4, "Expected 4 or more tokens but found " + tokens.length); - + long creationTime; try { creationTime = Long.decode(tokens[0]).longValue(); } catch (NumberFormatException nfe) { throw new IllegalArgumentException("Expected number but found " + tokens[0]); } - + String serverSecret = computeServerSecretApplicableAt(creationTime); String pseudoRandomNumber = tokens[1]; - + // Permit extendedInfo to itself contain ":" characters StringBuffer extendedInfo = new StringBuffer(); for (int i = 2; i < tokens.length-1; i++) { @@ -98,17 +98,17 @@ public class KeyBasedPersistenceTokenService implements TokenService, Initializi } extendedInfo.append(tokens[i]); } - + String sha1Hex = tokens[tokens.length-1]; - + // Verification String content = new Long(creationTime).toString() + ":" + pseudoRandomNumber + ":" + extendedInfo.toString(); String expectedSha512Hex = Sha512DigestUtils.shaHex(content + ":" + serverSecret); Assert.isTrue(expectedSha512Hex.equals(sha1Hex), "Key verification failure"); - + return new DefaultToken(key, creationTime, extendedInfo.toString()); } - + private byte[] convertToBytes(String input) { try { return input.getBytes("UTF-8"); @@ -116,7 +116,7 @@ public class KeyBasedPersistenceTokenService implements TokenService, Initializi throw new RuntimeException(e); } } - + private String convertToString(byte[] bytes) { try { return new String(bytes, "UTF-8"); @@ -124,16 +124,16 @@ public class KeyBasedPersistenceTokenService implements TokenService, Initializi throw new RuntimeException(e); } } - + /** * @return a pseduo random number (hex encoded) */ private String generatePseudoRandomNumber() { byte[] randomizedBits = new byte[pseudoRandomNumberBits]; secureRandom.nextBytes(randomizedBits); - return new String(Hex.encodeHex(randomizedBits)); + return new String(Hex.encode(randomizedBits)); } - + private String computeServerSecretApplicableAt(long time) { return serverSecret + ":" + new Long(time % serverInteger.intValue()).intValue(); } @@ -144,11 +144,11 @@ public class KeyBasedPersistenceTokenService implements TokenService, Initializi public void setServerSecret(String serverSecret) { this.serverSecret = serverSecret; } - + public void setSecureRandom(SecureRandom secureRandom) { this.secureRandom = secureRandom; } - + /** * @param pseudoRandomNumberBits changes the number of bits issued (must be >= 0; defaults to 256) */ diff --git a/core/src/main/java/org/springframework/security/core/token/Sha512DigestUtils.java b/core/src/main/java/org/springframework/security/core/token/Sha512DigestUtils.java index deae57745c..dfd8447041 100644 --- a/core/src/main/java/org/springframework/security/core/token/Sha512DigestUtils.java +++ b/core/src/main/java/org/springframework/security/core/token/Sha512DigestUtils.java @@ -3,15 +3,15 @@ package org.springframework.security.core.token; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import org.apache.commons.codec.binary.Hex; +import org.springframework.security.core.codec.Hex; /** * Provides SHA512 digest methods. - * + * *

* Based on Commons Codec, which does not presently provide SHA512 support. *

- * + * * @author Ben Alex * @since 2.0.1 * @@ -43,7 +43,7 @@ public abstract class Sha512DigestUtils { } /** - * Calculates the SHA digest and returns the value as a + * Calculates the SHA digest and returns the value as a * byte[]. * * @param data Data to digest @@ -54,7 +54,7 @@ public abstract class Sha512DigestUtils { } /** - * Calculates the SHA digest and returns the value as a + * Calculates the SHA digest and returns the value as a * byte[]. * * @param data Data to digest @@ -71,7 +71,7 @@ public abstract class Sha512DigestUtils { * @return SHA digest as a hex string */ public static String shaHex(byte[] data) { - return new String(Hex.encodeHex(sha(data))); + return new String(Hex.encode(sha(data))); } /** @@ -81,7 +81,7 @@ public abstract class Sha512DigestUtils { * @return SHA digest as a hex string */ public static String shaHex(String data) { - return new String(Hex.encodeHex(sha(data))); + return new String(Hex.encode(sha(data))); } } diff --git a/core/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java b/core/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java index 8ea173c97c..5945a80bf8 100644 --- a/core/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java +++ b/core/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java @@ -15,18 +15,15 @@ package org.springframework.security.remoting.httpinvoker; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; +import java.io.IOException; +import java.net.HttpURLConnection; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor; - -import java.io.IOException; - -import java.net.HttpURLConnection; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.codec.Base64; +import org.springframework.security.core.context.SecurityContextHolder; /** @@ -71,7 +68,7 @@ public class AuthenticationSimpleHttpInvokerRequestExecutor extends SimpleHttpIn if ((auth != null) && (auth.getName() != null) && (auth.getCredentials() != null)) { String base64 = auth.getName() + ":" + auth.getCredentials().toString(); - con.setRequestProperty("Authorization", "Basic " + new String(Base64.encodeBase64(base64.getBytes()))); + con.setRequestProperty("Authorization", "Basic " + new String(Base64.encode(base64.getBytes()))); if (logger.isDebugEnabled()) { logger.debug("HttpInvocation now presenting via BASIC authentication SecurityContextHolder-derived: " diff --git a/core/src/main/java/org/springframework/security/util/EncryptionUtils.java b/core/src/main/java/org/springframework/security/util/EncryptionUtils.java index c6602f143d..7b5945f62f 100644 --- a/core/src/main/java/org/springframework/security/util/EncryptionUtils.java +++ b/core/src/main/java/org/springframework/security/util/EncryptionUtils.java @@ -23,8 +23,8 @@ import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESedeKeySpec; -import org.apache.commons.codec.binary.Base64; import org.springframework.core.NestedRuntimeException; +import org.springframework.security.core.codec.Base64; import org.springframework.util.Assert; /** @@ -102,7 +102,7 @@ public final class EncryptionUtils { public static String encrypt(String key, String inputString) throws EncryptionException { isValidKey(key); final byte[] cipherText = cipher(key, stringToByteArray(inputString), Cipher.ENCRYPT_MODE); - return byteArrayToString(Base64.encodeBase64(cipherText)); + return byteArrayToString(Base64.encode(cipherText)); } /** @@ -115,7 +115,7 @@ public final class EncryptionUtils { */ public static byte[] encrypt(String key, byte[] inputBytes) throws EncryptionException { isValidKey(key); - return Base64.encodeBase64(cipher(key, inputBytes, Cipher.ENCRYPT_MODE)); + return Base64.encode(cipher(key, inputBytes, Cipher.ENCRYPT_MODE)); } /** @@ -128,7 +128,7 @@ public final class EncryptionUtils { */ public static String decrypt(String key, String inputString) throws EncryptionException { Assert.hasText(key, "A key is required to attempt decryption"); - final byte[] cipherText = cipher(key, Base64.decodeBase64(stringToByteArray(inputString)), Cipher.DECRYPT_MODE); + final byte[] cipherText = cipher(key, Base64.decode(stringToByteArray(inputString)), Cipher.DECRYPT_MODE); return byteArrayToString(cipherText); } @@ -142,7 +142,7 @@ public final class EncryptionUtils { */ public static byte[] decrypt(String key, byte[] inputBytes) throws EncryptionException { Assert.hasText(key, "A key is required to attempt decryption"); - return cipher(key, Base64.decodeBase64(inputBytes), Cipher.DECRYPT_MODE); + return cipher(key, Base64.decode(inputBytes), Cipher.DECRYPT_MODE); } private static void isValidKey(String key) { diff --git a/pom.xml b/pom.xml index 90d851ecfe..290a7e9c19 100644 --- a/pom.xml +++ b/pom.xml @@ -702,11 +702,6 @@ 1.1.1 true
- - commons-codec - commons-codec - 1.3 - javax.servlet servlet-api diff --git a/web/pom.xml b/web/pom.xml index 48c45189b1..b9f03a0d08 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -30,6 +30,12 @@ spring-test test + + commons-codec + commons-codec + 1.3 + test + jaxen jaxen diff --git a/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java b/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java index d6e872a99c..2d8f2f05b2 100644 --- a/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java +++ b/web/src/main/java/org/springframework/security/web/access/expression/WebSecurityExpressionRoot.java @@ -4,6 +4,8 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; +import javax.servlet.http.HttpServletRequest; + import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; @@ -16,11 +18,14 @@ import org.springframework.util.StringUtils; * @since 3.0 */ public class WebSecurityExpressionRoot extends SecurityExpressionRoot { - private FilterInvocation filterInvocation; + //private FilterInvocation filterInvocation; + /** Allows direct access to the request object */ + public final HttpServletRequest request; public WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) { super(a); - this.filterInvocation = fi; + //this.filterInvocation = fi; + this.request = fi.getRequest(); } /** @@ -39,7 +44,7 @@ public class WebSecurityExpressionRoot extends SecurityExpressionRoot { } InetAddress requiredAddress = parseAddress(ipAddress); - InetAddress remoteAddress = parseAddress(filterInvocation.getHttpRequest().getRemoteAddr()); + InetAddress remoteAddress = parseAddress(request.getRemoteAddr()); if (!requiredAddress.getClass().equals(remoteAddress.getClass())) { throw new IllegalArgumentException("IP Address in expression must be the same type as " + diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java index ba1660b5c5..839237118d 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java @@ -1,6 +1,9 @@ package org.springframework.security.web.authentication.rememberme; -import org.apache.commons.codec.binary.Base64; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; @@ -11,6 +14,7 @@ import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.codec.Base64; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsChecker; import org.springframework.security.core.userdetails.UserDetailsService; @@ -21,10 +25,6 @@ import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - /** * Base class for RememberMeServices implementations. * @@ -160,11 +160,11 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, cookieValue = cookieValue + "="; } - if (!Base64.isArrayByteBase64(cookieValue.getBytes())) { + if (!Base64.isBase64(cookieValue.getBytes())) { throw new InvalidCookieException( "Cookie token was not Base64 encoded; value was '" + cookieValue + "'"); } - String cookieAsPlainText = new String(Base64.decodeBase64(cookieValue.getBytes())); + String cookieAsPlainText = new String(Base64.decode(cookieValue.getBytes())); return StringUtils.delimitedListToStringArray(cookieAsPlainText, DELIMITER); } @@ -187,7 +187,7 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, String value = sb.toString(); - sb = new StringBuffer(new String(Base64.encodeBase64(value.getBytes()))); + sb = new StringBuffer(new String(Base64.encode(value.getBytes()))); while (sb.charAt(sb.length() - 1) == '=') { sb.deleteCharAt(sb.length() - 1); diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java index 055bbb784e..03d6fb54f1 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java @@ -7,9 +7,9 @@ import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.codec.binary.Base64; import org.springframework.dao.DataAccessException; import org.springframework.security.core.Authentication; +import org.springframework.security.core.codec.Base64; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.util.Assert; @@ -151,13 +151,13 @@ public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeSe protected String generateSeriesData() { byte[] newSeries = new byte[seriesLength]; random.nextBytes(newSeries); - return new String(Base64.encodeBase64(newSeries)); + return new String(Base64.encode(newSeries)); } protected String generateTokenData() { byte[] newToken = new byte[tokenLength]; random.nextBytes(newToken); - return new String(Base64.encodeBase64(newToken)); + return new String(Base64.encode(newToken)); } private void addCookie(PersistentRememberMeToken token, HttpServletRequest request, HttpServletResponse response) { diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java index f1f18e4e39..fabc8687c3 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java @@ -16,13 +16,15 @@ package org.springframework.security.web.authentication.rememberme; import org.springframework.security.core.Authentication; +import org.springframework.security.core.codec.Hex; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.util.StringUtils; -import org.apache.commons.codec.digest.DigestUtils; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Date; @@ -129,7 +131,15 @@ public class TokenBasedRememberMeServices extends AbstractRememberMeServices { * MD5 ("username:tokenExpiryTime:password:key") */ protected String makeTokenSignature(long tokenExpiryTime, String username, String password) { - return DigestUtils.md5Hex(username + ":" + tokenExpiryTime + ":" + password + ":" + getKey()); + String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey(); + MessageDigest digest; + try { + digest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("No MD5 algorithm available!"); + } + + return new String(Hex.encode(digest.digest(data.getBytes()))); } protected boolean isTokenExpired(long tokenExpiryTime) { diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java index 311b510bcf..74fadc5494 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java @@ -24,13 +24,13 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.codec.binary.Base64; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.codec.Base64; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.NullRememberMeServices; @@ -117,7 +117,7 @@ public class BasicAuthenticationFilter extends GenericFilterBean { if ((header != null) && header.startsWith("Basic ")) { byte[] base64Token = header.substring(6).getBytes("UTF-8"); - String token = new String(Base64.decodeBase64(base64Token), getCredentialsCharset(request)); + String token = new String(Base64.decode(base64Token), getCredentialsCharset(request)); String username = ""; String password = ""; diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java index 2d98f61cb7..9bb5edf2cd 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java @@ -1,27 +1,28 @@ package org.springframework.security.web.authentication.www; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.security.core.codec.Hex; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -abstract class DigestAuthUtils { +final class DigestAuthUtils { private static final String[] EMPTY_STRING_ARRAY = new String[0]; - public final static String encodePasswordInA1Format(String username, String realm, String password) { + static String encodePasswordInA1Format(String username, String realm, String password) { String a1 = username + ":" + realm + ":" + password; - String a1Md5 = new String(DigestUtils.md5Hex(a1)); + String a1Md5 = md5Hex(a1); return a1Md5; } - - final static String[] splitIgnoringQuotes(String str, char separatorChar) { + static String[] splitIgnoringQuotes(String str, char separatorChar) { if (str == null) { return null; } @@ -87,12 +88,12 @@ abstract class DigestAuthUtils { * @return the MD5 of the digest authentication response, encoded in hex * @throws IllegalArgumentException if the supplied qop value is unsupported. */ - final static String generateDigest(boolean passwordAlreadyEncoded, String username, String realm, String password, + static String generateDigest(boolean passwordAlreadyEncoded, String username, String realm, String password, String httpMethod, String uri, String qop, String nonce, String nc, String cnonce) throws IllegalArgumentException { String a1Md5 = null; String a2 = httpMethod + ":" + uri; - String a2Md5 = new String(DigestUtils.md5Hex(a2)); + String a2Md5 = md5Hex(a2); if (passwordAlreadyEncoded) { a1Md5 = password; @@ -112,7 +113,7 @@ abstract class DigestAuthUtils { throw new IllegalArgumentException("This method does not support a qop: '" + qop + "'"); } - String digestMd5 = new String(DigestUtils.md5Hex(digest)); + String digestMd5 = new String(md5Hex(digest)); return digestMd5; } @@ -130,7 +131,7 @@ abstract class DigestAuthUtils { * @return a Map representing the array contents, or null if the array to process was * null or empty */ - final static Map splitEachArrayElementAndCreateMap(String[] array, String delimiter, String removeCharacters) { + static Map splitEachArrayElementAndCreateMap(String[] array, String delimiter, String removeCharacters) { if ((array == null) || (array.length == 0)) { return null; } @@ -169,7 +170,7 @@ abstract class DigestAuthUtils { * (neither element includes the delimiter) * @throws IllegalArgumentException if an argument was invalid */ - final static String[] split(String toSplit, String delimiter) { + static String[] split(String toSplit, String delimiter) { Assert.hasLength(toSplit, "Cannot split a null or empty string"); Assert.hasLength(delimiter, "Cannot use a null or empty delimiter to split a string"); @@ -188,4 +189,15 @@ abstract class DigestAuthUtils { return new String[]{beforeDelimiter, afterDelimiter}; } + + static String md5Hex(String data) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("No MD5 algorithm available!"); + } + + return new String(Hex.encode(digest.digest(data.getBytes()))); + } } diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java index 935e6c0162..992708d453 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java @@ -21,14 +21,13 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.Ordered; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.codec.Base64; +import org.springframework.security.web.AuthenticationEntryPoint; /** @@ -82,9 +81,9 @@ public class DigestAuthenticationEntryPoint implements AuthenticationEntryPoint, // format of nonce is: // base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key)) long expiryTime = System.currentTimeMillis() + (nonceValiditySeconds * 1000); - String signatureValue = new String(DigestUtils.md5Hex(expiryTime + ":" + key)); + String signatureValue = new String(DigestAuthUtils.md5Hex(expiryTime + ":" + key)); String nonceValue = expiryTime + ":" + signatureValue; - String nonceValueBase64 = new String(Base64.encodeBase64(nonceValue.getBytes())); + String nonceValueBase64 = new String(Base64.encode(nonceValue.getBytes())); // qop is quality of protection, as defined by RFC 2617. // we do not use opaque due to IE violation of RFC 2617 in not diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java index 5fa322772c..8c226c7c93 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java @@ -25,8 +25,6 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.MessageSource; @@ -38,6 +36,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.codec.Base64; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserCache; import org.springframework.security.core.userdetails.UserDetails; @@ -96,7 +95,7 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes - @Override + @Override public void afterPropertiesSet() { Assert.notNull(userDetailsService, "A UserDetailsService is required"); Assert.notNull(authenticationEntryPoint, "A DigestAuthenticationEntryPoint is required"); @@ -168,7 +167,7 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes } // Check nonce was a Base64 encoded (as sent by DigestAuthenticationEntryPoint) - if (!Base64.isArrayByteBase64(nonce.getBytes())) { + if (!Base64.isBase64(nonce.getBytes())) { fail(request, response, new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.nonceEncoding", new Object[]{nonce}, "Nonce is not encoded in Base64; received nonce {0}"))); @@ -179,7 +178,7 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes // Decode nonce from Base64 // format of nonce is: // base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key)) - String nonceAsPlainText = new String(Base64.decodeBase64(nonce.getBytes())); + String nonceAsPlainText = new String(Base64.decode(nonce.getBytes())); String[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, ":"); if (nonceTokens.length != 2) { @@ -205,7 +204,7 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes } // Check signature of nonce matches this expiry time - String expectedNonceSignature = DigestUtils.md5Hex(nonceExpiryTime + ":" + String expectedNonceSignature = DigestAuthUtils.md5Hex(nonceExpiryTime + ":" + this.getAuthenticationEntryPoint().getKey()); if (!expectedNonceSignature.equals(nonceTokens[1])) { @@ -305,11 +304,11 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes UsernamePasswordAuthenticationToken authRequest; if (createAuthenticatedToken) { - authRequest = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); + authRequest = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); } else { - authRequest = new UsernamePasswordAuthenticationToken(user, user.getPassword()); + authRequest = new UsernamePasswordAuthenticationToken(user, user.getPassword()); } authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request)); @@ -367,23 +366,23 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } - - - /** + + + /** * If you set this property, the Authentication object, which is * created after the successful digest authentication will be marked - * as authenticated and filled with the authorities loaded by + * as authenticated and filled with the authorities loaded by * the UserDetailsService. It therefore will not be re-authenticated * by your AuthenticationProvider. This means, that only the password * of the user is checked, but not the flags like isEnabled() or - * isAccountNonExpired(). You will save some time by enabling this flag, + * isAccountNonExpired(). You will save some time by enabling this flag, * as otherwise your UserDetailsService will be called twice. A more secure * option would be to introduce a cache around your UserDetailsService, but * if you don't use these flags, you can also safely enable this option. - * + * * @param createAuthenticatedToken default is false */ public void setCreateAuthenticatedToken(boolean createAuthenticatedToken) { - this.createAuthenticatedToken = createAuthenticatedToken; - } + this.createAuthenticatedToken = createAuthenticatedToken; + } }