Skip to content

Commit 4d8dd46

Browse files
cYKatherineRelease Workflow
andauthored
UID2-4808 Add AKS protocol for AzureCCCoreAttestationService (#374)
* Add paraent class for `AzureCCCoreAttestationService` * Fix UT * Change log info for different azureCcProtocol * Remove wrong comment * Add unit tests for policy validator * [CI Pipeline] Released Snapshot version: 8.0.33-alpha-190-SNAPSHOT * Remove parent child hierarchy * Add debug message * [CI Pipeline] Released Snapshot version: 8.0.34-alpha-191-SNAPSHOT * Change constant variable to AZURE_CC_ACI_PROTOCOL * Rename jwt_payload_aci.json file * Rename testAciPayload UT * Use ParameterizedTest to test payload * Use String.format to format log message * Use logback parameterized logging * Throw AttestationClientException when protocol not valid * Refactor MaaTokenSignatureValidatorTest * Use @MethodSource("argumentProvider") * Replace string with MaaTokenPayload contant * Fix typo --------- Co-authored-by: Release Workflow <[email protected]>
1 parent 4c93129 commit 4d8dd46

File tree

11 files changed

+180
-41
lines changed

11 files changed

+180
-41
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.uid2</groupId>
77
<artifactId>uid2-shared</artifactId>
8-
<version>8.0.32</version>
8+
<version>8.0.34-alpha-191-SNAPSHOT</version>
99
<name>${project.groupId}:${project.artifactId}</name>
1010
<description>Library for all the shared uid2 operations</description>
1111
<url>https://github.com/IABTechLab/uid2docs</url>

src/main/java/com/uid2/shared/secure/AzureCCCoreAttestationService.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,38 @@ public class AzureCCCoreAttestationService implements ICoreAttestationService {
2525

2626
private final IPolicyValidator policyValidator;
2727

28-
public AzureCCCoreAttestationService(String maaServerBaseUrl, String attestationUrl) {
29-
this(new MaaTokenSignatureValidator(maaServerBaseUrl), new PolicyValidator(attestationUrl));
28+
private final String azureCcProtocol;
29+
30+
public AzureCCCoreAttestationService(String maaServerBaseUrl, String attestationUrl, String azureCcProtocol) {
31+
this(new MaaTokenSignatureValidator(maaServerBaseUrl), new PolicyValidator(attestationUrl), azureCcProtocol);
3032
}
3133

3234
// used in UT
33-
protected AzureCCCoreAttestationService(IMaaTokenSignatureValidator tokenSignatureValidator, IPolicyValidator policyValidator) {
35+
protected AzureCCCoreAttestationService(IMaaTokenSignatureValidator tokenSignatureValidator, IPolicyValidator policyValidator, String azureCcProtocol) {
3436
this.tokenSignatureValidator = tokenSignatureValidator;
3537
this.policyValidator = policyValidator;
38+
this.azureCcProtocol = azureCcProtocol;
3639
}
3740

3841
@Override
3942
public void attest(byte[] attestationRequest, byte[] publicKey, Handler<AsyncResult<AttestationResult>> handler) {
4043
try {
4144
var tokenString = new String(attestationRequest, StandardCharsets.US_ASCII);
4245

46+
log.debug("Attesting for {} operator...", azureCcProtocol);
4347
log.debug("Validating signature...");
44-
var tokenPayload = tokenSignatureValidator.validate(tokenString);
48+
var tokenPayload = tokenSignatureValidator.validate(tokenString, azureCcProtocol);
4549

4650
log.debug("Validating policy...");
4751
var encodedPublicKey = Utils.toBase64String(publicKey);
4852

4953
var enclaveId = policyValidator.validate(tokenPayload, encodedPublicKey);
5054

5155
if (allowedEnclaveIds.contains(enclaveId)) {
52-
log.info("Successfully attested azure-cc against registered enclaves, enclave id: " + enclaveId);
56+
log.info("Successfully attested {} against registered enclaves, enclave id: {}", azureCcProtocol, enclaveId);
5357
handler.handle(Future.succeededFuture(new AttestationResult(publicKey, enclaveId)));
5458
} else {
55-
log.warn("Got unsupported azure-cc enclave id: " + enclaveId);
59+
log.warn("Got unsupported {} enclave id: {}", azureCcProtocol, enclaveId);
5660
handler.handle(Future.succeededFuture(new AttestationResult(AttestationFailure.FORBIDDEN_ENCLAVE)));
5761
}
5862
}

src/main/java/com/uid2/shared/secure/azurecc/IMaaTokenSignatureValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ public interface IMaaTokenSignatureValidator {
1010
* @return Parsed token payload.
1111
* @throws AttestationException
1212
*/
13-
MaaTokenPayload validate(String tokenString) throws AttestationException;
13+
MaaTokenPayload validate(String tokenString, String protocol) throws AttestationException;
1414
}

src/main/java/com/uid2/shared/secure/azurecc/MaaTokenPayload.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
package com.uid2.shared.secure.azurecc;
22

3+
import com.uid2.shared.secure.AttestationClientException;
4+
import com.uid2.shared.secure.AttestationException;
5+
import com.uid2.shared.secure.AttestationFailure;
36
import lombok.Builder;
47
import lombok.Value;
58

69
@Value
710
@Builder(toBuilder = true)
811
public class MaaTokenPayload {
912
public static final String SEV_SNP_VM_TYPE = "sevsnpvm";
13+
public static final String AZURE_CC_ACI_PROTOCOL = "azure-cc";
14+
public static final String AZURE_CC_AKS_PROTOCOL = "azure-cc-aks";
15+
// the `x-ms-compliance-status` value for ACI CC
1016
public static final String AZURE_COMPLIANT_UVM = "azure-compliant-uvm";
17+
// the `x-ms-compliance-status` value for AKS CC
18+
public static final String AZURE_COMPLIANT_UVM_AKS = "azure-signed-katacc-uvm";
1119

20+
private String azureProtocol;
1221
private String attestationType;
1322
private String complianceStatus;
1423
private boolean vmDebuggable;
@@ -20,7 +29,13 @@ public boolean isSevSnpVM(){
2029
return SEV_SNP_VM_TYPE.equalsIgnoreCase(attestationType);
2130
}
2231

23-
public boolean isUtilityVMCompliant(){
24-
return AZURE_COMPLIANT_UVM.equalsIgnoreCase(complianceStatus);
32+
public boolean isUtilityVMCompliant() throws AttestationClientException {
33+
if (azureProtocol == AZURE_CC_ACI_PROTOCOL) {
34+
return AZURE_COMPLIANT_UVM.equalsIgnoreCase(complianceStatus);
35+
} else if (azureProtocol == AZURE_CC_AKS_PROTOCOL) {
36+
return AZURE_COMPLIANT_UVM_AKS.equalsIgnoreCase(complianceStatus);
37+
} else {
38+
throw new AttestationClientException(String.format("Azure protocol: %s not supported", azureProtocol), AttestationFailure.INVALID_PROTOCOL);
39+
}
2540
}
2641
}

src/main/java/com/uid2/shared/secure/azurecc/MaaTokenSignatureValidator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import static com.uid2.shared.secure.JwtUtils.tryGetField;
1616

1717
public class MaaTokenSignatureValidator implements IMaaTokenSignatureValidator {
18-
1918
// set to true to facilitate local test.
2019
public static final boolean BYPASS_SIGNATURE_CHECK = false;
2120

@@ -52,7 +51,7 @@ private TokenVerifier buildTokenVerifier(String kid) throws AttestationException
5251
}
5352

5453
@Override
55-
public MaaTokenPayload validate(String tokenString) throws AttestationException {
54+
public MaaTokenPayload validate(String tokenString, String protocol) throws AttestationException {
5655
if (Strings.isNullOrEmpty(tokenString)) {
5756
throw new IllegalArgumentException("tokenString can not be null or empty");
5857
}
@@ -77,6 +76,7 @@ public MaaTokenPayload validate(String tokenString) throws AttestationException
7776

7877
var tokenPayloadBuilder = MaaTokenPayload.builder();
7978

79+
tokenPayloadBuilder.azureProtocol(protocol);
8080
tokenPayloadBuilder.attestationType(tryGetField(rawPayload, "x-ms-attestation-type", String.class));
8181
tokenPayloadBuilder.complianceStatus(tryGetField(rawPayload, "x-ms-compliance-status", String.class));
8282
tokenPayloadBuilder.vmDebuggable(tryGetField(rawPayload, "x-ms-sevsnpvm-is-debuggable", Boolean.class));

src/test/java/com/uid2/shared/secure/AzureCCCoreAttestationServiceTest.java

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
import org.junit.jupiter.api.BeforeEach;
1010
import org.junit.jupiter.api.Test;
1111
import org.junit.jupiter.api.extension.ExtendWith;
12+
import org.junit.jupiter.params.ParameterizedTest;
13+
import org.junit.jupiter.params.provider.Arguments;
14+
import org.junit.jupiter.params.provider.MethodSource;
15+
import org.junit.jupiter.params.provider.ValueSource;
1216
import org.mockito.Mock;
1317
import org.mockito.junit.jupiter.MockitoExtension;
1418
import org.mockito.junit.jupiter.MockitoSettings;
@@ -18,6 +22,7 @@
1822
import java.nio.charset.StandardCharsets;
1923
import java.util.Arrays;
2024
import java.util.Base64;
25+
import java.util.stream.Stream;
2126

2227
import static org.junit.jupiter.api.Assertions.*;
2328
import static org.mockito.ArgumentMatchers.any;
@@ -55,25 +60,27 @@ private static byte[] encodeStringUnicodeAttestationEndpoint(String data) {
5560

5661
@BeforeEach
5762
public void setup() throws AttestationException {
58-
when(alwaysPassTokenValidator.validate(any())).thenReturn(VALID_TOKEN_PAYLOAD);
63+
when(alwaysPassTokenValidator.validate(any(), any())).thenReturn(VALID_TOKEN_PAYLOAD);
5964
when(alwaysPassPolicyValidator.validate(any(), any())).thenReturn(ENCLAVE_ID);
6065
}
6166

62-
@Test
63-
public void testHappyPath() throws AttestationException {
64-
var provider = new AzureCCCoreAttestationService(alwaysPassTokenValidator, alwaysPassPolicyValidator);
67+
@ParameterizedTest
68+
@MethodSource("argumentProvider")
69+
public void testHappyPath(String azureProtocol) throws AttestationException {
70+
var provider = new AzureCCCoreAttestationService(alwaysPassTokenValidator, alwaysPassPolicyValidator, azureProtocol);
6571
provider.registerEnclave(ENCLAVE_ID);
6672
attest(provider, ar -> {
6773
assertTrue(ar.succeeded());
6874
assertTrue(ar.result().isSuccess());
6975
});
7076
}
7177

72-
@Test
73-
public void testSignatureCheckFailed_ClientError() throws AttestationException {
78+
@ParameterizedTest
79+
@MethodSource("argumentProvider")
80+
public void testSignatureCheckFailed_ClientError(String azureProtocol) throws AttestationException {
7481
var errorStr = "token signature validation failed";
75-
when(alwaysFailTokenValidator.validate(any())).thenThrow(new AttestationClientException(errorStr, AttestationFailure.BAD_PAYLOAD));
76-
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysPassPolicyValidator);
82+
when(alwaysFailTokenValidator.validate(any(), any())).thenThrow(new AttestationClientException(errorStr, AttestationFailure.BAD_PAYLOAD));
83+
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysPassPolicyValidator, azureProtocol);
7784
provider.registerEnclave(ENCLAVE_ID);
7885
attest(provider, ar -> {
7986
assertTrue(ar.succeeded());
@@ -82,22 +89,24 @@ public void testSignatureCheckFailed_ClientError() throws AttestationException {
8289
});
8390
}
8491

85-
@Test
86-
public void testSignatureCheckFailed_ServerError() throws AttestationException {
87-
when(alwaysFailTokenValidator.validate(any())).thenThrow(new AttestationException("unknown server error"));
88-
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysPassPolicyValidator);
92+
@ParameterizedTest
93+
@MethodSource("argumentProvider")
94+
public void testSignatureCheckFailed_ServerError(String azureProtocol) throws AttestationException {
95+
when(alwaysFailTokenValidator.validate(any(), any())).thenThrow(new AttestationException("unknown server error"));
96+
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysPassPolicyValidator, azureProtocol);
8997
provider.registerEnclave(ENCLAVE_ID);
9098
attest(provider, ar -> {
9199
assertFalse(ar.succeeded());
92100
assertTrue(ar.cause() instanceof AttestationException);
93101
});
94102
}
95103

96-
@Test
97-
public void testPolicyCheckSuccess_ClientError() throws AttestationException {
104+
@ParameterizedTest
105+
@MethodSource("argumentProvider")
106+
public void testPolicyCheckSuccess_ClientError(String azureProtocol) throws AttestationException {
98107
var errorStr = "policy validation failed";
99108
when(alwaysFailPolicyValidator.validate(any(), any())).thenThrow(new AttestationClientException(errorStr, AttestationFailure.BAD_PAYLOAD));
100-
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysFailPolicyValidator);
109+
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysFailPolicyValidator, azureProtocol);
101110
provider.registerEnclave(ENCLAVE_ID);
102111
attest(provider, ar -> {
103112
assertTrue(ar.succeeded());
@@ -106,20 +115,22 @@ public void testPolicyCheckSuccess_ClientError() throws AttestationException {
106115
});
107116
}
108117

109-
@Test
110-
public void testPolicyCheckFailed_ServerError() throws AttestationException {
118+
@ParameterizedTest
119+
@MethodSource("argumentProvider")
120+
public void testPolicyCheckFailed_ServerError(String azureProtocol) throws AttestationException {
111121
when(alwaysFailPolicyValidator.validate(any(), any())).thenThrow(new AttestationException("unknown server error"));
112-
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysFailPolicyValidator);
122+
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysFailPolicyValidator, azureProtocol);
113123
provider.registerEnclave(ENCLAVE_ID);
114124
attest(provider, ar -> {
115125
assertFalse(ar.succeeded());
116126
assertTrue(ar.cause() instanceof AttestationException);
117127
});
118128
}
119129

120-
@Test
121-
public void testEnclaveNotRegistered() throws AttestationException {
122-
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysPassPolicyValidator);
130+
@ParameterizedTest
131+
@MethodSource("argumentProvider")
132+
public void testEnclaveNotRegistered(String azureProtocol) throws AttestationException {
133+
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysPassPolicyValidator, azureProtocol);
123134
attest(provider, ar -> {
124135
assertTrue(ar.succeeded());
125136
assertFalse(ar.result().isSuccess());
@@ -133,4 +144,11 @@ private static void attest(ICoreAttestationService provider, Handler<AsyncResult
133144
PUBLIC_KEY.getBytes(StandardCharsets.UTF_8),
134145
handler);
135146
}
147+
148+
static Stream<Arguments> argumentProvider() {
149+
return Stream.of(
150+
Arguments.of(MaaTokenPayload.AZURE_CC_ACI_PROTOCOL),
151+
Arguments.of(MaaTokenPayload.AZURE_CC_AKS_PROTOCOL)
152+
);
153+
}
136154
}

src/test/java/com/uid2/shared/secure/azurecc/MaaTokenSignatureValidatorTest.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
import com.uid2.shared.secure.AttestationException;
44
import com.uid2.shared.secure.TestClock;
55
import org.junit.jupiter.api.Disabled;
6-
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.Arguments;
8+
import org.junit.jupiter.params.provider.MethodSource;
9+
10+
import java.util.stream.Stream;
711

812
import static com.uid2.shared.secure.TestUtils.loadFromJson;
913
import static com.uid2.shared.secure.azurecc.MaaTokenUtils.validateAndParseToken;
1014
import static org.junit.jupiter.api.Assertions.assertEquals;
1115

1216
public class MaaTokenSignatureValidatorTest {
13-
@Test
14-
public void testPayload() throws Exception {
17+
@ParameterizedTest
18+
@MethodSource("argumentProvider")
19+
public void testPayload(String payloadPath, String protocol) throws Exception {
1520
// expire at 1695313895
16-
var payloadPath = "/com.uid2.shared/test/secure/azurecc/jwt_payload.json";
1721
var payload = loadFromJson(payloadPath);
1822
var clock = new TestClock();
1923
clock.setCurrentTimeMs(1695313893000L);
@@ -22,7 +26,7 @@ public void testPayload() throws Exception {
2226
var expectedLocation = "East US";
2327
var expectedPublicKey = "abc";
2428

25-
var tokenPayload = validateAndParseToken(payload, clock);
29+
var tokenPayload = validateAndParseToken(payload, clock, protocol);
2630
assertEquals(true, tokenPayload.isSevSnpVM());
2731
assertEquals(true, tokenPayload.isUtilityVMCompliant());
2832
assertEquals(false, tokenPayload.isVmDebuggable());
@@ -37,6 +41,13 @@ public void testE2E() throws AttestationException {
3741
var maaToken = "<Placeholder>";
3842
var maaServerUrl = "https://sharedeus.eus.attest.azure.net";
3943
var validator = new MaaTokenSignatureValidator(maaServerUrl);
40-
var token = validator.validate(maaToken);
44+
var token = validator.validate(maaToken, MaaTokenPayload.AZURE_CC_ACI_PROTOCOL);
45+
}
46+
47+
static Stream<Arguments> argumentProvider() {
48+
return Stream.of(
49+
Arguments.of("/com.uid2.shared/test/secure/azurecc/jwt_payload_aci.json", MaaTokenPayload.AZURE_CC_ACI_PROTOCOL),
50+
Arguments.of("/com.uid2.shared/test/secure/azurecc/jwt_payload_aks.json", MaaTokenPayload.AZURE_CC_AKS_PROTOCOL)
51+
);
4152
}
4253
}

src/test/java/com/uid2/shared/secure/azurecc/MaaTokenUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
public class MaaTokenUtils {
1515
public static final String MAA_BASE_URL = "https://sharedeus.eus.attest.azure.net";
1616

17-
public static MaaTokenPayload validateAndParseToken(JsonObject payload, Clock clock) throws Exception{
17+
public static MaaTokenPayload validateAndParseToken(JsonObject payload, Clock clock, String protocol) throws Exception{
1818
var gen = KeyPairGenerator.getInstance(Const.Name.AsymetricEncryptionKeyClass);
1919
gen.initialize(2048, new SecureRandom());
2020
var keyPair = gen.generateKeyPair();
@@ -30,7 +30,7 @@ public static MaaTokenPayload validateAndParseToken(JsonObject payload, Clock cl
3030
var tokenVerifier = new MaaTokenSignatureValidator(MAA_BASE_URL, keyProvider, clock);
3131

3232
// validate token
33-
return tokenVerifier.validate(token);
33+
return tokenVerifier.validate(token, protocol);
3434
}
3535

3636
private static class MockKeyProvider implements IPublicKeyProvider {

src/test/java/com/uid2/shared/secure/azurecc/PolicyValidatorTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ private MaaTokenPayload generateBasicPayload() {
9797
.vmDebuggable(false)
9898
.runtimeData(generateBasicRuntimeData())
9999
.ccePolicyDigest(CCE_POLICY_DIGEST)
100+
.azureProtocol(MaaTokenPayload.AZURE_CC_ACI_PROTOCOL)
100101
.build();
101102
}
102103

@@ -125,4 +126,53 @@ public void testValidationFailure_DifferentAttestationUrl() {
125126
assertEquals(AttestationFailure.UNKNOWN_ATTESTATION_URL, ((AttestationClientException)t).getAttestationFailure());
126127

127128
}
129+
130+
@Test
131+
public void testValidationFailure_AzureCcWithOtherUvm() {
132+
var validator = new PolicyValidator(ATTESTATION_URL);
133+
var aksPayload = generateBasicPayload()
134+
.toBuilder()
135+
.complianceStatus("fake-compliance")
136+
.build();
137+
Throwable t = assertThrows(AttestationException.class, ()-> validator.validate(aksPayload, PUBLIC_KEY));
138+
assertEquals("Not run in Azure Compliance Utility VM", t.getMessage());
139+
assertEquals(AttestationFailure.BAD_FORMAT, ((AttestationClientException)t).getAttestationFailure());
140+
}
141+
142+
@Test
143+
public void testValidationSuccess_AksWithAzureSignedKataccUvm() throws AttestationClientException {
144+
var validator = new PolicyValidator(ATTESTATION_URL);
145+
var aksPayload = generateBasicPayload()
146+
.toBuilder()
147+
.complianceStatus("azure-signed-katacc-uvm")
148+
.azureProtocol(MaaTokenPayload.AZURE_CC_AKS_PROTOCOL)
149+
.build();
150+
var enclaveId = validator.validate(aksPayload, PUBLIC_KEY);
151+
assertEquals(CCE_POLICY_DIGEST, enclaveId);
152+
}
153+
154+
@Test
155+
public void testValidationFailure_AksWithOtherUvm() {
156+
var validator = new PolicyValidator(ATTESTATION_URL);
157+
var aksPayload = generateBasicPayload()
158+
.toBuilder()
159+
.complianceStatus("fake-compliance")
160+
.azureProtocol(MaaTokenPayload.AZURE_CC_AKS_PROTOCOL)
161+
.build();
162+
Throwable t = assertThrows(AttestationException.class, ()-> validator.validate(aksPayload, PUBLIC_KEY));
163+
assertEquals("Not run in Azure Compliance Utility VM", t.getMessage());
164+
assertEquals(AttestationFailure.BAD_FORMAT, ((AttestationClientException)t).getAttestationFailure());
165+
}
166+
167+
@Test
168+
public void testValidationFailure_InvalidProtocol() {
169+
var validator = new PolicyValidator(ATTESTATION_URL);
170+
var aksPayload = generateBasicPayload()
171+
.toBuilder()
172+
.azureProtocol("fake-protocol")
173+
.build();
174+
Throwable t = assertThrows(AttestationException.class, ()-> validator.validate(aksPayload, PUBLIC_KEY));
175+
assertEquals("Azure protocol: fake-protocol not supported", t.getMessage());
176+
assertEquals(AttestationFailure.INVALID_PROTOCOL, ((AttestationClientException)t).getAttestationFailure());
177+
}
128178
}

src/test/resources/com.uid2.shared/test/secure/azurecc/jwt_payload.json renamed to src/test/resources/com.uid2.shared/test/secure/azurecc/jwt_payload_aci.json

File renamed without changes.

0 commit comments

Comments
 (0)