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 138cb23d37..c99a48ba11 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 { // Note that this default value only applies to BCCryptoService. Other implementations of CryptoService may have to use different @@ -75,6 +77,8 @@ object X509Utilities { return "Alias '$alias' must contain only lowercase alphanumeric characters and not exceed 100 characters length." } + private const val CERTIFICATE_SERIAL_NUMBER_LENGTH = 16 + val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days) /** @@ -184,7 +188,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 @@ -364,6 +368,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 a086e91306..f880ecf489 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 @@ -553,4 +553,16 @@ class X509UtilitiesTest { cert.checkValidity({ "Error text" }, { }, Date.from(today.toInstant() + 51.days)) } } + + @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) + } }