diff --git a/src/main/java/org/archive/util/Base32.java b/src/main/java/org/archive/util/Base32.java deleted file mode 100644 index c5aded40..00000000 --- a/src/main/java/org/archive/util/Base32.java +++ /dev/null @@ -1,165 +0,0 @@ -/* Base32 -* -* $Id: Base32.java,v 1.4 2004/04/15 19:04:01 stack-sf Exp $ -* -* Created on Jan 21, 2004 -* -* Copyright (C) 2004 Internet Archive. -* -* This file is part of the Heritrix web crawler (crawler.archive.org). -* -* Heritrix is free software; you can redistribute it and/or modify -* it under the terms of the GNU Lesser Public License as published by -* the Free Software Foundation; either version 2.1 of the License, or -* any later version. -* -* Heritrix is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Lesser Public License for more details. -* -* You should have received a copy of the GNU Lesser Public License -* along with Heritrix; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ -package org.archive.util; - -/** - * Base32 - encodes and decodes RFC3548 Base32 - * (see http://www.faqs.org/rfcs/rfc3548.html ) - * - * Imported public-domain code of Bitzi. - * - * @author Robert Kaye - * @author Gordon Mohr - */ -public class Base32 { - private static final String base32Chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - private static final int[] base32Lookup = - { 0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7' - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // '8', '9', ':', ';', '<', '=', '>', '?' - 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G' - 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' - 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W' - 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_' - 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g' - 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' - 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' - 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL' - }; - - /** - * Encodes byte array to Base32 String. - * - * @param bytes Bytes to encode. - * @return Encoded byte array bytes as a String. - * - */ - static public String encode(final byte[] bytes) { - int i = 0, index = 0, digit = 0; - int currByte, nextByte; - StringBuffer base32 = new StringBuffer((bytes.length + 7) * 8 / 5); - - while (i < bytes.length) { - currByte = (bytes[i] >= 0) ? bytes[i] : (bytes[i] + 256); // unsign - - /* Is the current digit going to span a byte boundary? */ - if (index > 3) { - if ((i + 1) < bytes.length) { - nextByte = - (bytes[i + 1] >= 0) ? bytes[i + 1] : (bytes[i + 1] + 256); - } else { - nextByte = 0; - } - - digit = currByte & (0xFF >> index); - index = (index + 5) % 8; - digit <<= index; - digit |= nextByte >> (8 - index); - i++; - } else { - digit = (currByte >> (8 - (index + 5))) & 0x1F; - index = (index + 5) % 8; - if (index == 0) - i++; - } - base32.append(base32Chars.charAt(digit)); - } - - return base32.toString(); - } - - /** - * Decodes the given Base32 String to a raw byte array. - * - * @param base32 the base32 string that should be decoded - * @return Decoded base32 String as a raw byte array. - */ - static public byte[] decode(final String base32) { - int i, index, lookup, offset, digit; - byte[] bytes = new byte[base32.length() * 5 / 8]; - - for (i = 0, index = 0, offset = 0; i < base32.length(); i++) { - lookup = base32.charAt(i) - '0'; - - /* Skip chars outside the lookup table */ - if (lookup < 0 || lookup >= base32Lookup.length) { - continue; - } - - digit = base32Lookup[lookup]; - - /* If this digit is not in the table, ignore it */ - if (digit == 0xFF) { - continue; - } - - if (index <= 3) { - index = (index + 5) % 8; - if (index == 0) { - bytes[offset] |= digit; - offset++; - if (offset >= bytes.length) - break; - } else { - bytes[offset] |= digit << (8 - index); - } - } else { - index = (index + 5) % 8; - bytes[offset] |= (digit >>> index); - offset++; - - if (offset >= bytes.length) { - break; - } - bytes[offset] |= digit << (8 - index); - } - } - return bytes; - } - - /** For testing, take a command-line argument in Base32, decode, print in hex, - * encode, print - * - * @param args contains the base32 encoded string that should be decoded as the only element - */ - static public void main(String[] args) { - if (args.length == 0) { - System.out.println("Supply a Base32-encoded argument."); - return; - } - System.out.println(" Original: " + args[0]); - byte[] decoded = Base32.decode(args[0]); - System.out.print(" Hex: "); - for (int i = 0; i < decoded.length; i++) { - int b = decoded[i]; - if (b < 0) { - b += 256; - } - System.out.print((Integer.toHexString(b + 256)).substring(1)); - } - System.out.println(); - System.out.println("Reencoded: " + Base32.encode(decoded)); - } -} diff --git a/src/main/java/org/freenetproject/freemail/AccountManager.java b/src/main/java/org/freenetproject/freemail/AccountManager.java index 60474a86..cc739948 100644 --- a/src/main/java/org/freenetproject/freemail/AccountManager.java +++ b/src/main/java/org/freenetproject/freemail/AccountManager.java @@ -38,13 +38,13 @@ import java.util.Map; import java.util.TimeZone; -import org.archive.util.Base32; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.digests.MD5Digest; import org.bouncycastle.crypto.generators.RSAKeyPairGenerator; import org.bouncycastle.crypto.params.RSAKeyGenerationParameters; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.util.encoders.Hex; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.EmailAddress; import org.freenetproject.freemail.utils.Logger; import org.freenetproject.freemail.utils.PropsFile; diff --git a/src/main/java/org/freenetproject/freemail/FreemailAccount.java b/src/main/java/org/freenetproject/freemail/FreemailAccount.java index 0432a6fa..608166f2 100644 --- a/src/main/java/org/freenetproject/freemail/FreemailAccount.java +++ b/src/main/java/org/freenetproject/freemail/FreemailAccount.java @@ -22,9 +22,9 @@ import java.io.File; import java.util.Locale; -import org.archive.util.Base32; import org.freenetproject.freemail.fcp.HighLevelFCPClientFactory; import org.freenetproject.freemail.transport.MessageHandler; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.PropsFile; import freenet.support.Base64; @@ -68,7 +68,7 @@ public String getIdentity() { public String getDomain() { try { - return Base32.encode(Base64.decode(identity)).toLowerCase(Locale.ROOT) + ".freemail"; + return Base32.encodeWithoutPadding(Base64.decode(identity)).toLowerCase(Locale.ROOT) + ".freemail"; } catch(IllegalBase64Exception e) { //This would mean that WoT has changed the encoding of the identity string throw new AssertionError("Got IllegalBase64Exception when decoding " + identity); diff --git a/src/main/java/org/freenetproject/freemail/HashSlotManager.java b/src/main/java/org/freenetproject/freemail/HashSlotManager.java index 904ba46d..c158f7a9 100644 --- a/src/main/java/org/freenetproject/freemail/HashSlotManager.java +++ b/src/main/java/org/freenetproject/freemail/HashSlotManager.java @@ -19,7 +19,7 @@ package org.freenetproject.freemail; -import org.archive.util.Base32; +import org.freenetproject.freemail.utils.Base32; import org.bouncycastle.crypto.digests.SHA256Digest; @@ -35,6 +35,6 @@ protected String incSlot(String slot) { sha256.update(buf, 0, buf.length); sha256.doFinal(buf, 0); - return Base32.encode(buf); + return Base32.encodeWithoutPadding(buf); } } diff --git a/src/main/java/org/freenetproject/freemail/imap/IMAPHandler.java b/src/main/java/org/freenetproject/freemail/imap/IMAPHandler.java index 14cb311e..9abb45b1 100644 --- a/src/main/java/org/freenetproject/freemail/imap/IMAPHandler.java +++ b/src/main/java/org/freenetproject/freemail/imap/IMAPHandler.java @@ -46,12 +46,12 @@ import java.util.Date; import java.util.concurrent.TimeUnit; -import org.archive.util.Base32; import org.freenetproject.freemail.AccountManager; import org.freenetproject.freemail.FreemailAccount; import org.freenetproject.freemail.MailMessage; import org.freenetproject.freemail.MessageBank; import org.freenetproject.freemail.ServerHandler; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.EmailAddress; import org.freenetproject.freemail.utils.Logger; diff --git a/src/main/java/org/freenetproject/freemail/smtp/SMTPHandler.java b/src/main/java/org/freenetproject/freemail/smtp/SMTPHandler.java index 6815ea0f..6ed169e9 100644 --- a/src/main/java/org/freenetproject/freemail/smtp/SMTPHandler.java +++ b/src/main/java/org/freenetproject/freemail/smtp/SMTPHandler.java @@ -42,13 +42,13 @@ import freenet.support.api.Bucket; import freenet.support.io.FileBucket; -import org.archive.util.Base32; import org.bouncycastle.util.encoders.Base64; import org.freenetproject.freemail.AccountManager; import org.freenetproject.freemail.Freemail; import org.freenetproject.freemail.FreemailAccount; import org.freenetproject.freemail.ServerHandler; import org.freenetproject.freemail.transport.MessageHandler; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.Logger; import org.freenetproject.freemail.wot.Identity; import org.freenetproject.freemail.wot.IdentityMatcher; diff --git a/src/main/java/org/freenetproject/freemail/transport/Channel.java b/src/main/java/org/freenetproject/freemail/transport/Channel.java index ad8cb729..d0039844 100644 --- a/src/main/java/org/freenetproject/freemail/transport/Channel.java +++ b/src/main/java/org/freenetproject/freemail/transport/Channel.java @@ -39,7 +39,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import org.archive.util.Base32; import org.bouncycastle.crypto.AsymmetricBlockCipher; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.digests.SHA256Digest; @@ -66,6 +65,7 @@ import org.freenetproject.freemail.fcp.FCPPutFailedException; import org.freenetproject.freemail.fcp.HighLevelFCPClient; import org.freenetproject.freemail.fcp.SSKKeyPair; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.DateStringFactory; import org.freenetproject.freemail.utils.Logger; import org.freenetproject.freemail.utils.PropsFile; @@ -593,14 +593,14 @@ boolean canSendMessages() { private String calculateNextSlot(String slot) { byte[] buf = Base32.decode(slot); - return Base32.encode(SHA256.digest(buf)); + return Base32.encodeWithoutPadding(SHA256.digest(buf)); } private String generateRandomSlot() { SHA256Digest sha256 = new SHA256Digest(); byte[] buf = new byte[sha256.getDigestSize()]; Freemail.getRNG().nextBytes(buf); - return Base32.encode(buf); + return Base32.encodeWithoutPadding(buf); } private class Fetcher implements Runnable { diff --git a/src/main/java/org/freenetproject/freemail/transport/MessageHandler.java b/src/main/java/org/freenetproject/freemail/transport/MessageHandler.java index 5a62b0d5..9ba0d8c9 100644 --- a/src/main/java/org/freenetproject/freemail/transport/MessageHandler.java +++ b/src/main/java/org/freenetproject/freemail/transport/MessageHandler.java @@ -39,7 +39,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.TimeUnit; -import org.archive.util.Base32; import org.freenetproject.freemail.Freemail; import org.freenetproject.freemail.Freemail.TaskType; import org.freenetproject.freemail.FreemailAccount; @@ -48,6 +47,7 @@ import org.freenetproject.freemail.Postman; import org.freenetproject.freemail.fcp.HighLevelFCPClientFactory; import org.freenetproject.freemail.transport.Channel.ChannelEventCallback; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.EmailAddress; import org.freenetproject.freemail.utils.Logger; import org.freenetproject.freemail.utils.PropsFile; @@ -550,7 +550,7 @@ private class AckCallback extends Postman implements ChannelEventCallback { private AckCallback(String remoteId) { assert (remoteId != null); try { - this.remoteId = Base32.encode(Base64.decode(remoteId)).toLowerCase(Locale.ROOT); + this.remoteId = Base32.encodeWithoutPadding(Base64.decode(remoteId)).toLowerCase(Locale.ROOT); } catch (IllegalBase64Exception e) { throw new AssertionError(); } diff --git a/src/main/java/org/freenetproject/freemail/ui/web/OutboxToadlet.java b/src/main/java/org/freenetproject/freemail/ui/web/OutboxToadlet.java index 8015a1c4..c746261f 100644 --- a/src/main/java/org/freenetproject/freemail/ui/web/OutboxToadlet.java +++ b/src/main/java/org/freenetproject/freemail/ui/web/OutboxToadlet.java @@ -25,12 +25,12 @@ import java.util.List; import java.util.concurrent.TimeUnit; -import org.archive.util.Base32; import org.freenetproject.freemail.AccountManager; import org.freenetproject.freemail.FreemailAccount; import org.freenetproject.freemail.FreemailPlugin; import org.freenetproject.freemail.l10n.FreemailL10n; import org.freenetproject.freemail.transport.MessageHandler.OutboxMessage; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.Logger; import org.freenetproject.freemail.utils.Timer; import org.freenetproject.freemail.wot.Identity; @@ -101,7 +101,7 @@ HTTPResponse makeWebPageGet(URI uri, HTTPRequest req, ToadletContext ctx, PageNo //Fall back to showing the address without the nickname String id; try { - id = Base32.encode(Base64.decode(message.recipient)); + id = Base32.encodeWithoutPadding(Base64.decode(message.recipient)); } catch (IllegalBase64Exception e) { //This should never happen since we couldn't possibly have sent the message if //the id couldn't be decoded diff --git a/src/main/java/org/freenetproject/freemail/utils/Base32.java b/src/main/java/org/freenetproject/freemail/utils/Base32.java new file mode 100644 index 00000000..aaa402b1 --- /dev/null +++ b/src/main/java/org/freenetproject/freemail/utils/Base32.java @@ -0,0 +1,129 @@ +/* Base32 + * + * $Id: Base32.java,v 1.4 2004/04/15 19:04:01 stack-sf Exp $ + * + * Created on Jan 21, 2004 + * + * Copyright (C) 2004 Internet Archive. + * + * This file is part of the Heritrix web crawler (crawler.archive.org). + * + * Heritrix is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * any later version. + * + * Heritrix is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with Heritrix; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.freenetproject.freemail.utils; + +import java.util.Arrays; +import java.util.stream.IntStream; + +/** + * RFC-compliant Base32 encoder and decoder. + * + * @see RFC 4648 + */ +public class Base32 { + + /** + * Encodes the given byte array using the Base32 encoding and returns + * the result string. + * + * @param bytes The bytes to encode + * @return The Base32-encoded string + */ + public static String encode(byte[] bytes) { + String encodedValue = encodeWithoutPadding(bytes); + + // now pad with as many ‘=’ as needed to make the length a multiple of 8 + return encodedValue + "======".substring(0, (8 - (encodedValue.length() % 8)) % 8); + } + + /** + * Encodes the given byte array using the Base32 encoding and returns + * the result string without any padding. + * + *

+ * Omitting the padding is a violation of the RFC, so use this with caution. + *

+ * + * @param bytes The bytes to encode + * @return The string without any padding + */ + public static String encodeWithoutPadding(byte[] bytes) { + StringBuilder result = new StringBuilder(); + int buffer = 0; + int availableBits = 0; + + // shift each byte into a buffer and then use as many 5-bit units as possible + for (byte b : bytes) { + buffer = (buffer << 8) | (b & 0xff); + availableBits += 8; + while (availableBits >= 5) { + result.append(base32Characters[(buffer >> (availableBits - 5)) & 0x1f]); + availableBits -= 5; + } + } + + // one character might be left, append it + if (availableBits > 0) { + result.append(base32Characters[(buffer << (5 - availableBits)) & 0x1f]); + } + + return result.toString(); + } + + /** + * Decodes the given Base32-encoded string and returns the resulting + * byte array. Non-alphanet characters are ignored. + * + * @param encodedValue The Base32-encoded string + * @return The decoded byte array + */ + public static byte[] decode(String encodedValue) { + byte[] output = new byte[encodedValue.length() * 5 / 8]; + int buffer = 0; + int availableBits = 0; + int totalBytes = 0; + + for (char character : encodedValue.toCharArray()) { + int decodedValue = (character < base32DecodedValues.length) ? base32DecodedValues[character] : -1; + if (decodedValue == -1) { + continue; + } + buffer = (buffer << 5) | decodedValue; + availableBits += 5; + if (availableBits >= 8) { + output[totalBytes++] = (byte) (buffer >> (availableBits - 8)); + availableBits -= 8; + } + } + + return Arrays.copyOf(output, totalBytes); + } + + private static final char[] base32Characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".toCharArray(); + private static final int[] base32DecodedValues = generateBase32DecodedValues(); + + private static int[] generateBase32DecodedValues() { + int[] result = new int[128]; + Arrays.fill(result, (byte) -1); + IntStream.range(0, base32Characters.length).forEach(index -> { + result[base32Characters[index]] = index; + // add the lower-case letters as well; for the numbers, ORing + // them with 0x20 doesn’t do anything, as that bit is already set. + result[base32Characters[index] | 0x20] = index; + }); + return result; + } + +} diff --git a/src/main/java/org/freenetproject/freemail/utils/EmailAddress.java b/src/main/java/org/freenetproject/freemail/utils/EmailAddress.java index fc928df6..4cfce201 100644 --- a/src/main/java/org/freenetproject/freemail/utils/EmailAddress.java +++ b/src/main/java/org/freenetproject/freemail/utils/EmailAddress.java @@ -26,7 +26,6 @@ import freenet.support.Base64; -import org.archive.util.Base32; import org.freenetproject.freemail.AccountManager; import org.freenetproject.freemail.MailSite; diff --git a/src/main/java/org/freenetproject/freemail/wot/Identity.java b/src/main/java/org/freenetproject/freemail/wot/Identity.java index 35c7f971..bece814a 100644 --- a/src/main/java/org/freenetproject/freemail/wot/Identity.java +++ b/src/main/java/org/freenetproject/freemail/wot/Identity.java @@ -22,7 +22,7 @@ import java.util.Locale; -import org.archive.util.Base32; +import org.freenetproject.freemail.utils.Base32; import freenet.support.Base64; import freenet.support.IllegalBase64Exception; @@ -44,7 +44,7 @@ public String getIdentityID() { public String getBase32IdentityID() { try { - return Base32.encode(Base64.decode(identityID)).toLowerCase(Locale.ROOT); + return Base32.encodeWithoutPadding(Base64.decode(identityID)).toLowerCase(Locale.ROOT); } catch (IllegalBase64Exception e) { //Can't happen since we always get the id from WoT throw new AssertionError(); diff --git a/src/test/java/data/TestId1Data.java b/src/test/java/data/TestId1Data.java index 99aeeb7e..f54b12b9 100644 --- a/src/test/java/data/TestId1Data.java +++ b/src/test/java/data/TestId1Data.java @@ -19,7 +19,7 @@ package data; -import org.archive.util.Base32; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.DateStringFactory; import freenet.support.Base64; @@ -30,7 +30,7 @@ public class TestId1Data { public static final String BASE32_ID; static { try { - BASE32_ID = Base32.encode(Base64.decode(BASE64_ID)); + BASE32_ID = Base32.encodeWithoutPadding(Base64.decode(BASE64_ID)); } catch (IllegalBase64Exception e) { throw new AssertionError(e); } diff --git a/src/test/java/data/TestId2Data.java b/src/test/java/data/TestId2Data.java index eece16db..bf8a8d52 100644 --- a/src/test/java/data/TestId2Data.java +++ b/src/test/java/data/TestId2Data.java @@ -19,7 +19,7 @@ package data; -import org.archive.util.Base32; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.DateStringFactory; import freenet.support.Base64; @@ -30,7 +30,7 @@ public class TestId2Data { private static final String BASE32_ID; static { try { - BASE32_ID = Base32.encode(Base64.decode(BASE64_ID)); + BASE32_ID = Base32.encodeWithoutPadding(Base64.decode(BASE64_ID)); } catch (IllegalBase64Exception e) { throw new AssertionError(e); } diff --git a/src/test/java/fakes/MockFreemailAccount.java b/src/test/java/fakes/MockFreemailAccount.java index 26d591c0..92e8286b 100644 --- a/src/test/java/fakes/MockFreemailAccount.java +++ b/src/test/java/fakes/MockFreemailAccount.java @@ -22,9 +22,9 @@ import java.io.File; import java.util.Locale; -import org.archive.util.Base32; import org.freenetproject.freemail.Freemail; import org.freenetproject.freemail.NullFreemailAccount; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.PropsFile; import freenet.support.Base64; @@ -60,7 +60,7 @@ public PropsFile getProps() { @Override public String getDomain() { try { - return Base32.encode(Base64.decode(identity)).toLowerCase(Locale.ROOT) + ".freemail"; + return Base32.encodeWithoutPadding(Base64.decode(identity)).toLowerCase(Locale.ROOT) + ".freemail"; } catch(IllegalBase64Exception e) { //This would mean that WoT has changed the encoding of the identity string throw new AssertionError("Got IllegalBase64Exception when decoding " + identity); diff --git a/src/test/java/fakes/MockIdentity.java b/src/test/java/fakes/MockIdentity.java index 70f68ff7..3757c8e0 100644 --- a/src/test/java/fakes/MockIdentity.java +++ b/src/test/java/fakes/MockIdentity.java @@ -19,7 +19,7 @@ package fakes; -import org.archive.util.Base32; +import org.freenetproject.freemail.utils.Base32; import org.freenetproject.freemail.utils.Logger; import freenet.support.Base64; @@ -45,7 +45,7 @@ public String getRequestURI() { public String getBase32IdentityID() { Logger.debug(this, "getBase32IdentityID()"); try { - return Base32.encode(Base64.decode(identityID)); + return Base32.encodeWithoutPadding(Base64.decode(identityID)); } catch (IllegalBase64Exception e) { throw new AssertionError(); } diff --git a/src/test/java/org/freenetproject/freemail/FreemailAccountTest.java b/src/test/java/org/freenetproject/freemail/FreemailAccountTest.java index 93af5e97..95262393 100644 --- a/src/test/java/org/freenetproject/freemail/FreemailAccountTest.java +++ b/src/test/java/org/freenetproject/freemail/FreemailAccountTest.java @@ -19,13 +19,18 @@ package org.freenetproject.freemail; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; +import freenet.crypt.Yarrow; +import freenet.keys.InsertableClientSSK; import java.util.Locale; import org.junit.Test; -import org.archive.util.Base32; +import org.freenetproject.freemail.utils.Base32; import freenet.support.Base64; @@ -47,4 +52,12 @@ public void simpleAddress() { Locale.setDefault(defaultLocale); } } + + @Test + public void freemailAccountStripsBase32PaddingFromDomain() { + byte[] routingKey = InsertableClientSSK.createRandom(new Yarrow(), "").getURI().getRoutingKey(); + FreemailAccount account = new FreemailAccount(Base64.encode(routingKey), null, null, null); + assertThat(account.getDomain(), not(containsString("="))); + } + } diff --git a/src/test/java/org/freenetproject/freemail/utils/Base32Test.java b/src/test/java/org/freenetproject/freemail/utils/Base32Test.java new file mode 100644 index 00000000..14d5905d --- /dev/null +++ b/src/test/java/org/freenetproject/freemail/utils/Base32Test.java @@ -0,0 +1,139 @@ +package org.freenetproject.freemail.utils; + +import org.junit.Test; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.freenetproject.freemail.utils.Base32.decode; +import static org.freenetproject.freemail.utils.Base32.encode; +import static org.freenetproject.freemail.utils.Base32.encodeWithoutPadding; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class Base32Test { + + @Test + public void decodingAnEmptyStringResultsInAnEmptyArray() { + assertThat(decode(""), equalTo(new byte[0])); + } + + @Test + public void encodingAnEmptyArrayResultsInAnEmptyString() { + assertThat(encode(new byte[0]), equalTo("")); + } + + @Test + public void encodingAnEmptyArrayWithoutPaddingResultsInAnEmptyString() { + assertThat(encode(new byte[0]), equalTo("")); + } + + @Test + public void encodingASingleByteResultsInAnEightCharacterString() { + assertThat(encode(bytes(0xff)), equalTo("74======")); + } + + @Test + public void encodingASingleByteWithoutPaddingResultsInATwoCharacterString() { + assertThat(encodeWithoutPadding(bytes(0xff)), equalTo("74")); + } + + @Test + public void twoBytesAreEncodedCorrectly() { + assertThat(encode(bytes(0x55, 0xaa)), equalTo("KWVA====")); + } + + @Test + public void twoBytesAreEncodedWithoutPaddingCorrectly() { + assertThat(encodeWithoutPadding(bytes(0x55, 0xaa)), equalTo("KWVA")); + } + + @Test + public void threeBytesAreEncodedCorrectly() { + assertThat(encode(bytes(0xaa, 0x55, 0xff)), equalTo("VJK76===")); + } + + @Test + public void threeBytesAreEncodedWithoutPaddingCorrectly() { + assertThat(encodeWithoutPadding(bytes(0xaa, 0x55, 0xff)), equalTo("VJK76")); + } + + @Test + public void fourBytesAreEncodedCorrectly() { + assertThat(encode(bytes(0x12, 0x34, 0x56, 0x78)), equalTo("CI2FM6A=")); + } + + @Test + public void fourBytesAreEncodedWithoutPaddingCorrectly() { + assertThat(encodeWithoutPadding(bytes(0x12, 0x34, 0x56, 0x78)), equalTo("CI2FM6A")); + } + + @Test + public void fiveBytesAreEncodedCorrectly() { + assertThat(encode(bytes(0xff, 0xfe, 0xfd, 0xfc, 0xfb)), equalTo("777P37H3")); + } + + @Test + public void fiveBytesAreEncodedWithoutPaddingCorrectly() { + assertThat(encodeWithoutPadding(bytes(0xff, 0xfe, 0xfd, 0xfc, 0xfb)), equalTo("777P37H3")); + } + + @Test + public void decodingAnEightCharacterStringReturnsTheCorrectByte() { + assertThat(decode("74======"), equalTo(bytes(0xff))); + } + + // https://datatracker.ietf.org/doc/html/draft-ietf-sasl-gssapi-00, § 2.1 + @Test + public void exampleDataFromSaslGssapiDraftIsEncodedCorrectly() { + assertThat(encode(bytes(0x57, 0xee, 0x81, 0x82, 0x4e, 0xac, 0x4d, 0xb0, 0xe6, 0x50)), equalTo("K7XIDASOVRG3BZSQ")); + } + + // https://datatracker.ietf.org/doc/html/draft-ietf-sasl-gssapi-00, § 2.1 + @Test + public void exampleDataFromSaslGssapiDraftIsDecodedCorrectly() { + assertThat(decode("K7XIDASOVRG3BZSQ"), equalTo(bytes(0x57, 0xee, 0x81, 0x82, 0x4e, 0xac, 0x4d, 0xb0, 0xe6, 0x50))); + } + + @Test + public void testVectorsFromRfc4648AreEncodedCorrectly() { + assertThat(encode("f".getBytes(UTF_8)), equalTo("MY======")); + assertThat(encode("fo".getBytes(UTF_8)), equalTo("MZXQ====")); + assertThat(encode("foo".getBytes(UTF_8)), equalTo("MZXW6===")); + assertThat(encode("foob".getBytes(UTF_8)), equalTo("MZXW6YQ=")); + assertThat(encode("fooba".getBytes(UTF_8)), equalTo("MZXW6YTB")); + assertThat(encode("foobar".getBytes(UTF_8)), equalTo("MZXW6YTBOI======")); + } + + @Test + public void testVectorsFromRfc4648AreDecodedCorrectly() { + assertThat(decode("MY======"), equalTo(bytes('f'))); + assertThat(decode("MZXQ===="), equalTo(bytes('f', 'o'))); + assertThat(decode("MZXW6==="), equalTo(bytes('f', 'o', 'o'))); + assertThat(decode("MZXW6YQ="), equalTo(bytes('f', 'o', 'o', 'b'))); + assertThat(decode("MZXW6YTB"), equalTo(bytes('f', 'o', 'o', 'b', 'a'))); + assertThat(decode("MZXW6YTBOI======"), equalTo(bytes('f', 'o', 'o', 'b', 'a', 'r'))); + } + + @Test + public void lowercaseAlphabetCharactersAreDecodedCorrectly() { + assertThat(decode("hntq3blvijjhyg4t4cdq===="), equalTo(bytes(0x3b, 0x67, 0x0d, 0x85, 0x75, 0x42, 0x52, 0x7c, 0x1b, 0x93, 0xe0, 0x87))); + } + + @Test + public void missingPaddingIsIgnored() { + assertThat(decode("HNTQ3BLVIJJHYG4T4CDQ="), equalTo(bytes(0x3b, 0x67, 0x0d, 0x85, 0x75, 0x42, 0x52, 0x7c, 0x1b, 0x93, 0xe0, 0x87))); + } + + @Test + public void nonAlphabetCharactersAreIgnored() { + assertThat(decode("ßK87-X%I$D#A/S!O'V\"RäG930B(Z)S}Q["), equalTo(bytes(0x57, 0xee, 0x81, 0x82, 0x4e, 0xac, 0x4d, 0xb0, 0xe6, 0x50))); + } + + private static byte[] bytes(int... values) { + byte[] result = new byte[values.length]; + for (int index = 0; index < values.length; index++) { + result[index] = (byte) values[index]; + } + return result; + } + +}