Skip to content

Commit 4dbc8ce

Browse files
BarbatosBarbatos
authored andcommitted
test(framework): add SR keystore loading regression tests
The Toolkit side (KeystoreUpdateTest) already covered both the legacy truncation tip behavior (whitespace and non-whitespace branches) and the address-mismatch defense, but the SR startup path through WitnessInitializer.initFromKeystore had no equivalent coverage. Since witnesses are the primary consumer of these fixes, exercise them here too. Three new tests: * testLegacyTruncationTipFiresOnWhitespacePassword — wrong password containing whitespace must trigger the FullNode keystore-factory hint, with TronError code WITNESS_KEYSTORE_LOAD. * testLegacyTruncationTipSuppressedOnNoWhitespacePassword — wrong password without whitespace must still log the load failure but must NOT surface the legacy tip (otherwise it would be noise on the common wrong-password case). * testTamperedKeystoreRejectedAtSrLoading — a keystore whose declared address does not match the address derived from its encrypted private key must be rejected with a CipherException carrying "address mismatch", wrapped in TronError(WITNESS_KEYSTORE_LOAD). A small Logback ListAppender helper attaches to the WitnessInitializer logger to capture log output for the tip assertions.
1 parent 8aa8df1 commit 4dbc8ce

1 file changed

Lines changed: 121 additions & 0 deletions

File tree

framework/src/test/java/org/tron/core/config/args/WitnessInitializerKeystoreTest.java

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,29 @@
33
import static org.junit.Assert.assertEquals;
44
import static org.junit.Assert.assertFalse;
55
import static org.junit.Assert.assertNotNull;
6+
import static org.junit.Assert.assertThrows;
7+
import static org.junit.Assert.assertTrue;
68

9+
import ch.qos.logback.classic.Logger;
10+
import ch.qos.logback.classic.spi.ILoggingEvent;
11+
import ch.qos.logback.core.read.ListAppender;
12+
import com.fasterxml.jackson.databind.DeserializationFeature;
13+
import com.fasterxml.jackson.databind.ObjectMapper;
714
import java.io.File;
815
import java.security.SecureRandom;
916
import org.junit.AfterClass;
1017
import org.junit.BeforeClass;
1118
import org.junit.ClassRule;
1219
import org.junit.Test;
1320
import org.junit.rules.TemporaryFolder;
21+
import org.slf4j.LoggerFactory;
1422
import org.tron.common.crypto.SignInterface;
1523
import org.tron.common.crypto.SignUtils;
1624
import org.tron.common.utils.ByteArray;
1725
import org.tron.common.utils.LocalWitnesses;
26+
import org.tron.core.exception.TronError;
1827
import org.tron.keystore.Credentials;
28+
import org.tron.keystore.WalletFile;
1929
import org.tron.keystore.WalletUtils;
2030

