Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tor/Onion code review changes #24

Merged
merged 2 commits into from
Mar 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 40 additions & 40 deletions core/src/main/java/org/bitcoinj/core/PeerAddress.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
package org.bitcoinj.core;

import com.google.common.net.InetAddresses;
import org.bitcoinj.net.AddressChecker;
import org.bitcoinj.net.OnionCat;
import org.bitcoinj.net.OnionCatAddressChecker;
import org.bitcoinj.net.OnionCatConverter;
import org.bitcoinj.params.MainNetParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -46,8 +46,10 @@ public class PeerAddress extends ChildMessage {

static final int MESSAGE_SIZE = 30;

//addr and hostname are alternatives and should not be used together.
private InetAddress addr;
private String hostname; // Used for .onion addresses
private String hostname;

private int port;
private BigInteger services;
private long time;
Expand Down Expand Up @@ -117,25 +119,14 @@ public PeerAddress(NetworkParameters params, InetAddress addr) {
/**
* Constructs a peer address from an {@link InetSocketAddress}. An InetSocketAddress can take in as parameters an
* InetAddress or a String hostname. If you want to connect to a .onion, set the hostname to the .onion address.
* Protocol version is the default. Protocol version is the default
* for Bitcoin.
* Protocol version is the default for Bitcoin.
*/
public PeerAddress(InetSocketAddress addr) {
/* socks addresses, eg Tor, use hostname only because no local lookup is performed.
* includes .onion hidden services.
*/
String host = addr.getHostString();
if( host != null && host.endsWith(".onion") ) {
this.hostname = host;
try {
this.addr = OnionCat.onionHostToInetAddress(this.hostname);
}
catch (UnknownHostException e) {
log.warn( "Invalid format for onion address: {}", this.hostname );
}
}
else {
this.addr = checkNotNull(addr.getAddress());
InetAddress inetAddress = addr.getAddress();
if(inetAddress != null) {
this.addr = inetAddress;
} else {
this.hostname = checkNotNull(addr.getHostString());
}
this.port = addr.getPort();
this.protocolVersion = NetworkParameters.ProtocolVersion.CURRENT.getBitcoinProtocolVersion();
Expand All @@ -148,11 +139,21 @@ public PeerAddress(InetSocketAddress addr) {
* InetAddress or a String hostname. If you want to connect to a .onion, set the hostname to the .onion address.
*/
public PeerAddress(NetworkParameters params, InetSocketAddress addr) {
this(params, addr.getAddress(), addr.getPort());
super(params);
InetAddress inetAddress = addr.getAddress();
if(inetAddress != null) {
this.addr = inetAddress;
} else {
this.hostname = checkNotNull(addr.getHostString());
}
this.port = addr.getPort();
this.protocolVersion = params.getProtocolVersionNum(NetworkParameters.ProtocolVersion.CURRENT);
this.services = BigInteger.ZERO;
length = protocolVersion > 31402 ? MESSAGE_SIZE : MESSAGE_SIZE - 4;
}

/**
* Constructs a peer address from a stringified hostname+port. Use this if you want to connect to a Tor .onion address.
* Constructs a peer address from a stringified hostname+port.
* Protocol version is the default for Bitcoin.
*/
public PeerAddress(String hostname, int port) {
Expand All @@ -163,7 +164,7 @@ public PeerAddress(String hostname, int port) {
}

/**
* Constructs a peer address from a stringified hostname+port. Use this if you want to connect to a Tor .onion address.
* Constructs a peer address from a stringified hostname+port.
*/
public PeerAddress(NetworkParameters params, String hostname, int port) {
super(params);
Expand All @@ -188,12 +189,14 @@ protected void bitcoinSerializeToStream(OutputStream stream) throws IOException
}
uint64ToByteStreamLE(services, stream); // nServices.

AddressChecker addrChecker = new AddressChecker();
byte[] ipBytes;
if( addrChecker.IsOnionCatTor( addr ) ) {
ipBytes = OnionCat.onionHostToIPV6Bytes(hostname);
}
else if( addr != null ) {
if (hostname!=null) {
if (hostname.endsWith(".onion")) {
ipBytes = OnionCatConverter.onionHostToIPV6Bytes(hostname);
} else {
ipBytes = new byte[16];
}
} else if( addr != null ) {
// Java does not provide any utility to map an IPv4 address into IPv6 space, so we have to do it by hand.
ipBytes = addr.getAddress();
if (ipBytes.length == 4) {
Expand All @@ -203,12 +206,8 @@ else if( addr != null ) {
v6addr[11] = (byte) 0xFF;
ipBytes = v6addr;
}
else {
ipBytes = new byte[16];
}
}
else {
ipBytes = new byte[16]; // zero-filled.
} else {
throw new IllegalStateException("Either hostname or addr should be not null");
}
stream.write(ipBytes);
// And write out the port. Unlike the rest of the protocol, address and port is in big endian byte order.
Expand All @@ -229,16 +228,17 @@ protected void parse() throws ProtocolException {
time = -1;
services = readUint64();
byte[] addrBytes = readBytes(16);
AddressChecker addrChecker = new AddressChecker();
InetAddress inetAddress;
try {
addr = InetAddress.getByAddress(addrBytes);

if( addrChecker.IsOnionCatTor( addr )) {
hostname = OnionCat.IPV6BytesToOnionHost( addr.getAddress() );
}
inetAddress = InetAddress.getByAddress(addrBytes);
} catch (UnknownHostException e) {
throw new RuntimeException(e); // Cannot happen.
}
if(OnionCatAddressChecker.isOnionCatTor(inetAddress)) {
hostname = OnionCatConverter.IPV6BytesToOnionHost(inetAddress.getAddress());
} else {
addr = inetAddress;
}
port = ((0xFF & payload[cursor++]) << 8) | (0xFF & payload[cursor++]);
// The 4 byte difference is the uint32 timestamp that was introduced in version 31402
length = protocolVersion > 31402 ? MESSAGE_SIZE : MESSAGE_SIZE - 4;
Expand Down
71 changes: 0 additions & 71 deletions core/src/main/java/org/bitcoinj/net/AddressChecker.java

This file was deleted.

40 changes: 40 additions & 0 deletions core/src/main/java/org/bitcoinj/net/OnionCatAddressChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.bitcoinj.net;

import org.bitcoinj.utils.CIDRUtils;

import java.net.InetAddress;

/**
* Checks an IPv6 InetAddress represents a valid OnionCat address
* @author danda
* @author Oscar Guindzberg
*/
public class OnionCatAddressChecker {

// Note: this is borrowed/ported from btcd (written in go).

// btcd has many more rules that are probably important and should be
// implemented in this class, but for now we only care about onion
// addresses for onioncat (ipv6) encoding/decoding.

// onionCatNet defines the IPv6 address block used to support Tor.
// bitcoind encodes a .onion address as a 16 byte number by decoding the
// address prior to the .onion (i.e. the key hash) base32 into a ten
// byte number. It then stores the first 6 bytes of the address as
// 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43.
//
// This is the same range used by OnionCat, which is part part of the
// RFC4193 unique local IPv6 range.
//
// In summary the format is:
// { magic 6 bytes, 10 bytes base32 decode of key hash }
private static CIDRUtils onionCatNet = new CIDRUtils("fd87:d87e:eb43::", 48);

// isOnionCatTor returns whether or not the passed address is in the IPv6 range
// used by bitcoin to support Tor (fd87:d87e:eb43::/48). Note that this range
// is the same range used by OnionCat, which is part of the RFC4193 unique local
// IPv6 range.
public static boolean isOnionCatTor(InetAddress addr) {
return onionCatNet.isInRange(addr);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.bitcoinj.net;

import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.Base32;

import java.net.InetAddress;
Expand All @@ -9,19 +10,28 @@


/**
* Created by danda on 1/12/17.
* Converts .onion addresses to IPv6 format and viceversa.
* @author danda
* @author Oscar Guindzberg
*/
public class OnionCat {
public class OnionCatConverter {

/** Converts a .onion address to onioncat format
*
* @param hostname
* @return
* @param hostname e.g. explorernuoc63nb.onion
* @return e.g. fd87:d87e:eb43:25de:b744:916d:1c2f:6da1
*/
public static byte[] onionHostToIPV6Bytes(String hostname) {
String needle = ".onion";
if( hostname.endsWith(needle) ) {
hostname = hostname.substring(0,hostname.length() - needle.length());
if(hostname.endsWith(needle)) {
if (hostname.length() != 22) {
throw new IllegalArgumentException("Invalid hostname: " + hostname);
}
hostname = hostname.substring(0, hostname.length() - needle.length());
} else {
if (hostname.length() != 16) {
throw new IllegalArgumentException("Invalid hostname: " + hostname);
}
}
byte[] prefix = new byte[] {(byte)0xfd, (byte)0x87, (byte)0xd8, (byte)0x7e, (byte)0xeb, (byte)0x43};
byte[] onionaddr = Base32.base32Decode(hostname);
Expand All @@ -36,17 +46,15 @@ public static InetAddress onionHostToInetAddress(String hostname) throws Unknown
return InetAddress.getByAddress(onionHostToIPV6Bytes(hostname));
}

public static InetSocketAddress onionHostToInetSocketAddress(String hostname, int port) throws UnknownHostException {
return new InetSocketAddress( onionHostToInetAddress(hostname), port);
}


/** Converts an IPV6 onioncat encoded address to a hostname
*
* @param bytes
* @return
/** Converts an IPV6 onioncat encoded address to a .onion address
* @param bytes e.g. fd87:d87e:eb43:25de:b744:916d:1c2f:6da1
* @return e.g. explorernuoc63nb.onion
*/
public static String IPV6BytesToOnionHost( byte[] bytes) {
public static String IPV6BytesToOnionHost(byte[] bytes) {
if (bytes.length != 16) {
throw new IllegalArgumentException("Invalid IPv6 address: " + Utils.HEX.encode(bytes));
}
String base32 = Base32.base32Encode( Arrays.copyOfRange(bytes, 6, 16) );
return base32.toLowerCase() + ".onion";
}
Expand Down
Loading