Skip to content

Commit e7110c7

Browse files
committed
Removing v2 and v3 advertising token
1 parent f08c77c commit e7110c7

3 files changed

Lines changed: 42 additions & 136 deletions

File tree

src/main/java/com/uid2/operator/service/EncryptedTokenEncoder.java

Lines changed: 18 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,12 @@ public EncryptedTokenEncoder(KeyManager keyManager) {
2727
}
2828

2929
public byte[] encode(AdvertisingToken t, Instant asOf) {
30+
if (t.version != TokenVersion.V4) {
31+
throw new ClientInputValidationException("Only advertising token V4 is supported");
32+
}
3033
final KeysetKey masterKey = this.keyManager.getMasterKey(asOf);
3134
final KeysetKey siteEncryptionKey = this.keyManager.getActiveKeyBySiteIdWithFallback(t.publisherIdentity.siteId, Data.AdvertisingTokenSiteId, asOf, siteKeysetStatusMetrics);
32-
33-
return t.version == TokenVersion.V2
34-
? encodeV2(t, masterKey, siteEncryptionKey)
35-
: encodeV3(t, masterKey, siteEncryptionKey); //TokenVersion.V4 also calls encodeV3() since the byte array is identical between V3 and V4
36-
}
37-
38-
private byte[] encodeV2(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteKey) {
39-
final Buffer b = Buffer.buffer();
40-
41-
b.appendByte((byte) t.version.rawVersion);
42-
b.appendInt(masterKey.getId());
43-
44-
Buffer b2 = Buffer.buffer();
45-
b2.appendLong(t.expiresAt.toEpochMilli());
46-
encodeSiteIdentityV2(b2, t.publisherIdentity, t.userIdentity, siteKey);
47-
48-
final byte[] encryptedId = AesCbc.encrypt(b2.getBytes(), masterKey).getPayload();
49-
50-
b.appendBytes(encryptedId);
51-
52-
return b.getBytes();
35+
return encodeV3(t, masterKey, siteEncryptionKey);
5336
}
5437

