Issue 1120: support base64 encode/decode for url applications

This commit is contained in:
Adrian Cole 2012-11-04 15:35:26 -08:00
parent 312420a761
commit a551d912c4
5 changed files with 137 additions and 20 deletions

View File

@ -78,7 +78,54 @@ public class CryptoStreams {
public static String base64(byte[] in) {
return Base64.encodeBytes(in, Base64.DONT_BREAK_LINES);
}
/**
* encodes value substituting {@code '-' and '_'} for {@code '+' and '/'},
* and without adding trailing {@code '='} padding.
*
* @see <a
* href="http://en.wikipedia.org/wiki/Base64#URL_applications">url-safe
* encoding</a>
*/
public static String base64URLSafe(byte[] in) {
String provisional = base64(in);
int length = provisional.length();
if (length == 0)
return provisional;
// we know base64 is in 4 character chunks, so out of bounds risk here
else if (provisional.charAt((length - 2)) == '=')
length-=2;
else if (provisional.charAt((length - 1)) == '=')
length-=1;
char[] tmp = new char[length];
for (int i = 0; i < length; i++) {
char c = provisional.charAt(i);
switch (c) {
case '+':
tmp[i] = '-';
break;
case '/':
tmp[i] = '_';
break;
default:
tmp[i] = c;
break;
}
}
return new String(tmp);
}
/**
* decodes base 64 encoded string, regardless of whether padding {@code '='} padding is present.
*
* Note this seamlessly handles the URL-safe case where {@code '-' and '_'} are substituted for {@code '+' and '/'}.
*
* @see <a
* href="http://en.wikipedia.org/wiki/Base64#URL_applications">url-safe
* encoding</a>
*/
public static byte[] base64(String in) {
return Base64.decode(in);
}

View File

@ -45,7 +45,6 @@ import java.security.spec.RSAPublicKeySpec;
import java.util.Map;
import org.bouncycastle.openssl.PEMWriter;
import org.jclouds.encryption.internal.Base64;
import org.jclouds.io.InputSuppliers;
import org.jclouds.util.Strings2;
@ -102,7 +101,7 @@ public class SshKeys {
Iterable<String> parts = Splitter.on(' ').split(Strings2.toStringAndClose(stream));
checkArgument(Iterables.size(parts) >= 2 && "ssh-rsa".equals(Iterables.get(parts, 0)),
"bad format, should be: ssh-rsa AAAAB3...");
stream = new ByteArrayInputStream(Base64.decode(Iterables.get(parts, 1)));
stream = new ByteArrayInputStream(CryptoStreams.base64(Iterables.get(parts, 1)));
String marker = new String(readLengthFirst(stream));
checkArgument("ssh-rsa".equals(marker), "looking for marker ssh-rsa but got %s", marker);
BigInteger publicExponent = new BigInteger(readLengthFirst(stream));

View File

@ -152,6 +152,12 @@ public class Base64
/**
* Translates a Base64 value to either its 6-bit reconstruction value
* or a negative number indicating some other meaning.
*
* <h4>Note</h4>
* {@code +} and {@code -} both decode to 62. {@code /} and {@code _} both decode to 63
*
* This accomodates URL-safe encoding
* @see <a href="http://en.wikipedia.org/wiki/Base64#URL_applications">url-safe base64</a>
**/
private final static byte[] DECODABET =
{
@ -164,7 +170,9 @@ public class Base64
-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
-9, // Decimal 44
62, // Hyphen at decimal 45
-9, // Decimal 45
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
@ -172,7 +180,9 @@ public class Base64
-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
-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
@ -199,7 +209,7 @@ public class Base64
/* ******** E N C O D I N G ApiMetadata E T H O D S ******** */
/* ******** E N C O D I N G M E T H O D S ******** */
/**
@ -727,6 +737,16 @@ public class Base64
*/
public static byte[] decode( String s )
{
int modulo = s.length() % 4;
switch (modulo) {
case 2:
s += "==";
break;
case 3:
s += "=";
break;
}
byte[] bytes;
try
{
@ -737,7 +757,7 @@ public class Base64
bytes = s.getBytes();
} // end catch
//</change>
// Decode
bytes = decode( bytes, 0, bytes.length );

View File

@ -31,7 +31,7 @@ import com.google.common.base.Charsets;
*
* @author Adrian Cole
*/
@Test(groups = "unit", sequential = true)
@Test(groups = "unit", singleThreaded = true)
public class CryptoStreamsTest {
@Test
@ -43,11 +43,65 @@ public class CryptoStreamsTest {
@Test
public void testBase64Decode() throws IOException {
byte[] decoded = CryptoStreams.base64Decode(Payloads.newStringPayload("aGVsbG8gd29ybGQ="));
assertEquals(new String(decoded, Charsets.UTF_8), "hello world");
}
@Test
public void testBase64DecodeURLSafeJson() throws IOException {
byte[] decoded = CryptoStreams.base64("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9");
assertEquals(new String(decoded, Charsets.UTF_8), "{\"alg\":\"RS256\",\"typ\":\"JWT\"}");
}
@Test
public void testBase64DecodeURLSafeNoPadding() throws IOException {
byte[] decoded = CryptoStreams
.base64("eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ");
assertEquals(new String(decoded, Charsets.UTF_8), "{"
+ "\"iss\":\"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com\","
+ "\"scope\":\"https://www.googleapis.com/auth/prediction\","
+ "\"aud\":\"https://accounts.google.com/o/oauth2/token\"," + "\"exp\":1328554385," + "\"iat\":1328550785"
+ "}");
}
@Test
public void testBase64EncodeURLSafeNoSinglePad() {
assertEquals(CryptoStreams.base64("any carnal pleasu".getBytes(Charsets.UTF_8)), "YW55IGNhcm5hbCBwbGVhc3U=");
assertEquals(CryptoStreams.base64URLSafe("any carnal pleasu".getBytes(Charsets.UTF_8)), "YW55IGNhcm5hbCBwbGVhc3U");
}
@Test
public void testBase64EncodeURLSafeNoDoublePad() {
assertEquals(CryptoStreams.base64("any carnal pleas".getBytes(Charsets.UTF_8)), "YW55IGNhcm5hbCBwbGVhcw==");
assertEquals(CryptoStreams.base64URLSafe("any carnal pleas".getBytes(Charsets.UTF_8)), "YW55IGNhcm5hbCBwbGVhcw");
}
@Test
public void testBase64EncodeURLSafeHyphenNotPlus() {
assertEquals(CryptoStreams.base64("i?>".getBytes(Charsets.UTF_8)), "aT8+");
assertEquals(CryptoStreams.base64URLSafe("i?>".getBytes(Charsets.UTF_8)), "aT8-");
}
@Test
public void testBase64EncodeURLSafeUnderscoreNotSlash() {
assertEquals(CryptoStreams.base64("i??".getBytes(Charsets.UTF_8)), "aT8/");
assertEquals(CryptoStreams.base64URLSafe("i??".getBytes(Charsets.UTF_8)), "aT8_");
}
@Test
public void testBase64DecodeWithoutSinglePad() {
assertEquals(new String(CryptoStreams.base64("YW55IGNhcm5hbCBwbGVhc3U="), Charsets.UTF_8), "any carnal pleasu");
assertEquals(new String(CryptoStreams.base64("YW55IGNhcm5hbCBwbGVhc3U"), Charsets.UTF_8), "any carnal pleasu");
}
@Test
public void testBase64DecodeWithoutDoublePad() {
assertEquals(new String(CryptoStreams.base64("YW55IGNhcm5hbCBwbGVhcw=="), Charsets.UTF_8), "any carnal pleas");
assertEquals(new String(CryptoStreams.base64("YW55IGNhcm5hbCBwbGVhcw"), Charsets.UTF_8), "any carnal pleas");
}
@Test
public void testHexEncode() throws IOException {

View File

@ -29,10 +29,11 @@ import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import static javax.xml.bind.DatatypeConverter.*;
import org.jclouds.PerformanceTest;
import org.jclouds.crypto.Crypto;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.encryption.internal.Base64;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@ -46,7 +47,7 @@ import com.google.inject.Injector;
* @author Adrian Cole
*/
// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
@Test(groups = "performance", sequential = true, timeOut = 2 * 60 * 1000, testName = "CryptoTest")
@Test(groups = "performance", singleThreaded = true, timeOut = 2 * 60 * 1000, testName = "CryptoTest")
public class CryptoTest extends PerformanceTest {
protected Crypto crypto;
@ -57,17 +58,13 @@ public class CryptoTest extends PerformanceTest {
crypto = i.getInstance(Crypto.class);
}
public final static Object[][] base64KeyMessageDigest = {
{ Base64.decode("CwsLCwsLCwsLCwsLCwsLCwsLCws="), "Hi There", "thcxhlUFcmTii8C2+zeMjvFGvgA=" },
{ Base64.decode("SmVmZQ=="), "what do ya want for nothing?", "7/zfauXrL6LSdBbV8YTfnCWafHk=" },
{ Base64.decode("DAwMDAwMDAwMDAwMDAwMDAwMDAw="), "Test With Truncation", "TBoDQktV4H/n8nvh1Yu5MkqaWgQ=" },
{
Base64
.decode("qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo="),
public static final Object[][] base64KeyMessageDigest = {
{ parseBase64Binary("CwsLCwsLCwsLCwsLCwsLCwsLCws="), "Hi There", "thcxhlUFcmTii8C2+zeMjvFGvgA=" },
{ parseBase64Binary("SmVmZQ=="), "what do ya want for nothing?", "7/zfauXrL6LSdBbV8YTfnCWafHk=" },
{ parseBase64Binary("DAwMDAwMDAwMDAwMDAwMDAwMDAw="), "Test With Truncation", "TBoDQktV4H/n8nvh1Yu5MkqaWgQ=" },
{ parseBase64Binary("qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo="),
"Test Using Larger Than Block-Size Key - Hash Key First", "qkrl4VJy0A6VcFY3zoo7Ve1AIRI=" },
{
Base64
.decode("qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo="),
{ parseBase64Binary("qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqo="),
"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
"6OmdD0UjfXhta7qnllx4CLv/GpE=" } };