Skip to content

Commit

Permalink
Merge pull request #941 from alvasw/implement_DirectoryAuthorityFactory
Browse files Browse the repository at this point in the history
Implement DirectoryAuthorityFactory
  • Loading branch information
alvasw committed Jun 19, 2023
2 parents cc68bc5 + 8aceaf8 commit 38177ea
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.tor.local_network;

import bisq.common.util.NetworkUtils;
import bisq.tor.local_network.torrc.DirectoryAuthorityTorrcGenerator;
import bisq.tor.local_network.torrc.TorrcFileGenerator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

public class DirectoryAuthorityTests {

@Test
public void createOneDA(@TempDir Path tempDir) throws IOException, InterruptedException {
var firstDirectoryAuthority = DirectoryAuthority.builder()
.nickname("DA_1")
.dataDir(tempDir)
.controlPort(NetworkUtils.findFreeSystemPort())
.orPort(NetworkUtils.findFreeSystemPort())
.dirPort(NetworkUtils.findFreeSystemPort())
.build();
DirectoryAuthorityFactory.createDirectoryAuthority(firstDirectoryAuthority, "my_passphrase");

assertThat(tempDir).isNotEmptyDirectory();
assertThat(tempDir.resolve("keys")).isNotEmptyDirectory();
}

@Test
public void createThreeDA(@TempDir Path tempDir) throws IOException, InterruptedException {
Path firstDaDataDir = tempDir.resolve("da_1");
var firstDirectoryAuthority = DirectoryAuthority.builder()
.nickname("DA_1")
.dataDir(firstDaDataDir)
.controlPort(NetworkUtils.findFreeSystemPort())
.orPort(NetworkUtils.findFreeSystemPort())
.dirPort(NetworkUtils.findFreeSystemPort())
.build();

Path secondDaDataDir = tempDir.resolve("da_2");
var secondDirectoryAuthority = DirectoryAuthority.builder()
.nickname("DA_2")
.dataDir(secondDaDataDir)
.controlPort(NetworkUtils.findFreeSystemPort())
.orPort(NetworkUtils.findFreeSystemPort())
.dirPort(NetworkUtils.findFreeSystemPort())
.build();

Path thirdDaDataDir = tempDir.resolve("da_3");
var thirdDirectoryAuthority = DirectoryAuthority.builder()
.nickname("DA_3")
.dataDir(thirdDaDataDir)
.controlPort(NetworkUtils.findFreeSystemPort())
.orPort(NetworkUtils.findFreeSystemPort())
.dirPort(NetworkUtils.findFreeSystemPort())
.build();

Set<DirectoryAuthority> allDAs = Set.of(
firstDirectoryAuthority,
secondDirectoryAuthority,
thirdDirectoryAuthority);

// Generate all keys to have fingerprints
for (DirectoryAuthority da : allDAs) {
DirectoryAuthorityFactory.createDirectoryAuthority(da, "my_passphrase");
}

// Fingerprints are now available
for (DirectoryAuthority da : allDAs) {
var torDaTorrcGenerator = new DirectoryAuthorityTorrcGenerator(da);
var torrcFileGenerator = new TorrcFileGenerator(torDaTorrcGenerator, allDAs);
torrcFileGenerator.generate();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.nio.file.Path;
import java.util.Optional;

@Builder
@Getter
public class DirectoryAuthority {
private final String nickname;
Expand All @@ -33,11 +34,22 @@ public class DirectoryAuthority {
private final int orPort;
private final int dirPort;

private final String v3LongTermSigningKeyFingerprint;
private final String torKeyFingerprint;

private final String exitPolicy = "ExitPolicy accept *:*";

@Setter
private Optional<String> identityKeyFingerprint = Optional.empty();
@Setter
private Optional<String> relayKeyFingerprint = Optional.empty();

@Builder
public DirectoryAuthority(String nickname, Path dataDir, int controlPort, int orPort, int dirPort) {
this.nickname = nickname;
this.dataDir = dataDir;
this.controlPort = controlPort;
this.orPort = orPort;
this.dirPort = dirPort;
}

public Path getTorrcPath() {
return dataDir.resolve("torrc");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.tor.local_network;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;

public class DirectoryAuthorityFactory {
public static void createDirectoryAuthority(DirectoryAuthority directoryAuthority,
String passphrase) throws IOException, InterruptedException {
Path dataDir = directoryAuthority.getDataDir();
createDataDirIfNotPresent(dataDir);

Path keysPath = dataDir.resolve("keys");
boolean isSuccess = keysPath.toFile().mkdirs();
if (!isSuccess) {
throw new IllegalStateException("Couldn't create keys folder in data directory for directory authority.");
}

var relayKeyGenProcess = new RelayKeyGenProcess(directoryAuthority);
String firstDirectoryAuthorityAddress = "127.0.0.1:" + directoryAuthority.getDirPort();
var torDAKeyGenProcess = new DirectoryIdentityKeyGenProcess(keysPath, firstDirectoryAuthorityAddress);

var directoryAuthorityKeyGenerator = new DirectoryAuthorityKeyGenerator(torDAKeyGenProcess, relayKeyGenProcess);
directoryAuthorityKeyGenerator.generate(passphrase);

directoryAuthority.setIdentityKeyFingerprint(
directoryAuthorityKeyGenerator.getIdentityKeyFingerprint()
);
directoryAuthority.setRelayKeyFingerprint(
directoryAuthorityKeyGenerator.getRelayKeyFingerprint()
);
}

private static void createDataDirIfNotPresent(Path dataDir) {
File dataDirFile = dataDir.toFile();
if (!dataDirFile.exists()) {
boolean isSuccess = dataDir.toFile().mkdir();
if (!isSuccess) {
throw new IllegalStateException("Couldn't create data directory for directory authority.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@

package bisq.tor.local_network;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

@Slf4j
public class DirectoryAuthorityKeyGenerator {
Expand All @@ -32,6 +34,11 @@ public class DirectoryAuthorityKeyGenerator {
private final DirectoryIdentityKeyGenProcess identityKeyGenProcess;
private final RelayKeyGenProcess relayKeyGenProcess;

@Getter
private Optional<String> identityKeyFingerprint = Optional.empty();
@Getter
private Optional<String> relayKeyFingerprint = Optional.empty();

public DirectoryAuthorityKeyGenerator(DirectoryIdentityKeyGenProcess identityKeyGenProcess,
RelayKeyGenProcess relayKeyGenProcess) {
this.identityKeyGenProcess = identityKeyGenProcess;
Expand All @@ -40,7 +47,10 @@ public DirectoryAuthorityKeyGenerator(DirectoryIdentityKeyGenProcess identityKey

public void generate(String passphrase) throws IOException, InterruptedException {
String identityKeyFingerprint = generateIdentityKeys(passphrase);
relayKeyGenProcess.generateKeys(identityKeyFingerprint);
this.identityKeyFingerprint = Optional.of(identityKeyFingerprint);

String relayKeyFingerprint = relayKeyGenProcess.generateKeys(identityKeyFingerprint);
this.relayKeyFingerprint = Optional.of(relayKeyFingerprint);
}

private String generateIdentityKeys(String passphrase) throws IOException, InterruptedException {
Expand All @@ -55,7 +65,7 @@ private String generateIdentityKeys(String passphrase) throws IOException, Inter
inputStreamWaiter.waitForString(PEM_VERIFY_PASSPHRASE_PROMPT);
enterPassphrase(passphrase);

return identityKeyGenProcess.waitUntilGenerated();
return identityKeyGenProcess.getKeyFingerprint();
}

private void enterPassphrase(String passphrase) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;

@Slf4j
public class DirectoryIdentityKeyGenProcess {
Expand Down Expand Up @@ -54,36 +59,19 @@ public void start() throws IOException {
outputStream = Optional.of(process.getOutputStream());
}

public String waitUntilGenerated() throws InterruptedException, IOException {
public String getKeyFingerprint() throws InterruptedException, IOException {
Process process = this.process.orElseThrow();
process.waitFor(1, TimeUnit.MINUTES);
return readKeyFingerprint();
}

private String readKeyFingerprint() throws IOException {
File certificateFile = new File(torKeyDirPath.toFile(), "authority_certificate");
try (var reader = new BufferedReader(new FileReader(certificateFile))) {
String line = reader.readLine();
while (line != null) {

if (isFingerprintLine(line)) {
return extractFingerprint(line);
}
Predicate<String> lineMatcher = s -> s.startsWith("fingerprint ");
UnaryOperator<String> dataExtractor = s -> s.split(" ")[1].strip();

line = reader.readLine();
}
}

throw new IllegalStateException("Authority certificate was never created.");
}

private boolean isFingerprintLine(String line) {
// fingerprint A547708A712364A3782FB49E8207AF3FA2BC9713
return line.startsWith("fingerprint ");
}

private String extractFingerprint(String line) {
// fingerprint A547708A712364A3782FB49E8207AF3FA2BC9713
return line.split(" ")[1].strip();
var keyFingerprintReader = new KeyFingerprintReader(certificateFile, lineMatcher, dataExtractor);
return keyFingerprintReader.read();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.tor.local_network;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;

public class KeyFingerprintReader {
private final File fingerprintFile;
private final Predicate<String> lineMatcher;
private final UnaryOperator<String> dataExtractor;

public KeyFingerprintReader(File fingerprintFile,
Predicate<String> lineMatcher,
UnaryOperator<String> dataExtractor) {
this.fingerprintFile = fingerprintFile;
this.lineMatcher = lineMatcher;
this.dataExtractor = dataExtractor;
}


public String read() throws IOException {
try (var reader = new BufferedReader(new FileReader(fingerprintFile))) {
String line = reader.readLine();
while (line != null) {

if (lineMatcher.test(line)) {
return dataExtractor.apply(line);
}

line = reader.readLine();
}
}

throw new IllegalStateException(fingerprintFile.getAbsolutePath() + " was never created.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@

package bisq.tor.local_network;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;

public class RelayKeyGenProcess {
private final DirectoryAuthority directoryAuthority;
Expand All @@ -27,7 +30,7 @@ public RelayKeyGenProcess(DirectoryAuthority directoryAuthority) {
this.directoryAuthority = directoryAuthority;
}

public void generateKeys(String identityKeyFingerprint) throws IOException, InterruptedException {
public String generateKeys(String identityKeyFingerprint) throws IOException, InterruptedException {
var processBuilder = new ProcessBuilder(
"tor", "--list-fingerprint",
"--DataDirectory", directoryAuthority.getDataDir().toAbsolutePath().toString(),
Expand All @@ -46,5 +49,18 @@ public void generateKeys(String identityKeyFingerprint) throws IOException, Inte

Process process = processBuilder.start();
process.waitFor(45, TimeUnit.SECONDS);

return readKeyFingerprint();
}

private String readKeyFingerprint() throws IOException {
File dataDirFile = directoryAuthority.getDataDir().toFile();
File fingerprintFile = new File(dataDirFile, "fingerprint");

Predicate<String> lineMatcher = s -> s.startsWith("Unnamed ");
UnaryOperator<String> dataExtractor = s -> s.split(" ")[1].strip();

var keyFingerprintReader = new KeyFingerprintReader(fingerprintFile, lineMatcher, dataExtractor);
return keyFingerprintReader.read();
}
}
Loading

0 comments on commit 38177ea

Please sign in to comment.