Skip to content

Commit ef475ae

Browse files
authored
[CON-1245] Add support for Intel reference PCCS
# Changes - Fix bug that caused corrupted binary encoded CRLs/Certs. - Refactor quote JSON parsing so that it supports multiple versions. - Add checks to `QuoteVerifier.kt` to exclude TDX.
1 parent ded9f57 commit ef475ae

File tree

14 files changed

+700
-327
lines changed

14 files changed

+700
-327
lines changed

conclave-common/src/main/kotlin/com/r3/conclave/common/internal/attestation/AttestationJsonTypes.kt

Lines changed: 274 additions & 116 deletions
Large diffs are not rendered by default.

conclave-common/src/main/kotlin/com/r3/conclave/common/internal/attestation/QuoteCollateral.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,15 @@ data class QuoteCollateral(
2727

2828
val tcbInfoIssuerChain: CertPath by lazy { parseCertPath(rawTcbInfoIssuerChain) }
2929

30-
val signedTcbInfo: SignedTcbInfo by lazy { parseJson(rawSignedTcbInfo) }
30+
val signedTcbInfo: SignedTcbInfo by lazy {
31+
SignedTcbInfo.fromJson(attestationObjectMapper.readTree(rawSignedTcbInfo.inputStream()))
32+
}
3133

3234
val qeIdentityIssuerChain: CertPath by lazy { parseCertPath(rawQeIdentityIssuerChain) }
3335

34-
val signedQeIdentity: SignedEnclaveIdentity by lazy { parseJson(rawSignedQeIdentity) }
36+
val signedQeIdentity: SignedEnclaveIdentity by lazy {
37+
SignedEnclaveIdentity.fromJson(attestationObjectMapper.readTree(rawSignedQeIdentity.inputStream()))
38+
}
3539

3640
private fun parseCertPath(bytes: OpaqueBytes): CertPath {
3741
return AttestationUtils.parsePemCertPath(bytes.inputStream())
@@ -41,10 +45,6 @@ data class QuoteCollateral(
4145
return CertificateFactory.getInstance("X.509").generateCRL(bytes.inputStream()) as X509CRL
4246
}
4347

44-
private inline fun <reified T> parseJson(bytes: OpaqueBytes): T {
45-
return attestationObjectMapper.readValue(bytes.inputStream(), T::class.java)
46-
}
47-
4848
fun serialiseTo(dos: DataOutputStream) {
4949
// We need to serialise the version as a length prefixed string to maintain backwards compatibility.
5050
dos.writeIntLengthPrefixBytes(version.toString().toByteArray())

conclave-common/src/main/kotlin/com/r3/conclave/common/internal/attestation/QuoteVerifier.kt

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -445,13 +445,21 @@ object QuoteVerifier {
445445
return EnclaveTcbStatus.Revoked
446446
}
447447

448-
private fun getMatchingTcbLevel(pckExtensions: SGXExtensionASN1Parser, tcbInfo: TcbInfo): TcbStatus {
448+
private fun getMatchingTcbLevel(pckExtensions: SGXExtensionASN1Parser, tcbInfo: TcbInfo): TcbLevel {
449449
val pckTcbs = IntArray(16) { pckExtensions.getInt("${SGX_TCB_OID}.${it + 1}") }
450450
val pckPceSvn = pckExtensions.getInt(SGX_PCESVN_OID)
451451

452-
for (lvl in tcbInfo.tcbLevels) {
453-
if (isCpuSvnHigherOrEqual(pckTcbs, lvl.tcb) && pckPceSvn >= lvl.tcb.pcesvn) {
454-
return lvl.tcbStatus
452+
/**
453+
* Ignore TDX quotes for now
454+
* TODO: CON-1273, Audit changes to the intel QVL
455+
*/
456+
require(tcbInfo.id != TypeID.TDX) {
457+
"TDX quotes are not supported."
458+
}
459+
460+
for (tcbLevel in tcbInfo.tcbLevels) {
461+
if (isCpuSvnHigherOrEqual(pckTcbs, tcbLevel.tcb) && pckPceSvn >= tcbLevel.tcb.pcesvn) {
462+
return tcbLevel
455463
}
456464
}
457465

@@ -461,41 +469,25 @@ object QuoteVerifier {
461469
private fun isCpuSvnHigherOrEqual(pckTcb: IntArray, jsonTcb: Tcb): Boolean {
462470
for (j in pckTcb.indices) {
463471
// If *ANY* CPUSVN component is lower then CPUSVN is considered lower
464-
if (pckTcb[j] < getSgxTcbComponentSvn(jsonTcb, j)) return false
472+
if (pckTcb[j] < jsonTcb.sgxtcbcompsvn[j]) return false
465473
}
466474
// but for CPUSVN to be considered higher it requires that *EVERY* CPUSVN component to be higher or equal
467475
return true
468476
}
469477

470478
private fun checkTcbLevel(pckExtensions: SGXExtensionASN1Parser, tcbInfo: TcbInfo): TcbStatus {
471-
val tcbLevelStatus = getMatchingTcbLevel(pckExtensions, tcbInfo)
472-
check(tcbInfo.version == 2 || tcbLevelStatus != TcbStatus.OutOfDateConfigurationNeeded) {
473-
"TCB_UNRECOGNIZED_STATUS"
479+
val tcbLevel = getMatchingTcbLevel(pckExtensions, tcbInfo)
480+
481+
/**
482+
* This check will currently always pass, but is added for safety in case of future modifications.
483+
* Logic comes from the Intel QVL
484+
* TODO: CON-1273, Audit changes to the intel QVL
485+
*/
486+
check (!(tcbLevel.tcbStatus == TcbStatus.OutOfDateConfigurationNeeded && tcbInfo.version.id < 2)) {
487+
"TCB_UNRCOGNISED_STATUS"
474488
}
475-
return tcbLevelStatus
476-
}
477489

478-
private fun getSgxTcbComponentSvn(tcb: Tcb, index: Int): Int {
479-
// 0 base index
480-
return when (index) {
481-
0 -> tcb.sgxtcbcomp01svn
482-
1 -> tcb.sgxtcbcomp02svn
483-
2 -> tcb.sgxtcbcomp03svn
484-
3 -> tcb.sgxtcbcomp04svn
485-
4 -> tcb.sgxtcbcomp05svn
486-
5 -> tcb.sgxtcbcomp06svn
487-
6 -> tcb.sgxtcbcomp07svn
488-
7 -> tcb.sgxtcbcomp08svn
489-
8 -> tcb.sgxtcbcomp09svn
490-
9 -> tcb.sgxtcbcomp10svn
491-
10 -> tcb.sgxtcbcomp11svn
492-
11 -> tcb.sgxtcbcomp12svn
493-
12 -> tcb.sgxtcbcomp13svn
494-
13 -> tcb.sgxtcbcomp14svn
495-
14 -> tcb.sgxtcbcomp15svn
496-
15 -> tcb.sgxtcbcomp16svn
497-
else -> throw GeneralSecurityException("Invalid sgxtcbcompsvn index $index")
498-
}
490+
return tcbLevel.tcbStatus
499491
}
500492

501493
private fun verify(check: Boolean, status: ErrorStatus) {

conclave-common/src/test/kotlin/com/r3/conclave/common/internal/attestation/AttestationJsonTypesTest.kt

Lines changed: 45 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
package com.r3.conclave.common.internal.attestation
22

3+
import com.fasterxml.jackson.databind.JsonNode
34
import com.fasterxml.jackson.databind.ObjectMapper
45
import com.r3.conclave.common.OpaqueBytes
56
import com.r3.conclave.common.SHA256Hash
67
import com.r3.conclave.common.internal.Cursor
78
import com.r3.conclave.common.internal.SgxQuote
89
import org.assertj.core.api.Assertions.assertThat
910
import org.junit.jupiter.api.Test
11+
import org.junit.jupiter.params.ParameterizedTest
12+
import org.junit.jupiter.params.provider.EnumSource
13+
import org.junit.jupiter.params.provider.MethodSource
14+
import java.io.IOException
1015
import java.time.Instant
1116
import java.time.ZoneOffset.UTC
1217
import java.time.ZonedDateTime
1318
import java.util.*
1419
import kotlin.random.Random
1520

21+
22+
private val mapper = ObjectMapper()
23+
24+
/** Load a resource as text, throw if the resource is missing. */
25+
fun loadResourceAsJson(path: String): JsonNode {
26+
val text = SignedTcbInfoTest::class.java.getResource(path)?.readText() ?: throw IOException("Missing resource: $path")
27+
return mapper.readTree(text)
28+
}
29+
1630
class EpidVerificationReportTest {
1731
@Test
1832
fun `serialise minimum fields`() {
@@ -144,101 +158,45 @@ class EpidVerificationReportTest {
144158

145159

146160
class SignedTcbInfoTest {
147-
@Test
148-
fun `deserialise tcb`() {
149-
val json = """
150-
{
151-
"sgxtcbcomp01svn":1,
152-
"sgxtcbcomp02svn":2,
153-
"sgxtcbcomp03svn":3,
154-
"sgxtcbcomp04svn":4,
155-
"sgxtcbcomp05svn":5,
156-
"sgxtcbcomp06svn":6,
157-
"sgxtcbcomp07svn":7,
158-
"sgxtcbcomp08svn":8,
159-
"sgxtcbcomp09svn":9,
160-
"sgxtcbcomp10svn":10,
161-
"sgxtcbcomp11svn":11,
162-
"sgxtcbcomp12svn":12,
163-
"sgxtcbcomp13svn":13,
164-
"sgxtcbcomp14svn":14,
165-
"sgxtcbcomp15svn":15,
166-
"sgxtcbcomp16svn":16,
167-
"pcesvn":9
168-
}
169-
""".trimIndent()
170-
val result = attestationObjectMapper.readValue(json, Tcb::class.java)
171-
assertThat(1).isEqualTo(result.sgxtcbcomp01svn)
161+
@ParameterizedTest
162+
@EnumSource(TcbInfo.Version::class)
163+
fun `tcb deserialization test`(version: TcbInfo.Version) {
164+
val json = loadResourceAsJson("test_tcb_${version}.json")
165+
val result = Tcb.fromJson(json, version)
166+
167+
for (i in 0 until 16) {
168+
assertThat(result.sgxtcbcompsvn[i]).isEqualTo(i + 1)
169+
}
170+
171+
assertThat(result.pcesvn).isEqualTo(9)
172172
}
173173

174-
@Test
175-
fun `deserialise signature`() {
176-
val signature = "01020304"
177-
val json = """
178-
{
179-
"tcbInfo":{"version":2,"issueDate":"2020-01-02T03:04:05Z","nextUpdate":"2021-02-03T04:05:06Z","fmspc":"00906ed50000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":7,
180-
"tcbLevels":[]
181-
},
182-
"signature":"$signature"
183-
}
184-
""".trimIndent()
185-
val result = attestationObjectMapper.readValue(json, SignedTcbInfo::class.java)
186-
assertThat(result.signature).isEqualTo(OpaqueBytes.parse(signature))
174+
@ParameterizedTest
175+
@EnumSource(TcbInfo.Version::class)
176+
fun `signature deserialization test`(version: TcbInfo.Version) {
177+
val json = loadResourceAsJson("test_tcbinfo_${version}.json")
178+
val result = SignedTcbInfo.fromJson(json)
179+
assertThat(result.signature).isEqualTo(OpaqueBytes.parse("01020304")) // Matches resource content
187180
}
188181

189-
@Test
190-
fun `deserialise tcb info`() {
191-
val signature =
192-
"2fcfea244996d64794c3729acff632887de67722cfca7b0458464a74d4101d01879fe28fa01594f28c6e0e97e9558ff0a45898bd6af275e8edffc2364780fe06"
193-
val json = """
194-
{
195-
"tcbInfo":{
196-
"version":2,"issueDate":"2020-01-02T03:04:05Z","nextUpdate":"2021-02-03T04:05:06Z","fmspc":"00906ed50000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":7,
197-
"tcbLevels":[
198-
{"tcb":{"sgxtcbcomp01svn":1,"sgxtcbcomp02svn":2,"sgxtcbcomp03svn":3,"sgxtcbcomp04svn":4,"sgxtcbcomp05svn":5,"sgxtcbcomp06svn":6,"sgxtcbcomp07svn":7,"sgxtcbcomp08svn":8,"sgxtcbcomp09svn":9,"sgxtcbcomp10svn":10,"sgxtcbcomp11svn":11,"sgxtcbcomp12svn":12,"sgxtcbcomp13svn":13,"sgxtcbcomp14svn":14,"sgxtcbcomp15svn":15,"sgxtcbcomp16svn":16,"pcesvn":9},"tcbDate":"2019-11-13T00:00:00Z","tcbStatus":"UpToDate"},
199-
{"tcb":{"sgxtcbcomp01svn":13,"sgxtcbcomp02svn":13,"sgxtcbcomp03svn":2,"sgxtcbcomp04svn":4,"sgxtcbcomp05svn":1,"sgxtcbcomp06svn":128,"sgxtcbcomp07svn":0,"sgxtcbcomp08svn":0,"sgxtcbcomp09svn":0,"sgxtcbcomp10svn":0,"sgxtcbcomp11svn":0,"sgxtcbcomp12svn":0,"sgxtcbcomp13svn":0,"sgxtcbcomp14svn":0,"sgxtcbcomp15svn":0,"sgxtcbcomp16svn":0,"pcesvn":9},"tcbDate":"2019-11-13T00:00:00Z","tcbStatus":"ConfigurationNeeded"},
200-
{"tcb":{"sgxtcbcomp01svn":2,"sgxtcbcomp02svn":2,"sgxtcbcomp03svn":2,"sgxtcbcomp04svn":4,"sgxtcbcomp05svn":1,"sgxtcbcomp06svn":128,"sgxtcbcomp07svn":0,"sgxtcbcomp08svn":0,"sgxtcbcomp09svn":0,"sgxtcbcomp10svn":0,"sgxtcbcomp11svn":0,"sgxtcbcomp12svn":0,"sgxtcbcomp13svn":0,"sgxtcbcomp14svn":0,"sgxtcbcomp15svn":0,"sgxtcbcomp16svn":0,"pcesvn":7},"tcbDate":"2019-05-15T00:00:00Z","tcbStatus":"OutOfDate"},
201-
{"tcb":{"sgxtcbcomp01svn":1,"sgxtcbcomp02svn":1,"sgxtcbcomp03svn":2,"sgxtcbcomp04svn":4,"sgxtcbcomp05svn":1,"sgxtcbcomp06svn":128,"sgxtcbcomp07svn":0,"sgxtcbcomp08svn":0,"sgxtcbcomp09svn":0,"sgxtcbcomp10svn":0,"sgxtcbcomp11svn":0,"sgxtcbcomp12svn":0,"sgxtcbcomp13svn":0,"sgxtcbcomp14svn":0,"sgxtcbcomp15svn":0,"sgxtcbcomp16svn":0,"pcesvn":7},"tcbDate":"2019-01-09T00:00:00Z","tcbStatus":"OutOfDate"},
202-
{"tcb":{"sgxtcbcomp01svn":1,"sgxtcbcomp02svn":1,"sgxtcbcomp03svn":2,"sgxtcbcomp04svn":4,"sgxtcbcomp05svn":1,"sgxtcbcomp06svn":128,"sgxtcbcomp07svn":0,"sgxtcbcomp08svn":0,"sgxtcbcomp09svn":0,"sgxtcbcomp10svn":0,"sgxtcbcomp11svn":0,"sgxtcbcomp12svn":0,"sgxtcbcomp13svn":0,"sgxtcbcomp14svn":0,"sgxtcbcomp15svn":0,"sgxtcbcomp16svn":0,"pcesvn":6},"tcbDate":"2018-08-15T00:00:00Z","tcbStatus":"OutOfDate"}
203-
]
204-
},
205-
"signature":"$signature"
206-
}
207-
""".trimIndent()
208-
val result = attestationObjectMapper.readValue(json, SignedTcbInfo::class.java)
209-
assertThat(result.signature).isEqualTo(OpaqueBytes.parse(signature))
210-
assertThat(result.tcbInfo.version).isEqualTo(2)
182+
@ParameterizedTest
183+
@EnumSource(TcbInfo.Version::class)
184+
fun `tcb info deserialization test`(version: TcbInfo.Version) {
185+
val json = loadResourceAsJson("test_tcbinfo_$version.json")
186+
val result = SignedTcbInfo.fromJson(json)
187+
assertThat(result.signature).isEqualTo(OpaqueBytes.parse("01020304"))
188+
assertThat(result.tcbInfo.version).isEqualTo(version)
211189
assertThat(result.tcbInfo.issueDate).isEqualTo(Instant.parse("2020-01-02T03:04:05Z"))
212190
assertThat(result.tcbInfo.nextUpdate).isEqualTo(Instant.parse("2021-02-03T04:05:06Z"))
213191
}
214192
}
215193

216194
class SignedEnclaveIdentityTest {
217-
@Test
218-
fun `deserialise signature`() {
219-
val signature = "01020304"
220-
val json = """
221-
{
222-
"enclaveIdentity":{
223-
"id":"QE",
224-
"version":2,
225-
"issueDate":"2019-09-05T07:47:08Z",
226-
"nextUpdate":"2029-09-05T07:47:08Z",
227-
"tcbEvaluationDataNumber":0,
228-
"miscselect":"D182B18C",
229-
"miscselectMask":"FFFFFFFF",
230-
"attributes":"70C8CBF48BD76EAB9C8126CE95E96C90",
231-
"attributesMask":"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
232-
"mrsigner":"8C4F5775D796503E96137F77C68A829A0056AC8DED70140B081B094490C57BFF",
233-
"isvprodid":1,
234-
"tcbLevels":[
235-
{"tcb":{"isvsvn":1},"tcbDate":"2019-09-01T00:00:00Z","tcbStatus":"UpToDate"}
236-
]
237-
},
238-
"signature":"$signature"
239-
}
240-
""".trimIndent()
241-
val result = attestationObjectMapper.readValue(json, SignedEnclaveIdentity::class.java)
242-
assertThat(result.signature).isEqualTo(OpaqueBytes.parse(signature))
195+
@ParameterizedTest
196+
@EnumSource(EnclaveIdentity.Version::class)
197+
fun `signature deserializes correctly`(version: EnclaveIdentity.Version) {
198+
val json = loadResourceAsJson("test_signed_enclave_identity_$version.json")
199+
val result = SignedEnclaveIdentity.fromJson(json)
200+
assertThat(result.signature).isEqualTo(OpaqueBytes.parse("01020304"))
243201
}
244-
}
202+
}

0 commit comments

Comments
 (0)