mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
ENT-4628: Harmonize CryptoService and BCCryptoService between OS and ENT (#5822)
This commit is contained in:
parent
8d5781db43
commit
bc96bea24a
@ -80,6 +80,7 @@ buildscript {
|
||||
ext.deterministic_rt_version = constants.getProperty('deterministicRtVersion')
|
||||
ext.okhttp_version = '3.14.2'
|
||||
ext.netty_version = '4.1.29.Final'
|
||||
ext.tcnative_version = '2.0.14.Final'
|
||||
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
||||
ext.fileupload_version = '1.4'
|
||||
// Legacy JUnit 4 version
|
||||
@ -376,11 +377,15 @@ allprojects {
|
||||
// Demand that everything uses our given version of Netty.
|
||||
eachDependency { details ->
|
||||
if (details.requested.group == 'io.netty' && details.requested.name.startsWith('netty-')) {
|
||||
if (details.requested.name.startsWith('netty-tcnative')){
|
||||
details.useVersion tcnative_version
|
||||
} else {
|
||||
details.useVersion netty_version
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
compile {
|
||||
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
|
||||
// Remove any transitive dependency on Apache's version.
|
||||
|
@ -195,6 +195,7 @@
|
||||
<ID>ComplexMethod:StartedFlowTransition.kt$StartedFlowTransition$override fun transition(): TransitionResult</ID>
|
||||
<ID>ComplexMethod:StatusTransitions.kt$StatusTransitions$ fun verify(tx: LedgerTransaction)</ID>
|
||||
<ID>ComplexMethod:StringToMethodCallParser.kt$StringToMethodCallParser$ @Throws(UnparseableCallException::class) fun parse(target: T?, command: String): ParsedMethodCall</ID>
|
||||
<ID>ComplexMethod:TlsDiffAlgorithmsTest.kt$TlsDiffAlgorithmsTest$@Test fun testClientServerTlsExchange()</ID>
|
||||
<ID>ComplexMethod:TlsDiffProtocolsTest.kt$TlsDiffProtocolsTest$@Test fun testClientServerTlsExchange()</ID>
|
||||
<ID>ComplexMethod:TransactionBuilder.kt$TransactionBuilder$ fun withItems(vararg items: Any)</ID>
|
||||
<ID>ComplexMethod:TransactionBuilder.kt$TransactionBuilder$ private fun addMissingDependency(services: ServicesForResolution, wireTx: WireTransaction): Boolean</ID>
|
||||
@ -986,6 +987,10 @@
|
||||
<ID>MagicNumber:MockNetworkParametersService.kt$MockNetworkParametersStorage$20</ID>
|
||||
<ID>MagicNumber:MockServices.kt$MockServices$10000</ID>
|
||||
<ID>MagicNumber:MockServices.kt$MockServices.Companion.<no name provided>$512</ID>
|
||||
<ID>MagicNumber:NettyTestClient.kt$NettyTestClient$5</ID>
|
||||
<ID>MagicNumber:NettyTestHandler.kt$NettyTestHandler$5</ID>
|
||||
<ID>MagicNumber:NettyTestServer.kt$NettyTestServer$100</ID>
|
||||
<ID>MagicNumber:NettyTestServer.kt$NettyTestServer$5</ID>
|
||||
<ID>MagicNumber:Network.kt$Network$0.8</ID>
|
||||
<ID>MagicNumber:Network.kt$Network$1.2</ID>
|
||||
<ID>MagicNumber:Network.kt$Network$10</ID>
|
||||
@ -1343,9 +1348,6 @@
|
||||
<ID>MaxLineLength:AddressBindingFailureTests.kt$AddressBindingFailureTests$@Test fun `rpc admin address`()</ID>
|
||||
<ID>MaxLineLength:AddressBindingFailureTests.kt$AddressBindingFailureTests$assertThat(exception.addresses).contains(address).withFailMessage("Expected addresses to contain $address but was ${exception.addresses}.")</ID>
|
||||
<ID>MaxLineLength:AddressBindingFailureTests.kt$AddressBindingFailureTests$assertThatThrownBy { startNode(customOverrides = overrides(address)).getOrThrow() }</ID>
|
||||
<ID>MaxLineLength:AliasPrivateKeyTest.kt$AliasPrivateKeyTest$signingCertStore.query { setPrivateKey(alias, aliasPrivateKey, listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), "entrypassword") }</ID>
|
||||
<ID>MaxLineLength:AliasPrivateKeyTest.kt$AliasPrivateKeyTest$val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "keystorepass").get(createNew = true)</ID>
|
||||
<ID>MaxLineLength:AliasPrivateKeyTest.kt$AliasPrivateKeyTest${ val alias = "01234567890" val aliasPrivateKey = AliasPrivateKey(alias) val certificatesDirectory = tempFolder.root.toPath() val signingCertStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory, "keystorepass").get(createNew = true) signingCertStore.query { setPrivateKey(alias, aliasPrivateKey, listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), "entrypassword") } // We can retrieve the certificate. assertTrue { signingCertStore.contains(alias) } // We can retrieve the certificate. assertEquals(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT, signingCertStore[alias]) // Although we can store an AliasPrivateKey, we cannot retrieve it. But, it's fine as we use certStore for storing/handling certs only. assertThatIllegalArgumentException().isThrownBy { signingCertStore.query { getPrivateKey(alias, "entrypassword") } }.withMessage("Unrecognised algorithm: 1.3.6.1.4.1.50530.1.2") }</ID>
|
||||
<ID>MaxLineLength:AllButBlacklisted.kt$AllButBlacklisted$throw IllegalStateException("The $matchType $aMatch of ${type.name} is blacklisted, so it cannot be used in serialization.")</ID>
|
||||
<ID>MaxLineLength:Amount.kt$Amount<T : Any> : Comparable</ID>
|
||||
<ID>MaxLineLength:Amount.kt$AmountTransfer$remaining = SourceAndAmount(payer, balance.amount.copy(quantity = Math.subtractExact(balance.amount.quantity, residual)), newRef)</ID>
|
||||
@ -1464,8 +1466,12 @@
|
||||
<ID>MaxLineLength:AutoOfferFlow.kt$AutoOfferFlow.Requester$val notary = serviceHub.networkMapCache.notaryIdentities.first() // TODO We should pass the notary as a parameter to the flow, not leave it to random choice.</ID>
|
||||
<ID>MaxLineLength:AutoOfferFlow.kt$AutoOfferFlow.Requester$val otherParty = excludeHostNode(serviceHub, groupAbstractPartyByWellKnownParty(serviceHub, dealToBeOffered.participants)).keys.single()</ID>
|
||||
<ID>MaxLineLength:BCCryptoService.kt$BCCryptoService : CryptoService</ID>
|
||||
<ID>MaxLineLength:BCCryptoService.kt$BCCryptoService$/** * JKS keystore does not support storage for secret keys, so the existing keystore cannot be re-used. * JCEKS keystore supports storage of symmetric keys according to the spec, but there are several issues around classloaders and deserialization filtering (see links below). * - https://stackoverflow.com/questions/49990904/what-is-the-cause-of-java-security-unrecoverablekeyexception-rejected-by-the-j * - https://stackoverflow.com/questions/50393533/java-io-ioexception-invalid-secret-key-format-when-opening-jceks-key-store-wi * Thus, PKCS12 is used for storing the wrapping key. */ private val wrappingKeyStore: KeyStore by lazy { loadOrCreateKeyStore(wrappingKeyStorePath!!, certificateStore.password, "PKCS12") }</ID>
|
||||
<ID>MaxLineLength:BCCryptoService.kt$BCCryptoService$return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())</ID>
|
||||
<ID>MaxLineLength:BCCryptoService.kt$BCCryptoService$throw CryptoServiceException("Cannot generate key for alias $alias and signature scheme ${scheme.schemeCodeName} (id ${scheme.schemeNumberID})", e)</ID>
|
||||
<ID>MaxLineLength:BCCryptoService.kt$BCCryptoService$val privateKey = cipher.unwrap(wrappedPrivateKey.keyMaterial, keyAlgorithmFromScheme(wrappedPrivateKey.signatureScheme), Cipher.PRIVATE_KEY) as PrivateKey</ID>
|
||||
<ID>MaxLineLength:BCCryptoService.kt$BCCryptoService$wrappingKeyStore.setEntry(alias, KeyStore.SecretKeyEntry(wrappingKey), KeyStore.PasswordProtection(certificateStore.entryPassword.toCharArray()))</ID>
|
||||
<ID>MaxLineLength:BCCryptoServiceTests.kt$BCCryptoServiceTests$private</ID>
|
||||
<ID>MaxLineLength:BFTNotaryServiceTests.kt$BFTNotaryServiceTests$addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)</ID>
|
||||
<ID>MaxLineLength:BFTNotaryServiceTests.kt$BFTNotaryServiceTests.Companion$fun startBftClusterAndNode(clusterSize: Int, mockNet: InternalMockNetwork, exposeRaces: Boolean = false): Pair<Party, TestStartedNode></ID>
|
||||
<ID>MaxLineLength:BFTSmart.kt$BFTSmart$Client : SingletonSerializeAsToken</ID>
|
||||
@ -1966,6 +1972,9 @@
|
||||
<ID>MaxLineLength:Crypto.kt$Crypto$is BCRSAPrivateKey, is BCSphincs256PrivateKey -> true</ID>
|
||||
<ID>MaxLineLength:Crypto.kt$Crypto$is BCRSAPublicKey -> key.modulus.bitLength() >= 2048</ID>
|
||||
<ID>MaxLineLength:Crypto.kt$Crypto$val signableData = SignableData(originalSignedHash(txId, transactionSignature.partialMerkleTree), transactionSignature.signatureMetadata)</ID>
|
||||
<ID>MaxLineLength:CryptoService.kt$CryptoService$ fun createWrappingKey(alias: String, failIfExists: Boolean = true)</ID>
|
||||
<ID>MaxLineLength:CryptoService.kt$CryptoService$ fun generateWrappedKeyPair(masterKeyAlias: String, childKeyScheme: SignatureScheme = defaultWrappingSignatureScheme()): Pair<PublicKey, WrappedPrivateKey></ID>
|
||||
<ID>MaxLineLength:CryptoService.kt$CryptoService$ fun getWrappingMode(): WrappingMode?</ID>
|
||||
<ID>MaxLineLength:CryptoServiceFactory.kt$CryptoServiceFactory.Companion$throw IllegalArgumentException("Currently only BouncyCastle is used as a crypto service. A valid signing certificate store is required.")</ID>
|
||||
<ID>MaxLineLength:CryptoUtils.kt$ @DeleteForDJVM @Throws(NoSuchAlgorithmException::class) fun newSecureRandom(): SecureRandom</ID>
|
||||
<ID>MaxLineLength:CryptoUtils.kt$ @DeleteForDJVM @Throws(NoSuchAlgorithmException::class) fun secureRandomBytes(numOfBytes: Int): ByteArray</ID>
|
||||
@ -2479,6 +2488,8 @@
|
||||
<ID>MaxLineLength:MockAttachmentStorage.kt$MockAttachmentStorage$val version = try { Integer.parseInt(baseAttachment.openAsJAR().manifest?.mainAttributes?.getValue(Attributes.Name.IMPLEMENTATION_VERSION)) } catch (e: Exception) { DEFAULT_CORDAPP_VERSION }</ID>
|
||||
<ID>MaxLineLength:MockContractAttachment.kt$// A valid zip file with 1 entry. val simpleZip = byteArrayOf(80, 75, 3, 4, 20, 0, 8, 8, 8, 0, 15, 113, 79, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 4, 0, 47, 97, -2, -54, 0, 0, 75, 4, 0, 80, 75, 7, 8, 67, -66, -73, -24, 3, 0, 0, 0, 1, 0, 0, 0, 80, 75, 1, 2, 20, 0, 20, 0, 8, 8, 8, 0, 15, 113, 79, 78, 67, -66, -73, -24, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 47, 97, -2, -54, 0, 0, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 52, 0, 0, 0, 55, 0, 0, 0, 0, 0)</ID>
|
||||
<ID>MaxLineLength:MockCryptoService.kt$MockCryptoService$return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())</ID>
|
||||
<ID>MaxLineLength:MockCryptoService.kt$MockCryptoService$val privateKey = cipher.unwrap(wrappedPrivateKey.keyMaterial, keyAlgorithmFromScheme(wrappedPrivateKey.signatureScheme), Cipher.PRIVATE_KEY) as PrivateKey</ID>
|
||||
<ID>MaxLineLength:MockCryptoService.kt$MockCryptoService$val wrappingKey = wrappingKeys[masterKeyAlias] ?: throw IllegalStateException("There is no master key under the alias: $masterKeyAlias")</ID>
|
||||
<ID>MaxLineLength:MockNetwork.kt$MockNetwork$ fun createPartyNode(legalName: CordaX500Name? = null): StartedMockNode</ID>
|
||||
<ID>MaxLineLength:MockNetwork.kt$MockNetwork$val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy</ID>
|
||||
<ID>MaxLineLength:MockNetwork.kt$MockNetworkParameters$fun withCordappsForAllNodes(cordappsForAllNodes: Collection<TestCordapp>): MockNetworkParameters</ID>
|
||||
@ -3368,6 +3379,11 @@
|
||||
<ID>MaxLineLength:TimedFlowTests.kt$TimedFlowTests.TestNotaryService$override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?></ID>
|
||||
<ID>MaxLineLength:TimedFlowTests.kt$TimedFlowTests.TestNotaryService$private</ID>
|
||||
<ID>MaxLineLength:TimedFlowTests.kt$TimedFlowTests.TestNotaryService.<no name provided>$override</ID>
|
||||
<ID>MaxLineLength:TlsDiffAlgorithmsTest.kt$TlsDiffAlgorithmsTest$val clientKeyStore = CertificateStore.fromResource("net/corda/nodeapi/internal/crypto/keystores/bridge_$clientAlgo.jks", "bridgepass", "bridgepass")</ID>
|
||||
<ID>MaxLineLength:TlsDiffAlgorithmsTest.kt$TlsDiffAlgorithmsTest$val serverKeyStore = CertificateStore.fromResource("net/corda/nodeapi/internal/crypto/keystores/float_$serverAlgo.jks", "floatpass", "floatpass")</ID>
|
||||
<ID>MaxLineLength:TlsDiffAlgorithmsTest.kt$TlsDiffAlgorithmsTest.Companion$arrayOf("ec", "ec", CIPHER_SUITES_ALL, false)</ID>
|
||||
<ID>MaxLineLength:TlsDiffAlgorithmsTest.kt$TlsDiffAlgorithmsTest.Companion$arrayOf("ec", "ec", CIPHER_SUITES_JUST_EC, false)</ID>
|
||||
<ID>MaxLineLength:TlsDiffAlgorithmsTest.kt$TlsDiffAlgorithmsTest.Companion$arrayOf("ec", "ec", CIPHER_SUITES_JUST_RSA, true)</ID>
|
||||
<ID>MaxLineLength:TlsDiffProtocolsTest.kt$TlsDiffProtocolsTest$logger.info("Testing: ServerAlgo: $serverAlgo, ClientAlgo: $clientAlgo, Suites: $cipherSuites, Server protocols: $serverProtocols, Client protocols: $clientProtocols, Should fail: $shouldFail")</ID>
|
||||
<ID>MaxLineLength:TlsDiffProtocolsTest.kt$TlsDiffProtocolsTest$val clientKeyStore = CertificateStore.fromResource("net/corda/nodeapi/internal/crypto/keystores/bridge_$clientAlgo.jks", "bridgepass", "bridgepass")</ID>
|
||||
<ID>MaxLineLength:TlsDiffProtocolsTest.kt$TlsDiffProtocolsTest$val serverKeyStore = CertificateStore.fromResource("net/corda/nodeapi/internal/crypto/keystores/float_$serverAlgo.jks", "floatpass", "floatpass")</ID>
|
||||
@ -4043,6 +4059,7 @@
|
||||
<ID>TooGenericExceptionCaught:MockNodeMessagingService.kt$MockNodeMessagingService$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:MyCustomNotaryService.kt$MyValidatingNotaryFlow$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:NamedCacheTest.kt$NamedCacheTest$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:NettyTestHandler.kt$NettyTestHandler$e: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:NetworkBootstrapper.kt$NetworkBootstrapper$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:NetworkMapServer.kt$NetworkMapServer.InMemoryNetworkMapService$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:NetworkMapUpdater.kt$NetworkMapUpdater$e: Exception</ID>
|
||||
@ -4101,6 +4118,7 @@
|
||||
<ID>TooGenericExceptionCaught:StringToMethodCallParser.kt$StringToMethodCallParser$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:TLSAuthenticationTests.kt$TLSAuthenticationTests$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:ThrowableSerializer.kt$ThrowableSerializer$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:TlsDiffAlgorithmsTest.kt$TlsDiffAlgorithmsTest$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:TlsDiffProtocolsTest.kt$TlsDiffProtocolsTest$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:TraderDemo.kt$TraderDemo$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:TransactionBuilder.kt$TransactionBuilder$e: Throwable</ID>
|
||||
@ -4151,6 +4169,7 @@
|
||||
<ID>TooManyFunctions:ActionExecutorImpl.kt$ActionExecutorImpl : ActionExecutor</ID>
|
||||
<ID>TooManyFunctions:AppendOnlyPersistentMap.kt$AppendOnlyPersistentMapBase<K, V, E, out EK></ID>
|
||||
<ID>TooManyFunctions:ArtemisTcpTransport.kt$ArtemisTcpTransport$Companion</ID>
|
||||
<ID>TooManyFunctions:BCCryptoService.kt$BCCryptoService : CryptoService</ID>
|
||||
<ID>TooManyFunctions:BFTSmart.kt$BFTSmart$Replica : DefaultRecoverable</ID>
|
||||
<ID>TooManyFunctions:BaseTransaction.kt$BaseTransaction : NamedByHash</ID>
|
||||
<ID>TooManyFunctions:ClassCarpenter.kt$ClassCarpenterImpl : ClassCarpenter</ID>
|
||||
@ -4387,6 +4406,8 @@
|
||||
<ID>WildcardImport:AttachmentsClassLoader.kt$import net.corda.core.serialization.*</ID>
|
||||
<ID>WildcardImport:AttachmentsClassLoaderStaticContractTests.kt$import net.corda.core.contracts.*</ID>
|
||||
<ID>WildcardImport:AutoOfferFlow.kt$import net.corda.core.flows.*</ID>
|
||||
<ID>WildcardImport:BCCryptoService.kt$import java.security.*</ID>
|
||||
<ID>WildcardImport:BCCryptoService.kt$import net.corda.nodeapi.internal.cryptoservice.*</ID>
|
||||
<ID>WildcardImport:BCCryptoServiceTests.kt$import java.security.*</ID>
|
||||
<ID>WildcardImport:BFTNotaryServiceTests.kt$import net.corda.core.crypto.*</ID>
|
||||
<ID>WildcardImport:BFTNotaryServiceTests.kt$import net.corda.testing.node.internal.*</ID>
|
||||
@ -4709,6 +4730,7 @@
|
||||
<ID>WildcardImport:MessageState.kt$import net.corda.core.contracts.*</ID>
|
||||
<ID>WildcardImport:MigrationServicesForResolution.kt$import net.corda.core.contracts.*</ID>
|
||||
<ID>WildcardImport:MockAttachmentStorage.kt$import net.corda.core.node.services.vault.*</ID>
|
||||
<ID>WildcardImport:MockCryptoService.kt$import net.corda.nodeapi.internal.cryptoservice.*</ID>
|
||||
<ID>WildcardImport:MockKeyManagementService.kt$import net.corda.core.crypto.*</ID>
|
||||
<ID>WildcardImport:MockNetworkTest.kt$import org.junit.Assert.*</ID>
|
||||
<ID>WildcardImport:MockNodeMessagingService.kt$import net.corda.node.services.messaging.*</ID>
|
||||
@ -4959,6 +4981,7 @@
|
||||
<ID>WildcardImport:ThrowableSerializer.kt$import net.corda.serialization.internal.amqp.*</ID>
|
||||
<ID>WildcardImport:TimedFlowTests.kt$import net.corda.core.flows.*</ID>
|
||||
<ID>WildcardImport:TimedFlowTests.kt$import net.corda.testing.node.internal.*</ID>
|
||||
<ID>WildcardImport:TlsDiffAlgorithmsTest.kt$import javax.net.ssl.*</ID>
|
||||
<ID>WildcardImport:TlsDiffProtocolsTest.kt$import javax.net.ssl.*</ID>
|
||||
<ID>WildcardImport:TopLevelTransition.kt$import net.corda.node.services.statemachine.*</ID>
|
||||
<ID>WildcardImport:TraderDemoTest.kt$import net.corda.testing.driver.*</ID>
|
||||
|
@ -158,6 +158,7 @@ This file is based on or incorporates material from the projects listed below (T
|
||||
150. snappy
|
||||
151. sshd-core
|
||||
152. sshd-pam
|
||||
153. netty-tcnative-boringssl-static
|
||||
|
||||
================================================
|
||||
================================================
|
||||
@ -395,6 +396,10 @@ netty-resolver 4.1.29.Final
|
||||
https://netty.io/
|
||||
https://github.com/netty/netty/blob/4.1/LICENSE.txt
|
||||
|
||||
netty-tcnative-boringssl-static 2.0.14.Final
|
||||
https://netty.io/
|
||||
https://github.com/netty/netty/blob/4.1/LICENSE.txt
|
||||
|
||||
netty-transport 4.1.29.Final
|
||||
https://netty.io/
|
||||
https://github.com/netty/netty/blob/4.1/LICENSE.txt
|
||||
|
@ -51,6 +51,7 @@ dependencies {
|
||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
testCompile project(':node-driver')
|
||||
testCompile project(':test-utils')
|
||||
|
||||
compile ("org.apache.activemq:artemis-amqp-protocol:${artemis_version}") {
|
||||
// Gains our proton-j version from core module.
|
||||
|
@ -50,7 +50,7 @@ object X509Utilities {
|
||||
const val CORDA_CLIENT_CA = "cordaclientca"
|
||||
|
||||
// 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].
|
||||
// future and stick to [a-z0-9].
|
||||
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"
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.nodeapi.internal.cryptoservice
|
||||
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
@ -64,6 +65,87 @@ interface CryptoService : SignOnlyCryptoService {
|
||||
* Returns the type of the service.
|
||||
*/
|
||||
fun getType(): SupportedCryptoServices
|
||||
|
||||
|
||||
// ******************************************************
|
||||
// ENTERPRISE ONLY CODE FOR WRAPPING KEYS API STARTS HERE
|
||||
// ******************************************************
|
||||
|
||||
/**
|
||||
* Generates a new key to be used as a wrapping key.
|
||||
*
|
||||
* @param alias the alias under which the created wrapping key is created.
|
||||
* @param failIfExists a flag indicating whether the method should fail if a key already exists under the provided alias or return normally without overriding the key.
|
||||
* The default value is true.
|
||||
*
|
||||
* @throws IllegalArgumentException if a key already exists under this alias (and [failIfExists] is set to true].
|
||||
*/
|
||||
fun createWrappingKey(alias: String, failIfExists: Boolean = true)
|
||||
|
||||
/**
|
||||
* The size of the wrapping key, as specified in the standard. See: https://csrc.nist.gov/publications/detail/sp/800-38f/final
|
||||
* All underlying implementations should use this key size, unless there is a specific reason for not doing so.
|
||||
*/
|
||||
fun wrappingKeySize(): Int = 256
|
||||
|
||||
/**
|
||||
* Generates an asymmetric key pair, returning the public key and the private key material wrapped using the specified wrapping key.
|
||||
*
|
||||
* @param masterKeyAlias the alias of the key to be used as a wrapping key.
|
||||
* @param childKeyScheme the parameters of the key pair to be generated.
|
||||
*
|
||||
* @throws IllegalStateException if there is no master key existing under the alias specified ([masterKeyAlias]).
|
||||
*/
|
||||
fun generateWrappedKeyPair(masterKeyAlias: String, childKeyScheme: SignatureScheme = defaultWrappingSignatureScheme()): Pair<PublicKey, WrappedPrivateKey>
|
||||
|
||||
/**
|
||||
* Unwraps the provided wrapped key, using the specified wrapping key and signs the provided payload.
|
||||
*
|
||||
* @param masterKeyAlias the alias of the key to be used as a wrapping key.
|
||||
* @param wrappedPrivateKey the private key to be used for signing in a wrapped form.
|
||||
* @param payloadToSign the payload to be signed.
|
||||
*
|
||||
* @throws IllegalStateException if there is no master key existing under the alias specified ([masterKeyAlias]).
|
||||
*/
|
||||
fun sign(masterKeyAlias: String, wrappedPrivateKey: WrappedPrivateKey, payloadToSign: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* Returns the [WrappingMode] supported by the associated implementation.
|
||||
*
|
||||
* If no mode is supported, then null will be returned by this method and all the associated operations will throw an [UnsupportedOperationException].
|
||||
* Note: the long-term plan is to completely eradicate this case and have all implementations support one of the modes, even the degraded one.
|
||||
* As a result, this optionality is introduced for practical reasons and might be removed in the next major release,
|
||||
* when support will be added for all the existing implementations.
|
||||
*/
|
||||
fun getWrappingMode(): WrappingMode?
|
||||
|
||||
/**
|
||||
* Returns the [SignatureScheme] that should be used with this [CryptoService] when generating wrapped keys
|
||||
*/
|
||||
fun defaultWrappingSignatureScheme(): SignatureScheme = Crypto.ECDSA_SECP256R1_SHA256
|
||||
|
||||
// *****************************************************
|
||||
// ENTERPRISE ONLY CODE FOR WRAPPING KEYS API ENDS HERE
|
||||
// *****************************************************
|
||||
}
|
||||
|
||||
open class CryptoServiceException(message: String?, cause: Throwable? = null) : Exception(message, cause)
|
||||
/**
|
||||
* If an exception is deemed unrecoverable then it must be set with the flag ``isRecoverable=false``
|
||||
*
|
||||
* [ManagedCryptoService] will assume any [Throwable] which isn't a [CryptoServiceException] is recoverable and
|
||||
* will wrap it in a [CryptoServiceException] with ``isRecoverable=true``.
|
||||
*/
|
||||
open class CryptoServiceException(message: String?, cause: Throwable? = null, val isRecoverable: Boolean = true) : Exception(message, cause)
|
||||
|
||||
enum class WrappingMode {
|
||||
/**
|
||||
* In degraded mode, wrapped keys' material is encrypted at rest, but it's temporarily exposed during key generation and signing.
|
||||
*/
|
||||
DEGRADED_WRAPPED,
|
||||
/**
|
||||
* In normal mode, wrapped keys' material is encrypted, never exposed to the application and only accessible from inside the HSM.
|
||||
*/
|
||||
WRAPPED
|
||||
}
|
||||
|
||||
class WrappedPrivateKey(val keyMaterial: ByteArray, val signatureScheme: SignatureScheme)
|
@ -2,29 +2,44 @@ 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.Instances.getSignatureInstance
|
||||
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.utilities.detailedLogger
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.nodeapi.internal.config.CertificateStore
|
||||
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
|
||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
|
||||
import net.corda.nodeapi.internal.crypto.save
|
||||
import net.corda.nodeapi.internal.cryptoservice.*
|
||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
|
||||
import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStore
|
||||
import java.security.PublicKey
|
||||
import java.security.Signature
|
||||
import java.nio.file.Path
|
||||
import java.security.*
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
/**
|
||||
* Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations
|
||||
* and a Java KeyStore in the form of [CertificateStore] to store private keys.
|
||||
* This service reuses the [NodeConfiguration.signingCertificateStore] to store keys.
|
||||
*
|
||||
* The [wrappingKeyStorePath] must be provided in order to execute any wrapping operations (e.g. [createWrappingKey], [generateWrappedKeyPair])
|
||||
*/
|
||||
class BCCryptoService(private val legalName: X500Principal, private val certificateStoreSupplier: CertificateStoreSupplier) : CryptoService {
|
||||
class BCCryptoService(private val legalName: X500Principal,
|
||||
private val certificateStoreSupplier: CertificateStoreSupplier,
|
||||
private val wrappingKeyStorePath: Path? = null) : CryptoService {
|
||||
|
||||
private companion object {
|
||||
val detailedLogger = detailedLogger()
|
||||
}
|
||||
|
||||
override fun getType(): SupportedCryptoServices = SupportedCryptoServices.BC_SIMPLE
|
||||
|
||||
@ -32,9 +47,22 @@ class BCCryptoService(private val legalName: X500Principal, private val certific
|
||||
// TODO make it private when E2ETestKeyManagementService does not require direct access to the private key.
|
||||
var certificateStore: CertificateStore = certificateStoreSupplier.get(true)
|
||||
|
||||
/**
|
||||
* JKS keystore does not support storage for secret keys, so the existing keystore cannot be re-used.
|
||||
* JCEKS keystore supports storage of symmetric keys according to the spec, but there are several issues around classloaders and deserialization filtering (see links below).
|
||||
* - https://stackoverflow.com/questions/49990904/what-is-the-cause-of-java-security-unrecoverablekeyexception-rejected-by-the-j
|
||||
* - https://stackoverflow.com/questions/50393533/java-io-ioexception-invalid-secret-key-format-when-opening-jceks-key-store-wi
|
||||
* Thus, PKCS12 is used for storing the wrapping key.
|
||||
*/
|
||||
private val wrappingKeyStore: KeyStore by lazy {
|
||||
loadOrCreateKeyStore(wrappingKeyStorePath!!, certificateStore.password, "PKCS12")
|
||||
}
|
||||
|
||||
override fun generateKeyPair(alias: String, scheme: SignatureScheme): PublicKey {
|
||||
try {
|
||||
detailedLogger.trace { "CryptoService(action=generate_key_pair_start;alias=$alias;scheme=$scheme)" }
|
||||
val keyPair = Crypto.generateKeyPair(scheme)
|
||||
detailedLogger.trace { "CryptoService(action=generate_key_pair_end;alias=$alias;scheme=$scheme)" }
|
||||
importKey(alias, keyPair)
|
||||
return keyPair.public
|
||||
} catch (e: Exception) {
|
||||
@ -43,14 +71,19 @@ class BCCryptoService(private val legalName: X500Principal, private val certific
|
||||
}
|
||||
|
||||
override fun containsKey(alias: String): Boolean {
|
||||
return certificateStore.contains(alias)
|
||||
return if (wrappingKeyStorePath == null) {
|
||||
certificateStore.contains(alias)
|
||||
} else {
|
||||
certificateStore.contains(alias) || wrappingKeyStore.containsAlias(alias)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getPublicKey(alias: String): PublicKey {
|
||||
try {
|
||||
return certificateStore.query { getPublicKey(alias) }
|
||||
} catch (e: Exception) {
|
||||
throw CryptoServiceException("Cannot get public key for alias $alias", e)
|
||||
throw CryptoServiceException("Cannot get public key for alias $alias", e, isRecoverable = false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,13 +101,16 @@ class BCCryptoService(private val legalName: X500Principal, private val certific
|
||||
private fun signWithAlgorithm(alias: String, data: ByteArray, signAlgorithm: String): ByteArray {
|
||||
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
|
||||
val signature = Signature.getInstance(signAlgorithm, cordaBouncyCastleProvider)
|
||||
detailedLogger.trace { "CryptoService(action=signing_start;alias=$alias;algorithm=$signAlgorithm)" }
|
||||
signature.initSign(privateKey, newSecureRandom())
|
||||
signature.update(data)
|
||||
detailedLogger.trace { "CryptoService(action=signing_end;alias=$alias;algorithm=$signAlgorithm)" }
|
||||
return signature.sign()
|
||||
}
|
||||
|
||||
override fun getSigner(alias: String): ContentSigner {
|
||||
try {
|
||||
detailedLogger.trace { "CryptoService(action=get_signer;alias=$alias)" }
|
||||
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
|
||||
val signatureScheme = Crypto.findSignatureScheme(privateKey)
|
||||
return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())
|
||||
@ -105,10 +141,81 @@ class BCCryptoService(private val legalName: X500Principal, private val certific
|
||||
try {
|
||||
// Store a self-signed certificate, as Keystore requires to store certificates instead of public keys.
|
||||
// We could probably add a null cert, but we store a self-signed cert that will be used to retrieve the public key.
|
||||
detailedLogger.trace { "CryptoService(action=key_import;alias=$alias)" }
|
||||
val cert = X509Utilities.createSelfSignedCACertificate(legalName, keyPair)
|
||||
certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) }
|
||||
} catch (e: Exception) {
|
||||
throw CryptoServiceException("Cannot import key with alias $alias", e)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun createWrappingKey(alias: String, failIfExists: Boolean) {
|
||||
if (wrappingKeyStore.containsAlias(alias)) {
|
||||
when (failIfExists) {
|
||||
true -> throw IllegalArgumentException("There is an existing key with the alias: $alias")
|
||||
false -> return
|
||||
}
|
||||
}
|
||||
|
||||
val keyGenerator = KeyGenerator.getInstance("AES")
|
||||
keyGenerator.init(wrappingKeySize())
|
||||
val wrappingKey = keyGenerator.generateKey()
|
||||
wrappingKeyStore.setEntry(alias, KeyStore.SecretKeyEntry(wrappingKey), KeyStore.PasswordProtection(certificateStore.entryPassword.toCharArray()))
|
||||
wrappingKeyStore.save(wrappingKeyStorePath!!, certificateStore.password)
|
||||
}
|
||||
|
||||
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)
|
||||
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))
|
||||
}
|
||||
|
||||
override fun sign(masterKeyAlias: String, wrappedPrivateKey: WrappedPrivateKey, payloadToSign: ByteArray): ByteArray {
|
||||
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)
|
||||
cipher.init(Cipher.UNWRAP_MODE, wrappingKey)
|
||||
|
||||
val privateKey = cipher.unwrap(wrappedPrivateKey.keyMaterial, keyAlgorithmFromScheme(wrappedPrivateKey.signatureScheme), Cipher.PRIVATE_KEY) as PrivateKey
|
||||
|
||||
val signature = getSignatureInstance(wrappedPrivateKey.signatureScheme.signatureName, cordaBouncyCastleProvider)
|
||||
signature.initSign(privateKey, newSecureRandom())
|
||||
signature.update(payloadToSign)
|
||||
return signature.sign()
|
||||
}
|
||||
|
||||
override fun getWrappingMode(): WrappingMode? = WrappingMode.DEGRADED_WRAPPED
|
||||
|
||||
private fun keyPairGeneratorFromScheme(scheme: SignatureScheme): KeyPairGenerator {
|
||||
val algorithm = keyAlgorithmFromScheme(scheme)
|
||||
val keyPairGenerator = KeyPairGenerator.getInstance(algorithm, cordaBouncyCastleProvider)
|
||||
when (scheme) {
|
||||
Crypto.ECDSA_SECP256R1_SHA256 -> keyPairGenerator.initialize(ECGenParameterSpec("secp256r1"))
|
||||
Crypto.ECDSA_SECP256K1_SHA256 -> keyPairGenerator.initialize(ECGenParameterSpec("secp256k1"))
|
||||
Crypto.RSA_SHA256 -> keyPairGenerator.initialize(scheme.keySize!!)
|
||||
else -> throw IllegalArgumentException("No mapping for scheme ID ${scheme.schemeNumberID}")
|
||||
}
|
||||
return keyPairGenerator
|
||||
}
|
||||
|
||||
private fun keyAlgorithmFromScheme(scheme: SignatureScheme): String = when (scheme) {
|
||||
Crypto.ECDSA_SECP256R1_SHA256, Crypto.ECDSA_SECP256K1_SHA256 -> "EC"
|
||||
Crypto.RSA_SHA256 -> "RSA"
|
||||
else -> throw IllegalArgumentException("No algorithm for scheme ID ${scheme.schemeNumberID}")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,8 +29,6 @@ class AliasPrivateKeyTest {
|
||||
assertTrue { signingCertStore.contains(alias) }
|
||||
// We can retrieve the certificate.
|
||||
assertEquals(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT, signingCertStore[alias])
|
||||
// Although we can store an AliasPrivateKey, we cannot retrieve it. But, it's fine as we use certStore for storing/handling certs
|
||||
// only.
|
||||
assertEquals(aliasPrivateKey, signingCertStore.query { getPrivateKey(alias, "entrypassword") })
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,173 @@
|
||||
package net.corda.nodeapi.internal.crypto
|
||||
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.utilities.Try
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.config.CertificateStore
|
||||
import net.corda.nodeapi.internal.protonwrapper.netty.init
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import javax.net.ssl.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class TlsDiffAlgorithmsTest(private val serverAlgo: String, private val clientAlgo: String,
|
||||
private val cipherSuites: Array<String>, private val shouldFail: Boolean) {
|
||||
companion object {
|
||||
private val CIPHER_SUITES_ALL = arrayOf(
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||
)
|
||||
|
||||
private val CIPHER_SUITES_JUST_RSA = arrayOf(
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||
)
|
||||
|
||||
private val CIPHER_SUITES_JUST_EC = arrayOf(
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
|
||||
)
|
||||
|
||||
@Parameterized.Parameters(name = "ServerAlgo: {0}, ClientAlgo: {1}, Should fail: {3}")
|
||||
@JvmStatic
|
||||
fun data() = listOf(
|
||||
arrayOf("ec", "ec", CIPHER_SUITES_ALL, false), arrayOf("rsa", "rsa", CIPHER_SUITES_ALL, false), arrayOf("ec", "rsa", CIPHER_SUITES_ALL, false), arrayOf("rsa", "ec", CIPHER_SUITES_ALL, false),
|
||||
arrayOf("ec", "ec", CIPHER_SUITES_JUST_RSA, true), arrayOf("rsa", "rsa", CIPHER_SUITES_JUST_RSA, false), arrayOf("ec", "rsa", CIPHER_SUITES_JUST_RSA, true), arrayOf("rsa", "ec", CIPHER_SUITES_JUST_RSA, false),
|
||||
arrayOf("ec", "ec", CIPHER_SUITES_JUST_EC, false), arrayOf("rsa", "rsa", CIPHER_SUITES_JUST_EC, true), arrayOf("ec", "rsa", CIPHER_SUITES_JUST_EC, false), arrayOf("rsa", "ec", CIPHER_SUITES_JUST_EC, true)
|
||||
)
|
||||
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun testClientServerTlsExchange() {
|
||||
|
||||
//System.setProperty("javax.net.debug", "all")
|
||||
|
||||
logger.info("Testing: ServerAlgo: $serverAlgo, ClientAlgo: $clientAlgo, Suites: ${cipherSuites.toList()}, Should fail: $shouldFail")
|
||||
|
||||
val trustStore = CertificateStore.fromResource("net/corda/nodeapi/internal/crypto/keystores/trust.jks", "trustpass", "trustpass")
|
||||
val rootCa = trustStore.value.getCertificate("root")
|
||||
|
||||
val serverKeyStore = CertificateStore.fromResource("net/corda/nodeapi/internal/crypto/keystores/float_$serverAlgo.jks", "floatpass", "floatpass")
|
||||
val serverCa = serverKeyStore.value.getCertificateAndKeyPair("floatcert", "floatpass")
|
||||
|
||||
val clientKeyStore = CertificateStore.fromResource("net/corda/nodeapi/internal/crypto/keystores/bridge_$clientAlgo.jks", "bridgepass", "bridgepass")
|
||||
//val clientCa = clientKeyStore.value.getCertificateAndKeyPair("bridgecert", "bridgepass")
|
||||
|
||||
val serverSocketFactory = createSslContext(serverKeyStore, trustStore).serverSocketFactory
|
||||
val clientSocketFactory = createSslContext(clientKeyStore, trustStore).socketFactory
|
||||
|
||||
val serverSocket = (serverSocketFactory.createServerSocket(0) as SSLServerSocket).apply {
|
||||
// use 0 to get first free socket
|
||||
val serverParams = SSLParameters(cipherSuites, arrayOf("TLSv1.2"))
|
||||
serverParams.wantClientAuth = true
|
||||
serverParams.needClientAuth = true
|
||||
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
|
||||
sslParameters = serverParams
|
||||
useClientMode = false
|
||||
}
|
||||
|
||||
val clientSocket = (clientSocketFactory.createSocket() as SSLSocket).apply {
|
||||
val clientParams = SSLParameters(cipherSuites, arrayOf("TLSv1.2"))
|
||||
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
|
||||
sslParameters = clientParams
|
||||
useClientMode = true
|
||||
// We need to specify this explicitly because by default the client binds to 'localhost' and we want it to bind
|
||||
// to whatever <hostname> resolves to(as that's what the server binds to). In particular on Debian <hostname>
|
||||
// resolves to 127.0.1.1 instead of the external address of the interface, so the ssl handshake fails.
|
||||
bind(InetSocketAddress(InetAddress.getLocalHost(), 0))
|
||||
}
|
||||
|
||||
val lock = Object()
|
||||
var done = false
|
||||
var serverError = false
|
||||
|
||||
val testPhrase = "Hello World"
|
||||
val serverThread = thread {
|
||||
try {
|
||||
val sslServerSocket = serverSocket.accept()
|
||||
assertTrue(sslServerSocket.isConnected)
|
||||
val serverInput = DataInputStream(sslServerSocket.inputStream)
|
||||
val receivedString = serverInput.readUTF()
|
||||
assertEquals(testPhrase, receivedString)
|
||||
synchronized(lock) {
|
||||
done = true
|
||||
lock.notifyAll()
|
||||
}
|
||||
sslServerSocket.close()
|
||||
} catch (ex: Exception) {
|
||||
serverError = true
|
||||
}
|
||||
}
|
||||
|
||||
clientSocket.connect(InetSocketAddress(InetAddress.getLocalHost(), serverSocket.localPort))
|
||||
assertTrue(clientSocket.isConnected)
|
||||
|
||||
// Double check hostname manually
|
||||
val peerChainTry = Try.on { clientSocket.session.peerCertificates.x509 }
|
||||
assertEquals(!shouldFail, peerChainTry.isSuccess)
|
||||
when(peerChainTry) {
|
||||
is Try.Success -> {
|
||||
val peerChain = peerChainTry.getOrThrow()
|
||||
val peerX500Principal = peerChain[0].subjectX500Principal
|
||||
assertEquals(serverCa.certificate.subjectX500Principal, peerX500Principal)
|
||||
X509Utilities.validateCertificateChain(rootCa, peerChain)
|
||||
with(DataOutputStream(clientSocket.outputStream)) {
|
||||
writeUTF(testPhrase)
|
||||
}
|
||||
var timeout = 0
|
||||
synchronized(lock) {
|
||||
while (!done) {
|
||||
timeout++
|
||||
if (timeout > 10) throw IOException("Timed out waiting for server to complete")
|
||||
lock.wait(1000)
|
||||
}
|
||||
}
|
||||
|
||||
clientSocket.close()
|
||||
serverThread.join(1000)
|
||||
assertFalse { serverError }
|
||||
serverSocket.close()
|
||||
assertTrue(done)
|
||||
}
|
||||
is Try.Failure -> {
|
||||
Assertions.assertThatThrownBy {
|
||||
peerChainTry.getOrThrow()
|
||||
}.isInstanceOf(SSLPeerUnverifiedException::class.java)
|
||||
|
||||
// Tidy-up in case of failure
|
||||
clientSocket.close()
|
||||
serverSocket.close()
|
||||
serverThread.interrupt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSslContext(keyStore: CertificateStore, trustStore: CertificateStore): SSLContext {
|
||||
return SSLContext.getInstance("TLS").apply {
|
||||
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
||||
keyManagerFactory.init(keyStore)
|
||||
val keyManagers = keyManagerFactory.keyManagers
|
||||
val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
trustMgrFactory.init(trustStore)
|
||||
val trustManagers = trustMgrFactory.trustManagers
|
||||
init(keyManagers, trustManagers, newSecureRandom())
|
||||
}
|
||||
}
|
||||
}
|
@ -18,13 +18,12 @@ import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import javax.net.ssl.*
|
||||
import javax.net.ssl.SNIHostName
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.assertNotNull
|
||||
import javax.net.ssl.SNIHostName
|
||||
import javax.net.ssl.StandardConstants
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* This test checks compatibility of TLS 1.2 and 1.3 communication using different cipher suites with SNI header
|
||||
|
@ -1,5 +1,9 @@
|
||||
package net.corda.nodeapi.internal.crypto
|
||||
|
||||
|
||||
import io.netty.handler.ssl.ClientAuth
|
||||
import io.netty.handler.ssl.SslContextBuilder
|
||||
import io.netty.handler.ssl.SslProvider
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.Crypto.COMPOSITE_KEY
|
||||
import net.corda.core.crypto.Crypto.ECDSA_SECP256K1_SHA256
|
||||
@ -15,6 +19,8 @@ import net.corda.core.internal.div
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.hours
|
||||
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
||||
import net.corda.nodeapi.internal.createDevNodeCa
|
||||
@ -30,12 +36,15 @@ import net.corda.serialization.internal.amqp.amqpMagic
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.internal.NettyTestClient
|
||||
import net.corda.testing.internal.NettyTestHandler
|
||||
import net.corda.testing.internal.NettyTestServer
|
||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||
import net.corda.testing.internal.stubs.CertificateStoreStubs
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey
|
||||
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@ -66,6 +75,9 @@ class X509UtilitiesTest {
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||
)
|
||||
|
||||
val portAllocation = incrementalPortAllocation()
|
||||
|
||||
// We ensure that all of the algorithms are both used (at least once) as first and second in the following [Pair]s.
|
||||
// We also add [DEFAULT_TLS_SIGNATURE_SCHEME] and [DEFAULT_IDENTITY_SIGNATURE_SCHEME] combinations for consistency.
|
||||
val certChainSchemeCombinations = listOf(
|
||||
@ -349,6 +361,65 @@ class X509UtilitiesTest {
|
||||
assertTrue(done)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create server cert and use in OpenSSL channel`() {
|
||||
val sslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(tempFolder.root.toPath(), keyStorePassword = "serverstorepass")
|
||||
|
||||
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
|
||||
|
||||
// Generate server cert and private key and populate another keystore suitable for SSL
|
||||
sslConfig.keyStore.get(true).registerDevP2pCertificates(MEGA_CORP.name, rootCa.certificate, intermediateCa)
|
||||
sslConfig.createTrustStore(rootCa.certificate)
|
||||
|
||||
val keyStore = sslConfig.keyStore.get()
|
||||
val trustStore = sslConfig.trustStore.get()
|
||||
|
||||
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
||||
keyManagerFactory.init(keyStore)
|
||||
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
trustManagerFactory.init(trustStore)
|
||||
|
||||
|
||||
val sslServerContext = SslContextBuilder
|
||||
.forServer(keyManagerFactory)
|
||||
.trustManager(trustManagerFactory)
|
||||
.clientAuth(ClientAuth.REQUIRE)
|
||||
.ciphers(CIPHER_SUITES.toMutableList())
|
||||
.sslProvider(SslProvider.OPENSSL)
|
||||
.protocols("TLSv1.2")
|
||||
.build()
|
||||
val sslClientContext = SslContextBuilder
|
||||
.forClient()
|
||||
.keyManager(keyManagerFactory)
|
||||
.trustManager(trustManagerFactory)
|
||||
.ciphers(CIPHER_SUITES.toMutableList())
|
||||
.sslProvider(SslProvider.OPENSSL)
|
||||
.protocols("TLSv1.2")
|
||||
.build()
|
||||
val serverHandler = NettyTestHandler { ctx, msg -> ctx?.writeAndFlush(msg) }
|
||||
val clientHandler = NettyTestHandler { _, msg -> assertEquals("Hello", NettyTestHandler.readString(msg)) }
|
||||
NettyTestServer(sslServerContext, serverHandler, portAllocation.nextPort()).use { server ->
|
||||
server.start()
|
||||
NettyTestClient(sslClientContext, InetAddress.getLocalHost().canonicalHostName, server.port, clientHandler).use { client ->
|
||||
client.start()
|
||||
|
||||
clientHandler.writeString("Hello")
|
||||
val readCalled = clientHandler.waitForReadCalled()
|
||||
clientHandler.rethrowIfFailed()
|
||||
serverHandler.rethrowIfFailed()
|
||||
assertTrue(readCalled)
|
||||
assertEquals(1, serverHandler.readCalledCounter)
|
||||
assertEquals(1, clientHandler.readCalledCounter)
|
||||
|
||||
val peerChain = client.engine!!.session.peerCertificates.x509
|
||||
val peerX500Principal = peerChain[0].subjectX500Principal
|
||||
assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal)
|
||||
X509Utilities.validateCertificateChain(rootCa.certificate, peerChain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name
|
||||
|
||||
private fun MutualSslConfiguration.createTrustStore(rootCert: X509Certificate) {
|
||||
@ -452,4 +523,34 @@ class X509UtilitiesTest {
|
||||
assertEquals(childKeyPair.public, reloadedPublicKey)
|
||||
assertEquals(childKeyPair.private, reloadedPrivateKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check certificate validity or print warning if expiry is within 30 days`() {
|
||||
val keyPair = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
|
||||
val cert = X509Utilities.createSelfSignedCACertificate(testName, keyPair, 0.days to 50.days)
|
||||
val today = cert.notBefore
|
||||
var warnings = 0
|
||||
cert.checkValidity({ "No error expected" }, { fail("No warning expected") }, today)
|
||||
cert.checkValidity({ "No error expected" }, { fail("No warning expected") }, Date.from(today.toInstant() + 20.days))
|
||||
cert.checkValidity({ "No error expected" }, { daysToExpiry ->
|
||||
assertEquals(30, daysToExpiry)
|
||||
warnings++
|
||||
}, Date.from(today.toInstant() + 20.days + 3.hours))
|
||||
cert.checkValidity({ "No error expected" }, { daysToExpiry ->
|
||||
assertEquals(11, daysToExpiry)
|
||||
warnings++
|
||||
}, Date.from(today.toInstant() + 40.days))
|
||||
cert.checkValidity({ "No error expected" }, { daysToExpiry ->
|
||||
assertEquals(1, daysToExpiry)
|
||||
warnings++
|
||||
}, Date.from(today.toInstant() + 49.days + 20.hours))
|
||||
assertEquals(3, warnings)
|
||||
assertFailsWith(IllegalArgumentException::class, "Error text") {
|
||||
cert.checkValidity({ "Error text" }, { }, Date.from(today.toInstant() + 50.days + 1.hours))
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class, "Error text") {
|
||||
cert.checkValidity({ "Error text" }, { }, Date.from(today.toInstant() + 51.days))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,37 +2,35 @@ 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
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
|
||||
import net.corda.nodeapi.internal.cryptoservice.WrappedPrivateKey
|
||||
import net.corda.nodeapi.internal.cryptoservice.WrappingMode
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.internal.stubs.CertificateStoreStubs
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
|
||||
import org.bouncycastle.asn1.sec.SECObjectIdentifiers
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
|
||||
import org.bouncycastle.jce.ECNamedCurveTable
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.file.Path
|
||||
import java.security.*
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class BCCryptoServiceTests {
|
||||
|
||||
companion object {
|
||||
val clearData = "data".toByteArray()
|
||||
}
|
||||
@ -46,16 +44,20 @@ class BCCryptoServiceTests {
|
||||
@JvmField
|
||||
val temporaryKeystoreFolder = TemporaryFolder()
|
||||
|
||||
lateinit var certificatesDirectory: Path
|
||||
lateinit var wrappingKeyStorePath: Path
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val baseDirectory = temporaryFolder.root.toPath()
|
||||
val certificatesDirectory = baseDirectory / "certificates"
|
||||
certificatesDirectory = baseDirectory / "certificates"
|
||||
signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
|
||||
wrappingKeyStorePath = certificatesDirectory / "wrappingkeystore.pkcs12"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BCCryptoService generate key pair and sign both data and cert`() {
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||
// Testing every supported scheme.
|
||||
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY }.forEach { generateKeyAndSignForScheme(cryptoService, it) }
|
||||
}
|
||||
@ -87,7 +89,7 @@ class BCCryptoServiceTests {
|
||||
|
||||
@Test
|
||||
fun `BCCryptoService generate key pair and sign with existing schemes`() {
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||
// Testing every supported scheme.
|
||||
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY
|
||||
&& it.signatureName != "SHA512WITHSPHINCS256"}.forEach {
|
||||
@ -121,7 +123,7 @@ class BCCryptoServiceTests {
|
||||
private fun signAndVerify(signAlgo: String, alias: String, keyTypeAlgo: String): Boolean {
|
||||
val keyPairGenerator = KeyPairGenerator.getInstance(keyTypeAlgo)
|
||||
val keyPair = keyPairGenerator.genKeyPair()
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, createKeystore(alias, keyPair))
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, createKeystore(alias, keyPair), wrappingKeyStorePath)
|
||||
assertTrue { cryptoService.containsKey(alias) }
|
||||
val signatureData = cryptoService.sign(alias, clearData, signAlgo)
|
||||
return verify(signAlgo, cryptoService.getPublicKey(alias), signatureData, clearData)
|
||||
@ -164,10 +166,90 @@ class BCCryptoServiceTests {
|
||||
@Test
|
||||
fun `When key does not exist getPublicKey, sign and getSigner should throw`() {
|
||||
val nonExistingAlias = "nonExistingAlias"
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||
assertFalse { cryptoService.containsKey(nonExistingAlias) }
|
||||
assertFailsWith<CryptoServiceException> { cryptoService.getPublicKey(nonExistingAlias) }
|
||||
assertFailsWith<CryptoServiceException> { cryptoService.sign(nonExistingAlias, clearData) }
|
||||
assertFailsWith<CryptoServiceException> { cryptoService.getSigner(nonExistingAlias) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cryptoService supports degraded mode of wrapping`() {
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||
val supportedMode = cryptoService.getWrappingMode()
|
||||
|
||||
assertThat(supportedMode).isEqualTo(WrappingMode.DEGRADED_WRAPPED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cryptoService does not fail when requested to create same wrapping key twice with failIfExists is false`() {
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||
|
||||
val keyAlias = UUID.randomUUID().toString()
|
||||
cryptoService.createWrappingKey(keyAlias)
|
||||
cryptoService.createWrappingKey(keyAlias, failIfExists = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cryptoService does fail when requested to create same wrapping key twice with failIfExists is true`() {
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||
|
||||
val keyAlias = UUID.randomUUID().toString()
|
||||
cryptoService.createWrappingKey(keyAlias)
|
||||
assertThat(cryptoService.containsKey(keyAlias)).isTrue()
|
||||
assertThatThrownBy { cryptoService.createWrappingKey(keyAlias) }
|
||||
.isInstanceOf(IllegalArgumentException::class.java)
|
||||
.hasMessage("There is an existing key with the alias: $keyAlias")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cryptoService fails when asked to generate wrapped key pair or sign, but the master key specified does not exist`() {
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||
|
||||
val wrappingKeyAlias = UUID.randomUUID().toString()
|
||||
|
||||
assertThatThrownBy { cryptoService.generateWrappedKeyPair(wrappingKeyAlias) }
|
||||
.isInstanceOf(IllegalStateException::class.java)
|
||||
.hasMessage("There is no master key under the alias: $wrappingKeyAlias")
|
||||
|
||||
val dummyWrappedPrivateKey = WrappedPrivateKey("key".toByteArray(), Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val data = "data".toByteArray()
|
||||
assertThatThrownBy { cryptoService.sign(wrappingKeyAlias, dummyWrappedPrivateKey, data) }
|
||||
.isInstanceOf(IllegalStateException::class.java)
|
||||
.hasMessage("There is no master key under the alias: $wrappingKeyAlias")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cryptoService can generate wrapped key pair and sign with the private key successfully, using default algorithm`() {
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||
|
||||
val wrappingKeyAlias = UUID.randomUUID().toString()
|
||||
cryptoService.createWrappingKey(wrappingKeyAlias)
|
||||
generateWrappedKeyPairSignAndVerify(cryptoService, wrappingKeyAlias)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cryptoService can generate wrapped key pair and sign with the private key successfully`() {
|
||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||
|
||||
val wrappingKeyAlias = UUID.randomUUID().toString()
|
||||
cryptoService.createWrappingKey(wrappingKeyAlias)
|
||||
generateWrappedKeyPairSignAndVerify(cryptoService, wrappingKeyAlias, Crypto.ECDSA_SECP256R1_SHA256)
|
||||
generateWrappedKeyPairSignAndVerify(cryptoService, wrappingKeyAlias, Crypto.ECDSA_SECP256K1_SHA256)
|
||||
generateWrappedKeyPairSignAndVerify(cryptoService, wrappingKeyAlias, Crypto.RSA_SHA256)
|
||||
}
|
||||
|
||||
private fun generateWrappedKeyPairSignAndVerify(cryptoService: CryptoService, wrappingKeyAlias: String, algorithm: SignatureScheme? = null) {
|
||||
val data = "data".toByteArray()
|
||||
|
||||
val (publicKey, wrappedPrivateKey) = if (algorithm != null) {
|
||||
cryptoService.generateWrappedKeyPair(wrappingKeyAlias, algorithm)
|
||||
} else {
|
||||
cryptoService.generateWrappedKeyPair(wrappingKeyAlias)
|
||||
}
|
||||
|
||||
val signature = cryptoService.sign(wrappingKeyAlias, wrappedPrivateKey, data)
|
||||
|
||||
Crypto.doVerify(publicKey, signature, data)
|
||||
}
|
||||
}
|
||||
|
@ -18,21 +18,21 @@ keytool.exe -exportcert -rfc -alias floatroot -keystore floatca.jks -storepass c
|
||||
keytool.exe -importcert -noprompt -file root.pem -alias root -keystore trust.jks -storepass trustpass
|
||||
|
||||
// Create a chain for EC Bridge
|
||||
keytool.exe -certreq -alias bridgecert -keystore bridge_ec.jks -storepass bridgepass -keypass bridgepass |keytool.exe -gencert -ext ku:c=dig,keyEncipherment -ext: eku:true=serverAuth,clientAuth -rfc -keystore floatca.jks -alias floatroot -storepass capass -keypass cakeypass > bridge_ec.pem
|
||||
keytool.exe -certreq -alias bridgecert -keystore bridge_ec.jks -storepass bridgepass -keypass bridgepass |keytool.exe -gencert -validity 1000 -ext ku:c=dig,keyEncipherment -ext: eku:true=serverAuth,clientAuth -rfc -keystore floatca.jks -alias floatroot -storepass capass -keypass cakeypass > bridge_ec.pem
|
||||
type root.pem bridge_ec.pem >> bridgechain_ec.pem
|
||||
keytool.exe -importcert -noprompt -file bridgechain_ec.pem -alias bridgecert -keystore bridge_ec.jks -storepass bridgepass -keypass bridgepass
|
||||
|
||||
// Create a chain for RSA Bridge
|
||||
keytool.exe -certreq -alias bridgecert -keystore bridge_rsa.jks -storepass bridgepass -keypass bridgepass |keytool.exe -gencert -ext ku:c=dig,keyEncipherment -ext: eku:true=serverAuth,clientAuth -rfc -keystore floatca.jks -alias floatroot -storepass capass -keypass cakeypass > bridge_rsa.pem
|
||||
keytool.exe -certreq -alias bridgecert -keystore bridge_rsa.jks -storepass bridgepass -keypass bridgepass |keytool.exe -gencert -validity 1000 -ext ku:c=dig,keyEncipherment -ext: eku:true=serverAuth,clientAuth -rfc -keystore floatca.jks -alias floatroot -storepass capass -keypass cakeypass > bridge_rsa.pem
|
||||
type root.pem bridge_rsa.pem >> bridgechain_rsa.pem
|
||||
keytool.exe -importcert -noprompt -file bridgechain_rsa.pem -alias bridgecert -keystore bridge_rsa.jks -storepass bridgepass -keypass bridgepass
|
||||
|
||||
// Create a chain for EC Float
|
||||
keytool.exe -certreq -alias floatcert -keystore float_ec.jks -storepass floatpass -keypass floatpass |keytool.exe -gencert -ext ku:c=dig,keyEncipherment -ext: eku::true=serverAuth,clientAuth -rfc -keystore floatca.jks -alias floatroot -storepass capass -keypass cakeypass > float_ec.pem
|
||||
keytool.exe -certreq -alias floatcert -keystore float_ec.jks -storepass floatpass -keypass floatpass |keytool.exe -gencert -validity 1000 -ext ku:c=dig,keyEncipherment -ext: eku::true=serverAuth,clientAuth -rfc -keystore floatca.jks -alias floatroot -storepass capass -keypass cakeypass > float_ec.pem
|
||||
type root.pem float_ec.pem >> floatchain_ec.pem
|
||||
keytool.exe -importcert -noprompt -file floatchain_ec.pem -alias floatcert -keystore float_ec.jks -storepass floatpass -keypass floatpass
|
||||
|
||||
// Create a chain for RSA Float
|
||||
keytool.exe -certreq -alias floatcert -keystore float_rsa.jks -storepass floatpass -keypass floatpass |keytool.exe -gencert -ext ku:c=dig,keyEncipherment -ext: eku::true=serverAuth,clientAuth -rfc -keystore floatca.jks -alias floatroot -storepass capass -keypass cakeypass > float_rsa.pem
|
||||
keytool.exe -certreq -alias floatcert -keystore float_rsa.jks -storepass floatpass -keypass floatpass |keytool.exe -gencert -validity 1000 -ext ku:c=dig,keyEncipherment -ext: eku::true=serverAuth,clientAuth -rfc -keystore floatca.jks -alias floatroot -storepass capass -keypass cakeypass > float_rsa.pem
|
||||
type root.pem float_rsa.pem >> floatchain_rsa.pem
|
||||
keytool.exe -importcert -noprompt -file floatchain_rsa.pem -alias floatcert -keystore float_rsa.jks -storepass floatpass -keypass floatpass
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -230,6 +230,9 @@ dependencies {
|
||||
// Optional New Relic JVM reporter, used to push metrics to the configured account associated with a newrelic.yml configuration. See https://mvnrepository.com/artifact/com.palominolabs.metrics/metrics-new-relic
|
||||
compile "com.palominolabs.metrics:metrics-new-relic:${metrics_new_relic_version}"
|
||||
|
||||
// Adding native SSL library to allow using native SSL with Artemis and AMQP
|
||||
compile "io.netty:netty-tcnative-boringssl-static:$tcnative_version"
|
||||
|
||||
// Required by JVMAgentUtil (x-compatible java 8 & 11 agent lookup mechanism)
|
||||
compile files("${System.properties['java.home']}/../lib/tools.jar")
|
||||
|
||||
|
@ -2,17 +2,23 @@ package net.corda.testing.node.internal
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.core.crypto.internal.Instances
|
||||
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||
import net.corda.nodeapi.internal.cryptoservice.*
|
||||
import net.corda.nodeapi.internal.cryptoservice.CryptoService
|
||||
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
|
||||
import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.Signature
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
|
||||
class MockCryptoService(initialKeyPairs: Map<String, KeyPair>) : CryptoService {
|
||||
|
||||
@ -20,6 +26,8 @@ class MockCryptoService(initialKeyPairs: Map<String, KeyPair>) : CryptoService {
|
||||
|
||||
private val aliasToKey: MutableMap<String, KeyPair> = mutableMapOf()
|
||||
|
||||
private val wrappingKeys: MutableMap<String, SecretKey> = mutableMapOf()
|
||||
|
||||
init {
|
||||
initialKeyPairs.forEach {
|
||||
aliasToKey[it.key] = it.value
|
||||
@ -68,4 +76,48 @@ class MockCryptoService(initialKeyPairs: Map<String, KeyPair>) : CryptoService {
|
||||
aliasToKey[alias] = keyPair
|
||||
return keyPair.public
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun createWrappingKey(alias: String, failIfExists: Boolean) {
|
||||
if (wrappingKeys[alias] != null) {
|
||||
when (failIfExists) {
|
||||
true -> throw IllegalArgumentException("There is an existing key with the alias: $alias")
|
||||
false -> return
|
||||
}
|
||||
}
|
||||
val keyGenerator = KeyGenerator.getInstance("AES")
|
||||
keyGenerator.init(wrappingKeySize())
|
||||
val wrappingKey = keyGenerator.generateKey()
|
||||
wrappingKeys[alias] = wrappingKey
|
||||
}
|
||||
|
||||
override fun generateWrappedKeyPair(masterKeyAlias: String, childKeyScheme: SignatureScheme): Pair<PublicKey, WrappedPrivateKey> {
|
||||
val wrappingKey = wrappingKeys[masterKeyAlias] ?: throw IllegalStateException("There is no master key under the alias: $masterKeyAlias")
|
||||
val keyPair = Crypto.generateKeyPair(childKeyScheme)
|
||||
val cipher = Cipher.getInstance("AES", cordaBouncyCastleProvider)
|
||||
cipher.init(Cipher.WRAP_MODE, wrappingKey)
|
||||
val privateKeyMaterialWrapped = cipher.wrap(keyPair.private)
|
||||
return Pair(keyPair.public, WrappedPrivateKey(privateKeyMaterialWrapped, childKeyScheme))
|
||||
}
|
||||
|
||||
override fun sign(masterKeyAlias: String, wrappedPrivateKey: WrappedPrivateKey, payloadToSign: ByteArray): ByteArray {
|
||||
val wrappingKey = wrappingKeys[masterKeyAlias] ?: throw IllegalStateException("There is no master key under the alias: $masterKeyAlias")
|
||||
val cipher = Cipher.getInstance("AES", cordaBouncyCastleProvider)
|
||||
cipher.init(Cipher.UNWRAP_MODE, wrappingKey)
|
||||
val privateKey = cipher.unwrap(wrappedPrivateKey.keyMaterial, keyAlgorithmFromScheme(wrappedPrivateKey.signatureScheme), Cipher.PRIVATE_KEY) as PrivateKey
|
||||
val signature = Instances.getSignatureInstance(wrappedPrivateKey.signatureScheme.signatureName, cordaBouncyCastleProvider)
|
||||
signature.initSign(privateKey, newSecureRandom())
|
||||
signature.update(payloadToSign)
|
||||
return signature.sign()
|
||||
}
|
||||
|
||||
private fun keyAlgorithmFromScheme(scheme: SignatureScheme): String = when (scheme) {
|
||||
Crypto.ECDSA_SECP256R1_SHA256, Crypto.ECDSA_SECP256K1_SHA256 -> "EC"
|
||||
Crypto.RSA_SHA256 -> "RSA"
|
||||
else -> throw IllegalArgumentException("No algorithm for scheme ID ${scheme.schemeNumberID}")
|
||||
}
|
||||
|
||||
override fun getWrappingMode(): WrappingMode? {
|
||||
return WrappingMode.DEGRADED_WRAPPED
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package net.corda.testing.internal
|
||||
|
||||
import io.netty.bootstrap.Bootstrap
|
||||
import io.netty.channel.ChannelFuture
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter
|
||||
import io.netty.handler.ssl.SslContext
|
||||
import io.netty.channel.ChannelInitializer
|
||||
import io.netty.channel.ChannelOption
|
||||
import io.netty.channel.nio.NioEventLoopGroup
|
||||
import io.netty.channel.socket.SocketChannel
|
||||
import io.netty.channel.socket.nio.NioSocketChannel
|
||||
import io.netty.handler.ssl.SslHandler
|
||||
import java.io.Closeable
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import javax.net.ssl.SSLEngine
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
|
||||
class NettyTestClient(
|
||||
val sslContext: SslContext?,
|
||||
val targetHost: String,
|
||||
val targetPort: Int,
|
||||
val handler: ChannelInboundHandlerAdapter
|
||||
) : Closeable {
|
||||
internal var mainThread: Thread? = null
|
||||
internal var channelFuture: ChannelFuture? = null
|
||||
|
||||
// lock/condition to make sure that start only returns when the server is actually running
|
||||
private val lock = ReentrantLock()
|
||||
private val condition = lock.newCondition()
|
||||
|
||||
var engine: SSLEngine? = null
|
||||
private set
|
||||
|
||||
fun start() {
|
||||
try {
|
||||
lock.lock()
|
||||
mainThread = thread(start = true) { run() }
|
||||
if (!condition.await(5, TimeUnit.SECONDS)) {
|
||||
throw TimeoutException("Netty test server failed to start")
|
||||
}
|
||||
} finally {
|
||||
lock.unlock()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun run() {
|
||||
// Configure the client.
|
||||
val group = NioEventLoopGroup()
|
||||
try {
|
||||
val b = Bootstrap()
|
||||
b.group(group)
|
||||
.channel(NioSocketChannel::class.java)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.handler(object : ChannelInitializer<SocketChannel>() {
|
||||
@Throws(Exception::class)
|
||||
public override fun initChannel(ch: SocketChannel) {
|
||||
val p = ch.pipeline()
|
||||
if (sslContext != null) {
|
||||
engine = sslContext.newEngine(ch.alloc(), targetHost, targetPort)
|
||||
p.addLast(SslHandler(engine))
|
||||
}
|
||||
//p.addLast(new LoggingHandler(LogLevel.INFO));
|
||||
p.addLast(handler)
|
||||
}
|
||||
})
|
||||
|
||||
// Start the client.
|
||||
val f = b.connect(targetHost, targetPort)
|
||||
try {
|
||||
lock.lock()
|
||||
condition.signal()
|
||||
channelFuture = f.sync()
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
// Wait until the connection is closed.
|
||||
f.channel().closeFuture().sync()
|
||||
} finally {
|
||||
// Shut down the event loop to terminate all threads.
|
||||
group.shutdownGracefully()
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
channelFuture?.channel()?.close()
|
||||
mainThread?.join()
|
||||
mainThread = null
|
||||
channelFuture = null
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
stop()
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package net.corda.testing.internal
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.Unpooled
|
||||
import io.netty.channel.Channel
|
||||
import io.netty.channel.ChannelDuplexHandler
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
class NettyTestHandler(val onMessageFunc: (ctx: ChannelHandlerContext?, msg: Any?) -> Unit = { _, _ -> }) : ChannelDuplexHandler() {
|
||||
private var channel: Channel? = null
|
||||
private var failure: Throwable? = null
|
||||
|
||||
private val lock = ReentrantLock()
|
||||
private val condition = lock.newCondition()
|
||||
|
||||
var readCalledCounter: Int = 0
|
||||
private set
|
||||
|
||||
override fun channelRegistered(ctx: ChannelHandlerContext?) {
|
||||
channel = ctx?.channel()
|
||||
super.channelRegistered(ctx)
|
||||
}
|
||||
|
||||
override fun channelRead(ctx: ChannelHandlerContext?, msg: Any?) {
|
||||
try {
|
||||
lock.lock()
|
||||
readCalledCounter++
|
||||
onMessageFunc(ctx, msg)
|
||||
} catch( e: Throwable ){
|
||||
failure = e
|
||||
} finally {
|
||||
condition.signal()
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
fun writeString(msg: String) {
|
||||
val buffer = Unpooled.wrappedBuffer(msg.toByteArray())
|
||||
require(channel != null) { "Channel must be registered before sending messages" }
|
||||
channel!!.writeAndFlush(buffer)
|
||||
}
|
||||
|
||||
fun rethrowIfFailed() {
|
||||
failure?.also { throw it }
|
||||
}
|
||||
|
||||
fun waitForReadCalled(numberOfExpectedCalls: Int = 1): Boolean {
|
||||
try {
|
||||
lock.lock()
|
||||
if (readCalledCounter >= numberOfExpectedCalls) {
|
||||
return true
|
||||
}
|
||||
while (readCalledCounter < numberOfExpectedCalls) {
|
||||
if (!condition.await(5, TimeUnit.SECONDS)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun readString(buffer: Any?): String {
|
||||
checkNotNull(buffer)
|
||||
val ar = ByteArray((buffer as ByteBuf).readableBytes())
|
||||
buffer.readBytes(ar)
|
||||
return String(ar)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package net.corda.testing.internal
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap
|
||||
import io.netty.channel.ChannelFuture
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter
|
||||
import io.netty.channel.ChannelInitializer
|
||||
import io.netty.channel.ChannelOption
|
||||
import io.netty.channel.nio.NioEventLoopGroup
|
||||
import io.netty.channel.socket.SocketChannel
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel
|
||||
import io.netty.handler.logging.LogLevel
|
||||
import io.netty.handler.logging.LoggingHandler
|
||||
import io.netty.handler.ssl.SslContext
|
||||
import java.io.Closeable
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.locks.Condition
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
|
||||
class NettyTestServer(
|
||||
private val sslContext: SslContext?,
|
||||
val messageHandler: ChannelInboundHandlerAdapter,
|
||||
val port: Int
|
||||
) : Closeable {
|
||||
internal var mainThread: Thread? = null
|
||||
internal var channel: ChannelFuture? = null
|
||||
|
||||
// lock/condition to make sure that start only returns when the server is actually running
|
||||
val lock = ReentrantLock()
|
||||
val condition: Condition = lock.newCondition()
|
||||
|
||||
fun start() {
|
||||
try {
|
||||
lock.lock()
|
||||
mainThread = thread(start = true) { run() }
|
||||
if (!condition.await(5, TimeUnit.SECONDS)) {
|
||||
throw TimeoutException("Netty test server failed to start")
|
||||
}
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
fun run() {
|
||||
// Configure the server.
|
||||
val bossGroup = NioEventLoopGroup(1)
|
||||
val workerGroup = NioEventLoopGroup()
|
||||
try {
|
||||
val b = ServerBootstrap()
|
||||
b.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel::class.java)
|
||||
.option(ChannelOption.SO_BACKLOG, 100)
|
||||
.handler(LoggingHandler(LogLevel.INFO))
|
||||
.childHandler(object : ChannelInitializer<SocketChannel>() {
|
||||
@Throws(Exception::class)
|
||||
public override fun initChannel(ch: SocketChannel) {
|
||||
val p = ch.pipeline()
|
||||
if (sslContext != null) {
|
||||
p.addLast(sslContext.newHandler(ch.alloc()))
|
||||
}
|
||||
//p.addLast(new LoggingHandler(LogLevel.INFO));
|
||||
p.addLast(messageHandler)
|
||||
}
|
||||
})
|
||||
|
||||
// Start the server.
|
||||
val f = b.bind(port)
|
||||
try {
|
||||
lock.lock()
|
||||
channel = f.sync()
|
||||
condition.signal()
|
||||
} finally {
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
// Wait until the server socket is closed.
|
||||
channel!!.channel().closeFuture().sync()
|
||||
} finally {
|
||||
// Shut down all event loops to terminate all threads.
|
||||
bossGroup.shutdownGracefully()
|
||||
workerGroup.shutdownGracefully()
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
channel?.channel()?.close()
|
||||
mainThread?.join()
|
||||
channel = null
|
||||
mainThread = null
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
stop()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user