Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
name: c-sharp
version: "8.3.2"
version: "8.3.3"
schema: 1
scm: github.com/pubnub/c-sharp
changelog:
- date: 2026-07-20
version: v8.3.3
changes:
- type: feature
text: "Obscure exceptions thrown by cryptors to be available in logs only."
- date: 2026-07-15
version: v8.3.2
changes:
Expand Down Expand Up @@ -1004,14 +1009,14 @@ features:
- QUERY-PARAM
supported-platforms:
-
version: Pubnub 'C#' 8.3.2
version: Pubnub 'C#' 8.3.3
platforms:
- Windows 10 and up
- Windows Server 2008 and up
frameworks:
- .Net Framework 4.5+
-
version: PubnubPCL 'C#' 8.3.2
version: PubnubPCL 'C#' 8.3.3
platforms:
- Xamarin.Android
- Xamarin.iOS
Expand All @@ -1023,7 +1028,7 @@ supported-platforms:
frameworks:
- .Net 4.5+
-
version: PubnubUWP 'C#' 8.3.2
version: PubnubUWP 'C#' 8.3.3
platforms:
- Windows Phone 10
- Universal Windows Apps
Expand All @@ -1047,7 +1052,7 @@ sdks:
distribution-type: source
distribution-repository: GitHub
package-name: Pubnub
location: https://github.com/pubnub/c-sharp/releases/tag/v8.3.2
location: https://github.com/pubnub/c-sharp/releases/tag/v8.3.3
requires:
-
name: ".Net"
Expand Down Expand Up @@ -1288,7 +1293,7 @@ sdks:
distribution-type: source
distribution-repository: GitHub
package-name: PubNubPCL
location: https://github.com/pubnub/c-sharp/releases/tag/v8.3.2
location: https://github.com/pubnub/c-sharp/releases/tag/v8.3.3
requires:
-
name: ".Net"
Expand Down Expand Up @@ -1639,7 +1644,7 @@ sdks:
distribution-type: source
distribution-repository: GitHub
package-name: PubnubUWP
location: https://github.com/pubnub/c-sharp/releases/tag/v8.3.2
location: https://github.com/pubnub/c-sharp/releases/tag/v8.3.3
requires:
-
name: "Universal Windows Platform Development"
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
v8.3.3 - July 20 2026
-----------------------------
- Added: obscure exceptions thrown by cryptors to be available in logs only.

v8.3.2 - July 15 2026
-----------------------------
- Fixed: added additional checks to ensure that post-reconnection handshaking doesn't start with null values of Channels and ChannelGroups.
Expand Down
4 changes: 3 additions & 1 deletion src/Api/PubnubApi/Model/PNStatusCategoryHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,10 @@ public static PNStatusCategory GetPNStatusCategory(Exception ex)
{
ret = PNStatusCategory.PNNetworkIssuesCategory;
}
else if (errorType == "System.Security.Cryptography.CryptographicException" && errorMessage == "Padding is invalid and cannot be removed.")
else if (errorType == "PubnubApi.PNException" && errorMessage == "Decrypt Error")
{
// All crypto failure modes now surface as a single generic "Decrypt Error" so that
// bad padding and wrong block length map to the same category (no padding-oracle bit).
ret = PNStatusCategory.PNDecryptionErrorCategory;
}
else if (errorType == "System.Runtime.InteropServices.SEHException" && errorMessage == "External component has thrown an exception.")
Expand Down
4 changes: 2 additions & 2 deletions src/Api/PubnubApi/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
[assembly: AssemblyProduct("Pubnub C# SDK")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("8.3.2")]
[assembly: AssemblyFileVersion("8.3.2")]
[assembly: AssemblyVersion("8.3.3")]
[assembly: AssemblyFileVersion("8.3.3")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
Expand Down
4 changes: 2 additions & 2 deletions src/Api/PubnubApi/PubnubApi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@

<PropertyGroup>
<PackageId>Pubnub</PackageId>
<PackageVersion>8.3.2</PackageVersion>
<PackageVersion>8.3.3</PackageVersion>
<Title>PubNub C# .NET - Web Data Push API</Title>
<Authors>Pandu Masabathula</Authors>
<Owners>PubNub</Owners>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIconUrl>http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png</PackageIconUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/pubnub/c-sharp/</RepositoryUrl>
<PackageReleaseNotes>Added additional checks to ensure that post-reconnection handshaking doesn't start with null values of Channels and ChannelGroups.</PackageReleaseNotes>
<PackageReleaseNotes>Obscure exceptions thrown by cryptors to be available in logs only.</PackageReleaseNotes>
<PackageTags>Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing</PackageTags>
<!--<Summary>PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously</Summary>-->
<Description>PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously</Description>
Expand Down
4 changes: 2 additions & 2 deletions src/Api/PubnubApi/Security/Crypto/Cryptors/AesCbcCryptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public override string Decrypt(string encryptedData)
catch(Exception ex)
{
logger?.Error($"AesCbcCryptor Error while decrypting string data. ErrorMessage {ex.Message}, StackTrace {ex.StackTrace}");
throw new PNException("Decrypt Error", ex);
throw new PNException("Decrypt Error");
}
}
public override byte[] Decrypt(byte[] encryptedData)
Expand Down Expand Up @@ -139,7 +139,7 @@ public override byte[] Decrypt(byte[] encryptedData)
catch (Exception ex)
{
logger?.Error($"AesCbcCryptor Error while decrypting bytes. ErrorMessage {ex.Message}, StackTrace {ex.StackTrace}");
throw new PNException("Decrypt Error", ex);
throw new PNException("Decrypt Error");
}
}

