diff --git a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt index 3ce8698aa4..af6898f7b4 100644 --- a/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/SendTransactionFlow.kt @@ -13,6 +13,7 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.StateAndRef import net.corda.core.internal.FetchDataFlow +import net.corda.core.internal.readFully import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.unwrap @@ -70,7 +71,7 @@ open class DataVendingFlow(val otherSideSession: FlowSession, val payload: Any) serviceHub.validatedTransactions.getTransaction(it) ?: throw FetchDataFlow.HashNotFound(it) } FetchDataFlow.DataType.ATTACHMENT -> dataRequest.hashes.map { - serviceHub.attachments.openAttachment(it)?.open()?.readBytes() ?: throw FetchDataFlow.HashNotFound(it) + serviceHub.attachments.openAttachment(it)?.open()?.readFully() ?: throw FetchDataFlow.HashNotFound(it) } } } diff --git a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt index 4627b99bd7..bb8b125d8f 100644 --- a/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt +++ b/core/src/main/kotlin/net/corda/core/internal/AbstractAttachment.kt @@ -38,7 +38,7 @@ abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment { fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray { return { val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id)) - (a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() } + (a as? AbstractAttachment)?.attachmentData ?: a.open().readFully() } } diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index aa3fe09997..820171a02a 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -12,6 +12,8 @@ package net.corda.core.internal +import com.google.common.hash.Hashing +import com.google.common.hash.HashingInputStream import net.corda.core.cordapp.Cordapp import net.corda.core.cordapp.CordappConfig import net.corda.core.cordapp.CordappContext @@ -50,6 +52,7 @@ import java.nio.file.attribute.FileAttribute import java.nio.file.attribute.FileTime import java.security.KeyPair import java.security.PrivateKey +import java.security.PublicKey import java.security.cert.X509Certificate import java.time.Duration import java.time.temporal.Temporal @@ -160,8 +163,31 @@ fun Path.writeLines(lines: Iterable, charset: Charset = UTF_8, var inline fun Path.readObject(): T = readAll().deserialize() +/** Calculate the hash of the contents of this file. */ +val Path.hash: SecureHash get() = read { it.hash() } + fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options) +/** Same as [InputStream.readBytes] but also closes the stream. */ +fun InputStream.readFully(): ByteArray = use { it.readBytes() } + +/** Calculate the hash of the remaining bytes in this input stream. The stream is closed at the end. */ +fun InputStream.hash(): SecureHash { + return use { + val his = HashingInputStream(Hashing.sha256(), it) + his.copyTo(NullOutputStream) // To avoid reading in the entire stream into memory just write out the bytes to /dev/null + SecureHash.SHA256(his.hash().asBytes()) + } +} + +inline fun InputStream.readObject(): T = readFully().deserialize() + +object NullOutputStream : OutputStream() { + override fun write(b: Int) = Unit + override fun write(b: ByteArray) = Unit + override fun write(b: ByteArray, off: Int, len: Int) = Unit +} + fun String.abbreviate(maxWidth: Int): String = if (length <= maxWidth) this else take(maxWidth - 1) + "…" /** Return the sum of an Iterable of [BigDecimal]s. */ @@ -215,7 +241,7 @@ fun logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T /** Convert a [ByteArrayOutputStream] to [InputStreamAndHash]. */ fun ByteArrayOutputStream.toInputStreamAndHash(): InputStreamAndHash { val bytes = toByteArray() - return InputStreamAndHash(ByteArrayInputStream(bytes), bytes.sha256()) + return InputStreamAndHash(bytes.inputStream(), bytes.sha256()) } data class InputStreamAndHash(val inputStream: InputStream, val sha256: SecureHash.SHA256) { @@ -343,7 +369,7 @@ fun URL.post(serializedData: OpaqueBytes): ByteArray { setRequestProperty("Content-Type", "application/octet-stream") outputStream.use { serializedData.open().copyTo(it) } checkOkResponse() - inputStream.use { it.readBytes() } + inputStream.readFully() } } @@ -356,7 +382,7 @@ fun HttpURLConnection.checkOkResponse() { inline fun HttpURLConnection.responseAs(): T { checkOkResponse() - return inputStream.use { it.readBytes() }.deserialize() + return inputStream.readObject() } /** Analogous to [Thread.join]. */ @@ -427,3 +453,5 @@ fun NotarisationRequest.generateSignature(serviceHub: ServiceHub): NotarisationR } return NotarisationRequestSignature(signature, serviceHub.myInfo.platformVersion) } + +val PublicKey.hash: SecureHash get() = encoded.sha256() diff --git a/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt index 43796ce874..21c9eb953c 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt @@ -14,7 +14,7 @@ package net.corda.core.utilities import net.corda.core.crypto.Base58 import net.corda.core.crypto.Crypto -import net.corda.core.crypto.sha256 +import net.corda.core.internal.hash import java.nio.charset.Charset import java.security.PublicKey import java.util.* @@ -95,4 +95,4 @@ fun parsePublicKeyBase58(base58String: String): PublicKey = Crypto.decodePublicK fun PublicKey.toBase58String(): String = this.encoded.toBase58() /** Return the bytes of the SHA-256 output for this public key. */ -fun PublicKey.toSHA256Bytes(): ByteArray = this.encoded.sha256().bytes +fun PublicKey.toSHA256Bytes(): ByteArray = this.hash.bytes diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index 1df45dd5a2..555d105a0e 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -17,6 +17,7 @@ import net.corda.core.crypto.sha256 import net.corda.core.identity.Party import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow +import net.corda.core.internal.hash import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.persistence.NodeAttachmentService @@ -29,7 +30,6 @@ import net.corda.testing.node.internal.startFlow import org.junit.After import org.junit.Before import org.junit.Test -import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.util.jar.JarOutputStream import java.util.zip.ZipEntry @@ -68,7 +68,7 @@ class AttachmentTests { bobNode.registerInitiatedFlow(FetchAttachmentsResponse::class.java) // Insert an attachment into node zero's store directly. val id = aliceNode.database.transaction { - aliceNode.attachments.importAttachment(ByteArrayInputStream(fakeAttachment())) + aliceNode.attachments.importAttachment(fakeAttachment().inputStream()) } // Get node one to run a flow to fetch it and insert it. @@ -82,7 +82,7 @@ class AttachmentTests { bobNode.attachments.openAttachment(id)!! } - assertEquals(id, attachment.open().readBytes().sha256()) + assertEquals(id, attachment.open().hash()) // Shut down node zero and ensure node one can still resolve the attachment. aliceNode.dispose() @@ -121,7 +121,7 @@ class AttachmentTests { val attachment = fakeAttachment() // Insert an attachment into node zero's store directly. val id = aliceNode.database.transaction { - aliceNode.attachments.importAttachment(ByteArrayInputStream(attachment)) + aliceNode.attachments.importAttachment(attachment.inputStream()) } // Corrupt its store. diff --git a/experimental/kryo-hook/src/main/kotlin/net/corda/kryohook/KryoHook.kt b/experimental/kryo-hook/src/main/kotlin/net/corda/kryohook/KryoHook.kt index 18007c2fd5..3dee28dbee 100644 --- a/experimental/kryo-hook/src/main/kotlin/net/corda/kryohook/KryoHook.kt +++ b/experimental/kryo-hook/src/main/kotlin/net/corda/kryohook/KryoHook.kt @@ -15,13 +15,10 @@ import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.io.Output import javassist.ClassPool import javassist.CtClass -import java.io.ByteArrayInputStream -import java.lang.StringBuilder import java.lang.instrument.ClassFileTransformer import java.lang.instrument.Instrumentation import java.security.ProtectionDomain import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicInteger class KryoHookAgent { companion object { @@ -79,7 +76,7 @@ object KryoHook : ClassFileTransformer { return null } return try { - val clazz = classPool.makeClass(ByteArrayInputStream(classfileBuffer)) + val clazz = classPool.makeClass(classfileBuffer.inputStream()) instrumentClass(clazz)?.toBytecode() } catch (throwable: Throwable) { println("SOMETHING WENT WRONG") diff --git a/experimental/quasar-hook/src/main/kotlin/net/corda/quasarhook/QuasarInstrumentationHook.kt b/experimental/quasar-hook/src/main/kotlin/net/corda/quasarhook/QuasarInstrumentationHook.kt index f5a2b90eee..787a2f7c4a 100644 --- a/experimental/quasar-hook/src/main/kotlin/net/corda/quasarhook/QuasarInstrumentationHook.kt +++ b/experimental/quasar-hook/src/main/kotlin/net/corda/quasarhook/QuasarInstrumentationHook.kt @@ -12,7 +12,6 @@ package net.corda.quasarhook import javassist.ClassPool import javassist.CtClass -import java.io.ByteArrayInputStream import java.lang.instrument.ClassFileTransformer import java.lang.instrument.Instrumentation import java.security.ProtectionDomain @@ -193,9 +192,9 @@ object QuasarInstrumentationHook : ClassFileTransformer { classfileBuffer: ByteArray ): ByteArray { return try { - val instrument = instrumentMap.get(className) + val instrument = instrumentMap[className] return instrument?.let { - val clazz = classPool.makeClass(ByteArrayInputStream(classfileBuffer)) + val clazz = classPool.makeClass(classfileBuffer.inputStream()) it(clazz) clazz.toBytecode() } ?: classfileBuffer diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoader.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoader.kt index 742f0a4ec4..6ab83424f7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoader.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoader.kt @@ -15,7 +15,6 @@ import net.corda.core.contracts.ContractAttachment import net.corda.core.crypto.SecureHash import net.corda.core.internal.isUploaderTrusted import net.corda.core.serialization.CordaSerializable -import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.FileNotFoundException import java.io.InputStream @@ -111,12 +110,12 @@ class AttachmentsClassLoader(attachments: List, parent: ClassLoader if (url.protocol != "attachment") return null val attachment = idsToAttachments[SecureHash.parse(url.host)] ?: return null val path = url.path?.substring(1) ?: return null // Chop off the leading slash. - try { + return try { val stream = ByteArrayOutputStream() attachment.extractFile(path, stream) - return ByteArrayInputStream(stream.toByteArray()) + stream.toByteArray().inputStream() } catch (e: FileNotFoundException) { - return null + null } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index 2dcfdf709c..c4c4f3d57c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -18,12 +18,14 @@ import net.corda.core.internal.* import net.corda.core.utilities.days import net.corda.core.utilities.millis import org.bouncycastle.asn1.* +import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.asn1.x509.* import org.bouncycastle.asn1.x509.Extension import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509v3CertificateBuilder import org.bouncycastle.cert.bc.BcX509ExtensionUtils +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.operator.ContentSigner @@ -159,18 +161,25 @@ $trustedRoot""", e, certPath, e.index) /** * Build a partial X.509 certificate ready for signing. * + * @param certificateType type of the certificate. * @param issuer name of the issuing entity. + * @param issuerPublicKey public key of the issuing entity. * @param subject name of the certificate subject. * @param subjectPublicKey public key of the certificate subject. * @param validityWindow the time period the certificate is valid for. * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. + * @param crlDistPoint CRL distribution point. + * @param crlIssuer X500Name of the CRL issuer. */ - fun createPartialCertificate(certificateType: CertificateType, + private fun createPartialCertificate(certificateType: CertificateType, issuer: X500Principal, + issuerPublicKey: PublicKey, subject: X500Principal, subjectPublicKey: PublicKey, validityWindow: Pair, - nameConstraints: NameConstraints? = null): X509v3CertificateBuilder { + nameConstraints: NameConstraints? = null, + crlDistPoint: String? = null, + crlIssuer: X500Name? = null): X509v3CertificateBuilder { val serial = BigInteger.valueOf(random63BitValue()) val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } }) val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded)) @@ -181,10 +190,12 @@ $trustedRoot""", e, certPath, e.index) .addExtension(Extension.basicConstraints, true, BasicConstraints(certificateType.isCA)) .addExtension(Extension.keyUsage, false, certificateType.keyUsage) .addExtension(Extension.extendedKeyUsage, false, keyPurposes) + .addExtension(Extension.authorityKeyIdentifier, false, JcaX509ExtensionUtils().createAuthorityKeyIdentifier(issuerPublicKey)) if (role != null) { builder.addExtension(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), false, role) } + addCrlInfo(builder, crlDistPoint, crlIssuer) if (nameConstraints != null) { builder.addExtension(Extension.nameConstraints, true, nameConstraints) } @@ -195,11 +206,15 @@ $trustedRoot""", e, certPath, e.index) /** * Create a X509 v3 certificate using the given issuer certificate and key pair. * + * @param certificateType type of the certificate. * @param issuerCertificate The Public certificate of the root CA above this used to sign it. * @param issuerKeyPair The KeyPair of the root CA above this used to sign it. * @param subject subject of the generated certificate. * @param subjectPublicKey subject's public key. * @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided. + * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. + * @param crlDistPoint CRL distribution point. + * @param crlIssuer X500Name of the CRL issuer. * @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates. * Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates. */ @@ -210,7 +225,9 @@ $trustedRoot""", e, certPath, e.index) subject: X500Principal, subjectPublicKey: PublicKey, validityWindow: Pair = DEFAULT_VALIDITY_WINDOW, - nameConstraints: NameConstraints? = null): X509Certificate { + nameConstraints: NameConstraints? = null, + crlDistPoint: String? = null, + crlIssuer: X500Name? = null): X509Certificate { val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate) return createCertificate( certificateType, @@ -219,28 +236,36 @@ $trustedRoot""", e, certPath, e.index) subject, subjectPublicKey, window, - nameConstraints + nameConstraints, + crlDistPoint, + crlIssuer ) } /** * Build and sign an X.509 certificate with the given signer. * + * @param certificateType type of the certificate. * @param issuer name of the issuing entity. * @param issuerSigner content signer to sign the certificate with. * @param subject name of the certificate subject. * @param subjectPublicKey public key of the certificate subject. * @param validityWindow the time period the certificate is valid for. * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. + * @param crlDistPoint CRL distribution point. + * @param crlIssuer X500Name of the CRL issuer. */ fun createCertificate(certificateType: CertificateType, issuer: X500Principal, + issuerPublicKey: PublicKey, issuerSigner: ContentSigner, subject: X500Principal, subjectPublicKey: PublicKey, validityWindow: Pair, - nameConstraints: NameConstraints? = null): X509Certificate { - val builder = createPartialCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints) + nameConstraints: NameConstraints? = null, + crlDistPoint: String? = null, + crlIssuer: X500Name? = null): X509Certificate { + val builder = createPartialCertificate(certificateType, issuer, issuerPublicKey, subject, subjectPublicKey, validityWindow, nameConstraints, crlDistPoint, crlIssuer) return builder.build(issuerSigner).run { require(isValidOn(Date())) toJca() @@ -250,6 +275,7 @@ $trustedRoot""", e, certPath, e.index) /** * Build and sign an X.509 certificate with CA cert private key. * + * @param certificateType type of the certificate. * @param issuer name of the issuing entity. * @param issuerKeyPair the public & private key to sign the certificate with. * @param subject name of the certificate subject. @@ -263,11 +289,21 @@ $trustedRoot""", e, certPath, e.index) subject: X500Principal, subjectPublicKey: PublicKey, validityWindow: Pair, - nameConstraints: NameConstraints? = null): X509Certificate { + nameConstraints: NameConstraints? = null, + crlDistPoint: String? = null, + crlIssuer: X500Name? = null): X509Certificate { val signatureScheme = Crypto.findSignatureScheme(issuerKeyPair.private) val provider = Crypto.findProvider(signatureScheme.providerName) val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider) - val builder = createPartialCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints) + val builder = createPartialCertificate( + certificateType, + issuer, + issuerKeyPair.public, + subject, subjectPublicKey, + validityWindow, + nameConstraints, + crlDistPoint, + crlIssuer) return builder.build(signer).run { require(isValidOn(Date())) require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public))) @@ -312,6 +348,21 @@ $trustedRoot""", e, certPath, e.index) fun buildCertPath(certificates: List): CertPath { return X509CertificateFactory().generateCertPath(certificates) } + + private fun addCrlInfo(builder: X509v3CertificateBuilder, crlDistPoint: String?, crlIssuer: X500Name?) { + if (crlDistPoint != null) { + val distPointName = DistributionPointName(GeneralNames(GeneralName(GeneralName.uniformResourceIdentifier, crlDistPoint))) + val crlIssuerGeneralNames = crlIssuer?.let { + GeneralNames(GeneralName(crlIssuer)) + } + // The second argument is flag that allows you to define what reason of certificate revocation is served by this distribution point see [ReasonFlags]. + // The idea is that you have different revocation per revocation reason. Since we won't go into such a granularity, we can skip that parameter. + // The third argument allows you to specify the name of the CRL issuer, it needs to be consistent with the crl (IssuingDistributionPoint) extension and the idp argument. + // If idp == true, set it, if idp == false, leave it null as done here. + val distPoint = DistributionPoint(distPointName, null, crlIssuerGeneralNames) + builder.addExtension(Extension.cRLDistributionPoints, false, CRLDistPoint(arrayOf(distPoint))) + } + } } // Assuming cert type to role is 1:1 diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt index dabc836a39..52a545edd9 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkBootstrapper.kt @@ -10,12 +10,8 @@ package net.corda.nodeapi.internal.network -import com.google.common.hash.Hashing -import com.google.common.hash.HashingInputStream import com.typesafe.config.ConfigFactory import net.corda.cordform.CordformNode -import net.corda.core.contracts.ContractClassName -import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash.Companion.parse import net.corda.core.identity.Party import net.corda.core.internal.* @@ -37,7 +33,6 @@ import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme import net.corda.nodeapi.internal.serialization.kryo.kryoMagic -import java.io.File import java.io.PrintStream import java.nio.file.Files import java.nio.file.Path @@ -201,7 +196,7 @@ class NetworkBootstrapper { private fun generateWhitelist(whitelistFile: Path, excludeWhitelistFile: Path, cordapps: List?): Map> { val existingWhitelist = if (whitelistFile.exists()) readContractWhitelist(whitelistFile) else emptyMap() - println(if (existingWhitelist.isEmpty()) "No existing whitelist file found." else "Found existing whitelist: ${whitelistFile}") + println(if (existingWhitelist.isEmpty()) "No existing whitelist file found." else "Found existing whitelist: $whitelistFile") val excludeContracts = if (excludeWhitelistFile.exists()) readExcludeWhitelist(excludeWhitelistFile) else emptyList() if (excludeContracts.isNotEmpty()) { @@ -209,7 +204,7 @@ class NetworkBootstrapper { } val newWhiteList = cordapps?.flatMap { cordappJarPath -> - val jarHash = getJarHash(cordappJarPath) + val jarHash = Paths.get(cordappJarPath).hash scanJarForContracts(cordappJarPath).map { contract -> contract to jarHash } @@ -223,30 +218,26 @@ class NetworkBootstrapper { contractClassName to (if (newHash == null || newHash in existing) existing else existing + newHash) }.toMap() - println("CorDapp whitelist " + (if (existingWhitelist.isEmpty()) "generated" else "updated") + " in ${whitelistFile}") + println("CorDapp whitelist " + (if (existingWhitelist.isEmpty()) "generated" else "updated") + " in $whitelistFile") return merged } private fun overwriteWhitelist(whitelistFile: Path, mergedWhiteList: Map>) { PrintStream(whitelistFile.toFile().outputStream()).use { out -> mergedWhiteList.forEach { (contract, attachments) -> - out.println("${contract}:${attachments.joinToString(",")}") + out.println("$contract:${attachments.joinToString(",")}") } } } - private fun getJarHash(cordappPath: String): AttachmentId = File(cordappPath).inputStream().use { jar -> - val hs = HashingInputStream(Hashing.sha256(), jar) - hs.readBytes() - SecureHash.SHA256(hs.hash().asBytes()) + private fun readContractWhitelist(file: Path): Map> { + return file.readAllLines() + .map { line -> line.split(":") } + .map { (contract, attachmentIds) -> + contract to (attachmentIds.split(",").map(::parse)) + }.toMap() } - private fun readContractWhitelist(file: Path): Map> = file.readAllLines() - .map { line -> line.split(":") } - .map { (contract, attachmentIds) -> - contract to (attachmentIds.split(",").map(::parse)) - }.toMap() - private fun readExcludeWhitelist(file: Path): List = file.readAllLines().map(String::trim) private fun NotaryInfo.prettyPrint(): String = "${identity.name} (${if (validating) "" else "non-"}validating)" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt index 11786c5674..0a7317e821 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/ContractAttachmentSerializer.kt @@ -13,6 +13,7 @@ package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractClassName +import net.corda.core.internal.readFully import net.corda.core.serialization.MissingAttachmentsException import net.corda.nodeapi.internal.serialization.GeneratedAttachment import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer @@ -27,7 +28,7 @@ class ContractAttachmentSerializer(factory: SerializerFactory) : CustomSerialize ContractAttachmentProxy::class.java, factory) { override fun toProxy(obj: ContractAttachment): ContractAttachmentProxy { val bytes = try { - obj.attachment.open().readBytes() + obj.attachment.open().readFully() } catch (e: Exception) { throw MissingAttachmentsException(listOf(obj.id)) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt index 27a98e7abb..e9d96784c8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/InputStreamSerializer.kt @@ -46,6 +46,6 @@ object InputStreamSerializer : CustomSerializer.Implements(InputStr override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput): InputStream { val bits = input.readObject(obj, schemas, ByteArray::class.java) as ByteArray - return ByteArrayInputStream(bits) + return bits.inputStream() } } \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt index 7cf9f9df3f..0b9d42f97d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/DefaultKryoCustomizer.kt @@ -28,6 +28,7 @@ import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.SecureHash import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.AbstractAttachment +import net.corda.core.internal.readFully import net.corda.core.serialization.MissingAttachmentsException import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken @@ -233,7 +234,7 @@ object DefaultKryoCustomizer { val lazyAttachment = object : AbstractAttachment({ val attachment = attachmentStorage.openAttachment(attachmentHash) ?: throw MissingAttachmentsException(listOf(attachmentHash)) - attachment.open().readBytes() + attachment.open().readFully() }) { override val id = attachmentHash } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt index 8aa8b16e27..37905fcd15 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt @@ -39,7 +39,6 @@ import net.corda.nodeapi.internal.serialization.serializationContextKey import org.slf4j.Logger import org.slf4j.LoggerFactory import rx.Observable -import java.io.ByteArrayInputStream import java.io.InputStream import java.lang.reflect.InvocationTargetException import java.security.PrivateKey @@ -222,7 +221,7 @@ object InputStreamSerializer : Serializer() { System.arraycopy(chunk, 0, flattened, offset, chunk.size) offset += chunk.size } - return ByteArrayInputStream(flattened) + return flattened.inputStream() } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt index 7f3f156170..c638fff0f3 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoStreams.kt @@ -76,5 +76,5 @@ internal fun Output.substitute(transform: (OutputStream) -> OutputStream) { } internal fun Input.substitute(transform: (InputStream) -> InputStream) { - inputStream = transform(SequenceInputStream(ByteArrayInputStream(buffer.copyOfRange(position(), limit())), inputStream)) + inputStream = transform(SequenceInputStream(buffer.copyOfRange(position(), limit()).inputStream(), inputStream)) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt index 1143b65409..fa9f3b1ea2 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt @@ -12,7 +12,8 @@ package net.corda.nodeapi.internal import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever -import net.corda.core.contracts.* +import net.corda.core.contracts.Attachment +import net.corda.core.contracts.Contract import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.internal.declaredField @@ -32,9 +33,9 @@ import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DUMMY_NOTARY_NAME import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.TestIdentity +import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.internal.kryoSpecific import net.corda.testing.internal.rigorousMock -import net.corda.testing.internal.MockCordappConfigProvider import net.corda.testing.services.MockAttachmentStorage import org.apache.commons.io.IOUtils import org.junit.Assert.* @@ -137,8 +138,8 @@ class AttachmentsClassLoaderTests { fun `test overlapping file exception`() { val storage = attachments val att0 = attachmentId - val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some data"))) - val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file.txt", "some other data"))) + val att1 = storage.importAttachment(fakeAttachment("file.txt", "some data").inputStream()) + val att2 = storage.importAttachment(fakeAttachment("file.txt", "some other data").inputStream()) assertFailsWith(AttachmentsClassLoader.OverlappingAttachments::class) { AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }) @@ -149,8 +150,8 @@ class AttachmentsClassLoaderTests { fun `basic`() { val storage = attachments val att0 = attachmentId - val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data"))) - val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data"))) + val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream()) + val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream()) val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }) val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name()) @@ -161,8 +162,8 @@ class AttachmentsClassLoaderTests { fun `Check platform independent path handling in attachment jars`() { val storage = MockAttachmentStorage() - val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("/folder1/foldera/file1.txt", "some data"))) - val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data"))) + val att1 = storage.importAttachment(fakeAttachment("/folder1/foldera/file1.txt", "some data").inputStream()) + val att2 = storage.importAttachment(fakeAttachment("\\folder1\\folderb\\file2.txt", "some other data").inputStream()) val data1a = readAttachment(storage.openAttachment(att1)!!, "/folder1/foldera/file1.txt") assertArrayEquals("some data".toByteArray(), data1a) @@ -181,8 +182,8 @@ class AttachmentsClassLoaderTests { fun `loading class AnotherDummyContract`() { val storage = attachments val att0 = attachmentId - val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data"))) - val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data"))) + val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream()) + val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream()) val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader) val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, cl) @@ -205,8 +206,8 @@ class AttachmentsClassLoaderTests { val bytes = contract.serialize() val storage = attachments val att0 = attachmentId - val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data"))) - val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data"))) + val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream()) + val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream()) val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader) @@ -231,8 +232,8 @@ class AttachmentsClassLoaderTests { val bytes = data.serialize(context = context2) val storage = attachments val att0 = attachmentId - val att1 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file1.txt", "some data"))) - val att2 = storage.importAttachment(ByteArrayInputStream(fakeAttachment("file2.txt", "some other data"))) + val att1 = storage.importAttachment(fakeAttachment("file1.txt", "some data").inputStream()) + val att2 = storage.importAttachment(fakeAttachment("file2.txt", "some other data").inputStream()) val cl = AttachmentsClassLoader(arrayOf(att0, att1, att2).map { storage.openAttachment(it)!! }, FilteringClassLoader) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 9a77c73f1c..45e3c4bd92 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -30,9 +30,7 @@ import net.corda.testing.core.BOB_NAME import net.corda.testing.core.TestIdentity import net.corda.testing.internal.createDevIntermediateCaCertPath import org.assertj.core.api.Assertions.assertThat -import org.bouncycastle.asn1.x509.BasicConstraints -import org.bouncycastle.asn1.x509.Extension -import org.bouncycastle.asn1.x509.KeyUsage +import org.bouncycastle.asn1.x509.* import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -113,6 +111,28 @@ class X509UtilitiesTest { } } + @Test + fun `create valid server certificate chain includes CRL info`() { + val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val caCert = X509Utilities.createSelfSignedCACertificate(X500Principal("CN=Test CA Cert,O=R3 Ltd,L=London,C=GB"), caKey) + val caSubjectKeyIdentifier = SubjectKeyIdentifier.getInstance(caCert.toBc().getExtension(Extension.subjectKeyIdentifier).parsedValue) + val keyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val crlDistPoint = "http://test.com" + val serverCert = X509Utilities.createCertificate( + CertificateType.TLS, + caCert, + caKey, + X500Principal("CN=Server Cert,O=R3 Ltd,L=London,C=GB"), + keyPair.public, + crlDistPoint = crlDistPoint) + serverCert.toBc().run { + val certCrlDistPoint = CRLDistPoint.getInstance(getExtension(Extension.cRLDistributionPoints).parsedValue) + assertTrue(certCrlDistPoint.distributionPoints.first().distributionPoint.toString().contains(crlDistPoint)) + val certCaAuthorityKeyIdentifier = AuthorityKeyIdentifier.getInstance(getExtension(Extension.authorityKeyIdentifier).parsedValue) + assertTrue(Arrays.equals(caSubjectKeyIdentifier.keyIdentifier, certCaAuthorityKeyIdentifier.keyIdentifier)) + } + } + @Test fun `storing EdDSA key in java keystore`() { val tmpKeyStore = tempFile("keystore.jks") diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index e024898eb7..9ee0997213 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -53,7 +53,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.junit.runners.Parameterized.Parameters -import java.io.ByteArrayInputStream import java.io.IOException import java.io.NotSerializableException import java.math.BigDecimal @@ -1093,9 +1092,9 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi val factory2 = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) factory2.register(net.corda.nodeapi.internal.serialization.amqp.custom.InputStreamSerializer) val bytes = ByteArray(10) { it.toByte() } - val obj = ByteArrayInputStream(bytes) + val obj = bytes.inputStream() val obj2 = serdes(obj, factory, factory2, expectedEqual = false, expectDeserializedEqual = false) - val obj3 = ByteArrayInputStream(bytes) // Can't use original since the stream pointer has moved. + val obj3 = bytes.inputStream() // Can't use original since the stream pointer has moved. assertEquals(obj3.available(), obj2.available()) assertEquals(obj3.read(), obj2.read()) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt index 2fe6f4f341..a294b15695 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/kryo/KryoTests.kt @@ -39,7 +39,6 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.junit.runners.Parameterized.Parameters import org.slf4j.LoggerFactory -import java.io.ByteArrayInputStream import java.io.InputStream import java.time.Instant import java.util.* @@ -200,7 +199,11 @@ class KryoTests(private val compression: CordaSerializationEncoding?) { @Test fun `HashCheckingStream (de)serialize`() { val rubbish = ByteArray(12345, { (it * it * 0.12345).toByte() }) - val readRubbishStream: InputStream = NodeAttachmentService.HashCheckingStream(SecureHash.sha256(rubbish), rubbish.size, ByteArrayInputStream(rubbish)).serialize(factory, context).deserialize(factory, context) + val readRubbishStream: InputStream = NodeAttachmentService.HashCheckingStream( + SecureHash.sha256(rubbish), + rubbish.size, + rubbish.inputStream() + ).serialize(factory, context).deserialize(factory, context) for (i in 0..12344) { assertEquals(rubbish[i], readRubbishStream.read().toByte()) } diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index ec1b278e4a..3bce4dac71 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -13,6 +13,7 @@ package net.corda.node.utilities.registration import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.logElapsedTime +import net.corda.core.internal.readFully import net.corda.core.messaging.startFlow import net.corda.core.utilities.* import net.corda.finance.DOLLARS @@ -143,7 +144,7 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) @Produces(MediaType.TEXT_PLAIN) fun registration(input: InputStream): Response { return log.logElapsedTime("Registration") { - val certificationRequest = input.use { JcaPKCS10CertificationRequest(it.readBytes()) } + val certificationRequest = JcaPKCS10CertificationRequest(input.readFully()) val (certPath, name) = createSignedClientCertificate( certificationRequest, rootCertAndKeyPair.keyPair, diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 9dbd7e4e32..4010de4a7a 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -15,6 +15,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.toStringShort import net.corda.core.identity.* import net.corda.core.internal.CertRole +import net.corda.core.internal.hash import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.MAX_HASH_HEX_SIZE @@ -72,7 +73,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, ) } - private fun mapToKey(owningKey: PublicKey) = SecureHash.sha256(owningKey.encoded) + private fun mapToKey(owningKey: PublicKey) = owningKey.hash private fun mapToKey(party: PartyAndCertificate) = mapToKey(party.owningKey) } diff --git a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt index 1a18f7863c..b4f2540f70 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt @@ -50,6 +50,7 @@ fun freshCertificate(identityService: IdentityService, val ourCertificate = X509Utilities.createCertificate( CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuerCert.subjectX500Principal, + issuerCert.publicKey, issuerSigner, issuer.name.x500Principal, subjectPublicKey, diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index 7655333936..e6dfcee280 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -21,9 +21,11 @@ import net.corda.core.contracts.Attachment import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractClassName import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.sha256 import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.UNKNOWN_UPLOADER import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.readFully import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.vault.AttachmentQueryCriteria @@ -37,7 +39,10 @@ import net.corda.node.utilities.NonInvalidatingWeightBasedCache import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import net.corda.nodeapi.internal.persistence.currentDBSession import net.corda.nodeapi.internal.withContractsInJar -import java.io.* +import java.io.FilterInputStream +import java.io.IOException +import java.io.InputStream +import java.io.Serializable import java.nio.file.Paths import java.time.Instant import java.util.* @@ -272,14 +277,6 @@ class NodeAttachmentService( return import(jar, uploader, filename) } - fun getAttachmentIdAndBytes(jar: InputStream): Pair { - val hs = HashingInputStream(Hashing.sha256(), jar) - val bytes = hs.readBytes() - checkIsAValidJAR(ByteArrayInputStream(bytes)) - val id = SecureHash.SHA256(hs.hash().asBytes()) - return Pair(id, bytes) - } - override fun hasAttachment(attachmentId: AttachmentId): Boolean = currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null @@ -288,15 +285,16 @@ class NodeAttachmentService( return withContractsInJar(jar) { contractClassNames, inputStream -> require(inputStream !is JarInputStream) - // Read the file into RAM, hashing it to find the ID as we go. The attachment must fit into memory. + // Read the file into RAM and then calculate its hash. The attachment must fit into memory. // TODO: Switch to a two-phase insert so we can handle attachments larger than RAM. // To do this we must pipe stream into the database without knowing its hash, which we will learn only once // the insert/upload is complete. We can then query to see if it's a duplicate and if so, erase, and if not // set the hash field of the new attachment record. - val (id, bytes) = getAttachmentIdAndBytes(inputStream) + val bytes = inputStream.readFully() + val id = bytes.sha256() if (!hasAttachment(id)) { - checkIsAValidJAR(ByteArrayInputStream(bytes)) + checkIsAValidJAR(bytes.inputStream()) val session = currentDBSession() val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename, contractClassNames = contractClassNames) session.save(attachment) diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 514cdc85b8..a2d41c3e64 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -45,24 +45,26 @@ import net.corda.finance.contracts.asset.Cash import net.corda.finance.flows.TwoPartyTradeFlow.Buyer import net.corda.finance.flows.TwoPartyTradeFlow.Seller import net.corda.node.internal.StartedNode -import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.api.IdentityServiceInternal +import net.corda.node.services.api.WritableTransactionStorage import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.checkpoints import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.testing.core.* -import net.corda.testing.internal.LogHelper import net.corda.testing.dsl.LedgerDSL import net.corda.testing.dsl.TestLedgerDSLInterpreter import net.corda.testing.dsl.TestTransactionDSLInterpreter +import net.corda.testing.internal.LogHelper import net.corda.testing.internal.TEST_TX_TIME import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.vault.VaultFiller -import net.corda.testing.node.* +import net.corda.testing.node.InMemoryMessagingNetwork +import net.corda.testing.node.MockServices import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNodeParameters import net.corda.testing.node.internal.pumpReceive import net.corda.testing.node.internal.startFlow +import net.corda.testing.node.ledger import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -70,7 +72,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import rx.Observable -import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.util.* import java.util.jar.JarOutputStream @@ -357,7 +358,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { it.closeEntry() } val attachmentID = aliceNode.database.transaction { - attachment(ByteArrayInputStream(stream.toByteArray())) + attachment(stream.toByteArray().inputStream()) } val bobsFakeCash = bobNode.database.transaction { @@ -461,7 +462,7 @@ class TwoPartyTradeFlowTests(private val anonymous: Boolean) { it.closeEntry() } val attachmentID = aliceNode.database.transaction { - attachment(ByteArrayInputStream(stream.toByteArray())) + attachment(stream.toByteArray().inputStream()) } val bobsKey = bobNode.services.keyManagementService.keys.single() diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt index 4ea34e57cc..1f2c3dc38d 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt @@ -15,10 +15,7 @@ import com.google.common.jimfs.Configuration import com.google.common.jimfs.Jimfs import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 -import net.corda.core.internal.read -import net.corda.core.internal.readAll -import net.corda.core.internal.write -import net.corda.core.internal.writeLines +import net.corda.core.internal.* import net.corda.core.node.services.vault.AttachmentQueryCriteria import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.node.services.vault.Builder @@ -240,7 +237,7 @@ class NodeAttachmentStorageTest { database.transaction { val storage = NodeAttachmentService(MetricRegistry()) val e = assertFailsWith { - storage.openAttachment(id)!!.open().use { it.readBytes() } + storage.openAttachment(id)!!.open().readFully() } assertEquals(e.expected, id) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index 0ce31f08a7..0b093937cc 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -12,14 +12,14 @@ package net.corda.testing.node.internal.network import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData +import net.corda.core.internal.readObject +import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo -import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair -import net.corda.core.node.NetworkParameters import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.ParametersUpdate import org.eclipse.jetty.server.Server @@ -120,7 +120,7 @@ class NetworkMapServer(private val pollInterval: Duration, @Consumes(MediaType.APPLICATION_OCTET_STREAM) fun publishNodeInfo(input: InputStream): Response { return try { - val signedNodeInfo = input.readBytes().deserialize() + val signedNodeInfo = input.readObject() signedNodeInfo.verified() nodeInfoMap[signedNodeInfo.raw.hash] = signedNodeInfo ok() @@ -136,7 +136,7 @@ class NetworkMapServer(private val pollInterval: Duration, @Path("ack-parameters") @Consumes(MediaType.APPLICATION_OCTET_STREAM) fun ackNetworkParameters(input: InputStream): Response { - val signedParametersHash = input.readBytes().deserialize>() + val signedParametersHash = input.readObject>() val hash = signedParametersHash.verified() latestAcceptedParametersMap[signedParametersHash.sig.by] = hash return ok().build() diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt index bda464f3c6..a24cb439ff 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt @@ -17,13 +17,13 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.UNKNOWN_UPLOADER +import net.corda.core.internal.readFully import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.vault.AttachmentQueryCriteria import net.corda.core.node.services.vault.AttachmentSort import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.nodeapi.internal.withContractsInJar -import java.io.ByteArrayOutputStream import java.io.InputStream import java.util.* import java.util.jar.JarInputStream @@ -63,7 +63,7 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { fun importContractAttachment(contractClassNames: List, uploader: String, jar: InputStream): AttachmentId = importAttachmentInternal(jar, uploader, null, contractClassNames) - fun getAttachmentIdAndBytes(jar: InputStream): Pair = jar.readBytes().let { bytes -> Pair(bytes.sha256(), bytes) } + fun getAttachmentIdAndBytes(jar: InputStream): Pair = jar.readFully().let { bytes -> Pair(bytes.sha256(), bytes) } private class MockAttachment(dataLoader: () -> ByteArray, override val id: SecureHash) : AbstractAttachment(dataLoader) @@ -71,7 +71,7 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { // JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here. require(jar !is JarInputStream) - val bytes = jar.readBytes() + val bytes = jar.readFully() val sha256 = bytes.sha256() if (sha256 !in files.keys) {