Skip to content

Commit 8e67b3a

Browse files
authored
Merge pull request #194 from IABTechLab/tjm-UID2-2810-validate-attestation-url-gcp
Shared changes to validate attestation URL for GCP
2 parents 7005525 + 6329847 commit 8e67b3a

14 files changed

+158
-66
lines changed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.uid2</groupId>
88
<artifactId>uid2-shared</artifactId>
9-
<version>7.0.4-7b2bf15eae</version>
9+
<version>7.0.5-SNAPSHOT</version>
1010
<name>${project.groupId}:${project.artifactId}</name>
1111
<description>Library for all the shared uid2 operations</description>
1212
<url>https://github.com/IABTechLab/uid2docs</url>

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,19 @@
22

33
public class AttestationClientException extends AttestationException
44
{
5+
private final AttestationFailure attestationFailure;
6+
57
public AttestationClientException(Throwable cause) {
68
super(cause, true);
9+
this.attestationFailure = AttestationFailure.UNKNOWN;
710
}
811

9-
public AttestationClientException(String message) {
12+
public AttestationClientException(String message, AttestationFailure attestationFailure) {
1013
super(message, true);
14+
this.attestationFailure = attestationFailure;
15+
}
16+
17+
public AttestationFailure getAttestationFailure() {
18+
return this.attestationFailure;
1119
}
1220
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public class GcpOidcCoreAttestationService implements ICoreAttestationService {
1919

2020
private final Set<String> allowedEnclaveIds = new HashSet<>();
2121

22-
public GcpOidcCoreAttestationService(){
23-
this(new TokenSignatureValidator(), Arrays.asList(new PolicyValidator()));
22+
public GcpOidcCoreAttestationService(String attestationUrl){
23+
this(new TokenSignatureValidator(), Arrays.asList(new PolicyValidator(attestationUrl)));
2424
}
2525

2626
// used in UT

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ private AttestationResult attestInternal(byte[] publicKey, AttestationRequest aR
5757

5858
String givenAttestationUrl = aDoc.getUserDataString();
5959
if (givenAttestationUrl != null && !givenAttestationUrl.isEmpty()) {
60-
if (!UrlEquivalenceValidator.areUrlsEquivalent(this.attestationUrl, givenAttestationUrl, LOGGER)) {
60+
if (!UrlEquivalenceValidator.areUrlsEquivalent(this.attestationUrl, givenAttestationUrl)) {
6161
return new AttestationResult(AttestationFailure.UNKNOWN_ATTESTATION_URL);
6262
}
6363
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.google.common.base.Strings;
88
import com.uid2.shared.secure.AttestationClientException;
99
import com.uid2.shared.secure.AttestationException;
10+
import com.uid2.shared.secure.AttestationFailure;
1011

1112
import java.io.IOException;
1213
import java.util.Map;
@@ -66,7 +67,7 @@ public MaaTokenPayload validate(String tokenString) throws AttestationException
6667
tokenVerifier.verify(tokenString);
6768
}
6869
} catch (TokenVerifier.VerificationException e) {
69-
throw new AttestationClientException("Fail to validate the token signature, error: " + e.getMessage());
70+
throw new AttestationClientException("Fail to validate the token signature, error: " + e.getMessage(), AttestationFailure.BAD_PAYLOAD);
7071
} catch (IOException e) {
7172
throw new AttestationException("Fail to parse token, error: " + e.getMessage());
7273
}

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

+9-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.base.Strings;
44
import com.uid2.shared.secure.AttestationClientException;
55
import com.uid2.shared.secure.AttestationException;
6+
import com.uid2.shared.secure.AttestationFailure;
67

78
public class PolicyValidator implements IPolicyValidator{
89
private static final String LOCATION_CHINA = "china";
@@ -17,39 +18,40 @@ public String validate(MaaTokenPayload maaTokenPayload, String publicKey) throws
1718

1819
private void verifyPublicKey(MaaTokenPayload maaTokenPayload, String publicKey) throws AttestationException {
1920
if(Strings.isNullOrEmpty(publicKey)){
20-
throw new AttestationClientException("public key to check is null or empty");
21+
throw new AttestationClientException("public key to check is null or empty", AttestationFailure.BAD_FORMAT);
2122
}
2223
var runtimePublicKey = maaTokenPayload.getRuntimeData().getPublicKey();
2324
if(!publicKey.equals(runtimePublicKey)){
2425
throw new AttestationClientException(
2526
String.format("Public key in payload is not match expected value. More info: runtime(%s), expected(%s)",
2627
runtimePublicKey,
2728
publicKey
28-
));
29+
),
30+
AttestationFailure.BAD_FORMAT);
2931
}
3032
}
3133

3234
private void verifyVM(MaaTokenPayload maaTokenPayload) throws AttestationException {
3335
if(!maaTokenPayload.isSevSnpVM()){
34-
throw new AttestationClientException("Not in SevSnp VM");
36+
throw new AttestationClientException("Not in SevSnp VM", AttestationFailure.BAD_FORMAT);
3537
}
3638
if(!maaTokenPayload.isUtilityVMCompliant()){
37-
throw new AttestationClientException("Not run in Azure Compliance Utility VM");
39+
throw new AttestationClientException("Not run in Azure Compliance Utility VM", AttestationFailure.BAD_FORMAT);
3840
}
3941
if(maaTokenPayload.isVmDebuggable()){
40-
throw new AttestationClientException("The underlying hardware should not run in debug mode");
42+
throw new AttestationClientException("The underlying hardware should not run in debug mode", AttestationFailure.BAD_FORMAT);
4143
}
4244
}
4345

4446
private void verifyLocation(MaaTokenPayload maaTokenPayload) throws AttestationException {
4547
var location = maaTokenPayload.getRuntimeData().getLocation();
4648
if(Strings.isNullOrEmpty(location)){
47-
throw new AttestationClientException("Location is not specified.");
49+
throw new AttestationClientException("Location is not specified.", AttestationFailure.BAD_PAYLOAD);
4850
}
4951
var lowerCaseLocation = location.toLowerCase();
5052
if(lowerCaseLocation.contains(LOCATION_CHINA) ||
5153
lowerCaseLocation.contains(LOCATION_EU)){
52-
throw new AttestationClientException("Location is not supported. Value: " + location);
54+
throw new AttestationClientException("Location is not supported. Value: " + location, AttestationFailure.BAD_PAYLOAD);
5355
}
5456
}
5557
}

src/main/java/com/uid2/shared/secure/gcpoidc/PolicyValidator.java

+36-13
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import com.uid2.shared.Utils;
77
import com.uid2.shared.secure.AttestationClientException;
88
import com.uid2.shared.secure.AttestationException;
9+
import com.uid2.shared.secure.AttestationFailure;
10+
import com.uid2.shared.util.UrlEquivalenceValidator;
911
import org.apache.commons.collections4.CollectionUtils;
1012
import org.apache.commons.collections4.MapUtils;
1113
import org.slf4j.Logger;
@@ -30,14 +32,24 @@ public class PolicyValidator implements IPolicyValidator {
3032

3133
private static final List<String> REQUIRED_ENV_OVERRIDES = ImmutableList.of(
3234
ENV_ENVIRONMENT,
33-
ENV_OPERATOR_API_KEY_SECRET_NAME,
34-
ENV_CORE_ENDPOINT,
35-
ENV_OPT_OUT_ENDPOINT
35+
ENV_OPERATOR_API_KEY_SECRET_NAME
3636
);
3737

3838
private static final Map<Environment, List<String>> OPTIONAL_ENV_OVERRIDES_MAP = ImmutableMap.of(
39-
Environment.Integration, ImmutableList.of()
39+
Environment.Production, ImmutableList.of(
40+
ENV_CORE_ENDPOINT,
41+
ENV_OPT_OUT_ENDPOINT
42+
),
43+
Environment.Integration, ImmutableList.of(
44+
ENV_CORE_ENDPOINT,
45+
ENV_OPT_OUT_ENDPOINT
46+
)
4047
);
48+
private final String attestationUrl;
49+
50+
public PolicyValidator(String attestationUrl) {
51+
this.attestationUrl = attestationUrl;
52+
}
4153

4254
@Override
4355
public String getVersion() {
@@ -56,18 +68,18 @@ public String validate(TokenPayload payload) throws AttestationException {
5668

5769
private static boolean checkConfidentialSpace(TokenPayload payload) throws AttestationException{
5870
if(!payload.isConfidentialSpaceSW()){
59-
throw new AttestationClientException("Unexpected SW_NAME: " + payload.getSwName());
71+
throw new AttestationClientException("Unexpected SW_NAME: " + payload.getSwName(), AttestationFailure.BAD_FORMAT);
6072
}
6173
var isDebugMode = payload.isDebugMode();
6274
if(!isDebugMode && !payload.isStableVersion()){
63-
throw new AttestationClientException("Confidential space image version is not stable.");
75+
throw new AttestationClientException("Confidential space image version is not stable.", AttestationFailure.BAD_FORMAT);
6476
}
6577
return isDebugMode;
6678
}
6779

6880
private static String checkWorkload(TokenPayload payload) throws AttestationException{
6981
if(!payload.isRestartPolicyNever()){
70-
throw new AttestationClientException("Restart policy is not set to Never. Value: " + payload.getRestartPolicy());
82+
throw new AttestationClientException("Restart policy is not set to Never. Value: " + payload.getRestartPolicy(), AttestationFailure.BAD_FORMAT);
7183
}
7284
return payload.getWorkloadImageDigest();
7385
}
@@ -78,35 +90,35 @@ private static String checkWorkload(TokenPayload payload) throws AttestationExce
7890
private static String checkRegion(TokenPayload payload) throws AttestationException{
7991
var region = payload.getGceZone();
8092
if(Strings.isNullOrEmpty(region) || region.startsWith(EU_REGION_PREFIX)){
81-
throw new AttestationClientException("Region is not supported. Value: " + region);
93+
throw new AttestationClientException("Region is not supported. Value: " + region, AttestationFailure.BAD_FORMAT);
8294
}
8395
return region;
8496
}
8597

8698
private static void checkCmdOverrides(TokenPayload payload) throws AttestationException{
8799
if(!CollectionUtils.isEmpty(payload.getCmdOverrides())){
88-
throw new AttestationClientException("Payload should not have cmd overrides");
100+
throw new AttestationClientException("Payload should not have cmd overrides", AttestationFailure.BAD_FORMAT);
89101
}
90102
}
91103

92104
private Environment checkEnvOverrides(TokenPayload payload) throws AttestationException{
93105
var envOverrides = payload.getEnvOverrides();
94106
if(MapUtils.isEmpty(envOverrides)){
95-
throw new AttestationClientException("env overrides should not be empty");
107+
throw new AttestationClientException("env overrides should not be empty", AttestationFailure.BAD_FORMAT);
96108
}
97109
HashMap<String, String> envOverridesCopy = new HashMap(envOverrides);
98110

99111
// check all required env overrides
100112
for(var envKey: REQUIRED_ENV_OVERRIDES){
101113
if(Strings.isNullOrEmpty(envOverridesCopy.get(envKey))){
102-
throw new AttestationClientException("Required env override is missing. key: " + envKey);
114+
throw new AttestationClientException("Required env override is missing. key: " + envKey, AttestationFailure.BAD_FORMAT);
103115
}
104116
}
105117

106118
// env could be parsed
107119
var env = Environment.fromString(envOverridesCopy.get(ENV_ENVIRONMENT));
108120
if(env == null){
109-
throw new AttestationClientException("Environment can not be parsed. " + envOverridesCopy.get(ENV_ENVIRONMENT));
121+
throw new AttestationClientException("Environment can not be parsed. " + envOverridesCopy.get(ENV_ENVIRONMENT), AttestationFailure.BAD_FORMAT);
110122
}
111123

112124
// make sure there's no unexpected overrides
@@ -120,13 +132,24 @@ private Environment checkEnvOverrides(TokenPayload payload) throws AttestationEx
120132
}
121133
}
122134

135+
checkAttestationUrl(new HashMap<>(envOverrides));
136+
123137
if(!envOverridesCopy.isEmpty()){
124-
throw new AttestationClientException("More env overrides than allowed. " + envOverridesCopy);
138+
throw new AttestationClientException("More env overrides than allowed. " + envOverridesCopy, AttestationFailure.BAD_FORMAT);
125139
}
126140

127141
return env;
128142
}
129143

144+
private void checkAttestationUrl(HashMap<String, String> optionalEnvOverrides) throws AttestationException {
145+
if (!Strings.isNullOrEmpty(optionalEnvOverrides.get(ENV_CORE_ENDPOINT))) {
146+
String givenAttestationUrl = optionalEnvOverrides.get(ENV_CORE_ENDPOINT);
147+
if (!UrlEquivalenceValidator.areUrlsEquivalent(givenAttestationUrl, this.attestationUrl)) {
148+
throw new AttestationClientException("The given attestation URL is unknown. Given URL: " + givenAttestationUrl, AttestationFailure.UNKNOWN_ATTESTATION_URL);
149+
}
150+
}
151+
}
152+
130153
private String generateEnclaveId(boolean isDebugMode, String imageDigest, Environment env) throws AttestationException {
131154
var str = String.format("%s,%s,%s", getVersion(), isDebugMode, imageDigest);
132155
LOGGER.info("Meta used to generate GCP EnclaveId: " + str);

src/main/java/com/uid2/shared/util/UrlEquivalenceValidator.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
package com.uid2.shared.util;
22

33
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
45

56
import java.net.MalformedURLException;
67
import java.net.URL;
78

89
public class UrlEquivalenceValidator {
10+
private static final Logger LOGGER = LoggerFactory.getLogger(UrlEquivalenceValidator.class);
911
private UrlEquivalenceValidator() {
1012

1113
}
1214

13-
public static Boolean areUrlsEquivalent(String url1, String url2, Logger logger) {
14-
logger.debug("Checking URLs equivalent: URL1: '{}', URL2: '{}'", url1, url2);
15+
public static Boolean areUrlsEquivalent(String url1, String url2) {
16+
LOGGER.debug("Checking URLs equivalent: URL1: '{}', URL2: '{}'", url1, url2);
1517
URL first;
1618
try {
1719
first = new URL(url1);
1820
} catch (MalformedURLException e) {
19-
logger.error("URL could not be parsed to a valid URL. Given URL: " + url1, e);
21+
LOGGER.error("URL could not be parsed to a valid URL. Given URL: " + url1, e);
2022
return false;
2123
}
2224
URL second;
2325
try {
2426
second = new URL(url2);
2527
} catch (MalformedURLException e) {
26-
logger.error("URL could not be parsed to a valid URL. Given URL: " + url2, e);
28+
LOGGER.error("URL could not be parsed to a valid URL. Given URL: " + url2, e);
2729
return false;
2830
}
2931

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void testHappyPath() throws AttestationException {
6060
@Test
6161
public void testSignatureCheckFailed_ClientError() throws AttestationException {
6262
var errorStr = "token signature validation failed";
63-
when(alwaysFailTokenValidator.validate(any())).thenThrow(new AttestationClientException(errorStr));
63+
when(alwaysFailTokenValidator.validate(any())).thenThrow(new AttestationClientException(errorStr, AttestationFailure.BAD_PAYLOAD));
6464
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysPassPolicyValidator);
6565
provider.registerEnclave(ENCLAVE_ID);
6666
attest(provider, ar -> {
@@ -84,7 +84,7 @@ public void testSignatureCheckFailed_ServerError() throws AttestationException {
8484
@Test
8585
public void testPolicyCheckFailed_ClientError() throws AttestationException {
8686
var errorStr = "policy validation failed";
87-
when(alwaysFailPolicyValidator.validate(any(), any())).thenThrow(new AttestationClientException(errorStr));
87+
when(alwaysFailPolicyValidator.validate(any(), any())).thenThrow(new AttestationClientException(errorStr, AttestationFailure.BAD_PAYLOAD));
8888
var provider = new AzureCCCoreAttestationService(alwaysFailTokenValidator, alwaysFailPolicyValidator);
8989
provider.registerEnclave(ENCLAVE_ID);
9090
attest(provider, ar -> {

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public void testHappyPath() throws AttestationException {
6767
@Test
6868
public void testSignatureCheckFailed_ClientError() throws AttestationException {
6969
var errorStr = "signature validation failed";
70-
when(alwaysFailTokenValidator.validate(any())).thenThrow(new AttestationClientException(errorStr));
70+
when(alwaysFailTokenValidator.validate(any())).thenThrow(new AttestationClientException(errorStr, AttestationFailure.BAD_PAYLOAD));
7171
var provider = new GcpOidcCoreAttestationService(alwaysFailTokenValidator, Arrays.asList(alwaysPassPolicyValidator1));
7272
provider.registerEnclave(ENCLAVE_ID_1);
7373
attest(provider, ar -> {
@@ -91,7 +91,7 @@ public void testSignatureCheckFailed_ServerError() throws AttestationException {
9191
@Test
9292
public void testPolicyCheckFailed_ClientError() throws AttestationException {
9393
var errorStr = "policy validation failed";
94-
when(alwaysFailPolicyValidator.validate(any())).thenThrow(new AttestationClientException(errorStr));
94+
when(alwaysFailPolicyValidator.validate(any())).thenThrow(new AttestationClientException(errorStr, AttestationFailure.BAD_PAYLOAD));
9595
var provider = new GcpOidcCoreAttestationService(alwaysPassTokenValidator, Arrays.asList(alwaysFailPolicyValidator));
9696
provider.registerEnclave(ENCLAVE_ID_1);
9797
attest(provider, ar -> {

src/test/java/com/uid2/shared/secure/gcpoidc/OidcPayloadValidationTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void testE2EPolicyCheck() throws Exception {
1717
clock.setCurrentTimeMs(1688132563000L);
1818

1919
var tokenPayload = validateAndParseToken(payload, clock);
20-
var policyValidator = new PolicyValidator();
20+
var policyValidator = new PolicyValidator("https://core-url.uidapi.com");
2121
var enclaveId = policyValidator.validate(tokenPayload);
2222
}
2323
}

0 commit comments

Comments
 (0)