Skip to content

Commit

Permalink
Merge pull request #21 from IABTechLab/gdm-UID2-1526-add-cstg-to-decr…
Browse files Browse the repository at this point in the history
…yptionresponse

Added v3/v4 identity type and advertising token version fields in DecryptionResponse
  • Loading branch information
gmsdelmundo authored Oct 13, 2023
2 parents 4e68f23 + 30530ed commit 9441e75
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 55 deletions.
2 changes: 1 addition & 1 deletion UID2.Client.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package>
<metadata>
<id>UID2.Client</id>
<version>5.3.2</version>
<version>5.4.0</version>
<title>UID2 Client C# SDK</title>
<authors>UID2 team</authors>
<owners>UID2 team</owners>
Expand Down
2 changes: 2 additions & 0 deletions src/SampleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ static void ExampleBasicRefresh()
Console.WriteLine($"UID={result.Uid}");
Console.WriteLine($"EstablishedAt={result.Established}");
Console.WriteLine($"SiteId={result.SiteId}");
Console.WriteLine($"IdentityType={result.IdentityType}");
Console.WriteLine($"AdvertisingTokenVersion={result.AdvertisingTokenVersion}");
Console.WriteLine($"IsClientSideGenerated={result.IsClientSideGenerated}");
}

Expand Down
9 changes: 7 additions & 2 deletions src/UID2.Client/DecryptionResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ namespace UID2.Client
{
public readonly struct DecryptionResponse
{
public DecryptionResponse(DecryptionStatus status, string uid, DateTime? established, int? siteId, int? siteKeySiteId, bool? isClientSideGenerated = false)
public DecryptionResponse(DecryptionStatus status, string uid, DateTime? established, int? siteId, int? siteKeySiteId, IdentityType? identityType, int? advertisingTokenVersion,
bool? isClientSideGenerated = false)
{
Status = status;
Uid = uid;
Established = established;
SiteId = siteId;
SiteKeySiteId = siteKeySiteId;
IdentityType = identityType;
AdvertisingTokenVersion = advertisingTokenVersion;
IsClientSideGenerated = isClientSideGenerated;
}

public static DecryptionResponse MakeError(DecryptionStatus status)
{
return new DecryptionResponse(status, null, null, null, null, null);
return new DecryptionResponse(status, null, null, null, null, null, null, null);
}

public bool Success => Status == DecryptionStatus.Success;
Expand All @@ -25,6 +28,8 @@ public static DecryptionResponse MakeError(DecryptionStatus status)
public DateTime? Established { get; }
public int? SiteId { get; }
public int? SiteKeySiteId { get; }
public IdentityType? IdentityType { get; }
public int? AdvertisingTokenVersion { get; }
public bool? IsClientSideGenerated { get; }
}
}
2 changes: 1 addition & 1 deletion src/UID2.Client/IdentityType.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace UID2.Client
{
internal enum IdentityType
public enum IdentityType
{
Email = 0,
Phone = 1,
Expand Down
2 changes: 1 addition & 1 deletion src/UID2.Client/UID2Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public async Task<RefreshResponse> RefreshAsync(CancellationToken token)

private string GetAssemblyNameAndVersion()
{
var version = "5.3.2";
var version = "5.4.0";
return "uid-client-net-" + version;
}

Expand Down
39 changes: 26 additions & 13 deletions src/UID2.Client/UID2Encryption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ internal static DecryptionResponse Decrypt(string token, KeyContainer keys, Date

if (data[1] == (int)AdvertisingTokenVersion.V3)
{
return DecryptV3(Convert.FromBase64String(token), keys, now, domainName, identityScope, enableDomainNameCheck);
return DecryptV3(Convert.FromBase64String(token), keys, now, identityScope, 3, domainName, enableDomainNameCheck);
}

if (data[1] == (int)AdvertisingTokenVersion.V4)
{
//same as V3 but use Base64URL encoding
return DecryptV3(UID2Base64UrlCoder.Decode(token), keys, now, domainName, identityScope, enableDomainNameCheck);
return DecryptV3(UID2Base64UrlCoder.Decode(token), keys, now, identityScope, 4, domainName, enableDomainNameCheck);
}

return DecryptionResponse.MakeError(DecryptionStatus.VersionNotSupported);
Expand Down Expand Up @@ -93,26 +93,26 @@ private static DecryptionResponse DecryptV2(byte[] encryptedId, KeyContainer key
var expiry = DateTimeUtils.FromEpochMilliseconds(expiresMilliseconds);
if (expiry < now)
{
return new DecryptionResponse(DecryptionStatus.ExpiredToken, null, established, siteId, siteKey.SiteId, privacyBits.IsClientSideGenerated);
return new DecryptionResponse(DecryptionStatus.ExpiredToken, null, established, siteId, siteKey.SiteId, null, 2, privacyBits.IsClientSideGenerated);
}

if (privacyBits.IsOptedOut)
{
return new DecryptionResponse(DecryptionStatus.UserOptedOut, null, established, siteId, siteKey.SiteId, privacyBits.IsClientSideGenerated);
return new DecryptionResponse(DecryptionStatus.UserOptedOut, null, established, siteId, siteKey.SiteId, null, 2, privacyBits.IsClientSideGenerated);
}

if (enableDomainNameCheck && !IsDomainNameAllowedForSite(privacyBits, siteId, domainName, keys))
{
return new DecryptionResponse(DecryptionStatus.DomainNameCheckFailed, null, established, siteId, siteKey.SiteId, privacyBits.IsClientSideGenerated);
return new DecryptionResponse(DecryptionStatus.DomainNameCheckFailed, null, established, siteId, siteKey.SiteId, null, 2, privacyBits.IsClientSideGenerated);
}

return new DecryptionResponse(DecryptionStatus.Success, idString, established, siteId, siteKey.SiteId, privacyBits.IsClientSideGenerated);
return new DecryptionResponse(DecryptionStatus.Success, idString, established, siteId, siteKey.SiteId, null, 2, privacyBits.IsClientSideGenerated);
}

private static DecryptionResponse DecryptV3(byte[] encryptedId, KeyContainer keys, DateTime now,
string domainName,
IdentityScope identityScope, bool enableDomainNameCheck)
private static DecryptionResponse DecryptV3(byte[] encryptedId, KeyContainer keys, DateTime now, IdentityScope identityScope, int advertisingTokenVersion, string domainName, bool enableDomainNameCheck)
{
IdentityType identityType = GetIdentityType(encryptedId);

var reader = new BigEndianByteReader(new MemoryStream(encryptedId));

var prefix = reader.ReadByte();
Expand Down Expand Up @@ -170,20 +170,21 @@ private static DecryptionResponse DecryptV3(byte[] encryptedId, KeyContainer key
var expiry = DateTimeUtils.FromEpochMilliseconds(expiresMilliseconds);
if (expiry < now)
{
return new DecryptionResponse(DecryptionStatus.ExpiredToken, null, established, siteId, siteKey.SiteId, privacyBits.IsClientSideGenerated);
return new DecryptionResponse(DecryptionStatus.ExpiredToken, null, established, siteId, siteKey.SiteId, identityType, advertisingTokenVersion, privacyBits.IsClientSideGenerated);
}

if (privacyBits.IsOptedOut)
{
return new DecryptionResponse(DecryptionStatus.UserOptedOut, null, established, siteId, siteKey.SiteId, privacyBits.IsClientSideGenerated);
return new DecryptionResponse(DecryptionStatus.UserOptedOut, null, established, siteId, siteKey.SiteId, identityType, advertisingTokenVersion, privacyBits.IsClientSideGenerated);
}

if (enableDomainNameCheck && !IsDomainNameAllowedForSite(privacyBits, siteId, domainName, keys))
{
return new DecryptionResponse(DecryptionStatus.DomainNameCheckFailed, null, established, siteId, siteKey.SiteId, privacyBits.IsClientSideGenerated);
return new DecryptionResponse(DecryptionStatus.DomainNameCheckFailed, null, established, siteId, siteKey.SiteId, identityType, advertisingTokenVersion,
privacyBits.IsClientSideGenerated);
}

return new DecryptionResponse(DecryptionStatus.Success, idString, established, siteId, siteKey.SiteId, privacyBits.IsClientSideGenerated);
return new DecryptionResponse(DecryptionStatus.Success, idString, established, siteId, siteKey.SiteId, identityType, advertisingTokenVersion, privacyBits.IsClientSideGenerated);
}

private static bool IsDomainNameAllowedForSite(PrivacyBits privacyBits, int siteId, string domainName, KeyContainer keys)
Expand Down Expand Up @@ -468,5 +469,17 @@ private static IdentityScope DecodeIdentityScopeV3(byte value)
{
return (IdentityScope)((value >> 4) & 1);
}

private static IdentityType GetIdentityType(byte[] encryptedId)
{
// For specifics about the bitwise logic, check:
// Confluence - UID2-79 UID2 Token v3/v4 and Raw UID2 format v3
// In the base64-encoded version of encryptedId, the first character is always either A/B/E/F.
// After converting to binary and performing the AND operation against 1100,the result is always 0X00.
// So just bitshift right twice to get 000X, which results in either 0 or 1.
byte idType = encryptedId[0];
byte piiType = (byte)((idType & 0b_1100) >> 2);
return piiType == 0 ? IdentityType.Email : IdentityType.Phone;
}
}
}
15 changes: 9 additions & 6 deletions test/UID2.Client.Test/EncryptionTestsV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ public void SmokeTest()
{
var refreshResult = _client.RefreshJson(KeySetToJson(MASTER_KEY, SITE_KEY));
Assert.True(refreshResult.Success);

var res = _client.Decrypt(_tokenBuilder.Build(), NOW);
Assert.True(res.Success);
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
Assert.Null(res.IdentityType);
Assert.Equal(2, res.AdvertisingTokenVersion);
}

[Fact]
Expand Down Expand Up @@ -48,7 +51,7 @@ public void TokenIsCstgDerivedTest(string domainName)
Assert.True(res.IsClientSideGenerated);
Assert.True(res.Success);
Assert.Equal(DecryptionStatus.Success, res.Status);
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
}

[Theory]
Expand Down Expand Up @@ -86,7 +89,7 @@ public void TokenIsCstgDerivedNoDomainNameTest()
Assert.True(res.IsClientSideGenerated);
Assert.True(res.Success);
Assert.Equal(DecryptionStatus.Success, res.Status);
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
}