2131
/**
@@ -83,4 +93,115 @@ public void testNewKeystoreLoadableByWitnessInitializer() {
8393
assertEquals("Private key must match original",
8494
expectedPrivateKey, result.getPrivateKeys().get(0));
8595
}
96+
97+
@Test
98+
public void testLegacyTruncationTipFiresOnWhitespacePassword() {
99+
// The SR startup path should mirror the Toolkit's behavior: when the
100+
// supplied password contains whitespace and decryption fails, emit the
101+
// legacy-truncation hint pointing operators at the FullNode keystore-
102+
// factory bug. The Toolkit covers this in
103+
// KeystoreUpdateTest#testUpdateLegacyTipFiresWhenPasswordHasWhitespace.
104+
java.util.List<String> keystores =
105+
java.util.Collections.singletonList(keystoreFileName);
106+
ListAppender<ILoggingEvent> appender = attachAppender();
107+
try {
108+
TronError err = assertThrows(TronError.class, () ->
109+
WitnessInitializer.initFromKeystore(
110+
keystores, "wrong pass with spaces", null));
111+
assertEquals(TronError.ErrCode.WITNESS_KEYSTORE_LOAD, err.getErrCode());
112+
String logs = renderLogs(appender);
113+
assertTrue("Legacy-truncation tip must fire for whitespace password,"
114+
+ " got: " + logs,
115+
logs.contains("first whitespace-separated word"));
116+
} finally {
117+
detachAppender(appender);
118+
}
119+
}
120+
121+
@Test
122+
public void testLegacyTruncationTipSuppressedOnNoWhitespacePassword() {
123+
// For the common "wrong password" case (no whitespace), the legacy tip
124+
// would be misleading noise — it must be suppressed while still surfacing
125+
// the underlying load failure.
126+
java.util.List<String> keystores =
127+
java.util.Collections.singletonList(keystoreFileName);
128+
ListAppender<ILoggingEvent> appender = attachAppender();
129+
try {
130+
TronError err = assertThrows(TronError.class, () ->
131+
WitnessInitializer.initFromKeystore(
132+
keystores, "wrongnospaces", null));
133+
assertEquals(TronError.ErrCode.WITNESS_KEYSTORE_LOAD, err.getErrCode());
134+
String logs = renderLogs(appender);
135+
assertTrue("Witness load failure must still be logged, got: " + logs,
136+
logs.contains("Witness node start failed"));
137+
assertFalse("Legacy-truncation tip must NOT fire for whitespace-free"
138+
+ " password, got: " + logs,
139+
logs.contains("first whitespace-separated word"));
140+
} finally {
141+
detachAppender(appender);
142+
}
143+
}
144+
145+
@Test
146+
public void testTamperedKeystoreRejectedAtSrLoading() throws Exception {
147+
// Address-spoofing defense: a keystore whose declared `address` field does
148+
// not match the address derived from the decrypted private key must be
149+
// rejected at decryption time. Without this check, an attacker who could
150+
// place a file in the SR's keystore dir could trick a witness into signing
151+
// with a different key while displaying a familiar address. The check
152+
// lives in Wallet.decrypt; this test verifies it propagates correctly
153+
// through the WitnessInitializer path.
154+
File dir = new File(System.getProperty("user.dir"), DIR_NAME);
155+
SignInterface keyPair = SignUtils.getGeneratedRandomSign(
156+
SecureRandom.getInstance("NativePRNG"), true);
157+
String pwd = "tamperpwd123";
158+
String generatedName = WalletUtils.generateWalletFile(pwd, keyPair, dir, true);
159+
File keystoreFile = new File(dir, generatedName);
160+
try {
161+
// Tamper: rewrite the address field to a different value than what the
162+
// encrypted private key actually derives to.
163+
ObjectMapper mapper = new ObjectMapper().configure(
164+
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
165+
WalletFile wf = mapper.readValue(keystoreFile, WalletFile.class);
166+
String spoofedAddress = "TSpoofedSrAddressXXXXXXXXXXXXXXXXXXX";
167+
wf.setAddress(spoofedAddress);
168+
mapper.writeValue(keystoreFile, wf);
169+
170+
java.util.List<String> keystores =
171+
java.util.Collections.singletonList(DIR_NAME + "/" + generatedName);
172+
TronError err = assertThrows(TronError.class, () ->
173+
WitnessInitializer.initFromKeystore(keystores, pwd, null));
174+
assertEquals("Should be a witness keystore load failure",
175+
TronError.ErrCode.WITNESS_KEYSTORE_LOAD, err.getErrCode());
176+
Throwable cause = err.getCause();
177+
assertNotNull("TronError must wrap the underlying CipherException", cause);
178+
assertNotNull("Cause message must not be null", cause.getMessage());
179+
assertTrue("Cause must mention address mismatch, got: " + cause.getMessage(),
180+
cause.getMessage().contains("address mismatch"));
181+
} finally {
182+
keystoreFile.delete();
183+
}
184+
}
185+
186+
private static ListAppender<ILoggingEvent> attachAppender() {
187+
ListAppender<ILoggingEvent> appender = new ListAppender<>();
188+
appender.start();
189+
Logger logger = (Logger) LoggerFactory.getLogger(WitnessInitializer.class);
190+
logger.addAppender(appender);
191+
return appender;
192+
}
193+
194+
private static void detachAppender(ListAppender<ILoggingEvent> appender) {
195+
Logger logger = (Logger) LoggerFactory.getLogger(WitnessInitializer.class);
196+
logger.detachAppender(appender);
197+
appender.stop();
198+
}
199+
200+
private static String renderLogs(ListAppender<ILoggingEvent> appender) {
201+
StringBuilder sb = new StringBuilder();
202+
for (ILoggingEvent event : appender.list) {
203+
sb.append(event.getFormattedMessage()).append('\n');
204+
}
205+
return sb.toString();
206+
}
86207
}

0 commit comments

Comments
 (0)