Expand Down
16 changes: 14 additions & 2 deletions src/Api/PubnubApi/Security/Crypto/Cryptors/CryptorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,20 @@ protected byte[] InternalDecrypt(byte[] dataBytes, byte[] ivBytes, byte[] keyByt

using(ICryptoTransform decrypto = aesAlg.CreateDecryptor())
{
byte[] buffer = decrypto.TransformFinalBlock(dataBytes, 0, dataBytes.Length);
return buffer;
try
{
byte[] buffer = decrypto.TransformFinalBlock(dataBytes, 0, dataBytes.Length);
return buffer;
}
catch (CryptographicException ex)
{
// Collapse every crypto failure mode (bad padding, wrong block length, etc.)
// into one generic error. The native message distinguishes these cases and
// would otherwise expose a padding-oracle bit to a caller submitting ciphertexts.
// The specific cause is kept in the internal log only, never on the thrown exception.
logger?.Error($"Decryption failed: {ex.Message}");
throw new PNException("Decrypt Error");
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Api/PubnubApi/Security/Crypto/Cryptors/LegacyCryptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public override string Decrypt(string encryptedData)
catch(Exception ex)
{
logger?.Error($"LegacyCryptor Error while decrypting string data. ErrorMessage {ex.Message}, StackTrace {ex.StackTrace}");
throw new PNException("Decrypt Error", ex);
throw new PNException("Decrypt Error");
}
}
public override byte[] Decrypt(byte[] encryptedData)
Expand Down Expand Up @@ -144,7 +144,7 @@ public override byte[] Decrypt(byte[] encryptedData)
catch(Exception ex)
{
logger?.Error($"LegacyCryptor Error while decrypting bytes. ErrorMessage {ex.Message}, StackTrace {ex.StackTrace}");
throw new PNException("Decrypt Error", ex);
throw new PNException("Decrypt Error");
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/Api/PubnubApi/Security/PubnubCrypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,10 @@ protected override byte[] EncryptOrDecrypt(bool type, byte[] dataBytes, bool dyn
{
LoggingMethod.WriteToLog(pubnubLog, $"[{DateTime.Now.ToString(CultureInfo.InvariantCulture)}] Error: Decryption Error. {ex}", config.LogVerbosity);
}
throw;
// Do not re-throw the raw CryptographicException: its native message (and the
// resulting PNStatusCategory) distinguishes bad padding from wrong block length,
// which is a padding-oracle signal. Surface a single generic error instead.
throw new PNException("Decrypt Error");
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Api/PubnubApiPCL/PubnubApiPCL.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@

<PropertyGroup>
<PackageId>PubnubPCL</PackageId>
<PackageVersion>8.3.2</PackageVersion>
<PackageVersion>8.3.3</PackageVersion>
<Title>PubNub C# .NET - Web Data Push API</Title>
<Authors>Pandu Masabathula</Authors>
<Owners>PubNub</Owners>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIconUrl>http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png</PackageIconUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/pubnub/c-sharp/</RepositoryUrl>
<PackageReleaseNotes>Added additional checks to ensure that post-reconnection handshaking doesn't start with null values of Channels and ChannelGroups.</PackageReleaseNotes>
<PackageReleaseNotes>Obscure exceptions thrown by cryptors to be available in logs only.</PackageReleaseNotes>
<PackageTags>Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing</PackageTags>
<!--<Summary>PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously</Summary>-->
<Description>PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously</Description>
Expand Down
4 changes: 2 additions & 2 deletions src/Api/PubnubApiUWP/PubnubApiUWP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@

<PropertyGroup>
<PackageId>PubnubUWP</PackageId>
<PackageVersion>8.3.2</PackageVersion>
<PackageVersion>8.3.3</PackageVersion>
<Title>PubNub C# .NET - Web Data Push API</Title>
<Authors>Pandu Masabathula</Authors>
<Owners>PubNub</Owners>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageIconUrl>http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png</PackageIconUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/pubnub/c-sharp/</RepositoryUrl>
<PackageReleaseNotes>Added additional checks to ensure that post-reconnection handshaking doesn't start with null values of Channels and ChannelGroups.</PackageReleaseNotes>
<PackageReleaseNotes>Obscure exceptions thrown by cryptors to be available in logs only.</PackageReleaseNotes>
<PackageTags>Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing</PackageTags>
<!--<Summary>PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously</Summary>-->
<Description>PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously</Description>
Expand Down
2 changes: 1 addition & 1 deletion src/Api/PubnubApiUnity/PubnubApiUnity.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<PropertyGroup>
<PackageId>PubnubApiUnity</PackageId>
<PackageVersion>8.3.2</PackageVersion>
<PackageVersion>8.3.3</PackageVersion>
<Title>PubNub C# .NET - Web Data Push API</Title>
<Authors>Pandu Masabathula</Authors>
<Owners>PubNub</Owners>
Expand Down
82 changes: 82 additions & 0 deletions src/UnitTests/PubnubApi.Tests/EncryptionTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using NUnit.Framework;
Expand Down Expand Up @@ -1305,6 +1306,87 @@ public void TestHistoryDecryptionOnNonEncryptedMessage()
Assert.True(passed);
}

/// <summary>
/// Padding-oracle hardening regression test.
/// Bad-padding and wrong-length ciphertexts must produce the exact same generic error,
/// with no distinguishing message, exception type, or inner (native) exception.
/// </summary>
[Test]
public void TestDecryptFailureModesAreIndistinguishable()
{
LegacyCryptor legacy = new LegacyCryptor("enigma", false);
AesCbcCryptor aes = new AesCbcCryptor("enigma");

// Legacy (static IV): 24 raw bytes are never block-aligned -> "wrong final block length".
PNException legacyWrongLength = CaptureDecryptFailure(() =>
legacy.Decrypt(Convert.ToBase64String(RandomBytes(24))));
// Legacy (static IV): a block-aligned input that fails PKCS7 unpadding -> "bad decrypt".
PNException legacyBadPadding = CaptureDecryptFailure(() =>
legacy.Decrypt(FindLegacyBadPaddingInput(legacy)));

// AES-CBC: tamper a valid ciphertext produced via round-trip encryption.
string validAes = aes.Encrypt("hello world - a message spanning more than one block");
byte[] validAesBytes = Convert.FromBase64String(validAes);
// Drop 5 trailing bytes so the ciphertext body is no longer block-aligned.
string aesWrongLengthInput = Convert.ToBase64String(validAesBytes.Take(validAesBytes.Length - 5).ToArray());
PNException aesWrongLength = CaptureDecryptFailure(() => aes.Decrypt(aesWrongLengthInput));
PNException aesBadPadding = CaptureDecryptFailure(() => aes.Decrypt(FindAesBadPaddingInput(aes, validAesBytes)));

foreach (var ex in new[] { legacyWrongLength, legacyBadPadding, aesWrongLength, aesBadPadding })
{
Assert.AreEqual("Decrypt Error", ex.Message, "Decrypt error message must be generic");
Assert.IsNull(ex.InnerException, "Native crypto exception must not be surfaced as inner exception");
StringAssert.DoesNotContain("Padding", ex.ToString());
StringAssert.DoesNotContain("block", ex.ToString());
}

// The whole point: bad padding and wrong length are not distinguishable at the call site.
Assert.AreEqual(legacyBadPadding.Message, legacyWrongLength.Message);
Assert.AreEqual(aesBadPadding.Message, aesWrongLength.Message);
Assert.AreEqual(legacyBadPadding.GetType(), legacyWrongLength.GetType());
Assert.AreEqual(aesBadPadding.GetType(), aesWrongLength.GetType());
}

private static PNException CaptureDecryptFailure(TestDelegate act)
{
return Assert.Throws<PNException>(act);
}

private static byte[] RandomBytes(int count)
{
byte[] buffer = new byte[count];
new Random().NextBytes(buffer);
return buffer;
}

private static string FindLegacyBadPaddingInput(LegacyCryptor legacy)
{
for (int attempt = 0; attempt < 2000; attempt++)
{
string candidate = Convert.ToBase64String(RandomBytes(16));
try { legacy.Decrypt(candidate); }
catch (PNException) { return candidate; }
}
Assert.Fail("Could not construct a block-aligned legacy input that fails unpadding");
return null;
}

private static string FindAesBadPaddingInput(AesCbcCryptor aes, byte[] validAesBytes)
{
Random rng = new Random();
for (int attempt = 0; attempt < 2000; attempt++)
{
byte[] tampered = (byte[])validAesBytes.Clone();
// Corrupt the final ciphertext byte so the last plaintext block's padding is (almost always) invalid.
tampered[tampered.Length - 1] ^= (byte)(rng.Next(1, 256));
string candidate = Convert.ToBase64String(tampered);
try { aes.Decrypt(candidate); }
catch (PNException) { return candidate; }
}
Assert.Fail("Could not construct an AES-CBC input that fails unpadding");
return null;
}

private PNConfiguration CreateTestConfig()
{
PNConfiguration config = new PNConfiguration(new UserId("test"));
Expand Down
Loading