[Theory]
Expand All @@ -104,7 +107,7 @@ public void TokenIsNotCstgDerivedDomainNameSuccessTest(string domainName)
Assert.False(res.IsClientSideGenerated);
Assert.True(res.Success);
Assert.Equal(DecryptionStatus.Success, res.Status);
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
}

[Fact]
Expand Down Expand Up @@ -156,13 +159,13 @@ public void TokenExpiryAndCustomNow()
var expiry = NOW.AddDays(-60);

_client.RefreshJson(KeySetToJson(MASTER_KEY, SITE_KEY));
var advertisingToken = _tokenBuilder.WithExpiry(expiry).Build();;
var advertisingToken = _tokenBuilder.WithExpiry(expiry).Build();

var res = _client.Decrypt(advertisingToken, expiry.AddSeconds(1));
Assert.Equal(DecryptionStatus.ExpiredToken, res.Status);

res = _client.Decrypt(advertisingToken, expiry.AddSeconds(-1));
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
}

[Fact]
Expand Down
34 changes: 29 additions & 5 deletions test/UID2.Client.Test/EncryptionTestsV3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,38 @@ public class EncryptionTestsV3
private readonly UID2Client _client = new("endpoint", "authkey", CLIENT_SECRET, IdentityScope.UID2);
private readonly AdvertisingTokenBuilder _tokenBuilder = AdvertisingTokenBuilder.Builder().WithVersion(AdvertisingTokenBuilder.TokenVersion.V3);

