mirror of
https://github.com/corda/corda.git
synced 2025-06-13 04:38:19 +00:00
ENT-4628: Harmonize net.corda.nodeapi.internal.crypto between OS and ENT (#5820)
* ENT-4628: Harmonize net.corda.nodeapi.internal.crypto between OS and ENT * ENT-4628: Fix detekt
This commit is contained in:
committed by
Matthew Nesbit
parent
af30e40397
commit
8d5781db43
@ -12,8 +12,9 @@ import net.corda.nodeapi.internal.config.SslConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_ALIAS_PREFIX
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_ALIAS
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
@ -41,7 +42,7 @@ object DevIdentityGenerator {
|
||||
val nodeKeyStore = signingCertStore.get(true).also { it.installDevNodeCaCertPath(legalName) }
|
||||
p2pSslConfig.keyStore.get(true).also { it.registerDevP2pCertificates(legalName) }
|
||||
|
||||
val identity = nodeKeyStore.storeLegalIdentity("$NODE_IDENTITY_ALIAS_PREFIX-private-key")
|
||||
val identity = nodeKeyStore.storeLegalIdentity(NODE_IDENTITY_KEY_ALIAS)
|
||||
return identity.party
|
||||
}
|
||||
|
||||
@ -86,7 +87,7 @@ object DevIdentityGenerator {
|
||||
private fun setPrivateKey(keyStore: X509KeyStore, keyPair: KeyPair, notaryPrincipal: X500Principal) {
|
||||
val serviceKeyCert = createCertificate(keyPair.public, notaryPrincipal)
|
||||
keyStore.setPrivateKey(
|
||||
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
|
||||
DISTRIBUTED_NOTARY_KEY_ALIAS,
|
||||
keyPair.private,
|
||||
listOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate),
|
||||
DEV_CA_KEY_STORE_PASS // Unfortunately we have to use the same password for private key due to Artemis limitation, for more details please see:
|
||||
@ -97,7 +98,7 @@ object DevIdentityGenerator {
|
||||
|
||||
private fun setCompositeKey(keyStore: X509KeyStore, compositeKey: PublicKey, notaryPrincipal: X500Principal) {
|
||||
val compositeKeyCert = createCertificate(compositeKey, notaryPrincipal)
|
||||
keyStore.setCertificate("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert)
|
||||
keyStore.setCertificate(DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS, compositeKeyCert)
|
||||
}
|
||||
|
||||
private fun createCertificate(publicKey: PublicKey, principal: X500Principal): X509Certificate {
|
||||
|
@ -16,9 +16,15 @@ import java.security.Signature
|
||||
* This builder will use BouncyCastle's JcaContentSignerBuilder as fallback for unknown algorithm.
|
||||
*/
|
||||
object ContentSignerBuilder {
|
||||
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider, random: SecureRandom? = null): ContentSigner {
|
||||
fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider,
|
||||
random: SecureRandom? = null, optimised: Boolean = true): ContentSigner {
|
||||
val sigAlgId = signatureScheme.signatureOID
|
||||
val sig = Instances.getSignatureInstance(signatureScheme.signatureName, provider).apply {
|
||||
val signatureInstance = if (optimised)
|
||||
Instances.getSignatureInstance(signatureScheme.signatureName, provider)
|
||||
else
|
||||
Signature.getInstance(signatureScheme.signatureName, provider)
|
||||
|
||||
val sig = signatureInstance.apply {
|
||||
// TODO special handling for Sphincs due to a known BouncyCastle's Sphincs bug we reported.
|
||||
// It is fixed in BC 161b12, so consider updating the below if-statement after updating BouncyCastle.
|
||||
if (random != null && signatureScheme != SPHINCS256_SHA256) {
|
||||
@ -28,17 +34,28 @@ object ContentSignerBuilder {
|
||||
}
|
||||
}
|
||||
return object : ContentSigner {
|
||||
private val stream = SignatureOutputStream(sig)
|
||||
private val stream = SignatureOutputStream(sig, optimised)
|
||||
override fun getAlgorithmIdentifier(): AlgorithmIdentifier = sigAlgId
|
||||
override fun getOutputStream(): OutputStream = stream
|
||||
override fun getSignature(): ByteArray = stream.signature
|
||||
}
|
||||
}
|
||||
|
||||
private class SignatureOutputStream(private val sig: Signature) : OutputStream() {
|
||||
internal val signature: ByteArray get() = sig.sign()
|
||||
override fun write(bytes: ByteArray, off: Int, len: Int) = sig.update(bytes, off, len)
|
||||
override fun write(bytes: ByteArray) = sig.update(bytes)
|
||||
override fun write(b: Int) = sig.update(b.toByte())
|
||||
private class SignatureOutputStream(private val sig: Signature, private val optimised: Boolean) : OutputStream() {
|
||||
private var alreadySigned = false
|
||||
internal val signature: ByteArray by lazy {
|
||||
try {
|
||||
alreadySigned = true
|
||||
sig.sign()
|
||||
} finally {
|
||||
if (optimised) {
|
||||
Instances.releaseSignatureInstance(sig)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun checkNotSigned(func: () -> Unit) { if (alreadySigned) throw IllegalStateException("Cannot write to already signed object"); func()}
|
||||
override fun write(bytes: ByteArray, off: Int, len: Int) = checkNotSigned { sig.update(bytes, off, len) }
|
||||
override fun write(bytes: ByteArray) = checkNotSigned { sig.update(bytes) }
|
||||
override fun write(b: Int) = checkNotSigned { sig.update(b.toByte()) }
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,18 @@ const val KEYSTORE_TYPE = "JKS"
|
||||
* @param keyStoreFilePath location of KeyStore file.
|
||||
* @param storePassword password to open the store. This does not have to be the same password as any keys stored,
|
||||
* but for SSL purposes this is recommended.
|
||||
* @param keystoreType the type of the keystore to be loaded. Defaults to "JKS".
|
||||
* @param provider KeyStore provider, optional.
|
||||
* @return returns the KeyStore opened/created.
|
||||
*/
|
||||
fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore {
|
||||
fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String, keystoreType: String = KEYSTORE_TYPE,
|
||||
provider: Provider? = null): KeyStore {
|
||||
val pass = storePassword.toCharArray()
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
val keyStore = if (provider != null) {
|
||||
KeyStore.getInstance(keystoreType, provider)
|
||||
} else {
|
||||
KeyStore.getInstance(keystoreType)
|
||||
}
|
||||
if (keyStoreFilePath.exists()) {
|
||||
keyStoreFilePath.read { keyStore.load(it, pass) }
|
||||
} else {
|
||||
@ -144,6 +151,6 @@ fun KeyStore.getX509Certificate(alias: String): X509Certificate {
|
||||
*/
|
||||
fun KeyStore.getSupportedKey(alias: String, keyPassword: String): PrivateKey {
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val key = getKey(alias, keyPass) as PrivateKey
|
||||
val key = requireNotNull(getKey(alias, keyPass)) { "Key for alias: '$alias' cannot be found" } as PrivateKey
|
||||
return Crypto.toSupportedPrivateKey(key)
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
object X509Utilities {
|
||||
// Note that this default value only applies to BCCryptoService. Other implementations of CryptoService may have to use different
|
||||
// schemes (for instance `UtimacoCryptoService.DEFAULT_IDENTITY_SIGNATURE_SCHEME`).
|
||||
val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512
|
||||
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
|
||||
|
||||
@ -47,15 +49,31 @@ object X509Utilities {
|
||||
const val CORDA_CLIENT_TLS = "cordaclienttls"
|
||||
const val CORDA_CLIENT_CA = "cordaclientca"
|
||||
|
||||
// TODO These don't need to be prefixes, but can be the full aliases. However, because they are used as key aliases
|
||||
// we should ensure that:
|
||||
// a) they always contain valid characters, preferably [A-Za-z0-9] in order to be supported by the majority of
|
||||
// crypto service implementations (i.e., HSMs).
|
||||
// b) they are at most 127 chars in length (i.e., as of 2018, Azure Key Vault does not support bigger aliases).
|
||||
const val NODE_IDENTITY_ALIAS_PREFIX = "identity"
|
||||
// TODO Hyphen (-) seems to be supported by the major HSM vendors, but we should consider remove it in the
|
||||
// future and stick to [A-Za-z0-9].
|
||||
const val DISTRIBUTED_NOTARY_ALIAS_PREFIX = "distributed-notary"
|
||||
const val NODE_IDENTITY_KEY_ALIAS = "identity-private-key"
|
||||
const val DISTRIBUTED_NOTARY_KEY_ALIAS = "distributed-notary-private-key"
|
||||
const val DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS = "distributed-notary-composite-key"
|
||||
|
||||
const val TLS_CERTIFICATE_DAYS_TO_EXPIRY_WARNING_THRESHOLD = 30
|
||||
private const val KEY_ALIAS_REGEX = "[a-z0-9-]+"
|
||||
private const val KEY_ALIAS_MAX_LENGTH = 100
|
||||
|
||||
/**
|
||||
* Checks if the provided key alias does not exceed maximum length and
|
||||
* only contains alphanumeric characters.
|
||||
*/
|
||||
fun isKeyAliasValid(alias: String): Boolean {
|
||||
if (alias.length > KEY_ALIAS_MAX_LENGTH) return false
|
||||
return KEY_ALIAS_REGEX.toRegex().matches(alias)
|
||||
}
|
||||
|
||||
/**
|
||||
* The error message to be displayed to the user when the alias validation fails.
|
||||
*/
|
||||
fun invalidKeyAliasErrorMessage(alias: String): String {
|
||||
return "Alias '$alias' must contain only lowercase alphanumeric characters and not exceed 100 characters length."
|
||||
}
|
||||
|
||||
val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days)
|
||||
|
||||
@ -378,6 +396,24 @@ fun PKCS10CertificationRequest.isSignatureValid(): Boolean {
|
||||
return this.isSignatureValid(JcaContentVerifierProviderBuilder().build(this.subjectPublicKeyInfo))
|
||||
}
|
||||
|
||||
/**
|
||||
* Check certificate validity or print warning if expiry is within 30 days
|
||||
*/
|
||||
fun X509Certificate.checkValidity(errorMessage: () -> Any, warningBlock: (daysToExpiry: Int) -> Unit, date: Date = Date()) {
|
||||
try {
|
||||
checkValidity(date)
|
||||
}
|
||||
catch (e: CertificateException) {
|
||||
throw IllegalArgumentException(errorMessage().toString(), e)
|
||||
}
|
||||
// Number of full days until midnight of expiry date: today is not included
|
||||
val daysToExpiry = ChronoUnit.DAYS.between(date.toInstant(), notAfter.toInstant()).toInt()
|
||||
if (daysToExpiry < X509Utilities.TLS_CERTIFICATE_DAYS_TO_EXPIRY_WARNING_THRESHOLD) {
|
||||
// Also include today, e.g. return 1 for tomorrow expiry
|
||||
warningBlock(daysToExpiry + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best
|
||||
* so assume this class is not.
|
||||
|
Reference in New Issue
Block a user