ENT-5219: Synchronize BCCryptoService between OS and ENT (#6178)

This commit is contained in:
Denis Rekalov 2020-04-23 12:44:27 +01:00 committed by GitHub
parent 73d5fc4db6
commit 824c01daad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 4 deletions

View File

@ -143,4 +143,4 @@ enum class WrappingMode {
WRAPPED
}
class WrappedPrivateKey(val keyMaterial: ByteArray, val signatureScheme: SignatureScheme)
class WrappedPrivateKey(val keyMaterial: ByteArray, val signatureScheme: SignatureScheme, val encodingVersion: Int? = null)

View File

@ -162,20 +162,30 @@ class BCCryptoService(private val legalName: X500Principal,
wrappingKeyStore.save(wrappingKeyStorePath!!, certificateStore.password)
}
/**
* Using "AESWRAPPAD" cipher spec for key wrapping defined by [RFC 5649](https://tools.ietf.org/html/rfc5649).
* "AESWRAPPAD" (same as "AESKWP" or "AESRFC5649WRAP") is implemented in [org.bouncycastle.jcajce.provider.symmetric.AES.WrapPad] using
* [org.bouncycastle.crypto.engines.RFC5649WrapEngine]. See:
* - https://www.bouncycastle.org/docs/docs1.5on/org/bouncycastle/crypto/engines/AESWrapPadEngine.html
* - https://www.bouncycastle.org/docs/docs1.5on/org/bouncycastle/crypto/engines/RFC5649WrapEngine.html
*
* Keys encoded with "AESWRAPPAD" are stored with encodingVersion = 1. Previously used cipher spec ("AES" == "AES/ECB/PKCS5Padding")
* corresponds to encodingVersion = null.
*/
override fun generateWrappedKeyPair(masterKeyAlias: String, childKeyScheme: SignatureScheme): Pair<PublicKey, WrappedPrivateKey> {
if (!wrappingKeyStore.containsAlias(masterKeyAlias)) {
throw IllegalStateException("There is no master key under the alias: $masterKeyAlias")
}
val wrappingKey = wrappingKeyStore.getKey(masterKeyAlias, certificateStore.entryPassword.toCharArray())
val cipher = Cipher.getInstance("AES", cordaBouncyCastleProvider)
val cipher = Cipher.getInstance("AESWRAPPAD", cordaBouncyCastleProvider)
cipher.init(Cipher.WRAP_MODE, wrappingKey)
val keyPairGenerator = keyPairGeneratorFromScheme(childKeyScheme)
val keyPair = keyPairGenerator.generateKeyPair()
val privateKeyMaterialWrapped = cipher.wrap(keyPair.private)
return Pair(keyPair.public, WrappedPrivateKey(privateKeyMaterialWrapped, childKeyScheme))
return Pair(keyPair.public, WrappedPrivateKey(privateKeyMaterialWrapped, childKeyScheme, encodingVersion = 1))
}
override fun sign(masterKeyAlias: String, wrappedPrivateKey: WrappedPrivateKey, payloadToSign: ByteArray): ByteArray {
@ -184,7 +194,12 @@ class BCCryptoService(private val legalName: X500Principal,
}
val wrappingKey = wrappingKeyStore.getKey(masterKeyAlias, certificateStore.entryPassword.toCharArray())
val cipher = Cipher.getInstance("AES", cordaBouncyCastleProvider)
// Keeping backwards compatibility with previous encoding algorithms
val algorithm = when(wrappedPrivateKey.encodingVersion) {
1 -> "AESWRAPPAD"
else -> "AES"
}
val cipher = Cipher.getInstance(algorithm, cordaBouncyCastleProvider)
cipher.init(Cipher.UNWRAP_MODE, wrappingKey)
val privateKey = cipher.unwrap(wrappedPrivateKey.keyMaterial, keyAlgorithmFromScheme(wrappedPrivateKey.signatureScheme), Cipher.PRIVATE_KEY) as PrivateKey

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.cryptoservice.bouncycastle
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
import net.corda.core.internal.div
import net.corda.core.utilities.days
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
@ -13,6 +14,7 @@ import net.corda.nodeapi.internal.cryptoservice.WrappedPrivateKey
import net.corda.nodeapi.internal.cryptoservice.WrappingMode
import net.corda.testing.core.ALICE_NAME
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.jce.provider.BouncyCastleProvider
@ -23,8 +25,10 @@ import org.junit.rules.TemporaryFolder
import java.io.FileOutputStream
import java.nio.file.Path
import java.security.*
import java.security.spec.ECGenParameterSpec
import java.time.Duration
import java.util.*
import javax.crypto.Cipher
import javax.security.auth.x500.X500Principal
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
@ -253,4 +257,27 @@ class BCCryptoServiceTests {
Crypto.doVerify(publicKey, signature, data)
}
@Test(timeout=300_000)
fun `cryptoService can sign with previously encoded version of wrapped key`() {
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
val wrappingKeyAlias = UUID.randomUUID().toString()
cryptoService.createWrappingKey(wrappingKeyAlias)
val wrappingKeyStore = loadOrCreateKeyStore(wrappingKeyStorePath, cryptoService.certificateStore.password, "PKCS12")
val wrappingKey = wrappingKeyStore.getKey(wrappingKeyAlias, cryptoService.certificateStore.entryPassword.toCharArray())
val cipher = Cipher.getInstance("AES", cordaBouncyCastleProvider)
cipher.init(Cipher.WRAP_MODE, wrappingKey)
val keyPairGenerator = KeyPairGenerator.getInstance("EC", cordaBouncyCastleProvider)
keyPairGenerator.initialize(ECGenParameterSpec("secp256r1"))
val keyPair = keyPairGenerator.generateKeyPair()
val privateKeyMaterialWrapped = cipher.wrap(keyPair.private)
val wrappedPrivateKey = WrappedPrivateKey(privateKeyMaterialWrapped, Crypto.ECDSA_SECP256R1_SHA256, encodingVersion = null)
val data = "data".toByteArray()
val signature = cryptoService.sign(wrappingKeyAlias, wrappedPrivateKey, data)
Crypto.doVerify(keyPair.public, signature, data)
}
}