mirror of
https://github.com/corda/corda.git
synced 2024-12-25 07:31:10 +00:00
Added nice extension methods for Path, which are more readable than the static methods from Files
This commit is contained in:
parent
2bc8f8414d
commit
bd89da458b
core
node
build.gradle
src
main/kotlin/com/r3corda/node
test/kotlin/com/r3corda/node
messaging
services
utilities/certsigning
test-utils/src/main/kotlin/com/r3corda/testing/node
@ -44,6 +44,7 @@ dependencies {
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2"
|
||||
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
// Thread safety annotations
|
||||
|
@ -8,6 +8,7 @@ import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import com.r3corda.core.crypto.newSecureRandom
|
||||
import kotlinx.support.jdk7.use
|
||||
import org.slf4j.Logger
|
||||
import rx.Observable
|
||||
import rx.subjects.UnicastSubject
|
||||
@ -15,16 +16,16 @@ import java.io.BufferedInputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.math.BigDecimal
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.LinkOption
|
||||
import java.nio.file.OpenOption
|
||||
import java.nio.file.Path
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets.UTF_8
|
||||
import java.nio.file.*
|
||||
import java.nio.file.attribute.FileAttribute
|
||||
import java.time.Duration
|
||||
import java.time.temporal.Temporal
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.stream.Stream
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.reflect.KProperty
|
||||
@ -95,15 +96,27 @@ inline fun <T> SettableFuture<T>.catch(block: () -> T) {
|
||||
|
||||
/** Allows you to write code like: Paths.get("someDir") / "subdir" / "filename" but using the Paths API to avoid platform separator problems. */
|
||||
operator fun Path.div(other: String): Path = resolve(other)
|
||||
fun Path.createDirectory(vararg attrs: FileAttribute<*>): Path = Files.createDirectory(this, *attrs)
|
||||
fun Path.createDirectories(vararg attrs: FileAttribute<*>): Path = Files.createDirectories(this, *attrs)
|
||||
fun Path.exists(vararg options: LinkOption): Boolean = Files.exists(this, *options)
|
||||
inline fun <R> Path.use(block: (InputStream) -> R): R = Files.newInputStream(this).use(block)
|
||||
inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption, block: (OutputStream) -> Unit) {
|
||||
fun Path.moveTo(target: Path, vararg options: CopyOption): Path = Files.move(this, target, *options)
|
||||
fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options)
|
||||
fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options)
|
||||
val Path.size: Long get() = Files.size(this)
|
||||
inline fun <R> Path.list(block: (Stream<Path>) -> R): R = Files.list(this).use(block)
|
||||
fun Path.deleteIfExists(): Boolean = Files.deleteIfExists(this)
|
||||
fun Path.readAll(): ByteArray = Files.readAllBytes(this)
|
||||
inline fun <R> Path.read(vararg options: OpenOption, block: (InputStream) -> R): R = Files.newInputStream(this, *options).use(block)
|
||||
inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption = emptyArray(), block: (OutputStream) -> Unit) {
|
||||
if (createDirs) {
|
||||
normalize().parent?.createDirectories()
|
||||
}
|
||||
Files.newOutputStream(this, *options).use(block)
|
||||
}
|
||||
inline fun <R> Path.readLines(charset: Charset = UTF_8, block: (Stream<String>) -> R): R = Files.lines(this, charset).use(block)
|
||||
fun Path.writeLines(lines: Iterable<CharSequence>, charset: Charset = UTF_8, vararg options: OpenOption): Path = Files.write(this, lines, charset, *options)
|
||||
|
||||
fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options)
|
||||
|
||||
// Simple infix function to add back null safety that the JDK lacks: timeA until timeB
|
||||
infix fun Temporal.until(endExclusive: Temporal) = Duration.between(this, endExclusive)
|
||||
@ -213,23 +226,23 @@ class TransientProperty<out T>(private val initializer: () -> T) {
|
||||
*/
|
||||
fun extractZipFile(zipPath: Path, toPath: Path) {
|
||||
val normalisedToPath = toPath.normalize()
|
||||
if (!Files.exists(normalisedToPath))
|
||||
Files.createDirectories(normalisedToPath)
|
||||
normalisedToPath.createDirectories()
|
||||
|
||||
ZipInputStream(BufferedInputStream(Files.newInputStream(zipPath))).use { zip ->
|
||||
zipPath.read {
|
||||
val zip = ZipInputStream(BufferedInputStream(it))
|
||||
while (true) {
|
||||
val e = zip.nextEntry ?: break
|
||||
val outPath = normalisedToPath.resolve(e.name)
|
||||
val outPath = normalisedToPath / e.name
|
||||
|
||||
// Security checks: we should reject a zip that contains tricksy paths that try to escape toPath.
|
||||
if (!outPath.normalize().startsWith(normalisedToPath))
|
||||
throw IllegalStateException("ZIP contained a path that resolved incorrectly: ${e.name}")
|
||||
|
||||
if (e.isDirectory) {
|
||||
Files.createDirectories(outPath)
|
||||
outPath.createDirectories()
|
||||
continue
|
||||
}
|
||||
Files.newOutputStream(outPath).use { out ->
|
||||
outPath.write { out ->
|
||||
ByteStreams.copy(zip, out)
|
||||
}
|
||||
zip.closeEntry()
|
||||
|
@ -1,7 +1,9 @@
|
||||
package com.r3corda.core.crypto
|
||||
|
||||
import com.r3corda.core.exists
|
||||
import com.r3corda.core.random63BitValue
|
||||
import com.r3corda.core.use
|
||||
import com.r3corda.core.read
|
||||
import com.r3corda.core.write
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
||||
import org.bouncycastle.asn1.DERSequence
|
||||
@ -27,7 +29,6 @@ import java.io.FileWriter
|
||||
import java.io.InputStream
|
||||
import java.math.BigInteger
|
||||
import java.net.InetAddress
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.*
|
||||
import java.security.cert.Certificate
|
||||
@ -135,14 +136,11 @@ object X509Utilities {
|
||||
fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore {
|
||||
val pass = storePassword.toCharArray()
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
if (Files.exists(keyStoreFilePath)) {
|
||||
keyStoreFilePath.use { keyStore.load(it, pass) }
|
||||
if (keyStoreFilePath.exists()) {
|
||||
keyStoreFilePath.read { keyStore.load(it, pass) }
|
||||
} else {
|
||||
keyStore.load(null, pass)
|
||||
val output = Files.newOutputStream(keyStoreFilePath)
|
||||
output.use {
|
||||
keyStore.store(output, pass)
|
||||
}
|
||||
keyStoreFilePath.write { keyStore.store(it, pass) }
|
||||
}
|
||||
return keyStore
|
||||
}
|
||||
@ -157,7 +155,7 @@ object X509Utilities {
|
||||
fun loadKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore {
|
||||
val pass = storePassword.toCharArray()
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
|
||||
keyStoreFilePath.use { keyStore.load(it, pass) }
|
||||
keyStoreFilePath.read { keyStore.load(it, pass) }
|
||||
return keyStore
|
||||
}
|
||||
|
||||
@ -186,10 +184,7 @@ object X509Utilities {
|
||||
*/
|
||||
fun saveKeyStore(keyStore: KeyStore, keyStoreFilePath: Path, storePassword: String) {
|
||||
val pass = storePassword.toCharArray()
|
||||
val output = Files.newOutputStream(keyStoreFilePath)
|
||||
output.use {
|
||||
keyStore.store(output, pass)
|
||||
}
|
||||
keyStoreFilePath.write { keyStore.store(it, pass) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.r3corda.core.crypto
|
||||
|
||||
import com.r3corda.core.div
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
@ -11,6 +12,7 @@ import java.io.DataOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.file.Path
|
||||
import java.security.PrivateKey
|
||||
import java.security.SecureRandom
|
||||
import java.security.Signature
|
||||
@ -38,18 +40,15 @@ class X509UtilitiesTest {
|
||||
assertTrue { caCertAndKey.certificate.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `load and save a PEM file certificate`() {
|
||||
val tmpCertificateFile = tempFolder.root.toPath().resolve("cacert.pem")
|
||||
|
||||
val tmpCertificateFile = tempFile("cacert.pem")
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert("Test Cert")
|
||||
X509Utilities.saveCertificateAsPEMFile(caCertAndKey.certificate, tmpCertificateFile)
|
||||
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
|
||||
assertEquals(caCertAndKey.certificate, readCertificate)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `create valid server certificate chain`() {
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert("Test CA Cert")
|
||||
@ -84,15 +83,11 @@ class X509UtilitiesTest {
|
||||
|
||||
@Test
|
||||
fun `create full CA keystore`() {
|
||||
val tmpKeyStore = tempFolder.root.toPath().resolve("keystore.jks")
|
||||
val tmpTrustStore = tempFolder.root.toPath().resolve("truststore.jks")
|
||||
val tmpKeyStore = tempFile("keystore.jks")
|
||||
val tmpTrustStore = tempFile("truststore.jks")
|
||||
|
||||
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
|
||||
X509Utilities.createCAKeyStoreAndTrustStore(tmpKeyStore,
|
||||
"keystorepass",
|
||||
"keypass",
|
||||
tmpTrustStore,
|
||||
"trustpass")
|
||||
X509Utilities.createCAKeyStoreAndTrustStore(tmpKeyStore, "keystorepass", "keypass", tmpTrustStore, "trustpass")
|
||||
|
||||
// Load back generated root CA Cert and private key from keystore and check against copy in truststore
|
||||
val keyStore = X509Utilities.loadKeyStore(tmpKeyStore, "keystorepass")
|
||||
@ -132,12 +127,11 @@ class X509UtilitiesTest {
|
||||
assertTrue { intermediateVerifier.verify(intermediateSignature) }
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `create server certificate in keystore for SSL`() {
|
||||
val tmpCAKeyStore = tempFolder.root.toPath().resolve("keystore.jks")
|
||||
val tmpTrustStore = tempFolder.root.toPath().resolve("truststore.jks")
|
||||
val tmpServerKeyStore = tempFolder.root.toPath().resolve("serverkeystore.jks")
|
||||
val tmpCAKeyStore = tempFile("keystore.jks")
|
||||
val tmpTrustStore = tempFile("truststore.jks")
|
||||
val tmpServerKeyStore = tempFile("serverkeystore.jks")
|
||||
|
||||
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
|
||||
X509Utilities.createCAKeyStoreAndTrustStore(tmpCAKeyStore,
|
||||
@ -177,9 +171,9 @@ class X509UtilitiesTest {
|
||||
|
||||
@Test
|
||||
fun `create server cert and use in SSL socket`() {
|
||||
val tmpCAKeyStore = tempFolder.root.toPath().resolve("keystore.jks")
|
||||
val tmpTrustStore = tempFolder.root.toPath().resolve("truststore.jks")
|
||||
val tmpServerKeyStore = tempFolder.root.toPath().resolve("serverkeystore.jks")
|
||||
val tmpCAKeyStore = tempFile("keystore.jks")
|
||||
val tmpTrustStore = tempFile("truststore.jks")
|
||||
val tmpServerKeyStore = tempFile("serverkeystore.jks")
|
||||
|
||||
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
|
||||
val caKeyStore = X509Utilities.createCAKeyStoreAndTrustStore(tmpCAKeyStore,
|
||||
@ -218,7 +212,6 @@ class X509UtilitiesTest {
|
||||
serverSocket.sslParameters = serverParams
|
||||
serverSocket.useClientMode = false
|
||||
|
||||
|
||||
val clientSocket = clientSocketFactory.createSocket() as SSLSocket
|
||||
val clientParams = SSLParameters(arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
|
||||
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
|
||||
@ -285,4 +278,6 @@ class X509UtilitiesTest {
|
||||
serverSocket.close()
|
||||
assertTrue(done)
|
||||
}
|
||||
|
||||
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name
|
||||
}
|
||||
|
@ -56,7 +56,6 @@ dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.2"
|
||||
|
||||
compile "com.google.guava:guava:19.0"
|
||||
|
||||
|
@ -4,6 +4,7 @@ import com.codahale.metrics.MetricRegistry
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import com.r3corda.core.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.crypto.X509Utilities
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
@ -12,7 +13,6 @@ import com.r3corda.core.node.services.*
|
||||
import com.r3corda.core.node.services.NetworkMapCache.MapChangeType
|
||||
import com.r3corda.core.protocols.ProtocolLogic
|
||||
import com.r3corda.core.protocols.ProtocolLogicRefFactory
|
||||
import com.r3corda.core.seconds
|
||||
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
||||
import com.r3corda.core.serialization.deserialize
|
||||
import com.r3corda.core.serialization.serialize
|
||||
@ -47,7 +47,6 @@ import com.r3corda.protocols.sendRequest
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.slf4j.Logger
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.time.Clock
|
||||
@ -468,11 +467,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo
|
||||
// "permissioned". The identity file is what gets distributed and contains the node's legal name along with
|
||||
// the public key. Obviously in a real system this would need to be a certificate chain of some kind to ensure
|
||||
// the legal name is actually validated in some way.
|
||||
val privKeyFile = dir.resolve(privateKeyFileName)
|
||||
val pubIdentityFile = dir.resolve(publicKeyFileName)
|
||||
val privKeyFile = dir / privateKeyFileName
|
||||
val pubIdentityFile = dir / publicKeyFileName
|
||||
val identityName = if (serviceName == null) configuration.myLegalName else configuration.myLegalName + "|" + serviceName
|
||||
|
||||
val identityAndKey = if (!Files.exists(privKeyFile)) {
|
||||
val identityAndKey = if (!privKeyFile.exists()) {
|
||||
log.info("Identity key not found, generating fresh key!")
|
||||
val keyPair: KeyPair = generateKeyPair()
|
||||
keyPair.serialize().writeToFile(privKeyFile)
|
||||
@ -485,12 +484,12 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo
|
||||
// Check that the identity in the config file matches the identity file we have stored to disk.
|
||||
// This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed
|
||||
// things up for us.
|
||||
val myIdentity = Files.readAllBytes(pubIdentityFile).deserialize<Party>()
|
||||
val myIdentity = pubIdentityFile.readAll().deserialize<Party>()
|
||||
if (myIdentity.name != identityName)
|
||||
throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:" +
|
||||
"${identityName} vs ${myIdentity.name}")
|
||||
"$identityName vs ${myIdentity.name}")
|
||||
// Load the private key.
|
||||
val keyPair = Files.readAllBytes(privKeyFile).deserialize<KeyPair>()
|
||||
val keyPair = privKeyFile.readAll().deserialize<KeyPair>()
|
||||
Pair(myIdentity, keyPair)
|
||||
}
|
||||
partyKeys += identityAndKey.second
|
||||
@ -500,17 +499,15 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, val netwo
|
||||
protected open fun generateKeyPair() = com.r3corda.core.crypto.generateKeyPair()
|
||||
|
||||
protected fun makeAttachmentStorage(dir: Path): NodeAttachmentService {
|
||||
val attachmentsDir = dir.resolve("attachments")
|
||||
val attachmentsDir = dir / "attachments"
|
||||
try {
|
||||
Files.createDirectory(attachmentsDir)
|
||||
attachmentsDir.createDirectory()
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
}
|
||||
return NodeAttachmentService(attachmentsDir, services.monitoringService.metrics)
|
||||
}
|
||||
|
||||
protected fun createNodeDir() {
|
||||
if (!Files.exists(configuration.basedir)) {
|
||||
Files.createDirectories(configuration.basedir)
|
||||
}
|
||||
configuration.basedir.createDirectories()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.r3corda.node.internal
|
||||
|
||||
import com.codahale.metrics.JmxReporter
|
||||
import com.r3corda.core.div
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.node.ServiceHub
|
||||
import com.r3corda.core.node.services.ServiceInfo
|
||||
@ -366,7 +367,7 @@ class Node(override val configuration: FullNodeConfiguration, networkMapAddress:
|
||||
// file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already
|
||||
// exists, we try to take the file lock first before replacing it and if that fails it means we're being started
|
||||
// twice with the same directory: that's a user error and we should bail out.
|
||||
val pidPath = configuration.basedir.resolve("process-id")
|
||||
val pidPath = configuration.basedir / "process-id"
|
||||
val file = pidPath.toFile()
|
||||
if (!file.exists()) {
|
||||
file.createNewFile()
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.r3corda.node.services
|
||||
|
||||
import com.r3corda.core.exists
|
||||
import com.r3corda.core.use
|
||||
import com.r3corda.core.read
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
|
||||
@ -25,7 +25,7 @@ class PropertiesFileRPCUserService(file: Path) : RPCUserService {
|
||||
init {
|
||||
_users = if (file.exists()) {
|
||||
val properties = Properties()
|
||||
file.use {
|
||||
file.read {
|
||||
properties.load(it)
|
||||
}
|
||||
properties.map {
|
||||
|
@ -1,7 +1,10 @@
|
||||
package com.r3corda.node.services.config
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.r3corda.core.copyTo
|
||||
import com.r3corda.core.createDirectories
|
||||
import com.r3corda.core.crypto.X509Utilities
|
||||
import com.r3corda.core.div
|
||||
import com.r3corda.core.exists
|
||||
import com.r3corda.core.utilities.loggerFor
|
||||
import com.typesafe.config.Config
|
||||
@ -29,7 +32,7 @@ object ConfigHelper {
|
||||
val defaultConfig = ConfigFactory.parseResources("reference.conf", ConfigParseOptions.defaults().setAllowMissing(false))
|
||||
|
||||
val normalisedBaseDir = baseDirectoryPath.normalize()
|
||||
val configFile = (configFileOverride?.normalize() ?: normalisedBaseDir.resolve("node.conf")).toFile()
|
||||
val configFile = (configFileOverride?.normalize() ?: normalisedBaseDir / "node.conf").toFile()
|
||||
val appConfig = ConfigFactory.parseFile(configFile, ConfigParseOptions.defaults().setAllowMissing(allowMissingConfig))
|
||||
|
||||
val overridesMap = HashMap<String, Any?>() // If we do require a few other command line overrides eg for a nicer development experience they would go inside this map.
|
||||
@ -89,10 +92,9 @@ fun Config.getProperties(path: String): Properties {
|
||||
* the CA certs in Node resources. Then provision KeyStores into certificates folder under node path.
|
||||
*/
|
||||
fun NodeSSLConfiguration.configureWithDevSSLCertificate() {
|
||||
Files.createDirectories(certificatesPath)
|
||||
certificatesPath.createDirectories()
|
||||
if (!trustStorePath.exists()) {
|
||||
Files.copy(javaClass.classLoader.getResourceAsStream("com/r3corda/node/internal/certificates/cordatruststore.jks"),
|
||||
trustStorePath)
|
||||
javaClass.classLoader.getResourceAsStream("com/r3corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStorePath)
|
||||
}
|
||||
if (!keyStorePath.exists()) {
|
||||
val caKeyStore = X509Utilities.loadKeyStore(
|
||||
|
@ -7,7 +7,7 @@ import com.r3corda.core.crypto.toBase58String
|
||||
import com.r3corda.core.messaging.MessageRecipients
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.serialization.SingletonSerializeAsToken
|
||||
import com.r3corda.core.use
|
||||
import com.r3corda.core.read
|
||||
import com.r3corda.node.services.config.NodeSSLConfiguration
|
||||
import com.r3corda.node.services.config.configureWithDevSSLCertificate
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
@ -110,10 +110,10 @@ abstract class ArtemisMessagingComponent() : SingletonSerializeAsToken() {
|
||||
* unfortunately Artemis tends to bury the exception when the password is wrong.
|
||||
*/
|
||||
fun checkStorePasswords() {
|
||||
config.keyStorePath.use {
|
||||
config.keyStorePath.read {
|
||||
KeyStore.getInstance("JKS").load(it, config.keyStorePassword.toCharArray())
|
||||
}
|
||||
config.trustStorePath.use {
|
||||
config.trustStorePath.read {
|
||||
KeyStore.getInstance("JKS").load(it, config.trustStorePassword.toCharArray())
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ import com.google.common.annotations.VisibleForTesting
|
||||
import com.google.common.hash.Hashing
|
||||
import com.google.common.hash.HashingInputStream
|
||||
import com.google.common.io.CountingInputStream
|
||||
import com.r3corda.core.*
|
||||
import com.r3corda.core.contracts.Attachment
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.extractZipFile
|
||||
import com.r3corda.core.node.services.AttachmentStorage
|
||||
import com.r3corda.core.utilities.loggerFor
|
||||
import com.r3corda.node.services.api.AcceptsFileUpload
|
||||
@ -35,7 +35,7 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta
|
||||
}
|
||||
|
||||
// Just count all non-directories in the attachment store, and assume the admin hasn't dumped any junk there.
|
||||
private fun countAttachments() = Files.list(storePath).filter { Files.isRegularFile(it) }.count()
|
||||
private fun countAttachments() = storePath.list { it.filter { it.isRegularFile() }.count() }
|
||||
|
||||
/**
|
||||
* If true, newly inserted attachments will be unzipped to a subdirectory of the [storePath]. This is intended for
|
||||
@ -45,7 +45,7 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta
|
||||
@Volatile var automaticallyExtractAttachments = false
|
||||
|
||||
init {
|
||||
require(Files.isDirectory(storePath)) { "$storePath must be a directory" }
|
||||
require(storePath.isDirectory()) { "$storePath must be a directory" }
|
||||
}
|
||||
|
||||
class OnDiskHashMismatch(val file: Path, val actual: SecureHash) : Exception() {
|
||||
@ -66,7 +66,7 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta
|
||||
private val counter: CountingInputStream = CountingInputStream(input),
|
||||
private val stream: HashingInputStream = HashingInputStream(Hashing.sha256(), counter)) : FilterInputStream(stream) {
|
||||
|
||||
private val expectedSize = Files.size(filePath)
|
||||
private val expectedSize = filePath.size
|
||||
|
||||
override fun close() {
|
||||
super.close()
|
||||
@ -94,8 +94,8 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta
|
||||
}
|
||||
|
||||
override fun openAttachment(id: SecureHash): Attachment? {
|
||||
val path = storePath.resolve(id.toString())
|
||||
if (!Files.exists(path)) return null
|
||||
val path = storePath / id.toString()
|
||||
if (!path.exists()) return null
|
||||
return AttachmentImpl(id, path, checkAttachmentsOnLoad)
|
||||
}
|
||||
|
||||
@ -103,28 +103,28 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta
|
||||
override fun importAttachment(jar: InputStream): SecureHash {
|
||||
require(jar !is JarInputStream)
|
||||
val hs = HashingInputStream(Hashing.sha256(), jar)
|
||||
val tmp = storePath.resolve("tmp.${UUID.randomUUID()}")
|
||||
Files.copy(hs, tmp)
|
||||
val tmp = storePath / "tmp.${UUID.randomUUID()}"
|
||||
hs.copyTo(tmp)
|
||||
checkIsAValidJAR(tmp)
|
||||
val id = SecureHash.SHA256(hs.hash().asBytes())
|
||||
val finalPath = storePath.resolve(id.toString())
|
||||
val finalPath = storePath / id.toString()
|
||||
try {
|
||||
// Move into place atomically or fail if that isn't possible. We don't want a half moved attachment to
|
||||
// be exposed to parallel threads. This gives us thread safety.
|
||||
if (!Files.exists(finalPath)) {
|
||||
if (!finalPath.exists()) {
|
||||
log.info("Stored new attachment $id")
|
||||
attachmentCount.inc()
|
||||
} else {
|
||||
log.info("Replacing attachment $id - only bother doing this if you're trying to repair file corruption")
|
||||
}
|
||||
Files.move(tmp, finalPath, StandardCopyOption.ATOMIC_MOVE)
|
||||
tmp.moveTo(finalPath, StandardCopyOption.ATOMIC_MOVE)
|
||||
} finally {
|
||||
Files.deleteIfExists(tmp)
|
||||
tmp.deleteIfExists()
|
||||
}
|
||||
if (automaticallyExtractAttachments) {
|
||||
val extractTo = storePath.resolve("$id.jar")
|
||||
val extractTo = storePath / "$id.jar"
|
||||
try {
|
||||
Files.createDirectory(extractTo)
|
||||
extractTo.createDirectory()
|
||||
extractZipFile(finalPath, extractTo)
|
||||
} catch(e: FileAlreadyExistsException) {
|
||||
log.trace("Did not extract attachment jar to directory because it already exists")
|
||||
@ -138,9 +138,10 @@ class NodeAttachmentService(val storePath: Path, metrics: MetricRegistry) : Atta
|
||||
|
||||
private fun checkIsAValidJAR(path: Path) {
|
||||
// Just iterate over the entries with verification enabled: should be good enough to catch mistakes.
|
||||
JarInputStream(Files.newInputStream(path), true).use { stream ->
|
||||
path.read {
|
||||
val jar = JarInputStream(it)
|
||||
while (true) {
|
||||
val cursor = stream.nextJarEntry ?: break
|
||||
val cursor = jar.nextJarEntry ?: break
|
||||
val entryPath = Paths.get(cursor.name)
|
||||
// Security check to stop zips trying to escape their rightful place.
|
||||
if (entryPath.isAbsolute || entryPath.normalize() != entryPath || '\\' in cursor.name)
|
||||
|
@ -1,13 +1,12 @@
|
||||
package com.r3corda.node.utilities.certsigning
|
||||
|
||||
import com.r3corda.core.*
|
||||
import com.r3corda.core.crypto.X509Utilities
|
||||
import com.r3corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||
import com.r3corda.core.crypto.X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY
|
||||
import com.r3corda.core.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import com.r3corda.core.crypto.X509Utilities.addOrReplaceCertificate
|
||||
import com.r3corda.core.crypto.X509Utilities.addOrReplaceKey
|
||||
import com.r3corda.core.div
|
||||
import com.r3corda.core.minutes
|
||||
import com.r3corda.core.utilities.loggerFor
|
||||
import com.r3corda.node.services.config.ConfigHelper
|
||||
import com.r3corda.node.services.config.FullNodeConfiguration
|
||||
@ -15,7 +14,6 @@ import com.r3corda.node.services.config.NodeConfiguration
|
||||
import com.r3corda.node.services.config.getValue
|
||||
import joptsimple.OptionParser
|
||||
import java.net.URL
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.Certificate
|
||||
@ -34,7 +32,7 @@ class CertificateSigner(val config: NodeConfiguration, val certService: Certific
|
||||
}
|
||||
|
||||
fun buildKeyStore() {
|
||||
Files.createDirectories(config.certificatesPath)
|
||||
config.certificatesPath.createDirectories()
|
||||
|
||||
val caKeyStore = X509Utilities.loadOrCreateKeyStore(config.keyStorePath, config.keyStorePassword)
|
||||
|
||||
@ -101,15 +99,15 @@ class CertificateSigner(val config: NodeConfiguration, val certService: Certific
|
||||
private fun submitCertificateSigningRequest(keyPair: KeyPair): String {
|
||||
val requestIdStore = config.certificatesPath / "certificate-request-id.txt"
|
||||
// Retrieve request id from file if exists, else post a request to server.
|
||||
return if (!Files.exists(requestIdStore)) {
|
||||
return if (!requestIdStore.exists()) {
|
||||
val request = X509Utilities.createCertificateSigningRequest(config.myLegalName, config.nearestCity, config.emailAddress, keyPair)
|
||||
// Post request to signing server via http.
|
||||
val requestId = certService.submitRequest(request)
|
||||
// Persists request ID to file in case of node shutdown.
|
||||
Files.write(requestIdStore, listOf(requestId), Charsets.UTF_8)
|
||||
requestIdStore.writeLines(listOf(requestId))
|
||||
requestId
|
||||
} else {
|
||||
Files.readAllLines(requestIdStore).first()
|
||||
requestIdStore.readLines { it.findFirst().get() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.crypto.sha256
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.node.services.ServiceInfo
|
||||
import com.r3corda.core.serialization.OpaqueBytes
|
||||
import com.r3corda.core.write
|
||||
import com.r3corda.node.services.config.NodeConfiguration
|
||||
import com.r3corda.node.services.network.NetworkMapService
|
||||
import com.r3corda.node.services.persistence.NodeAttachmentService
|
||||
@ -18,9 +18,6 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.security.KeyPair
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
@ -103,9 +100,7 @@ class AttachmentTests {
|
||||
val id = n0.storage.attachments.importAttachment(ByteArrayInputStream(fakeAttachment()))
|
||||
|
||||
// Corrupt its store.
|
||||
val writer = Files.newByteChannel(network.filesystem.getPath("/nodes/0/attachments/$id"), StandardOpenOption.WRITE)
|
||||
writer.write(ByteBuffer.wrap(OpaqueBytes.of(99, 99, 99, 99).bits))
|
||||
writer.close()
|
||||
network.filesystem.getPath("/nodes/0/attachments/$id").write { it.write(byteArrayOf(99, 99, 99, 99)) }
|
||||
|
||||
// Get n1 to fetch the attachment. Should receive corrupted bytes.
|
||||
network.runNetwork()
|
||||
|
@ -4,12 +4,19 @@ import com.codahale.metrics.MetricRegistry
|
||||
import com.google.common.jimfs.Configuration
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.use
|
||||
import com.r3corda.core.crypto.sha256
|
||||
import com.r3corda.core.div
|
||||
import com.r3corda.core.read
|
||||
import com.r3corda.core.readAll
|
||||
import com.r3corda.core.write
|
||||
import com.r3corda.node.services.persistence.NodeAttachmentService
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.file.*
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.FileSystem
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardOpenOption.WRITE
|
||||
import java.util.jar.JarEntry
|
||||
import java.util.jar.JarOutputStream
|
||||
import kotlin.test.assertEquals
|
||||
@ -28,10 +35,10 @@ class NodeAttachmentStorageTest {
|
||||
@Test
|
||||
fun `insert and retrieve`() {
|
||||
val testJar = makeTestJar()
|
||||
val expectedHash = SecureHash.sha256(Files.readAllBytes(testJar))
|
||||
val expectedHash = testJar.readAll().sha256()
|
||||
|
||||
val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
|
||||
val id = testJar.use { storage.importAttachment(it) }
|
||||
val id = testJar.read { storage.importAttachment(it) }
|
||||
assertEquals(expectedHash, id)
|
||||
|
||||
assertNull(storage.openAttachment(SecureHash.randomSHA256()))
|
||||
@ -48,9 +55,9 @@ class NodeAttachmentStorageTest {
|
||||
fun `duplicates not allowed`() {
|
||||
val testJar = makeTestJar()
|
||||
val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
|
||||
testJar.use { storage.importAttachment(it) }
|
||||
testJar.read { storage.importAttachment(it) }
|
||||
assertFailsWith<FileAlreadyExistsException> {
|
||||
testJar.use { storage.importAttachment(it) }
|
||||
testJar.read { storage.importAttachment(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,15 +65,15 @@ class NodeAttachmentStorageTest {
|
||||
fun `corrupt entry throws exception`() {
|
||||
val testJar = makeTestJar()
|
||||
val storage = NodeAttachmentService(fs.getPath("/"), MetricRegistry())
|
||||
val id = testJar.use { storage.importAttachment(it) }
|
||||
val id = testJar.read { storage.importAttachment(it) }
|
||||
|
||||
// Corrupt the file in the store.
|
||||
Files.write(fs.getPath("/", id.toString()), "arggghhhh".toByteArray(), StandardOpenOption.WRITE)
|
||||
fs.getPath("/", id.toString()).write(options = WRITE) { it.write("arggghhhh".toByteArray()) }
|
||||
|
||||
val e = assertFailsWith<NodeAttachmentService.OnDiskHashMismatch> {
|
||||
storage.openAttachment(id)!!.open().use { it.readBytes() }
|
||||
}
|
||||
assertEquals(e.file, storage.storePath.resolve(id.toString()))
|
||||
assertEquals(e.file, storage.storePath / id.toString())
|
||||
|
||||
// But if we skip around and read a single entry, no exception is thrown.
|
||||
storage.openAttachment(id)!!.openAsJAR().use {
|
||||
@ -78,15 +85,16 @@ class NodeAttachmentStorageTest {
|
||||
private var counter = 0
|
||||
private fun makeTestJar(): Path {
|
||||
counter++
|
||||
val f = fs.getPath("$counter.jar")
|
||||
JarOutputStream(Files.newOutputStream(f)).use {
|
||||
it.putNextEntry(JarEntry("test1.txt"))
|
||||
it.write("This is some useful content".toByteArray())
|
||||
it.closeEntry()
|
||||
it.putNextEntry(JarEntry("test2.txt"))
|
||||
it.write("Some more useful content".toByteArray())
|
||||
it.closeEntry()
|
||||
val file = fs.getPath("$counter.jar")
|
||||
file.write {
|
||||
val jar = JarOutputStream(it)
|
||||
jar.putNextEntry(JarEntry("test1.txt"))
|
||||
jar.write("This is some useful content".toByteArray())
|
||||
jar.closeEntry()
|
||||
jar.putNextEntry(JarEntry("test2.txt"))
|
||||
jar.write("Some more useful content".toByteArray())
|
||||
jar.closeEntry()
|
||||
}
|
||||
return f
|
||||
return file
|
||||
}
|
||||
}
|
@ -2,11 +2,11 @@ package com.r3corda.node.services
|
||||
|
||||
import com.google.common.jimfs.Configuration.unix
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import com.r3corda.core.writeLines
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
|
||||
class PropertiesFileRPCUserServiceTest {
|
||||
|
||||
@ -75,7 +75,7 @@ class PropertiesFileRPCUserServiceTest {
|
||||
}
|
||||
|
||||
private fun loadWithContents(vararg lines: String): PropertiesFileRPCUserService {
|
||||
Files.write(file, lines.asList())
|
||||
file.writeLines(lines.asList())
|
||||
return PropertiesFileRPCUserService(file)
|
||||
}
|
||||
}
|
@ -6,11 +6,12 @@ import com.nhaarman.mockito_kotlin.mock
|
||||
import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.crypto.X509Utilities
|
||||
import com.r3corda.core.div
|
||||
import com.r3corda.core.exists
|
||||
import com.r3corda.core.readLines
|
||||
import com.r3corda.node.services.config.NodeConfiguration
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
@ -47,13 +48,13 @@ class CertificateSignerTest {
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
}
|
||||
|
||||
assertFalse(Files.exists(config.keyStorePath))
|
||||
assertFalse(Files.exists(config.trustStorePath))
|
||||
assertFalse(config.keyStorePath.exists())
|
||||
assertFalse(config.trustStorePath.exists())
|
||||
|
||||
CertificateSigner(config, certService).buildKeyStore()
|
||||
|
||||
assertTrue(Files.exists(config.keyStorePath))
|
||||
assertTrue(Files.exists(config.trustStorePath))
|
||||
assertTrue(config.keyStorePath.exists())
|
||||
assertTrue(config.trustStorePath.exists())
|
||||
|
||||
X509Utilities.loadKeyStore(config.keyStorePath, config.keyStorePassword).run {
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY))
|
||||
@ -73,7 +74,7 @@ class CertificateSignerTest {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY))
|
||||
}
|
||||
|
||||
assertEquals(id, Files.readAllLines(config.certificatesPath / "certificate-request-id.txt").first())
|
||||
assertEquals(id, (config.certificatesPath / "certificate-request-id.txt").readLines { it.findFirst().get() })
|
||||
}
|
||||
|
||||
}
|
@ -3,7 +3,10 @@ package com.r3corda.testing.node
|
||||
import com.google.common.jimfs.Configuration.unix
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.r3corda.core.createDirectories
|
||||
import com.r3corda.core.createDirectory
|
||||
import com.r3corda.core.crypto.Party
|
||||
import com.r3corda.core.div
|
||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||
import com.r3corda.core.node.PhysicalLocation
|
||||
import com.r3corda.core.node.services.KeyManagementService
|
||||
@ -13,11 +16,9 @@ import com.r3corda.core.random63BitValue
|
||||
import com.r3corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import com.r3corda.core.utilities.loggerFor
|
||||
import com.r3corda.node.internal.AbstractNode
|
||||
import com.r3corda.node.internal.CordaRPCOpsImpl
|
||||
import com.r3corda.node.services.api.MessagingServiceInternal
|
||||
import com.r3corda.node.services.config.NodeConfiguration
|
||||
import com.r3corda.node.services.keys.E2ETestKeyManagementService
|
||||
import com.r3corda.node.services.messaging.CordaRPCOps
|
||||
import com.r3corda.node.services.messaging.RPCOps
|
||||
import com.r3corda.node.services.network.InMemoryNetworkMapService
|
||||
import com.r3corda.node.services.network.NetworkMapService
|
||||
@ -29,7 +30,6 @@ import com.r3corda.node.utilities.AffinityExecutor
|
||||
import com.r3corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
|
||||
import org.slf4j.Logger
|
||||
import java.nio.file.FileSystem
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.util.*
|
||||
@ -66,7 +66,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
||||
val nodes: List<MockNode> = _nodes
|
||||
|
||||
init {
|
||||
Files.createDirectory(filesystem.getPath("/nodes"))
|
||||
filesystem.getPath("/nodes").createDirectory()
|
||||
}
|
||||
|
||||
/** Allows customisation of how nodes are created. */
|
||||
@ -182,7 +182,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
||||
|
||||
val path = filesystem.getPath("/nodes/$id")
|
||||
if (newNode)
|
||||
Files.createDirectories(path.resolve("attachments"))
|
||||
(path / "attachments").createDirectories()
|
||||
|
||||
// TODO: create a base class that provides a default implementation
|
||||
val config = object : NodeConfiguration {
|
||||
|
Loading…
Reference in New Issue
Block a user