From 447c1e027fb5ed80dd94b798cd57038905a9cf03 Mon Sep 17 00:00:00 2001 From: titus Date: Thu, 2 Jan 2025 16:24:09 +0100 Subject: [PATCH] Initial Commit --- README.md | 2 - csplayready.sln | 16 - csplayready/Cdm.cs | 248 ---------- csplayready/Exceptions.cs | 38 -- csplayready/Program.cs | 150 ------ csplayready/Utils.cs | 122 ----- csplayready/constructcs/BinaryParser.cs | 613 ------------------------ csplayready/constructcs/Enums.cs | 8 - csplayready/crypto/Crypto.cs | 67 --- csplayready/crypto/EccKey.cs | 94 ---- csplayready/crypto/ElGamal.cs | 30 -- csplayready/csplayready.csproj | 16 - csplayready/device/Device.cs | 95 ---- csplayready/license/Key.cs | 31 -- csplayready/license/XmlKey.cs | 33 -- csplayready/license/XmrLicense.cs | 255 ---------- csplayready/system/BCert.cs | 505 ------------------- csplayready/system/PSSH.cs | 59 --- csplayready/system/Session.cs | 14 - 19 files changed, 2396 deletions(-) delete mode 100644 README.md delete mode 100644 csplayready.sln delete mode 100644 csplayready/Cdm.cs delete mode 100644 csplayready/Exceptions.cs delete mode 100644 csplayready/Program.cs delete mode 100644 csplayready/Utils.cs delete mode 100644 csplayready/constructcs/BinaryParser.cs delete mode 100644 csplayready/constructcs/Enums.cs delete mode 100644 csplayready/crypto/Crypto.cs delete mode 100644 csplayready/crypto/EccKey.cs delete mode 100644 csplayready/crypto/ElGamal.cs delete mode 100644 csplayready/csplayready.csproj delete mode 100644 csplayready/device/Device.cs delete mode 100644 csplayready/license/Key.cs delete mode 100644 csplayready/license/XmlKey.cs delete mode 100644 csplayready/license/XmrLicense.cs delete mode 100644 csplayready/system/BCert.cs delete mode 100644 csplayready/system/PSSH.cs delete mode 100644 csplayready/system/Session.cs diff --git a/README.md b/README.md deleted file mode 100644 index ea61ab0..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# csplayready -C# implementation of Microsoft's Playready DRM CDM (Content Decryption Module) diff --git a/csplayready.sln b/csplayready.sln deleted file mode 100644 index 71ce3e6..0000000 --- a/csplayready.sln +++ /dev/null @@ -1,16 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csplayready", "csplayready\csplayready.csproj", "{8480D9A6-2F92-4913-BCF1-6DD2A0814457}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8480D9A6-2F92-4913-BCF1-6DD2A0814457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8480D9A6-2F92-4913-BCF1-6DD2A0814457}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8480D9A6-2F92-4913-BCF1-6DD2A0814457}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8480D9A6-2F92-4913-BCF1-6DD2A0814457}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/csplayready/Cdm.cs b/csplayready/Cdm.cs deleted file mode 100644 index a478df1..0000000 --- a/csplayready/Cdm.cs +++ /dev/null @@ -1,248 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using System.Xml.Linq; -using csplayready.crypto; -using csplayready.device; -using csplayready.license; -using csplayready.system; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using ECPoint = Org.BouncyCastle.Math.EC.ECPoint; - -namespace csplayready; - -public class Cdm -{ - public static readonly int MaxNumOfSessions = 16; - - private static readonly byte[] RawWmrmEcc256PubKey = - "c8b6af16ee941aadaa5389b4af2c10e356be42af175ef3face93254e7b0b3d9b982b27b5cb2341326e56aa857dbfd5c634ce2cf9ea74fca8f2af5957efeea562" - .HexToBytes(); - - private readonly ECPoint _wmrmEcc256PubKey; - - private readonly Dictionary _sessions = []; - - private CertificateChain _certificateChain; - private EccKey _encryptionKey; - private EccKey _signingKey; - - public Cdm(CertificateChain certificateChain, EccKey encryptionKey, EccKey signingKey) - { - _certificateChain = certificateChain; - _encryptionKey = encryptionKey; - _signingKey = signingKey; - - var curve = ECNamedCurveTable.GetByName("secp256r1").Curve; - _wmrmEcc256PubKey = curve.CreatePoint( - new BigInteger(1, RawWmrmEcc256PubKey[..32]), - new BigInteger(1, RawWmrmEcc256PubKey[32..]) - ); - } - - public static Cdm FromDevice(Device device) - { - return new Cdm(device.GroupCertificate!, device.EncryptionKey!, device.SigningKey!); - } - - public int Open() - { - if (_sessions.Count > MaxNumOfSessions) - throw new TooManySessions($"Too many Sessions open ({MaxNumOfSessions})."); - - var session = new Session(_sessions.Count + 1); - _sessions[session.Id] = session; - - return session.Id; - } - - public void Close(int sessionId) - { - if (!_sessions.Remove(sessionId)) - throw new InvalidSession($"Session identifier {sessionId} is invalid."); - } - - private byte[] GetKeyData(Session session) - { - (ECPoint point1, ECPoint point2) = Crypto.Ecc256Encrypt(session.XmlKey.GetPoint(), _wmrmEcc256PubKey); - return point1.ToBytes().Concat(point2.ToBytes()).ToArray(); - } - - private byte[] GetCipherData(Session session) - { - var b64Chain = Convert.ToBase64String(_certificateChain.Dumps()); - var body = $"{b64Chain}"; - - var ciphertext = Crypto.AesCbcEncrypt(session.XmlKey.AesKey, session.XmlKey.AesIv, Encoding.UTF8.GetBytes(body)); - return session.XmlKey.AesIv.Concat(ciphertext).ToArray(); - } - - private static string GetDigestContent(string wrmHeader, string nonce, string encryptedKey, string encryptedCert) - { - TimeSpan t = DateTime.UtcNow - new DateTime(1970, 1, 1); - var secondsSinceEpoch = (int)t.TotalSeconds; - - return - "" + - "1" + - $"{wrmHeader}" + - "" + - "10.0.16384.10011" + - "" + - $"{nonce}" + - $"{secondsSinceEpoch}" + - "" + - "" + - "" + - "" + - "" + - "" + - "WMRMServer" + - "" + - "" + - $"{encryptedKey}" + - "" + - "" + - "" + - "" + - $"{encryptedCert}" + - "" + - "" + - ""; - } - - private static string GetSignedInfo(string digestValue) - { - return - "" + - "" + - "" + - "" + - "" + - $"{digestValue}" + - "" + - ""; - } - - public string GetLicenseChallenge(int sessionId, string wrmHeader) - { - if (!_sessions.TryGetValue(sessionId, out Session? session)) - throw new InvalidSession($"Session identifier {sessionId} is invalid."); - - session.SigningKey = _signingKey; - session.EncryptionKey = _encryptionKey; - - SecureRandom secureRandom = new SecureRandom(); - - var randomBytes = new byte[16]; - secureRandom.NextBytes(randomBytes); - - var laContent = GetDigestContent( - wrmHeader, - Convert.ToBase64String(randomBytes), - Convert.ToBase64String(GetKeyData(session)), - Convert.ToBase64String(GetCipherData(session)) - ); - - var laHash = SHA256.HashData(Encoding.UTF8.GetBytes(laContent)); - var signedInfo = GetSignedInfo(Convert.ToBase64String(laHash)); - var signature = Crypto.Ecc256Sign(session.SigningKey.PrivateKey, Encoding.UTF8.GetBytes(signedInfo)); - - return - "" + - "" + - "" + - "" + - "" + - "" + - laContent + - "" + - signedInfo + - $"{Convert.ToBase64String(signature)}" + - "" + - "" + - "" + - $"{Convert.ToBase64String(session.SigningKey.PublicBytes())}" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; - } - - private bool VerifyEncryptionKey(Session session, XmrLicense license) - { - var eccKeys = license.GetObject(42); - if (eccKeys == null) - throw new InvalidLicense("No ECC public key in license"); - - var encryptionKey = (byte[])eccKeys.First()["key"]; - return encryptionKey.SequenceEqual(session.EncryptionKey!.PublicBytes()); - } - - public void ParseLicense(int sessionId, string xmrLicense) - { - if (!_sessions.TryGetValue(sessionId, out Session? session)) - throw new InvalidSession($"Session identifier {sessionId} is invalid"); - - if (session.EncryptionKey == null || session.SigningKey == null) - throw new InvalidSession("Cannot parse a license message without first making a license request"); - - XDocument doc = XDocument.Parse(xmrLicense); - XNamespace drmNs = "http://schemas.microsoft.com/DRM/2007/03/protocols"; - - foreach (var b64License in doc.Descendants(drmNs + "License").Select(l => l.Value)) - { - var rawLicense = Convert.FromBase64String(b64License); - var license = XmrLicense.Loads(rawLicense); - - if (!VerifyEncryptionKey(session, license)) - throw new InvalidLicense("Public encryption key does not match"); - - var contentKeys = license.GetObject(10); - if (contentKeys == null) - throw new InvalidLicense("License does not contain any content keys"); - - foreach (var contentKey in contentKeys) - { - var keyId = (byte[])contentKey["key_id"]; - var keyType = (Key.KeyTypes)contentKey["key_type"]; - var cipherType = (Key.CipherTypes)contentKey["cipher_type"]; - var encryptedKey = (byte[])contentKey["encrypted_key"]; - - byte[] key; - byte[] integrityKey; - - switch (cipherType) - { - case Key.CipherTypes.Ecc256: - (ECPoint point1, ECPoint point2) = (Utils.FromBytes(encryptedKey[..64]), Utils.FromBytes(encryptedKey[64..])); - var decrypted = Crypto.Ecc256Decrypt(point1, point2, session.EncryptionKey.PrivateKey).ToBytes(); - integrityKey = decrypted[..16]; - key = decrypted[16..32]; - break; - default: - throw new InvalidLicense($"Cipher type {cipherType} is not supported"); - } - - if (!license.CheckSignature(integrityKey)) - throw new InvalidLicense("License integrity signature does not match"); - - session.Keys.Add(new Key(keyId, keyType, cipherType, key)); - } - } - } - - public List GetKeys(int sessionId) - { - if (!_sessions.TryGetValue(sessionId, out Session? session)) - throw new InvalidSession($"Session identifier {sessionId} is invalid"); - - return session.Keys; - } -} \ No newline at end of file diff --git a/csplayready/Exceptions.cs b/csplayready/Exceptions.cs deleted file mode 100644 index fbdee55..0000000 --- a/csplayready/Exceptions.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace csplayready; - - -public class CsPlayreadyException : Exception -{ - public CsPlayreadyException(string message) : base(message) { } - public CsPlayreadyException(string message, Exception innerException) : base(message, innerException) { } -} - -public class InvalidCertificate : CsPlayreadyException -{ - public InvalidCertificate(string message) : base(message) { } - public InvalidCertificate(string message, Exception innerException) : base(message, innerException) { } -} - -public class InvalidCertificateChain : CsPlayreadyException -{ - public InvalidCertificateChain(string message) : base(message) { } - public InvalidCertificateChain(string message, Exception innerException) : base(message, innerException) { } -} - -public class TooManySessions : CsPlayreadyException -{ - public TooManySessions(string message) : base(message) { } - public TooManySessions(string message, Exception innerException) : base(message, innerException) { } -} - -public class InvalidSession : CsPlayreadyException -{ - public InvalidSession(string message) : base(message) { } - public InvalidSession(string message, Exception innerException) : base(message, innerException) { } -} - -public class InvalidLicense : CsPlayreadyException -{ - public InvalidLicense(string message) : base(message) { } - public InvalidLicense(string message, Exception innerException) : base(message, innerException) { } -} diff --git a/csplayready/Program.cs b/csplayready/Program.cs deleted file mode 100644 index 7227c7a..0000000 --- a/csplayready/Program.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System.Text; -using System.Xml.Linq; -using csplayready.device; -using csplayready.system; - -namespace csplayready; - -class Program -{ - private static readonly XNamespace DrmNs = "http://schemas.microsoft.com/DRM/2007/03/protocols"; - - public static IEnumerable GetAllLicenses(XDocument doc) - { - return doc.Descendants(DrmNs + "License").Select(l => l.Value); - } - - public static void Main(string[] args) - { - - /*using Org.BouncyCastle.Math; - using Org.BouncyCastle.Math.EC; - using Org.BouncyCastle.Utilities.Encoders;*/ - /*EccKey key = EccKey.Generate(); - Console.WriteLine("Private key: " + key.PrivateKey); - - EccKey messagePoint = EccKey.Generate(); - Console.WriteLine("plaintext: " + messagePoint.PublicBytes().ToHex()); - - (ECPoint e1, ECPoint e2) = Crypto.Ecc256Encrypt(messagePoint.PublicKey, key.PublicKey); - Console.WriteLine("encrypted 1: " + e1.XCoord.ToBigInteger() + " " + e1.YCoord.ToBigInteger()); - Console.WriteLine("encrypted 2: " + e2.XCoord.ToBigInteger() + " " + e2.YCoord.ToBigInteger()); - - var bytes = e1.ToBytes().Concat(e2.ToBytes()).ToArray(); - Console.WriteLine("encrypted: " + bytes.ToHex()); - - var curve = ECNamedCurveTable.GetByName("secp256r1"); - var domainParams = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); - - var l1 = Utils.FromBytes(bytes[..64], domainParams); - var l2 = Utils.FromBytes(bytes[64..], domainParams); - - ECPoint decryptedE = Crypto.Ecc256Decrypt(e1, e2, key.PrivateKey); - Console.WriteLine("decrypted e: " + decryptedE.ToBytes().ToHex()); - - ECPoint decryptedL = Crypto.Ecc256Decrypt(l1, l2, key.PrivateKey); - Console.WriteLine("decrypted l: " + decryptedL.ToBytes().ToHex());*/ - /* - using constructcs; - using static constructcs.ParserBuilder; - - string text = "AAADfHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1xcAwAAAQABAFIDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4" + - "AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC" + - "8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0A" + - "C4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAx" + - "ADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgB" + - "PAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgA0AFIAcABsAGIAKwBUAGIATgBFAFMAOAB0AEcAawBOAEYAVwBUAEUASA" + - "BBAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEsATABqADMAUQB6AFEAUAAvAE4AQQA9ADwALwBDAEgAR" + - "QBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAHAAcgBvAGYAZgBpAGMAaQBhAGwAcwBpAHQA" + - "ZQAuAGsAZQB5AGQAZQBsAGkAdgBlAHIAeQAuAG0AZQBkAGkAYQBzAGUAcgB2AGkAYwBlAHMALgB3AGkAbgBkAG8AdwBzAC4" + - "AbgBlAHQALwBQAGwAYQB5AFIAZQBhAGQAeQAvADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAE" + - "IAVQBUAEUAUwA+ADwASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA4AC4AMQAuADIAMwAwADQALgAzADEAPAAvA" + - "EkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8" + - "AC8ARABBAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA=="; - - var pssh = new Pssh(text); - Console.WriteLine(string.Join(", ", pssh.GetWrmHeaders()));*/ - /* - using csplayready.license; - - const string licenseString = "WE1SAAAAAANtHSY9EpvluoRHZaggdNEeAAMAAQAAAaYAAwACAAAAMgABAA0AAAAKAAEAAAAzAAAACgABAAEAMgAAAAwAAABMAAEANAAAAAoH0AACAAQAAABeAAEABQAAABIBkAEOAJYAZABkAAMABwAAACQAAQAIAAAAHJGhg9eD4K9Lstrmn5ELN3JA7wcAAAIANgAAACAAAAA5AAAAGB/ZIbbM7TVAjUvccXYNQ+kAAwAJAAAA8gABAAoAAACeHl06aWdatUKc9+vZTOADAQABAAMAgFpnAzpVEpVCWcpDHRv8K7dVTfDu1KVeLfpb4kvFWbD9hcNEDSpse946LHZRYsFw19sPnhs5sOnJe+Q/zy4EoX+BG9zZc6WCetrPhb/vKC2tGvwJrCqHFUE5DM82g5WjIV96cf61OQtSLMvrIT0dJmIV5YKfi5RTeAAb2kOj+AE7AAAAKgAAAEwAAQBA8yyUn9LQzBQonmbYcnuUQ3iZMVxdjP3VDDi5goFt3ofTWrFdOT4MXi0YKUE4G/zk8Xp6gPHkJjG8XKsM6mTbPQABAAsAAAAcAAEAELeiTV1WtdIiQPmFZnF1JN4="; - - var data = Convert.FromBase64String(licenseString); - var license = XmrLicense.Loads(data); - - Utils.PrintObject(license.GetObject(10));*/ - /* - using csplayready.crypto; - using csplayready.device; - using csplayready.system; - using Org.BouncyCastle.Security; - - var encryptionKey = EccKey.Load(@"C:\Users\titus\RiderProjects\csplayready\csplayready\hisense\encr.dat"); - var signingKey = EccKey.Load(@"C:\Users\titus\RiderProjects\csplayready\csplayready\hisense\sig.dat"); - - var groupKey = EccKey.Load(@"C:\Users\titus\RiderProjects\csplayready\csplayready\hisense\zgpriv.dat"); - var certificateChain = CertificateChain.Load(@"C:\Users\titus\RiderProjects\csplayready\csplayready\hisense\bgroupcert.dat"); - - if (!certificateChain.Get(0).GetIssuerKey()!.SequenceEqual(groupKey.PublicBytes())) - throw new InvalidCertificateChain("Group key does not match this certificate"); - - var random = new SecureRandom(); - var certId = random.GenerateSeed(16); - var clientId = random.GenerateSeed(16); - - var leafCert = Certificate.NewLeafCertificate(certId, (uint)certificateChain.GetSecurityLevel()!, clientId, signingKey, encryptionKey, groupKey, certificateChain); - certificateChain.Prepend(leafCert); - - Console.WriteLine("Valid: " + certificateChain.Verify()); - - var device = new Device(3, groupKey, encryptionKey, signingKey, certificateChain); - device.Dump("fourth_cs_device.prd");*/ - - // TODO: - // + make Utils class better - // + more exceptions - // + cli tool - - var device = Device.Load(args[0]); - var cdm = Cdm.FromDevice(device); - var sessionId = cdm.Open(); - - var pssh = new Pssh( - "AAADfHBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAA1xcAwAAAQABAFIDPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4Ac" + - "wA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANw" + - "AvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA" + - "8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABB" + - "AEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgA0A" + - "FIAcABsAGIAKwBUAGIATgBFAFMAOAB0AEcAawBOAEYAVwBUAEUASABBAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AE" + - "sATABqADMAUQB6AFEAUAAvAE4AQQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAHA" + - "AcgBvAGYAZgBpAGMAaQBhAGwAcwBpAHQAZQAuAGsAZQB5AGQAZQBsAGkAdgBlAHIAeQAuAG0AZQBkAGkAYQBzAGUAcgB2AGkAYwBlAHMA" + - "LgB3AGkAbgBkAG8AdwBzAC4AbgBlAHQALwBQAGwAYQB5AFIAZQBhAGQAeQAvADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQ" + - "QBUAFQAUgBJAEIAVQBUAEUAUwA+ADwASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA4AC4AMQAuADIAMwAwADQALgAzADEAPA" + - "AvAEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4APAAvAEMAVQBTAFQATwBNAEEAVABUAFIASQBCAFUAVABFAFMAPgA8AC8ARAB" + - "BAFQAQQA+ADwALwBXAFIATQBIAEUAQQBEAEUAUgA+AA=="); - - var wrmHeaders = pssh.GetWrmHeaders(); - var challenge = cdm.GetLicenseChallenge(sessionId, wrmHeaders[0]); - - Console.WriteLine(challenge); - - using HttpClient client = new HttpClient(); - const string url = "https://test.playready.microsoft.com/service/rightsmanager.asmx?cfg=(persist:false,sl:2000)"; - var content = new StringContent(challenge, Encoding.UTF8, "text/xml"); - - HttpResponseMessage response = client.PostAsync(url, content).Result; - response.EnsureSuccessStatusCode(); - var responseBody = response.Content.ReadAsStringAsync().Result; - - Console.WriteLine(responseBody); - - cdm.ParseLicense(sessionId, responseBody); - - foreach (var key in cdm.GetKeys(sessionId)) - { - Console.WriteLine($"{key.KeyId.ToHex()}:{key.RawKey.ToHex()}"); - } - - cdm.Close(sessionId); - } -} diff --git a/csplayready/Utils.cs b/csplayready/Utils.cs deleted file mode 100644 index e69b657..0000000 --- a/csplayready/Utils.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System.Collections; -using System.Text; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Math.EC; - -namespace csplayready; - -public static class Utils -{ - private static readonly ECCurve Curve = ECNamedCurveTable.GetByName("secp256r1").Curve; - - public static byte[] ToRawByteArray(this BigInteger value) { - var bytes = value.ToByteArray(); - return bytes[0] == 0 ? bytes[1..] : bytes; - } - - public static byte[] ToBytes(this ECPoint point) - { - return point.XCoord.ToBigInteger().ToRawByteArray() - .Concat(point.YCoord.ToBigInteger().ToRawByteArray()) - .ToArray(); - } - - public static ECPoint FromBytes(byte[] bytes) - { - if (bytes.Length != 64) - throw new ArgumentException("Byte array must be exactly 64 bytes (32 bytes each for X and Y coordinates)"); - - ECPoint point = Curve.CreatePoint( - new BigInteger(1, bytes[..32]), - new BigInteger(1, bytes[32..]) - ); - if (!point.IsValid()) - throw new ArgumentException("Point is not valid for the given curve"); - - return point; - } - - public static ECPoint ToEcPoint(this byte[] data) - { - return ECNamedCurveTable.GetByName("secp256r1").Curve.CreatePoint( - new BigInteger(1, data[..32]), - new BigInteger(1, data[32..64]) - ); - } - - public static string ToHex(this byte[] bytes) => string.Concat(bytes.Select(b => b.ToString("x2"))); - - public static byte[] HexToBytes(this string hex) - { - if (string.IsNullOrWhiteSpace(hex)) - throw new ArgumentException("Hex string cannot be null or empty.", nameof(hex)); - - if (hex.Length % 2 != 0) - throw new FormatException("Hex string must have an even length."); - - var bytes = new byte[hex.Length / 2]; - for (var i = 0; i < bytes.Length; i++) - { - bytes[i] = (byte)((GetHexVal(hex[i * 2]) << 4) + GetHexVal(hex[i * 2 + 1])); - } - return bytes; - } - - private static int GetHexVal(char hex) - { - int val = hex; - return val - (val < 58 ? 48 : val < 97 ? 55 : 87); - } - - public static string FormatBytes(byte[] data) - { - StringBuilder builder = new StringBuilder(); - - foreach (byte b in data) - { - if (b >= 32 && b <= 126) - { - builder.Append((char)b); - } - else - { - builder.AppendFormat("\\x{0:X2}", b); - } - } - - return builder.ToString(); - } - - public static void PrintObject(object? obj, int indentLevel = 0) - { - var indent = new string(' ', indentLevel * 2); - - switch (obj) - { - case Dictionary dictionary: - Console.WriteLine($"{indent}Dictionary({dictionary.Count}):"); - foreach (var kvp in dictionary) - { - Console.WriteLine($"{indent} {kvp.Key}:"); - PrintObject(kvp.Value, indentLevel + 2); - } - break; - case byte[] bytes: - Console.WriteLine($"{indent}byte[{bytes.Length}]: \"{FormatBytes(bytes)}\""); - break; - case IList list: - Console.WriteLine($"{indent}List({list.Count}):"); - for (var i = 0; i < list.Count; i++) - { - Console.WriteLine($"{indent} --- {i} ---"); - PrintObject(list[i], indentLevel + 1); - } - break; - default: - Console.WriteLine($"{indent}{obj}"); - break; - } - } -} \ No newline at end of file diff --git a/csplayready/constructcs/BinaryParser.cs b/csplayready/constructcs/BinaryParser.cs deleted file mode 100644 index bcf6dff..0000000 --- a/csplayready/constructcs/BinaryParser.cs +++ /dev/null @@ -1,613 +0,0 @@ -using constructcs; - -namespace csplayready.constructcs; - -public abstract class Field(string name) -{ - public string Name { get; } = name; - - protected static void Assert(int length, int size) => _ = length < size ? throw new EndOfStreamException($"Expected {size}, got {length}") : true; - public abstract object Read(BinaryReader reader, Context context); - public abstract void Write(BinaryWriter writer, Context context, object value); -} - -public class Context -{ - private readonly Dictionary _values = new(); - - public void SetValue(string name, object value) => _values[name] = value; - public T GetValue(string fieldName) - { - if (_values.TryGetValue(fieldName, out var value)) - { - return (T)value; - } - throw new KeyNotFoundException($"Field '{fieldName}' not found in context"); - } -} - -public class ContextAccess -{ - private readonly Context _context; - - public ContextAccess(Context context) - { - _context = context; - } - - public object this[string fieldName] => _context.GetValue(fieldName); -} - -public class IntField : Field -{ - private readonly int _size; - private readonly bool _bigEndian; - private readonly bool _signed; - - public IntField(string name, int size, bool bigEndian, bool signed) : base(name) - { - _size = size; - _bigEndian = bigEndian; - _signed = signed; - } - - public override object Read(BinaryReader reader, Context context) - { - var bytes = reader.ReadBytes(_size); - Assert(bytes.Length, _size); - - if (_bigEndian) - Array.Reverse(bytes); - - object value = (_size, _signed) switch - { - (1, true) => (sbyte)bytes[0], - (1, false) => bytes[0], - (2, true) => BitConverter.ToInt16(bytes, 0), - (2, false) => BitConverter.ToUInt16(bytes, 0), - (4, true) => BitConverter.ToInt32(bytes, 0), - (4, false) => BitConverter.ToUInt32(bytes, 0), - (8, true) => BitConverter.ToInt64(bytes, 0), - (8, false) => BitConverter.ToUInt64(bytes, 0), - _ => throw new ArgumentException($"Unsupported byte count: {_size}") - }; - - context.SetValue(Name, value); - return value; - } - - public override void Write(BinaryWriter writer, Context context, object value) - { - byte[] bytes = (_size, _signed) switch - { - (1, true) => [(byte)Convert.ToSByte(value)], - (1, false) => [Convert.ToByte(value)], - (2, true) => BitConverter.GetBytes(Convert.ToInt16(value)), - (2, false) => BitConverter.GetBytes(Convert.ToUInt16(value)), - (4, true) => BitConverter.GetBytes(Convert.ToInt32(value)), - (4, false) => BitConverter.GetBytes(Convert.ToUInt32(value)), - (8, true) => BitConverter.GetBytes(Convert.ToInt64(value)), - (8, false) => BitConverter.GetBytes(Convert.ToUInt64(value)), - _ => throw new ArgumentException($"Unsupported byte count: {_size}") - }; - - if (_bigEndian) - Array.Reverse(bytes); - - writer.Write(bytes); - context.SetValue(Name, value); - } -} - - -public class BytesField : Field -{ - private protected readonly int? Length; - private protected readonly Func? LengthExpression; - - public BytesField(string name, int length) : base(name) - { - Length = length; - } - - public BytesField(string name, Func lengthExpression) : base(name) - { - LengthExpression = lengthExpression; - } - - public override object Read(BinaryReader reader, Context context) - { - var length = Length ?? Convert.ToInt32(LengthExpression!(new ContextAccess(context))); - var value = reader.ReadBytes(length); - - Assert(value.Length, length); - context.SetValue(Name, value); - - return value; - } - - public override void Write(BinaryWriter writer, Context context, object value) - { - var bytes = (byte[])value; - var length = Length ?? Convert.ToInt32(LengthExpression!(new ContextAccess(context))); - - if (bytes.Length != length) - throw new InvalidDataException($"Expected {length}, got {bytes.Length}"); - - writer.Write(bytes); - context.SetValue(Name, value); - } -} - -public class StringField : BytesField -{ - private readonly Encoding _encoding; - - public StringField(string name, int length, Encoding encoding) : base(name, length) - { - _encoding = encoding; - } - - public StringField(string name, Func lengthExpression, Encoding encoding) : base(name, lengthExpression) - { - _encoding = encoding; - } - - public override object Read(BinaryReader reader, Context context) - { - var bytes = (byte[])base.Read(reader, context); - - return _encoding switch - { - Encoding.Ascii => System.Text.Encoding.ASCII.GetString(bytes), - Encoding.Utf8 => System.Text.Encoding.UTF8.GetString(bytes), - Encoding.Utf16 => System.Text.Encoding.Unicode.GetString(bytes), - _ => throw new ArgumentOutOfRangeException() - }; - } - - public override void Write(BinaryWriter writer, Context context, object value) - { - var text = (string)value; - var length = Length ?? Convert.ToInt32(LengthExpression!(new ContextAccess(context))); - - byte[] bytes = _encoding switch - { - Encoding.Ascii => System.Text.Encoding.ASCII.GetBytes(text), - Encoding.Utf8 => System.Text.Encoding.UTF8.GetBytes(text), - Encoding.Utf16 => System.Text.Encoding.Unicode.GetBytes(text), - _ => throw new ArgumentOutOfRangeException() - }; - - if (bytes.Length != length) - throw new InvalidDataException($"Expected {length}, got {bytes.Length}"); - - writer.Write(bytes); - context.SetValue(Name, value); - } -} - - -public class ConstField : Field -{ - private readonly byte[] _expected; - - public ConstField(string name, byte[] expected) : base(name) - { - _expected = expected; - } - - public override object Read(BinaryReader reader, Context context) - { - var actual = reader.ReadBytes(_expected.Length); - - Assert(actual.Length, _expected.Length); - if (!actual.SequenceEqual(_expected)) - throw new InvalidDataException($"Expected constant {_expected.ToHex()} but got {actual.ToHex()}"); - - context.SetValue(Name, actual); - return actual; - } - - public override void Write(BinaryWriter writer, Context context, object value) - { - var bytes = (byte[])value; - - Assert(bytes.Length, _expected.Length); - if (!bytes.SequenceEqual(_expected)) - throw new InvalidDataException($"Expected constant {_expected.ToHex()} but got {bytes.ToHex()}"); - - writer.Write(bytes); - context.SetValue(Name, value); - } -} - -public class ArrayField : Field -{ - private readonly Field _field; - private readonly int? _count; - private readonly Func? _countExpression; - - public ArrayField(string name, Field field, int count) : base(name) - { - _field = field; - _count = count; - } - - public ArrayField(string name, Field field, Func countExpression) : base(name) - { - _field = field; - _countExpression = countExpression; - } - - public override object Read(BinaryReader reader, Context context) - { - var count = _count ?? Convert.ToInt32(_countExpression!(new ContextAccess(context))); - var results = new List(); - - for (var i = 0; i < count; i++) - { - var value = _field.Read(reader, context); - results.Add(value); - } - Assert(results.Count, count); - - context.SetValue(Name, results); - return results; - } - - public override void Write(BinaryWriter writer, Context context, object value) - { - var count = _count ?? Convert.ToInt32(_countExpression!(new ContextAccess(context))); - var results = new List(); - - for (var i = 0; i < count; i++) - { - var item = ((List)value)[i]; - _field.Write(writer, context, item); - results.Add(item); - } - Assert(results.Count, count); - - context.SetValue(Name, value); - } -} - - -public class StructField : Field -{ - private readonly Struct? _nestedStruct; - private readonly Func? _nestedStructExpression; - - public StructField(string name, Struct nestedStruct) : base(name) - { - _nestedStruct = nestedStruct; - } - - public StructField(string name, Func nestedStructExpression) : base(name) - { - _nestedStructExpression = nestedStructExpression; - } - - public override object Read(BinaryReader reader, Context context) - { - var nestedStruct = _nestedStruct ?? _nestedStructExpression!(); - - var nestedData = nestedStruct.Parse(reader, context); - context.SetValue(Name, nestedData); - return nestedData; - } - - public override void Write(BinaryWriter writer, Context context, object value) - { - var nestedStruct = _nestedStruct ?? _nestedStructExpression!(); - - nestedStruct.Build(writer, context, (Dictionary)value); - context.SetValue(Name, value); - } -} - - -public class SwitchField : Field -{ - private readonly int? _index; - private readonly Func? _indexExpression; - private readonly Func _switchExpression; - - public SwitchField(string name, int index, Func switchExpression) : base(name) - { - _index = index; - _switchExpression = switchExpression; - } - - public SwitchField(string name, Func indexExpression, Func switchExpression) : base(name) - { - _indexExpression = indexExpression; - _switchExpression = switchExpression; - } - - public override object Read(BinaryReader reader, Context context) - { - var index = _index ?? Convert.ToInt32(_indexExpression!(new ContextAccess(context))); - var field = _switchExpression(index); - - if(field == null) - throw new InvalidOperationException($"No case found for index {index} in Switch {Name}"); - - var value = field.Read(reader, context); - context.SetValue(Name, value); - return value; - } - - public override void Write(BinaryWriter writer, Context context, object value) - { - var index = _index ?? Convert.ToInt32(_indexExpression!(new ContextAccess(context))); - var field = _switchExpression(index); - - if(field == null) - throw new InvalidOperationException($"No case found for index {index} in Switch {Name}"); - - field.Write(writer, context, value); - context.SetValue(Name, value); - } -} - - -public class ConditionalField : Field -{ - private readonly Field _thenField; - private readonly Field _elseField; - private readonly bool? _condition; - private readonly Func? _conditionExpression; - - public ConditionalField(string name, bool condition, Field thenField, Field elseField) : base(name) - { - _condition = condition; - _thenField = thenField; - _elseField = elseField; - } - - public ConditionalField(string name, Func conditionExpression, Field thenField, Field elseField) : base(name) - { - _conditionExpression = conditionExpression; - _thenField = thenField; - _elseField = elseField; - } - - public override object Read(BinaryReader reader, Context context) - { - var condition = _condition ?? Convert.ToBoolean(_conditionExpression!(new ContextAccess(context))); - var value = condition ? _thenField.Read(reader, context) : _elseField.Read(reader, context); - - context.SetValue(Name, value); - return value; - } - - public override void Write(BinaryWriter writer, Context context, object value) - { - var condition = _condition ?? Convert.ToBoolean(_conditionExpression!(new ContextAccess(context))); - - if (condition) - { - _thenField.Write(writer, context, value); - } - else - { - _elseField.Write(writer, context, value); - } - - context.SetValue(Name, value); - } -} - - -public class RangeField : Field -{ - private readonly Field _field; - private readonly int? _min; - private readonly Func? _minExpression; - private readonly int? _max; - private readonly Func? _maxExpression; - - public RangeField(string name, Field field, int min, int max) : base(name) - { - _field = field; - _min = min; - _max = max; - } - - public RangeField(string name, Field field, Func minExpression, int max) : base(name) - { - _field = field; - _minExpression = minExpression; - _max = max; - } - - public RangeField(string name, Field field, int min, Func maxExpression) : base(name) - { - _field = field; - _min = min; - _maxExpression = maxExpression; - } - - public RangeField(string name, Field field, Func minExpression, Func maxExpression) : base(name) - { - _field = field; - _minExpression = minExpression; - _maxExpression = maxExpression; - } - - public override object Read(BinaryReader reader, Context context) - { - var min = _min ?? Convert.ToInt32(_minExpression!(new ContextAccess(context))); - var max = _max ?? Convert.ToInt32(_maxExpression!(new ContextAccess(context))); - - var results = new List(); - while (results.Count < max) - { - var pos = reader.BaseStream.Position; - try - { - var value = _field.Read(reader, context); - results.Add(value); - } - catch (Exception) - { - reader.BaseStream.Position = pos; - break; - } - } - - Assert(results.Count, min); - context.SetValue(Name, results); - - return results; - } - - public override void Write(BinaryWriter writer, Context context, object value) - { - var min = _min ?? Convert.ToInt32(_minExpression!(new ContextAccess(context))); - var max = _max ?? Convert.ToInt32(_maxExpression!(new ContextAccess(context))); - var items = (List)value; - - if (!(min <= items.Count && items.Count <= max)) - throw new Exception($"Expected from {min} to {max} elements, found {items.Count}"); - - var results = new List(); - for (var i = 0; i < items.Count; i++) - { - var item = items[results.Count]; - _field.Write(writer, context, item); - results.Add(value); - } - - Assert(results.Count, min); - context.SetValue(Name, results); - } -} - - -public class PassField(string name) : Field(name) -{ - public override object Read(BinaryReader reader, Context context) - { - return new object(); - } - - public override void Write(BinaryWriter writer, Context context, object value) { } -} - -public class Struct -{ - private readonly List _fields = []; - - public Struct(params Field[] fields) - { - _fields.AddRange(fields); - } - - public Dictionary Parse(byte[] data) - { - using var stream = new MemoryStream(data); - return Parse(stream); - } - - public Dictionary Parse(Stream stream) - { - using var reader = new BinaryReader(stream); - return Parse(reader, new Context()); - } - - public Dictionary Parse(BinaryReader reader, Context context) - { - var result = new Dictionary(); - - foreach (var field in _fields) - { - var value = field.Read(reader, context); - result[field.Name] = value; - } - - return result; - } - - public byte[] Build(Dictionary values) - { - using var memoryStream = new MemoryStream(); - Build(memoryStream, values); - return memoryStream.ToArray(); - } - - public void Build(Stream stream, Dictionary values) - { - using var writer = new BinaryWriter(stream); - Build(writer, new Context(), values); - } - - public void Build(BinaryWriter writer, Context context, Dictionary values) - { - foreach (Field field in _fields.Where(field => !string.IsNullOrEmpty(field.Name))) - { - if (!values.TryGetValue(field.Name, out var value)) - throw new KeyNotFoundException($"No value provided for Field '{field.Name}'"); - - field.Write(writer, context, value); - } - writer.Flush(); - } -} - -public static class ParserBuilder -{ - public static IntField Int8sb(string name) => new(name, 1, true, true); - public static IntField Int8sl(string name) => new(name, 1, false, true); - public static IntField Int16sb(string name) => new(name, 2, true, true); - public static IntField Int16sl(string name) => new(name, 2, false, true); - public static IntField Int32sb(string name) => new(name, 4, true, true); - public static IntField Int32sl(string name) => new(name, 4, false, true); - public static IntField Int64sb(string name) => new(name, 8, true, true); - public static IntField Int64sl(string name) => new(name, 8, false, true); - - public static IntField Int8ub(string name) => new(name, 1, true, false); - public static IntField Int8ul(string name) => new(name, 1, false, false); - public static IntField Int16ub(string name) => new(name, 2, true, false); - public static IntField Int16ul(string name) => new(name, 2, false, false); - public static IntField Int32ub(string name) => new(name, 4, true, false); - public static IntField Int32ul(string name) => new(name, 4, false, false); - public static IntField Int64ub(string name) => new(name, 8, true, false); - public static IntField Int64ul(string name) => new(name, 8, false, false); - - public static BytesField Bytes(string name, int length) => new(name, length); - public static BytesField Bytes(string name, Func lengthExpression) => new(name, lengthExpression); - - public static ConstField Const(string name, byte[] expected) => new(name, expected); - - public static StringField ASCIIString(string name, int length) => new(name, length, Encoding.Ascii); - public static StringField ASCIIString(string name, Func lengthExpression) => new(name, lengthExpression, Encoding.Ascii); - public static StringField UTF8String(string name, int length) => new(name, length, Encoding.Utf8); - public static StringField UTF8String(string name, Func lengthExpression) => new(name, lengthExpression, Encoding.Utf8); - public static StringField UTF16String(string name, int length) => new(name, length, Encoding.Utf16); - public static StringField UTF16String(string name, Func lengthExpression) => new(name, lengthExpression, Encoding.Utf16); - - public static ArrayField Array(string name, Field element, int count) => new(name, element, count); - public static ArrayField Array(string name, Field element, Func countExpression) => new(name, element, countExpression); - - public static StructField Child(string name, Struct nestedStruct) => new(name, nestedStruct); - public static StructField Child(string name, Func nestedStructExpression) => new(name, nestedStructExpression); - - public static SwitchField Switch(string name, int index, Func switchExpression) => new(name, index, switchExpression); - public static SwitchField Switch(string name, Func indexExpression, Func switchExpression) => new(name, indexExpression, switchExpression); - - public static ConditionalField IfThenElse(string name, bool condition, Field thenField, Field elseField) => new(name, condition, thenField, elseField); - public static ConditionalField IfThenElse(string name, Func conditionExpression, Field thenField, Field elseField) => new(name, conditionExpression, thenField, elseField); - - public static ConditionalField If(string name, bool condition, Field thenField) => new(name, condition, thenField, new PassField(thenField.Name)); - public static ConditionalField If(string name, Func conditionExpression, Field thenField) => new(name, conditionExpression, thenField, new PassField(thenField.Name)); - - public static RangeField Range(string name, Field field, int min, int max) => new(name, field, min, max); - public static RangeField Range(string name, Field field, Func minExpression, int max) => new(name, field, minExpression, max); - public static RangeField Range(string name, Field field, int min, Func maxExpression) => new(name, field, min, maxExpression); - public static RangeField Range(string name, Field field, Func minExpression, Func maxExpression) => new(name, field, minExpression, maxExpression); - - public static RangeField GreedyRange(string name, Field field) => new(name, field, 0, int.MaxValue); -} diff --git a/csplayready/constructcs/Enums.cs b/csplayready/constructcs/Enums.cs deleted file mode 100644 index 82a8b95..0000000 --- a/csplayready/constructcs/Enums.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace constructcs; - -public enum Encoding -{ - Utf8, - Utf16, - Ascii -} diff --git a/csplayready/crypto/Crypto.cs b/csplayready/crypto/Crypto.cs deleted file mode 100644 index 792faab..0000000 --- a/csplayready/crypto/Crypto.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Security.Cryptography; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto.Signers; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using ECPoint = Org.BouncyCastle.Math.EC.ECPoint; - -namespace csplayready.crypto; - -public static class Crypto -{ - private static readonly X9ECParameters Curve = ECNamedCurveTable.GetByName("secp256r1"); - private static readonly ECDomainParameters DomainParams = new(Curve.Curve, Curve.G, Curve.N, Curve.H); - - public static byte[] AesCbcEncrypt(byte[] key, byte[] iv, byte[] data) - { - var cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7"); - var keyParam = new KeyParameter(key); - var parameters = new ParametersWithIV(keyParam, iv); - - cipher.Init(true, parameters); - return cipher.DoFinal(data); - } - - public static byte[] AesCbcDecrypt(byte[] key, byte[] iv, byte[] data) - { - var cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7"); - var keyParam = new KeyParameter(key); - var parameters = new ParametersWithIV(keyParam, iv); - - cipher.Init(false, parameters); - return cipher.DoFinal(data); - } - - public static (ECPoint point1, ECPoint point2) Ecc256Encrypt(ECPoint messagePoint, ECPoint publicKey) => ElGamal.Encrypt(messagePoint, publicKey); - public static ECPoint Ecc256Decrypt(ECPoint point1, ECPoint point2, BigInteger privateKey) => ElGamal.Decrypt(point1, point2, privateKey); - - public static byte[] Ecc256Sign(BigInteger privateKey, byte[] data) - { - var signer = new ECDsaSigner(); - - var hash = SHA256.HashData(data); - - var privateKeyParams = new ECPrivateKeyParameters(privateKey, DomainParams); - signer.Init(true, privateKeyParams); - - var signature = signer.GenerateSignature(hash); - return signature[0].ToRawByteArray() - .Concat(signature[1].ToRawByteArray()) - .ToArray(); - } - - public static bool Ecc256Verify(ECPoint publicKey, byte[] data, byte[] signature) - { - var signer = new ECDsaSigner(); - - var publicKeyParams = new ECPublicKeyParameters(publicKey, DomainParams); - signer.Init(false, publicKeyParams); - - var hash = SHA256.HashData(data); - var r = new BigInteger(1, signature[..32]); - var s = new BigInteger(1, signature[32..]); - - return signer.VerifySignature(hash, r, s); - } -} \ No newline at end of file diff --git a/csplayready/crypto/EccKey.cs b/csplayready/crypto/EccKey.cs deleted file mode 100644 index 10c3019..0000000 --- a/csplayready/crypto/EccKey.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Security.Cryptography; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using ECPoint = Org.BouncyCastle.Math.EC.ECPoint; - -namespace csplayready.crypto; - -public class EccKey -{ - private static readonly X9ECParameters Curve = ECNamedCurveTable.GetByName("secp256r1"); - private static readonly ECDomainParameters DomainParams = new(Curve.Curve, Curve.G, Curve.N, Curve.H); - - public readonly BigInteger PrivateKey; - public readonly ECPoint PublicKey; - - public EccKey(BigInteger privateKey, ECPoint publicKey) - { - PrivateKey = privateKey; - PublicKey = publicKey; - } - - public EccKey(BigInteger privateKey) - { - PrivateKey = privateKey; - PublicKey = Curve.G.Multiply(PrivateKey).Normalize(); - } - - public static EccKey Generate() - { - var eccGenerator = GeneratorUtilities.GetKeyPairGenerator("EC"); - eccGenerator.Init(new ECKeyGenerationParameters(DomainParams, new SecureRandom())); - - var eccKeyPair = eccGenerator.GenerateKeyPair(); - return new EccKey(((ECPrivateKeyParameters)eccKeyPair.Private).D, ((ECPublicKeyParameters)eccKeyPair.Public).Q); - } - - public static EccKey Loads(byte[] data, bool verify = true) - { - if (data.Length != 32 && data.Length != 96) - { - throw new InvalidDataException($"Invalid data length. Expecting 96 or 32 bytes, got {data.Length}"); - } - - BigInteger privateKey = new BigInteger(1, data[..32]); - ECPoint publicKey = Curve.G.Multiply(privateKey).Normalize(); - - if (data.Length == 96) - { - ECPoint loadedPublicKey = DomainParams.Curve.CreatePoint( - new BigInteger(1, data[32..64]), - new BigInteger(1, data[64..96]) - ); - - if (verify) - { - if (!publicKey.XCoord.Equals(loadedPublicKey.XCoord) || !publicKey.YCoord.Equals(loadedPublicKey.YCoord)) - { - throw new InvalidDataException("Derived Public Key does not match loaded Public Key"); - } - } - - publicKey = loadedPublicKey; - } - - return new EccKey(privateKey, publicKey); - } - - public static EccKey Load(string path) => Loads(File.ReadAllBytes(path)); - - public byte[] Dumps(bool privateOnly = false) - { - if (privateOnly) - return PrivateBytes(); - return PrivateBytes() - .Concat(PublicBytes()).ToArray(); - } - - public void Dump(string path, bool privateOnly = false) => File.WriteAllBytes(path, Dumps(privateOnly)); - - public byte[] PrivateBytes() => PrivateKey.ToRawByteArray(); - - public byte[] PrivateSha256Digest() => SHA256.HashData(PrivateBytes()); - - public byte[] PublicBytes() - { - return PublicKey.XCoord.ToBigInteger().ToRawByteArray() - .Concat(PublicKey.YCoord.ToBigInteger().ToRawByteArray()) - .ToArray(); - } - - public byte[] PublicSha256Digest() => SHA256.HashData(PublicBytes()); -} \ No newline at end of file diff --git a/csplayready/crypto/ElGamal.cs b/csplayready/crypto/ElGamal.cs deleted file mode 100644 index 97b27c9..0000000 --- a/csplayready/crypto/ElGamal.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Math.EC; -using Org.BouncyCastle.Security; - -namespace csplayready.crypto; - -public class ElGamal -{ - private static readonly X9ECParameters Curve = ECNamedCurveTable.GetByName("secp256r1"); - private static readonly ECDomainParameters DomainParams = new(Curve.Curve, Curve.G, Curve.N, Curve.H); - - public static (ECPoint point1, ECPoint point2) Encrypt(ECPoint messagePoint, ECPoint publicKey) - { - var random = new SecureRandom(); - var ephemeralKey = new BigInteger(DomainParams.N.BitLength, random); - - var point1 = Curve.G.Multiply(ephemeralKey).Normalize(); - var point2 = messagePoint.Add(publicKey.Multiply(ephemeralKey)).Normalize(); - - return (point1, point2); - } - - public static ECPoint Decrypt(ECPoint point1, ECPoint point2, BigInteger privateKey) - { - var sharedSecret = point1.Multiply(privateKey); - return point2.Subtract(sharedSecret).Normalize(); - } -} diff --git a/csplayready/csplayready.csproj b/csplayready/csplayready.csproj deleted file mode 100644 index 474cebc..0000000 --- a/csplayready/csplayready.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - net9.0 - enable - enable - true - true - - - - - - - diff --git a/csplayready/device/Device.cs b/csplayready/device/Device.cs deleted file mode 100644 index 67c95ee..0000000 --- a/csplayready/device/Device.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Text.RegularExpressions; -using csplayready.crypto; - -using csplayready.constructcs; -using csplayready.system; -using static csplayready.constructcs.ParserBuilder; - -namespace csplayready.device; - -public class Device(byte version, EccKey? groupKey, EccKey? encryptionKey, EccKey? signingKey, CertificateChain? groupCertificate) -{ - private static readonly Struct PrdV2 = new( - Int32ub("group_certificate_length"), - Bytes("group_certificate", ctx => ctx["group_certificate_length"]), - Bytes("encryption_key", 96), - Bytes("signing_key", 96) - ); - - private static readonly Struct PrdV3 = new( - Bytes("group_key", 96), - Bytes("encryption_key", 96), - Bytes("signing_key", 96), - Int32ub("group_certificate_length"), - Bytes("group_certificate", ctx => ctx["group_certificate_length"]) - ); - - private static readonly Struct Prd = new( - Const("signature", "PRD"u8.ToArray()), - Int8ub("version"), - Switch("data", ctx => ctx["version"], i => i switch - { - 2 => Child(string.Empty, PrdV2), - 3 => Child(string.Empty, PrdV3), - _ => throw new InvalidDataException($"Unknown PRD version {i}") - }) - ); - - public byte Version = version; - public EccKey? GroupKey = groupKey; - public EccKey? EncryptionKey = encryptionKey; - public EccKey? SigningKey = signingKey; - public CertificateChain? GroupCertificate = groupCertificate; - - public Device() : this(0, null, null, null, null) { } - - public static Device Loads(byte[] bytes) - { - var result = Prd.Parse(bytes); - var data = (Dictionary)result["data"]; - - Device device = new Device - { - Version = (byte)result["version"], - GroupKey = EccKey.Loads((byte[])data["group_key"]), - EncryptionKey = EccKey.Loads((byte[])data["encryption_key"]), - SigningKey = EccKey.Loads((byte[])data["signing_key"]), - GroupCertificate = CertificateChain.Loads((byte[])data["group_certificate"]) - }; - return device; - } - - public static Device Load(string path) - { - return Loads(File.ReadAllBytes(path)); - } - - public byte[] Dumps() - { - return Prd.Build(new Dictionary - { - { "signature", "PRD"u8.ToArray() }, - { "version", 3 }, - { "data", new Dictionary - { - { "group_key", GroupKey!.Dumps() }, - { "encryption_key", EncryptionKey!.Dumps() }, - { "signing_key", SigningKey!.Dumps() }, - { "group_certificate_length", GroupCertificate!.Dumps().Length }, - { "group_certificate", GroupCertificate!.Dumps() }, - }} - }); - } - - public void Dump(string path) - { - File.WriteAllBytes(path, Dumps()); - } - - public string? GetName() - { - if (GroupCertificate == null) return null; - var name = $"{GroupCertificate!.GetName()}_sl{GroupCertificate!.GetSecurityLevel()}"; - return Regex.Replace(name, @"[^a-zA-Z0-9_\- ]", "").Replace(" ", "_").ToLower(); - } -} diff --git a/csplayready/license/Key.cs b/csplayready/license/Key.cs deleted file mode 100644 index 7fbe9de..0000000 --- a/csplayready/license/Key.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace csplayready.license; - -public class Key(byte[] keyId, Key.KeyTypes keyType, Key.CipherTypes cipherType, byte[] rawKey) -{ - public enum KeyTypes : ushort - { - Invalid = 0x0000, - Aes128Ctr = 0x0001, - Rc4Cipher = 0x0002, - Aes128Ecb = 0x0003, - Cocktail = 0x0004, - Aes128Cbc = 0x0005, - KeyExchange = 0x0006 - } - - public enum CipherTypes : ushort - { - Invalid = 0x0000, - Rsa1024 = 0x0001, - ChainedLicense = 0x0002, - Ecc256 = 0x0003, - Ecc256WithKz = 0x0004, - TeeTransient = 0x0005, - Ecc256ViaSymmetric = 0x0006 - } - - public readonly byte[] KeyId = keyId; - public readonly KeyTypes KeyType = keyType; - public readonly CipherTypes CipherType = cipherType; - public readonly byte[] RawKey = rawKey; -} \ No newline at end of file diff --git a/csplayready/license/XmlKey.cs b/csplayready/license/XmlKey.cs deleted file mode 100644 index a9628a7..0000000 --- a/csplayready/license/XmlKey.cs +++ /dev/null @@ -1,33 +0,0 @@ -using csplayready.crypto; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Math.EC; - -namespace csplayready.license; - -public class XmlKey -{ - private static readonly ECCurve Curve = ECNamedCurveTable.GetByName("secp256r1").Curve; - - private readonly BigInteger _sharedX; - private readonly BigInteger _sharedY; - - public readonly byte[] AesIv; - public readonly byte[] AesKey; - - public XmlKey() - { - var sharedPoint = EccKey.Generate(); - _sharedX = sharedPoint.PublicKey.XCoord.ToBigInteger(); - _sharedY = sharedPoint.PublicKey.YCoord.ToBigInteger(); - - var sharedXBytes = _sharedX.ToRawByteArray(); - AesIv = sharedXBytes[..16]; - AesKey = sharedXBytes[16..]; - } - - public ECPoint GetPoint() - { - return Curve.CreatePoint(_sharedX, _sharedY); - } -} diff --git a/csplayready/license/XmrLicense.cs b/csplayready/license/XmrLicense.cs deleted file mode 100644 index 9cecdcc..0000000 --- a/csplayready/license/XmrLicense.cs +++ /dev/null @@ -1,255 +0,0 @@ -using csplayready.constructcs; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Macs; -using Org.BouncyCastle.Crypto.Parameters; -using static csplayready.constructcs.ParserBuilder; - -namespace csplayready.license; - -public class XmrLicenseStructs -{ - private static readonly Struct PlayEnablerType = new( - Bytes("player_enabler_type", 16) - ); - - private static readonly Struct DomainRestrictionObject = new( - Bytes("account_id", 16), - Int32ub("revision") - ); - - private static readonly Struct IssueDateObject = new( - Int32ub("issue_date") - ); - - private static readonly Struct RevInfoVersionObject = new( - Int32ub("sequence") - ); - - private static readonly Struct SecurityLevelObject = new( - Int16ub("minimum_security_level") - ); - - private static readonly Struct EmbeddedLicenseSettingsObject = new( - Int16ub("indicator") - ); - - private static readonly Struct EccKeyObject = new( - Int16ub("curve_type"), - Int16ub("key_length"), - Bytes("key", ctx => ctx["key_length"]) - ); - - private static readonly Struct SignatureObject = new( - Int16ub("signature_type"), - Int16ub("signature_data_length"), - Bytes("signature_data", ctx => ctx["signature_data_length"]) - ); - - private static readonly Struct ContentKeyObject = new( - Bytes("key_id", 16), - Int16ub("key_type"), - Int16ub("cipher_type"), - Int16ub("key_length"), - Bytes("encrypted_key", ctx => ctx["key_length"]) - ); - - private static readonly Struct RightsSettingsObject = new( - Int16ub("rights") - ); - - private static readonly Struct OutputProtectionLevelRestrictionObject = new( - Int16ub("minimum_compressed_digital_video_opl"), - Int16ub("minimum_uncompressed_digital_video_opl"), - Int16ub("minimum_analog_video_opl"), - Int16ub("minimum_digital_compressed_audio_opl"), - Int16ub("minimum_digital_uncompressed_audio_opl") - ); - - private static readonly Struct ExpirationRestrictionObject = new( - Int32ub("begin_date"), - Int32ub("end_date") - ); - - private static readonly Struct RemovalDateObject = new( - Int32ub("removal_date") - ); - - private static readonly Struct UplinkKidObject = new( - Bytes("uplink_kid", 16), - Int16ub("chained_checksum_type"), - Int16ub("chained_checksum_length"), - Bytes("chained_checksum", ctx => ctx["chained_checksum_length"]) - ); - - private static readonly Struct AnalogVideoOutputConfigurationRestriction = new( - Bytes("video_output_protection_id", 16), - Bytes("binary_configuration_data", ctx => (uint)ctx["length"] - 24) - ); - - private static readonly Struct DigitalVideoOutputRestrictionObject = new( - Bytes("video_output_protection_id", 16), - Bytes("binary_configuration_data", ctx => (uint)ctx["length"] - 24) - ); - - private static readonly Struct DigitalAudioOutputRestrictionObject = new( - Bytes("audio_output_protection_id", 16), - Bytes("binary_configuration_data", ctx => (uint)ctx["length"] - 24) - ); - - private static readonly Struct PolicyMetadataObject = new( - Bytes("metadata_type", 16), - Bytes("policy_data", ctx => (uint)ctx["length"] - 24) - ); - - private static readonly Struct SecureStopRestrictionObject = new( - Bytes("metering_id", 16) - ); - - private static readonly Struct MeteringRestrictionObject = new( - Bytes("metering_id", 16) - ); - - private static readonly Struct ExpirationAfterFirstPlayRestrictionObject = new( - Int32ub("seconds") - ); - - private static readonly Struct GracePeriodObject = new( - Int32ub("grace_period") - ); - - private static readonly Struct SourceIdObject = new( - Int32ub("source_id") - ); - - private static readonly Struct AuxiliaryKey = new( - Int32ub("location"), - Bytes("key", 16) - ); - - private static readonly Struct AuxiliaryKeysObject = new( - Int16ub("count"), - Array("auxiliary_keys", Child(string.Empty, AuxiliaryKey), ctx => ctx["count"]) - ); - - private static readonly Struct UplinkKeyObject3 = new( - Bytes("uplink_key_id", 16), - Int16ub("chained_length"), - Bytes("checksum", ctx => ctx["chained_length"]), - Int16ub("count"), - Array("entries", Int32ub(string.Empty), ctx => ctx["count"]) - ); - - private static readonly Struct CopyEnablerObject = new( - Bytes("copy_enabler_type", 16) - ); - - private static readonly Struct CopyCountRestrictionObject = new( - Int32ub("count") - ); - - private static readonly Struct MoveObject = new( - Int32ub("minimum_move_protection_level") - ); - - private static readonly Struct XmrObject = new( - Int16ub("flags"), - Int16ub("type"), - Int32ub("length"), - Switch("data", ctx => ctx["type"], i => i switch - { - 0x0005 => Child(string.Empty, OutputProtectionLevelRestrictionObject), - 0x0008 => Child(string.Empty, AnalogVideoOutputConfigurationRestriction), - 0x000a => Child(string.Empty, ContentKeyObject), - 0x000b => Child(string.Empty, SignatureObject), - 0x000d => Child(string.Empty, RightsSettingsObject), - 0x0012 => Child(string.Empty, ExpirationRestrictionObject), - 0x0013 => Child(string.Empty, IssueDateObject), - 0x0016 => Child(string.Empty, MeteringRestrictionObject), - 0x001a => Child(string.Empty, GracePeriodObject), - 0x0022 => Child(string.Empty, SourceIdObject), - 0x002a => Child(string.Empty, EccKeyObject), - 0x002c => Child(string.Empty, PolicyMetadataObject), - 0x0029 => Child(string.Empty, DomainRestrictionObject), - 0x0030 => Child(string.Empty, ExpirationAfterFirstPlayRestrictionObject), - 0x0031 => Child(string.Empty, DigitalAudioOutputRestrictionObject), - 0x0032 => Child(string.Empty, RevInfoVersionObject), - 0x0033 => Child(string.Empty, EmbeddedLicenseSettingsObject), - 0x0034 => Child(string.Empty, SecurityLevelObject), - 0x0037 => Child(string.Empty, MoveObject), - 0x0039 => Child(string.Empty, PlayEnablerType), - 0x003a => Child(string.Empty, CopyEnablerObject), - 0x003b => Child(string.Empty, UplinkKidObject), - 0x003d => Child(string.Empty, CopyCountRestrictionObject), - 0x0050 => Child(string.Empty, RemovalDateObject), - 0x0051 => Child(string.Empty, AuxiliaryKeysObject), - 0x0052 => Child(string.Empty, UplinkKeyObject3), - 0x005a => Child(string.Empty, SecureStopRestrictionObject), - 0x0059 => Child(string.Empty, DigitalVideoOutputRestrictionObject), - _ => Child(string.Empty, () => XmrObject!) - }) - ); - - protected static readonly Struct XmrLicense = new( - Const("signature", "XMR\0"u8.ToArray()), - Int32ub("xmr_version"), - Bytes("rights_id", 16), - GreedyRange("containers", Child(string.Empty, XmrObject)) - ); -} - -public class XmrLicense(Dictionary data) : XmrLicenseStructs -{ - public static XmrLicense Loads(byte[] data) - { - return new XmrLicense(XmrLicense.Parse(data)); - } - - public byte[] Dumps() - { - return XmrLicense.Build(data); - } - - private static Dictionary Locate(Dictionary container) - { - while (true) - { - var flags = (ushort)container["flags"]; - if (flags != 2 && flags != 3) return container; - - container = (Dictionary)container["data"]; - } - } - - public IEnumerable> GetObject(ushort type) - { - foreach (Dictionary obj in (List)data["containers"]) - { - var container = Locate(obj); - if ((ushort)container["type"] == type) - yield return (Dictionary)container["data"]; - } - } - - public bool CheckSignature(byte[] integrityKey) - { - var cmac = new CMac(new AesEngine()); - cmac.Init(new KeyParameter(integrityKey)); - - var signatureObject = GetObject(11).FirstOrDefault(); - if (signatureObject == null) - throw new InvalidLicense("License does not contain a signature object"); - - var message = Dumps()[..^((ushort)signatureObject["signature_data_length"] + 12)]; - cmac.BlockUpdate(message, 0, message.Length); - - var result = new byte[cmac.GetMacSize()]; - cmac.DoFinal(result, 0); - - return result.SequenceEqual((byte[])signatureObject["signature_data"]); - } - - public Dictionary GetData() - { - return data; - } -} \ No newline at end of file diff --git a/csplayready/system/BCert.cs b/csplayready/system/BCert.cs deleted file mode 100644 index cb2effe..0000000 --- a/csplayready/system/BCert.cs +++ /dev/null @@ -1,505 +0,0 @@ -using System.Text; -using csplayready.constructcs; -using csplayready.crypto; -using Org.BouncyCastle.Asn1.X9; -using Org.BouncyCastle.Math; -using static csplayready.constructcs.ParserBuilder; - -namespace csplayready.system; - -public class BCertStructs -{ - protected static readonly Struct DrmBCertBasicInfo = new( - Bytes("cert_id", 16), - Int32ub("security_level"), - Int32ub("flags"), - Int32ub("cert_type"), - Bytes("public_key_digest", 32), - Int32ub("expiration_date"), - Bytes("client_id", 16) - ); - - protected static readonly Struct DrmBCertDomainInfo = new( - Bytes("service_id", 16), - Bytes("account_id", 16), - Int32ub("revision_timestamp"), - Int32ub("domain_url_length"), - Bytes("domain_url", ctx => ((uint)ctx["domain_url_length"] + 3) & 0xfffffffc) // TODO: use string - ); - - protected static readonly Struct DrmBCertPcInfo = new( - Int32ub("security_version") - ); - - protected static readonly Struct DrmBCertDeviceInfo = new( - Int32ub("max_license"), - Int32ub("max_header"), - Int32ub("max_chain_depth") - ); - - protected static readonly Struct DrmBCertFeatureInfo = new( - Int32ub("feature_count"), - Array("features", Int32ub(string.Empty), ctx => ctx["feature_count"]) - ); - - protected static readonly Struct DrmBCertKeyInfo = new( - Int32ub("key_count"), - Array("cert_keys", Child(string.Empty, new Struct( - Int16ub("type"), - Int16ub("length"), - Int32ub("flags"), - Bytes("key", ctx => (ushort)ctx["length"] / 8), - Int32ub("usages_count"), - Array("usages", Int32ub(string.Empty), ctx => ctx["usages_count"]) - )), ctx => ctx["key_count"]) - ); - - protected static readonly Struct DrmBCertManufacturerInfo = new( - Int32ub("flags"), - Int32ub("manufacturer_name_length"), - Bytes("manufacturer_name", ctx => ((uint)ctx["manufacturer_name_length"] + 3) & 0xfffffffc), // TODO: use string - Int32ub("model_name_length"), - Bytes("model_name", ctx => ((uint)ctx["model_name_length"] + 3) & 0xfffffffc), // TODO: use string - Int32ub("model_number_length"), - Bytes("model_number", ctx => ((uint)ctx["model_number_length"] + 3) & 0xfffffffc) // TODO: use string - ); - - protected static readonly Struct DrmBCertSignatureInfo = new( - Int16ub("signature_type"), - Int16ub("signature_size"), - Bytes("signature", ctx => ctx["signature_size"]), - Int32ub("signature_key_size"), - Bytes("signature_key", ctx => (uint)ctx["signature_key_size"] / 8) - ); - - protected static readonly Struct DrmBCertSilverlightInfo = new( - Int32ub("security_version"), - Int32ub("platform_identifier") - ); - - protected static readonly Struct DrmBCertMeteringInfo = new( - Bytes("metering_id", 16), - Int32ub("metering_url_length"), - Bytes("metering_url", ctx => ((uint)ctx["metering_url_length"] + 3) & 0xfffffffc) // TODO: use string - ); - - protected static readonly Struct DrmBCertExtDataSignKeyInfo = new( - Int16ub("key_type"), - Int16ub("key_length"), - Int32ub("flags"), - Bytes("key", ctx => (ushort)ctx["key_length"] / 8) - ); - - protected static readonly Struct BCertExtDataRecord = new( - Int32ub("data_size"), - Bytes("data", ctx => ctx["data_size"]) - ); - - protected static readonly Struct DrmBCertExtDataSignature = new( - Int16ub("signature_type"), - Int16ub("signature_size"), - Bytes("signature", ctx => ctx["signature_size"]) - ); - - protected static readonly Struct BCertExtDataContainer = new( - Int32ub("record_count"), - Array("records", Child(string.Empty, BCertExtDataRecord), ctx => ctx["record_count"]), - Child("signature", DrmBCertExtDataSignature) - ); - - protected static readonly Struct DrmBCertServerInfo = new( - Int32ub("warning_days") - ); - - protected static readonly Struct DrmBcertSecurityVersion = new( - Int32ub("security_version"), - Int32ub("platform_identifier") - ); - - protected static readonly Struct Attribute = new( - Int16ub("flags"), - Int16ub("tag"), - Int32ub("length"), - Switch("attribute", ctx => ctx["tag"], i => i switch - { - 1 => Child(string.Empty, DrmBCertBasicInfo), - 2 => Child(string.Empty, DrmBCertDomainInfo), - 3 => Child(string.Empty, DrmBCertPcInfo), - 4 => Child(string.Empty, DrmBCertDeviceInfo), - 5 => Child(string.Empty, DrmBCertFeatureInfo), - 6 => Child(string.Empty, DrmBCertKeyInfo), - 7 => Child(string.Empty, DrmBCertManufacturerInfo), - 8 => Child(string.Empty, DrmBCertSignatureInfo), - 9 => Child(string.Empty, DrmBCertSilverlightInfo), - 10 => Child(string.Empty, DrmBCertMeteringInfo), - 11 => Child(string.Empty, DrmBCertExtDataSignKeyInfo), - 12 => Child(string.Empty, BCertExtDataContainer), - 13 => Child(string.Empty, DrmBCertExtDataSignature), - 14 => Bytes(string.Empty, ctx => (uint)ctx["length"] - 8), - 15 => Child(string.Empty, DrmBCertServerInfo), - 16 => Child(string.Empty, DrmBcertSecurityVersion), - 17 => Child(string.Empty, DrmBcertSecurityVersion), - _ => Bytes(string.Empty, ctx => (uint)ctx["length"] - 8) - }) - ); - - protected static readonly Struct BCert = new( - Const("signature", "CERT"u8.ToArray()), - Int32ub("version"), - Int32ub("total_length"), - Int32ub("certificate_length"), - GreedyRange("attributes", Child(string.Empty, Attribute)) - ); - - protected static readonly Struct BCertChain = new( - Const("signature", "CHAI"u8.ToArray()), - Int32ub("version"), - Int32ub("total_length"), - Int32ub("flags"), - Int32ub("certificate_count"), - GreedyRange("certificates", Child(string.Empty, BCert)) - ); -} - -public class Certificate(Dictionary data) : BCertStructs -{ - public static Certificate Loads(byte[] data) - { - return new Certificate(BCert.Parse(data)); - } - - public byte[] Dumps() - { - return BCert.Build(data); - } - - public static Certificate NewLeafCertificate(byte[] certId, uint securityLevel, byte[] clientId, EccKey signingKey, EccKey encryptionKey, EccKey groupKey, CertificateChain parent, uint expiry = 0xFFFFFFFF) - { - var basicInfo = new Dictionary - { - { "cert_id", certId }, - { "security_level", securityLevel }, - { "flags", (uint)0 }, - { "cert_type", (uint)2 }, - { "public_key_digest", signingKey.PublicSha256Digest() }, - { "expiration_date", expiry }, - { "client_id", clientId } - }; - var basicInfoAttribute = new Dictionary - { - { "flags", (ushort)1 }, - { "tag", (ushort)1 }, - { "length", (uint)(DrmBCertBasicInfo.Build(basicInfo).Length + 8) }, - { "attribute", basicInfo } - }; - - var deviceInfo = new Dictionary - { - { "max_license", (uint)10240 }, - { "max_header", (uint)15360 }, - { "max_chain_depth", (uint)2 } - }; - var deviceInfoAttribute = new Dictionary - { - { "flags", (ushort)1 }, - { "tag", (ushort)4 }, - { "length", (uint)(DrmBCertDeviceInfo.Build(deviceInfo).Length + 8) }, - { "attribute", deviceInfo } - }; - - var feature = new Dictionary - { - { "feature_count", 3 }, - { "features", new List - { - // 1, // Transmitter - // 2, // Receiver - // 3, // SharedCertificate - 4, // SecureClock - // 5, // AntiRollBackClock - // 6, // ReservedMetering - // 7, // ReservedLicSync - // 8, // ReservedSymOpt - 9, // CRLS (Revocation Lists) - // 10, // ServerBasicEdition - // 11, // ServerStandardEdition - // 12, // ServerPremiumEdition - 13, // PlayReady3Features - // 14, // DeprecatedSecureStop - } }, - }; - var featureAttribute = new Dictionary - { - { "flags", (ushort)1 }, - { "tag", (ushort)5 }, - { "length", (uint)(DrmBCertFeatureInfo.Build(feature).Length + 8) }, - { "attribute", feature } - }; - - var certKeySign = new Dictionary - { - { "type", (ushort)1 }, - { "length", (ushort)512 }, - { "flags", (uint)0 }, - { "key", signingKey.PublicBytes() }, - { "usages_count", (uint)1 }, - { "usages", new List - { - (uint)1 // KEYUSAGE_SIGN - } }, - }; - var certKeyEncrypt = new Dictionary - { - { "type", (ushort)1 }, - { "length", (ushort)512 }, - { "flags", (uint)0 }, - { "key", encryptionKey.PublicBytes() }, - { "usages_count", (uint)1 }, - { "usages", new List - { - (uint)2 // KEYUSAGE_ENCRYPT_KEY - } }, - }; - var keyInfo = new Dictionary - { - { "key_count", (uint)2 }, - { "cert_keys", new List - { - certKeySign, - certKeyEncrypt - } }, - }; - var keyInfoAttribute = new Dictionary - { - { "flags", (ushort)1 }, - { "tag", (ushort)6 }, - { "length", (uint)(DrmBCertKeyInfo.Build(keyInfo).Length + 8) }, - { "attribute", keyInfo } - }; - - var manufacturerInfo = parent.Get(0).GetAttribute(7); - if (manufacturerInfo == null) - throw new InvalidCertificate("Parent's manufacturer info required for provisioning"); - - var newBCertContainer = new Dictionary - { - { "signature", "CERT"u8.ToArray() }, - { "version", (uint)1 }, - { "total_length", (uint)0 }, // filled at a later time - { "certificate_length", (uint)0 }, // filled at a later time - { "attributes", new List - { - basicInfoAttribute, - deviceInfoAttribute, - featureAttribute, - keyInfoAttribute, - manufacturerInfo - } }, - }; - - var payload = BCert.Build(newBCertContainer); - newBCertContainer["certificate_length"] = payload.Length; - newBCertContainer["total_length"] = payload.Length + 144; - - var signPayload = BCert.Build(newBCertContainer); - var signature = Crypto.Ecc256Sign(groupKey.PrivateKey, signPayload); - - var signatureInfo = new Dictionary - { - { "signature_type", (ushort)1 }, - { "signature_size", (ushort)signature.Length }, - { "signature", signature }, - { "signature_key_size", (uint)512 }, - { "signature_key", groupKey.PublicBytes() }, - }; - var signatureInfoAttribute = new Dictionary - { - { "flags", (ushort)1 }, - { "tag", (ushort)8 }, - { "length", (uint)(DrmBCertSignatureInfo.Build(signatureInfo).Length + 8) }, - { "attribute", signatureInfo } - }; - ((List)newBCertContainer["attributes"]).Add(signatureInfoAttribute); - - return new Certificate(newBCertContainer); - } - - public Dictionary? GetAttribute(ushort type) - { - foreach (Dictionary attribute in (List)data["attributes"]) - { - if ((ushort)attribute["tag"] == type) - return attribute; - } - return null; - } - - public uint? GetSecurityLevel() - { - var basicInfo = (Dictionary)GetAttribute(1)?["attribute"]!; - return (uint?)basicInfo["security_level"]; - } - - private static string UnPad(byte[] bytes, byte strip = 0) - { - var i = bytes.Length - 1; - for (; i >= 0; i--) - if(bytes[i] != strip) - break; - return Encoding.UTF8.GetString(bytes[..(i + 1)]); - } - - public string GetName() - { - var manufacturerInfo = (Dictionary)GetAttribute(7)?["attribute"]!; - return $"{UnPad((byte[])manufacturerInfo["manufacturer_name"])} {UnPad((byte[])manufacturerInfo["model_name"])} {UnPad((byte[])manufacturerInfo["model_number"])}"; - } - - public byte[]? GetIssuerKey() - { - var keyInfoObject = GetAttribute(6); - if (keyInfoObject == null) - return null; - - var keyInfoAttribute = (Dictionary)keyInfoObject["attribute"]; - return ((List)keyInfoAttribute["cert_keys"]) - .Cast>() - .Where(key => ((List)key["usages"]) - .Cast() - .Contains(6)) - .Select(key => (byte[]?)key["key"]) - .FirstOrDefault(); - } - - public byte[]? Verify(byte[] publicKey, int index) - { - var signatureObject = GetAttribute(8); - if (signatureObject == null) - throw new InvalidCertificate($"No signature object found in certificate {index}"); - - var signatureAttribute = (Dictionary)signatureObject["attribute"]; - - var rawSignatureKey = (byte[])signatureAttribute["signature_key"]; - if (!publicKey.SequenceEqual(rawSignatureKey)) - throw new InvalidCertificate($"Signature keys of certificate {index} do not match"); - - var signatureKey = ECNamedCurveTable.GetByName("secp256r1").Curve.CreatePoint( - new BigInteger(1, rawSignatureKey[..32]), - new BigInteger(1, rawSignatureKey[32..]) - ); - - var signPayload = Dumps()[..^(int)(uint)signatureObject["length"]]; - var signature = (byte[])signatureAttribute["signature"]; - - if (!Crypto.Ecc256Verify(signatureKey, signPayload, signature)) - throw new InvalidCertificate($"Signature of certificate {index} is not authentic"); - - return GetIssuerKey(); - } - - public Dictionary GetData() - { - return data; - } -} - -public class CertificateChain(Dictionary data) : BCertStructs -{ - private static readonly byte[] Ecc256MsbCertRootIssuerPubKey = - "864d61cff2256e422c568b3c28001cfb3e1527658584ba0521b79b1828d936de1d826a8fc3e6e7fa7a90d5ca2946f1f64a2efb9f5dcffe7e434eb44293fac5ab" - .HexToBytes(); - - public static CertificateChain Loads(byte[] data) - { - return new CertificateChain(BCertChain.Parse(data)); - } - - public static CertificateChain Load(string path) - { - var bytes = File.ReadAllBytes(path); - return Loads(bytes); - } - - public byte[] Dumps() - { - return BCertChain.Build(data); - } - - public void Dump(string path) - { - var bytes = Dumps(); - File.WriteAllBytes(path, bytes); - } - - public Certificate Get(int index) - { - var certificates = (List)data["certificates"]; - return new Certificate((Dictionary)certificates[index]); - } - - public uint Count() - { - return (uint)data["certificate_count"]; - } - - public void Append(Certificate bCert) - { - data["certificate_count"] = Count() + 1; - ((List)data["certificates"]).Add(bCert.GetData()); - data["total_length"] = (uint)data["total_length"] + bCert.Dumps().Length; - } - - public void Prepend(Certificate bCert) - { - data["certificate_count"] = Count() + 1; - ((List)data["certificates"]).Insert(0, bCert.GetData()); - data["total_length"] = (uint)data["total_length"] + bCert.Dumps().Length; - } - - public void Remove(int index) - { - data["certificate_count"] = Count() - 1; - data["total_length"] = (uint)data["total_length"] - Get(index).Dumps().Length; - ((List)data["certificates"]).RemoveAt(index); - } - - public uint? GetSecurityLevel() - { - return Get(0).GetSecurityLevel(); - } - - public string GetName() - { - return Get(0).GetName(); - } - - public bool Verify() - { - var issuerKey = Ecc256MsbCertRootIssuerPubKey; - - try - { - for (var i = (int)(Count() - 1); i >= 0; i--) - { - var certificate = Get(i); - issuerKey = certificate.Verify(issuerKey!, i); - - if (issuerKey == null && i != 0) - { - throw new InvalidCertificate($"Certificate {i} is not valid"); - } - } - } - catch (InvalidCertificate e) - { - throw new InvalidCertificateChain("CertificateChain is not valid", e); - } - - return true; - } - - public Dictionary GetData() - { - return data; - } -} \ No newline at end of file diff --git a/csplayready/system/PSSH.cs b/csplayready/system/PSSH.cs deleted file mode 100644 index 8bd06a7..0000000 --- a/csplayready/system/PSSH.cs +++ /dev/null @@ -1,59 +0,0 @@ -using csplayready.constructcs; -using static csplayready.constructcs.ParserBuilder; - -namespace csplayready.system; - -public class PsshStructs() -{ - private static readonly Struct PlayreadyObject = new( - Int16ul("type"), - Int16ul("length"), - Switch("data", ctx => ctx["type"], i => i switch - { - 1 => UTF16String(string.Empty, ctx => ctx["length"]), - 2 => Bytes(string.Empty, ctx => ctx["length"]), - 3 => Bytes(string.Empty, ctx => ctx["length"]), - _ => throw new ArgumentOutOfRangeException(nameof(i), i, null) - }) - ); - - private static readonly Struct PlayreadyHeader = new( - Int32ul("length"), - Int16ul("record_count"), - Array("records", Child("playreadyObject", PlayreadyObject), ctx => ctx["record_count"]) - ); - - protected static readonly Struct PsshBox = new( - Int32ub("length"), - Const("pssh", "pssh"u8.ToArray()), - Int32ub("fullbox"), - Bytes("system_id", 16), - Int32ub("data_length"), - Child("playreadyHeader", PlayreadyHeader) - ); -} - -public class Pssh : PsshStructs -{ - private readonly Dictionary _data; - - public Pssh(byte[] data) - { - _data = PsshBox.Parse(data); - } - - public Pssh(string b64Data) - { - var data = Convert.FromBase64String(b64Data); - _data = PsshBox.Parse(data); - } - - public string[] GetWrmHeaders() - { - var playreadyHeader = (Dictionary)_data["playreadyHeader"]; - var records = (List)playreadyHeader["records"]; - return records.Where(dict => (ushort)((Dictionary)dict)["type"] == 1) - .Select(dict => Convert.ToString(((Dictionary)dict)["data"])!) - .ToArray(); - } -} \ No newline at end of file diff --git a/csplayready/system/Session.cs b/csplayready/system/Session.cs deleted file mode 100644 index acca5e9..0000000 --- a/csplayready/system/Session.cs +++ /dev/null @@ -1,14 +0,0 @@ -using csplayready.crypto; -using csplayready.license; - -namespace csplayready.system; - -public class Session(int number) -{ - public readonly int Number = number; - public readonly int Id = new Random().Next(1, int.MaxValue); - public readonly XmlKey XmlKey = new XmlKey(); - public EccKey? SigningKey = null; - public EccKey? EncryptionKey = null; - public List Keys = []; -}