Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>master
@@ -9,8 +9,8 @@ import android.content.Context; | |||||
import android.util.Log; | import android.util.Log; | ||||
import com.wireguard.android.R; | import com.wireguard.android.R; | ||||
import com.wireguard.config.BadConfigException; | |||||
import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||
import com.wireguard.config.ParseException; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.FileInputStream; | import java.io.FileInputStream; | ||||
@@ -69,7 +69,7 @@ public final class FileConfigStore implements ConfigStore { | |||||
} | } | ||||
@Override | @Override | ||||
public Config load(final String name) throws IOException, ParseException { | |||||
public Config load(final String name) throws BadConfigException, IOException { | |||||
try (final FileInputStream stream = new FileInputStream(fileFor(name))) { | try (final FileInputStream stream = new FileInputStream(fileFor(name))) { | ||||
return Config.parse(stream); | return Config.parse(stream); | ||||
} | } | ||||
@@ -18,8 +18,8 @@ import android.view.inputmethod.InputMethodManager; | |||||
import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||
import com.wireguard.android.R; | import com.wireguard.android.R; | ||||
import com.wireguard.android.databinding.ConfigNamingDialogFragmentBinding; | import com.wireguard.android.databinding.ConfigNamingDialogFragmentBinding; | ||||
import com.wireguard.config.BadConfigException; | |||||
import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||
import com.wireguard.config.ParseException; | |||||
import java.io.ByteArrayInputStream; | import java.io.ByteArrayInputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
@@ -67,8 +67,8 @@ public class ConfigNamingDialogFragment extends DialogFragment { | |||||
try { | try { | ||||
config = Config.parse(new ByteArrayInputStream(getArguments().getString(KEY_CONFIG_TEXT).getBytes(StandardCharsets.UTF_8))); | config = Config.parse(new ByteArrayInputStream(getArguments().getString(KEY_CONFIG_TEXT).getBytes(StandardCharsets.UTF_8))); | ||||
} catch (final IOException | ParseException exception) { | |||||
throw new RuntimeException(getResources().getString(R.string.invalid_config_error, getClass().getSimpleName()), exception); | |||||
} catch (final BadConfigException | IOException e) { | |||||
throw new RuntimeException(getResources().getString(R.string.invalid_config_error, getClass().getSimpleName()), e); | |||||
} | } | ||||
} | } | ||||
@@ -40,8 +40,8 @@ import com.wireguard.android.model.Tunnel; | |||||
import com.wireguard.android.util.ExceptionLoggers; | import com.wireguard.android.util.ExceptionLoggers; | ||||
import com.wireguard.android.widget.MultiselectableRelativeLayout; | import com.wireguard.android.widget.MultiselectableRelativeLayout; | ||||
import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener; | import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener; | ||||
import com.wireguard.config.BadConfigException; | |||||
import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||
import com.wireguard.config.ParseException; | |||||
import java.io.BufferedReader; | import java.io.BufferedReader; | ||||
import java.io.ByteArrayInputStream; | import java.io.ByteArrayInputStream; | ||||
@@ -89,8 +89,8 @@ public class TunnelListFragment extends BaseFragment { | |||||
final FragmentManager fragmentManager = getFragmentManager(); | final FragmentManager fragmentManager = getFragmentManager(); | ||||
if (fragmentManager != null) | if (fragmentManager != null) | ||||
ConfigNamingDialogFragment.newInstance(configText).show(fragmentManager, null); | ConfigNamingDialogFragment.newInstance(configText).show(fragmentManager, null); | ||||
} catch (final IllegalArgumentException | IOException | ParseException exception) { | |||||
onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(exception)); | |||||
} catch (final BadConfigException | IOException e) { | |||||
onTunnelImportFinished(Collections.emptyList(), Collections.singletonList(e)); | |||||
} | } | ||||
} | } | ||||
@@ -11,8 +11,9 @@ import android.util.Log; | |||||
import com.wireguard.android.Application; | import com.wireguard.android.Application; | ||||
import com.wireguard.android.R; | import com.wireguard.android.R; | ||||
import com.wireguard.config.BadConfigException; | |||||
import com.wireguard.config.ParseException; | import com.wireguard.config.ParseException; | ||||
import com.wireguard.crypto.Key; | |||||
import com.wireguard.crypto.KeyFormatException; | |||||
import java9.util.concurrent.CompletionException; | import java9.util.concurrent.CompletionException; | ||||
import java9.util.function.BiConsumer; | import java9.util.function.BiConsumer; | ||||
@@ -37,6 +38,8 @@ public enum ExceptionLoggers implements BiConsumer<Object, Throwable> { | |||||
public static Throwable unwrap(final Throwable throwable) { | public static Throwable unwrap(final Throwable throwable) { | ||||
if (throwable instanceof CompletionException && throwable.getCause() != null) | if (throwable instanceof CompletionException && throwable.getCause() != null) | ||||
return throwable.getCause(); | return throwable.getCause(); | ||||
if (throwable instanceof ParseException && throwable.getCause() != null) | |||||
return throwable.getCause(); | |||||
return throwable; | return throwable; | ||||
} | } | ||||
@@ -44,13 +47,14 @@ public enum ExceptionLoggers implements BiConsumer<Object, Throwable> { | |||||
final Throwable innerThrowable = unwrap(throwable); | final Throwable innerThrowable = unwrap(throwable); | ||||
final Resources resources = Application.get().getResources(); | final Resources resources = Application.get().getResources(); | ||||
String message; | String message; | ||||
if (innerThrowable instanceof ParseException) { | |||||
final ParseException parseException = (ParseException) innerThrowable; | |||||
message = resources.getString(R.string.parse_error, parseException.getText(), parseException.getContext()); | |||||
if (parseException.getMessage() != null) | |||||
message += ": " + parseException.getMessage(); | |||||
} else if (innerThrowable instanceof Key.KeyFormatException) { | |||||
final Key.KeyFormatException keyFormatException = (Key.KeyFormatException) innerThrowable; | |||||
if (innerThrowable instanceof BadConfigException) { | |||||
final BadConfigException configException = (BadConfigException) innerThrowable; | |||||
message = resources.getString(R.string.parse_error, configException.getText(), configException.getLocation()); | |||||
final Throwable cause = unwrap(configException); | |||||
if (cause.getMessage() != null) | |||||
message += ": " + cause.getMessage(); | |||||
} else if (innerThrowable instanceof KeyFormatException) { | |||||
final KeyFormatException keyFormatException = (KeyFormatException) innerThrowable; | |||||
switch (keyFormatException.getFormat()) { | switch (keyFormatException.getFormat()) { | ||||
case BASE64: | case BASE64: | ||||
message = resources.getString(R.string.key_length_base64_exception_message); | message = resources.getString(R.string.key_length_base64_exception_message); | ||||
@@ -10,8 +10,8 @@ import android.databinding.ObservableList; | |||||
import android.os.Parcel; | import android.os.Parcel; | ||||
import android.os.Parcelable; | import android.os.Parcelable; | ||||
import com.wireguard.config.BadConfigException; | |||||
import com.wireguard.config.Config; | import com.wireguard.config.Config; | ||||
import com.wireguard.config.ParseException; | |||||
import com.wireguard.config.Peer; | import com.wireguard.config.Peer; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
@@ -63,7 +63,7 @@ public class ConfigProxy implements Parcelable { | |||||
return peers; | return peers; | ||||
} | } | ||||
public Config resolve() throws ParseException { | |||||
public Config resolve() throws BadConfigException { | |||||
final Collection<Peer> resolvedPeers = new ArrayList<>(); | final Collection<Peer> resolvedPeers = new ArrayList<>(); | ||||
for (final PeerProxy proxy : peers) | for (final PeerProxy proxy : peers) | ||||
resolvedPeers.add(proxy.resolve()); | resolvedPeers.add(proxy.resolve()); | ||||
@@ -14,9 +14,10 @@ import android.os.Parcelable; | |||||
import com.wireguard.android.BR; | import com.wireguard.android.BR; | ||||
import com.wireguard.config.Attribute; | import com.wireguard.config.Attribute; | ||||
import com.wireguard.config.BadConfigException; | |||||
import com.wireguard.config.Interface; | import com.wireguard.config.Interface; | ||||
import com.wireguard.config.ParseException; | |||||
import com.wireguard.crypto.Key; | import com.wireguard.crypto.Key; | ||||
import com.wireguard.crypto.KeyFormatException; | |||||
import com.wireguard.crypto.KeyPair; | import com.wireguard.crypto.KeyPair; | ||||
import java.net.InetAddress; | import java.net.InetAddress; | ||||
@@ -116,7 +117,7 @@ public class InterfaceProxy extends BaseObservable implements Parcelable { | |||||
return publicKey; | return publicKey; | ||||
} | } | ||||
public Interface resolve() throws ParseException { | |||||
public Interface resolve() throws BadConfigException { | |||||
final Interface.Builder builder = new Interface.Builder(); | final Interface.Builder builder = new Interface.Builder(); | ||||
if (!addresses.isEmpty()) | if (!addresses.isEmpty()) | ||||
builder.parseAddresses(addresses); | builder.parseAddresses(addresses); | ||||
@@ -157,7 +158,7 @@ public class InterfaceProxy extends BaseObservable implements Parcelable { | |||||
this.privateKey = privateKey; | this.privateKey = privateKey; | ||||
try { | try { | ||||
publicKey = new KeyPair(Key.fromBase64(privateKey)).getPublicKey().toBase64(); | publicKey = new KeyPair(Key.fromBase64(privateKey)).getPublicKey().toBase64(); | ||||
} catch (final Key.KeyFormatException ignored) { | |||||
} catch (final KeyFormatException ignored) { | |||||
publicKey = ""; | publicKey = ""; | ||||
} | } | ||||
notifyPropertyChanged(BR.privateKey); | notifyPropertyChanged(BR.privateKey); | ||||
@@ -15,8 +15,8 @@ import android.support.annotation.Nullable; | |||||
import com.wireguard.android.BR; | import com.wireguard.android.BR; | ||||
import com.wireguard.config.Attribute; | import com.wireguard.config.Attribute; | ||||
import com.wireguard.config.BadConfigException; | |||||
import com.wireguard.config.InetEndpoint; | import com.wireguard.config.InetEndpoint; | ||||
import com.wireguard.config.ParseException; | |||||
import com.wireguard.config.Peer; | import com.wireguard.config.Peer; | ||||
import com.wireguard.crypto.Key; | import com.wireguard.crypto.Key; | ||||
@@ -164,7 +164,7 @@ public class PeerProxy extends BaseObservable implements Parcelable { | |||||
return allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS; | return allowedIpsState == AllowedIpsState.CONTAINS_IPV4_PUBLIC_NETWORKS; | ||||
} | } | ||||
public Peer resolve() throws ParseException { | |||||
public Peer resolve() throws BadConfigException { | |||||
final Peer.Builder builder = new Peer.Builder(); | final Peer.Builder builder = new Peer.Builder(); | ||||
if (!allowedIps.isEmpty()) | if (!allowedIps.isEmpty()) | ||||
builder.parseAllowedIPs(allowedIps); | builder.parseAllowedIPs(allowedIps); | ||||
@@ -0,0 +1,118 @@ | |||||
/* | |||||
* Copyright © 2018 WireGuard LLC. All Rights Reserved. | |||||
* SPDX-License-Identifier: Apache-2.0 | |||||
*/ | |||||
package com.wireguard.config; | |||||
import android.support.annotation.Nullable; | |||||
import com.wireguard.crypto.KeyFormatException; | |||||
public class BadConfigException extends Exception { | |||||
private final Location location; | |||||
private final Reason reason; | |||||
private final Section section; | |||||
@Nullable private final CharSequence text; | |||||
private BadConfigException(final Section section, final Location location, | |||||
final Reason reason, @Nullable final CharSequence text, | |||||
@Nullable final Throwable cause) { | |||||
super(cause); | |||||
this.section = section; | |||||
this.location = location; | |||||
this.reason = reason; | |||||
this.text = text; | |||||
} | |||||
public BadConfigException(final Section section, final Location location, | |||||
final Reason reason, @Nullable final CharSequence text) { | |||||
this(section, location, reason, text, null); | |||||
} | |||||
public BadConfigException(final Section section, final Location location, | |||||
final KeyFormatException cause) { | |||||
this(section, location, Reason.INVALID_KEY, null, cause); | |||||
} | |||||
public BadConfigException(final Section section, final Location location, | |||||
@Nullable final CharSequence text, | |||||
final NumberFormatException cause) { | |||||
this(section, location, Reason.INVALID_NUMBER, text, cause); | |||||
} | |||||
public BadConfigException(final Section section, final Location location, | |||||
final ParseException cause) { | |||||
this(section, location, Reason.INVALID_VALUE, cause.getText(), cause); | |||||
} | |||||
public Location getLocation() { | |||||
return location; | |||||
} | |||||
public Reason getReason() { | |||||
return reason; | |||||
} | |||||
public Section getSection() { | |||||
return section; | |||||
} | |||||
@Nullable | |||||
public CharSequence getText() { | |||||
return text; | |||||
} | |||||
public enum Location { | |||||
TOP_LEVEL(""), | |||||
ADDRESS("Address"), | |||||
ALLOWED_IPS("AllowedIPs"), | |||||
DNS("DNS"), | |||||
ENDPOINT("Endpoint"), | |||||
EXCLUDED_APPLICATIONS("ExcludedApplications"), | |||||
LISTEN_PORT("ListenPort"), | |||||
MTU("MTU"), | |||||
PERSISTENT_KEEPALIVE("PersistentKeepalive"), | |||||
PRE_SHARED_KEY("PresharedKey"), | |||||
PRIVATE_KEY("PrivateKey"), | |||||
PUBLIC_KEY("PublicKey"); | |||||
private final String name; | |||||
Location(final String name) { | |||||
this.name = name; | |||||
} | |||||
public String getName() { | |||||
return name; | |||||
} | |||||
} | |||||
public enum Reason { | |||||
INVALID_KEY, | |||||
INVALID_NUMBER, | |||||
INVALID_VALUE, | |||||
MISSING_ATTRIBUTE, | |||||
MISSING_SECTION, | |||||
MISSING_VALUE, | |||||
SYNTAX_ERROR, | |||||
UNKNOWN_ATTRIBUTE, | |||||
UNKNOWN_SECTION | |||||
} | |||||
public enum Section { | |||||
CONFIG("Config"), | |||||
INTERFACE("Interface"), | |||||
PEER("Peer"); | |||||
private final String name; | |||||
Section(final String name) { | |||||
this.name = name; | |||||
} | |||||
public String getName() { | |||||
return name; | |||||
} | |||||
} | |||||
} |
@@ -7,6 +7,10 @@ package com.wireguard.config; | |||||
import android.support.annotation.Nullable; | import android.support.annotation.Nullable; | ||||
import com.wireguard.config.BadConfigException.Location; | |||||
import com.wireguard.config.BadConfigException.Reason; | |||||
import com.wireguard.config.BadConfigException.Section; | |||||
import java.io.BufferedReader; | import java.io.BufferedReader; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
@@ -37,23 +41,27 @@ public final class Config { | |||||
/** | /** | ||||
* Parses an series of "Interface" and "Peer" sections into a {@code Config}. Throws | * Parses an series of "Interface" and "Peer" sections into a {@code Config}. Throws | ||||
* {@link ParseException} if the input is not well-formed or contains unparseable sections. | |||||
* {@link BadConfigException} if the input is not well-formed or contains data that cannot | |||||
* be parsed. | |||||
* | * | ||||
* @param stream a stream of UTF-8 text that is interpreted as a WireGuard configuration file | |||||
* @param stream a stream of UTF-8 text that is interpreted as a WireGuard configuration | |||||
* @return a {@code Config} instance representing the supplied configuration | * @return a {@code Config} instance representing the supplied configuration | ||||
*/ | */ | ||||
public static Config parse(final InputStream stream) throws IOException, ParseException { | |||||
public static Config parse(final InputStream stream) | |||||
throws IOException, BadConfigException { | |||||
return parse(new BufferedReader(new InputStreamReader(stream))); | return parse(new BufferedReader(new InputStreamReader(stream))); | ||||
} | } | ||||
/** | /** | ||||
* Parses an series of "Interface" and "Peer" sections into a {@code Config}. Throws | * Parses an series of "Interface" and "Peer" sections into a {@code Config}. Throws | ||||
* {@link ParseException} if the input is not well-formed or contains unparseable sections. | |||||
* {@link BadConfigException} if the input is not well-formed or contains data that cannot | |||||
* be parsed. | |||||
* | * | ||||
* @param reader a BufferedReader of UTF-8 text that is interpreted as a WireGuard configuration file | |||||
* @param reader a BufferedReader of UTF-8 text that is interpreted as a WireGuard configuration | |||||
* @return a {@code Config} instance representing the supplied configuration | * @return a {@code Config} instance representing the supplied configuration | ||||
*/ | */ | ||||
public static Config parse(final BufferedReader reader) throws IOException, ParseException { | |||||
public static Config parse(final BufferedReader reader) | |||||
throws IOException, BadConfigException { | |||||
final Builder builder = new Builder(); | final Builder builder = new Builder(); | ||||
final Collection<String> interfaceLines = new ArrayList<>(); | final Collection<String> interfaceLines = new ArrayList<>(); | ||||
final Collection<String> peerLines = new ArrayList<>(); | final Collection<String> peerLines = new ArrayList<>(); | ||||
@@ -80,20 +88,23 @@ public final class Config { | |||||
inInterfaceSection = false; | inInterfaceSection = false; | ||||
inPeerSection = true; | inPeerSection = true; | ||||
} else { | } else { | ||||
throw new ParseException("top level", line, "Unknown section name"); | |||||
throw new BadConfigException(Section.CONFIG, Location.TOP_LEVEL, | |||||
Reason.UNKNOWN_SECTION, line); | |||||
} | } | ||||
} else if (inInterfaceSection) { | } else if (inInterfaceSection) { | ||||
interfaceLines.add(line); | interfaceLines.add(line); | ||||
} else if (inPeerSection) { | } else if (inPeerSection) { | ||||
peerLines.add(line); | peerLines.add(line); | ||||
} else { | } else { | ||||
throw new ParseException("top level", line, "Expected [Interface] or [Peer]"); | |||||
throw new BadConfigException(Section.CONFIG, Location.TOP_LEVEL, | |||||
Reason.UNKNOWN_SECTION, line); | |||||
} | } | ||||
} | } | ||||
if (inPeerSection) | if (inPeerSection) | ||||
builder.parsePeer(peerLines); | builder.parsePeer(peerLines); | ||||
else if (!inInterfaceSection) | else if (!inInterfaceSection) | ||||
throw new ParseException("top level", "", "Empty configuration"); | |||||
throw new BadConfigException(Section.CONFIG, Location.TOP_LEVEL, | |||||
Reason.MISSING_SECTION, null); | |||||
// Combine all [Interface] sections in the file. | // Combine all [Interface] sections in the file. | ||||
builder.parseInterface(interfaceLines); | builder.parseInterface(interfaceLines); | ||||
return builder.build(); | return builder.build(); | ||||
@@ -192,11 +203,13 @@ public final class Config { | |||||
return new Config(this); | return new Config(this); | ||||
} | } | ||||
public Builder parseInterface(final Iterable<? extends CharSequence> lines) throws ParseException { | |||||
public Builder parseInterface(final Iterable<? extends CharSequence> lines) | |||||
throws BadConfigException { | |||||
return setInterface(Interface.parse(lines)); | return setInterface(Interface.parse(lines)); | ||||
} | } | ||||
public Builder parsePeer(final Iterable<? extends CharSequence> lines) throws ParseException { | |||||
public Builder parsePeer(final Iterable<? extends CharSequence> lines) | |||||
throws BadConfigException { | |||||
return addPeer(Peer.parse(lines)); | return addPeer(Peer.parse(lines)); | ||||
} | } | ||||
@@ -37,17 +37,18 @@ public final class InetAddresses { | |||||
* @param address a string representing the IP address | * @param address a string representing the IP address | ||||
* @return an instance of {@link Inet4Address} or {@link Inet6Address}, as appropriate | * @return an instance of {@link Inet4Address} or {@link Inet6Address}, as appropriate | ||||
*/ | */ | ||||
public static InetAddress parse(final String address) { | |||||
public static InetAddress parse(final String address) throws ParseException { | |||||
if (address.isEmpty()) | if (address.isEmpty()) | ||||
throw new IllegalArgumentException("Empty address"); | |||||
throw new ParseException(InetAddress.class, address, "Empty address"); | |||||
try { | try { | ||||
return (InetAddress) PARSER_METHOD.invoke(null, address); | return (InetAddress) PARSER_METHOD.invoke(null, address); | ||||
} catch (final IllegalAccessException | InvocationTargetException e) { | } catch (final IllegalAccessException | InvocationTargetException e) { | ||||
final Throwable cause = e.getCause(); | final Throwable cause = e.getCause(); | ||||
// Re-throw parsing exceptions with the original type, as callers might try to catch | // Re-throw parsing exceptions with the original type, as callers might try to catch | ||||
// them. On the other hand, callers cannot be expected to handle reflection failures. | // them. On the other hand, callers cannot be expected to handle reflection failures. | ||||
throw cause instanceof IllegalArgumentException ? | |||||
(IllegalArgumentException) cause : new RuntimeException(e); | |||||
if (cause instanceof IllegalArgumentException) | |||||
throw new ParseException(InetAddress.class, address, cause); | |||||
throw new RuntimeException(e); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -42,9 +42,9 @@ public final class InetEndpoint { | |||||
this.port = port; | this.port = port; | ||||
} | } | ||||
public static InetEndpoint parse(final String endpoint) { | |||||
public static InetEndpoint parse(final String endpoint) throws ParseException { | |||||
if (FORBIDDEN_CHARACTERS.matcher(endpoint).find()) | if (FORBIDDEN_CHARACTERS.matcher(endpoint).find()) | ||||
throw new IllegalArgumentException("Forbidden characters in endpoint"); | |||||
throw new ParseException(InetEndpoint.class, endpoint, "Forbidden characters"); | |||||
final URI uri; | final URI uri; | ||||
try { | try { | ||||
uri = new URI("wg://" + endpoint); | uri = new URI("wg://" + endpoint); | ||||
@@ -52,12 +52,12 @@ public final class InetEndpoint { | |||||
throw new IllegalArgumentException(e); | throw new IllegalArgumentException(e); | ||||
} | } | ||||
if (uri.getPort() < 0) | if (uri.getPort() < 0) | ||||
throw new IllegalArgumentException("An endpoint must specify a port (e.g. 51820)"); | |||||
throw new ParseException(InetEndpoint.class, endpoint, "Missing/invalid port number"); | |||||
try { | try { | ||||
InetAddresses.parse(uri.getHost()); | InetAddresses.parse(uri.getHost()); | ||||
// Parsing ths host as a numeric address worked, so we don't need to do DNS lookups. | // Parsing ths host as a numeric address worked, so we don't need to do DNS lookups. | ||||
return new InetEndpoint(uri.getHost(), true, uri.getPort()); | return new InetEndpoint(uri.getHost(), true, uri.getPort()); | ||||
} catch (final IllegalArgumentException ignored) { | |||||
} catch (final ParseException ignored) { | |||||
// Failed to parse the host as a numeric address, so it must be a DNS hostname/FQDN. | // Failed to parse the host as a numeric address, so it must be a DNS hostname/FQDN. | ||||
return new InetEndpoint(uri.getHost(), false, uri.getPort()); | return new InetEndpoint(uri.getHost(), false, uri.getPort()); | ||||
} | } | ||||
@@ -22,19 +22,28 @@ public final class InetNetwork { | |||||
this.mask = mask; | this.mask = mask; | ||||
} | } | ||||
public static InetNetwork parse(final String network) { | |||||
public static InetNetwork parse(final String network) throws ParseException { | |||||
final int slash = network.lastIndexOf('/'); | final int slash = network.lastIndexOf('/'); | ||||
final String maskString; | |||||
final int rawMask; | final int rawMask; | ||||
final String rawAddress; | final String rawAddress; | ||||
if (slash >= 0) { | if (slash >= 0) { | ||||
rawMask = Integer.parseInt(network.substring(slash + 1), 10); | |||||
maskString = network.substring(slash + 1); | |||||
try { | |||||
rawMask = Integer.parseInt(maskString, 10); | |||||
} catch (final NumberFormatException ignored) { | |||||
throw new ParseException(Integer.class, maskString); | |||||
} | |||||
rawAddress = network.substring(0, slash); | rawAddress = network.substring(0, slash); | ||||
} else { | } else { | ||||
maskString = ""; | |||||
rawMask = -1; | rawMask = -1; | ||||
rawAddress = network; | rawAddress = network; | ||||
} | } | ||||
final InetAddress address = InetAddresses.parse(rawAddress); | final InetAddress address = InetAddresses.parse(rawAddress); | ||||
final int maxMask = (address instanceof Inet4Address) ? 32 : 128; | final int maxMask = (address instanceof Inet4Address) ? 32 : 128; | ||||
if (rawMask > maxMask) | |||||
throw new ParseException(InetNetwork.class, maskString, "Invalid network mask"); | |||||
final int mask = rawMask >= 0 && rawMask <= maxMask ? rawMask : maxMask; | final int mask = rawMask >= 0 && rawMask <= maxMask ? rawMask : maxMask; | ||||
return new InetNetwork(address, mask); | return new InetNetwork(address, mask); | ||||
} | } | ||||
@@ -7,7 +7,11 @@ package com.wireguard.config; | |||||
import android.support.annotation.Nullable; | import android.support.annotation.Nullable; | ||||
import com.wireguard.config.BadConfigException.Location; | |||||
import com.wireguard.config.BadConfigException.Reason; | |||||
import com.wireguard.config.BadConfigException.Section; | |||||
import com.wireguard.crypto.Key; | import com.wireguard.crypto.Key; | ||||
import com.wireguard.crypto.KeyFormatException; | |||||
import com.wireguard.crypto.KeyPair; | import com.wireguard.crypto.KeyPair; | ||||
import java.net.InetAddress; | import java.net.InetAddress; | ||||
@@ -22,7 +26,6 @@ import java.util.Set; | |||||
import java9.util.Lists; | import java9.util.Lists; | ||||
import java9.util.Optional; | import java9.util.Optional; | ||||
import java9.util.stream.Collectors; | import java9.util.stream.Collectors; | ||||
import java9.util.stream.Stream; | |||||
import java9.util.stream.StreamSupport; | import java9.util.stream.StreamSupport; | ||||
/** | /** | ||||
@@ -60,11 +63,13 @@ public final class Interface { | |||||
* @param lines An iterable sequence of lines, containing at least a private key attribute | * @param lines An iterable sequence of lines, containing at least a private key attribute | ||||
* @return An {@code Interface} with all of the attributes from {@code lines} set | * @return An {@code Interface} with all of the attributes from {@code lines} set | ||||
*/ | */ | ||||
public static Interface parse(final Iterable<? extends CharSequence> lines) throws ParseException { | |||||
public static Interface parse(final Iterable<? extends CharSequence> lines) | |||||
throws BadConfigException { | |||||
final Builder builder = new Builder(); | final Builder builder = new Builder(); | ||||
for (final CharSequence line : lines) { | for (final CharSequence line : lines) { | ||||
final Attribute attribute = Attribute.parse(line) | |||||
.orElseThrow(() -> new ParseException("[Interface]", line, "Syntax error")); | |||||
final Attribute attribute = Attribute.parse(line).orElseThrow(() -> | |||||
new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL, | |||||
Reason.SYNTAX_ERROR, line)); | |||||
switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) { | switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) { | ||||
case "address": | case "address": | ||||
builder.parseAddresses(attribute.getValue()); | builder.parseAddresses(attribute.getValue()); | ||||
@@ -85,7 +90,8 @@ public final class Interface { | |||||
builder.parsePrivateKey(attribute.getValue()); | builder.parsePrivateKey(attribute.getValue()); | ||||
break; | break; | ||||
default: | default: | ||||
throw new ParseException("[Interface]", attribute.getKey(), "Unknown attribute"); | |||||
throw new BadConfigException(Section.INTERFACE, Location.TOP_LEVEL, | |||||
Reason.UNKNOWN_ATTRIBUTE, attribute.getKey()); | |||||
} | } | ||||
} | } | ||||
return builder.build(); | return builder.build(); | ||||
@@ -260,9 +266,10 @@ public final class Interface { | |||||
return this; | return this; | ||||
} | } | ||||
public Interface build() { | |||||
public Interface build() throws BadConfigException { | |||||
if (keyPair == null) | if (keyPair == null) | ||||
throw new IllegalArgumentException("Interfaces must have a private key"); | |||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY, | |||||
Reason.MISSING_ATTRIBUTE, null); | |||||
return new Interface(this); | return new Interface(this); | ||||
} | } | ||||
@@ -276,57 +283,51 @@ public final class Interface { | |||||
return this; | return this; | ||||
} | } | ||||
public Builder parseAddresses(final CharSequence addresses) throws ParseException { | |||||
public Builder parseAddresses(final CharSequence addresses) throws BadConfigException { | |||||
try { | try { | ||||
final List<InetNetwork> parsed = Stream.of(Attribute.split(addresses)) | |||||
.map(InetNetwork::parse) | |||||
.collect(Collectors.toUnmodifiableList()); | |||||
return addAddresses(parsed); | |||||
} catch (final IllegalArgumentException e) { | |||||
throw new ParseException("Address", addresses, e); | |||||
for (final String address : Attribute.split(addresses)) | |||||
addAddress(InetNetwork.parse(address)); | |||||
return this; | |||||
} catch (final ParseException e) { | |||||
throw new BadConfigException(Section.INTERFACE, Location.ADDRESS, e); | |||||
} | } | ||||
} | } | ||||
public Builder parseDnsServers(final CharSequence dnsServers) throws ParseException { | |||||
public Builder parseDnsServers(final CharSequence dnsServers) throws BadConfigException { | |||||
try { | try { | ||||
final List<InetAddress> parsed = Stream.of(Attribute.split(dnsServers)) | |||||
.map(InetAddresses::parse) | |||||
.collect(Collectors.toUnmodifiableList()); | |||||
return addDnsServers(parsed); | |||||
} catch (final IllegalArgumentException e) { | |||||
throw new ParseException("DNS", dnsServers, e); | |||||
for (final String dnsServer : Attribute.split(dnsServers)) | |||||
addDnsServer(InetAddresses.parse(dnsServer)); | |||||
return this; | |||||
} catch (final ParseException e) { | |||||
throw new BadConfigException(Section.INTERFACE, Location.DNS, e); | |||||
} | } | ||||
} | } | ||||
public Builder parseExcludedApplications(final CharSequence apps) throws ParseException { | |||||
try { | |||||
return excludeApplications(Lists.of(Attribute.split(apps))); | |||||
} catch (final IllegalArgumentException e) { | |||||
throw new ParseException("ExcludedApplications", apps, e); | |||||
} | |||||
public Builder parseExcludedApplications(final CharSequence apps) { | |||||
return excludeApplications(Lists.of(Attribute.split(apps))); | |||||
} | } | ||||
public Builder parseListenPort(final String listenPort) throws ParseException { | |||||
public Builder parseListenPort(final String listenPort) throws BadConfigException { | |||||
try { | try { | ||||
return setListenPort(Integer.parseInt(listenPort)); | return setListenPort(Integer.parseInt(listenPort)); | ||||
} catch (final IllegalArgumentException e) { | |||||
throw new ParseException("ListenPort", listenPort, e); | |||||
} catch (final NumberFormatException e) { | |||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, listenPort, e); | |||||
} | } | ||||
} | } | ||||
public Builder parseMtu(final String mtu) throws ParseException { | |||||
public Builder parseMtu(final String mtu) throws BadConfigException { | |||||
try { | try { | ||||
return setMtu(Integer.parseInt(mtu)); | return setMtu(Integer.parseInt(mtu)); | ||||
} catch (final IllegalArgumentException e) { | |||||
throw new ParseException("MTU", mtu, e); | |||||
} catch (final NumberFormatException e) { | |||||
throw new BadConfigException(Section.INTERFACE, Location.MTU, mtu, e); | |||||
} | } | ||||
} | } | ||||
public Builder parsePrivateKey(final String privateKey) throws ParseException { | |||||
public Builder parsePrivateKey(final String privateKey) throws BadConfigException { | |||||
try { | try { | ||||
return setKeyPair(new KeyPair(Key.fromBase64(privateKey))); | return setKeyPair(new KeyPair(Key.fromBase64(privateKey))); | ||||
} catch (final Key.KeyFormatException e) { | |||||
throw new ParseException("PrivateKey", "(omitted)", e); | |||||
} catch (final KeyFormatException e) { | |||||
throw new BadConfigException(Section.INTERFACE, Location.PRIVATE_KEY, e); | |||||
} | } | ||||
} | } | ||||
@@ -335,16 +336,18 @@ public final class Interface { | |||||
return this; | return this; | ||||
} | } | ||||
public Builder setListenPort(final int listenPort) { | |||||
public Builder setListenPort(final int listenPort) throws BadConfigException { | |||||
if (listenPort < MIN_UDP_PORT || listenPort > MAX_UDP_PORT) | if (listenPort < MIN_UDP_PORT || listenPort > MAX_UDP_PORT) | ||||
throw new IllegalArgumentException("ListenPort must be a valid UDP port number"); | |||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, | |||||
Reason.INVALID_VALUE, String.valueOf(listenPort)); | |||||
this.listenPort = listenPort == 0 ? Optional.empty() : Optional.of(listenPort); | this.listenPort = listenPort == 0 ? Optional.empty() : Optional.of(listenPort); | ||||
return this; | return this; | ||||
} | } | ||||
public Builder setMtu(final int mtu) { | |||||
public Builder setMtu(final int mtu) throws BadConfigException { | |||||
if (mtu < 0) | if (mtu < 0) | ||||
throw new IllegalArgumentException("MTU must not be negative"); | |||||
throw new BadConfigException(Section.INTERFACE, Location.LISTEN_PORT, | |||||
Reason.INVALID_VALUE, String.valueOf(mtu)); | |||||
this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu); | this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu); | ||||
return this; | return this; | ||||
} | } | ||||
@@ -5,34 +5,37 @@ | |||||
package com.wireguard.config; | package com.wireguard.config; | ||||
import android.support.annotation.Nullable; | |||||
/** | /** | ||||
* An exception representing a failure to parse an element of a WireGuard configuration. The context | |||||
* for this failure can be retrieved with {@link #getContext}, and the text that failed to parse can | |||||
* be retrieved with {@link #getText}. | |||||
*/ | */ | ||||
public class ParseException extends Exception { | public class ParseException extends Exception { | ||||
private final String context; | |||||
private final Class<?> parsingClass; | |||||
private final CharSequence text; | private final CharSequence text; | ||||
public ParseException(final String context, final CharSequence text, final String message) { | |||||
super(message); | |||||
this.context = context; | |||||
public ParseException(final Class<?> parsingClass, final CharSequence text, | |||||
@Nullable final String message, @Nullable final Throwable cause) { | |||||
super(message, cause); | |||||
this.parsingClass = parsingClass; | |||||
this.text = text; | this.text = text; | ||||
} | } | ||||
public ParseException(final String context, final CharSequence text, final Throwable cause) { | |||||
super(cause.getMessage(), cause); | |||||
this.context = context; | |||||
this.text = text; | |||||
public ParseException(final Class<?> parsingClass, final CharSequence text, | |||||
@Nullable final String message) { | |||||
this(parsingClass, text, message, null); | |||||
} | } | ||||
public ParseException(final String context, final CharSequence text) { | |||||
this.context = context; | |||||
this.text = text; | |||||
public ParseException(final Class<?> parsingClass, final CharSequence text, | |||||
@Nullable final Throwable cause) { | |||||
this(parsingClass, text, null, cause); | |||||
} | |||||
public ParseException(final Class<?> parsingClass, final CharSequence text) { | |||||
this(parsingClass, text, null, null); | |||||
} | } | ||||
public String getContext() { | |||||
return context; | |||||
public Class<?> getParsingClass() { | |||||
return parsingClass; | |||||
} | } | ||||
public CharSequence getText() { | public CharSequence getText() { | ||||
@@ -7,19 +7,20 @@ package com.wireguard.config; | |||||
import android.support.annotation.Nullable; | import android.support.annotation.Nullable; | ||||
import com.wireguard.config.BadConfigException.Location; | |||||
import com.wireguard.config.BadConfigException.Reason; | |||||
import com.wireguard.config.BadConfigException.Section; | |||||
import com.wireguard.crypto.Key; | import com.wireguard.crypto.Key; | ||||
import com.wireguard.crypto.KeyFormatException; | |||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.LinkedHashSet; | import java.util.LinkedHashSet; | ||||
import java.util.List; | |||||
import java.util.Locale; | import java.util.Locale; | ||||
import java.util.Objects; | import java.util.Objects; | ||||
import java.util.Set; | import java.util.Set; | ||||
import java9.util.Optional; | import java9.util.Optional; | ||||
import java9.util.stream.Collectors; | |||||
import java9.util.stream.Stream; | |||||
/** | /** | ||||
* Represents the configuration for a WireGuard peer (a [Peer] block). Peers must have a public key, | * Represents the configuration for a WireGuard peer (a [Peer] block). Peers must have a public key, | ||||
@@ -50,11 +51,13 @@ public final class Peer { | |||||
* @param lines an iterable sequence of lines, containing at least a public key attribute | * @param lines an iterable sequence of lines, containing at least a public key attribute | ||||
* @return a {@code Peer} with all of its attributes set from {@code lines} | * @return a {@code Peer} with all of its attributes set from {@code lines} | ||||
*/ | */ | ||||
public static Peer parse(final Iterable<? extends CharSequence> lines) throws ParseException { | |||||
public static Peer parse(final Iterable<? extends CharSequence> lines) | |||||
throws BadConfigException { | |||||
final Builder builder = new Builder(); | final Builder builder = new Builder(); | ||||
for (final CharSequence line : lines) { | for (final CharSequence line : lines) { | ||||
final Attribute attribute = Attribute.parse(line) | |||||
.orElseThrow(() -> new ParseException("[Peer]", line, "Syntax error")); | |||||
final Attribute attribute = Attribute.parse(line).orElseThrow(() -> | |||||
new BadConfigException(Section.PEER, Location.TOP_LEVEL, | |||||
Reason.SYNTAX_ERROR, line)); | |||||
switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) { | switch (attribute.getKey().toLowerCase(Locale.ENGLISH)) { | ||||
case "allowedips": | case "allowedips": | ||||
builder.parseAllowedIPs(attribute.getValue()); | builder.parseAllowedIPs(attribute.getValue()); | ||||
@@ -72,7 +75,8 @@ public final class Peer { | |||||
builder.parsePublicKey(attribute.getValue()); | builder.parsePublicKey(attribute.getValue()); | ||||
break; | break; | ||||
default: | default: | ||||
throw new ParseException("[Peer]", line, "Unknown attribute"); | |||||
throw new BadConfigException(Section.PEER, Location.TOP_LEVEL, | |||||
Reason.UNKNOWN_ATTRIBUTE, attribute.getKey()); | |||||
} | } | ||||
} | } | ||||
return builder.build(); | return builder.build(); | ||||
@@ -223,52 +227,54 @@ public final class Peer { | |||||
return this; | return this; | ||||
} | } | ||||
public Peer build() { | |||||
public Peer build() throws BadConfigException { | |||||
if (publicKey == null) | if (publicKey == null) | ||||
throw new IllegalArgumentException("Peers must have a public key"); | |||||
throw new BadConfigException(Section.PEER, Location.PUBLIC_KEY, | |||||
Reason.MISSING_ATTRIBUTE, null); | |||||
return new Peer(this); | return new Peer(this); | ||||
} | } | ||||
public Builder parseAllowedIPs(final CharSequence allowedIps) throws ParseException { | |||||
public Builder parseAllowedIPs(final CharSequence allowedIps) throws BadConfigException { | |||||
try { | try { | ||||
final List<InetNetwork> parsed = Stream.of(Attribute.split(allowedIps)) | |||||
.map(InetNetwork::parse) | |||||
.collect(Collectors.toUnmodifiableList()); | |||||
return addAllowedIps(parsed); | |||||
} catch (final IllegalArgumentException e) { | |||||
throw new ParseException("AllowedIPs", allowedIps, e); | |||||
for (final String allowedIp : Attribute.split(allowedIps)) | |||||
addAllowedIp(InetNetwork.parse(allowedIp)); | |||||
return this; | |||||
} catch (final ParseException e) { | |||||
throw new BadConfigException(Section.PEER, Location.ALLOWED_IPS, e); | |||||
} | } | ||||
} | } | ||||
public Builder parseEndpoint(final String endpoint) throws ParseException { | |||||
public Builder parseEndpoint(final String endpoint) throws BadConfigException { | |||||
try { | try { | ||||
return setEndpoint(InetEndpoint.parse(endpoint)); | return setEndpoint(InetEndpoint.parse(endpoint)); | ||||
} catch (final IllegalArgumentException e) { | |||||
throw new ParseException("Endpoint", endpoint, e); | |||||
} catch (final ParseException e) { | |||||
throw new BadConfigException(Section.PEER, Location.ENDPOINT, e); | |||||
} | } | ||||
} | } | ||||
public Builder parsePersistentKeepalive(final String persistentKeepalive) throws ParseException { | |||||
public Builder parsePersistentKeepalive(final String persistentKeepalive) | |||||
throws BadConfigException { | |||||
try { | try { | ||||
return setPersistentKeepalive(Integer.parseInt(persistentKeepalive)); | return setPersistentKeepalive(Integer.parseInt(persistentKeepalive)); | ||||
} catch (final IllegalArgumentException e) { | |||||
throw new ParseException("PersistentKeepalive", persistentKeepalive, e); | |||||
} catch (final NumberFormatException e) { | |||||
throw new BadConfigException(Section.PEER, Location.PERSISTENT_KEEPALIVE, | |||||
persistentKeepalive, e); | |||||
} | } | ||||
} | } | ||||
public Builder parsePreSharedKey(final String preSharedKey) throws ParseException { | |||||
public Builder parsePreSharedKey(final String preSharedKey) throws BadConfigException { | |||||
try { | try { | ||||
return setPreSharedKey(Key.fromBase64(preSharedKey)); | return setPreSharedKey(Key.fromBase64(preSharedKey)); | ||||
} catch (final Key.KeyFormatException e) { | |||||
throw new ParseException("PresharedKey", preSharedKey, e); | |||||
} catch (final KeyFormatException e) { | |||||
throw new BadConfigException(Section.PEER, Location.PRE_SHARED_KEY, e); | |||||
} | } | ||||
} | } | ||||
public Builder parsePublicKey(final String publicKey) throws ParseException { | |||||
public Builder parsePublicKey(final String publicKey) throws BadConfigException { | |||||
try { | try { | ||||
return setPublicKey(Key.fromBase64(publicKey)); | return setPublicKey(Key.fromBase64(publicKey)); | ||||
} catch (final Key.KeyFormatException e) { | |||||
throw new ParseException("PublicKey", publicKey, e); | |||||
} catch (final KeyFormatException e) { | |||||
throw new BadConfigException(Section.PEER, Location.PUBLIC_KEY, e); | |||||
} | } | ||||
} | } | ||||
@@ -277,9 +283,11 @@ public final class Peer { | |||||
return this; | return this; | ||||
} | } | ||||
public Builder setPersistentKeepalive(final int persistentKeepalive) { | |||||
public Builder setPersistentKeepalive(final int persistentKeepalive) | |||||
throws BadConfigException { | |||||
if (persistentKeepalive < 0 || persistentKeepalive > MAX_PERSISTENT_KEEPALIVE) | if (persistentKeepalive < 0 || persistentKeepalive > MAX_PERSISTENT_KEEPALIVE) | ||||
throw new IllegalArgumentException("Invalid value for PersistentKeepalive"); | |||||
throw new BadConfigException(Section.PEER, Location.PERSISTENT_KEEPALIVE, | |||||
Reason.INVALID_VALUE, String.valueOf(persistentKeepalive)); | |||||
this.persistentKeepalive = persistentKeepalive == 0 ? | this.persistentKeepalive = persistentKeepalive == 0 ? | ||||
Optional.empty() : Optional.of(persistentKeepalive); | Optional.empty() : Optional.of(persistentKeepalive); | ||||
return this; | return this; | ||||
@@ -5,6 +5,9 @@ | |||||
package com.wireguard.crypto; | package com.wireguard.crypto; | ||||
import com.wireguard.crypto.KeyFormatException.Type; | |||||
import java.security.SecureRandom; | |||||
import java.util.Arrays; | import java.util.Arrays; | ||||
/** | /** | ||||
@@ -83,10 +86,10 @@ public final class Key { | |||||
* @param str the base64 string representation of a WireGuard key | * @param str the base64 string representation of a WireGuard key | ||||
* @return the decoded key encapsulated in an immutable container | * @return the decoded key encapsulated in an immutable container | ||||
*/ | */ | ||||
public static Key fromBase64(final String str) { | |||||
public static Key fromBase64(final String str) throws KeyFormatException { | |||||
final char[] input = str.toCharArray(); | final char[] input = str.toCharArray(); | ||||
if (input.length != Format.BASE64.length || input[Format.BASE64.length - 1] != '=') | if (input.length != Format.BASE64.length || input[Format.BASE64.length - 1] != '=') | ||||
throw new KeyFormatException(Format.BASE64); | |||||
throw new KeyFormatException(Format.BASE64, Type.LENGTH); | |||||
final byte[] key = new byte[Format.BINARY.length]; | final byte[] key = new byte[Format.BINARY.length]; | ||||
int i; | int i; | ||||
int ret = 0; | int ret = 0; | ||||
@@ -109,7 +112,7 @@ public final class Key { | |||||
key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff); | key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff); | ||||
if (ret != 0) | if (ret != 0) | ||||
throw new KeyFormatException(Format.BASE64); | |||||
throw new KeyFormatException(Format.BASE64, Type.CONTENTS); | |||||
return new Key(key); | return new Key(key); | ||||
} | } | ||||
@@ -120,9 +123,9 @@ public final class Key { | |||||
* @param bytes an array of bytes containing a WireGuard key in binary format | * @param bytes an array of bytes containing a WireGuard key in binary format | ||||
* @return the key encapsulated in an immutable container | * @return the key encapsulated in an immutable container | ||||
*/ | */ | ||||
public static Key fromBytes(final byte[] bytes) { | |||||
public static Key fromBytes(final byte[] bytes) throws KeyFormatException { | |||||
if (bytes.length != Format.BINARY.length) | if (bytes.length != Format.BINARY.length) | ||||
throw new KeyFormatException(Format.BINARY); | |||||
throw new KeyFormatException(Format.BINARY, Type.LENGTH); | |||||
return new Key(bytes); | return new Key(bytes); | ||||
} | } | ||||
@@ -133,10 +136,10 @@ public final class Key { | |||||
* @param str the hexadecimal string representation of a WireGuard key | * @param str the hexadecimal string representation of a WireGuard key | ||||
* @return the decoded key encapsulated in an immutable container | * @return the decoded key encapsulated in an immutable container | ||||
*/ | */ | ||||
public static Key fromHex(final String str) { | |||||
public static Key fromHex(final String str) throws KeyFormatException { | |||||
final char[] input = str.toCharArray(); | final char[] input = str.toCharArray(); | ||||
if (input.length != Format.HEX.length) | if (input.length != Format.HEX.length) | ||||
throw new KeyFormatException(Format.HEX); | |||||
throw new KeyFormatException(Format.HEX, Type.LENGTH); | |||||
final byte[] key = new byte[Format.BINARY.length]; | final byte[] key = new byte[Format.BINARY.length]; | ||||
int ret = 0; | int ret = 0; | ||||
for (int i = 0; i < key.length; ++i) { | for (int i = 0; i < key.length; ++i) { | ||||
@@ -167,10 +170,37 @@ public final class Key { | |||||
key[i] = (byte) (cAcc | cVal); | key[i] = (byte) (cAcc | cVal); | ||||
} | } | ||||
if (ret != 0) | if (ret != 0) | ||||
throw new KeyFormatException(Format.HEX); | |||||
throw new KeyFormatException(Format.HEX, Type.CONTENTS); | |||||
return new Key(key); | return new Key(key); | ||||
} | } | ||||
/** | |||||
* Generates a private key using the system's {@link SecureRandom} number generator. | |||||
* | |||||
* @return a well-formed random private key | |||||
*/ | |||||
static Key generatePrivateKey() { | |||||
final SecureRandom secureRandom = new SecureRandom(); | |||||
final byte[] privateKey = new byte[Format.BINARY.getLength()]; | |||||
secureRandom.nextBytes(privateKey); | |||||
privateKey[0] &= 248; | |||||
privateKey[31] &= 127; | |||||
privateKey[31] |= 64; | |||||
return new Key(privateKey); | |||||
} | |||||
/** | |||||
* Generates a public key from an existing private key. | |||||
* | |||||
* @param privateKey a private key | |||||
* @return a well-formed public key that corresponds to the supplied private key | |||||
*/ | |||||
static Key generatePublicKey(final Key privateKey) { | |||||
final byte[] publicKey = new byte[Format.BINARY.getLength()]; | |||||
Curve25519.eval(publicKey, 0, privateKey.getBytes(), null); | |||||
return new Key(publicKey); | |||||
} | |||||
/** | /** | ||||
* Returns the key as an array of bytes. | * Returns the key as an array of bytes. | ||||
* | * | ||||
@@ -236,20 +266,4 @@ public final class Key { | |||||
} | } | ||||
} | } | ||||
/** | |||||
* An exception thrown when attempting to parse an invalid key (too short, too long, or byte | |||||
* data inappropriate for the format). The format being parsed can be accessed with the | |||||
* {@link #getFormat} method. | |||||
*/ | |||||
public static final class KeyFormatException extends RuntimeException { | |||||
private final Format format; | |||||
private KeyFormatException(final Format format) { | |||||
this.format = format; | |||||
} | |||||
public Format getFormat() { | |||||
return format; | |||||
} | |||||
} | |||||
} | } |
@@ -0,0 +1,34 @@ | |||||
/* | |||||
* Copyright © 2018 WireGuard LLC. All Rights Reserved. | |||||
* SPDX-License-Identifier: Apache-2.0 | |||||
*/ | |||||
package com.wireguard.crypto; | |||||
/** | |||||
* An exception thrown when attempting to parse an invalid key (too short, too long, or byte | |||||
* data inappropriate for the format). The format being parsed can be accessed with the | |||||
* {@link #getFormat} method. | |||||
*/ | |||||
public final class KeyFormatException extends Exception { | |||||
private final Key.Format format; | |||||
private final Type type; | |||||
KeyFormatException(final Key.Format format, final Type type) { | |||||
this.format = format; | |||||
this.type = type; | |||||
} | |||||
public Key.Format getFormat() { | |||||
return format; | |||||
} | |||||
public Type getType() { | |||||
return type; | |||||
} | |||||
public enum Type { | |||||
CONTENTS, | |||||
LENGTH | |||||
} | |||||
} |
@@ -5,8 +5,6 @@ | |||||
package com.wireguard.crypto; | package com.wireguard.crypto; | ||||
import java.security.SecureRandom; | |||||
/** | /** | ||||
* Represents a Curve25519 key pair as used by WireGuard. | * Represents a Curve25519 key pair as used by WireGuard. | ||||
* <p> | * <p> | ||||
@@ -20,7 +18,7 @@ public class KeyPair { | |||||
* Creates a key pair using a newly-generated private key. | * Creates a key pair using a newly-generated private key. | ||||
*/ | */ | ||||
public KeyPair() { | public KeyPair() { | ||||
this(generatePrivateKey()); | |||||
this(Key.generatePrivateKey()); | |||||
} | } | ||||
/** | /** | ||||
@@ -30,35 +28,7 @@ public class KeyPair { | |||||
*/ | */ | ||||
public KeyPair(final Key privateKey) { | public KeyPair(final Key privateKey) { | ||||
this.privateKey = privateKey; | this.privateKey = privateKey; | ||||
publicKey = generatePublicKey(privateKey); | |||||
} | |||||
/** | |||||
* Generates a private key using the system's {@link SecureRandom} number generator. | |||||
* | |||||
* @return a well-formed random private key | |||||
*/ | |||||
@SuppressWarnings("MagicNumber") | |||||
private static Key generatePrivateKey() { | |||||
final SecureRandom secureRandom = new SecureRandom(); | |||||
final byte[] privateKey = new byte[Key.Format.BINARY.getLength()]; | |||||
secureRandom.nextBytes(privateKey); | |||||
privateKey[0] &= 248; | |||||
privateKey[31] &= 127; | |||||
privateKey[31] |= 64; | |||||
return Key.fromBytes(privateKey); | |||||
} | |||||
/** | |||||
* Generates a public key from an existing private key. | |||||
* | |||||
* @param privateKey a private key | |||||
* @return a well-formed public key that corresponds to the supplied private key | |||||
*/ | |||||
private static Key generatePublicKey(final Key privateKey) { | |||||
final byte[] publicKey = new byte[Key.Format.BINARY.getLength()]; | |||||
Curve25519.eval(publicKey, 0, privateKey.getBytes(), null); | |||||
return Key.fromBytes(publicKey); | |||||
publicKey = Key.generatePublicKey(privateKey); | |||||
} | } | ||||
/** | /** | ||||