Skip to content

Commit 34b682b

Browse files
fixed failed validation logs to log only for successful attestation
2 parents d9478ef + c51dfbf commit 34b682b

File tree

14 files changed

+268
-108
lines changed

14 files changed

+268
-108
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>9.1.17-alpha-219-SNAPSHOT</version>
8+
<version>9.2.3</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/middleware/AttestationMiddleware.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,21 +105,15 @@ public void handle(RoutingContext rc) {
105105
} else {
106106
if (this.enforceJwt) {
107107
LOGGER.info("JWT is required, but was not received. Attestation validation failed. SiteId: {}, Name: {}, Contact: {}", operatorKey.getSiteId(), operatorKey.getName(), operatorKey.getContact());
108-
} else {
109-
LOGGER.info("JWT is blank but is not required. SiteId: {}, Name: {}, Contact: {}", operatorKey.getSiteId(), operatorKey.getName(), operatorKey.getContact());
110108
}
111109
}
112-
} else {
113-
LOGGER.info("Attestation was not successful. JWT validation failed. SiteId: {}, Name: {}, Contact: {}", operatorKey.getSiteId(), operatorKey.getName(), operatorKey.getContact());
114110
}
115-
} else {
116-
LOGGER.info("Profile is not an OperatorKey. JWT validation failed. SiteId: {}, Contact: {}", profile.getSiteId(), profile.getContact());
117111
}
118112

