Skip to content

Commit 14df574

Browse files
committed
Unit test for RSA Root certificate with EC intermediate & leaf certificates
1 parent 4c95d32 commit 14df574

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package eu.europa.ec.eudi.wallet.trust
2+
3+
import org.bouncycastle.asn1.x500.X500Name
4+
import org.bouncycastle.asn1.x509.BasicConstraints
5+
import org.bouncycastle.asn1.x509.Extension
6+
import org.bouncycastle.asn1.x509.KeyUsage
7+
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
8+
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils
9+
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
10+
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
11+
import java.math.BigInteger
12+
import java.security.KeyPair
13+
import java.security.KeyPairGenerator
14+
import java.security.SecureRandom
15+
import java.security.cert.X509Certificate
16+
import java.security.spec.ECGenParameterSpec
17+
import java.time.Instant
18+
import java.util.Date
19+
20+
private val extUtils = JcaX509ExtensionUtils()
21+
22+
// A dedicated RSA KeyPair for the Trust Anchor
23+
private val rsaTrustedKeyPair: KeyPair by lazy {
24+
KeyPairGenerator.getInstance("RSA").apply {
25+
initialize(2048)
26+
}.generateKeyPair()
27+
}
28+
29+
// The RSA Root CA (Trust Anchor)
30+
val rsaTrustedRootCertificate: X509Certificate by lazy {
31+
val serialNumber = BigInteger(64, SecureRandom())
32+
val issuer = X500Name("CN=RSA Root CA")
33+
val subject = X500Name("CN=RSA Root CA")
34+
val notBefore = Date.from(Instant.now().minusSeconds(86400))
35+
val notAfter = Date(notBefore.time + 30 * 86400000L)
36+
37+
val builder = JcaX509v3CertificateBuilder(
38+
issuer,
39+
serialNumber,
40+
notBefore,
41+
notAfter,
42+
subject,
43+
rsaTrustedKeyPair.public
44+
).apply {
45+
addExtension(Extension.basicConstraints, true, BasicConstraints(true))
46+
addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign or KeyUsage.cRLSign))
47+
// Subject Key ID
48+
addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(rsaTrustedKeyPair.public))
49+
// Authority Key ID (Self-signed)
50+
addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(rsaTrustedKeyPair.public))
51+
}
52+
53+
// SIGNATURE Signed with RSA
54+
val signer = JcaContentSignerBuilder("SHA256WithRSAEncryption").build(rsaTrustedKeyPair.private)
55+
JcaX509CertificateConverter().getCertificate(builder.build(signer))
56+
}
57+
58+
// 2. NEW: EC Intermediate KeyPair
59+
private val ecIntermediateKeyPair: KeyPair by lazy {
60+
KeyPairGenerator.getInstance("EC").apply {
61+
initialize(ECGenParameterSpec("secp256r1"))
62+
}.generateKeyPair()
63+
}
64+
65+
// EC Intermediate Certificate
66+
val ecIntermediateCertificate: X509Certificate by lazy {
67+
val serialNumber = BigInteger(64, SecureRandom())
68+
val issuer = X500Name(rsaTrustedRootCertificate.subjectX500Principal.name) // Issuer = RSA Root
69+
val subject = X500Name("CN=EC Intermediate CA")
70+
val notBefore = Date.from(Instant.now().minusSeconds(86400))
71+
val notAfter = Date(notBefore.time + 30 * 86400000L)
72+
73+
val builder = JcaX509v3CertificateBuilder(
74+
issuer,
75+
serialNumber,
76+
notBefore,
77+
notAfter,
78+
subject,
79+
ecIntermediateKeyPair.public // Intermediate has an EC Key
80+
).apply {
81+
// Critical: Basic Constraints CA = TRUE
82+
addExtension(Extension.basicConstraints, true, BasicConstraints(true))
83+
addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.keyCertSign or KeyUsage.cRLSign))
84+
85+
addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(ecIntermediateKeyPair.public))
86+
addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(rsaTrustedRootCertificate))
87+
}
88+
89+
// SIGNATURE: Signed by RSA Root
90+
val signer = JcaContentSignerBuilder("SHA256WithRSAEncryption").build(rsaTrustedKeyPair.private)
91+
JcaX509CertificateConverter().getCertificate(builder.build(signer))
92+
}
93+
94+
// The EC Leaf Certificate (Verifier), signed by the RSA Root
95+
val ecLeafSignedByIntermediateCertificate: X509Certificate by lazy {
96+
// The Verifier has an EC KeyPair
97+
val leafEcKeyPair = KeyPairGenerator.getInstance("EC").apply {
98+
initialize(ECGenParameterSpec("secp256r1"))
99+
}.generateKeyPair()
100+
101+
val serialNumber = BigInteger(64, SecureRandom())
102+
val issuer = X500Name(ecIntermediateCertificate.subjectX500Principal.name) // Intermediate Certificate signs it
103+
val subject = X500Name("CN=EC Leaf Signed By RSA")
104+
val notBefore = Date.from(Instant.now().minusSeconds(86400))
105+
val notAfter = Date(notBefore.time + 30 * 86400000L)
106+
107+
val builder = JcaX509v3CertificateBuilder(
108+
issuer,
109+
serialNumber,
110+
notBefore,
111+
notAfter,
112+
subject,
113+
leafEcKeyPair.public // The Leaf has an EC Public Key
114+
).apply {
115+
addExtension(Extension.basicConstraints, true, BasicConstraints(false))
116+
addExtension(Extension.keyUsage, true, KeyUsage(KeyUsage.digitalSignature))
117+
118+
// Subject Key ID (calculated from Leaf's EC key)
119+
addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(leafEcKeyPair.public))
120+
121+
// AUTHORITY KEY: Refers to the EC Intermediate
122+
addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(ecIntermediateCertificate))
123+
}
124+
125+
// SIGNATURE: Signed by the Intermediate's EC Key using ECDSA Algorithm
126+
val signer = JcaContentSignerBuilder("SHA256WithECDSA").build(ecIntermediateKeyPair.private)
127+
JcaX509CertificateConverter().getCertificate(builder.build(signer))
128+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package eu.europa.ec.eudi.wallet.trust
2+
3+
import eu.europa.ec.eudi.iso18013.transfer.readerauth.ReaderTrustStoreImpl
4+
import io.mockk.every
5+
import io.mockk.mockkStatic
6+
import io.mockk.unmockkStatic
7+
import org.bouncycastle.jce.provider.BouncyCastleProvider
8+
import org.junit.After
9+
import org.junit.Before
10+
import org.junit.Test
11+
import java.security.Security
12+
import java.security.cert.X509Certificate
13+
import kotlin.test.assertTrue
14+
15+
class TrustStoreRsaRootEcIntermediateTest {
16+
17+
// Fully qualified name of the internal class we need to mock
18+
private val crlClassName = "eu.europa.ec.eudi.iso18013.transfer.internal.readerauth.crl.CertificateCRLValidation"
19+
20+
@Before
21+
fun setup() {
22+
Security.addProvider(BouncyCastleProvider())
23+
24+
// Mock the internal class by Name
25+
mockkStatic(crlClassName)
26+
27+
// Stub the 'verify' method to do nothing
28+
val crlClass = Class.forName(crlClassName)
29+
val verifyMethod = crlClass.getMethod("verify", X509Certificate::class.java)
30+
31+
32+
// Define the stub
33+
every {
34+
verifyMethod.invoke(null, any<X509Certificate>())
35+
} returns Unit
36+
}
37+
38+
@After
39+
fun tearDown() {
40+
// Clean up the static mock to avoid affecting other tests
41+
unmockkStatic(crlClassName)
42+
}
43+
44+
@Test
45+
fun `Interop Hybrid Chain (RSA Root - EC Leaf) is valid with Default Config`() {
46+
47+
val certificateChain = listOf(ecLeafSignedByIntermediateCertificate, ecIntermediateCertificate)
48+
49+
val trustStore = ReaderTrustStoreImpl(
50+
listOf(rsaTrustedRootCertificate),
51+
profileValidation = { _, _ -> true }
52+
)
53+
54+
val result = trustStore!!.validateCertificationTrustPath(certificateChain)
55+
56+
assertTrue(result, "Trust store should accept RSA Root -> EC Intermediate -> EC Leaf chain")
57+
}
58+
}

0 commit comments

Comments
 (0)