diff --git a/app/src/main/java/com/wireguard/config/Interface.java b/app/src/main/java/com/wireguard/config/Interface.java index 35185cf9..9f74bab0 100644 --- a/app/src/main/java/com/wireguard/config/Interface.java +++ b/app/src/main/java/com/wireguard/config/Interface.java @@ -6,6 +6,7 @@ import android.databinding.Observable; import com.wireguard.android.BR; import com.wireguard.crypto.Keypair; +import com.wireguard.crypto.KeyEncoding; /** * Represents the configuration for a WireGuard interface (an [Interface] block). @@ -90,7 +91,7 @@ public class Interface extends BaseObservable implements Observable { public void setPrivateKey(String privateKey) { // Avoid exceptions from Keypair while the user is typing. - if (privateKey.length() != Keypair.KEY_STRING_LENGTH) + if (privateKey.length() != KeyEncoding.WG_KEY_LEN_BASE64) return; keypair = new Keypair(privateKey); notifyPropertyChanged(BR.privateKey); diff --git a/app/src/main/java/com/wireguard/crypto/KeyEncoding.java b/app/src/main/java/com/wireguard/crypto/KeyEncoding.java new file mode 100644 index 00000000..c62657cb --- /dev/null +++ b/app/src/main/java/com/wireguard/crypto/KeyEncoding.java @@ -0,0 +1,75 @@ +/* Copyright (C) 2015-2017 Jason A. Donenfeld . All Rights Reserved. + * + * This is a specialized constant-time base64 implementation that resists side-channel attacks. + */ + +package com.wireguard.crypto; + +public class KeyEncoding { + public static final int WG_KEY_LEN = 32; + public static final int WG_KEY_LEN_BASE64 = 44; + + private static void encodeBase64(char dest[], final int dest_offset, final byte src[], final int src_offset) { + final byte input[] = { (byte)((src[0 + src_offset] >>> 2) & 63), + (byte)(((src[0 + src_offset] << 4) | ((src[1 + src_offset] & 0xff) >>> 4)) & 63), + (byte)(((src[1 + src_offset] << 2) | ((src[2 + src_offset] & 0xff) >>> 6)) & 63), + (byte)(src[2 + src_offset] & 63) }; + for (int i = 0; i < 4; ++i) + dest[i + dest_offset] = (char)(input[i] + 'A' + + (((25 - input[i]) >>> 8) & 6) + - (((51 - input[i]) >>> 8) & 75) + - (((61 - input[i]) >>> 8) & 15) + + (((62 - input[i]) >>> 8) & 3)); + } + + public static String keyToBase64(final byte key[]) { + if (key.length != WG_KEY_LEN) + throw new IllegalArgumentException("WireGuard keys must be 32 bytes"); + final char base64[] = new char[WG_KEY_LEN_BASE64]; + int i; + for (i = 0; i < WG_KEY_LEN / 3; ++i) + encodeBase64(base64, i * 4, key, i * 3); + final byte endSegment[] = { key[i * 3 + 0], key[i * 3 + 1], 0 }; + encodeBase64(base64, i * 4, endSegment, 0); + base64[WG_KEY_LEN_BASE64 - 1] = '='; + return new String(base64); + } + + private static int decodeBase64(final char src[], int src_offset) { + int val = 0; + for (int i = 0; i < 4; ++i) + val |= (-1 + + ((((('A' - 1) - src[i + src_offset]) & (src[i + src_offset] - ('Z' + 1))) >>> 8) & (src[i + src_offset] - 64)) + + ((((('a' - 1) - src[i + src_offset]) & (src[i + src_offset] - ('z' + 1))) >>> 8) & (src[i + src_offset] - 70)) + + ((((('0' - 1) - src[i + src_offset]) & (src[i + src_offset] - ('9' + 1))) >>> 8) & (src[i + src_offset] + 5)) + + ((((('+' - 1) - src[i + src_offset]) & (src[i + src_offset] - ('+' + 1))) >>> 8) & 63) + + ((((('/' - 1) - src[i + src_offset]) & (src[i + src_offset] - ('/' + 1))) >>> 8) & 64) + ) << (18 - 6 * i); + return val; + } + + public static byte[] keyFromBase64(final String input) { + final char base64[] = input.toCharArray(); + if (base64.length != WG_KEY_LEN_BASE64 || base64[WG_KEY_LEN_BASE64 - 1] != '=') + throw new IllegalArgumentException("WireGuard base64 keys must be 44 characters encoding 32 bytes"); + final byte key[] = new byte[WG_KEY_LEN]; + int i; + int val; + + for (i = 0; i < WG_KEY_LEN / 3; ++i) { + val = decodeBase64(base64, i * 4); + if (val < 0) + throw new IllegalArgumentException("WireGuard base64 keys must be 44 characters encoding 32 bytes"); + key[i * 3 + 0] = (byte)((val >>> 16) & 0xff); + key[i * 3 + 1] = (byte)((val >>> 8) & 0xff); + key[i * 3 + 2] = (byte)(val & 0xff); + } + final char endSegment[] = { base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' }; + val = decodeBase64(endSegment, 0); + if (val < 0 || (val & 0xff) != 0) + throw new IllegalArgumentException("WireGuard base64 keys must be 44 characters encoding 32 bytes"); + key[i * 3 + 0] = (byte)((val >>> 16) & 0xff); + key[i * 3 + 1] = (byte)((val >>> 8) & 0xff); + return key; + } +} diff --git a/app/src/main/java/com/wireguard/crypto/Keypair.java b/app/src/main/java/com/wireguard/crypto/Keypair.java index ef2a4b31..f98b5e21 100644 --- a/app/src/main/java/com/wireguard/crypto/Keypair.java +++ b/app/src/main/java/com/wireguard/crypto/Keypair.java @@ -9,12 +9,9 @@ import java.security.SecureRandom; */ public class Keypair { - private static final int KEY_LENGTH = 32; - public static final int KEY_STRING_LENGTH = 44; - private static byte[] generatePrivateKey() { final SecureRandom secureRandom = new SecureRandom(); - final byte privateKey[] = new byte[KEY_LENGTH]; + final byte privateKey[] = new byte[KeyEncoding.WG_KEY_LEN]; secureRandom.nextBytes(privateKey); privateKey[0] &= 248; privateKey[31] &= 127; @@ -23,22 +20,11 @@ public class Keypair { } private static byte[] generatePublicKey(byte privateKey[]) { - final byte publicKey[] = new byte[KEY_LENGTH]; + final byte publicKey[] = new byte[KeyEncoding.WG_KEY_LEN]; Curve25519.eval(publicKey, 0, privateKey, null); return publicKey; } - private static byte[] parseKey(String key) { - final byte keyBytes[] = Base64.decode(key, Base64.NO_WRAP); - if (keyBytes.length != KEY_LENGTH) - throw new IndexOutOfBoundsException("Key is not the correct length"); - return keyBytes; - } - - private static String unParseKey(byte keyBytes[]) { - return Base64.encodeToString(keyBytes, Base64.NO_WRAP); - } - private final byte privateKey[]; private final byte publicKey[]; @@ -52,14 +38,14 @@ public class Keypair { } public Keypair(String privateKey) { - this(parseKey(privateKey)); + this(KeyEncoding.keyFromBase64(privateKey)); } public String getPrivateKey() { - return unParseKey(privateKey); + return KeyEncoding.keyToBase64(privateKey); } public String getPublicKey() { - return unParseKey(publicKey); + return KeyEncoding.keyToBase64(publicKey); } }