119-
if (!isJwtValid && this.enforceJwt) {
113+
if (success && !isJwtValid && this.enforceJwt) {
120114
LOGGER.info("JWT validation has failed.");
121115
success = false;
122-
} else if (!isJwtValid && !this.enforceJwt) {
116+
} else if (success && !isJwtValid && !this.enforceJwt) {
123117
LOGGER.info("JWT validation has failed, but JWTs are not being enforced.");
124118
}
125119

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,43 @@
11
package com.uid2.shared.model;
22

3-
public class SaltEntry {
4-
private final long id;
5-
private final String hashedId;
6-
private final long lastUpdated;
7-
private final String salt;
3+
public record SaltEntry(
4+
long id,
5+
String hashedId,
6+
long lastUpdated,
7+
String currentSalt,
88

9-
public SaltEntry(long id, String hashedId, long lastUpdated, String salt) {
10-
this.id = id;
11-
this.lastUpdated = lastUpdated;
12-
this.hashedId = hashedId;
13-
this.salt = salt;
14-
}
15-
16-
public long getId() {
17-
return id;
18-
}
19-
20-
public String getHashedId() {
21-
return hashedId;
22-
}
9+
Long refreshFrom, // needs to be nullable until V3 Identity Map is fully rolled out
10+
String previousSalt,
2311

24-
public long getLastUpdated() {
25-
return lastUpdated;
12+
KeyMaterial currentKey,
13+
KeyMaterial previousKey
14+
) {
15+
@Override
16+
public String toString() {
17+
return "SaltEntry{" +
18+
"id=" + id +
19+
", hashedId='" + hashedId + '\'' +
20+
", lastUpdated=" + lastUpdated +
21+
", currentSalt=<REDACTED>" +
22+
", refreshFrom=" + refreshFrom +
23+
", previousSalt=<REDACTED>" +
24+
", currentKey=" + currentKey +
25+
", previousKey=" + previousKey +
26+
'}';
2627
}
2728

28-
public String getSalt() {
29-
return salt;
29+
public record KeyMaterial(
30+
int id,
31+
String key,
32+
String salt
33+
) {
34+
@Override
35+
public String toString() {
36+
return "KeyMaterial{" +
37+
"id=" + id +
38+
", key=<REDACTED>" +
39+
", currentSalt=<REDACTED>" +
40+
'}';
41+
}
3042
}
3143
}

src/main/java/com/uid2/shared/store/EncryptedRotatingSaltProvider.java renamed to src/main/java/com/uid2/shared/store/salt/EncryptedRotatingSaltProvider.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.uid2.shared.store;
1+
package com.uid2.shared.store.salt;
22

33
import com.uid2.shared.cloud.DownloadCloudStorage;
44
import com.uid2.shared.model.SaltEntry;
@@ -19,15 +19,8 @@ public EncryptedRotatingSaltProvider(DownloadCloudStorage fileStreamProvider, Ro
1919
}
2020

2121
@Override
22-
protected SaltEntry[] readInputStream(InputStream inputStream, SaltEntryBuilder entryBuilder, Integer size) throws IOException {
22+
protected SaltEntry[] readInputStream(InputStream inputStream, SaltFileParser saltFileParser, Integer size) throws IOException {
2323
String decrypted = decryptInputStream(inputStream, cloudEncryptionKeyProvider, "salts");
24-
SaltEntry[] entries = new SaltEntry[size];
25-
int idx = 0;
26-
for (String line : decrypted.split("\n")) {
27-
final SaltEntry entry = entryBuilder.toEntry(line);
28-
entries[idx] = entry;
29-
idx++;
30-
}
31-
return entries;
24+
return saltFileParser.parseFile(decrypted, size);
3225
}
3326
}

src/main/java/com/uid2/shared/store/ISaltProvider.java renamed to src/main/java/com/uid2/shared/store/salt/ISaltProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.uid2.shared.store;
1+
package com.uid2.shared.store.salt;
22

33
import com.uid2.shared.model.SaltEntry;
44
import org.slf4j.Logger;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.uid2.shared.store.salt;
2+
3+
import org.hashids.Hashids;
4+
5+
public class IdHashingScheme {
6+
private final String prefix;
7+
private final Hashids hasher;
8+
9+
public IdHashingScheme(final String prefix, final String secret) {
10+
this.prefix = prefix;
11+
this.hasher = new Hashids(secret, 9);
12+
}
13+
14+
public String encode(long id) {
15+
return prefix + this.hasher.encode(id);
16+
}
17+
}

src/main/java/com/uid2/shared/store/RotatingSaltProvider.java renamed to src/main/java/com/uid2/shared/store/salt/RotatingSaltProvider.java

Lines changed: 15 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.uid2.shared.store;
1+
package com.uid2.shared.store.salt;
22

33
import com.uid2.shared.Utils;
44
import com.uid2.shared.attest.UidCoreClient;
@@ -10,7 +10,6 @@
1010
import lombok.Getter;
1111
import org.slf4j.Logger;
1212
import org.slf4j.LoggerFactory;
13-
import org.hashids.Hashids;
1413

1514
import java.io.BufferedReader;
1615
import java.io.IOException;
@@ -41,9 +40,9 @@
4140
]
4241
}
4342
44-
2. salt file format
45-
<id>, <hash_id>, <salt>
46-
9000099,1614556800000,salt
43+
2. currentSalt file format
44+
<id>, <hash_id>, <currentSalt>
45+
9000099,1614556800000,currentSalt
4746
*/
4847
public class RotatingSaltProvider implements ISaltProvider, IMetadataVersionedStore {
4948
private static final Logger LOGGER = LoggerFactory.getLogger(RotatingSaltProvider.class);
@@ -80,14 +79,14 @@ public long getVersion(JsonObject metadata) {
8079
public long loadContent(JsonObject metadata) throws Exception {
8180
final JsonArray salts = metadata.getJsonArray("salts");
8281
final String firstLevelSalt = metadata.getString("first_level");
83-
final SaltEntryBuilder entryBuilder = new SaltEntryBuilder(
82+
final SaltFileParser saltFileParser = new SaltFileParser(
8483
new IdHashingScheme(metadata.getString("id_prefix"), metadata.getString("id_secret")));
8584
final Instant now = Instant.now();
8685
final List<SaltSnapshot> snapshots = new ArrayList<>();
8786

8887
int saltCount = 0;
8988
for (int i = 0; i < salts.size(); ++i) {
90-
final SaltSnapshot snapshot = this.loadSnapshot(salts.getJsonObject(i), firstLevelSalt, entryBuilder, now);
89+
final SaltSnapshot snapshot = this.loadSnapshot(salts.getJsonObject(i), firstLevelSalt, saltFileParser, now);
9190
if (snapshot == null) continue;
9291
snapshots.add(snapshot);
9392

@@ -120,33 +119,26 @@ public ISaltSnapshot getSnapshot(Instant asOf) {
120119
if (!snapshot.isEffective(asOf)) break;
121120
current = snapshot;
122121
}
123-
return current != null ? current : snapshots.get(snapshots.size() - 1);
122+
return current != null ? current : snapshots.getLast();
124123
}
125124

126-
private SaltSnapshot loadSnapshot(JsonObject spec, String firstLevelSalt, SaltEntryBuilder entryBuilder, Instant now) throws Exception {
125+
private SaltSnapshot loadSnapshot(JsonObject spec, String firstLevelSalt, SaltFileParser saltFileParser, Instant now) throws Exception {
127126
final Instant defaultExpires = now.plus(365, ChronoUnit.DAYS);
128127
final Instant effective = Instant.ofEpochMilli(spec.getLong("effective"));
129128
final Instant expires = Instant.ofEpochMilli(spec.getLong("expires", defaultExpires.toEpochMilli()));
130129

131130
final String path = spec.getString("location");
132131
Integer size = spec.getInteger("size");
133-
SaltEntry[] entries = readInputStream(this.contentStreamProvider.download(path), entryBuilder, size);
132+
SaltEntry[] entries = readInputStream(this.contentStreamProvider.download(path), saltFileParser, size);
134133

135134
LOGGER.info("Loaded {} salts", size);
136135
return new SaltSnapshot(effective, expires, entries, firstLevelSalt);
137136
}
138137

139-
protected SaltEntry[] readInputStream(InputStream inputStream, SaltEntryBuilder entryBuilder, Integer size) throws IOException {
138+
protected SaltEntry[] readInputStream(InputStream inputStream, SaltFileParser saltFileParser, Integer size) throws IOException {
140139
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
141-
String line;
142-
SaltEntry[] entries = new SaltEntry[size];
143-
int idx = 0;
144-
while ((line = reader.readLine()) != null) {
145-
final SaltEntry entry = entryBuilder.toEntry(line);
146-
entries[idx] = entry;
147-
idx++;
148-
}
149-
return entries;
140+
String[] saltFileLines = reader.lines().toArray(String[]::new);
141+
return saltFileParser.parseFileLines(saltFileLines, size);
150142
}
151143
}
152144

@@ -167,10 +159,10 @@ public SaltSnapshot(Instant effective, Instant expires, SaltEntry[] entries, Str
167159
this.entries = entries;
168160
this.firstLevelSalt = firstLevelSalt;
169161
if (entries.length == 1_048_576) {
170-
LOGGER.info("Total salt entries 1 million, {}, special production salt entry indexer", entries.length);
162+
LOGGER.info("Total currentSalt entries 1 million, {}, special production currentSalt entry indexer", entries.length);
171163
this.saltEntryIndexer = MILLION_ENTRY_INDEXER;
172164
} else {
173-
LOGGER.warn("Total salt entries {}, using slower mod-based indexer", entries.length);
165+
LOGGER.warn("Total currentSalt entries {}, using slower mod-based indexer", entries.length);
174166
this.saltEntryIndexer = MOD_BASED_INDEXER;
175167
}
176168
}
@@ -203,42 +195,8 @@ public SaltEntry getRotatingSalt(byte[] identity) {
203195
@Override
204196
public List<SaltEntry> getModifiedSince(Instant timestamp) {
205197
final long timestampMillis = timestamp.toEpochMilli();
206-
return Arrays.stream(this.entries).filter(e -> e.getLastUpdated() >= timestampMillis).collect(Collectors.toList());
207-
}
208-
}
209-
210-
protected static final class IdHashingScheme {
211-
private final String prefix;
212-
private final Hashids hasher;
213-
214-
public IdHashingScheme(final String prefix, final String secret) {
215-
this.prefix = prefix;
216-
this.hasher = new Hashids(secret, 9);
217-
}
218-
219-
public String encode(long id) {
220-
return prefix + this.hasher.encode(id);
198+
return Arrays.stream(this.entries).filter(e -> e.lastUpdated() >= timestampMillis).collect(Collectors.toList());
221199
}
222200
}
223201

224-
protected static final class SaltEntryBuilder {
225-
private final IdHashingScheme idHashingScheme;
226-
227-
public SaltEntryBuilder(IdHashingScheme idHashingScheme) {
228-
this.idHashingScheme = idHashingScheme;
229-
}
230-
231-
public SaltEntry toEntry(String line) {
232-
try {
233-
final String[] fields = line.split(",");
234-
final long id = Integer.parseInt(fields[0]);
235-
final String hashedId = this.idHashingScheme.encode(id);
236-
final long lastUpdated = Long.parseLong(fields[1]);
237-
final String salt = fields[2];
238-
return new SaltEntry(id, hashedId, lastUpdated, salt);
239-
} catch (Exception e) {
240-
throw new RuntimeException("Trouble parsing Salt Entry " + line, e);
241-
}
242-
}
243-
}
244202
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.uid2.shared.store.salt;
2+
3+
import com.uid2.shared.model.SaltEntry;
4+
5+
public class SaltFileParser {
6+
private final IdHashingScheme idHashingScheme;
7+
8+
public SaltFileParser(IdHashingScheme idHashingScheme) {
9+
this.idHashingScheme = idHashingScheme;
10+
}
11+
12+
public SaltEntry[] parseFile(String saltFileContent, Integer size) {
13+
var lines = saltFileContent.split("\n");
14+
return parseFileLines(lines, size);
15+
}
16+
17+
public SaltEntry[] parseFileLines(String[] saltFileLines, Integer size) {
18+
var entries = new SaltEntry[size];
19+
int lineNumber = 0;
20+
for (String line : saltFileLines) {
21+
final SaltEntry entry = parseLine(line, lineNumber);
22+
entries[lineNumber] = entry;
23+
lineNumber++;
24+
}
25+
return entries;
26+
}
27+
28+
private SaltEntry parseLine(String line, int lineNumber) {
29+
try {
30+
final String[] fields = line.split(",");
31+
final long id = Integer.parseInt(fields[0]);
32+
final String hashedId = this.idHashingScheme.encode(id);
33+
final long lastUpdated = Long.parseLong(fields[1]);
34+
final String salt = fields[2];
35+
36+
Long refreshFrom = null;
37+
String previousSalt = null;
38+
SaltEntry.KeyMaterial currentKey = null;
39+
SaltEntry.KeyMaterial previousKey = null;
40+
41+
// TODO: The fields below should stop being optional once the refresh from, previous salt
42+
// and refreshable UIDs features get rolled out in production. We can remove them one by one as necessary.
43+
// AU, 2025/04/28
44+
if (fields.length > 3) {
45+
refreshFrom = Long.parseLong(fields[3]);
46+
}
47+
if (fields.length > 4) {
48+
previousSalt = fields[4];
49+
}
50+
if (fields.length > 7) {
51+
currentKey = new SaltEntry.KeyMaterial(Integer.parseInt(fields[5]), fields[6], fields[7]);
52+
}
53+
if (fields.length > 10) {
54+
previousKey = new SaltEntry.KeyMaterial(Integer.parseInt(fields[8]), fields[9], fields[10]);
55+
}
56+
57+
return new SaltEntry(id, hashedId, lastUpdated, salt, refreshFrom, previousSalt, currentKey, previousKey);
58+
} catch (Exception e) {
59+
throw new RuntimeException("Trouble parsing Salt Entry, line number: " + lineNumber, e);
60+
}
61+
}
62+
63+
}

src/test/java/com/uid2/shared/secret/KeyHasherTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void hashKey_returnsNewHashEverytime_withRandomSalt() {
2222
KeyHashResult result2 = hasher.hashKey("test-key");
2323

2424
assertAll(
25-
"hashKey returns new hash every time with random salt",
25+
"hashKey returns new hash every time with random currentSalt",
2626
() -> assertNotEquals(result1.getHash(), result2.getHash()),
2727
() -> assertNotEquals(result1.getSalt(), result2.getSalt())
2828
);

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

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

1919
public class AttestationTokenTest {
2020
private static final String ENCRYPTION_KEY = "attestation-token-secret";
21-
private static final String SALT = "attestation-token-salt";
21+
private static final String SALT = "attestation-token-currentSalt";
2222

2323
private final Clock clock = mock(Clock.class);
2424

0 commit comments

Comments
 (0)