@@ -6,6 +6,7 @@ import android.databinding.Observable; | |||||
import com.wireguard.android.BR; | import com.wireguard.android.BR; | ||||
import com.wireguard.crypto.Keypair; | import com.wireguard.crypto.Keypair; | ||||
import com.wireguard.crypto.KeyEncoding; | |||||
/** | /** | ||||
* Represents the configuration for a WireGuard interface (an [Interface] block). | * 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) { | public void setPrivateKey(String privateKey) { | ||||
// Avoid exceptions from Keypair while the user is typing. | // 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; | return; | ||||
keypair = new Keypair(privateKey); | keypair = new Keypair(privateKey); | ||||
notifyPropertyChanged(BR.privateKey); | notifyPropertyChanged(BR.privateKey); | ||||
@@ -0,0 +1,75 @@ | |||||
/* Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. 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; | |||||
} | |||||
} |
@@ -9,12 +9,9 @@ import java.security.SecureRandom; | |||||
*/ | */ | ||||
public class Keypair { | public class Keypair { | ||||
private static final int KEY_LENGTH = 32; | |||||
public static final int KEY_STRING_LENGTH = 44; | |||||
private static byte[] generatePrivateKey() { | private static byte[] generatePrivateKey() { | ||||
final SecureRandom secureRandom = new SecureRandom(); | final SecureRandom secureRandom = new SecureRandom(); | ||||
final byte privateKey[] = new byte[KEY_LENGTH]; | |||||
final byte privateKey[] = new byte[KeyEncoding.WG_KEY_LEN]; | |||||
secureRandom.nextBytes(privateKey); | secureRandom.nextBytes(privateKey); | ||||
privateKey[0] &= 248; | privateKey[0] &= 248; | ||||
privateKey[31] &= 127; | privateKey[31] &= 127; | ||||
@@ -23,22 +20,11 @@ public class Keypair { | |||||
} | } | ||||
private static byte[] generatePublicKey(byte privateKey[]) { | 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); | Curve25519.eval(publicKey, 0, privateKey, null); | ||||
return publicKey; | 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 privateKey[]; | ||||
private final byte publicKey[]; | private final byte publicKey[]; | ||||
@@ -52,14 +38,14 @@ public class Keypair { | |||||
} | } | ||||
public Keypair(String privateKey) { | public Keypair(String privateKey) { | ||||
this(parseKey(privateKey)); | |||||
this(KeyEncoding.keyFromBase64(privateKey)); | |||||
} | } | ||||
public String getPrivateKey() { | public String getPrivateKey() { | ||||
return unParseKey(privateKey); | |||||
return KeyEncoding.keyToBase64(privateKey); | |||||
} | } | ||||
public String getPublicKey() { | public String getPublicKey() { | ||||
return unParseKey(publicKey); | |||||
return KeyEncoding.keyToBase64(publicKey); | |||||
} | } | ||||
} | } |