mirror of
https://github.com/corda/corda.git
synced 2025-06-01 23:20:54 +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.deterministic_rt_version = constants.getProperty('deterministicRtVersion')
|
||||||
ext.okhttp_version = '3.14.2'
|
ext.okhttp_version = '3.14.2'
|
||||||
ext.netty_version = '4.1.29.Final'
|
ext.netty_version = '4.1.29.Final'
|
||||||
|
ext.tcnative_version = '2.0.14.Final'
|
||||||
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
||||||
ext.fileupload_version = '1.4'
|
ext.fileupload_version = '1.4'
|
||||||
// Legacy JUnit 4 version
|
// Legacy JUnit 4 version
|
||||||
@ -376,7 +377,11 @@ allprojects {
|
|||||||
// Demand that everything uses our given version of Netty.
|
// Demand that everything uses our given version of Netty.
|
||||||
eachDependency { details ->
|
eachDependency { details ->
|
||||||
if (details.requested.group == 'io.netty' && details.requested.name.startsWith('netty-')) {
|
if (details.requested.group == 'io.netty' && details.requested.name.startsWith('netty-')) {
|
||||||
details.useVersion netty_version
|
if (details.requested.name.startsWith('netty-tcnative')){
|
||||||
|
details.useVersion tcnative_version
|
||||||
|
} else {
|
||||||
|
details.useVersion netty_version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,6 +195,7 @@
|
|||||||
<ID>ComplexMethod:StartedFlowTransition.kt$StartedFlowTransition$override fun transition(): TransitionResult</ID>
|
<ID>ComplexMethod:StartedFlowTransition.kt$StartedFlowTransition$override fun transition(): TransitionResult</ID>
|
||||||
<ID>ComplexMethod:StatusTransitions.kt$StatusTransitions$ fun verify(tx: LedgerTransaction)</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: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:TlsDiffProtocolsTest.kt$TlsDiffProtocolsTest$@Test fun testClientServerTlsExchange()</ID>
|
||||||
<ID>ComplexMethod:TransactionBuilder.kt$TransactionBuilder$ fun withItems(vararg items: Any)</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>
|
<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:MockNetworkParametersService.kt$MockNetworkParametersStorage$20</ID>
|
||||||
<ID>MagicNumber:MockServices.kt$MockServices$10000</ID>
|
<ID>MagicNumber:MockServices.kt$MockServices$10000</ID>
|
||||||
<ID>MagicNumber:MockServices.kt$MockServices.Companion.<no name provided>$512</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$0.8</ID>
|
||||||
<ID>MagicNumber:Network.kt$Network$1.2</ID>
|
<ID>MagicNumber:Network.kt$Network$1.2</ID>
|
||||||
<ID>MagicNumber:Network.kt$Network$10</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$@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$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: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: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$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>
|
<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 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: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 : 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$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$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$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: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>
|
<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 BCRSAPrivateKey, is BCSphincs256PrivateKey -> true</ID>
|
||||||
<ID>MaxLineLength:Crypto.kt$Crypto$is BCRSAPublicKey -> key.modulus.bitLength() >= 2048</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: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: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 newSecureRandom(): SecureRandom</ID>
|
||||||
<ID>MaxLineLength:CryptoUtils.kt$ @DeleteForDJVM @Throws(NoSuchAlgorithmException::class) fun secureRandomBytes(numOfBytes: Int): ByteArray</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: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: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$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$ fun createPartyNode(legalName: CordaX500Name? = null): StartedMockNode</ID>
|
||||||
<ID>MaxLineLength:MockNetwork.kt$MockNetwork$val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy</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>
|
<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$override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?></ID>
|
||||||
<ID>MaxLineLength:TimedFlowTests.kt$TimedFlowTests.TestNotaryService$private</ID>
|
<ID>MaxLineLength:TimedFlowTests.kt$TimedFlowTests.TestNotaryService$private</ID>
|
||||||
<ID>MaxLineLength:TimedFlowTests.kt$TimedFlowTests.TestNotaryService.<no name provided>$override</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$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 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>
|
<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:MockNodeMessagingService.kt$MockNodeMessagingService$e: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:MyCustomNotaryService.kt$MyValidatingNotaryFlow$e: Exception</ID>
|
<ID>TooGenericExceptionCaught:MyCustomNotaryService.kt$MyValidatingNotaryFlow$e: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:NamedCacheTest.kt$NamedCacheTest$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:NetworkBootstrapper.kt$NetworkBootstrapper$e: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:NetworkMapServer.kt$NetworkMapServer.InMemoryNetworkMapService$e: Exception</ID>
|
<ID>TooGenericExceptionCaught:NetworkMapServer.kt$NetworkMapServer.InMemoryNetworkMapService$e: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:NetworkMapUpdater.kt$NetworkMapUpdater$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:StringToMethodCallParser.kt$StringToMethodCallParser$e: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:TLSAuthenticationTests.kt$TLSAuthenticationTests$ex: Exception</ID>
|
<ID>TooGenericExceptionCaught:TLSAuthenticationTests.kt$TLSAuthenticationTests$ex: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:ThrowableSerializer.kt$ThrowableSerializer$e: 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:TlsDiffProtocolsTest.kt$TlsDiffProtocolsTest$ex: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:TraderDemo.kt$TraderDemo$e: Exception</ID>
|
<ID>TooGenericExceptionCaught:TraderDemo.kt$TraderDemo$e: Exception</ID>
|
||||||
<ID>TooGenericExceptionCaught:TransactionBuilder.kt$TransactionBuilder$e: Throwable</ID>
|
<ID>TooGenericExceptionCaught:TransactionBuilder.kt$TransactionBuilder$e: Throwable</ID>
|
||||||
@ -4151,6 +4169,7 @@
|
|||||||
<ID>TooManyFunctions:ActionExecutorImpl.kt$ActionExecutorImpl : ActionExecutor</ID>
|
<ID>TooManyFunctions:ActionExecutorImpl.kt$ActionExecutorImpl : ActionExecutor</ID>
|
||||||
<ID>TooManyFunctions:AppendOnlyPersistentMap.kt$AppendOnlyPersistentMapBase<K, V, E, out EK></ID>
|
<ID>TooManyFunctions:AppendOnlyPersistentMap.kt$AppendOnlyPersistentMapBase<K, V, E, out EK></ID>
|
||||||
<ID>TooManyFunctions:ArtemisTcpTransport.kt$ArtemisTcpTransport$Companion</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:BFTSmart.kt$BFTSmart$Replica : DefaultRecoverable</ID>
|
||||||
<ID>TooManyFunctions:BaseTransaction.kt$BaseTransaction : NamedByHash</ID>
|
<ID>TooManyFunctions:BaseTransaction.kt$BaseTransaction : NamedByHash</ID>
|
||||||
<ID>TooManyFunctions:ClassCarpenter.kt$ClassCarpenterImpl : ClassCarpenter</ID>
|
<ID>TooManyFunctions:ClassCarpenter.kt$ClassCarpenterImpl : ClassCarpenter</ID>
|
||||||
@ -4387,6 +4406,8 @@
|
|||||||
<ID>WildcardImport:AttachmentsClassLoader.kt$import net.corda.core.serialization.*</ID>
|
<ID>WildcardImport:AttachmentsClassLoader.kt$import net.corda.core.serialization.*</ID>
|
||||||
<ID>WildcardImport:AttachmentsClassLoaderStaticContractTests.kt$import net.corda.core.contracts.*</ID>
|
<ID>WildcardImport:AttachmentsClassLoaderStaticContractTests.kt$import net.corda.core.contracts.*</ID>
|
||||||
<ID>WildcardImport:AutoOfferFlow.kt$import net.corda.core.flows.*</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:BCCryptoServiceTests.kt$import java.security.*</ID>
|
||||||
<ID>WildcardImport:BFTNotaryServiceTests.kt$import net.corda.core.crypto.*</ID>
|
<ID>WildcardImport:BFTNotaryServiceTests.kt$import net.corda.core.crypto.*</ID>
|
||||||
<ID>WildcardImport:BFTNotaryServiceTests.kt$import net.corda.testing.node.internal.*</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:MessageState.kt$import net.corda.core.contracts.*</ID>
|
||||||
<ID>WildcardImport:MigrationServicesForResolution.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: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:MockKeyManagementService.kt$import net.corda.core.crypto.*</ID>
|
||||||
<ID>WildcardImport:MockNetworkTest.kt$import org.junit.Assert.*</ID>
|
<ID>WildcardImport:MockNetworkTest.kt$import org.junit.Assert.*</ID>
|
||||||
<ID>WildcardImport:MockNodeMessagingService.kt$import net.corda.node.services.messaging.*</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: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.core.flows.*</ID>
|
||||||
<ID>WildcardImport:TimedFlowTests.kt$import net.corda.testing.node.internal.*</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:TlsDiffProtocolsTest.kt$import javax.net.ssl.*</ID>
|
||||||
<ID>WildcardImport:TopLevelTransition.kt$import net.corda.node.services.statemachine.*</ID>
|
<ID>WildcardImport:TopLevelTransition.kt$import net.corda.node.services.statemachine.*</ID>
|
||||||
<ID>WildcardImport:TraderDemoTest.kt$import net.corda.testing.driver.*</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
|
150. snappy
|
||||||
151. sshd-core
|
151. sshd-core
|
||||||
152. sshd-pam
|
152. sshd-pam
|
||||||
|
153. netty-tcnative-boringssl-static
|
||||||
|
|
||||||
================================================
|
================================================
|
||||||
================================================
|
================================================
|
||||||
@ -395,6 +396,10 @@ netty-resolver 4.1.29.Final
|
|||||||
https://netty.io/
|
https://netty.io/
|
||||||
https://github.com/netty/netty/blob/4.1/LICENSE.txt
|
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
|
netty-transport 4.1.29.Final
|
||||||
https://netty.io/
|
https://netty.io/
|
||||||
https://github.com/netty/netty/blob/4.1/LICENSE.txt
|
https://github.com/netty/netty/blob/4.1/LICENSE.txt
|
||||||
|
@ -51,6 +51,7 @@ dependencies {
|
|||||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
testCompile project(':node-driver')
|
testCompile project(':node-driver')
|
||||||
|
testCompile project(':test-utils')
|
||||||
|
|
||||||
compile ("org.apache.activemq:artemis-amqp-protocol:${artemis_version}") {
|
compile ("org.apache.activemq:artemis-amqp-protocol:${artemis_version}") {
|
||||||
// Gains our proton-j version from core module.
|
// Gains our proton-j version from core module.
|
||||||
|
@ -50,7 +50,7 @@ object X509Utilities {
|
|||||||
const val CORDA_CLIENT_CA = "cordaclientca"
|
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
|
// 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 NODE_IDENTITY_KEY_ALIAS = "identity-private-key"
|
||||||
const val DISTRIBUTED_NOTARY_KEY_ALIAS = "distributed-notary-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 DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS = "distributed-notary-composite-key"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.nodeapi.internal.cryptoservice
|
package net.corda.nodeapi.internal.cryptoservice
|
||||||
|
|
||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SignatureScheme
|
import net.corda.core.crypto.SignatureScheme
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
@ -64,6 +65,87 @@ interface CryptoService : SignOnlyCryptoService {
|
|||||||
* Returns the type of the service.
|
* Returns the type of the service.
|
||||||
*/
|
*/
|
||||||
fun getType(): SupportedCryptoServices
|
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.Crypto
|
||||||
import net.corda.core.crypto.SignatureScheme
|
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.internal.cordaBouncyCastleProvider
|
||||||
import net.corda.core.crypto.newSecureRandom
|
import net.corda.core.crypto.newSecureRandom
|
||||||
import net.corda.core.crypto.sha256
|
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.CertificateStore
|
||||||
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
|
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
|
||||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
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.CryptoService
|
||||||
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
|
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
|
||||||
import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices
|
import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
import java.security.KeyPair
|
import java.nio.file.Path
|
||||||
import java.security.KeyStore
|
import java.security.*
|
||||||
import java.security.PublicKey
|
import java.security.spec.ECGenParameterSpec
|
||||||
import java.security.Signature
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.KeyGenerator
|
||||||
import javax.security.auth.x500.X500Principal
|
import javax.security.auth.x500.X500Principal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations
|
* Basic implementation of a [CryptoService] that uses BouncyCastle for cryptographic operations
|
||||||
* and a Java KeyStore in the form of [CertificateStore] to store private keys.
|
* and a Java KeyStore in the form of [CertificateStore] to store private keys.
|
||||||
* This service reuses the [NodeConfiguration.signingCertificateStore] to store 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
|
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.
|
// TODO make it private when E2ETestKeyManagementService does not require direct access to the private key.
|
||||||
var certificateStore: CertificateStore = certificateStoreSupplier.get(true)
|
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 {
|
override fun generateKeyPair(alias: String, scheme: SignatureScheme): PublicKey {
|
||||||
try {
|
try {
|
||||||
|
detailedLogger.trace { "CryptoService(action=generate_key_pair_start;alias=$alias;scheme=$scheme)" }
|
||||||
val keyPair = Crypto.generateKeyPair(scheme)
|
val keyPair = Crypto.generateKeyPair(scheme)
|
||||||
|
detailedLogger.trace { "CryptoService(action=generate_key_pair_end;alias=$alias;scheme=$scheme)" }
|
||||||
importKey(alias, keyPair)
|
importKey(alias, keyPair)
|
||||||
return keyPair.public
|
return keyPair.public
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -43,14 +71,19 @@ class BCCryptoService(private val legalName: X500Principal, private val certific
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun containsKey(alias: String): Boolean {
|
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 {
|
override fun getPublicKey(alias: String): PublicKey {
|
||||||
try {
|
try {
|
||||||
return certificateStore.query { getPublicKey(alias) }
|
return certificateStore.query { getPublicKey(alias) }
|
||||||
} catch (e: Exception) {
|
} 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,15 +99,18 @@ class BCCryptoService(private val legalName: X500Principal, private val certific
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun signWithAlgorithm(alias: String, data: ByteArray, signAlgorithm: String): ByteArray {
|
private fun signWithAlgorithm(alias: String, data: ByteArray, signAlgorithm: String): ByteArray {
|
||||||
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
|
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
|
||||||
val signature = Signature.getInstance(signAlgorithm, cordaBouncyCastleProvider)
|
val signature = Signature.getInstance(signAlgorithm, cordaBouncyCastleProvider)
|
||||||
signature.initSign(privateKey, newSecureRandom())
|
detailedLogger.trace { "CryptoService(action=signing_start;alias=$alias;algorithm=$signAlgorithm)" }
|
||||||
signature.update(data)
|
signature.initSign(privateKey, newSecureRandom())
|
||||||
return signature.sign()
|
signature.update(data)
|
||||||
|
detailedLogger.trace { "CryptoService(action=signing_end;alias=$alias;algorithm=$signAlgorithm)" }
|
||||||
|
return signature.sign()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSigner(alias: String): ContentSigner {
|
override fun getSigner(alias: String): ContentSigner {
|
||||||
try {
|
try {
|
||||||
|
detailedLogger.trace { "CryptoService(action=get_signer;alias=$alias)" }
|
||||||
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
|
val privateKey = certificateStore.query { getPrivateKey(alias, certificateStore.entryPassword) }
|
||||||
val signatureScheme = Crypto.findSignatureScheme(privateKey)
|
val signatureScheme = Crypto.findSignatureScheme(privateKey)
|
||||||
return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())
|
return ContentSignerBuilder.build(signatureScheme, privateKey, Crypto.findProvider(signatureScheme.providerName), newSecureRandom())
|
||||||
@ -105,10 +141,81 @@ class BCCryptoService(private val legalName: X500Principal, private val certific
|
|||||||
try {
|
try {
|
||||||
// Store a self-signed certificate, as Keystore requires to store certificates instead of public keys.
|
// 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.
|
// 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)
|
val cert = X509Utilities.createSelfSignedCACertificate(legalName, keyPair)
|
||||||
certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) }
|
certificateStore.query { setPrivateKey(alias, keyPair.private, listOf(cert), certificateStore.entryPassword) }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw CryptoServiceException("Cannot import key with alias $alias", e)
|
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) }
|
assertTrue { signingCertStore.contains(alias) }
|
||||||
// We can retrieve the certificate.
|
// We can retrieve the certificate.
|
||||||
assertEquals(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT, signingCertStore[alias])
|
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") })
|
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.InetAddress
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import javax.net.ssl.*
|
import javax.net.ssl.*
|
||||||
|
import javax.net.ssl.SNIHostName
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import javax.net.ssl.SNIHostName
|
import kotlin.test.assertTrue
|
||||||
import javax.net.ssl.StandardConstants
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This test checks compatibility of TLS 1.2 and 1.3 communication using different cipher suites with SNI header
|
* 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
|
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
|
||||||
import net.corda.core.crypto.Crypto.COMPOSITE_KEY
|
import net.corda.core.crypto.Crypto.COMPOSITE_KEY
|
||||||
import net.corda.core.crypto.Crypto.ECDSA_SECP256K1_SHA256
|
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.SerializationContext
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
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.node.serialization.amqp.AMQPServerSerializationScheme
|
||||||
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
import net.corda.nodeapi.internal.config.MutualSslConfiguration
|
||||||
import net.corda.nodeapi.internal.createDevNodeCa
|
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.ALICE_NAME
|
||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import net.corda.testing.core.TestIdentity
|
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.createDevIntermediateCaCertPath
|
||||||
import net.corda.testing.internal.stubs.CertificateStoreStubs
|
import net.corda.testing.internal.stubs.CertificateStoreStubs
|
||||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.bouncycastle.asn1.x509.*
|
import org.bouncycastle.asn1.x509.*
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey
|
|
||||||
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
|
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -66,6 +75,9 @@ class X509UtilitiesTest {
|
|||||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||||
"TLS_ECDHE_RSA_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 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.
|
// We also add [DEFAULT_TLS_SIGNATURE_SCHEME] and [DEFAULT_IDENTITY_SIGNATURE_SCHEME] combinations for consistency.
|
||||||
val certChainSchemeCombinations = listOf(
|
val certChainSchemeCombinations = listOf(
|
||||||
@ -349,6 +361,65 @@ class X509UtilitiesTest {
|
|||||||
assertTrue(done)
|
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 tempFile(name: String): Path = tempFolder.root.toPath() / name
|
||||||
|
|
||||||
private fun MutualSslConfiguration.createTrustStore(rootCert: X509Certificate) {
|
private fun MutualSslConfiguration.createTrustStore(rootCert: X509Certificate) {
|
||||||
@ -452,4 +523,34 @@ class X509UtilitiesTest {
|
|||||||
assertEquals(childKeyPair.public, reloadedPublicKey)
|
assertEquals(childKeyPair.public, reloadedPublicKey)
|
||||||
assertEquals(childKeyPair.private, reloadedPrivateKey)
|
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.Crypto
|
||||||
import net.corda.core.crypto.SignatureScheme
|
import net.corda.core.crypto.SignatureScheme
|
||||||
import net.corda.core.crypto.internal.cordaBouncyCastleProvider
|
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
|
import net.corda.nodeapi.internal.config.CertificateStoreSupplier
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
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.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.core.ALICE_NAME
|
||||||
import net.corda.testing.internal.stubs.CertificateStoreStubs
|
import net.corda.testing.internal.stubs.CertificateStoreStubs
|
||||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
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.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TemporaryFolder
|
import org.junit.rules.TemporaryFolder
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
import java.nio.file.Path
|
||||||
import java.security.*
|
import java.security.*
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.util.*
|
||||||
import javax.security.auth.x500.X500Principal
|
import javax.security.auth.x500.X500Principal
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class BCCryptoServiceTests {
|
class BCCryptoServiceTests {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val clearData = "data".toByteArray()
|
val clearData = "data".toByteArray()
|
||||||
}
|
}
|
||||||
@ -46,16 +44,20 @@ class BCCryptoServiceTests {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val temporaryKeystoreFolder = TemporaryFolder()
|
val temporaryKeystoreFolder = TemporaryFolder()
|
||||||
|
|
||||||
|
lateinit var certificatesDirectory: Path
|
||||||
|
lateinit var wrappingKeyStorePath: Path
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
val baseDirectory = temporaryFolder.root.toPath()
|
val baseDirectory = temporaryFolder.root.toPath()
|
||||||
val certificatesDirectory = baseDirectory / "certificates"
|
certificatesDirectory = baseDirectory / "certificates"
|
||||||
signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
|
signingCertificateStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certificatesDirectory)
|
||||||
|
wrappingKeyStorePath = certificatesDirectory / "wrappingkeystore.pkcs12"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `BCCryptoService generate key pair and sign both data and cert`() {
|
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.
|
// Testing every supported scheme.
|
||||||
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY }.forEach { generateKeyAndSignForScheme(cryptoService, it) }
|
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY }.forEach { generateKeyAndSignForScheme(cryptoService, it) }
|
||||||
}
|
}
|
||||||
@ -87,7 +89,7 @@ class BCCryptoServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `BCCryptoService generate key pair and sign with existing schemes`() {
|
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.
|
// Testing every supported scheme.
|
||||||
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY
|
Crypto.supportedSignatureSchemes().filter { it != Crypto.COMPOSITE_KEY
|
||||||
&& it.signatureName != "SHA512WITHSPHINCS256"}.forEach {
|
&& it.signatureName != "SHA512WITHSPHINCS256"}.forEach {
|
||||||
@ -121,7 +123,7 @@ class BCCryptoServiceTests {
|
|||||||
private fun signAndVerify(signAlgo: String, alias: String, keyTypeAlgo: String): Boolean {
|
private fun signAndVerify(signAlgo: String, alias: String, keyTypeAlgo: String): Boolean {
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance(keyTypeAlgo)
|
val keyPairGenerator = KeyPairGenerator.getInstance(keyTypeAlgo)
|
||||||
val keyPair = keyPairGenerator.genKeyPair()
|
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) }
|
assertTrue { cryptoService.containsKey(alias) }
|
||||||
val signatureData = cryptoService.sign(alias, clearData, signAlgo)
|
val signatureData = cryptoService.sign(alias, clearData, signAlgo)
|
||||||
return verify(signAlgo, cryptoService.getPublicKey(alias), signatureData, clearData)
|
return verify(signAlgo, cryptoService.getPublicKey(alias), signatureData, clearData)
|
||||||
@ -164,10 +166,90 @@ class BCCryptoServiceTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `When key does not exist getPublicKey, sign and getSigner should throw`() {
|
fun `When key does not exist getPublicKey, sign and getSigner should throw`() {
|
||||||
val nonExistingAlias = "nonExistingAlias"
|
val nonExistingAlias = "nonExistingAlias"
|
||||||
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore)
|
val cryptoService = BCCryptoService(ALICE_NAME.x500Principal, signingCertificateStore, wrappingKeyStorePath)
|
||||||
assertFalse { cryptoService.containsKey(nonExistingAlias) }
|
assertFalse { cryptoService.containsKey(nonExistingAlias) }
|
||||||
assertFailsWith<CryptoServiceException> { cryptoService.getPublicKey(nonExistingAlias) }
|
assertFailsWith<CryptoServiceException> { cryptoService.getPublicKey(nonExistingAlias) }
|
||||||
assertFailsWith<CryptoServiceException> { cryptoService.sign(nonExistingAlias, clearData) }
|
assertFailsWith<CryptoServiceException> { cryptoService.sign(nonExistingAlias, clearData) }
|
||||||
assertFailsWith<CryptoServiceException> { cryptoService.getSigner(nonExistingAlias) }
|
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
|
keytool.exe -importcert -noprompt -file root.pem -alias root -keystore trust.jks -storepass trustpass
|
||||||
|
|
||||||
// Create a chain for EC Bridge
|
// 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
|
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
|
keytool.exe -importcert -noprompt -file bridgechain_ec.pem -alias bridgecert -keystore bridge_ec.jks -storepass bridgepass -keypass bridgepass
|
||||||
|
|
||||||
// Create a chain for RSA Bridge
|
// 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
|
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
|
keytool.exe -importcert -noprompt -file bridgechain_rsa.pem -alias bridgecert -keystore bridge_rsa.jks -storepass bridgepass -keypass bridgepass
|
||||||
|
|
||||||
// Create a chain for EC Float
|
// 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
|
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
|
keytool.exe -importcert -noprompt -file floatchain_ec.pem -alias floatcert -keystore float_ec.jks -storepass floatpass -keypass floatpass
|
||||||
|
|
||||||
// Create a chain for RSA Float
|
// 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
|
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
|
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.
@ -229,7 +229,10 @@ dependencies {
|
|||||||
compile "org.jolokia:jolokia-jvm:${jolokia_version}:agent"
|
compile "org.jolokia:jolokia-jvm:${jolokia_version}:agent"
|
||||||
// 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
|
// 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}"
|
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)
|
// Required by JVMAgentUtil (x-compatible java 8 & 11 agent lookup mechanism)
|
||||||
compile files("${System.properties['java.home']}/../lib/tools.jar")
|
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.Crypto
|
||||||
import net.corda.core.crypto.SignatureScheme
|
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.internal.cordaBouncyCastleProvider
|
||||||
import net.corda.core.crypto.newSecureRandom
|
import net.corda.core.crypto.newSecureRandom
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import net.corda.nodeapi.internal.crypto.ContentSignerBuilder
|
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.CryptoService
|
||||||
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
|
import net.corda.nodeapi.internal.cryptoservice.CryptoServiceException
|
||||||
import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices
|
import net.corda.nodeapi.internal.cryptoservice.SupportedCryptoServices
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
|
import java.security.PrivateKey
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.Signature
|
import java.security.Signature
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.KeyGenerator
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
|
||||||
class MockCryptoService(initialKeyPairs: Map<String, KeyPair>) : CryptoService {
|
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 aliasToKey: MutableMap<String, KeyPair> = mutableMapOf()
|
||||||
|
|
||||||
|
private val wrappingKeys: MutableMap<String, SecretKey> = mutableMapOf()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initialKeyPairs.forEach {
|
initialKeyPairs.forEach {
|
||||||
aliasToKey[it.key] = it.value
|
aliasToKey[it.key] = it.value
|
||||||
@ -68,4 +76,48 @@ class MockCryptoService(initialKeyPairs: Map<String, KeyPair>) : CryptoService {
|
|||||||
aliasToKey[alias] = keyPair
|
aliasToKey[alias] = keyPair
|
||||||
return keyPair.public
|
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…
x
Reference in New Issue
Block a user