Легка інтеграція tor в android додаток на прикладі клієнта для рутрекера

Мені давно було цікаво, можна легко додати проксіювання через тор в Android додаток. Начебто досить очевидна завдання, плюс тор браузери вже під цю платформу давно є… Але є багато завдань, які складніше, ніж здаються. Для нетерплячих відразу скажу — так, можна, і виходить досить легко, швидко і класно. Особливо якщо не копати з нуля, а скористатися моїми напрацюваннями.

Для прикладу я буду використовувати додаток для роботи з рутрекером — ніхто не любить код, який працює з сферичний конем у вакуумі. Раніше це додаток обходило блокування за допомогою Google Compression Proxy — але на жаль — то рутрекер, то гугл випиляв можливість авторизації з цієї проксі. Відразу скажу, що, звичайно, є всякі впны та інше, що ви використовуєте для легкого обходу блокування і перегляду сериальчиков. Але мова тут йде не про це. Як ви розумієте, тор можна використовувати в мобільному додатку для величезної кількості речей — наприклад, для доступу до веб сайтів в .onion або для реалізації особливо безпечного месенджера.

Як підключити бібліотеку для роботи з Тором
Як зібрати з нуля
Якщо вас не цікавить збірка з нуля, то перейдіть до наступного заголовку.

Отже, що у нас є на цю тему з готового інструментарію. Є особливий репозиторій від якихось хлопців під проводом Microsoft (посилання в підвалі). Начебто у них все працювало — але якість і механізм складання просто жахають. А ще репозиторій застарів на два роки. І скомпільовані версії бібліотеки там немає, є тільки досить стрьомні інструкції щодо того, як зібрати її самостійно (в стилі — «я робив так, не знаю чому, але без цього нічого не працювало»). Втім, наявних інструкцій цілком достатньо для того, щоб оновити код до актуального стану і виправити всі дивні косяки.

  1. Клонируем собі цей репозиторій.

  2. Оновлюємо там компонент, який відповідає за управління тором — jtorctl. Вони використовували форк основного репозиторію з правками від briar, але ці правки вже включені в основний репозиторій, так що краще взяти з основного. Можна підключати з maven репозиторію, але я такі речі зазвичай забираю першоджерелом — можна відразу подивитися, прогнати аналіз і правити на льоту баги — проект досить сирий, незважаючи на вік.

  3. Оновлюємо geoip і geoip6 — бази даних блоків IP-адрес з прив'язкою до географічному положенню кожного блоку для версій IPv4 і IPv6 відповідно. Для цього завантажуємо на сайті тора windows expert bundle.

  4. Оновлюємо сам тор (тобто нативну бібліотеку). Стандартної загальнодоступній немає (ну або я погано шукав) — так що йдемо до хлопців, які розробляють тор і тор браузер під андроїд (Orbot і Orfox), беремо останній реліз їх Orbot і виймаємо звідти бібліотеку. Тор там досить свіжий, що приємно.

  5. Правимо руками все, що перестало компілюватися в нашому проекті. Кілька функцій залежних бібліотеках змінилися, але в цілому все інтуїтивно зрозуміло і можна виправити за 5 хвилин.

  6. Слідуючи рекомендаціям рідмі нашого проекту, створюємо локальні мавен репозиторії і будуємо з купи кусків наш проект. До речі, зверніть увагу, що білд скрипт настільки кривої, що в одному місці включає в себе попередній реліз себе. Жуть. Так що рекомендую переписати його заново, простою і зрозумілою мовою, щоб отримати на виході звичайну бібліотеку aar.
Як зібрати з моїх напрацювань
Пункти 1-6 я вже зробив за вас, так що просто збирати бібліотеку з мого репозиторію, або скачайте її в секції релізів. Посилання буде в «підвалі» посту. Однак звертаю увагу, що правильним буде перевірити код і бібліотеки на відповідність оригінальним і відсутність закладок. Не варто такі речі додавати наосліп в свої додатки.

Як перестати хвилюватися і почати проксировать через тор
Спочатку потрібно включити тор:

int totalSecondsPerTorStartup = 4 * 60;
int totalTriesPerTorStartup = 5;
try {
boolean ok = onionProxyManager.startWithRepeat(totalSecondsPerTorStartup, totalTriesPerTorStartup);
if (!ok)
Log.e("TorTest", "couldn't start Tor!");
}
catch (InterruptedException | IOException e) {
e.printStackTrace();
}

Потім почекати, поки він подцепится:

while (!onionProxyManager.isRunning())
Thread.sleep(90);

Якщо все пройшло успішно — ура, він слухає у нас localhost на якомусь випадковому порту:

Log.v("My App", "Tor initialized on port " + onionProxyManager.getIPv4LocalHostSocksPort());

Але це ще не все. У нас тепер є тор, який слухає порт в якості Socks4a проксі. Однак далеко не всі стандартні бібліотеки вміють працювати з Socks4a. Там з міркувань анонімності потрібно, щоб резолв хоста відбувався на проксі, а не раніше. Не знаю, які з стандартних бібліотек це вміють, і у мене був код, написаний з Apache HttpComponents. Я вже писав раніше, чому їх можна використовувати, так і даний пост не про те. Якщо ви хочете, то можете реалізувати те ж саме на будь-якій іншій бібліотеці.