[Theory]
[InlineData(EXAMPLE_EMAIL_RAW_UID2_V2, nameof(IdentityScope.UID2), IdentityType.Email)]
[InlineData(EXAMPLE_PHONE_RAW_UID2_V3, nameof(IdentityScope.UID2), IdentityType.Phone)]
[InlineData(EXAMPLE_EMAIL_RAW_UID2_V2, nameof(IdentityScope.EUID), IdentityType.Email)]
[InlineData(EXAMPLE_PHONE_RAW_UID2_V3, nameof(IdentityScope.EUID), IdentityType.Phone)]
public void IdentityScopeAndType_TestCases(String uid, string identityScope, IdentityType? identityType)
{
var client = new UID2Client("ep", "ak", CLIENT_SECRET, Enum.Parse<IdentityScope>(identityScope));
var refreshResult = client.RefreshJson(KeySetToJson(MASTER_KEY, SITE_KEY));
Assert.True(refreshResult.Success);

string advertisingToken = identityScope == "UID2"
? UID2TokenGenerator.GenerateUid2TokenV3(uid, MASTER_KEY, SITE_ID, SITE_KEY, UID2TokenGenerator.DefaultParams)
: UID2TokenGenerator.GenerateEuidTokenV3(uid, MASTER_KEY, SITE_ID, SITE_KEY);
var res = client.Decrypt(advertisingToken, NOW);
Assert.True(res.Success);
Assert.Equal(uid, res.Uid);
Assert.Equal(identityType, res.IdentityType);
Assert.Equal(3, res.AdvertisingTokenVersion);
}

