mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
ENT-1074 - Proof-of-concept ISV for SGX remote attestation (#161)
* Initial WIP. * Configure IAS host via system properties. * Create separate Gretty configurations for testing and for IAS. * (WIP) Separate configuration values from WAR; Add msg3 -> msg4 handling. * Check the IAS report's cryptographic signature. * Accept CertPath from IAS instead of a Certificate. * Validate the certificate chain for the IAS report. * Refactor response handling, and add a secret to Message4. * Append public DH keys to generated shared secret. * Use DH secret to generate a 256 bit AES key. * Fix runISV Gradle task so that it creates WAR file. * Migrate MockIAS service into a separate package. * Remove unused aesCMAC field from Message3. * Configure HTTP sessions to expire after 10 idle minutes. * Ensure we select the "isv" key for MTLS with Intel Attestation Service. * Set key alias for Intel's public certificate. * Implement GET /attest/provision endpoint. * Use elliptic curves for Diffie-Hellman keys. * Pass public keys as Little Endian byte arrays without ASN.1 encoding. * Add AES-CMAC signature to Message2. * Remove signature fields from QUOTE body for sending to IAS. * Add a dummy AES-CMAC field to Message3 for later validation. * Generate AEC-CMAC for Message 3, and refactor crypto functionality. * Calculate AES-CMAC using AES/CBC/PKCS5Padding algorithm. * Use BouncyCastle's AESCMAC algorithm for MAC calculation. * Include standard crypto test vectors to the unit tests. * Encrypt MSG3 secret using AES/GCM/NoPadding with 128 bit key. * Hash shared key with Little Endian versions of public keys. * Refactor so that hexToBytes() is a utility. * Simplify signing of MocKIAS report. * Separate AES/GCM authentication tag from the encrypted data. * Create /ias/report endpoint for ISV which proxies IAS. * Remove unnecessary @Throws from MockIAS handlers. * Log HTTP error status from IAS. * Replace runISV task with startISV and stopISV tasks. * Refactor tests to use CryptoProvider @Rule instead of @Suite. * Move Web server for integration tests to use non-production ports. * Add proxy endpoint for IAS revocation list. * Generate an ECDSA "service key" for signing (gb|ga). * Generate a persistent key-pair for the ISV to sign with. * Verify the (Gb|Ga) signature from Message2. * Add debugging aids. * Fix Gradle warning. * Remove TLV header from Platform Info Body for MSG4. * Small tidy-up. * Use SPID "as-is" when calculating CMAC for MSG2. * Add DEBUG messages for MSG2's KDK and SMK values (AES-CMAC). * Add DEBUG logging for ECDH shared secret. * More DEBUG logging. * The ECDH shared secret *is* the x-coordinate: no need to subrange. * Adjust MockIAS to return an empty revocationList for GID 0000000b. * Fix ArrayOutOfBoundsException for "small" integer values. * Test MSG1 with empty revocation list. * Add extra logging for IAS report request. * ReportResponse object cannot be null. * Fix misreading of spec - don't remove quote's signature when requesting report from IAS. * Log invalid contents of X-IAS-Report-Signing-Certificate HTTP header. * Build CertPath for IAS from explicit list of Certificates. * Rename quote fields on IAS ReportResponse to match Intel. * Log report ID and quote status from IAS. * Add a revocation list checker to the certificate path validator. * Tweak revocation list options, depending on IAS vs MockIAS. * Extract Intel's certificate specifically by alias for PKIX. * Tune quote body returned by MockIAS. * Add AES-CMAC field to Message4 for validation. * Increase GCM authentication tag to 128 bits. * Receive platformInfoBlob from IAS as hexadecimal string. * Generate secret encryption key using KDK and SK values. * Marshall platformInfoBlob between Base16 string and ByteArray. * Interpret status results from IAS as enums. * Use lateinit for HttpServletRequest field. * Refactor ExceptionHandler out of messages package. * Alias is for ISV, so rename it. * Refactor classes into more correct packages. * Use random 96 bit IV for GCM encryption. * Parameterise HTTP/HTTPS ports via Gradle. * Do not forward a securityManifest containing only zeros to IAS. * Address review comments. * Review comment: Use NativePRNGNonBlocking for SecureRandom. * Rename isv.pfx to isv-svc.pfx * Rename keystore to isv.pfx, for clarity. * Update scripts so that they no longer require user input. * Generate isv.pfx from the key and certificates. * Remove private key from repository. * Declare an empty PSE Manifest to be invalid. * Generate keystores "on the fly". * Rename integration tests to end in "IT" instead of "Test". * Add README * Turn remote-attestation into a separate Gradle project.
This commit is contained in:
parent
1fc200efa7
commit
2725f53ef5
@ -56,4 +56,3 @@ project(':hsm-tool').with {
|
|||||||
projectDir = file("$settingsDir/sgx-jvm/hsm-tool")
|
projectDir = file("$settingsDir/sgx-jvm/hsm-tool")
|
||||||
}
|
}
|
||||||
include 'perftestcordapp'
|
include 'perftestcordapp'
|
||||||
|
|
||||||
|
42
sgx-jvm/remote-attestation/attestation-server/README.md
Normal file
42
sgx-jvm/remote-attestation/attestation-server/README.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
Remote Attestation ISV: Proof-Of-Concept
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
This initial version of the ISV expects to communicate with the [Attestation Host](../host), which should run on hardware
|
||||||
|
with a SGX enclave. The ISV also communicates with the Intel Attestation Service (IAS) over HTTPS with mutual TLS, which
|
||||||
|
requires it to contain our development private key. (We have already shared this key's public key with Intel, and IAS
|
||||||
|
expects to use it to authenticate us.)
|
||||||
|
|
||||||
|
Please install this private key in PEM formt as `src/main/ssl/intel-ssl/client.key`.
|
||||||
|
|
||||||
|
This ISV runs as a WAR file within Tomcat8, and implements the message flow as described in Intel's [end-to-end example](https://software.intel.com/en-us/articles/intel-software-guard-extensions-remote-attestation-end-to-end-example)
|
||||||
|
using JSON and HTTP. The use of HTTP here is mere convenience for our proof-of-concept; we anticipate using
|
||||||
|
something completely different when we integrate with Corda.
|
||||||
|
|
||||||
|
Gradle/Tomcat integration is achieved using the [Gretty plugin](https://github.com/akhikhl/gretty).
|
||||||
|
|
||||||
|
You will need OpenSSL installed so that Gradle can generate the keystores and truststores required by HTTPS and Mutual TLS.
|
||||||
|
|
||||||
|
## Building the ISV
|
||||||
|
|
||||||
|
From this project directory, execute the command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ../gradlew build integrationTest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the ISV
|
||||||
|
|
||||||
|
To launch the ISV as a daemon process listening on TCP/8080, execute:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ nohup ../gradlew startISV &
|
||||||
|
```
|
||||||
|
|
||||||
|
The ISV can then be shutdown using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ../gradlew stopISV
|
||||||
|
```
|
||||||
|
|
||||||
|
It will log messages to `build/logs/attestation-server.log`.
|
||||||
|
|
173
sgx-jvm/remote-attestation/attestation-server/build.gradle
Normal file
173
sgx-jvm/remote-attestation/attestation-server/build.gradle
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'org.akhikhl.gretty:gretty:2.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.keyStoreDir = "$buildDir/keystore"
|
||||||
|
ext.httpsKeyStoreDir = "$buildDir/https-keystore"
|
||||||
|
|
||||||
|
// Port numbers to launch the different components on.
|
||||||
|
ext.isvHttpPort = 8080
|
||||||
|
ext.isvTestHttpPort = 9080
|
||||||
|
ext.iasTestHttpsPort = 9443
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'war'
|
||||||
|
apply plugin: 'org.akhikhl.gretty'
|
||||||
|
|
||||||
|
description 'Server side of SGX remote attestation process'
|
||||||
|
|
||||||
|
import org.akhikhl.gretty.AppStartTask
|
||||||
|
import org.akhikhl.gretty.AppStopTask
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
integrationTestCompile.extendsFrom testCompile
|
||||||
|
integrationTestRuntime.extendsFrom testRuntime
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
integrationTest {
|
||||||
|
kotlin {
|
||||||
|
compileClasspath += main.compileClasspath + test.compileClasspath
|
||||||
|
runtimeClasspath += main.runtimeClasspath + test.runtimeClasspath
|
||||||
|
//noinspection GroovyAssignabilityCheck
|
||||||
|
srcDir file('src/integration-test/kotlin')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
|
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
|
testCompile "junit:junit:$junit_version"
|
||||||
|
|
||||||
|
compile "org.bouncycastle:bcpkix-jdk15on:$bouncycastle_version"
|
||||||
|
compile "org.jboss.resteasy:resteasy-jaxrs:$resteasy_version"
|
||||||
|
compile "org.jboss.resteasy:resteasy-jackson2-provider:$resteasy_version"
|
||||||
|
compile "org.jboss.resteasy:resteasy-servlet-initializer:$resteasy_version"
|
||||||
|
compile "com.fasterxml.jackson.core:jackson-core:$jackson_version"
|
||||||
|
compile "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
|
||||||
|
compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
|
||||||
|
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version"
|
||||||
|
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||||
|
compile "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||||
|
runtime "org.apache.logging.log4j:log4j-web:$log4j_version"
|
||||||
|
compile "org.slf4j:jcl-over-slf4j:$slf4j_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(Test) {
|
||||||
|
// Enable "unlimited" encryption.
|
||||||
|
systemProperties["java.security.properties"] = "$projectDir/src/integration-test/security.properties"
|
||||||
|
|
||||||
|
// Set logging directory for all tests.
|
||||||
|
systemProperties["attestation.home"] = "$buildDir/logs"
|
||||||
|
}
|
||||||
|
|
||||||
|
task intelKeyStores(type: Exec) {
|
||||||
|
doFirst {
|
||||||
|
mkdir keyStoreDir
|
||||||
|
}
|
||||||
|
inputs.dir "$projectDir/src/main/ssl/intel-ssl"
|
||||||
|
outputs.dir keyStoreDir
|
||||||
|
workingDir keyStoreDir
|
||||||
|
commandLine "$projectDir/src/main/ssl/intel-ssl/generate-keystores.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
task serviceKeyStore(type: Exec) {
|
||||||
|
doFirst {
|
||||||
|
mkdir keyStoreDir
|
||||||
|
}
|
||||||
|
inputs.dir "$projectDir/src/main/ssl/service-key"
|
||||||
|
outputs.dir keyStoreDir
|
||||||
|
workingDir keyStoreDir
|
||||||
|
commandLine "$projectDir/src/main/ssl/service-key/generate-keystore.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
dependsOn = [ intelKeyStores, serviceKeyStore ]
|
||||||
|
from keyStoreDir
|
||||||
|
}
|
||||||
|
|
||||||
|
task integrationTest(type: Test) {
|
||||||
|
testClassesDirs = sourceSets.integrationTest.output.classesDirs
|
||||||
|
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||||
|
systemProperties["javax.net.ssl.keyStore"] = "$httpsKeyStoreDir/keystore"
|
||||||
|
systemProperties["javax.net.ssl.keyStorePassword"] = "attestation"
|
||||||
|
systemProperties["test.isv.httpPort"] = isvTestHttpPort
|
||||||
|
}
|
||||||
|
|
||||||
|
task httpsKeyStores(type: Exec) {
|
||||||
|
doFirst {
|
||||||
|
mkdir httpsKeyStoreDir
|
||||||
|
}
|
||||||
|
inputs.dir "$projectDir/src/integration-test/ssl"
|
||||||
|
outputs.dir httpsKeyStoreDir
|
||||||
|
workingDir httpsKeyStoreDir
|
||||||
|
commandLine "$projectDir/src/integration-test/ssl/generate-ssl.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
project.afterEvaluate {
|
||||||
|
appBeforeIntegrationTest.dependsOn httpsKeyStores
|
||||||
|
}
|
||||||
|
|
||||||
|
gretty {
|
||||||
|
httpPort = isvTestHttpPort
|
||||||
|
contextPath = "/"
|
||||||
|
servletContainer = 'tomcat8'
|
||||||
|
logDir = "$buildDir/logs"
|
||||||
|
logFileName = "gretty-test"
|
||||||
|
integrationTestTask = 'integrationTest'
|
||||||
|
jvmArgs = [
|
||||||
|
"-Dorg.jboss.logging.provider=slf4j",
|
||||||
|
"-Djava.security.properties=$projectDir/src/integration-test/security.properties",
|
||||||
|
"-Djavax.net.ssl.keyStore=$httpsKeyStoreDir/keystore",
|
||||||
|
"-Djavax.net.ssl.keyStorePassword=attestation",
|
||||||
|
"-Djavax.net.ssl.trustStore=$httpsKeyStoreDir/truststore",
|
||||||
|
"-Djavax.net.ssl.trustStorePassword=attestation",
|
||||||
|
"-Dattestation.home=$buildDir/logs",
|
||||||
|
"-Dias.host=localhost:$iasTestHttpsPort",
|
||||||
|
]
|
||||||
|
|
||||||
|
httpsPort = iasTestHttpsPort
|
||||||
|
httpsEnabled = true
|
||||||
|
sslNeedClientAuth = true
|
||||||
|
sslKeyStorePath = "$httpsKeyStoreDir/keystore"
|
||||||
|
sslKeyStorePassword = 'attestation'
|
||||||
|
sslKeyManagerPassword = 'attestation'
|
||||||
|
}
|
||||||
|
|
||||||
|
task('startISV', type: AppStartTask, dependsOn: war) {
|
||||||
|
prepareServerConfig {
|
||||||
|
httpPort = isvHttpPort
|
||||||
|
servletContainer = 'tomcat8'
|
||||||
|
logDir = "$buildDir/logs"
|
||||||
|
logFileName = "gretty-isv"
|
||||||
|
jvmArgs = [
|
||||||
|
"-Dorg.jboss.logging.provider=slf4j",
|
||||||
|
"-Djava.security.properties=$projectDir/src/main/security.properties",
|
||||||
|
"-Djavax.net.ssl.keyStore=$keyStoreDir/isv.pfx",
|
||||||
|
"-Djavax.net.ssl.keyStorePassword=attestation",
|
||||||
|
"-Dias.host=test-as.sgx.trustedservices.intel.com",
|
||||||
|
"-Dattestation.home=$buildDir/logs",
|
||||||
|
]
|
||||||
|
|
||||||
|
httpsEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareWebAppConfig {
|
||||||
|
contextPath = "/"
|
||||||
|
inplace = false
|
||||||
|
}
|
||||||
|
|
||||||
|
interactive = false
|
||||||
|
}
|
||||||
|
|
||||||
|
task("stopISV", type: AppStopTask)
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.Description
|
||||||
|
import org.junit.runners.model.Statement
|
||||||
|
import java.security.Security
|
||||||
|
|
||||||
|
class CryptoProvider : TestRule {
|
||||||
|
override fun apply(statement: Statement, description: Description?): Statement {
|
||||||
|
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||||
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
}
|
||||||
|
return statement
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import net.corda.attestation.message.ChallengeResponse
|
||||||
|
import org.apache.http.HttpStatus.*
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpGet
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class GetProvisioningIT {
|
||||||
|
private val httpPort = Integer.getInteger("test.isv.httpPort")
|
||||||
|
private lateinit var httpClient: CloseableHttpClient
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val cryptoProvider = CryptoProvider()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
httpClient = HttpClients.custom()
|
||||||
|
.setConnectionManager(BasicHttpClientConnectionManager().apply { socketConfig = httpSocketConfig })
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun done() {
|
||||||
|
httpClient.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getProvisioning() {
|
||||||
|
val request = HttpGet("http://localhost:$httpPort/attest/provision")
|
||||||
|
val response = httpClient.execute(request).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity)
|
||||||
|
assertEquals(output, SC_OK, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
val challenge = mapper.readValue(response, ChallengeResponse::class.java)
|
||||||
|
assertTrue(challenge.nonce.isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.apache.http.HttpStatus.SC_OK
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpGet
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.ssl.SSLContextBuilder
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Ignore
|
||||||
|
import org.junit.Test
|
||||||
|
import java.net.URI
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import javax.ws.rs.core.UriBuilder
|
||||||
|
|
||||||
|
@Ignore("This class exists only to probe IAS, and is not really a test at all.")
|
||||||
|
class IASIT {
|
||||||
|
private val iasHost: URI = URI.create("https://test-as.sgx.trustedservices.intel.com")
|
||||||
|
private val storePassword = (System.getProperty("javax.net.ssl.keyStorePassword") ?: "").toCharArray()
|
||||||
|
private val random = SecureRandom()
|
||||||
|
|
||||||
|
private lateinit var keyStore: KeyStore
|
||||||
|
|
||||||
|
private val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
private val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private fun createHttpClient(): CloseableHttpClient {
|
||||||
|
val sslContext = SSLContextBuilder()
|
||||||
|
.loadKeyMaterial(keyStore, storePassword, { _, _ -> "isv" })
|
||||||
|
.setSecureRandom(random)
|
||||||
|
.build()
|
||||||
|
return HttpClients.custom()
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.setDefaultSocketConfig(httpSocketConfig)
|
||||||
|
.setSSLContext(sslContext)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadKeyStoreResource(resourceName: String, password: CharArray, type: String = "PKCS12"): KeyStore {
|
||||||
|
return KeyStore.getInstance(type).apply {
|
||||||
|
RemoteAttestation::class.java.classLoader.getResourceAsStream(resourceName)?.use { input ->
|
||||||
|
load(input, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
keyStore = loadKeyStoreResource("isv.pfx", storePassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun huntGID() {
|
||||||
|
createHttpClient().use { httpClient ->
|
||||||
|
for (i in 1000..1999) {
|
||||||
|
val requestURI = UriBuilder.fromUri(iasHost)
|
||||||
|
.path("attestation/sgx/v2/sigrl/{gid}")
|
||||||
|
.build(String.format("%16x", i))
|
||||||
|
val request = HttpGet(requestURI)
|
||||||
|
httpClient.execute(request).use { response ->
|
||||||
|
if (response.statusLine.statusCode == SC_OK) {
|
||||||
|
println("FOUND: $i")
|
||||||
|
} else {
|
||||||
|
println("NO: $i -> ${response.statusLine.statusCode}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.Description
|
||||||
|
import org.junit.runners.model.Statement
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.PrivateKey
|
||||||
|
|
||||||
|
class KeyStoreProvider(private val storeName: String, private val storePassword: String) : TestRule {
|
||||||
|
private lateinit var keyStore: KeyStore
|
||||||
|
|
||||||
|
private fun loadKeyStoreResource(resourceName: String, password: CharArray, type: String = "PKCS12"): KeyStore {
|
||||||
|
return KeyStore.getInstance(type).apply {
|
||||||
|
KeyStoreProvider::class.java.classLoader.getResourceAsStream(resourceName)?.use { input ->
|
||||||
|
load(input, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(statement: Statement, description: Description?): Statement {
|
||||||
|
keyStore = loadKeyStoreResource(storeName, storePassword.toCharArray())
|
||||||
|
return statement
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getKeyPair(alias: String, password: String): KeyPair {
|
||||||
|
val privateKey = keyStore.getKey(alias, password.toCharArray()) as PrivateKey
|
||||||
|
return KeyPair(keyStore.getCertificate(alias).publicKey, privateKey)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import net.corda.attestation.message.Message0
|
||||||
|
import org.apache.http.HttpStatus.SC_OK
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.entity.ContentType.*
|
||||||
|
import org.apache.http.entity.StringEntity
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class PostMessage0IT {
|
||||||
|
private val httpPort = Integer.getInteger("test.isv.httpPort")
|
||||||
|
private lateinit var httpClient: CloseableHttpClient
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val cryptoProvider = CryptoProvider()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
httpClient = HttpClients.custom()
|
||||||
|
.setConnectionManager(BasicHttpClientConnectionManager().apply { socketConfig = httpSocketConfig })
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun done() {
|
||||||
|
httpClient.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun postMsg0() {
|
||||||
|
val request = HttpPost("http://localhost:$httpPort/attest/msg0")
|
||||||
|
val msg0 = Message0(extendedGID = 0)
|
||||||
|
request.entity = StringEntity(mapper.writeValueAsString(msg0), APPLICATION_JSON)
|
||||||
|
httpClient.execute(request).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity)
|
||||||
|
assertEquals(output, SC_OK, response.statusLine.statusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import net.corda.attestation.message.Message1
|
||||||
|
import net.corda.attestation.message.Message2
|
||||||
|
import org.apache.http.HttpStatus.*
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.entity.ContentType.*
|
||||||
|
import org.apache.http.entity.StringEntity
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.bouncycastle.asn1.ASN1EncodableVector
|
||||||
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
|
import org.bouncycastle.asn1.ASN1OutputStream
|
||||||
|
import org.bouncycastle.asn1.DLSequence
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
|
import java.security.KeyFactory
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.Signature
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
|
||||||
|
class PostMessage1IT {
|
||||||
|
private companion object {
|
||||||
|
private const val KEY_PASSWORD = "attestation"
|
||||||
|
private const val SERVICE_KEYSTORE = "isv-svc.pfx"
|
||||||
|
private val httpPort = Integer.getInteger("test.isv.httpPort")
|
||||||
|
}
|
||||||
|
private lateinit var httpClient: CloseableHttpClient
|
||||||
|
private lateinit var serviceKeyPair: KeyPair
|
||||||
|
private lateinit var keyPair: KeyPair
|
||||||
|
private lateinit var ecPublicKey: ECPublicKey
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
private lateinit var crypto: Crypto
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val cryptoProvider = CryptoProvider()
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val keyStoreProvider = KeyStoreProvider(SERVICE_KEYSTORE, KEY_PASSWORD)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
serviceKeyPair = keyStoreProvider.getKeyPair("isv-svc", KEY_PASSWORD)
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
crypto = Crypto()
|
||||||
|
keyPair = crypto.generateKeyPair()
|
||||||
|
ecPublicKey = keyPair.public as ECPublicKey
|
||||||
|
val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
httpClient = HttpClients.custom()
|
||||||
|
.setConnectionManager(BasicHttpClientConnectionManager().apply { socketConfig = httpSocketConfig })
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun done() {
|
||||||
|
httpClient.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun postMsg1() {
|
||||||
|
val ecParameters = ecPublicKey.params
|
||||||
|
val request = HttpPost("http://localhost:$httpPort/attest/msg1")
|
||||||
|
val msg1 = Message1(ga = ecPublicKey.toLittleEndian(), platformGID = "00000000")
|
||||||
|
request.entity = StringEntity(mapper.writeValueAsString(msg1), APPLICATION_JSON)
|
||||||
|
val response = httpClient.execute(request).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_OK, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
val msg2 = mapper.readValue(response, Message2::class.java)
|
||||||
|
assertEquals("84D402C36BA9EF9B0A86EF1A9CC8CE4F", msg2.spid.toUpperCase())
|
||||||
|
assertEquals(KEY_SIZE, msg2.signatureGbGa.size)
|
||||||
|
assertEquals(80, msg2.revocationList.size)
|
||||||
|
|
||||||
|
KeyFactory.getInstance("EC").generatePublic(msg2.gb.toBigEndianKeySpec(ecParameters))
|
||||||
|
|
||||||
|
val asn1Signature = ByteArrayOutputStream().let { baos ->
|
||||||
|
ASN1OutputStream(baos).apply {
|
||||||
|
writeObject(DLSequence(ASN1EncodableVector().apply {
|
||||||
|
add(ASN1Integer(msg2.signatureGbGa.copyOf(KEY_SIZE / 2).reversedArray().toPositiveInteger()))
|
||||||
|
add(ASN1Integer(msg2.signatureGbGa.copyOfRange(KEY_SIZE / 2, KEY_SIZE).reversedArray().toPositiveInteger()))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
baos.toByteArray()
|
||||||
|
}
|
||||||
|
val verified = Signature.getInstance("SHA256WithECDSA").let { verifier ->
|
||||||
|
verifier.initVerify(serviceKeyPair.public)
|
||||||
|
verifier.update(msg2.gb)
|
||||||
|
verifier.update(msg1.ga)
|
||||||
|
verifier.verify(asn1Signature)
|
||||||
|
}
|
||||||
|
assertTrue(verified)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEmptyRevocationList() {
|
||||||
|
val request = HttpPost("http://localhost:$httpPort/attest/msg1")
|
||||||
|
val msg1 = Message1(ga = ecPublicKey.toLittleEndian(), platformGID = "0000000b")
|
||||||
|
request.entity = StringEntity(mapper.writeValueAsString(msg1), APPLICATION_JSON)
|
||||||
|
val response = httpClient.execute(request).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_OK, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
val msg2 = mapper.readValue(response, Message2::class.java)
|
||||||
|
assertEquals(0, msg2.revocationList.size)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
import net.corda.attestation.message.*
|
||||||
|
import org.apache.http.HttpStatus.*
|
||||||
|
import org.apache.http.client.CookieStore
|
||||||
|
import org.apache.http.client.config.CookieSpecs.STANDARD_STRICT
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
import org.apache.http.client.protocol.HttpClientContext
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.entity.ContentType.*
|
||||||
|
import org.apache.http.entity.StringEntity
|
||||||
|
import org.apache.http.impl.client.BasicCookieStore
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.charset.StandardCharsets.*
|
||||||
|
import java.security.*
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
import java.security.spec.ECParameterSpec
|
||||||
|
|
||||||
|
class PostMessage3IT {
|
||||||
|
private val httpPort = Integer.getInteger("test.isv.httpPort")
|
||||||
|
private lateinit var httpClient: CloseableHttpClient
|
||||||
|
private lateinit var cookies: CookieStore
|
||||||
|
private lateinit var keyPair: KeyPair
|
||||||
|
private lateinit var ecParameters: ECParameterSpec
|
||||||
|
private lateinit var msg2: Message2
|
||||||
|
private lateinit var peerPublicKey: ECPublicKey
|
||||||
|
private lateinit var smk: ByteArray
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
private lateinit var crypto: Crypto
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val cryptoProvider = CryptoProvider()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
crypto = Crypto()
|
||||||
|
cookies = BasicCookieStore()
|
||||||
|
keyPair = crypto.generateKeyPair()
|
||||||
|
ecParameters = (keyPair.public as ECPublicKey).params
|
||||||
|
mapper = ObjectMapper().registerModule(JavaTimeModule())
|
||||||
|
|
||||||
|
val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setCookieSpec(STANDARD_STRICT)
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
httpClient = HttpClients.custom()
|
||||||
|
.setConnectionManager(BasicHttpClientConnectionManager().apply { socketConfig = httpSocketConfig })
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val context = HttpClientContext.create().apply {
|
||||||
|
cookieStore = cookies
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = HttpPost("http://localhost:$httpPort/attest/msg1")
|
||||||
|
val msg1 = Message1(ga = (keyPair.public as ECPublicKey).toLittleEndian(), platformGID = "00000000")
|
||||||
|
request.entity = StringEntity(mapper.writeValueAsString(msg1), APPLICATION_JSON)
|
||||||
|
val response = httpClient.execute(request, context).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_OK, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
msg2 = mapper.readValue(response, Message2::class.java)
|
||||||
|
|
||||||
|
val keyFactory = KeyFactory.getInstance("EC")
|
||||||
|
peerPublicKey = keyFactory.generatePublic(msg2.gb.toBigEndianKeySpec(ecParameters)) as ECPublicKey
|
||||||
|
smk = crypto.generateSMK(keyPair.private, peerPublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun done() {
|
||||||
|
httpClient.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun postMsg3() {
|
||||||
|
val context = HttpClientContext.create().apply {
|
||||||
|
cookieStore = cookies
|
||||||
|
}
|
||||||
|
|
||||||
|
val request3 = HttpPost("http://localhost:$httpPort/attest/msg3")
|
||||||
|
val ga = (keyPair.public as ECPublicKey).toLittleEndian()
|
||||||
|
val quote = byteArrayOf(0x02, 0x04, 0x08, 0x10)
|
||||||
|
val msg3 = Message3(
|
||||||
|
aesCMAC = crypto.aesCMAC(smk, { aes ->
|
||||||
|
aes.update(ga)
|
||||||
|
aes.update(quote)
|
||||||
|
}),
|
||||||
|
ga = ga,
|
||||||
|
quote = quote
|
||||||
|
)
|
||||||
|
request3.entity = StringEntity(mapper.writeValueAsString(msg3), APPLICATION_JSON)
|
||||||
|
val response3 = httpClient.execute(request3, context).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_OK, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
val msg4 = mapper.readValue(response3, Message4::class.java)
|
||||||
|
assertEquals("OK", msg4.quoteStatus)
|
||||||
|
assertArrayEquals(quote, msg4.quoteBody)
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x11, 0x22), msg4.platformInfo)
|
||||||
|
assertNull(msg4.securityManifestStatus)
|
||||||
|
|
||||||
|
val cmac = crypto.aesCMAC(crypto.generateMK(keyPair.private, peerPublicKey), { aes ->
|
||||||
|
aes.update(msg4.platformInfo ?: byteArrayOf())
|
||||||
|
})
|
||||||
|
assertArrayEquals(cmac, msg4.aesCMAC)
|
||||||
|
|
||||||
|
val secretKey = crypto.generateSecretKey(keyPair.private, peerPublicKey)
|
||||||
|
val secret = String(crypto.decrypt(msg4.secret.plus(msg4.secretHash), secretKey, msg4.secretIV), UTF_8)
|
||||||
|
assertEquals("And now for something completely different!", secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testHugeNonce() {
|
||||||
|
val context = HttpClientContext.create().apply {
|
||||||
|
cookieStore = cookies
|
||||||
|
}
|
||||||
|
|
||||||
|
val request3 = HttpPost("http://localhost:$httpPort/attest/msg3")
|
||||||
|
val ga = (keyPair.public as ECPublicKey).toLittleEndian()
|
||||||
|
val quote = byteArrayOf(0x02, 0x04, 0x08, 0x10)
|
||||||
|
val msg3 = Message3(
|
||||||
|
aesCMAC = crypto.aesCMAC(smk, { aes ->
|
||||||
|
aes.update(ga)
|
||||||
|
aes.update(quote)
|
||||||
|
}),
|
||||||
|
ga = ga,
|
||||||
|
quote = quote,
|
||||||
|
nonce = "1234567890123456789012345678901234"
|
||||||
|
)
|
||||||
|
request3.entity = StringEntity(mapper.writeValueAsString(msg3), APPLICATION_JSON)
|
||||||
|
val response3 = httpClient.execute(request3, context).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_BAD_REQUEST, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
val error = mapper.readValue(response3, AttestationError::class.java)
|
||||||
|
assertEquals("Nonce is too large: maximum 32 digits", error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unsignedByteArrayOf(vararg values: Int) = ByteArray(values.size).apply {
|
||||||
|
for (i in 0 until values.size) {
|
||||||
|
this[i] = values[i].toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
import net.corda.attestation.message.AttestationError
|
||||||
|
import net.corda.attestation.message.Message3
|
||||||
|
import org.apache.http.HttpHeaders.CONTENT_TYPE
|
||||||
|
import org.apache.http.HttpStatus.*
|
||||||
|
import org.apache.http.client.CookieStore
|
||||||
|
import org.apache.http.client.config.CookieSpecs.STANDARD_STRICT
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
import org.apache.http.client.protocol.HttpClientContext
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.entity.ContentType.*
|
||||||
|
import org.apache.http.entity.StringEntity
|
||||||
|
import org.apache.http.impl.client.BasicCookieStore
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
||||||
|
import org.apache.http.message.BasicHeader
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
|
||||||
|
class PostMessage3OnlyIT {
|
||||||
|
private val httpPort = Integer.getInteger("test.isv.httpPort")
|
||||||
|
private lateinit var httpClient: CloseableHttpClient
|
||||||
|
private lateinit var cookies: CookieStore
|
||||||
|
private lateinit var keyPair: KeyPair
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
private lateinit var crypto: Crypto
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val cryptoProvider = CryptoProvider()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper().registerModule(JavaTimeModule())
|
||||||
|
crypto = Crypto()
|
||||||
|
cookies = BasicCookieStore()
|
||||||
|
keyPair = crypto.generateKeyPair()
|
||||||
|
|
||||||
|
val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setCookieSpec(STANDARD_STRICT)
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
httpClient = HttpClients.custom()
|
||||||
|
.setConnectionManager(BasicHttpClientConnectionManager().apply { socketConfig = httpSocketConfig })
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun done() {
|
||||||
|
httpClient.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test msg3 without payload`() {
|
||||||
|
val context = HttpClientContext.create().apply {
|
||||||
|
cookieStore = cookies
|
||||||
|
}
|
||||||
|
|
||||||
|
val request3 = HttpPost("http://localhost:$httpPort/attest/msg3")
|
||||||
|
request3.addHeader(BasicHeader(CONTENT_TYPE, APPLICATION_JSON.mimeType))
|
||||||
|
val response3 = httpClient.execute(request3, context).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_BAD_REQUEST, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
val error = mapper.readValue(response3, AttestationError::class.java)
|
||||||
|
assertEquals("Message is missing", error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test Msg3 requires Msg1 first`() {
|
||||||
|
val context = HttpClientContext.create().apply {
|
||||||
|
cookieStore = cookies
|
||||||
|
}
|
||||||
|
|
||||||
|
val request3 = HttpPost("http://localhost:$httpPort/attest/msg3")
|
||||||
|
val msg3 = Message3(
|
||||||
|
aesCMAC = byteArrayOf(),
|
||||||
|
ga = (keyPair.public as ECPublicKey).toLittleEndian(),
|
||||||
|
securityManifest = byteArrayOf(),
|
||||||
|
quote = byteArrayOf()
|
||||||
|
)
|
||||||
|
request3.entity = StringEntity(mapper.writeValueAsString(msg3), APPLICATION_JSON)
|
||||||
|
val response3 = httpClient.execute(request3, context).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_UNAUTHORIZED, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
val error = mapper.readValue(response3, AttestationError::class.java)
|
||||||
|
assertEquals("Secret key has not been calculated yet", error.message)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
import net.corda.attestation.message.ias.*
|
||||||
|
import org.apache.http.HttpStatus.*
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.entity.ContentType
|
||||||
|
import org.apache.http.entity.StringEntity
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.charset.StandardCharsets.*
|
||||||
|
|
||||||
|
class ReportProxyIT {
|
||||||
|
private val httpPort = Integer.getInteger("test.isv.httpPort")
|
||||||
|
private lateinit var httpClient: CloseableHttpClient
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val cryptoProvider = CryptoProvider()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper().registerModule(JavaTimeModule())
|
||||||
|
val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
httpClient = HttpClients.custom()
|
||||||
|
.setConnectionManager(BasicHttpClientConnectionManager().apply { socketConfig = httpSocketConfig })
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun done() {
|
||||||
|
httpClient.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIASReportWithManifest() {
|
||||||
|
val request = HttpPost("http://localhost:$httpPort/ias/report")
|
||||||
|
val quote = byteArrayOf(0x02, 0x04, 0x08, 0x10)
|
||||||
|
val requestMessage = ReportRequest(
|
||||||
|
isvEnclaveQuote = quote,
|
||||||
|
pseManifest = byteArrayOf(0x73, 0x42),
|
||||||
|
nonce = "0000000000000000"
|
||||||
|
)
|
||||||
|
request.entity = StringEntity(mapper.writeValueAsString(requestMessage), ContentType.APPLICATION_JSON)
|
||||||
|
val response = httpClient.execute(request).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_OK, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseMessage = mapper.readValue(response, ReportProxyResponse::class.java)
|
||||||
|
assertTrue(responseMessage.signature.isNotEmpty())
|
||||||
|
assertTrue(responseMessage.certificatePath.isNotEmpty())
|
||||||
|
|
||||||
|
val iasReport = mapper.readValue(responseMessage.report.inputStream(), ReportResponse::class.java)
|
||||||
|
assertEquals(QuoteStatus.OK, iasReport.isvEnclaveQuoteStatus)
|
||||||
|
assertEquals("0000000000000000", iasReport.nonce)
|
||||||
|
assertEquals(ManifestStatus.OK, iasReport.pseManifestStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIASReportWithoutManifest() {
|
||||||
|
val request = HttpPost("http://localhost:$httpPort/ias/report")
|
||||||
|
val quote = byteArrayOf(0x02, 0x04, 0x08, 0x10)
|
||||||
|
val requestMessage = ReportRequest(
|
||||||
|
isvEnclaveQuote = quote,
|
||||||
|
nonce = "0000000000000000"
|
||||||
|
)
|
||||||
|
request.entity = StringEntity(mapper.writeValueAsString(requestMessage), ContentType.APPLICATION_JSON)
|
||||||
|
val response = httpClient.execute(request).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_OK, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseMessage = mapper.readValue(response, ReportProxyResponse::class.java)
|
||||||
|
assertTrue(responseMessage.signature.isNotEmpty())
|
||||||
|
assertTrue(responseMessage.certificatePath.isNotEmpty())
|
||||||
|
|
||||||
|
val iasReport = mapper.readValue(responseMessage.report.inputStream(), ReportResponse::class.java)
|
||||||
|
assertEquals(QuoteStatus.OK, iasReport.isvEnclaveQuoteStatus)
|
||||||
|
assertEquals("0000000000000000", iasReport.nonce)
|
||||||
|
assertNull(iasReport.pseManifestStatus)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import net.corda.attestation.message.ias.RevocationListProxyResponse
|
||||||
|
import org.apache.http.HttpStatus.*
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpGet
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.charset.StandardCharsets.*
|
||||||
|
|
||||||
|
class RevocationListProxyIT {
|
||||||
|
private val httpPort = Integer.getInteger("test.isv.httpPort")
|
||||||
|
private lateinit var httpClient: CloseableHttpClient
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
httpClient = HttpClients.custom()
|
||||||
|
.setConnectionManager(BasicHttpClientConnectionManager().apply { socketConfig = httpSocketConfig })
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun done() {
|
||||||
|
httpClient.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIASRevocationList() {
|
||||||
|
val request = HttpGet("http://localhost:$httpPort/ias/sigrl/0000000000000000")
|
||||||
|
val response = httpClient.execute(request).use { response ->
|
||||||
|
val output = EntityUtils.toString(response.entity, UTF_8)
|
||||||
|
assertEquals(output, SC_OK, response.statusLine.statusCode)
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseMessage = mapper.readValue(response, RevocationListProxyResponse::class.java)
|
||||||
|
assertEquals("84D402C36BA9EF9B0A86EF1A9CC8CE4F", responseMessage.spid)
|
||||||
|
assertTrue(responseMessage.revocationList.isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
crypto.policy=unlimited
|
||||||
|
|
@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
ALIAS=jetty
|
||||||
|
KEYPASS=attestation
|
||||||
|
STOREPASS=attestation
|
||||||
|
|
||||||
|
rm -f keystore truststore
|
||||||
|
|
||||||
|
# Generate the keystore and truststore that will allow us to enable HTTPS.
|
||||||
|
# Both keystore and truststore are expected to use password "attestation".
|
||||||
|
|
||||||
|
keytool -keystore keystore -storetype pkcs12 -genkey -alias ${ALIAS} -dname CN=localhost -keyalg RSA -keypass ${KEYPASS} -storepass ${STOREPASS}
|
||||||
|
keytool -keystore keystore -storetype pkcs12 -export -alias ${ALIAS} -keyalg RSA -file jetty.cert -keypass ${KEYPASS} -storepass ${STOREPASS}
|
||||||
|
keytool -keystore truststore -storetype pkcs12 -import -alias ${ALIAS} -file jetty.cert -keypass ${KEYPASS} -storepass ${STOREPASS} <<EOF
|
||||||
|
yes
|
||||||
|
EOF
|
||||||
|
|
||||||
|
rm -f jetty.cert
|
@ -0,0 +1,119 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.nio.charset.StandardCharsets.*
|
||||||
|
import java.security.*
|
||||||
|
import java.security.spec.ECGenParameterSpec
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.Cipher.*
|
||||||
|
import javax.crypto.KeyAgreement
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
class Crypto(val random: SecureRandom = SecureRandom.getInstance("NativePRNGNonBlocking")) {
|
||||||
|
internal companion object {
|
||||||
|
private const val AES_ALGORITHM = "AES/GCM/NoPadding"
|
||||||
|
private const val macBlockSize = 16
|
||||||
|
private const val gcmIvLength = 12
|
||||||
|
const val gcmTagLength = 16
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private val log = LoggerFactory.getLogger(Crypto::class.java)
|
||||||
|
@JvmStatic
|
||||||
|
private val smkValue = byteArrayOf(
|
||||||
|
0x01,
|
||||||
|
'S'.toAscii(),
|
||||||
|
'M'.toAscii(),
|
||||||
|
'K'.toAscii(),
|
||||||
|
0x00,
|
||||||
|
0x80.toByte(),
|
||||||
|
0x00
|
||||||
|
)
|
||||||
|
@JvmStatic
|
||||||
|
private val mkValue = byteArrayOf(
|
||||||
|
0x01,
|
||||||
|
'M'.toAscii(),
|
||||||
|
'K'.toAscii(),
|
||||||
|
0x00,
|
||||||
|
0x80.toByte(),
|
||||||
|
0x00
|
||||||
|
)
|
||||||
|
@JvmStatic
|
||||||
|
private val skValue = byteArrayOf(
|
||||||
|
0x01,
|
||||||
|
'S'.toAscii(),
|
||||||
|
'K'.toAscii(),
|
||||||
|
0x00,
|
||||||
|
0x80.toByte(),
|
||||||
|
0x00
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun Char.toAscii() = toString().toByteArray(US_ASCII)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
private val keyPairGenerator: KeyPairGenerator = KeyPairGenerator.getInstance("EC")
|
||||||
|
|
||||||
|
init {
|
||||||
|
keyPairGenerator.initialize(ECGenParameterSpec("secp256r1"), random)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateKeyPair(): KeyPair = keyPairGenerator.generateKeyPair()
|
||||||
|
|
||||||
|
fun aesCMAC(key: ByteArray = ByteArray(macBlockSize), value: ByteArray): ByteArray = aesCMAC(key, { aes -> aes.update(value) })
|
||||||
|
|
||||||
|
fun aesCMAC(key: ByteArray, update: (aes: Mac) -> Unit): ByteArray = Mac.getInstance("AESCMAC").let { aes ->
|
||||||
|
aes.init(SecretKeySpec(key, "AES"))
|
||||||
|
update(aes)
|
||||||
|
aes.doFinal()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createGCMParameters(iv: ByteArray) = GCMParameterSpec(gcmTagLength * 8, iv)
|
||||||
|
|
||||||
|
fun createIV(): ByteArray = ByteArray(gcmIvLength).apply { random.nextBytes(this) }
|
||||||
|
|
||||||
|
fun encrypt(data: ByteArray, secretKey: SecretKey, secretIV: ByteArray): ByteArray = Cipher.getInstance(AES_ALGORITHM).let { cip ->
|
||||||
|
cip.init(ENCRYPT_MODE, secretKey, createGCMParameters(secretIV), random)
|
||||||
|
cip.doFinal(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNUSED")
|
||||||
|
fun decrypt(data: ByteArray, secretKey: SecretKey, secretIV: ByteArray): ByteArray = Cipher.getInstance(AES_ALGORITHM).let { cip ->
|
||||||
|
cip.init(DECRYPT_MODE, secretKey, createGCMParameters(secretIV))
|
||||||
|
cip.doFinal(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateSharedSecret(privateKey: PrivateKey, peerPublicKey: PublicKey): ByteArray {
|
||||||
|
return KeyAgreement.getInstance("ECDH").let { ka ->
|
||||||
|
ka.init(privateKey, random)
|
||||||
|
ka.doPhase(peerPublicKey, true)
|
||||||
|
ka.generateSecret()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateKDK(sharedSecret: ByteArray)
|
||||||
|
= aesCMAC(ByteArray(macBlockSize), sharedSecret.reversedArray()).apply { log.debug("KDK: {}", toHexArrayString()) }
|
||||||
|
|
||||||
|
private fun generateSMK(sharedSecret: ByteArray)
|
||||||
|
= aesCMAC(generateKDK(sharedSecret), smkValue).apply { log.debug("SMK: {}", toHexArrayString()) }
|
||||||
|
|
||||||
|
fun generateSMK(privateKey: PrivateKey, peerPublicKey: PublicKey): ByteArray
|
||||||
|
= generateSMK(generateSharedSecret(privateKey, peerPublicKey))
|
||||||
|
|
||||||
|
private fun generateMK(sharedSecret: ByteArray)
|
||||||
|
= aesCMAC(generateKDK(sharedSecret), mkValue).apply { log.debug("MK: {}", toHexArrayString()) }
|
||||||
|
|
||||||
|
fun generateMK(privateKey: PrivateKey, peerPublicKey: PublicKey): ByteArray
|
||||||
|
= generateMK(generateSharedSecret(privateKey, peerPublicKey))
|
||||||
|
|
||||||
|
private fun generateSK(sharedSecret: ByteArray)
|
||||||
|
= aesCMAC(generateKDK(sharedSecret), skValue).apply { log.debug("SK: {}", toHexArrayString()) }
|
||||||
|
|
||||||
|
fun generateSecretKey(privateKey: PrivateKey, peerPublicKey: PublicKey): SecretKey
|
||||||
|
= SecretKeySpec(generateSK(generateSharedSecret(privateKey, peerPublicKey)), "AES")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.authenticationTag(): ByteArray = copyOfRange(size - Crypto.gcmTagLength, size)
|
||||||
|
fun ByteArray.encryptedData(): ByteArray = copyOf(size - Crypto.gcmTagLength)
|
||||||
|
fun ByteArray.toHexArrayString(): String = joinToString(prefix="[", separator=",", postfix="]", transform={ b -> String.format("0x%02x", b) })
|
@ -0,0 +1,62 @@
|
|||||||
|
@file:JvmName("EndianUtils")
|
||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder.LITTLE_ENDIAN
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
import java.security.spec.ECParameterSpec
|
||||||
|
import java.security.spec.ECPoint
|
||||||
|
import java.security.spec.ECPublicKeySpec
|
||||||
|
import java.security.spec.KeySpec
|
||||||
|
|
||||||
|
const val KEY_SIZE = 64
|
||||||
|
|
||||||
|
fun ByteArray.removeLeadingZeros(): ByteArray {
|
||||||
|
if (isEmpty() || this[0] != 0.toByte()) { return this }
|
||||||
|
for (i in 1 until size) {
|
||||||
|
if (this[i] != 0.toByte()) {
|
||||||
|
return copyOfRange(i, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return byteArrayOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toPositiveInteger() = BigInteger(1, this)
|
||||||
|
fun ByteArray.toHexString(): String = toPositiveInteger().toString(16)
|
||||||
|
|
||||||
|
fun BigInteger.toLittleEndian(size: Int = KEY_SIZE) = ByteArray(size).apply {
|
||||||
|
val leBytes = toByteArray().reversedArray()
|
||||||
|
System.arraycopy(leBytes, 0, this, 0, Math.min(size, leBytes.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BigInteger.toUnsignedBytes(): ByteArray = toByteArray().removeLeadingZeros()
|
||||||
|
|
||||||
|
fun String.hexToBytes(): ByteArray = BigInteger(this, 16).toUnsignedBytes()
|
||||||
|
|
||||||
|
fun ByteArray.toBigEndianKeySpec(ecParameters: ECParameterSpec): KeySpec {
|
||||||
|
if (size != KEY_SIZE) {
|
||||||
|
throw IllegalArgumentException("Public key has incorrect size ($size bytes)")
|
||||||
|
}
|
||||||
|
val ecPoint = ECPoint(
|
||||||
|
copyOf(size / 2).reversedArray().toPositiveInteger(),
|
||||||
|
copyOfRange(size / 2, size).reversedArray().toPositiveInteger()
|
||||||
|
)
|
||||||
|
return ECPublicKeySpec(ecPoint, ecParameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ECPublicKey.toLittleEndian(size: Int = KEY_SIZE): ByteArray {
|
||||||
|
val x = w.affineX.toByteArray().reversedArray()
|
||||||
|
val y = w.affineY.toByteArray().reversedArray()
|
||||||
|
return ByteArray(size).apply {
|
||||||
|
// Automatically discards any extra "most significant" last byte, which is assumed to be zero.
|
||||||
|
val half = size / 2
|
||||||
|
System.arraycopy(x, 0, this, 0, Math.min(half, x.size))
|
||||||
|
System.arraycopy(y, 0, this, half, Math.min(half, y.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Short.toLittleEndian(): ByteArray = ByteBuffer.allocate(2)
|
||||||
|
.order(LITTLE_ENDIAN)
|
||||||
|
.putShort(this)
|
||||||
|
.array()
|
@ -0,0 +1,21 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import javax.ws.rs.WebApplicationException
|
||||||
|
import javax.ws.rs.core.Response
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper
|
||||||
|
import javax.ws.rs.ext.Provider
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
class ExceptionHandler : ExceptionMapper<WebApplicationException> {
|
||||||
|
private companion object {
|
||||||
|
@JvmStatic
|
||||||
|
private val log: Logger = LoggerFactory.getLogger(ExceptionHandler::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toResponse(e: WebApplicationException): Response {
|
||||||
|
log.error("HTTP Status: {}: {}", e.response.status, e.message)
|
||||||
|
return e.response
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,180 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
import net.corda.attestation.message.AttestationError
|
||||||
|
import net.corda.attestation.message.ias.ReportProxyResponse
|
||||||
|
import net.corda.attestation.message.ias.ReportRequest
|
||||||
|
import net.corda.attestation.message.ias.RevocationListProxyResponse
|
||||||
|
import org.apache.http.HttpResponse
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpGet
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
import org.apache.http.config.RegistryBuilder
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.conn.socket.ConnectionSocketFactory
|
||||||
|
import org.apache.http.conn.ssl.SSLConnectionSocketFactory
|
||||||
|
import org.apache.http.entity.ContentType
|
||||||
|
import org.apache.http.entity.StringEntity
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
||||||
|
import org.apache.http.ssl.SSLContextBuilder
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.util.*
|
||||||
|
import javax.net.ssl.SSLException
|
||||||
|
import javax.servlet.http.HttpServletResponse.*
|
||||||
|
import javax.ws.rs.*
|
||||||
|
import javax.ws.rs.core.MediaType
|
||||||
|
import javax.ws.rs.core.Response
|
||||||
|
import javax.ws.rs.core.UriBuilder
|
||||||
|
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Path("/ias")
|
||||||
|
class IASProxy {
|
||||||
|
private companion object {
|
||||||
|
@JvmStatic
|
||||||
|
private val log: Logger = LoggerFactory.getLogger(IASProxy::class.java)
|
||||||
|
@JvmStatic
|
||||||
|
private val SPID = "84D402C36BA9EF9B0A86EF1A9CC8CE4F"
|
||||||
|
|
||||||
|
private val mapper = ObjectMapper().registerModule(JavaTimeModule())
|
||||||
|
private val random = SecureRandom()
|
||||||
|
|
||||||
|
private val storePassword = (System.getProperty("javax.net.ssl.keyStorePassword") ?: "").toCharArray()
|
||||||
|
private val keyStore: KeyStore
|
||||||
|
private val iasHost: URI = URI.create("https://${System.getProperty("ias.host", "localhost:8443")}")
|
||||||
|
private val isDummy = iasHost.host == "localhost"
|
||||||
|
private val isvKeyAlias = if (isDummy) "jetty" else "isv"
|
||||||
|
|
||||||
|
private val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
private val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", "PKCS12")
|
||||||
|
keyStore = loadKeyStore("javax.net.ssl.keyStore", storePassword, keyStoreType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadKeyStore(propertyName: String, password: CharArray, type: String = "PKCS12"): KeyStore {
|
||||||
|
val fileName = System.getProperty(propertyName) ?: throw IllegalStateException("System property $propertyName not set")
|
||||||
|
return KeyStore.getInstance(type).apply {
|
||||||
|
FileInputStream(fileName).use { input -> this.load(input, password) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/sigrl/{gid}")
|
||||||
|
fun proxyRevocationList(@PathParam("gid") platformGID: String): Response {
|
||||||
|
val revocationList = try {
|
||||||
|
createHttpClient().use { client ->
|
||||||
|
val sigRlURI = UriBuilder.fromUri(iasHost)
|
||||||
|
.path("attestation/sgx/v2/sigrl/{gid}")
|
||||||
|
.build(platformGID)
|
||||||
|
val getSigRL = HttpGet(sigRlURI)
|
||||||
|
client.execute(getSigRL).use { response ->
|
||||||
|
if (response.statusLine.statusCode != SC_OK) {
|
||||||
|
return response.toResponse("Error from Intel Attestation Service (HTTP ${response.statusLine.statusCode})")
|
||||||
|
}
|
||||||
|
EntityUtils.toByteArray(response.entity).decodeBase64()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: SSLException) {
|
||||||
|
log.error("HTTPS error: ${e.message}")
|
||||||
|
return responseOf("HTTPS connection failed: ${e.message}", SC_FORBIDDEN)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
log.error("HTTP client error", e)
|
||||||
|
return responseOf("HTTP client error: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok()
|
||||||
|
.entity(RevocationListProxyResponse(SPID, revocationList))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/report")
|
||||||
|
fun proxyReport(prq: ReportRequest?): Response {
|
||||||
|
val reportRequest = prq ?: return responseOf("Message is missing", SC_BAD_REQUEST)
|
||||||
|
|
||||||
|
val reportResponse = try {
|
||||||
|
createHttpClient().use { client ->
|
||||||
|
val reportURI = UriBuilder.fromUri(iasHost)
|
||||||
|
.path("attestation/sgx/v2/report")
|
||||||
|
.build()
|
||||||
|
val httpRequest = HttpPost(reportURI)
|
||||||
|
httpRequest.entity = StringEntity(mapper.writeValueAsString(reportRequest), ContentType.APPLICATION_JSON)
|
||||||
|
client.execute(httpRequest).use { httpResponse ->
|
||||||
|
if (httpResponse.statusLine.statusCode != SC_OK) {
|
||||||
|
return httpResponse.toResponse("Error from Intel Attestation Service")
|
||||||
|
}
|
||||||
|
ReportProxyResponse(
|
||||||
|
signature = httpResponse.requireHeader("X-IASReport-Signature"),
|
||||||
|
certificatePath = httpResponse.requireHeader("X-IASReport-Signing-Certificate").decodeURL(),
|
||||||
|
report = EntityUtils.toByteArray(httpResponse.entity)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: SSLException) {
|
||||||
|
log.error("HTTPS error: ${e.message}")
|
||||||
|
return responseOf("HTTPS connection failed: ${e.message}", SC_FORBIDDEN)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
log.error("HTTP client error", e)
|
||||||
|
return responseOf("HTTP client error: ${e.message}")
|
||||||
|
}
|
||||||
|
return Response.ok()
|
||||||
|
.entity(reportResponse)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun responseOf(message: String, statusCode: Int = SC_INTERNAL_SERVER_ERROR): Response = Response.status(statusCode)
|
||||||
|
.entity(AttestationError(message))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private fun HttpResponse.toResponse(message: String, statusCode: Int = statusLine.statusCode): Response {
|
||||||
|
return Response.status(statusCode)
|
||||||
|
.entity(AttestationError(message))
|
||||||
|
.apply {
|
||||||
|
val requestIdHeader = getFirstHeader("Request-ID") ?: return@apply
|
||||||
|
this.header(requestIdHeader.name, requestIdHeader.value)
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun HttpResponse.requireHeader(name: String): String
|
||||||
|
= (this.getFirstHeader(name) ?: throw ForbiddenException(toResponse("Response header '$name' missing", SC_FORBIDDEN))).value
|
||||||
|
|
||||||
|
private fun createHttpClient(): CloseableHttpClient {
|
||||||
|
val sslContext = SSLContextBuilder()
|
||||||
|
.loadKeyMaterial(keyStore, storePassword, { _, _ -> isvKeyAlias })
|
||||||
|
.setSecureRandom(random)
|
||||||
|
.build()
|
||||||
|
val registry = RegistryBuilder.create<ConnectionSocketFactory>()
|
||||||
|
.register("https", SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.getDefaultHostnameVerifier()))
|
||||||
|
.build()
|
||||||
|
return HttpClients.custom()
|
||||||
|
.setConnectionManager(BasicHttpClientConnectionManager(registry).apply {
|
||||||
|
socketConfig = httpSocketConfig
|
||||||
|
})
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.decodeBase64(): ByteArray = Base64.getDecoder().decode(this)
|
||||||
|
private fun String.decodeURL(): String = URLDecoder.decode(this, "UTF-8")
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
import javax.ws.rs.Consumes
|
||||||
|
import javax.ws.rs.Produces
|
||||||
|
import javax.ws.rs.ext.ContextResolver
|
||||||
|
import javax.ws.rs.ext.Provider
|
||||||
|
|
||||||
|
@Consumes("application/*+json", "text/json")
|
||||||
|
@Produces("application/*+json", "text/json")
|
||||||
|
@Provider
|
||||||
|
class JacksonConfig : ContextResolver<ObjectMapper> {
|
||||||
|
private val mapper = ObjectMapper().registerModule(JavaTimeModule())
|
||||||
|
override fun getContext(type: Class<*>?): ObjectMapper = mapper
|
||||||
|
}
|
@ -0,0 +1,449 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
import net.corda.attestation.message.*
|
||||||
|
import net.corda.attestation.message.ias.ReportRequest
|
||||||
|
import net.corda.attestation.message.ias.ReportResponse
|
||||||
|
import org.apache.http.HttpResponse
|
||||||
|
import org.apache.http.client.config.RequestConfig
|
||||||
|
import org.apache.http.client.methods.HttpGet
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
import org.apache.http.config.RegistryBuilder
|
||||||
|
import org.apache.http.config.SocketConfig
|
||||||
|
import org.apache.http.conn.socket.ConnectionSocketFactory
|
||||||
|
import org.apache.http.conn.ssl.SSLConnectionSocketFactory
|
||||||
|
import org.apache.http.conn.ssl.SSLConnectionSocketFactory.getDefaultHostnameVerifier
|
||||||
|
import org.apache.http.entity.ContentType
|
||||||
|
import org.apache.http.entity.StringEntity
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager
|
||||||
|
import org.apache.http.ssl.SSLContextBuilder
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.bouncycastle.asn1.ASN1InputStream
|
||||||
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
|
import org.bouncycastle.asn1.DLSequence
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.*
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
|
import java.security.*
|
||||||
|
import java.security.cert.*
|
||||||
|
import java.security.cert.Certificate
|
||||||
|
import java.security.cert.PKIXRevocationChecker.Option.*
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
import java.security.spec.*
|
||||||
|
import java.util.*
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
import javax.net.ssl.SSLException
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse.*
|
||||||
|
import javax.ws.rs.*
|
||||||
|
import javax.ws.rs.core.*
|
||||||
|
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Path("/attest")
|
||||||
|
class RemoteAttestation {
|
||||||
|
private companion object {
|
||||||
|
@JvmStatic
|
||||||
|
private val log: Logger = LoggerFactory.getLogger(RemoteAttestation::class.java)
|
||||||
|
@JvmStatic
|
||||||
|
private val SPID = "84D402C36BA9EF9B0A86EF1A9CC8CE4F".hexToBytes()
|
||||||
|
@JvmStatic
|
||||||
|
private val LINKABLE_QUOTE = byteArrayOf(0x01, 0x00)
|
||||||
|
|
||||||
|
private const val intelAES = 0
|
||||||
|
private const val maxNonceLength = 32
|
||||||
|
private const val tlvHeaderSize = 8
|
||||||
|
private const val AES_CMAC_FUNC = 1
|
||||||
|
private const val secretKeyAttrName = "Secret-Key"
|
||||||
|
private const val transientKeyPairAttrName = "Transient-Key-Pair"
|
||||||
|
private const val smkAttrName = "SMK"
|
||||||
|
|
||||||
|
private val mapper = ObjectMapper().registerModule(JavaTimeModule())
|
||||||
|
private val crypto = Crypto()
|
||||||
|
private val certificateFactory: CertificateFactory = CertificateFactory.getInstance("X.509")
|
||||||
|
private val keyFactory: KeyFactory = KeyFactory.getInstance("EC")
|
||||||
|
private val storePassword = (System.getProperty("javax.net.ssl.keyStorePassword") ?: "").toCharArray()
|
||||||
|
private val isvStore: KeyStore
|
||||||
|
private val iasStore: KeyStore
|
||||||
|
private val serviceKeyPair: KeyPair
|
||||||
|
private val pkixParameters: PKIXParameters
|
||||||
|
private val ecParameters: ECParameterSpec
|
||||||
|
|
||||||
|
private val iasHost: URI = URI.create("https://${System.getProperty("ias.host", "localhost:8443")}")
|
||||||
|
private val isDummy = iasHost.host == "localhost"
|
||||||
|
private val isvKeyAlias = if (isDummy) "jetty" else "isv"
|
||||||
|
|
||||||
|
private val httpRequestConfig: RequestConfig = RequestConfig.custom()
|
||||||
|
.setConnectTimeout(20_000)
|
||||||
|
.setSocketTimeout(5_000)
|
||||||
|
.build()
|
||||||
|
private val httpSocketConfig: SocketConfig = SocketConfig.custom()
|
||||||
|
.setSoReuseAddress(true)
|
||||||
|
.setTcpNoDelay(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
init {
|
||||||
|
ecParameters = (crypto.generateKeyPair().public as ECPublicKey).params
|
||||||
|
log.info("Elliptic Curve Parameters: {}", ecParameters)
|
||||||
|
|
||||||
|
val keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", "PKCS12")
|
||||||
|
isvStore = loadKeyStore("javax.net.ssl.keyStore", storePassword, keyStoreType)
|
||||||
|
serviceKeyPair = loadKeyStoreResource("isv-svc.pfx", storePassword).getKeyPair("isv-svc", storePassword)
|
||||||
|
|
||||||
|
val iasResourceName = if (isDummy) "dummyIAStrust.pfx" else "ias.pfx"
|
||||||
|
iasStore = loadKeyStoreResource(iasResourceName, storePassword)
|
||||||
|
|
||||||
|
val revocationListOptions = if (isDummy) EnumSet.of(SOFT_FAIL, PREFER_CRLS, NO_FALLBACK) else EnumSet.of(SOFT_FAIL)
|
||||||
|
pkixParameters = PKIXParameters(iasStore.trustAnchorsFor("ias")).apply {
|
||||||
|
val rlChecker = CertPathValidator.getInstance("PKIX").revocationChecker as PKIXRevocationChecker
|
||||||
|
addCertPathChecker(rlChecker.apply { options = revocationListOptions })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadKeyStore(propertyName: String, password: CharArray, type: String = "PKCS12"): KeyStore {
|
||||||
|
val fileName = System.getProperty(propertyName) ?: throw IllegalStateException("System property $propertyName not set")
|
||||||
|
return KeyStore.getInstance(type).apply {
|
||||||
|
FileInputStream(fileName).use { input -> this.load(input, password) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadKeyStoreResource(resourceName: String, password: CharArray, type: String = "PKCS12"): KeyStore {
|
||||||
|
return KeyStore.getInstance(type).apply {
|
||||||
|
RemoteAttestation::class.java.classLoader.getResourceAsStream(resourceName)?.use { input ->
|
||||||
|
load(input, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KeyStore.getKeyPair(alias: String, password: CharArray): KeyPair {
|
||||||
|
val privateKey = getKey(alias, password) as PrivateKey
|
||||||
|
return KeyPair(getCertificate(alias).publicKey, privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KeyStore.trustAnchorsFor(vararg aliases: String): Set<TrustAnchor>
|
||||||
|
= aliases.map { alias -> TrustAnchor(getCertificate(alias) as X509Certificate, null) }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
@field:Context
|
||||||
|
private lateinit var httpRequest: HttpServletRequest
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/provision")
|
||||||
|
fun provision(): Response {
|
||||||
|
log.info("Provisioning requested")
|
||||||
|
return Response.ok()
|
||||||
|
.entity(ChallengeResponse(createNonce(), (serviceKeyPair.public as ECPublicKey).toLittleEndian()))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/msg0")
|
||||||
|
fun receiveMsg0(m: Message0?): Response {
|
||||||
|
val msg0 = m ?: return responseOf("Message is missing", SC_BAD_REQUEST)
|
||||||
|
if (intelAES != msg0.extendedGID) {
|
||||||
|
return responseOf("Unsupported extended GID value", SC_BAD_REQUEST)
|
||||||
|
}
|
||||||
|
log.info("Message0 processed")
|
||||||
|
return Response.ok().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives Msg1 and returns Msg2.
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/msg1")
|
||||||
|
fun handleMsg1(m: Message1?): Response {
|
||||||
|
val msg1 = m ?: return responseOf("Message is missing", SC_BAD_REQUEST)
|
||||||
|
val session = httpRequest.session ?: return responseOf("No session in progress", SC_UNAUTHORIZED)
|
||||||
|
|
||||||
|
log.info("HTTP Session: {}", session.id)
|
||||||
|
|
||||||
|
val revocationList = try {
|
||||||
|
createHttpClient().use { client ->
|
||||||
|
val sigRlURI = UriBuilder.fromUri(iasHost)
|
||||||
|
.path("attestation/sgx/v2/sigrl/{gid}")
|
||||||
|
.build(msg1.platformGID)
|
||||||
|
val getSigRL = HttpGet(sigRlURI)
|
||||||
|
client.execute(getSigRL).use { response ->
|
||||||
|
if (response.statusLine.statusCode != SC_OK) {
|
||||||
|
return response.toResponse("Error from Intel Attestation Service (HTTP ${response.statusLine.statusCode})")
|
||||||
|
}
|
||||||
|
EntityUtils.toByteArray(response.entity).decodeBase64()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: SSLException) {
|
||||||
|
log.error("HTTPS error: ${e.message}")
|
||||||
|
return responseOf("HTTPS connection failed: ${e.message}", SC_FORBIDDEN)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
log.error("HTTP client error", e)
|
||||||
|
return responseOf("HTTP client error: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val transientKeyPair = crypto.generateKeyPair()
|
||||||
|
session.setAttribute(transientKeyPairAttrName, transientKeyPair)
|
||||||
|
|
||||||
|
val peerPublicKey = try {
|
||||||
|
keyFactory.generatePublic(msg1.ga.toBigEndianKeySpec(ecParameters)) as ECPublicKey
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return responseOf(e.message ?: "", SC_BAD_REQUEST)
|
||||||
|
}
|
||||||
|
session.setAttribute(secretKeyAttrName, crypto.generateSecretKey(transientKeyPair.private, peerPublicKey))
|
||||||
|
|
||||||
|
log.info("Message1 processed - returning Message2")
|
||||||
|
|
||||||
|
val smk = crypto.generateSMK(transientKeyPair.private, peerPublicKey)
|
||||||
|
session.setAttribute(smkAttrName, smk)
|
||||||
|
|
||||||
|
val publicKey = transientKeyPair.public as ECPublicKey
|
||||||
|
val signatureGbGa = signatureOf(publicKey, peerPublicKey)
|
||||||
|
val gb = publicKey.toLittleEndian()
|
||||||
|
val msg2 = Message2(
|
||||||
|
gb = gb,
|
||||||
|
spid = SPID.toHexString(),
|
||||||
|
keyDerivationFuncId = AES_CMAC_FUNC,
|
||||||
|
signatureGbGa = signatureGbGa,
|
||||||
|
aesCMAC = crypto.aesCMAC(smk, { aes ->
|
||||||
|
aes.update(gb)
|
||||||
|
aes.update(SPID)
|
||||||
|
aes.update(LINKABLE_QUOTE)
|
||||||
|
aes.update(AES_CMAC_FUNC.toShort().toLittleEndian())
|
||||||
|
aes.update(signatureGbGa)
|
||||||
|
}),
|
||||||
|
revocationList = revocationList
|
||||||
|
)
|
||||||
|
return Response.ok(msg2).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives Msg3 and return Msg4.
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("/msg3")
|
||||||
|
fun handleMsg3(m: Message3?): Response {
|
||||||
|
val msg3 = m ?: return responseOf("Message is missing", SC_BAD_REQUEST)
|
||||||
|
val session = httpRequest.session
|
||||||
|
?: return responseOf("No session in progress", SC_UNAUTHORIZED)
|
||||||
|
log.info("HTTP Session: {}", session.id)
|
||||||
|
|
||||||
|
val secretKey = session.getAttribute(secretKeyAttrName) as? SecretKey
|
||||||
|
?: return responseOf("Secret key has not been calculated yet", SC_UNAUTHORIZED)
|
||||||
|
val smk = session.getAttribute(smkAttrName) as? ByteArray
|
||||||
|
?: return responseOf("SMK value has not been calculated yet", SC_UNAUTHORIZED)
|
||||||
|
validateCMAC(msg3, smk)
|
||||||
|
|
||||||
|
val transientKeyPair = session.getAttribute(transientKeyPairAttrName) as? KeyPair
|
||||||
|
?: return responseOf("DH key unavailable", SC_UNAUTHORIZED)
|
||||||
|
val peerPublicKey = try {
|
||||||
|
keyFactory.generatePublic(msg3.ga.toBigEndianKeySpec(ecParameters))
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return responseOf(e.message ?: "", SC_BAD_REQUEST)
|
||||||
|
}
|
||||||
|
if (crypto.generateSecretKey(transientKeyPair.private, peerPublicKey) != secretKey) {
|
||||||
|
return responseOf("Keys do not match!", SC_FORBIDDEN)
|
||||||
|
}
|
||||||
|
validateNonce(msg3.nonce)
|
||||||
|
|
||||||
|
log.debug("Quote: {}", msg3.quote.toHexArrayString())
|
||||||
|
log.debug("Security manifest: {}", msg3.securityManifest?.toHexArrayString())
|
||||||
|
log.debug("Nonce: {}", msg3.nonce)
|
||||||
|
|
||||||
|
val report: ReportResponse = try {
|
||||||
|
createHttpClient().use { client ->
|
||||||
|
val reportURI = UriBuilder.fromUri(iasHost)
|
||||||
|
.path("attestation/sgx/v2/report")
|
||||||
|
.build()
|
||||||
|
val httpRequest = HttpPost(reportURI)
|
||||||
|
val reportRequest = ReportRequest(
|
||||||
|
isvEnclaveQuote = msg3.quote,
|
||||||
|
pseManifest = msg3.securityManifest?.ifNotZeros(),
|
||||||
|
nonce = msg3.nonce
|
||||||
|
)
|
||||||
|
httpRequest.entity = StringEntity(mapper.writeValueAsString(reportRequest), ContentType.APPLICATION_JSON)
|
||||||
|
client.execute(httpRequest).use { httpResponse ->
|
||||||
|
if (httpResponse.statusLine.statusCode != SC_OK) {
|
||||||
|
return httpResponse.toResponse("Error from Intel Attestation Service (HTTP ${httpResponse.statusLine.statusCode})")
|
||||||
|
}
|
||||||
|
mapper.readValue(validate(httpResponse), ReportResponse::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: SSLException) {
|
||||||
|
log.error("HTTPS error: ${e.message}")
|
||||||
|
return responseOf("HTTPS connection failed: ${e.message}", SC_FORBIDDEN)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
log.error("HTTP client error", e)
|
||||||
|
return responseOf("HTTP client error: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Report ID: {}", report.id)
|
||||||
|
log.info("Quote Status: {}", report.isvEnclaveQuoteStatus)
|
||||||
|
log.info("Message3 processed - returning Message4")
|
||||||
|
|
||||||
|
val secretIV = crypto.createIV()
|
||||||
|
val secretData = crypto.encrypt("And now for something completely different!".toByteArray(), secretKey, secretIV)
|
||||||
|
val platformInfo = report.platformInfoBlob?.removeHeader(tlvHeaderSize) ?: byteArrayOf()
|
||||||
|
val mk = crypto.generateMK(transientKeyPair.private, peerPublicKey)
|
||||||
|
val msg4 = Message4(
|
||||||
|
reportID = report.id,
|
||||||
|
quoteStatus = report.isvEnclaveQuoteStatus.toString(),
|
||||||
|
quoteBody = report.isvEnclaveQuoteBody,
|
||||||
|
aesCMAC = crypto.aesCMAC(mk, { aes ->
|
||||||
|
aes.update(platformInfo)
|
||||||
|
}),
|
||||||
|
securityManifestStatus = report.pseManifestStatus?.toString(),
|
||||||
|
securityManifestHash = report.pseManifestHash,
|
||||||
|
platformInfo = platformInfo,
|
||||||
|
epidPseudonym = report.epidPseudonym,
|
||||||
|
nonce = report.nonce,
|
||||||
|
secret = secretData.encryptedData(),
|
||||||
|
secretHash = secretData.authenticationTag(),
|
||||||
|
secretIV = secretIV,
|
||||||
|
timestamp = report.timestamp
|
||||||
|
)
|
||||||
|
return Response.ok(msg4)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNonce(): String = UUID.randomUUID().let { uuid ->
|
||||||
|
String.format("%016x%016x", uuid.mostSignificantBits, uuid.leastSignificantBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateNonce(n: String?) {
|
||||||
|
val nonce = n ?: return
|
||||||
|
if (nonce.length > maxNonceLength) {
|
||||||
|
throw BadRequestException(responseOf("Nonce is too large: maximum $maxNonceLength digits", SC_BAD_REQUEST))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateCMAC(msg3: Message3, smk: ByteArray) {
|
||||||
|
val cmac = crypto.aesCMAC(smk, { aes ->
|
||||||
|
aes.update(msg3.ga)
|
||||||
|
aes.update(msg3.securityManifest ?: byteArrayOf())
|
||||||
|
aes.update(msg3.quote)
|
||||||
|
})
|
||||||
|
if (!cmac.contentEquals(msg3.aesCMAC)) {
|
||||||
|
throw BadRequestException(responseOf("Incorrect CMAC value", SC_BAD_REQUEST))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validate(response: HttpResponse): String {
|
||||||
|
return EntityUtils.toByteArray(response.entity).let { payload ->
|
||||||
|
val iasSignature = response.requireHeader("X-IASReport-Signature")
|
||||||
|
val iasSigningCertificate = response.requireHeader("X-IASReport-Signing-Certificate").decodeURL()
|
||||||
|
|
||||||
|
val certificatePath = try {
|
||||||
|
parseCertificates(iasSigningCertificate)
|
||||||
|
} catch (e: CertificateException) {
|
||||||
|
log.error("Failed to parse certificate from HTTP header '{}': {}", iasSigningCertificate, e.message)
|
||||||
|
throw ForbiddenException(response.toResponse("Invalid X-IASReport HTTP headers", SC_FORBIDDEN))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val certValidator = CertPathValidator.getInstance("PKIX")
|
||||||
|
certValidator.validate(certificatePath, pkixParameters)
|
||||||
|
} catch (e: GeneralSecurityException) {
|
||||||
|
log.error("Certificate '{}' is invalid: {}", certificatePath, e.message)
|
||||||
|
throw ForbiddenException(response.toResponse("Invalid IAS certificate", SC_FORBIDDEN))
|
||||||
|
}
|
||||||
|
|
||||||
|
val signature = try {
|
||||||
|
Signature.getInstance("SHA256withRSA").apply {
|
||||||
|
initVerify(certificatePath.certificates[0])
|
||||||
|
}
|
||||||
|
} catch (e: GeneralSecurityException) {
|
||||||
|
log.error("Failed to initialise signature: {}", e.message)
|
||||||
|
throw ForbiddenException(response.toResponse("", SC_FORBIDDEN))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
signature.update(payload)
|
||||||
|
if (!signature.verify(iasSignature.toByteArray().decodeBase64())) {
|
||||||
|
throw ForbiddenException(response.toResponse("Report failed IAS signature check", SC_FORBIDDEN))
|
||||||
|
}
|
||||||
|
} catch (e: SignatureException) {
|
||||||
|
log.error("Failed to parse signature from IAS: {}", e.message)
|
||||||
|
throw ForbiddenException(response.toResponse("Corrupt IAS signature data", SC_FORBIDDEN))
|
||||||
|
}
|
||||||
|
|
||||||
|
String(payload, UTF_8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseCertificates(iasCertificateHeader: String): CertPath {
|
||||||
|
val certificates = mutableListOf<Certificate>()
|
||||||
|
iasCertificateHeader.byteInputStream().use { input ->
|
||||||
|
while (input.available() > 0) {
|
||||||
|
certificates.add(certificateFactory.generateCertificate(input))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return certificateFactory.generateCertPath(certificates)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun responseOf(message: String, statusCode: Int = SC_INTERNAL_SERVER_ERROR): Response = Response.status(statusCode)
|
||||||
|
.entity(AttestationError(message))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private fun HttpResponse.requireHeader(name: String): String {
|
||||||
|
return (this.getFirstHeader(name) ?: throw ForbiddenException(toResponse("Response header '$name' missing", SC_FORBIDDEN))).value
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun HttpResponse.toResponse(message: String, statusCode: Int = statusLine.statusCode): Response {
|
||||||
|
return Response.status(statusCode)
|
||||||
|
.entity(AttestationError(message))
|
||||||
|
.apply {
|
||||||
|
val requestIdHeader = getFirstHeader("Request-ID") ?: return@apply
|
||||||
|
this.header(requestIdHeader.name, requestIdHeader.value)
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createHttpClient(): CloseableHttpClient {
|
||||||
|
val sslContext = SSLContextBuilder()
|
||||||
|
.loadKeyMaterial(isvStore, storePassword, { _, _ -> isvKeyAlias })
|
||||||
|
.setSecureRandom(crypto.random)
|
||||||
|
.build()
|
||||||
|
val registry = RegistryBuilder.create<ConnectionSocketFactory>()
|
||||||
|
.register("https", SSLConnectionSocketFactory(sslContext, getDefaultHostnameVerifier()))
|
||||||
|
.build()
|
||||||
|
return HttpClients.custom()
|
||||||
|
.setConnectionManager(BasicHttpClientConnectionManager(registry).apply {
|
||||||
|
socketConfig = httpSocketConfig
|
||||||
|
})
|
||||||
|
.setDefaultRequestConfig(httpRequestConfig)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun signatureOf(publicKey: ECPublicKey, peerKey: ECPublicKey): ByteArray {
|
||||||
|
val signature = Signature.getInstance("SHA256WithECDSA").let { signer ->
|
||||||
|
signer.initSign(serviceKeyPair.private, crypto.random)
|
||||||
|
signer.update(publicKey.toLittleEndian())
|
||||||
|
signer.update(peerKey.toLittleEndian())
|
||||||
|
signer.sign()
|
||||||
|
}
|
||||||
|
return ByteBuffer.allocate(KEY_SIZE).let { buf ->
|
||||||
|
ASN1InputStream(signature).use { input ->
|
||||||
|
for (number in input.readObject() as DLSequence) {
|
||||||
|
val pos = (number as ASN1Integer).positiveValue.toLittleEndian(KEY_SIZE / 2)
|
||||||
|
buf.put(pos)
|
||||||
|
}
|
||||||
|
buf.array()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.decodeBase64(): ByteArray = Base64.getDecoder().decode(this)
|
||||||
|
private fun String.decodeURL(): String = URLDecoder.decode(this, "UTF-8")
|
||||||
|
private fun ByteArray.removeHeader(headerSize: Int) = copyOfRange(headerSize, size)
|
||||||
|
private fun ByteArray.ifNotZeros(): ByteArray? {
|
||||||
|
for (i in 0 until size) {
|
||||||
|
if (this[i] != 0.toByte()) return this
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
import java.security.Security
|
||||||
|
import javax.ws.rs.ApplicationPath
|
||||||
|
import javax.ws.rs.core.Application
|
||||||
|
|
||||||
|
@ApplicationPath("/")
|
||||||
|
class SgxApplication : Application() {
|
||||||
|
init {
|
||||||
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
|
||||||
|
@JsonPropertyOrder("message")
|
||||||
|
class AttestationError(@param:JsonProperty("message") val message: String)
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include.*
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
|
||||||
|
@JsonPropertyOrder("nonce", "serviceKey")
|
||||||
|
@JsonInclude(NON_NULL)
|
||||||
|
class ChallengeResponse(
|
||||||
|
@param:JsonProperty("nonce")
|
||||||
|
val nonce: String,
|
||||||
|
|
||||||
|
@param:JsonProperty("serviceKey")
|
||||||
|
val serviceKey: ByteArray
|
||||||
|
)
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include.*
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
|
||||||
|
@JsonPropertyOrder("extendedGID")
|
||||||
|
@JsonInclude(NON_NULL)
|
||||||
|
class Message0(
|
||||||
|
@param:JsonProperty("extendedGID")
|
||||||
|
val extendedGID: Int
|
||||||
|
)
|
@ -0,0 +1,15 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
|
||||||
|
@JsonPropertyOrder("ga", "platformGID")
|
||||||
|
class Message1(
|
||||||
|
// The client's 512 bit public DH key (Little Endian)
|
||||||
|
@param:JsonProperty("ga")
|
||||||
|
val ga: ByteArray,
|
||||||
|
|
||||||
|
// Platform GID value from the SGX client
|
||||||
|
@param:JsonProperty("platformGID")
|
||||||
|
val platformGID: String
|
||||||
|
)
|
@ -0,0 +1,37 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
|
||||||
|
@JsonPropertyOrder(
|
||||||
|
"gb",
|
||||||
|
"spid",
|
||||||
|
"linkableQuote",
|
||||||
|
"keyDerivationFuncId",
|
||||||
|
"signatureGbGa",
|
||||||
|
"aesCMAC",
|
||||||
|
"revocationList"
|
||||||
|
)
|
||||||
|
class Message2(
|
||||||
|
// The server's 512 bit public DH key (Little Endian)
|
||||||
|
@param:JsonProperty("gb")
|
||||||
|
val gb: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("spid")
|
||||||
|
val spid: String,
|
||||||
|
|
||||||
|
@param:JsonProperty("linkableQuote")
|
||||||
|
val linkableQuote: Boolean = true,
|
||||||
|
|
||||||
|
@param:JsonProperty("keyDerivationFuncId")
|
||||||
|
val keyDerivationFuncId: Int,
|
||||||
|
|
||||||
|
@param:JsonProperty("signatureGbGa")
|
||||||
|
val signatureGbGa: ByteArray, // Not ASN.1 encoded
|
||||||
|
|
||||||
|
@param:JsonProperty("aesCMAC")
|
||||||
|
val aesCMAC: ByteArray, // Not ASN.1 encoded
|
||||||
|
|
||||||
|
@param:JsonProperty("revocationList")
|
||||||
|
val revocationList: ByteArray
|
||||||
|
)
|
@ -0,0 +1,26 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include.*
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
|
||||||
|
@JsonPropertyOrder("aesCMAC", "ga", "quote", "securityManifest", "nonce")
|
||||||
|
@JsonInclude(NON_NULL)
|
||||||
|
class Message3(
|
||||||
|
@param:JsonProperty("aesCMAC")
|
||||||
|
val aesCMAC: ByteArray, // Not ASN.1 encoded
|
||||||
|
|
||||||
|
// The client's 512 bit public DH key (Little Endian)
|
||||||
|
@param:JsonProperty("ga")
|
||||||
|
val ga: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("quote")
|
||||||
|
val quote: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("securityManifest")
|
||||||
|
val securityManifest: ByteArray? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("nonce")
|
||||||
|
val nonce: String? = null
|
||||||
|
)
|
@ -0,0 +1,67 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include.*
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@JsonPropertyOrder(
|
||||||
|
"reportID",
|
||||||
|
"quoteStatus",
|
||||||
|
"quoteBody",
|
||||||
|
"aesCMAC",
|
||||||
|
"securityManifestStatus",
|
||||||
|
"securityManifestHash",
|
||||||
|
"platformInfo",
|
||||||
|
"epidPseudonym",
|
||||||
|
"nonce",
|
||||||
|
"secret",
|
||||||
|
"secretHash",
|
||||||
|
"secretIV",
|
||||||
|
"timestamp"
|
||||||
|
)
|
||||||
|
@JsonInclude(NON_NULL)
|
||||||
|
class Message4(
|
||||||
|
@param:JsonProperty("reportID")
|
||||||
|
val reportID: String,
|
||||||
|
|
||||||
|
@param:JsonProperty("quoteStatus")
|
||||||
|
val quoteStatus: String,
|
||||||
|
|
||||||
|
@param:JsonProperty("quoteBody")
|
||||||
|
val quoteBody: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("aesCMAC")
|
||||||
|
val aesCMAC: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("securityManifestStatus")
|
||||||
|
val securityManifestStatus: String? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("securityManifestHash")
|
||||||
|
val securityManifestHash: String? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("platformInfo")
|
||||||
|
@get:JsonInclude(NON_EMPTY)
|
||||||
|
val platformInfo: ByteArray? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("epidPseudonym")
|
||||||
|
val epidPseudonym: ByteArray? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("nonce")
|
||||||
|
val nonce: String? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("secret")
|
||||||
|
val secret: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("secretHash")
|
||||||
|
val secretHash: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("secretIV")
|
||||||
|
val secretIV: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("timestamp")
|
||||||
|
@field:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS", timezone = "UTC")
|
||||||
|
val timestamp: LocalDateTime
|
||||||
|
)
|
@ -0,0 +1,19 @@
|
|||||||
|
@file:JvmName("HexadecimalSerialisers")
|
||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||||
|
import net.corda.attestation.hexToBytes
|
||||||
|
import net.corda.attestation.toHexString
|
||||||
|
|
||||||
|
class HexadecimalSerialiser : StdSerializer<ByteArray>(ByteArray::class.java) {
|
||||||
|
override fun serialize(value: ByteArray, gen: JsonGenerator, provider: SerializerProvider) = gen.writeString(value.toHexString())
|
||||||
|
}
|
||||||
|
|
||||||
|
class HexadecimalDeserialiser : StdDeserializer<ByteArray>(ByteArray::class.java) {
|
||||||
|
override fun deserialize(p: JsonParser, context: DeserializationContext) = p.valueAsString.hexToBytes()
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
enum class ManifestStatus {
|
||||||
|
OK,
|
||||||
|
UNKNOWN,
|
||||||
|
INVALID,
|
||||||
|
OUT_OF_DATE,
|
||||||
|
REVOKED,
|
||||||
|
RL_VERSION_MISMATCH
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
enum class QuoteStatus {
|
||||||
|
OK,
|
||||||
|
SIGNATURE_INVALID,
|
||||||
|
GROUP_REVOKED,
|
||||||
|
SIGNATURE_REVOKED,
|
||||||
|
KEY_REVOKED,
|
||||||
|
SIGRL_VERSION_MISMATCH,
|
||||||
|
GROUP_OUT_OF_DATE
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include.*
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
|
||||||
|
@JsonPropertyOrder("signature", "certificatePath", "report")
|
||||||
|
@JsonInclude(NON_NULL)
|
||||||
|
class ReportProxyResponse(
|
||||||
|
@param:JsonProperty("signature")
|
||||||
|
val signature: String,
|
||||||
|
|
||||||
|
@param:JsonProperty("certificatePath")
|
||||||
|
val certificatePath: String,
|
||||||
|
|
||||||
|
@param:JsonProperty("report")
|
||||||
|
val report: ByteArray
|
||||||
|
)
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include.*
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
|
||||||
|
@JsonPropertyOrder("isvEnclaveQuote", "pseManifest", "nonce")
|
||||||
|
@JsonInclude(NON_NULL)
|
||||||
|
class ReportRequest(
|
||||||
|
@param:JsonProperty("isvEnclaveQuote")
|
||||||
|
val isvEnclaveQuote: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("pseManifest")
|
||||||
|
val pseManifest: ByteArray? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("nonce")
|
||||||
|
val nonce: String? = null
|
||||||
|
)
|
@ -0,0 +1,58 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include.*
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@JsonPropertyOrder(
|
||||||
|
"nonce",
|
||||||
|
"id",
|
||||||
|
"timestamp",
|
||||||
|
"epidPseudonym",
|
||||||
|
"isvEnclaveQuoteStatus",
|
||||||
|
"isvEnclaveQuoteBody",
|
||||||
|
"pseManifestStatus",
|
||||||
|
"pseManifestHash",
|
||||||
|
"platformInfoBlob",
|
||||||
|
"revocationReason"
|
||||||
|
)
|
||||||
|
@JsonInclude(NON_NULL)
|
||||||
|
class ReportResponse(
|
||||||
|
@param:JsonProperty("id")
|
||||||
|
val id: String,
|
||||||
|
|
||||||
|
@param:JsonProperty("isvEnclaveQuoteStatus")
|
||||||
|
val isvEnclaveQuoteStatus: QuoteStatus,
|
||||||
|
|
||||||
|
@param:JsonProperty("isvEnclaveQuoteBody")
|
||||||
|
val isvEnclaveQuoteBody: ByteArray,
|
||||||
|
|
||||||
|
@param:JsonProperty("platformInfoBlob")
|
||||||
|
@get:JsonSerialize(using = HexadecimalSerialiser::class)
|
||||||
|
@get:JsonDeserialize(using = HexadecimalDeserialiser::class)
|
||||||
|
val platformInfoBlob: ByteArray? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("revocationReason")
|
||||||
|
val revocationReason: Int? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("pseManifestStatus")
|
||||||
|
val pseManifestStatus: ManifestStatus? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("pseManifestHash")
|
||||||
|
val pseManifestHash: String? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("nonce")
|
||||||
|
val nonce: String? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("epidPseudonym")
|
||||||
|
val epidPseudonym: ByteArray? = null,
|
||||||
|
|
||||||
|
@param:JsonProperty("timestamp")
|
||||||
|
@field:JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS", timezone = "UTC")
|
||||||
|
val timestamp: LocalDateTime
|
||||||
|
)
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include.*
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyOrder
|
||||||
|
|
||||||
|
@JsonPropertyOrder("spid", "revocationList")
|
||||||
|
@JsonInclude(NON_NULL)
|
||||||
|
class RevocationListProxyResponse (
|
||||||
|
@param:JsonProperty("spid")
|
||||||
|
val spid: String,
|
||||||
|
|
||||||
|
@param:JsonProperty("revocationList")
|
||||||
|
val revocationList: ByteArray
|
||||||
|
)
|
@ -0,0 +1,59 @@
|
|||||||
|
package net.corda.mockias
|
||||||
|
|
||||||
|
import net.corda.attestation.message.ias.ManifestStatus
|
||||||
|
import net.corda.attestation.message.ias.QuoteStatus
|
||||||
|
import net.corda.attestation.message.ias.ReportRequest
|
||||||
|
import net.corda.attestation.message.ias.ReportResponse
|
||||||
|
import java.time.Clock
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import javax.servlet.http.HttpServletResponse.*
|
||||||
|
import javax.ws.rs.*
|
||||||
|
import javax.ws.rs.core.MediaType.*
|
||||||
|
import javax.ws.rs.core.Response
|
||||||
|
|
||||||
|
@Path("/attestation/sgx/v2")
|
||||||
|
class MockIAS {
|
||||||
|
private companion object {
|
||||||
|
private const val requestID = "de305d5475b4431badb2eb6b9e546014"
|
||||||
|
private const val QUOTE_BODY_SIZE = 432
|
||||||
|
|
||||||
|
private val platformInfo = byteArrayOf(0x12, 0x34, 0x56, 0x78, 0x9a.toByte(), 0xbc.toByte(), 0xde.toByte(), 0xf0.toByte(), 0x11, 0x22)
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/sigrl/{gid}")
|
||||||
|
fun getSigRL(@PathParam("gid") gid: String): Response {
|
||||||
|
return Response.ok(if (gid.toLowerCase() == "0000000b") "" else "AAIADgAAAAEAAAABAAAAAGSf/es1h/XiJeCg7bXmX0S/NUpJ2jmcEJglQUI8VT5sLGU7iMFu3/UTCv9uPADal3LhbrQvhBa6+/dWbj8hnsE=")
|
||||||
|
.header("Request-ID", requestID)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Consumes(APPLICATION_JSON)
|
||||||
|
@Produces(APPLICATION_JSON)
|
||||||
|
@IASReport
|
||||||
|
@POST
|
||||||
|
@Path("/report")
|
||||||
|
fun getReport(req: ReportRequest?): Response {
|
||||||
|
val request = req ?: return Response.status(SC_BAD_REQUEST)
|
||||||
|
.header("Request-ID", requestID)
|
||||||
|
.build()
|
||||||
|
val report = ReportResponse(
|
||||||
|
id = "9497457846286849067596886882708771068",
|
||||||
|
isvEnclaveQuoteStatus = QuoteStatus.OK,
|
||||||
|
isvEnclaveQuoteBody = if (request.isvEnclaveQuote.size > QUOTE_BODY_SIZE)
|
||||||
|
request.isvEnclaveQuote.copyOf(QUOTE_BODY_SIZE)
|
||||||
|
else
|
||||||
|
request.isvEnclaveQuote,
|
||||||
|
pseManifestStatus = req.pseManifest?.toStatus(),
|
||||||
|
platformInfoBlob = platformInfo,
|
||||||
|
nonce = request.nonce,
|
||||||
|
timestamp = LocalDateTime.now(Clock.systemUTC())
|
||||||
|
)
|
||||||
|
return Response.ok(report)
|
||||||
|
.header("Request-ID", requestID)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.toStatus(): ManifestStatus
|
||||||
|
= if (this.isEmpty() || this[0] == 0.toByte()) ManifestStatus.INVALID else ManifestStatus.OK
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
@file:JvmName("ReportSigner")
|
||||||
|
package net.corda.mockias
|
||||||
|
|
||||||
|
import net.corda.mockias.io.SignatureOutputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.URLEncoder
|
||||||
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.Signature
|
||||||
|
import java.security.cert.Certificate
|
||||||
|
import java.util.*
|
||||||
|
import javax.ws.rs.NameBinding
|
||||||
|
import javax.ws.rs.ext.Provider
|
||||||
|
import javax.ws.rs.ext.WriterInterceptor
|
||||||
|
import javax.ws.rs.ext.WriterInterceptorContext
|
||||||
|
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
@IASReport
|
||||||
|
class ReportSigner : WriterInterceptor {
|
||||||
|
private companion object {
|
||||||
|
private const val BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n"
|
||||||
|
private const val END_CERT = "\n-----END CERTIFICATE-----\n"
|
||||||
|
private const val signatureAlias = "ias"
|
||||||
|
private val storePassword = "attestation".toCharArray()
|
||||||
|
private val keyStore: KeyStore = KeyStore.getInstance("PKCS12").apply {
|
||||||
|
ReportSigner::class.java.classLoader.getResourceAsStream("dummyIAS.pfx")?.use { input ->
|
||||||
|
load(input, storePassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val signingKey = keyStore.getKey(signatureAlias, storePassword) as PrivateKey
|
||||||
|
private val signingCertHeader: String = keyStore.getCertificateChain(signatureAlias).let { chain ->
|
||||||
|
StringBuilder().apply {
|
||||||
|
chain.forEach { cert -> append(cert.toPEM()) }
|
||||||
|
}.toString().encodeURL()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.encodeBase64(): ByteArray = Base64.getEncoder().encode(this)
|
||||||
|
private fun String.encodeURL(): String = URLEncoder.encode(this, "UTF-8")
|
||||||
|
|
||||||
|
private fun Certificate.toPEM(): String = ByteArrayOutputStream().let { out ->
|
||||||
|
out.write(BEGIN_CERT.toByteArray())
|
||||||
|
out.write(encoded.encodeBase64())
|
||||||
|
out.write(END_CERT.toByteArray())
|
||||||
|
String(out.toByteArray(), UTF_8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun aroundWriteTo(context: WriterInterceptorContext) {
|
||||||
|
val contentStream = context.outputStream
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
val signature = Signature.getInstance("SHA256withRSA").apply {
|
||||||
|
initSign(signingKey)
|
||||||
|
}
|
||||||
|
context.outputStream = SignatureOutputStream(baos, signature)
|
||||||
|
try {
|
||||||
|
context.proceed()
|
||||||
|
context.headers?.apply {
|
||||||
|
add("X-IASReport-Signature", signature.sign().encodeBase64().toString(UTF_8))
|
||||||
|
add("X-IASReport-Signing-Certificate", signingCertHeader)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
baos.writeTo(contentStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NameBinding
|
||||||
|
annotation class IASReport
|
@ -0,0 +1,42 @@
|
|||||||
|
package net.corda.mockias.io
|
||||||
|
|
||||||
|
import java.io.FilterOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.security.Signature
|
||||||
|
import java.security.SignatureException
|
||||||
|
|
||||||
|
class SignatureOutputStream(wrapped: OutputStream, val signature: Signature) : FilterOutputStream(wrapped) {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun write(data: Int) {
|
||||||
|
try {
|
||||||
|
signature.update(data.toByte())
|
||||||
|
} catch (e: SignatureException) {
|
||||||
|
throw IOException(e.message, e)
|
||||||
|
}
|
||||||
|
super.write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun write(data: ByteArray, offset: Int, length: Int) {
|
||||||
|
try {
|
||||||
|
signature.update(data, offset, length)
|
||||||
|
} catch (e: SignatureException) {
|
||||||
|
throw IOException(e.message, e)
|
||||||
|
}
|
||||||
|
super.out.write(data, offset, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun flush() {
|
||||||
|
super.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun close() {
|
||||||
|
super.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(SignatureException::class)
|
||||||
|
fun sign(): ByteArray = signature.sign()
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="info">
|
||||||
|
|
||||||
|
<Properties>
|
||||||
|
<Property name="log-path">${sys:attestation.home}</Property>
|
||||||
|
<Property name="log-name">attestation-server</Property>
|
||||||
|
<Property name="archive">${sys:log-path}/archive</Property>
|
||||||
|
<Property name="consoleLogLevel">info</Property>
|
||||||
|
<Property name="defaultLogLevel">debug</Property>
|
||||||
|
</Properties>
|
||||||
|
|
||||||
|
<ThresholdFilter level="trace"/>
|
||||||
|
|
||||||
|
<Appenders>
|
||||||
|
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
|
||||||
|
those that are older than 60 days, but keep the most recent 10 GB -->
|
||||||
|
<RollingFile name="RollingFile-Appender"
|
||||||
|
fileName="${sys:log-path}/${log-name}.log"
|
||||||
|
filePattern="${archive}/${log-name}.%date{yyyy-MM-dd}-%i.log.gz">
|
||||||
|
|
||||||
|
<PatternLayout pattern="%date{ISO8601}{UTC}Z [%-5level] %c - %msg%n"/>
|
||||||
|
|
||||||
|
<Policies>
|
||||||
|
<TimeBasedTriggeringPolicy/>
|
||||||
|
<SizeBasedTriggeringPolicy size="10MB"/>
|
||||||
|
</Policies>
|
||||||
|
|
||||||
|
<DefaultRolloverStrategy min="1" max="10">
|
||||||
|
<Delete basePath="${archive}" maxDepth="1">
|
||||||
|
<IfFileName glob="${log-name}*.log.gz"/>
|
||||||
|
<IfLastModified age="60d">
|
||||||
|
<IfAny>
|
||||||
|
<IfAccumulatedFileSize exceeds="10 GB"/>
|
||||||
|
</IfAny>
|
||||||
|
</IfLastModified>
|
||||||
|
</Delete>
|
||||||
|
</DefaultRolloverStrategy>
|
||||||
|
|
||||||
|
</RollingFile>
|
||||||
|
</Appenders>
|
||||||
|
|
||||||
|
<Loggers>
|
||||||
|
<Root level="${sys:defaultLogLevel}">
|
||||||
|
<AppenderRef ref="RollingFile-Appender"/>
|
||||||
|
</Root>
|
||||||
|
<Logger name="org.apache.http" level="warn"/>
|
||||||
|
<Logger name="org.jboss.resteasy" level="warn"/>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
@ -0,0 +1 @@
|
|||||||
|
crypto.policy=unlimited
|
1
sgx-jvm/remote-attestation/attestation-server/src/main/ssl/intel-ssl/.gitignore
vendored
Normal file
1
sgx-jvm/remote-attestation/attestation-server/src/main/ssl/intel-ssl/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
client.key
|
@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFSzCCA7OgAwIBAgIJANEHdl0yo7CUMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV
|
||||||
|
BAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV
|
||||||
|
BAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0
|
||||||
|
YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwIBcNMTYxMTE0MTUzNzMxWhgPMjA0OTEy
|
||||||
|
MzEyMzU5NTlaMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwL
|
||||||
|
U2FudGEgQ2xhcmExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQD
|
||||||
|
DCdJbnRlbCBTR1ggQXR0ZXN0YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwggGiMA0G
|
||||||
|
CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCfPGR+tXc8u1EtJzLA10Feu1Wg+p7e
|
||||||
|
LmSRmeaCHbkQ1TF3Nwl3RmpqXkeGzNLd69QUnWovYyVSndEMyYc3sHecGgfinEeh
|
||||||
|
rgBJSEdsSJ9FpaFdesjsxqzGRa20PYdnnfWcCTvFoulpbFR4VBuXnnVLVzkUvlXT
|
||||||
|
L/TAnd8nIZk0zZkFJ7P5LtePvykkar7LcSQO85wtcQe0R1Raf/sQ6wYKaKmFgCGe
|
||||||
|
NpEJUmg4ktal4qgIAxk+QHUxQE42sxViN5mqglB0QJdUot/o9a/V/mMeH8KvOAiQ
|
||||||
|
byinkNndn+Bgk5sSV5DFgF0DffVqmVMblt5p3jPtImzBIH0QQrXJq39AT8cRwP5H
|
||||||
|
afuVeLHcDsRp6hol4P+ZFIhu8mmbI1u0hH3W/0C2BuYXB5PC+5izFFh/nP0lc2Lf
|
||||||
|
6rELO9LZdnOhpL1ExFOq9H/B8tPQ84T3Sgb4nAifDabNt/zu6MmCGo5U8lwEFtGM
|
||||||
|
RoOaX4AS+909x00lYnmtwsDVWv9vBiJCXRsCAwEAAaOByTCBxjBgBgNVHR8EWTBX
|
||||||
|
MFWgU6BRhk9odHRwOi8vdHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9jb250ZW50
|
||||||
|
L0NSTC9TR1gvQXR0ZXN0YXRpb25SZXBvcnRTaWduaW5nQ0EuY3JsMB0GA1UdDgQW
|
||||||
|
BBR4Q3t2pn680K9+QjfrNXw7hwFRPDAfBgNVHSMEGDAWgBR4Q3t2pn680K9+Qjfr
|
||||||
|
NXw7hwFRPDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAYEAeF8tYMXICvQqeXYQITkV2oLJsp6J4JAqJabHWxYJHGir
|
||||||
|
IEqucRiJSSx+HjIJEUVaj8E0QjEud6Y5lNmXlcjqRXaCPOqK0eGRz6hi+ripMtPZ
|
||||||
|
sFNaBwLQVV905SDjAzDzNIDnrcnXyB4gcDFCvwDFKKgLRjOB/WAqgscDUoGq5ZVi
|
||||||
|
zLUzTqiQPmULAQaB9c6Oti6snEFJiCQ67JLyW/E83/frzCmO5Ru6WjU4tmsmy8Ra
|
||||||
|
Ud4APK0wZTGtfPXU7w+IBdG5Ez0kE1qzxGQaL4gINJ1zMyleDnbuS8UicjJijvqA
|
||||||
|
152Sq049ESDz+1rRGc2NVEqh1KaGXmtXvqxXcTB+Ljy5Bw2ke0v8iGngFBPqCTVB
|
||||||
|
3op5KBG3RjbF6RRSzwzuWfL7QErNC8WEy5yDVARzTA5+xmBc388v9Dm21HGfcC8O
|
||||||
|
DD+gT9sSpssq0ascmvH49MOgjt1yoysLtdCtJW/9FZpoOypaHx0R+mJTLwPXVMrv
|
||||||
|
DaVzWh5aiEx+idkSGMnX
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,21 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDhDCCAmygAwIBAgIJAMJY5H3wxR1xMA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV
|
||||||
|
BAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMQswCQYDVQQKDAJSMzEQMA4GA1UE
|
||||||
|
AwwHUjMgVGVzdDAeFw0xNzEwMjUxMzEwMjJaFw0xODEwMjUxMzEwMjJaMEExCzAJ
|
||||||
|
BgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMQswCQYDVQQKDAJSMzEQMA4G
|
||||||
|
A1UEAwwHUjMgVGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALqc
|
||||||
|
lg7AXm5QPdJ/Yl3oUsIC+kfU4+OV6cvmJjAoThgah64h2bDjl7bYKnssSGbxZ2kH
|
||||||
|
21jTP6PxxB27XAPlkgGJEx0F7+Ss8Tq8XxtgfpARVNbQvwt4R7iujbeAS/1QIbJc
|
||||||
|
xpCCVsp4rAfWKV/DqzfBpSi/Rhdw5YmCfbsVZ/1GBY8kFDCI/KMLvZ67y1ZbqBYN
|
||||||
|
c146xp6uHxCiW5xhyOaIEgoefVyLCpK/SjzN9a3dQHyz07axOayjSRyK/gM8WDRU
|
||||||
|
K1+oiJOIvhp5zX7H5D5eRgksTNH3bwN24TVob63ltcs/N7MC/i09Omq+u9E99yo9
|
||||||
|
7JqSCzwnanxwafLYVbcCAwEAAaN/MH0wCwYDVR0PBAQDAgKkMB0GA1UdDgQWBBQ0
|
||||||
|
Rq9OPXGihG1iE6HUm7aMKorgeTAfBgNVHSMEGDAWgBQ0Rq9OPXGihG1iE6HUm7aM
|
||||||
|
KorgeTAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TBAgwBgEB
|
||||||
|
/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAFUr+hlaixchIQZkzXzvCZFu+gfe0I6YJ
|
||||||
|
rZd9RKUJQfNteVoC29emEORQazjOn69Q2T9iNFy3COHQrzHhS0kZZEdm72283Yj5
|
||||||
|
h6C/krEp784/cjWRObDcu8CP8o9FdFcXZb52NpC+xlTv9Re8rhV8NcitlGW2z3eW
|
||||||
|
9mBVePSvhilL0ssPXjfDQY2GALck5+ZxiA8gAmzvCkoI23fxUTwQ3u+qhjyPxjCk
|
||||||
|
kST2Ir7ilCNdeZTJErQhBtcQY+K39W5C98aVSJQDRDgeTT5nClBBMBlMrVE4ohaO
|
||||||
|
eMgJJkraVMDXiJqXXG1YccsoXb5ezmPCYFoTPOPtY7TyPMZb1Jxgag==
|
||||||
|
-----END CERTIFICATE-----
|
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
rm -f isv.pfx ias.pfx
|
||||||
|
|
||||||
|
ALIAS=isv
|
||||||
|
KEYPASS=attestation
|
||||||
|
STOREPASS=attestation
|
||||||
|
|
||||||
|
DIRHOME=$(dirname $0)
|
||||||
|
KEYFILE=${DIRHOME}/client.key
|
||||||
|
CRTFILE=${DIRHOME}/client.crt
|
||||||
|
INTEL_CRTFILE=${DIRHOME}/AttestationReportSigningCACert.pem
|
||||||
|
|
||||||
|
openssl verify -x509_strict -purpose sslclient -CAfile ${CRTFILE} ${CRTFILE}
|
||||||
|
|
||||||
|
if [ ! -r ${KEYFILE} ]; then
|
||||||
|
echo "Development private key missing. This is the key that IAS expects our HTTP client to be using for Mutual TLS."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
openssl pkcs12 -export -out client.pfx -inkey ${KEYFILE} -in ${CRTFILE} -passout pass:${STOREPASS}
|
||||||
|
|
||||||
|
keytool -importkeystore -srckeystore client.pfx -srcstoretype pkcs12 -destkeystore isv.pfx -deststoretype pkcs12 -srcstorepass ${STOREPASS} -deststorepass ${STOREPASS}
|
||||||
|
|
||||||
|
keytool -keystore isv.pfx -storetype pkcs12 -changealias -alias 1 -destalias ${ALIAS} -storepass ${STOREPASS}
|
||||||
|
|
||||||
|
rm -rf client.pfx
|
||||||
|
|
||||||
|
# Generate trust store for connecting with IAS.
|
||||||
|
if [ -r ${INTEL_CRTFILE} ]; then
|
||||||
|
keytool -import -keystore ias.pfx -storetype pkcs12 -file ${INTEL_CRTFILE} -alias ias -storepass ${STOREPASS} <<EOF
|
||||||
|
yes
|
||||||
|
EOF
|
||||||
|
fi
|
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
STOREPASS=attestation
|
||||||
|
ALIAS=isv-svc
|
||||||
|
|
||||||
|
rm -f isv-svc.pfx
|
||||||
|
|
||||||
|
openssl ecparam -name secp256r1 -genkey -noout -out privateKey.pem
|
||||||
|
openssl req -new -key privateKey.pem -x509 -out server.crt -days 1000 <<EOF
|
||||||
|
.
|
||||||
|
|
||||||
|
.
|
||||||
|
.
|
||||||
|
|
||||||
|
localhost
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
openssl pkcs12 -export -out isv-svc.pfx -inkey privateKey.pem -in server.crt -passout pass:${STOREPASS}
|
||||||
|
|
||||||
|
keytool -keystore isv-svc.pfx -storetype pkcs12 -changealias -alias 1 -destalias ${ALIAS} -storepass ${STOREPASS}
|
||||||
|
|
||||||
|
rm -f *.pem *.crt
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Context>
|
||||||
|
<JarScanner>
|
||||||
|
<JarScanFilter defaultTldScan="false"/>
|
||||||
|
</JarScanner>
|
||||||
|
</Context>
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||||
|
version="3.1">
|
||||||
|
<session-config>
|
||||||
|
<session-timeout>10</session-timeout>
|
||||||
|
</session-config>
|
||||||
|
</web-app>
|
@ -0,0 +1,10 @@
|
|||||||
|
@file:JvmName("ByteUtils")
|
||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
fun byteArray(size: Int, of: Int) = ByteArray(size, { of.toByte() })
|
||||||
|
|
||||||
|
fun unsignedByteArrayOf(vararg values: Int) = ByteArray(values.size).apply {
|
||||||
|
for (i in 0 until values.size) {
|
||||||
|
this[i] = values[i].toByte()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.Description
|
||||||
|
import org.junit.runners.model.Statement
|
||||||
|
import java.security.Security
|
||||||
|
|
||||||
|
class CryptoProvider : TestRule {
|
||||||
|
override fun apply(statement: Statement, description: Description?): Statement {
|
||||||
|
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||||
|
Security.addProvider(BouncyCastleProvider())
|
||||||
|
}
|
||||||
|
return statement
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import java.nio.charset.StandardCharsets.*
|
||||||
|
import java.security.KeyFactory
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
|
||||||
|
class CryptoTest {
|
||||||
|
private lateinit var keyFactory: KeyFactory
|
||||||
|
private lateinit var crypto: Crypto
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val cryptoProvider = CryptoProvider()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
keyFactory = KeyFactory.getInstance("EC")
|
||||||
|
crypto = Crypto()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testKeyConversions() {
|
||||||
|
val keyPair = crypto.generateKeyPair()
|
||||||
|
val ecPublicKey = keyPair.public as ECPublicKey
|
||||||
|
val ecParameters = ecPublicKey.params
|
||||||
|
val bytes = ecPublicKey.toLittleEndian()
|
||||||
|
val resultKey = keyFactory.generatePublic(bytes.toBigEndianKeySpec(ecParameters)) as ECPublicKey
|
||||||
|
assertEquals(ecPublicKey, resultKey)
|
||||||
|
assertArrayEquals(ecPublicKey.encoded, resultKey.encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSharedSecret() {
|
||||||
|
val keyPairA = crypto.generateKeyPair()
|
||||||
|
val keyPairB = crypto.generateKeyPair()
|
||||||
|
|
||||||
|
val secretA = crypto.generateSharedSecret(keyPairA.private, keyPairB.public)
|
||||||
|
val secretB = crypto.generateSharedSecret(keyPairB.private, keyPairA.public)
|
||||||
|
assertArrayEquals(secretB, secretA)
|
||||||
|
assertEquals(32, secretA.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEncryption() {
|
||||||
|
val keyPairA = crypto.generateKeyPair()
|
||||||
|
val keyPairB = crypto.generateKeyPair()
|
||||||
|
val secretKeyA = crypto.generateSecretKey(keyPairA.private, keyPairB.public)
|
||||||
|
val secretKeyB = crypto.generateSecretKey(keyPairB.private, keyPairA.public)
|
||||||
|
assertEquals(secretKeyA, secretKeyB)
|
||||||
|
assertArrayEquals(secretKeyA.encoded, secretKeyB.encoded)
|
||||||
|
|
||||||
|
val iv = crypto.createIV()
|
||||||
|
val data = crypto.encrypt("Sooper secret string value!".toByteArray(), secretKeyA, iv)
|
||||||
|
assertEquals("Sooper secret string value!", String(crypto.decrypt(data, secretKeyB, iv), UTF_8))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAesCMAC() {
|
||||||
|
val messageBytes = "Hello World".toByteArray()
|
||||||
|
val keyBytes = "0123456789012345".toByteArray()
|
||||||
|
val cmac = crypto.aesCMAC(keyBytes, messageBytes)
|
||||||
|
assertArrayEquals("3AFAFFFC4EB9274ABD6C9CC3D8B6984A".hexToBytes(), cmac)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testToHexArrayString() {
|
||||||
|
val bytes = unsignedByteArrayOf(0xf5, 0x04, 0x83, 0x71)
|
||||||
|
assertEquals("[0xf5,0x04,0x83,0x71]", bytes.toHexArrayString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test vectors from RFC4493 and NIST 800-38B`() {
|
||||||
|
// Key as defined on https://tools.ietf.org/html/rfc4493.html#appendix-A
|
||||||
|
val testKey = "2b7e151628aed2a6abf7158809cf4f3c".hexToBytes()
|
||||||
|
|
||||||
|
// Example 1: len = 0
|
||||||
|
val out0 = crypto.aesCMAC(testKey, ByteArray(0))
|
||||||
|
assertArrayEquals("bb1d6929e95937287fa37d129b756746".hexToBytes(), out0)
|
||||||
|
|
||||||
|
// Example 2: len = 16
|
||||||
|
val out16 = crypto.aesCMAC(testKey, "6bc1bee22e409f96e93d7e117393172a".hexToBytes())
|
||||||
|
assertArrayEquals("070a16b46b4d4144f79bdd9dd04a287c".hexToBytes(), out16)
|
||||||
|
|
||||||
|
// Example 3: len = 40
|
||||||
|
val messageBytes40 = ("6bc1bee22e409f96e93d7e117393172a" +
|
||||||
|
"ae2d8a571e03ac9c9eb76fac45af8e51" +
|
||||||
|
"30c81c46a35ce411").hexToBytes()
|
||||||
|
val out40 = crypto.aesCMAC(testKey, messageBytes40)
|
||||||
|
assertArrayEquals("dfa66747de9ae63030ca32611497c827".hexToBytes(), out40)
|
||||||
|
|
||||||
|
// Example 4: len = 64
|
||||||
|
val messageBytes64 = ("6bc1bee22e409f96e93d7e117393172a" +
|
||||||
|
"ae2d8a571e03ac9c9eb76fac45af8e51" +
|
||||||
|
"30c81c46a35ce411e5fbc1191a0a52ef" +
|
||||||
|
"f69f2445df4f9b17ad2b417be66c3710").hexToBytes()
|
||||||
|
val out64 = crypto.aesCMAC(testKey, messageBytes64)
|
||||||
|
assertArrayEquals("51f0bebf7e3b9d92fc49741779363cfe".hexToBytes(), out64)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class DummyRandom : SecureRandom(byteArrayOf()) {
|
||||||
|
override fun nextBoolean() = false
|
||||||
|
override fun nextInt() = 9
|
||||||
|
override fun nextInt(bound: Int) = 0
|
||||||
|
override fun nextBytes(bytes: ByteArray) = Arrays.fill(bytes, 9)
|
||||||
|
override fun nextDouble() = 9.0
|
||||||
|
override fun nextFloat() = 9.0f
|
||||||
|
override fun nextLong() = 9L
|
||||||
|
override fun nextGaussian() = 0.0
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Test
|
||||||
|
import sun.security.ec.ECPublicKeyImpl
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
import java.security.spec.ECGenParameterSpec
|
||||||
|
import java.security.spec.ECPoint
|
||||||
|
|
||||||
|
class EndianUtilsTest {
|
||||||
|
@Test
|
||||||
|
fun testBigIntegerToLittleEndian() {
|
||||||
|
val source = BigInteger("8877665544332211", 16)
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x11), source.toLittleEndian(1))
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x11, 0x22), source.toLittleEndian(2))
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x11, 0x22, 0x33), source.toLittleEndian(3))
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x11, 0x22, 0x33, 0x44), source.toLittleEndian(4))
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x11, 0x22, 0x33, 0x44, 0x55), source.toLittleEndian(5))
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x11, 0x22, 0x33, 0x44, 0x55, 0x66), source.toLittleEndian(6))
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77), source.toLittleEndian(7))
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88), source.toLittleEndian(8))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRemovingLeadingZeros() {
|
||||||
|
assertArrayEquals(byteArrayOf(), byteArrayOf().removeLeadingZeros())
|
||||||
|
assertArrayEquals(byteArrayOf(), byteArrayOf(0x00, 0x00, 0x00, 0x00).removeLeadingZeros())
|
||||||
|
assertArrayEquals(byteArrayOf(0x7F, 0x63), byteArrayOf(0x00, 0x00, 0x7F, 0x63).removeLeadingZeros())
|
||||||
|
assertArrayEquals(byteArrayOf(0x7F, 0x43), byteArrayOf(0x7F, 0x43).removeLeadingZeros())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUnsignedBytes() {
|
||||||
|
val source = BigInteger("FFAA", 16)
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0xFF, 0xAA), source.toUnsignedBytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testToHexString() {
|
||||||
|
val source = unsignedByteArrayOf(0xFF, 0xAA, 0x00)
|
||||||
|
assertEquals("FFAA00", source.toHexString().toUpperCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testShortToLittleEndian() {
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x0F, 0x00), 15.toShort().toLittleEndian())
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0xFF, 0xFF), 65535.toShort().toLittleEndian())
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0x00, 0x01), 256.toShort().toLittleEndian())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLittleEndianPublicKey() {
|
||||||
|
val ecParameters = (KeyPairGenerator.getInstance("EC").apply { initialize(ECGenParameterSpec("secp256r1")) }.generateKeyPair().public as ECPublicKey).params
|
||||||
|
val publicKey = ECPublicKeyImpl(ECPoint(BigInteger.TEN, BigInteger.ONE), ecParameters)
|
||||||
|
|
||||||
|
val littleEndian2 = publicKey.toLittleEndian(2)
|
||||||
|
assertArrayEquals(byteArrayOf(0x0A, 0x01), littleEndian2)
|
||||||
|
val littleEndian4 = publicKey.toLittleEndian(4)
|
||||||
|
assertArrayEquals(byteArrayOf(0x0A, 0x00, 0x01, 0x00), littleEndian4)
|
||||||
|
val littleEndian8 = publicKey.toLittleEndian(8)
|
||||||
|
assertArrayEquals(byteArrayOf(0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00), littleEndian8)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLittleEndianUnsigned() {
|
||||||
|
val veryBigNumber1 = BigInteger("FFFFFFFFEEEEEEEE", 16)
|
||||||
|
val veryBigNumber2 = BigInteger("AAAAAAAABBBBBBBB", 16)
|
||||||
|
val ecParameters = (KeyPairGenerator.getInstance("EC").apply { initialize(ECGenParameterSpec("secp256r1")) }.generateKeyPair().public as ECPublicKey).params
|
||||||
|
val publicKey = ECPublicKeyImpl(ECPoint(veryBigNumber1, veryBigNumber2), ecParameters)
|
||||||
|
|
||||||
|
val littleEndian16 = publicKey.toLittleEndian(16)
|
||||||
|
assertArrayEquals(byteArray(4, 0xEE)
|
||||||
|
.plus(byteArray(4, 0xFF)
|
||||||
|
.plus(byteArray(4, 0xBB))
|
||||||
|
.plus(byteArray(4, 0xAA))), littleEndian16)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLittleEndianUnderflow() {
|
||||||
|
val number = BigInteger("007FEDCB", 16)
|
||||||
|
val littleEndian = number.toLittleEndian(4)
|
||||||
|
assertArrayEquals(unsignedByteArrayOf(0xcb, 0xed, 0x7f, 0x00), littleEndian)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package net.corda.attestation
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.security.*
|
||||||
|
import java.security.interfaces.ECPrivateKey
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
import java.security.spec.*
|
||||||
|
|
||||||
|
class SampleTest {
|
||||||
|
private companion object {
|
||||||
|
private val privateKeyBytesBE = unsignedByteArrayOf(
|
||||||
|
0x40, 0x8A, 0x06, 0x1A, 0xE6, 0x81, 0x3A, 0x7B, 0xAC, 0x4E, 0x34, 0xAA, 0x4D, 0x61, 0x3C, 0x86,
|
||||||
|
0xF3, 0xDD, 0x9C, 0x48, 0x82, 0xA8, 0x68, 0x57, 0xFC, 0xB3, 0x9D, 0xF9, 0x81, 0xB6, 0x56, 0x2D
|
||||||
|
)
|
||||||
|
private val publicKeyBytesBE = unsignedByteArrayOf(
|
||||||
|
0x39, 0x16, 0xB0, 0x69, 0xB2, 0xBB, 0x0F, 0x92, 0xC4, 0x10, 0x27, 0x30, 0x9E,
|
||||||
|
0x45, 0x61, 0x93, 0xB1, 0x67, 0x4F, 0xFB, 0x3E, 0xBC, 0x1F, 0xC5, 0xAE, 0x9F, 0x1A, 0x59, 0x45, 0x9F, 0x8C, 0xC0,
|
||||||
|
0x4F, 0x96, 0x00, 0x30, 0x6E, 0x6C, 0x1F, 0x23, 0xF1, 0x5A, 0x2B, 0x1C, 0xC8, 0x32, 0xEB, 0xB8, 0xDC, 0x6A, 0x1A,
|
||||||
|
0xA9, 0xE0, 0xCA, 0x35, 0x2A, 0x72, 0x46, 0x52, 0x2B, 0x24, 0x6B, 0x98, 0x5D
|
||||||
|
)
|
||||||
|
|
||||||
|
private val privateKeyBytesLE = unsignedByteArrayOf(
|
||||||
|
0x90, 0xe7, 0x6c, 0xbb, 0x2d, 0x52, 0xa1, 0xce, 0x3b, 0x66, 0xde, 0x11, 0x43, 0x9c, 0x87, 0xec,
|
||||||
|
0x1f, 0x86, 0x6a, 0x3b, 0x65, 0xb6, 0xae, 0xea, 0xad, 0x57, 0x34, 0x53, 0xd1, 0x03, 0x8c, 0x01
|
||||||
|
)
|
||||||
|
private val publicKeyBytesLE = unsignedByteArrayOf(
|
||||||
|
0x72, 0x12, 0x8a, 0x7a, 0x17, 0x52, 0x6e, 0xbf, 0x85, 0xd0, 0x3a, 0x62, 0x37, 0x30, 0xae, 0xad,
|
||||||
|
0x3e, 0x3d, 0xaa, 0xee, 0x9c, 0x60, 0x73, 0x1d, 0xb0, 0x5b, 0xe8, 0x62, 0x1c, 0x4b, 0xeb, 0x38,
|
||||||
|
0xd4, 0x81, 0x40, 0xd9, 0x50, 0xe2, 0x57, 0x7b, 0x26, 0xee, 0xb7, 0x41, 0xe7, 0xc6, 0x14, 0xe2,
|
||||||
|
0x24, 0xb7, 0xbd, 0xc9, 0x03, 0xf2, 0x9a, 0x28, 0xa8, 0x3c, 0xc8, 0x10, 0x11, 0x14, 0x5e, 0x06
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var ecParameters: ECParameterSpec
|
||||||
|
private lateinit var keyFactory: KeyFactory
|
||||||
|
private lateinit var crypto: Crypto
|
||||||
|
|
||||||
|
private lateinit var publicKey: ECPublicKey
|
||||||
|
private lateinit var privateKey: ECPrivateKey
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
crypto = Crypto(DummyRandom())
|
||||||
|
keyFactory = KeyFactory.getInstance("EC")
|
||||||
|
ecParameters = (crypto.generateKeyPair().public as ECPublicKey).params
|
||||||
|
publicKey = keyFactory.generatePublic(publicKeyBytesLE.toBigEndianKeySpec(ecParameters)) as ECPublicKey
|
||||||
|
privateKey = keyFactory.generatePrivate(ECPrivateKeySpec(privateKeyBytesLE.reversedArray().toPositiveInteger(), ecParameters)) as ECPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun checkKey() {
|
||||||
|
val myData = "And now for something completely different!"
|
||||||
|
val signature = Signature.getInstance("SHA256WithECDSA").let { signer ->
|
||||||
|
signer.initSign(privateKey, crypto.random)
|
||||||
|
signer.update(myData.toByteArray())
|
||||||
|
signer.sign()
|
||||||
|
}
|
||||||
|
val verified = Signature.getInstance("SHA256WithECDSA").let { verifier ->
|
||||||
|
verifier.initVerify(publicKey)
|
||||||
|
verifier.update(myData.toByteArray())
|
||||||
|
verifier.verify(signature)
|
||||||
|
}
|
||||||
|
assertTrue(verified)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun reversePublicKey() {
|
||||||
|
val transformedKey = keyFactory.generatePublic(publicKey.toLittleEndian().toBigEndianKeySpec(ecParameters)) as ECPublicKey
|
||||||
|
assertArrayEquals(publicKey.encoded, transformedKey.encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toKeySpec(ecParameters: ECParameterSpec): KeySpec {
|
||||||
|
if (size != KEY_SIZE) {
|
||||||
|
throw IllegalArgumentException("Public key has incorrect size ($size bytes)")
|
||||||
|
}
|
||||||
|
val ecPoint = ECPoint(
|
||||||
|
copyOf(size / 2).toPositiveInteger(),
|
||||||
|
copyOfRange(size / 2, size).toPositiveInteger()
|
||||||
|
)
|
||||||
|
return ECPublicKeySpec(ecPoint, ecParameters)
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ChallengeResponseTest {
|
||||||
|
private companion object {
|
||||||
|
private val serviceKeyData = byteArrayOf(0x10, 0x00, 0x22, 0x00)
|
||||||
|
private val serviceKeyBase64 = serviceKeyData.toBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialise() {
|
||||||
|
val challenge = ChallengeResponse("<nonsense>", serviceKeyData)
|
||||||
|
val str = mapper.writeValueAsString(challenge)
|
||||||
|
assertEquals("""{"nonce":"<nonsense>","serviceKey":"$serviceKeyBase64"}""", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialise() {
|
||||||
|
val str = """{"nonce":"<nonsense>","serviceKey":"$serviceKeyBase64"}"""
|
||||||
|
val challenge = mapper.readValue(str, ChallengeResponse::class.java)
|
||||||
|
assertEquals("<nonsense>", challenge.nonce)
|
||||||
|
assertArrayEquals(serviceKeyData, challenge.serviceKey)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class Message0Test {
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialise() {
|
||||||
|
val msg0 = Message0(0x000F207F)
|
||||||
|
val str = mapper.writeValueAsString(msg0)
|
||||||
|
assertEquals("""{"extendedGID":991359}""", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialise() {
|
||||||
|
val str = """{"extendedGID":991359}"""
|
||||||
|
val msg0 = mapper.readValue(str, Message0::class.java)
|
||||||
|
assertEquals(0x000F207F, msg0.extendedGID)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class Message1Test {
|
||||||
|
private companion object {
|
||||||
|
private val gaData = byteArrayOf(0x10, 0x00, 0x22, 0x00)
|
||||||
|
private val gaBase64 = gaData.toBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialise() {
|
||||||
|
val msg1 = Message1(
|
||||||
|
ga = gaData,
|
||||||
|
platformGID = "2139062143"
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(msg1)
|
||||||
|
assertEquals("""{"ga":"$gaBase64","platformGID":"2139062143"}""", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialise() {
|
||||||
|
val str = """{"ga":"$gaBase64","platformGID":"2139062143"}"""
|
||||||
|
val msg1 = mapper.readValue(str, Message1::class.java)
|
||||||
|
assertArrayEquals(gaData, msg1.ga)
|
||||||
|
assertEquals("2139062143", msg1.platformGID)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class Message2Test {
|
||||||
|
private companion object {
|
||||||
|
private const val SPID = "8F42710C36029FA11744"
|
||||||
|
|
||||||
|
private val gbData = byteArrayOf(0x10, 0x00, 0x22, 0x00)
|
||||||
|
private val gbBase64 = gbData.toBase64()
|
||||||
|
private val revocationListData = byteArrayOf(0x7F, 0x7F, 0x7F, 0x7F)
|
||||||
|
private val revocationListBase64 = revocationListData.toBase64()
|
||||||
|
private val signatureData = byteArrayOf(0x31, 0x35, 0x5D, 0x1A, 0x27, 0x44)
|
||||||
|
private val signatureBase64 = signatureData.toBase64()
|
||||||
|
private val aesCMACData = byteArrayOf(0x7C, 0x62, 0x50, 0x2B, 0x47, 0x0E)
|
||||||
|
private val aesCMACBase64 = aesCMACData.toBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialise() {
|
||||||
|
val msg2 = Message2(
|
||||||
|
gb = gbData,
|
||||||
|
spid = SPID,
|
||||||
|
linkableQuote = true,
|
||||||
|
keyDerivationFuncId = 1,
|
||||||
|
signatureGbGa = signatureData,
|
||||||
|
aesCMAC = aesCMACData,
|
||||||
|
revocationList = revocationListData
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(msg2)
|
||||||
|
assertEquals("{"
|
||||||
|
+ "\"gb\":\"$gbBase64\","
|
||||||
|
+ "\"spid\":\"$SPID\","
|
||||||
|
+ "\"linkableQuote\":true,"
|
||||||
|
+ "\"keyDerivationFuncId\":1,"
|
||||||
|
+ "\"signatureGbGa\":\"$signatureBase64\","
|
||||||
|
+ "\"aesCMAC\":\"$aesCMACBase64\","
|
||||||
|
+ "\"revocationList\":\"$revocationListBase64\""
|
||||||
|
+ "}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialise() {
|
||||||
|
val str = """{
|
||||||
|
"gb":"$gbBase64",
|
||||||
|
"spid":"$SPID",
|
||||||
|
"linkableQuote":true,
|
||||||
|
"keyDerivationFuncId":1,
|
||||||
|
"signatureGbGa":"$signatureBase64",
|
||||||
|
"aesCMAC":"$aesCMACBase64",
|
||||||
|
"revocationList":"$revocationListBase64"
|
||||||
|
}"""
|
||||||
|
val msg2 = mapper.readValue(str, Message2::class.java)
|
||||||
|
assertArrayEquals(gbData, msg2.gb)
|
||||||
|
assertEquals(SPID, msg2.spid)
|
||||||
|
assertTrue(msg2.linkableQuote)
|
||||||
|
assertEquals(1, msg2.keyDerivationFuncId)
|
||||||
|
assertArrayEquals(signatureData, msg2.signatureGbGa)
|
||||||
|
assertArrayEquals(aesCMACData, msg2.aesCMAC)
|
||||||
|
assertArrayEquals(revocationListData, msg2.revocationList)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class Message3Test {
|
||||||
|
private companion object {
|
||||||
|
private val cmacData = byteArrayOf(0x50, 0x60, 0x70, 0x7F)
|
||||||
|
private val cmacBase64 = cmacData.toBase64()
|
||||||
|
private val gaData = byteArrayOf(0x10, 0x00, 0x22, 0x00)
|
||||||
|
private val gaBase64 = gaData.toBase64()
|
||||||
|
private val quoteData = byteArrayOf(0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F)
|
||||||
|
private val quoteBase64 = quoteData.toBase64()
|
||||||
|
private val manifestData = byteArrayOf(0x44, 0x44, 0x44, 0x44, 0x44, 0x44)
|
||||||
|
private val manifestBase64 = manifestData.toBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBasicSerialise() {
|
||||||
|
val msg3 = Message3(
|
||||||
|
aesCMAC = cmacData,
|
||||||
|
ga = gaData,
|
||||||
|
quote = quoteData
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(msg3)
|
||||||
|
assertEquals("{"
|
||||||
|
+ "\"aesCMAC\":\"$cmacBase64\","
|
||||||
|
+ "\"ga\":\"$gaBase64\","
|
||||||
|
+ "\"quote\":\"$quoteBase64\""
|
||||||
|
+ "}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFullSerialise() {
|
||||||
|
val msg3 = Message3(
|
||||||
|
aesCMAC = cmacData,
|
||||||
|
ga = gaData,
|
||||||
|
quote = quoteData,
|
||||||
|
securityManifest = manifestData,
|
||||||
|
nonce = "<nonce-value>"
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(msg3)
|
||||||
|
assertEquals("{"
|
||||||
|
+ "\"aesCMAC\":\"$cmacBase64\","
|
||||||
|
+ "\"ga\":\"$gaBase64\","
|
||||||
|
+ "\"quote\":\"$quoteBase64\","
|
||||||
|
+ "\"securityManifest\":\"$manifestBase64\","
|
||||||
|
+ "\"nonce\":\"<nonce-value>\""
|
||||||
|
+ "}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBasicDeserialise() {
|
||||||
|
val str = """{
|
||||||
|
"aesCMAC":"$cmacBase64",
|
||||||
|
"ga":"$gaBase64",
|
||||||
|
"quote":"$quoteBase64"
|
||||||
|
}"""
|
||||||
|
val msg3 = mapper.readValue(str, Message3::class.java)
|
||||||
|
assertArrayEquals(cmacData, msg3.aesCMAC)
|
||||||
|
assertArrayEquals(gaData, msg3.ga)
|
||||||
|
assertArrayEquals(quoteData, msg3.quote)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFullDeserialise() {
|
||||||
|
val str = """{
|
||||||
|
"aesCMAC":"$cmacBase64",
|
||||||
|
"ga":"$gaBase64",
|
||||||
|
"quote":"$quoteBase64",
|
||||||
|
"securityManifest":"$manifestBase64",
|
||||||
|
"nonce":"<nonce-value>"
|
||||||
|
}"""
|
||||||
|
val msg3 = mapper.readValue(str, Message3::class.java)
|
||||||
|
assertArrayEquals(cmacData, msg3.aesCMAC)
|
||||||
|
assertArrayEquals(gaData, msg3.ga)
|
||||||
|
assertArrayEquals(quoteData, msg3.quote)
|
||||||
|
assertArrayEquals(manifestData, msg3.securityManifest)
|
||||||
|
assertEquals("<nonce-value>", msg3.nonce)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
import net.corda.attestation.unsignedByteArrayOf
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class Message4Test {
|
||||||
|
private companion object {
|
||||||
|
private val iso8601Time = "2017-11-09T15:03:46.345678"
|
||||||
|
private val testTimestamp = LocalDateTime.from(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS").parse(iso8601Time))
|
||||||
|
private val quoteBodyData = byteArrayOf(0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D)
|
||||||
|
private val quoteBodyBase64 = quoteBodyData.toBase64()
|
||||||
|
private val cmacData = byteArrayOf(0x17, 0x1F, 0x73, 0x66, 0x2E, 0x3F)
|
||||||
|
private val cmacBase64 = cmacData.toBase64()
|
||||||
|
private val platformInfoData = unsignedByteArrayOf(0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF)
|
||||||
|
private val platformInfoBase64 = platformInfoData.toBase64()
|
||||||
|
private val pseudonymData = byteArrayOf(0x31, 0x7E, 0x4A, 0x14)
|
||||||
|
private val pseudonymBase64 = pseudonymData.toBase64()
|
||||||
|
private val secretData = "Mystery data".toByteArray()
|
||||||
|
private val secretBase64 = secretData.toBase64()
|
||||||
|
private val secretHashData = byteArrayOf(0x02, 0x08, 0x25, 0x74)
|
||||||
|
private val secretHashBase64 = secretHashData.toBase64()
|
||||||
|
private val secretIVData = byteArrayOf(0x62, 0x72, 0x2A, 0x4F, 0x0E, -0x44)
|
||||||
|
private val secretIVBase64 = secretIVData.toBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper().registerModule(JavaTimeModule())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBasicSerialise() {
|
||||||
|
val msg4 = Message4(
|
||||||
|
reportID = "<report-id>",
|
||||||
|
quoteStatus = "OK",
|
||||||
|
quoteBody = quoteBodyData,
|
||||||
|
aesCMAC = cmacData,
|
||||||
|
platformInfo = byteArrayOf(),
|
||||||
|
secret = secretData,
|
||||||
|
secretHash = secretHashData,
|
||||||
|
secretIV = secretIVData,
|
||||||
|
timestamp = testTimestamp
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(msg4)
|
||||||
|
assertEquals("{"
|
||||||
|
+ "\"reportID\":\"<report-id>\","
|
||||||
|
+ "\"quoteStatus\":\"OK\","
|
||||||
|
+ "\"quoteBody\":\"$quoteBodyBase64\","
|
||||||
|
+ "\"aesCMAC\":\"$cmacBase64\","
|
||||||
|
+ "\"secret\":\"$secretBase64\","
|
||||||
|
+ "\"secretHash\":\"$secretHashBase64\","
|
||||||
|
+ "\"secretIV\":\"$secretIVBase64\","
|
||||||
|
+ "\"timestamp\":\"$iso8601Time\""
|
||||||
|
+ "}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFullSerialise() {
|
||||||
|
val msg4 = Message4(
|
||||||
|
reportID = "<report-id>",
|
||||||
|
quoteStatus = "GROUP_OUT_OF_DATE",
|
||||||
|
quoteBody = quoteBodyData,
|
||||||
|
aesCMAC = cmacData,
|
||||||
|
securityManifestStatus = "INVALID",
|
||||||
|
securityManifestHash = "<hash-value>",
|
||||||
|
platformInfo = platformInfoData,
|
||||||
|
epidPseudonym = pseudonymData,
|
||||||
|
nonce = "<nonce-value>",
|
||||||
|
secret = secretData,
|
||||||
|
secretHash = secretHashData,
|
||||||
|
secretIV = secretIVData,
|
||||||
|
timestamp = testTimestamp
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(msg4)
|
||||||
|
assertEquals("{"
|
||||||
|
+ "\"reportID\":\"<report-id>\","
|
||||||
|
+ "\"quoteStatus\":\"GROUP_OUT_OF_DATE\","
|
||||||
|
+ "\"quoteBody\":\"$quoteBodyBase64\","
|
||||||
|
+ "\"aesCMAC\":\"$cmacBase64\","
|
||||||
|
+ "\"securityManifestStatus\":\"INVALID\","
|
||||||
|
+ "\"securityManifestHash\":\"<hash-value>\","
|
||||||
|
+ "\"platformInfo\":\"$platformInfoBase64\","
|
||||||
|
+ "\"epidPseudonym\":\"$pseudonymBase64\","
|
||||||
|
+ "\"nonce\":\"<nonce-value>\","
|
||||||
|
+ "\"secret\":\"$secretBase64\","
|
||||||
|
+ "\"secretHash\":\"$secretHashBase64\","
|
||||||
|
+ "\"secretIV\":\"$secretIVBase64\","
|
||||||
|
+ "\"timestamp\":\"$iso8601Time\""
|
||||||
|
+ "}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBasicDeserialise() {
|
||||||
|
val str = """{
|
||||||
|
"reportID":"<report-id>",
|
||||||
|
"quoteStatus":"OK",
|
||||||
|
"quoteBody":"$quoteBodyBase64",
|
||||||
|
"aesCMAC":"$cmacBase64",
|
||||||
|
"secret":"$secretBase64",
|
||||||
|
"secretHash":"$secretHashBase64",
|
||||||
|
"secretIV":"$secretIVBase64",
|
||||||
|
"timestamp":"$iso8601Time"
|
||||||
|
}"""
|
||||||
|
val msg4 = mapper.readValue(str, Message4::class.java)
|
||||||
|
assertEquals("<report-id>", msg4.reportID)
|
||||||
|
assertEquals("OK", msg4.quoteStatus)
|
||||||
|
assertArrayEquals(quoteBodyData, msg4.quoteBody)
|
||||||
|
assertArrayEquals(cmacData, msg4.aesCMAC)
|
||||||
|
assertNull(msg4.platformInfo)
|
||||||
|
assertArrayEquals(secretData, msg4.secret)
|
||||||
|
assertArrayEquals(secretHashData, msg4.secretHash)
|
||||||
|
assertArrayEquals(secretIVData, msg4.secretIV)
|
||||||
|
assertEquals(testTimestamp, msg4.timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFullDeserialise() {
|
||||||
|
val str = """{
|
||||||
|
"reportID":"<report-id>",
|
||||||
|
"quoteStatus":"GROUP_OUT_OF_DATE",
|
||||||
|
"quoteBody":"$quoteBodyBase64",
|
||||||
|
"aesCMAC":"$cmacBase64",
|
||||||
|
"securityManifestStatus":"INVALID",
|
||||||
|
"securityManifestHash":"<hash-value>",
|
||||||
|
"platformInfo":"$platformInfoBase64",
|
||||||
|
"epidPseudonym":"$pseudonymBase64",
|
||||||
|
"nonce":"<nonce-value>",
|
||||||
|
"secret":"$secretBase64",
|
||||||
|
"secretHash":"$secretHashBase64",
|
||||||
|
"secretIV":"$secretIVBase64",
|
||||||
|
"timestamp":"$iso8601Time"
|
||||||
|
}"""
|
||||||
|
val msg4 = mapper.readValue(str, Message4::class.java)
|
||||||
|
assertEquals("<report-id>", msg4.reportID)
|
||||||
|
assertEquals("GROUP_OUT_OF_DATE", msg4.quoteStatus)
|
||||||
|
assertArrayEquals(quoteBodyData, msg4.quoteBody)
|
||||||
|
assertArrayEquals(cmacData, msg4.aesCMAC)
|
||||||
|
assertEquals("INVALID", msg4.securityManifestStatus)
|
||||||
|
assertEquals("<hash-value>", msg4.securityManifestHash)
|
||||||
|
assertArrayEquals(platformInfoData, msg4.platformInfo)
|
||||||
|
assertArrayEquals(pseudonymData, msg4.epidPseudonym)
|
||||||
|
assertEquals("<nonce-value>", msg4.nonce)
|
||||||
|
assertArrayEquals(secretData, msg4.secret)
|
||||||
|
assertArrayEquals(secretHashData, msg4.secretHash)
|
||||||
|
assertArrayEquals(secretIVData, msg4.secretIV)
|
||||||
|
assertEquals(testTimestamp, msg4.timestamp)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
@file:JvmName("MessageUtils")
|
||||||
|
package net.corda.attestation.message
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
fun ByteArray.toBase64(): String = Base64.getEncoder().encodeToString(this)
|
@ -0,0 +1,49 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import net.corda.attestation.message.toBase64
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ReportProxyResponseTest {
|
||||||
|
private companion object {
|
||||||
|
private val reportData = byteArrayOf(0x51, 0x62, 0x43, 0x24, 0x75, 0x4D)
|
||||||
|
private val reportBase64 = reportData.toBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialiseBasic() {
|
||||||
|
val response = ReportProxyResponse(
|
||||||
|
signature = "<signature-data>",
|
||||||
|
certificatePath = "<certificate-path>",
|
||||||
|
report = reportData
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(response)
|
||||||
|
assertEquals("{"
|
||||||
|
+ "\"signature\":\"<signature-data>\","
|
||||||
|
+ "\"certificatePath\":\"<certificate-path>\","
|
||||||
|
+ "\"report\":\"$reportBase64\""
|
||||||
|
+ "}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialiseBasic() {
|
||||||
|
val str = """{
|
||||||
|
"signature":"<signature-data>",
|
||||||
|
"certificatePath":"<certificate-path>",
|
||||||
|
"report":"$reportBase64"
|
||||||
|
}"""
|
||||||
|
val response = mapper.readValue(str, ReportProxyResponse::class.java)
|
||||||
|
assertEquals("<signature-data>", response.signature)
|
||||||
|
assertEquals("<certificate-path>", response.certificatePath)
|
||||||
|
assertArrayEquals(reportData, response.report)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import net.corda.attestation.message.toBase64
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ReportRequestTest {
|
||||||
|
private companion object {
|
||||||
|
private val quoteData = byteArrayOf(0x41, 0x42, 0x43, 0x44, 0x45, 0x46)
|
||||||
|
private val quoteBase64 = quoteData.toBase64()
|
||||||
|
|
||||||
|
private val manifestData = byteArrayOf(0x55, 0x72, 0x19, 0x5B)
|
||||||
|
private val manifestBase64 = manifestData.toBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialise() {
|
||||||
|
val request = ReportRequest(
|
||||||
|
isvEnclaveQuote = quoteData,
|
||||||
|
pseManifest = manifestData,
|
||||||
|
nonce = "<my-nonce>"
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(request)
|
||||||
|
assertEquals("{\"isvEnclaveQuote\":\"$quoteBase64\",\"pseManifest\":\"$manifestBase64\",\"nonce\":\"<my-nonce>\"}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialiseEmpty() {
|
||||||
|
val request = ReportRequest(
|
||||||
|
isvEnclaveQuote = byteArrayOf()
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(request)
|
||||||
|
assertEquals("{\"isvEnclaveQuote\":\"\"}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialise() {
|
||||||
|
val str = """{"isvEnclaveQuote":"$quoteBase64","pseManifest":"$manifestBase64","nonce":"<my-nonce>"}"""
|
||||||
|
val request = mapper.readValue(str, ReportRequest::class.java)
|
||||||
|
assertArrayEquals(quoteData, request.isvEnclaveQuote)
|
||||||
|
assertArrayEquals(manifestData, request.pseManifest)
|
||||||
|
assertEquals("<my-nonce>", request.nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialiseQuoteOnly() {
|
||||||
|
val str = """{"isvEnclaveQuote":"$quoteBase64"}"""
|
||||||
|
val request = mapper.readValue(str, ReportRequest::class.java)
|
||||||
|
assertArrayEquals(quoteData, request.isvEnclaveQuote)
|
||||||
|
assertNull(request.pseManifest)
|
||||||
|
assertNull(request.nonce)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
|
import net.corda.attestation.message.toBase64
|
||||||
|
import net.corda.attestation.message.ias.ManifestStatus.*
|
||||||
|
import net.corda.attestation.message.ias.QuoteStatus.*
|
||||||
|
import net.corda.attestation.unsignedByteArrayOf
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class ReportResponseTest {
|
||||||
|
private companion object {
|
||||||
|
private val iso8601Time = "2017-11-08T18:19:27.123456"
|
||||||
|
private val testTimestamp = LocalDateTime.from(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS").parse(iso8601Time))
|
||||||
|
|
||||||
|
private val quoteBodyData = byteArrayOf(0x61, 0x62, 0x63, 0x64, 0x65, 0x66)
|
||||||
|
private val quoteBodyBase64 = quoteBodyData.toBase64()
|
||||||
|
|
||||||
|
private val platformInfoData = unsignedByteArrayOf(0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef)
|
||||||
|
|
||||||
|
private val pseudonymData = byteArrayOf(0x63, 0x18, 0x33, 0x72)
|
||||||
|
private val pseudonymBase64 = pseudonymData.toBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper().registerModule(JavaTimeModule())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialiseBasic() {
|
||||||
|
val response = ReportResponse(
|
||||||
|
id = "<report-id>",
|
||||||
|
isvEnclaveQuoteStatus = QuoteStatus.OK,
|
||||||
|
isvEnclaveQuoteBody = quoteBodyData,
|
||||||
|
timestamp = testTimestamp
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(response)
|
||||||
|
assertEquals("{"
|
||||||
|
+ "\"id\":\"<report-id>\","
|
||||||
|
+ "\"timestamp\":\"$iso8601Time\","
|
||||||
|
+ "\"isvEnclaveQuoteStatus\":\"OK\","
|
||||||
|
+ "\"isvEnclaveQuoteBody\":\"$quoteBodyBase64\""
|
||||||
|
+ "}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialiseFull() {
|
||||||
|
val response = ReportResponse(
|
||||||
|
id = "<report-id>",
|
||||||
|
isvEnclaveQuoteStatus = GROUP_OUT_OF_DATE,
|
||||||
|
isvEnclaveQuoteBody = quoteBodyData,
|
||||||
|
platformInfoBlob = platformInfoData,
|
||||||
|
revocationReason = 1,
|
||||||
|
pseManifestStatus = INVALID,
|
||||||
|
pseManifestHash = "<manifest-hash>",
|
||||||
|
nonce = "<nonce>",
|
||||||
|
epidPseudonym = pseudonymData,
|
||||||
|
timestamp = testTimestamp
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(response)
|
||||||
|
assertEquals("{"
|
||||||
|
+ "\"nonce\":\"<nonce>\","
|
||||||
|
+ "\"id\":\"<report-id>\","
|
||||||
|
+ "\"timestamp\":\"$iso8601Time\","
|
||||||
|
+ "\"epidPseudonym\":\"$pseudonymBase64\","
|
||||||
|
+ "\"isvEnclaveQuoteStatus\":\"GROUP_OUT_OF_DATE\","
|
||||||
|
+ "\"isvEnclaveQuoteBody\":\"$quoteBodyBase64\","
|
||||||
|
+ "\"pseManifestStatus\":\"INVALID\","
|
||||||
|
+ "\"pseManifestHash\":\"<manifest-hash>\","
|
||||||
|
+ "\"platformInfoBlob\":\"123456789abcdef\","
|
||||||
|
+ "\"revocationReason\":1"
|
||||||
|
+ "}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialiseBasic() {
|
||||||
|
val str = """{
|
||||||
|
"id":"<report-id>",
|
||||||
|
"isvEnclaveQuoteStatus":"OK",
|
||||||
|
"isvEnclaveQuoteBody":"$quoteBodyBase64",
|
||||||
|
"timestamp":"$iso8601Time"
|
||||||
|
}"""
|
||||||
|
val response = mapper.readValue(str, ReportResponse::class.java)
|
||||||
|
assertEquals("<report-id>", response.id)
|
||||||
|
assertEquals(QuoteStatus.OK, response.isvEnclaveQuoteStatus)
|
||||||
|
assertArrayEquals(quoteBodyData, response.isvEnclaveQuoteBody)
|
||||||
|
assertNull(response.platformInfoBlob)
|
||||||
|
assertNull(response.revocationReason)
|
||||||
|
assertNull(response.pseManifestStatus)
|
||||||
|
assertNull(response.pseManifestHash)
|
||||||
|
assertNull(response.nonce)
|
||||||
|
assertNull(response.epidPseudonym)
|
||||||
|
assertEquals(testTimestamp, response.timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialiseFull() {
|
||||||
|
val str = """{
|
||||||
|
"id":"<report-id>",
|
||||||
|
"isvEnclaveQuoteStatus":"GROUP_OUT_OF_DATE",
|
||||||
|
"isvEnclaveQuoteBody":"$quoteBodyBase64",
|
||||||
|
"platformInfoBlob":"0123456789ABCDEF",
|
||||||
|
"revocationReason":1,
|
||||||
|
"pseManifestStatus":"OK",
|
||||||
|
"pseManifestHash":"<manifest-hash>",
|
||||||
|
"nonce":"<nonce>",
|
||||||
|
"epidPseudonym":"$pseudonymBase64",
|
||||||
|
"timestamp":"$iso8601Time"
|
||||||
|
}"""
|
||||||
|
val response = mapper.readValue(str, ReportResponse::class.java)
|
||||||
|
assertEquals("<report-id>", response.id)
|
||||||
|
assertEquals(QuoteStatus.GROUP_OUT_OF_DATE, response.isvEnclaveQuoteStatus)
|
||||||
|
assertArrayEquals(quoteBodyData, response.isvEnclaveQuoteBody)
|
||||||
|
assertArrayEquals(platformInfoData, response.platformInfoBlob)
|
||||||
|
assertEquals(1, response.revocationReason)
|
||||||
|
assertEquals(ManifestStatus.OK, response.pseManifestStatus)
|
||||||
|
assertEquals("<manifest-hash>", response.pseManifestHash)
|
||||||
|
assertEquals("<nonce>", response.nonce)
|
||||||
|
assertArrayEquals(pseudonymData, response.epidPseudonym)
|
||||||
|
assertEquals(testTimestamp, response.timestamp)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package net.corda.attestation.message.ias
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import net.corda.attestation.message.toBase64
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RevocationListProxyResponseTest {
|
||||||
|
private companion object {
|
||||||
|
private const val SPID = "84D402C36BA9EF9B0A86EF1A9CC8CE4F"
|
||||||
|
private val revocationListData = byteArrayOf(0x51, 0x62, 0x43, 0x24, 0x75, 0x4D)
|
||||||
|
private val revocationListBase64 = revocationListData.toBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mapper: ObjectMapper
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mapper = ObjectMapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSerialiseBasic() {
|
||||||
|
val response = RevocationListProxyResponse(
|
||||||
|
spid = SPID,
|
||||||
|
revocationList = revocationListData
|
||||||
|
)
|
||||||
|
val str = mapper.writeValueAsString(response)
|
||||||
|
assertEquals("{"
|
||||||
|
+ "\"spid\":\"$SPID\","
|
||||||
|
+ "\"revocationList\":\"$revocationListBase64\""
|
||||||
|
+ "}", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeserialiseBasic() {
|
||||||
|
val str = """{
|
||||||
|
"spid":"$SPID",
|
||||||
|
"revocationList":"$revocationListBase64"
|
||||||
|
}"""
|
||||||
|
val response = mapper.readValue(str, RevocationListProxyResponse::class.java)
|
||||||
|
assertEquals(SPID, response.spid)
|
||||||
|
assertArrayEquals(revocationListData, response.revocationList)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package net.corda.mockias.io
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.Signature
|
||||||
|
|
||||||
|
class SignatureOutputStreamTest {
|
||||||
|
|
||||||
|
private lateinit var output: ByteArrayOutputStream
|
||||||
|
private lateinit var signedOutput: SignatureOutputStream
|
||||||
|
private lateinit var reference: Signature
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
|
||||||
|
val keyPair = keyPairGenerator.genKeyPair()
|
||||||
|
val signature = Signature.getInstance("SHA256withRSA").apply {
|
||||||
|
initSign(keyPair.private)
|
||||||
|
}
|
||||||
|
output = ByteArrayOutputStream()
|
||||||
|
signedOutput = SignatureOutputStream(output, signature)
|
||||||
|
reference = Signature.getInstance("SHA256withRSA").apply {
|
||||||
|
initSign(keyPair.private)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSignValue() {
|
||||||
|
signedOutput.write(-0x74)
|
||||||
|
signedOutput.write(0x00)
|
||||||
|
signedOutput.write(0x11)
|
||||||
|
reference.update(ByteArrayOutputStream().let { baos ->
|
||||||
|
baos.write(-0x74)
|
||||||
|
baos.write(0x00)
|
||||||
|
baos.write(0x11)
|
||||||
|
baos.toByteArray()
|
||||||
|
})
|
||||||
|
assertArrayEquals(byteArrayOf(-0x74, 0x00, 0x11), output.toByteArray())
|
||||||
|
assertArrayEquals(reference.sign(), signedOutput.sign())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSignBuffer() {
|
||||||
|
val buffer = byteArrayOf(0x01, -0x7F, 0x64, -0x52, 0x00)
|
||||||
|
signedOutput.write(buffer)
|
||||||
|
reference.update(buffer)
|
||||||
|
assertArrayEquals(buffer, output.toByteArray())
|
||||||
|
assertArrayEquals(reference.sign(), signedOutput.sign())
|
||||||
|
}
|
||||||
|
}
|
63
sgx-jvm/remote-attestation/build.gradle
Normal file
63
sgx-jvm/remote-attestation/build.gradle
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
buildscript {
|
||||||
|
// For sharing constants between builds
|
||||||
|
Properties constants = new Properties()
|
||||||
|
file("../../constants.properties").withInputStream { constants.load(it) }
|
||||||
|
|
||||||
|
ext.kotlin_version = constants.getProperty("kotlinVersion")
|
||||||
|
ext.bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
||||||
|
ext.resteasy_version = '3.1.4.Final'
|
||||||
|
ext.jackson_version = '2.9.2'
|
||||||
|
ext.slf4j_version = '1.7.25'
|
||||||
|
ext.log4j_version = '2.9.1'
|
||||||
|
ext.junit_version = '4.12'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
tasks.withType(KotlinCompile).all {
|
||||||
|
kotlinOptions {
|
||||||
|
languageVersion = "1.1"
|
||||||
|
apiVersion = "1.1"
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
javaParameters = true // Useful for reflection.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(Test) {
|
||||||
|
// Prevent the project from creating temporary files outside of the build directory.
|
||||||
|
systemProperties['java.io.tmpdir'] = buildDir
|
||||||
|
}
|
||||||
|
|
||||||
|
group 'com.r3.corda.enterprise'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compile {
|
||||||
|
// We want to use SLF4J's version of these bindings: jcl-over-slf4j
|
||||||
|
// Remove any transitive dependency on Apache's version.
|
||||||
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task wrapper(type: Wrapper) {
|
||||||
|
gradleVersion = "4.3.1"
|
||||||
|
}
|
BIN
sgx-jvm/remote-attestation/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
sgx-jvm/remote-attestation/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
sgx-jvm/remote-attestation/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
sgx-jvm/remote-attestation/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
|
172
sgx-jvm/remote-attestation/gradlew
vendored
Executable file
172
sgx-jvm/remote-attestation/gradlew
vendored
Executable file
@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
84
sgx-jvm/remote-attestation/gradlew.bat
vendored
Normal file
84
sgx-jvm/remote-attestation/gradlew.bat
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
2
sgx-jvm/remote-attestation/settings.gradle
Normal file
2
sgx-jvm/remote-attestation/settings.gradle
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
rootProject.name = 'remote-attestation'
|
||||||
|
include 'attestation-server'
|
Loading…
Reference in New Issue
Block a user