diff --git a/pom.xml b/pom.xml
index f1d08c85c..18f1a537d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.uid2
uid2-operator
- 5.70.14
+ 5.70.15-alpha-324-SNAPSHOT
UTF-8
diff --git a/src/main/java/com/uid2/operator/service/EncryptedTokenEncoder.java b/src/main/java/com/uid2/operator/service/EncryptedTokenEncoder.java
index 630918e19..a2dac487e 100644
--- a/src/main/java/com/uid2/operator/service/EncryptedTokenEncoder.java
+++ b/src/main/java/com/uid2/operator/service/EncryptedTokenEncoder.java
@@ -27,29 +27,12 @@ public EncryptedTokenEncoder(KeyManager keyManager) {
}
public byte[] encode(AdvertisingToken t, Instant asOf) {
+ if (t.version != TokenVersion.V4) {
+ throw new ClientInputValidationException("Only advertising token V4 is supported");
+ }
final KeysetKey masterKey = this.keyManager.getMasterKey(asOf);
final KeysetKey siteEncryptionKey = this.keyManager.getActiveKeyBySiteIdWithFallback(t.publisherIdentity.siteId, Data.AdvertisingTokenSiteId, asOf, siteKeysetStatusMetrics);
-
- return t.version == TokenVersion.V2
- ? encodeV2(t, masterKey, siteEncryptionKey)
- : encodeV3(t, masterKey, siteEncryptionKey); //TokenVersion.V4 also calls encodeV3() since the byte array is identical between V3 and V4
- }
-
- private byte[] encodeV2(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteKey) {
- final Buffer b = Buffer.buffer();
-
- b.appendByte((byte) t.version.rawVersion);
- b.appendInt(masterKey.getId());
-
- Buffer b2 = Buffer.buffer();
- b2.appendLong(t.expiresAt.toEpochMilli());
- encodeSiteIdentityV2(b2, t.publisherIdentity, t.userIdentity, siteKey);
-
- final byte[] encryptedId = AesCbc.encrypt(b2.getBytes(), masterKey).getPayload();
-
- b.appendBytes(encryptedId);
-
- return b.getBytes();
+ return encodeV3(t, masterKey, siteEncryptionKey);
}
private byte[] encodeV3(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteKey) {
@@ -176,69 +159,25 @@ public AdvertisingToken decodeAdvertisingToken(String base64AdvertisingToken) {
byte[] headerBytes = isBase64UrlEncoding ? Uid2Base64UrlCoder.decode(headerStr) : Base64.getDecoder().decode(headerStr);
if (headerBytes[0] == TokenVersion.V2.rawVersion) {
- final byte[] bytes = EncodingUtils.fromBase64(base64AdvertisingToken);
- final Buffer b = Buffer.buffer(bytes);
- return decodeAdvertisingTokenV2(b);
+ throw new ClientInputValidationException("Advertising token V2 is no longer supported");
}
//Java's byte is signed, so we convert to unsigned before checking the enum
int unsignedByte = ((int) headerBytes[1]) & 0xff;
- byte[] bytes;
- TokenVersion tokenVersion;
if (unsignedByte == TokenVersion.V3.rawVersion) {
- bytes = EncodingUtils.fromBase64(base64AdvertisingToken);
- tokenVersion = TokenVersion.V3;
- } else if (unsignedByte == TokenVersion.V4.rawVersion) {
- bytes = Uid2Base64UrlCoder.decode(base64AdvertisingToken); //same as V3 but use Base64URL encoding
- tokenVersion = TokenVersion.V4;
- } else {
+ throw new ClientInputValidationException("Advertising token V3 is no longer supported");
+ }
+ if (unsignedByte != TokenVersion.V4.rawVersion) {
throw new ClientInputValidationException("Invalid advertising token version");
}
+ final byte[] bytes = Uid2Base64UrlCoder.decode(base64AdvertisingToken);
final Buffer b = Buffer.buffer(bytes);
- return decodeAdvertisingTokenV3orV4(b, bytes, tokenVersion);
- }
-
- public AdvertisingToken decodeAdvertisingTokenV2(Buffer b) {
- try {
- final int masterKeyId = b.getInt(1);
-
- final byte[] decryptedPayload = AesCbc.decrypt(b.slice(5, b.length()).getBytes(), this.keyManager.getKey(masterKeyId));
-
- final Buffer b2 = Buffer.buffer(decryptedPayload);
-
- final long expiresMillis = b2.getLong(0);
- final int siteKeyId = b2.getInt(8);
-
- final byte[] decryptedSitePayload = AesCbc.decrypt(b2.slice(12, b2.length()).getBytes(), this.keyManager.getKey(siteKeyId));
-
- final Buffer b3 = Buffer.buffer(decryptedSitePayload);
-
- final int siteId = b3.getInt(0);
- final int length = b3.getInt(4);
-
- final byte[] advertisingId = EncodingUtils.fromBase64(b3.slice(8, 8 + length).getBytes());
-
- final int privacyBits = b3.getInt(8 + length);
- final long establishedMillis = b3.getLong(8 + length + 4);
-
- return new AdvertisingToken(
- TokenVersion.V2,
- Instant.ofEpochMilli(establishedMillis),
- Instant.ofEpochMilli(expiresMillis),
- new OperatorIdentity(0, OperatorType.Service, 0, masterKeyId),
- new PublisherIdentity(siteId, siteKeyId, 0),
- new UserIdentity(IdentityScope.UID2, IdentityType.Email, advertisingId, privacyBits, Instant.ofEpochMilli(establishedMillis), null),
- siteKeyId
- );
-
- } catch (Exception e) {
- throw new RuntimeException("Couldn't decode advertisingTokenV2", e);
- }
+ return decodeAdvertisingTokenV4(b, bytes);
}
- public AdvertisingToken decodeAdvertisingTokenV3orV4(Buffer b, byte[] bytes, TokenVersion tokenVersion) {
+ private AdvertisingToken decodeAdvertisingTokenV4(Buffer b, byte[] bytes) {
final int masterKeyId = b.getInt(2);
final byte[] masterPayloadBytes = AesGcm.decrypt(bytes, 6, this.keyManager.getKey(masterKeyId));
@@ -259,15 +198,15 @@ public AdvertisingToken decodeAdvertisingTokenV3orV4(Buffer b, byte[] bytes, Tok
if (id.length > 32) {
if (identityScope != decodeIdentityScopeV3(b.getByte(0))) {
- throw new ClientInputValidationException("Failed decoding advertisingTokenV3: Identity scope mismatch");
+ throw new ClientInputValidationException("Failed decoding advertising token: Identity scope mismatch");
}
if (identityType != decodeIdentityTypeV3(b.getByte(0))) {
- throw new ClientInputValidationException("Failed decoding advertisingTokenV3: Identity type mismatch");
+ throw new ClientInputValidationException("Failed decoding advertising token: Identity type mismatch");
}
}
return new AdvertisingToken(
- tokenVersion, createdAt, expiresAt, operatorIdentity, publisherIdentity,
+ TokenVersion.V4, createdAt, expiresAt, operatorIdentity, publisherIdentity,
new UserIdentity(identityScope, identityType, id, privacyBits, establishedAt, refreshedAt),
siteKeyId
);
@@ -329,15 +268,11 @@ public byte[] encodeV3(RefreshToken t, KeysetKey serviceKey) {
return b.getBytes();
}
- private void encodeSiteIdentityV2(Buffer b, PublisherIdentity publisherIdentity, UserIdentity userIdentity, KeysetKey siteEncryptionKey) {
- b.appendInt(siteEncryptionKey.getId());
- final byte[] encryptedIdentity = encryptIdentityV2(publisherIdentity, userIdentity, siteEncryptionKey);
- b.appendBytes(encryptedIdentity);
- }
-
public static String bytesToBase64Token(byte[] advertisingTokenBytes, TokenVersion tokenVersion) {
- return (tokenVersion == TokenVersion.V4) ?
- Uid2Base64UrlCoder.encode(advertisingTokenBytes) : EncodingUtils.toBase64String(advertisingTokenBytes);
+ if (tokenVersion != TokenVersion.V4) {
+ throw new ClientInputValidationException("Only advertising token V4 is supported");
+ }
+ return Uid2Base64UrlCoder.encode(advertisingTokenBytes);
}
@Override
diff --git a/src/test/java/com/uid2/operator/TokenEncodingTest.java b/src/test/java/com/uid2/operator/TokenEncodingTest.java
index e7816776d..7dd0ed6c7 100644
--- a/src/test/java/com/uid2/operator/TokenEncodingTest.java
+++ b/src/test/java/com/uid2/operator/TokenEncodingTest.java
@@ -16,8 +16,8 @@
import io.vertx.core.json.JsonObject;
import org.junit.Assert;
import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.ValueSource;
import java.time.Instant;
@@ -86,23 +86,15 @@ void testRefreshTokenEncoding(TokenVersion tokenVersion) {
}
@ParameterizedTest
- @CsvSource({
- "false, V4", //same as current UID2 prod (as at 2024-12-10)
- "true, V4", //same as current EUID prod (as at 2024-12-10)
- //the following combinations aren't used in any UID2/EUID environments but just testing them regardless
- "false, V3",
- "true, V3",
- "false, V2",
- "true, V2"
- })
- void testAdvertisingTokenEncodings(boolean useRawUIDv3, TokenVersion adTokenVersion) {
+ @ValueSource(booleans = {false, true})
+ void testAdvertisingTokenEncodings(boolean useRawUIDv3) {
final EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(this.keyManager);
final Instant now = EncodingUtils.NowUTCMillis();
final byte[] rawUid = UIDOperatorVerticleTest.getRawUid(IdentityScope.UID2, IdentityType.Email, "test@example.com", useRawUIDv3);
final AdvertisingToken token = new AdvertisingToken(
- adTokenVersion,
+ TokenVersion.V4,
now,
now.plusSeconds(60),
new OperatorIdentity(101, OperatorType.Service, 102, 103),
@@ -111,9 +103,9 @@ void testAdvertisingTokenEncodings(boolean useRawUIDv3, TokenVersion adTokenVers
);
final byte[] encodedBytes = encoder.encode(token, now);
- final AdvertisingToken decoded = encoder.decodeAdvertisingToken(EncryptedTokenEncoder.bytesToBase64Token(encodedBytes, adTokenVersion));
+ final AdvertisingToken decoded = encoder.decodeAdvertisingToken(EncryptedTokenEncoder.bytesToBase64Token(encodedBytes, TokenVersion.V4));
- assertEquals(adTokenVersion, decoded.version);
+ assertEquals(TokenVersion.V4, decoded.version);
assertEquals(token.createdAt, decoded.createdAt);
assertEquals(token.expiresAt, decoded.expiresAt);
assertTrue(token.userIdentity.matches(decoded.userIdentity));
@@ -122,7 +114,7 @@ void testAdvertisingTokenEncodings(boolean useRawUIDv3, TokenVersion adTokenVers
assertEquals(token.publisherIdentity.siteId, decoded.publisherIdentity.siteId);
Buffer b = Buffer.buffer(encodedBytes);
- int keyId = b.getInt(adTokenVersion == TokenVersion.V2 ? 1 : 2); //TODO - extract master key from token should be a helper function
+ int keyId = b.getInt(2);
assertEquals(Data.MasterKeySiteId, keyManager.getSiteIdFromKeyId(keyId));
}
}
diff --git a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
index 7159adc9d..8b159b757 100644
--- a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
+++ b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
@@ -917,28 +917,17 @@ private AdvertisingToken validateAndGetToken(EncryptedTokenEncoder encoder, Json
}
public static void validateAdvertisingToken(String advertisingTokenString, TokenVersion tokenVersion, IdentityScope identityScope, IdentityType identityType) {
- if (tokenVersion == TokenVersion.V2) {
- assertEquals("Ag", advertisingTokenString.substring(0, 2));
+ assertEquals(TokenVersion.V4, tokenVersion);
+ String firstChar = advertisingTokenString.substring(0, 1);
+ if (identityScope == IdentityScope.UID2) {
+ assertEquals(identityType == IdentityType.Email ? "A" : "B", firstChar);
} else {
- String firstChar = advertisingTokenString.substring(0, 1);
- if (identityScope == IdentityScope.UID2) {
- assertEquals(identityType == IdentityType.Email ? "A" : "B", firstChar);
- } else {
- assertEquals(identityType == IdentityType.Email ? "E" : "F", firstChar);
- }
-
- String secondChar = advertisingTokenString.substring(1, 2);
- if (tokenVersion == TokenVersion.V3) {
- assertEquals("3", secondChar);
- } else {
- assertEquals("4", secondChar);
-
- //No URL-unfriendly characters allowed:
- assertEquals(-1, advertisingTokenString.indexOf('='));
- assertEquals(-1, advertisingTokenString.indexOf('+'));
- assertEquals(-1, advertisingTokenString.indexOf('/'));
- }
+ assertEquals(identityType == IdentityType.Email ? "E" : "F", firstChar);
}
+ assertEquals("4", advertisingTokenString.substring(1, 2));
+ assertEquals(-1, advertisingTokenString.indexOf('='));
+ assertEquals(-1, advertisingTokenString.indexOf('+'));
+ assertEquals(-1, advertisingTokenString.indexOf('/'));
}
RefreshToken decodeRefreshToken(EncryptedTokenEncoder encoder, String refreshTokenString, IdentityType identityType) {
@@ -4392,24 +4381,14 @@ void tokenGenerateRotatingKeysets_GENERATOR(String testRun, Vertx vertx, VertxTe
AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, IdentityType.Email);
assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId);
//Uses a key from default keyset
- int clientKeyId;
- if (advertisingToken.version == TokenVersion.V3 || advertisingToken.version == TokenVersion.V4) {
- String advertisingTokenString = body.getString("advertising_token");
- byte[] bytes = null;
- if (advertisingToken.version == TokenVersion.V3) {
- bytes = EncodingUtils.fromBase64(advertisingTokenString);
- } else if (advertisingToken.version == TokenVersion.V4) {
- bytes = Uid2Base64UrlCoder.decode(advertisingTokenString); //same as V3 but use Base64URL encoding
- }
- final Buffer b = Buffer.buffer(bytes);
- final int masterKeyId = b.getInt(2);
-
- final byte[] masterPayloadBytes = AesGcm.decrypt(bytes, 6, keysetKeyStore.getSnapshot().getKey(masterKeyId));
- final Buffer masterPayload = Buffer.buffer(masterPayloadBytes);
- clientKeyId = masterPayload.getInt(29);
- } else {
- clientKeyId = advertisingToken.publisherIdentity.clientKeyId;
- }
+ assertEquals(TokenVersion.V4, advertisingToken.version);
+ String advertisingTokenString = body.getString("advertising_token");
+ byte[] bytes = Uid2Base64UrlCoder.decode(advertisingTokenString);
+ final Buffer b = Buffer.buffer(bytes);
+ final int masterKeyId = b.getInt(2);
+ final byte[] masterPayloadBytes = AesGcm.decrypt(bytes, 6, keysetKeyStore.getSnapshot().getKey(masterKeyId));
+ final Buffer masterPayload = Buffer.buffer(masterPayloadBytes);
+ int clientKeyId = masterPayload.getInt(29);
switch (testRun) {
case "MultiKeysets":
assertEquals(1007, clientKeyId); // should encrypt with active key in default keyset