Skip to content
Open
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
83 changes: 70 additions & 13 deletions framework/src/main/java/org/tron/core/zen/ZksnarkInitService.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
package org.tron.core.zen;

import java.io.File;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;
import org.tron.common.zksnark.JLibrustzcash;
import org.tron.common.zksnark.LibrustzcashParam;
Expand All @@ -18,6 +28,9 @@
public class ZksnarkInitService {

private static final AtomicBoolean initialized = new AtomicBoolean(false);
private static final Set<PosixFilePermission> OWNER_ONLY =
Collections.unmodifiableSet(EnumSet.of(
PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE));

@PostConstruct
private void init() {
Expand Down Expand Up @@ -56,19 +69,63 @@ public static void librustzcashInitZksnarkParams() {
}
}

private static String getParamsFile(String fileName) {
InputStream in = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("params" + File.separator + fileName);
File fileOut = new File(System.getProperty("java.io.tmpdir")
+ File.separator + fileName + "." + System.currentTimeMillis());
@VisibleForTesting
static String getParamsFile(String fileName) {
InputStream resource = ZksnarkInitService.class.getResourceAsStream("/params/" + fileName);
if (resource == null) {
throw new TronError("Missing zk param resource: " + fileName,
TronError.ErrCode.ZCASH_INIT);
}

Path fileOut = null;
try (InputStream in = resource) {
fileOut = copyToSecureTempFile(in, fileName);
fileOut.toFile().deleteOnExit();
return fileOut.toAbsolutePath().toString();
} catch (IOException | RuntimeException e) {
deleteTempFile(fileOut, e);
throw new TronError("Failed to release zk param resource: " + fileName, e,
TronError.ErrCode.ZCASH_INIT);
}
}

@VisibleForTesting
static Path copyToSecureTempFile(InputStream in, String fileName) throws IOException {
int dotIndex = fileName.lastIndexOf('.');
String prefix = (dotIndex > 0 ? fileName.substring(0, dotIndex) : fileName) + "-";
String suffix = dotIndex > 0 ? fileName.substring(dotIndex) : ".params";
Path fileOut = createTempFile(prefix, suffix);

try (OutputStream out = Files.newOutputStream(fileOut,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING,
LinkOption.NOFOLLOW_LINKS)) {
IOUtils.copyLarge(in, out);
return fileOut;
} catch (IOException | RuntimeException e) {
deleteTempFile(fileOut, e);
throw e;
}
}

private static Path createTempFile(String prefix, String suffix) throws IOException {
try {
FileUtils.copyToFile(in, fileOut);
} catch (IOException e) {
logger.error(e.getMessage(), e);
return Files.createTempFile(prefix, suffix,
PosixFilePermissions.asFileAttribute(OWNER_ONLY));
} catch (UnsupportedOperationException e) {
// Non-POSIX providers still create the unpredictable name atomically.
return Files.createTempFile(prefix, suffix);
}
if (fileOut.exists()) {
fileOut.deleteOnExit();
}

private static void deleteTempFile(Path file, Throwable failure) {
if (file == null) {
return;
}
try {
Files.deleteIfExists(file);
} catch (IOException | RuntimeException cleanupError) {
failure.addSuppressed(cleanupError);
}
return fileOut.getAbsolutePath();
}
}
127 changes: 127 additions & 0 deletions framework/src/test/java/org/tron/core/zen/ZksnarkInitServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package org.tron.core.zen;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Test;
import org.tron.core.exception.TronError;

public class ZksnarkInitServiceTest {

private static final String TEST_PARAM_FILE = "test-zksnark.params";
private static final Set<PosixFilePermission> OWNER_ONLY =
EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE);

private final List<Path> tempFiles = new ArrayList<>();

@After
public void tearDown() throws IOException {
for (Path tempFile : tempFiles) {
Files.deleteIfExists(tempFile);
}
}

@Test
public void testGetParamsFileCreatesSecureTempFile() throws IOException {
Path file = track(ZksnarkInitService.getParamsFile(TEST_PARAM_FILE));

assertTrue(Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS));
assertFalse(Files.isSymbolicLink(file));
if (Files.getFileStore(file).supportsFileAttributeView(PosixFileAttributeView.class)) {
assertEquals(OWNER_ONLY, Files.getPosixFilePermissions(file));
}
assertTrue(file.getFileName().toString().startsWith("test-zksnark-"));
assertTrue(file.getFileName().toString().endsWith(".params"));

try (InputStream expected = ZksnarkInitService.class
.getResourceAsStream("/params/" + TEST_PARAM_FILE);
InputStream actual = Files.newInputStream(file)) {
assertTrue(IOUtils.contentEquals(expected, actual));
}
}

@Test
public void testGetParamsFileUsesUniqueNames() {
Path first = track(ZksnarkInitService.getParamsFile(TEST_PARAM_FILE));
Path second = track(ZksnarkInitService.getParamsFile(TEST_PARAM_FILE));

assertNotEquals(first, second);
}

@Test
public void testGetParamsFileMissingResourceThrows() {
TronError exception = assertThrows(TronError.class,
() -> ZksnarkInitService.getParamsFile("does-not-exist.params"));

assertEquals(TronError.ErrCode.ZCASH_INIT, exception.getErrCode());
}

@Test
public void testCopyFailureDeletesPartialFile() throws IOException {
String fileName = "failing-zksnark-" + System.nanoTime() + ".params";
Set<Path> filesBefore = findTempFiles(fileName);

InputStream failingInput = new InputStream() {
private boolean firstRead = true;

@Override
public int read() throws IOException {
if (firstRead) {
firstRead = false;
return 1;
}
throw new IOException("expected copy failure");
}

@Override
public int read(byte[] buffer, int offset, int length) throws IOException {
if (firstRead) {
firstRead = false;
buffer[offset] = 1;
return 1;
}
throw new IOException("expected copy failure");
}
};

assertThrows(IOException.class,
() -> ZksnarkInitService.copyToSecureTempFile(failingInput, fileName));
assertEquals(filesBefore, findTempFiles(fileName));
}

private Path track(String fileName) {
Path file = Paths.get(fileName);
tempFiles.add(file);
return file;
}

private static Set<Path> findTempFiles(String fileName) throws IOException {
int dotIndex = fileName.lastIndexOf('.');
String prefix = (dotIndex > 0 ? fileName.substring(0, dotIndex) : fileName) + "-";
Path tempDirectory = Paths.get(System.getProperty("java.io.tmpdir"));
try (Stream<Path> files = Files.list(tempDirectory)) {
return files
.filter(path -> path.getFileName().toString().startsWith(prefix))
.collect(Collectors.toSet());
}
}
}
1 change: 1 addition & 0 deletions framework/src/test/resources/params/test-zksnark.params
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
java-tron secure temporary zk parameter test resource
Loading