Отже, для використання httpComponents нам потрібно переписати ConnectionSocketFactory і SSLConnectionSocketFactory.

SSLConnectionSocketFactory
public class MySSLConnectionSocketFactory extends SSLConnectionSocketFactory {

public MySSLConnectionSocketFactory(final SSLContext sslContext) {
super(sslContext);
}

@Override
public Socket createSocket(final HttpContext context) throws IOException {
return new Socket();
}

@Override
public Socket connectSocket(
int connectTimeout,
Socket socket,
final HttpHost host,
final InetSocketAddress remoteAddress,
final InetSocketAddress localAddress,
final HttpContext context) throws IOException {
Args.notNull(host, "HTTP host");
Args.notNull(remoteAddress, "Remote address");
InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
socket = new Socket();
connectTimeout = 100000;
socket.setSoTimeout(connectTimeout);
socket.connect(new InetSocketAddress(socksaddr.getHostName(), socksaddr.getPort()), connectTimeout);
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
outputStream.write((byte) 0x04);
outputStream.write((byte) 0x01);
outputStream.writeShort((short) host.getPort());
outputStream.writeInt(0x01);
outputStream.write((byte) 0x00);
outputStream.write(host.getHostName().getBytes());
outputStream.write((byte) 0x00);

DataInputStream inputStream = new DataInputStream(socket.getInputStream());
if (inputStream.readByte() != (byte) 0x00 || inputStream.readByte() != (byte) 0x5a) {
throw new IOException("SOCKS4a connect failed");
} else
Log.v("SSLConnectionSF", "SOCKS4a connect ok!");
inputStream.readShort();
inputStream.readInt();

SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createLayeredSocket(socket, host.getHostName(), host.getPort(), context);
prepareSocket(sslSocket);
return sslSocket;
}

}


ConnectionSocketFactory
public class MyConnectionSocketFactory implements ConnectionSocketFactory {

@Override
public Socket createSocket(final HttpContext context) throws IOException {
return new Socket();
}

@Override
public Socket connectSocket(
int connectTimeout,
Socket socket,
final HttpHost host,
final InetSocketAddress remoteAddress,
final InetSocketAddress localAddress,
final HttpContext context) throws IOException, ConnectTimeoutException {

InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
socket = new Socket();
connectTimeout = 100000;
socket.setSoTimeout(connectTimeout);
socket.connect(new InetSocketAddress(socksaddr.getHostName(), socksaddr.getPort()), connectTimeout);


DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
outputStream.write((byte) 0x04);
outputStream.write((byte) 0x01);
outputStream.writeShort((short) host.getPort());
outputStream.writeInt(0x01);
outputStream.write((byte) 0x00);
outputStream.write(host.getHostName().getBytes());
outputStream.write((byte) 0x00);

DataInputStream inputStream = new DataInputStream(socket.getInputStream());
if (inputStream.readByte() != (byte) 0x00 || inputStream.readByte() != (byte) 0x5a) {
throw new IOException("SOCKS4a connect failed");
} else
Log.v("SSLConnectionSF", "SOCKS4a connect ok!");
inputStream.readShort();
inputStream.readInt();
return socket;
}
}


Використовувати ці фабрики легко і просто. Для цього потрібно створити HttpClient, який використовує ці бібліотеки:

public HttpClient getNewHttpClient() {

Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new MyConnectionSocketFactory())
.register("https", new MySSLConnectionSocketFactory(SSLContexts.createSystemDefault()))
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
return HttpClients.custom()
.setConnectionManager(cm)
.build();
}

І вказати йому наш проксі сервер:

HttpClient cli = getNewHttpClient();
int port = onionProxyManager.getIPv4LocalHostSocksPort();
InetSocketAddress socksaddr = new InetSocketAddress("127.0.0.1", port);
HttpClientContext context = HttpClientContext.create();
context.setAttribute("socks.address", socksaddr);

Все, тепер ми можемо використовувати тор так само, як якщо б робили звичайні запити. Більше того, ми можемо так само звертатися і до веб сайтів .onion.

Результат
Отриманий код я використав у своєму додатку для рутрекера. Так, ініціалізація тора займає близько 20 секунд, і сторінки вантажаться не так швидко — але зате ми гарантовано проходимо блокування. А всі ресурси, які не блоковані, завантажуються через звичайне з'єднання. Можна було б інші ресурси пропускати через Google Compression Proxy, але багато скаржилися, що у них заблокований цей проксі — так що я не став цього робити. Звичайно, у додатку можна було б ще багато всього зробити — наприклад, кешувати статику на телефоні для економії трафіку і більш швидкої роботи — але це не настільки критично, так і додаток я писав скоріше для прикладу.

Висновок
Тор на андроїд — класна і зручна штука, яка досить працює, і її справді можна використовувати в своїх додатках. До речі так, є набагато більш легкий спосіб це робити — просто вимагати встановлення Orbot, який сам підніме вам тор. Але мені не подобаються залежності одних додатків від інших, так і 3 зайвих мегабайта не так критичні в розмірі програми. Так що якщо комусь сподобалося моє рішення — користуйтеся, робіть пулл реквесты, і нехай буде з вами свобода.

Посилання:
  1. Початкова бібліотека;
  2. Моя збірка бібліотеки;
  3. Додаток для рутрекера;
  4. Guardian Project — хлопці, яким ми зобов'язані наявністю нативної тор бібліотеки.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.