blob: eeaeeed4bcf7baa9493527668b87faa2ac58db19 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "<WebSig>" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 2001, Institute for
* Data Communications Systems, <http://www.nue.et-inf.uni-siegen.de/>.
* The development of this software was partly funded by the European
* Commission in the <WebSig> project in the ISIS Programme.
* For more information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.xml.security.utils;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
import java.util.StringTokenizer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.xml.security.exceptions.Base64DecodingException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
/**
* Implementation of MIME's Base64 encoding and decoding conversions.
* Optimized code. (raw version taken from oreilly.jonathan.util)
*
* @author Anli Shundi
* @author Christian Geuer-Pollmann
* @see <A HREF="ftp://ftp.isi.edu/in-notes/rfc2045.txt">RFC 2045</A>
* @see org.apache.xml.security.transforms.implementations.TransformBase64Decode
*/
public class Base64 {
/** {@link org.apache.commons.logging} logging facility */
static org.apache.commons.logging.Log log =
org.apache.commons.logging.LogFactory.getLog(Base64.class.getName());
/** Field LINE_SEPARATOR */
public static final String LINE_SEPARATOR = "\n";
/** Field BASE64DEFAULTLENGTH */
public static final int BASE64DEFAULTLENGTH = 76;
/** Field _base64length */
static int _base64length = Base64.BASE64DEFAULTLENGTH;
private Base64() {
// we don't allow instantiation
}
/**
* Method setBase64WrapLength
*
* @param length
*/
public static void setBase64WrapLength(int length) {
Base64._base64length = length;
}
/**
* Method getBase64WrapLength
*
*
*/
public static int getBase64WrapLength() {
return Base64._base64length;
}
/**
* Returns a byte-array representation of a <code>{@link BigInteger}<code>.
* No sign-bit is outputed.
*
* <b>N.B.:</B> <code>{@link BigInteger}<code>'s toByteArray
* retunrs eventually longer arrays because of the leading sign-bit.
*
* @param big <code>BigInteger<code> to be converted
* @param bitlen <code>int<code> the desired length in bits of the representation
* @return a byte array with <code>bitlen</code> bits of <code>big</code>
*/
static byte[] getBytes(BigInteger big, int bitlen) {
//round bitlen
bitlen = ((bitlen + 7) >> 3) << 3;
if (bitlen < big.bitLength()) {
throw new IllegalArgumentException(I18n
.translate("utils.Base64.IllegalBitlength"));
}
byte[] bigBytes = big.toByteArray();
if (((big.bitLength() % 8) != 0)
&& (((big.bitLength() / 8) + 1) == (bitlen / 8))) {
return bigBytes;
} else {
// some copying needed
int startSrc = 0; // no need to skip anything
int bigLen = bigBytes.length; //valid length of the string
if ((big.bitLength() % 8) == 0) { // correct values
startSrc = 1; // skip sign bit
bigLen--; // valid length of the string
}
int startDst = bitlen / 8 - bigLen; //pad with leading nulls
byte[] resizedBytes = new byte[bitlen / 8];
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, bigLen);
return resizedBytes;
}
}
/**
* Encode in Base64 the given <code>{@link BigInteger}<code>.
*
* @param big
* @return String with Base64 encoding
*/
public static String encode(BigInteger big) {
return encode(getBytes(big, big.bitLength()));
}
/**
* Returns a byte-array representation of a <code>{@link BigInteger}<code>.
* No sign-bit is outputed.
*
* <b>N.B.:</B> <code>{@link BigInteger}<code>'s toByteArray
* retunrs eventually longer arrays because of the leading sign-bit.
*
* @param big <code>BigInteger<code> to be converted
* @param bitlen <code>int<code> the desired length in bits of the representation
* @return a byte array with <code>bitlen</code> bits of <code>big</code>
*/
public static byte[] encode(BigInteger big, int bitlen) {
//round bitlen
bitlen = ((bitlen + 7) >> 3) << 3;
if (bitlen < big.bitLength()) {
throw new IllegalArgumentException(I18n
.translate("utils.Base64.IllegalBitlength"));
}
byte[] bigBytes = big.toByteArray();
if (((big.bitLength() % 8) != 0)
&& (((big.bitLength() / 8) + 1) == (bitlen / 8))) {
return bigBytes;
} else {
// some copying needed
int startSrc = 0; // no need to skip anything
int bigLen = bigBytes.length; //valid length of the string
if ((big.bitLength() % 8) == 0) { // correct values
startSrc = 1; // skip sign bit
bigLen--; // valid length of the string
}
int startDst = bitlen / 8 - bigLen; //pad with leading nulls
byte[] resizedBytes = new byte[bitlen / 8];
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, bigLen);
return resizedBytes;
}
}
/**
* Method decodeBigIntegerFromElement
*
* @param element
*
* @throws Base64DecodingException
*/
public static BigInteger decodeBigIntegerFromElement(Element element)
throws Base64DecodingException {
return new BigInteger(1, Base64.decode(element));
}
/**
* Method decodeBigIntegerFromText
*
* @param text
*
* @throws Base64DecodingException
*/
public static BigInteger decodeBigIntegerFromText(Text text)
throws Base64DecodingException {
return new BigInteger(1, Base64.decode(text.getData()));
}
/**
* This method takes an (empty) Element and a BigInteger and adds the
* base64 encoded BigInteger to the Element.
*
* @param element
* @param biginteger
*/
public static void fillElementWithBigInteger(Element element,
BigInteger biginteger) {
String encodedInt = encode(biginteger);
if (encodedInt.length() > 76) {
encodedInt = "\n" + encodedInt + "\n";
}
Document doc = element.getOwnerDocument();
Text text = doc.createTextNode(encodedInt);
element.appendChild(text);
}
/**
* Method decode
*
* Takes the <CODE>Text</CODE> children of the Element and interprets
* them as input for the <CODE>Base64.decode()</CODE> function.
*
* @param element
*
* $todo$ not tested yet
* @throws Base64DecodingException
*/
public static byte[] decode(Element element) throws Base64DecodingException {
NodeList nl = element.getChildNodes();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < nl.getLength(); i++) {
if (nl.item(i).getNodeType() == Node.TEXT_NODE) {
Text t = (Text) nl.item(i);
sb.append(t.getData());
}
}
return decode(sb.toString());
}
/**
* Method encodeToElement
*
* @param doc
* @param localName
* @param bytes
*
*/
public static Element encodeToElement(Document doc, String localName,
byte[] bytes) {
Element el = XMLUtils.createElementInSignatureSpace(doc, localName);
Text text = doc.createTextNode(encode(bytes));
el.appendChild(text);
return el;
}
/**
* Method decode
*
*
* @param base64
*
*
* @throws Base64DecodingException
*/
public static byte[] decode(byte[] base64) throws Base64DecodingException {
try {
return decode(new String(base64, "UTF-8"));
} catch (java.io.UnsupportedEncodingException ex) {
// should never be reached because Encoding is valid and fixed
return new byte[0];
}
}
/**
* Decode a Base64-encoded string to a byte array.
*
* @param base64 <code>String</code> encoded string (single line only !!)
* @return Decoded data in a byte array
* @throws Base64DecodingException
*/
public static byte[] decode(String base64) throws Base64DecodingException {
try {
if (base64.length() < 30) {
log.debug("I was asked to decode \"" + base64 + "\"");
} else {
log.debug("I was asked to decode \"" + base64.substring(0, 20)
+ "...\"");
}
//strip whitespace from anywhere in the string. Not the most memory
//efficient solution but elegant anyway :-)
StringTokenizer tok = new StringTokenizer(base64, " \n\r\t", false);
StringBuffer buf = new StringBuffer(base64.length());
while (tok.hasMoreElements()) {
buf.append(tok.nextToken());
}
base64 = buf.toString();
int pad = 0;
for (int i = base64.length() - 1;
(i > 0) && (base64.charAt(i) == '='); i--) {
pad++;
}
int length = base64.length() / 4 * 3 - pad;
byte[] raw = new byte[length];
for (int i = 0, rawIndex = 0; i < base64.length();
i += 4, rawIndex += 3) {
int block = (getValue(base64.charAt(i)) << 18)
+ (getValue(base64.charAt(i + 1)) << 12)
+ (getValue(base64.charAt(i + 2)) << 6)
+ (getValue(base64.charAt(i + 3)));
for (int j = 2; j >= 0; j--) {
if (rawIndex + j < raw.length) {
raw[rawIndex + j] = (byte) (block & 0xff);
}
block >>= 8;
}
}
return raw;
} catch (IndexOutOfBoundsException ex) {
throw new Base64DecodingException("utils.Base64.IllegalBitlength", ex);
}
}
/**
* Encode a byte array in Base64 format and return an optionally
* wrapped line.
*
* @param raw <code>byte[]</code> data to be encoded
* @param wrap <code>int<code> length of wrapped lines; No wrapping if less than 4.
* @return a <code>String</code> with encoded data
*/
public static String encode(byte[] raw, int wrap) {
//calculate length of encoded string
int encLen = ((raw.length + 2) / 3) * 4;
//adjust for newlines
if (wrap > 3) {
wrap -= wrap % 4;
encLen += 2 * (encLen / wrap);
} else { //disable wrapping
wrap = Integer.MAX_VALUE;
}
StringBuffer encoded = new StringBuffer(encLen);
int len3 = (raw.length / 3) * 3;
int outLen = 0; //length of output line
for (int i = 0; i < len3; i += 3, outLen += 4) {
if (outLen + 4 > wrap) {
encoded.append(LINE_SEPARATOR);
outLen = 0;
}
encoded.append(encodeFullBlock(raw, i));
}
if (outLen >= wrap) { //this will produce an extra newline if needed !? Sun had it this way...
encoded.append(LINE_SEPARATOR);
}
if (len3 < raw.length) {
encoded.append(encodeBlock(raw, len3));
}
return encoded.toString();
}
/**
* Encode a byte array and fold lines at the standard 76th character.
*
* @param raw <code>byte[]<code> to be base64 encoded
* @return the <code>String<code> with encoded data
*/
public static String encode(byte[] raw) {
return encode(raw, Base64.getBase64WrapLength());
}
/**
* Base64 decode the lines from the reader and return an InputStream
* with the bytes.
*
*
* @param reader
* @return InputStream with the decoded bytes
* @exception IOException passes what the reader throws
* @throws Base64DecodingException
* @throws IOException
*/
public static byte[] decode(BufferedReader reader)
throws IOException, Base64DecodingException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String line;
while (null != (line = reader.readLine())) {
byte[] bytes = decode(line);
baos.write(bytes);
}
return baos.toByteArray();
}
/**
* Method encodeBlock
*
* @param raw
* @param offset
*
*/
protected static char[] encodeBlock(byte[] raw, int offset) {
int block = 0;
int slack = raw.length - offset - 1;
int end = (slack >= 2)
? 2
: slack;
for (int i = 0; i < 3; i++) {
byte b = (offset + i < raw.length)
? raw[offset + i]
: 0;
int neuter = (b < 0)
? b + 256
: b;
block <<= 8;
block += neuter;
}
char[] base64 = new char[4];
for (int i = 3; i >= 0; i--) {
int sixBit = block & 0x3f;
base64[i] = getChar(sixBit);
block >>= 6;
}
if (slack < 1) {
base64[2] = '=';
}
if (slack < 2) {
base64[3] = '=';
}
return base64;
}
/**
* Method encodeFullBlock
*
* @param raw
* @param offset
*
*/
protected static char[] encodeFullBlock(byte[] raw, int offset) {
int block = 0;
for (int i = 0; i < 3; i++) {
//byte b = raw[offset + i];
//int neuter = (b < 0) ? b + 256 : b;
block <<= 8;
block += (0xff & raw[offset + i]);
}
block = ((raw[offset] & 0xff) << 16) + ((raw[offset + 1] & 0xff) << 8)
+ (raw[offset + 2] & 0xff);
char[] base64 = new char[4];
for (int i = 3; i >= 0; i--) {
int sixBit = block & 0x3f;
base64[i] = getChar(sixBit);
block >>= 6;
}
return base64;
}
/**
* Method getChar
*
* @param sixBit
*
*/
protected static char getChar(int sixBit) {
if ((sixBit >= 0) && (sixBit < 26)) {
return (char) ('A' + sixBit);
}
if ((sixBit >= 26) && (sixBit < 52)) {
return (char) ('a' + (sixBit - 26));
}
if ((sixBit >= 52) && (sixBit < 62)) {
return (char) ('0' + (sixBit - 52));
}
if (sixBit == 62) {
return '+';
}
if (sixBit == 63) {
return '/';
}
return '?';
}
/**
* Method getValue
*
* @param c
*
*/
protected static int getValue(char c) {
if ((c >= 'A') && (c <= 'Z')) {
return c - 'A';
}
if ((c >= 'a') && (c <= 'z')) {
return c - 'a' + 26;
}
if ((c >= '0') && (c <= '9')) {
return c - '0' + 52;
}
if (c == '+') {
return 62;
}
if (c == '/') {
return 63;
}
if (c == '=') {
return 0;
}
return -1;
}
// boolean bInWSpace = false;//?
// for(int i=0, j=0, len=base64.length(); i < len; i++) {
// if( bInWSpace ) {
// if( Character.isWhitespace(base64.charAt(i)) ) {
// skipLen++;
// } else {
// //copy here & reset
// }
// } else {
// if( Character.isWhitespace(base64.charAt(i)) ) {
// bInWSpace = true;
// skipLen++;
// } else {
// //copy here & reset
// }
// }
// }
/**
* Method main
*
*
* @param args
*
* @throws Exception
*/
public static void main(String[] args) throws Exception {
DocumentBuilderFactory docBuilderFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
String testString1 =
"<container><base64 value=\"Should be 'Hallo'\">SGFsbG8=</base64></container>";
InputSource inputSource = new InputSource(new StringReader(testString1));
Document doc = docBuilder.parse(inputSource);
Element base64Elem =
(Element) doc.getDocumentElement().getChildNodes().item(0);
System.out.println(new String(decode(base64Elem)));
}
}