[Fact]
public void SmokeTest()
{
var refreshResult = _client.RefreshJson(KeySetToJson(MASTER_KEY, SITE_KEY));
Assert.True(refreshResult.Success);

var res = _client.Decrypt(_tokenBuilder.Build(), NOW);
Assert.True(res.Success);
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
Assert.Equal(IdentityType.Email, res.IdentityType);
Assert.Equal(3, res.AdvertisingTokenVersion);
}

[Fact]
Expand Down Expand Up @@ -47,7 +71,7 @@ public void TokenIsCstgDerivedTest(string domainName)
Assert.True(res.IsClientSideGenerated);
Assert.True(res.Success);
Assert.Equal(DecryptionStatus.Success, res.Status);
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
}

[Theory]
Expand Down Expand Up @@ -85,7 +109,7 @@ public void TokenIsCstgDerivedNoDomainNameTest()
Assert.True(res.IsClientSideGenerated);
Assert.True(res.Success);
Assert.Equal(DecryptionStatus.Success, res.Status);
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
}

[Theory]
Expand All @@ -103,7 +127,7 @@ public void TokenIsNotCstgDerivedDomainNameSuccessTest(string domainName)
Assert.False(res.IsClientSideGenerated);
Assert.True(res.Success);
Assert.Equal(DecryptionStatus.Success, res.Status);
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
}

[Fact]
Expand Down Expand Up @@ -161,7 +185,7 @@ public void TokenExpiryAndCustomNow()
Assert.Equal(DecryptionStatus.ExpiredToken, res.Status);

res = _client.Decrypt(advertisingToken, expiry.AddSeconds(-1));
Assert.Equal(EXAMPLE_UID, res.Uid);
Assert.Equal(EXAMPLE_EMAIL_RAW_UID2_V2, res.Uid);
}

[Fact]
Expand Down
Loading

0 comments on commit 9441e75

Please sign in to comment.