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; + } + +}