5538
private byte[] encodeV3(AdvertisingToken t, KeysetKey masterKey, KeysetKey siteKey) {
@@ -176,69 +159,25 @@ public AdvertisingToken decodeAdvertisingToken(String base64AdvertisingToken) {
176159
byte[] headerBytes = isBase64UrlEncoding ? Uid2Base64UrlCoder.decode(headerStr) : Base64.getDecoder().decode(headerStr);
177160

178161
if (headerBytes[0] == TokenVersion.V2.rawVersion) {
179-
final byte[] bytes = EncodingUtils.fromBase64(base64AdvertisingToken);
180-
final Buffer b = Buffer.buffer(bytes);
181-
return decodeAdvertisingTokenV2(b);
162+
throw new ClientInputValidationException("Advertising token V2 is no longer supported");
182163
}
183164

184165
//Java's byte is signed, so we convert to unsigned before checking the enum
185166
int unsignedByte = ((int) headerBytes[1]) & 0xff;
186167

187-
byte[] bytes;
188-
TokenVersion tokenVersion;
189168
if (unsignedByte == TokenVersion.V3.rawVersion) {
190-
bytes = EncodingUtils.fromBase64(base64AdvertisingToken);
191-
tokenVersion = TokenVersion.V3;
192-
} else if (unsignedByte == TokenVersion.V4.rawVersion) {
193-
bytes = Uid2Base64UrlCoder.decode(base64AdvertisingToken); //same as V3 but use Base64URL encoding
194-
tokenVersion = TokenVersion.V4;
195-
} else {
169+
throw new ClientInputValidationException("Advertising token V3 is no longer supported");
170+
}
171+
if (unsignedByte != TokenVersion.V4.rawVersion) {
196172
throw new ClientInputValidationException("Invalid advertising token version");
197173
}
198174

175+
final byte[] bytes = Uid2Base64UrlCoder.decode(base64AdvertisingToken);
199176
final Buffer b = Buffer.buffer(bytes);
200-
return decodeAdvertisingTokenV3orV4(b, bytes, tokenVersion);
201-
}
202-
203-
public AdvertisingToken decodeAdvertisingTokenV2(Buffer b) {
204-
try {
205-
final int masterKeyId = b.getInt(1);
206-
207-
final byte[] decryptedPayload = AesCbc.decrypt(b.slice(5, b.length()).getBytes(), this.keyManager.getKey(masterKeyId));
208-
209-
final Buffer b2 = Buffer.buffer(decryptedPayload);
210-
211-
final long expiresMillis = b2.getLong(0);
212-
final int siteKeyId = b2.getInt(8);
213-
214-
final byte[] decryptedSitePayload = AesCbc.decrypt(b2.slice(12, b2.length()).getBytes(), this.keyManager.getKey(siteKeyId));
215-
216-
final Buffer b3 = Buffer.buffer(decryptedSitePayload);
217-
218-
final int siteId = b3.getInt(0);
219-
final int length = b3.getInt(4);
220-
221-
final byte[] advertisingId = EncodingUtils.fromBase64(b3.slice(8, 8 + length).getBytes());
222-
223-
final int privacyBits = b3.getInt(8 + length);
224-
final long establishedMillis = b3.getLong(8 + length + 4);
225-
226-
return new AdvertisingToken(
227-
TokenVersion.V2,
228-
Instant.ofEpochMilli(establishedMillis),
229-
Instant.ofEpochMilli(expiresMillis),
230-
new OperatorIdentity(0, OperatorType.Service, 0, masterKeyId),
231-
new PublisherIdentity(siteId, siteKeyId, 0),
232-
new UserIdentity(IdentityScope.UID2, IdentityType.Email, advertisingId, privacyBits, Instant.ofEpochMilli(establishedMillis), null),
233-
siteKeyId
234-
);
235-
236-
} catch (Exception e) {
237-
throw new RuntimeException("Couldn't decode advertisingTokenV2", e);
238-
}
177+
return decodeAdvertisingTokenV4(b, bytes);
239178
}
240179

241-
public AdvertisingToken decodeAdvertisingTokenV3orV4(Buffer b, byte[] bytes, TokenVersion tokenVersion) {
180+
private AdvertisingToken decodeAdvertisingTokenV4(Buffer b, byte[] bytes) {
242181
final int masterKeyId = b.getInt(2);
243182

244183
final byte[] masterPayloadBytes = AesGcm.decrypt(bytes, 6, this.keyManager.getKey(masterKeyId));
@@ -259,15 +198,15 @@ public AdvertisingToken decodeAdvertisingTokenV3orV4(Buffer b, byte[] bytes, Tok
259198

260199
if (id.length > 32) {
261200
if (identityScope != decodeIdentityScopeV3(b.getByte(0))) {
262-
throw new ClientInputValidationException("Failed decoding advertisingTokenV3: Identity scope mismatch");
201+
throw new ClientInputValidationException("Failed decoding advertising token: Identity scope mismatch");
263202
}
264203
if (identityType != decodeIdentityTypeV3(b.getByte(0))) {
265-
throw new ClientInputValidationException("Failed decoding advertisingTokenV3: Identity type mismatch");
204+
throw new ClientInputValidationException("Failed decoding advertising token: Identity type mismatch");
266205
}
267206
}
268207

269208
return new AdvertisingToken(
270-
tokenVersion, createdAt, expiresAt, operatorIdentity, publisherIdentity,
209+
TokenVersion.V4, createdAt, expiresAt, operatorIdentity, publisherIdentity,
271210
new UserIdentity(identityScope, identityType, id, privacyBits, establishedAt, refreshedAt),
272211
siteKeyId
273212
);
@@ -329,15 +268,11 @@ public byte[] encodeV3(RefreshToken t, KeysetKey serviceKey) {
329268
return b.getBytes();
330269
}
331270

332-
private void encodeSiteIdentityV2(Buffer b, PublisherIdentity publisherIdentity, UserIdentity userIdentity, KeysetKey siteEncryptionKey) {
333-
b.appendInt(siteEncryptionKey.getId());
334-
final byte[] encryptedIdentity = encryptIdentityV2(publisherIdentity, userIdentity, siteEncryptionKey);
335-
b.appendBytes(encryptedIdentity);
336-
}
337-
338271
public static String bytesToBase64Token(byte[] advertisingTokenBytes, TokenVersion tokenVersion) {
339-
return (tokenVersion == TokenVersion.V4) ?
340-
Uid2Base64UrlCoder.encode(advertisingTokenBytes) : EncodingUtils.toBase64String(advertisingTokenBytes);
272+
if (tokenVersion != TokenVersion.V4) {
273+
throw new ClientInputValidationException("Only advertising token V4 is supported");
274+
}
275+
return Uid2Base64UrlCoder.encode(advertisingTokenBytes);
341276
}
342277

343278
@Override

src/test/java/com/uid2/operator/TokenEncodingTest.java

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
import io.vertx.core.json.JsonObject;
1717
import org.junit.Assert;
1818
import org.junit.jupiter.params.ParameterizedTest;
19-
import org.junit.jupiter.params.provider.CsvSource;
2019
import org.junit.jupiter.params.provider.EnumSource;
20+
import org.junit.jupiter.params.provider.ValueSource;
2121

2222
import java.time.Instant;
2323

@@ -86,23 +86,15 @@ void testRefreshTokenEncoding(TokenVersion tokenVersion) {
8686
}
8787

8888
@ParameterizedTest
89-
@CsvSource({
90-
"false, V4", //same as current UID2 prod (as at 2024-12-10)
91-
"true, V4", //same as current EUID prod (as at 2024-12-10)
92-
//the following combinations aren't used in any UID2/EUID environments but just testing them regardless
93-
"false, V3",
94-
"true, V3",
95-
"false, V2",
96-
"true, V2"
97-
})
98-
void testAdvertisingTokenEncodings(boolean useRawUIDv3, TokenVersion adTokenVersion) {
89+
@ValueSource(booleans = {false, true})
90+
void testAdvertisingTokenEncodings(boolean useRawUIDv3) {
9991
final EncryptedTokenEncoder encoder = new EncryptedTokenEncoder(this.keyManager);
10092
final Instant now = EncodingUtils.NowUTCMillis();
10193

10294
final byte[] rawUid = UIDOperatorVerticleTest.getRawUid(IdentityScope.UID2, IdentityType.Email, "test@example.com", useRawUIDv3);
10395

10496
final AdvertisingToken token = new AdvertisingToken(
105-
adTokenVersion,
97+
TokenVersion.V4,
10698
now,
10799
now.plusSeconds(60),
108100
new OperatorIdentity(101, OperatorType.Service, 102, 103),
@@ -111,9 +103,9 @@ void testAdvertisingTokenEncodings(boolean useRawUIDv3, TokenVersion adTokenVers
111103
);
112104

113105
final byte[] encodedBytes = encoder.encode(token, now);
114-
final AdvertisingToken decoded = encoder.decodeAdvertisingToken(EncryptedTokenEncoder.bytesToBase64Token(encodedBytes, adTokenVersion));
106+
final AdvertisingToken decoded = encoder.decodeAdvertisingToken(EncryptedTokenEncoder.bytesToBase64Token(encodedBytes, TokenVersion.V4));
115107

116-
assertEquals(adTokenVersion, decoded.version);
108+
assertEquals(TokenVersion.V4, decoded.version);
117109
assertEquals(token.createdAt, decoded.createdAt);
118110
assertEquals(token.expiresAt, decoded.expiresAt);
119111
assertTrue(token.userIdentity.matches(decoded.userIdentity));
@@ -122,7 +114,7 @@ void testAdvertisingTokenEncodings(boolean useRawUIDv3, TokenVersion adTokenVers
122114
assertEquals(token.publisherIdentity.siteId, decoded.publisherIdentity.siteId);
123115

124116
Buffer b = Buffer.buffer(encodedBytes);
125-
int keyId = b.getInt(adTokenVersion == TokenVersion.V2 ? 1 : 2); //TODO - extract master key from token should be a helper function
117+
int keyId = b.getInt(2);
126118
assertEquals(Data.MasterKeySiteId, keyManager.getSiteIdFromKeyId(keyId));
127119
}
128120
}

src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -917,28 +917,17 @@ private AdvertisingToken validateAndGetToken(EncryptedTokenEncoder encoder, Json
917917
}
918918

919919
public static void validateAdvertisingToken(String advertisingTokenString, TokenVersion tokenVersion, IdentityScope identityScope, IdentityType identityType) {
920-
if (tokenVersion == TokenVersion.V2) {
921-
assertEquals("Ag", advertisingTokenString.substring(0, 2));
920+
assertEquals(TokenVersion.V4, tokenVersion);
921+
String firstChar = advertisingTokenString.substring(0, 1);
922+
if (identityScope == IdentityScope.UID2) {
923+
assertEquals(identityType == IdentityType.Email ? "A" : "B", firstChar);
922924
} else {
923-
String firstChar = advertisingTokenString.substring(0, 1);
924-
if (identityScope == IdentityScope.UID2) {
925-
assertEquals(identityType == IdentityType.Email ? "A" : "B", firstChar);
926-
} else {
927-
assertEquals(identityType == IdentityType.Email ? "E" : "F", firstChar);
928-
}
929-
930-
String secondChar = advertisingTokenString.substring(1, 2);
931-
if (tokenVersion == TokenVersion.V3) {
932-
assertEquals("3", secondChar);
933-
} else {
934-
assertEquals("4", secondChar);
935-
936-
//No URL-unfriendly characters allowed:
937-
assertEquals(-1, advertisingTokenString.indexOf('='));
938-
assertEquals(-1, advertisingTokenString.indexOf('+'));
939-
assertEquals(-1, advertisingTokenString.indexOf('/'));
940-
}
925+
assertEquals(identityType == IdentityType.Email ? "E" : "F", firstChar);
941926
}
927+
assertEquals("4", advertisingTokenString.substring(1, 2));
928+
assertEquals(-1, advertisingTokenString.indexOf('='));
929+
assertEquals(-1, advertisingTokenString.indexOf('+'));
930+
assertEquals(-1, advertisingTokenString.indexOf('/'));
942931
}
943932

944933
RefreshToken decodeRefreshToken(EncryptedTokenEncoder encoder, String refreshTokenString, IdentityType identityType) {
@@ -4392,24 +4381,14 @@ void tokenGenerateRotatingKeysets_GENERATOR(String testRun, Vertx vertx, VertxTe
43924381
AdvertisingToken advertisingToken = validateAndGetToken(encoder, body, IdentityType.Email);
43934382
assertEquals(clientSiteId, advertisingToken.publisherIdentity.siteId);
43944383
//Uses a key from default keyset
4395-
int clientKeyId;
4396-
if (advertisingToken.version == TokenVersion.V3 || advertisingToken.version == TokenVersion.V4) {
4397-
String advertisingTokenString = body.getString("advertising_token");
4398-
byte[] bytes = null;
4399-
if (advertisingToken.version == TokenVersion.V3) {
4400-
bytes = EncodingUtils.fromBase64(advertisingTokenString);
4401-
} else if (advertisingToken.version == TokenVersion.V4) {
4402-
bytes = Uid2Base64UrlCoder.decode(advertisingTokenString); //same as V3 but use Base64URL encoding
4403-
}
4404-
final Buffer b = Buffer.buffer(bytes);
4405-
final int masterKeyId = b.getInt(2);
4406-
4407-
final byte[] masterPayloadBytes = AesGcm.decrypt(bytes, 6, keysetKeyStore.getSnapshot().getKey(masterKeyId));
4408-
final Buffer masterPayload = Buffer.buffer(masterPayloadBytes);
4409-
clientKeyId = masterPayload.getInt(29);
4410-
} else {
4411-
clientKeyId = advertisingToken.publisherIdentity.clientKeyId;
4412-
}
4384+
assertEquals(TokenVersion.V4, advertisingToken.version);
4385+
String advertisingTokenString = body.getString("advertising_token");
4386+
byte[] bytes = Uid2Base64UrlCoder.decode(advertisingTokenString);
4387+
final Buffer b = Buffer.buffer(bytes);
4388+
final int masterKeyId = b.getInt(2);
4389+
final byte[] masterPayloadBytes = AesGcm.decrypt(bytes, 6, keysetKeyStore.getSnapshot().getKey(masterKeyId));
4390+
final Buffer masterPayload = Buffer.buffer(masterPayloadBytes);
4391+
int clientKeyId = masterPayload.getInt(29);
44134392
switch (testRun) {
44144393
case "MultiKeysets":
44154394
assertEquals(1007, clientKeyId); // should encrypt with active key in default keyset

0 commit comments

Comments
 (0)