diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index 85c4f440c4..a0d940f0c4 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -2,7 +2,7 @@ package net.corda.nodeapi.internal.crypto import net.corda.core.CordaOID import net.corda.core.crypto.Crypto -import net.corda.core.crypto.random63BitValue +import net.corda.core.crypto.newSecureRandom import net.corda.core.internal.* import net.corda.core.utilities.days import net.corda.core.utilities.millis @@ -35,6 +35,8 @@ import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* import javax.security.auth.x500.X500Principal +import kotlin.experimental.and +import kotlin.experimental.or object X509Utilities { val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512 @@ -57,6 +59,8 @@ object X509Utilities { // future and stick to [A-Za-z0-9]. const val DISTRIBUTED_NOTARY_ALIAS_PREFIX = "distributed-notary" + private const val CERTIFICATE_SERIAL_NUMBER_LENGTH = 16 + val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days) /** @@ -166,7 +170,7 @@ object X509Utilities { nameConstraints: NameConstraints? = null, crlDistPoint: String? = null, crlIssuer: X500Name? = null): X509v3CertificateBuilder { - val serial = BigInteger.valueOf(random63BitValue()) + val serial = generateCertificateSerialNumber() val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } }) val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded)) val role = certificateType.role @@ -346,6 +350,15 @@ object X509Utilities { builder.addExtension(Extension.cRLDistributionPoints, false, CRLDistPoint(arrayOf(distPoint))) } } + + @Suppress("MagicNumber") + private fun generateCertificateSerialNumber(): BigInteger { + val bytes = ByteArray(CERTIFICATE_SERIAL_NUMBER_LENGTH) + newSecureRandom().nextBytes(bytes) + // Set highest byte to 01xxxxxx to ensure positive sign and constant bit length. + bytes[0] = bytes[0].and(0x3F).or(0x40) + return BigInteger(bytes) + } } // Assuming cert type to role is 1:1 diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index e60be8435e..38d46e0666 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -452,4 +452,16 @@ class X509UtilitiesTest { assertEquals(childKeyPair.public, reloadedPublicKey) assertEquals(childKeyPair.private, reloadedPrivateKey) } + + @Test(timeout = 300_000) + fun `check certificate serial number`() { + val keyPair = generateKeyPair() + val subject = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB") + val cert = X509Utilities.createSelfSignedCACertificate(subject, keyPair) + assertTrue(cert.serialNumber.signum() > 0) + assertEquals(127, cert.serialNumber.bitLength()) + val serialized = X509Utilities.buildCertPath(cert).encoded + val deserialized = X509CertificateFactory().delegate.generateCertPath(serialized.inputStream()).x509Certificates.first() + assertEquals(cert.serialNumber, deserialized.serialNumber) + } }