Skip to content
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
2 changes: 1 addition & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Supported suites: `WOP-RSA2048-SHA256` / `WOP-RSA3072-SHA256` / `WOP-RSA4096-SHA
```java
WopClient client = WopClient.builder()
.appKey("app_001")
.suite("WOP-RSA3072-SHA256") // or WOP-RSA2048/4096-SHA256 / WOP-SM2-SM3
.suite("WOP-RSA2048-SHA256") // or WOP-RSA2048/4096-SHA256 / WOP-SM2-SM3
.merchantPrivateKey(merchantPrivateKey) // PEM or single-line Base64
.platformPublicKey(platformPublicKey)
.build();
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ VerifyResult result = WopClient.defaultClient().execute(

```java
WopClient client = WopClient.builder()
.appKey("app_001").suite("WOP-RSA3072-SHA256")
.appKey("app_001").suite("WOP-RSA2048-SHA256")
.merchantPrivateKey(merchantPrivateKey).platformPublicKey(platformPublicKey)
.serverRoot("https://gw.example.com/gateway")
.build();
Expand Down Expand Up @@ -107,7 +107,7 @@ WopClient client = WopClient.builder()
```java
WopClient client = WopClient.builder()
.appKey("app_001")
.suite("WOP-RSA3072-SHA256") // 或 WOP-RSA2048/4096-SHA256 / WOP-SM2-SM3
.suite("WOP-RSA2048-SHA256") // 或 WOP-RSA3072/4096-SHA256 / WOP-SM2-SM3
.merchantPrivateKey(merchantPrivateKey) // PEM 或 Base64 单行
.platformPublicKey(platformPublicKey)
.build();
Expand Down Expand Up @@ -166,7 +166,7 @@ mvn verify
```

向量覆盖面(字节级断言 + 全负向量):SHA-256/SM3 摘要与 digest 头、AES-256-GCM/SM4-GCM
固定 key/IV 密文、RSA3072/4096 确定性签名、SM2 固定 k 签名(r‖s 64B)、OAEP 解包与
固定 key/IV 密文、RSA2048/3072/4096 确定性签名、SM2 固定 k 签名(r‖s 64B)、OAEP 解包与
MGF1-SHA-1 陷阱、SM2 C1C3C2 解密与 C1C2C3 拒收、DEK 载荷、digest 头格式规则(双空格/
大写 hex/长度/跨族)、严格无填充 base64url。

Expand Down
2 changes: 1 addition & 1 deletion config/wopSdkConfigDefault.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"appKey": "YOUR_APP_KEY",
"suite": "WOP-RSA3072-SHA256",
"suite": "WOP-RSA2048-SHA256",
"merchantPrivateKey": "YOUR_MERCHANT_PRIVATE_KEY",
"platformPublicKey": "YOUR_PLATFORM_PUBLIC_KEY",
"serverRoot": "https://gw.example.com/gateway",
Expand Down
4 changes: 2 additions & 2 deletions wop-sdk-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
antrun 在 generate-test-resources 阶段按 commit SHA 从 raw.githubusercontent.com
拉取并逐字节 sha256 校验,不一致即构建失败(fail-closed)。升级流程见
CONTRIBUTING §4:先合 wop-specs PR,再以下面三项同步新钉。 -->
<wopSpecsRef>4a50824b3b9008fbda7a6c675209ccebc713f245</wopSpecsRef>
<wopSpecsCryptoVectorsSha256>1cbb1d3c494109d58e80cfd450c4aaf4159f991029abb875514517a232fd8001</wopSpecsCryptoVectorsSha256>
<wopSpecsRef>a1f7feb5b4af5c9052149e2adfd2063caa102677</wopSpecsRef>
<wopSpecsCryptoVectorsSha256>c7dd65740a1da9a5cfb804bca0c8b1bd7d4ab967adeb075297677a4bef148736</wopSpecsCryptoVectorsSha256>
<wopSpecsInteropCasesSha256>c920ca1a93ccb3899a659f59fed6ec4652cf9e1b3b58bbdac23c45ac3ed2353e</wopSpecsInteropCasesSha256>
</properties>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"appKey": "YOUR_APP_KEY",
"suite": "WOP-RSA3072-SHA256",
"suite": "WOP-RSA2048-SHA256",
"merchantPrivateKey": "YOUR_MERCHANT_PRIVATE_KEY",
"platformPublicKey": "YOUR_PLATFORM_PUBLIC_KEY",
"serverRoot": "https://gw.example.com/gateway",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -21,6 +22,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
Expand Down Expand Up @@ -104,6 +106,56 @@ void defaultClientConcurrentInitCreatesSingleInstance() throws Exception {
}
}

@Test
void defaultClientLateContenderReusesInstanceInsideLock() throws Exception {
// 二次检查 false 弧(先持锁者已初始化实例、后进锁线程直接复用):纯竞速调度不保证命中
// ——后醒线程快路径即可返回。反射持锁制造确定性交错补齐该弧(JaCoCo 分支门禁 100%)。
WopSdkConfig config = new WopSdkConfig.Builder()
.appKey("app_late")
.suite("WOP-RSA3072-SHA256")
.merchantPrivateKey(RSA_PRIV)
.platformPublicKey(RSA_PUB)
.serverRoot("https://gw.example.com/gateway")
.transport(draft -> new TransportResponse(200, Collections.emptyMap(), new byte[0]))
.build();
WopClient holder = WopClient.fromConfig(config);
WopClient.resetDefault();

Field lockField = WopClient.class.getDeclaredField("DEFAULT_CLIENT_LOCK");
lockField.setAccessible(true);
Object lock = lockField.get(null);
Field instanceField = WopClient.class.getDeclaredField("defaultClientInstance");
instanceField.setAccessible(true);

ExecutorService pool = Executors.newSingleThreadExecutor();
Future<WopClient> worker;
AtomicReference<Thread> workerThread = new AtomicReference<>();
CountDownLatch submitted = new CountDownLatch(1);
try {
synchronized (lock) {
worker = pool.submit(() -> {
workerThread.set(Thread.currentThread());
submitted.countDown();
return WopClient.defaultClient(); // 快路径读到 null,阻塞在锁入口
});
assertTrue(submitted.await(5, TimeUnit.SECONDS));
awaitThreadState(workerThread.get(), Thread.State.BLOCKED);
instanceField.set(null, holder); // 模拟"他人持锁期间已完成初始化"
} // 释放锁:worker 进入二次检查,instance 已非空 → 复用 holder
assertSame(holder, worker.get(5, TimeUnit.SECONDS));
} finally {
pool.shutdownNow();
}
}

private static void awaitThreadState(Thread thread, Thread.State expected) throws InterruptedException {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
while (thread.getState() != expected) {
assertTrue(System.nanoTime() < deadline, "worker 未进入 " + expected + " 状态");
Thread.sleep(10);
}
}

@Test
void resetDefaultReloadsNewConfigConcurrently() throws Exception {
Path file = writeConfig("app_v1");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,21 @@
*/
class KeyEncryptVectorTest {

private static final AlgorithmSuite RSA2048 = AlgorithmSuite.parse("WOP-RSA2048-SHA256");
private static final AlgorithmSuite RSA3072 = AlgorithmSuite.parse("WOP-RSA3072-SHA256");
private static final AlgorithmSuite RSA4096 = AlgorithmSuite.parse("WOP-RSA4096-SHA256");
private static final AlgorithmSuite SM2 = AlgorithmSuite.parse("WOP-SM2-SM3");

@Test
void oaep2048UnwrapMatchesGoldenVector() {
JsonNode vector = TestVectors.firstById("keyEncrypt", "oaep2048-unwrap");
PrivateKey priv = KeyCodec.parsePrivateKey(TestVectors.keys("rsa2048").path("privatePkcs8B64").asText(), RSA2048);
byte[] cipher = Codec.b64UrlDecode(vector.path("cipherB64u").asText());
byte[] plain = RsaOaepKeyEncryptStrategy.INSTANCE.decrypt(cipher, priv);
// 字节级:解包明文 = DEK 载荷
assertEquals(vector.path("expectedPlaintext").asText(), new String(plain, StandardCharsets.UTF_8));
}

@Test
void oaep3072UnwrapMatchesGoldenVector() {
JsonNode vector = TestVectors.firstById("keyEncrypt", "oaep3072-unwrap");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*/
class SignatureStrategyVectorTest {

private static final AlgorithmSuite RSA2048 = AlgorithmSuite.parse("WOP-RSA2048-SHA256");
private static final AlgorithmSuite RSA3072 = AlgorithmSuite.parse("WOP-RSA3072-SHA256");
private static final AlgorithmSuite RSA4096 = AlgorithmSuite.parse("WOP-RSA4096-SHA256");
private static final AlgorithmSuite SM2 = AlgorithmSuite.parse("WOP-SM2-SM3");
Expand All @@ -49,6 +50,21 @@ void rsa3072SignMatchesGoldenVector() {
assertTrue(RsaPkcs1SignatureStrategy.INSTANCE.verify(msg, sig, pub, Codec.utf8("x")));
}

@Test
void rsa2048SignMatchesGoldenVector() {
JsonNode vector = TestVectors.firstById("signature", "rsa2048-sign");
byte[] msg = Codec.utf8(vector.path("message").asText());
PrivateKey priv = KeyCodec.parsePrivateKey(TestVectors.keys("rsa2048").path("privatePkcs8B64").asText(), RSA2048);
PublicKey pub = KeyCodec.parsePublicKey(TestVectors.keys("rsa2048").path("publicSpkiB64").asText(), RSA2048);

byte[] sig = RsaPkcs1SignatureStrategy.INSTANCE.sign(msg, priv, Codec.utf8("x"));
// 字节级:签名 = 向量;b64url 长度恒 342(2048 位)
assertEquals(vector.path("expectedSigB64u").asText(), Codec.b64UrlEncode(sig));
assertEquals(vector.path("sigLenBytes").asInt(), sig.length);
assertEquals(vector.path("b64uLen").asInt(), Codec.b64UrlEncode(sig).length());
assertTrue(RsaPkcs1SignatureStrategy.INSTANCE.verify(msg, sig, pub, Codec.utf8("x")));
}

@Test
void rsa4096SignMatchesGoldenVector() {
JsonNode vector = TestVectors.firstById("signature", "rsa4096-sign");
Expand Down
Loading