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:
Denis Rekalov
2019-12-18 13:59:30 +00:00
committed by Matthew Nesbit
parent af30e40397
commit 8d5781db43
8 changed files with 99 additions and 37 deletions

View File

@ -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 {

View File

@ -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()) }
}
}

View File

@ -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)
}

View File

@ -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.