Initial Commit

This commit is contained in:
titus
2025-01-02 16:24:09 +01:00
parent 7586f99d61
commit 447c1e027f
19 changed files with 0 additions and 2396 deletions

View File

@@ -1,2 +0,0 @@
# csplayready
C# implementation of Microsoft's Playready DRM CDM (Content Decryption Module)

View File

@@ -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

View File

@@ -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<int, Session> _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 = $"<Data><CertificateChains><CertificateChain>{b64Chain}</CertificateChain></CertificateChains></Data>";
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
"<LA xmlns=\"http://schemas.microsoft.com/DRM/2007/03/protocols\" Id=\"SignedData\" xml:space=\"preserve\">" +
"<Version>1</Version>" +
$"<ContentHeader>{wrmHeader}</ContentHeader>" +
"<CLIENTINFO>" +
"<CLIENTVERSION>10.0.16384.10011</CLIENTVERSION>" +
"</CLIENTINFO>" +
$"<LicenseNonce>{nonce}</LicenseNonce>" +
$"<ClientTime>{secondsSinceEpoch}</ClientTime>" +
"<EncryptedData xmlns=\"http://www.w3.org/2001/04/xmlenc#\" Type=\"http://www.w3.org/2001/04/xmlenc#Element\">" +
"<EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>" +
"<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">" +
"<EncryptedKey xmlns=\"http://www.w3.org/2001/04/xmlenc#\">" +
"<EncryptionMethod Algorithm=\"http://schemas.microsoft.com/DRM/2007/03/protocols#ecc256\"></EncryptionMethod>" +
"<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">" +
"<KeyName>WMRMServer</KeyName>" +
"</KeyInfo>" +
"<CipherData>" +
$"<CipherValue>{encryptedKey}</CipherValue>" +
"</CipherData>" +
"</EncryptedKey>" +
"</KeyInfo>" +
"<CipherData>" +
$"<CipherValue>{encryptedCert}</CipherValue>" +
"</CipherData>" +
"</EncryptedData>" +
"</LA>";
}
private static string GetSignedInfo(string digestValue)
{
return
"<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">" +
"<CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"></CanonicalizationMethod>" +
"<SignatureMethod Algorithm=\"http://schemas.microsoft.com/DRM/2007/03/protocols#ecdsa-sha256\"></SignatureMethod>" +
"<Reference URI=\"#SignedData\">" +
"<DigestMethod Algorithm=\"http://schemas.microsoft.com/DRM/2007/03/protocols#sha256\"></DigestMethod>" +
$"<DigestValue>{digestValue}</DigestValue>" +
"</Reference>" +
"</SignedInfo>";
}
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
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
"<soap:Body>" +
"<AcquireLicense xmlns=\"http://schemas.microsoft.com/DRM/2007/03/protocols\">" +
"<challenge>" +
"<Challenge xmlns=\"http://schemas.microsoft.com/DRM/2007/03/protocols/messages\">" +
laContent +
"<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">" +
signedInfo +
$"<SignatureValue>{Convert.ToBase64String(signature)}</SignatureValue>" +
"<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">" +
"<KeyValue>" +
"<ECCKeyValue>" +
$"<PublicKey>{Convert.ToBase64String(session.SigningKey.PublicBytes())}</PublicKey>" +
"</ECCKeyValue>" +
"</KeyValue>" +
"</KeyInfo>" +
"</Signature>" +
"</Challenge>" +
"</challenge>" +
"</AcquireLicense>" +
"</soap:Body>" +
"</soap:Envelope>";
}
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<Key> GetKeys(int sessionId)
{
if (!_sessions.TryGetValue(sessionId, out Session? session))
throw new InvalidSession($"Session identifier {sessionId} is invalid");
return session.Keys;
}
}

View File

@@ -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) { }
}

View File

@@ -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<string> 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);
}
}

View File

@@ -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<string, object> 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;
}
}
}

View File

@@ -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<string, object> _values = new();
public void SetValue(string name, object value) => _values[name] = value;
public T GetValue<T>(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<object>(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<ContextAccess, object>? LengthExpression;
public BytesField(string name, int length) : base(name)
{
Length = length;
}
public BytesField(string name, Func<ContextAccess, object> 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<ContextAccess, object> 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<ContextAccess, object>? _countExpression;
public ArrayField(string name, Field field, int count) : base(name)
{
_field = field;
_count = count;
}
public ArrayField(string name, Field field, Func<ContextAccess, object> 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<object>();
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<object>();
for (var i = 0; i < count; i++)
{
var item = ((List<object>)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<Struct>? _nestedStructExpression;
public StructField(string name, Struct nestedStruct) : base(name)
{
_nestedStruct = nestedStruct;
}
public StructField(string name, Func<Struct> 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<string, object>)value);
context.SetValue(Name, value);
}
}
public class SwitchField : Field
{
private readonly int? _index;
private readonly Func<ContextAccess, object>? _indexExpression;
private readonly Func<int, Field> _switchExpression;
public SwitchField(string name, int index, Func<int, Field> switchExpression) : base(name)
{
_index = index;
_switchExpression = switchExpression;
}
public SwitchField(string name, Func<ContextAccess, object> indexExpression, Func<int, Field> 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<ContextAccess, object>? _conditionExpression;
public ConditionalField(string name, bool condition, Field thenField, Field elseField) : base(name)
{
_condition = condition;
_thenField = thenField;
_elseField = elseField;
}
public ConditionalField(string name, Func<ContextAccess, object> 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<ContextAccess, object>? _minExpression;
private readonly int? _max;
private readonly Func<ContextAccess, object>? _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<ContextAccess, object> minExpression, int max) : base(name)
{
_field = field;
_minExpression = minExpression;
_max = max;
}
public RangeField(string name, Field field, int min, Func<ContextAccess, object> maxExpression) : base(name)
{
_field = field;
_min = min;
_maxExpression = maxExpression;
}
public RangeField(string name, Field field, Func<ContextAccess, object> minExpression, Func<ContextAccess, object> 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<object>();
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<object>)value;
if (!(min <= items.Count && items.Count <= max))
throw new Exception($"Expected from {min} to {max} elements, found {items.Count}");
var results = new List<object>();
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<Field> _fields = [];
public Struct(params Field[] fields)
{
_fields.AddRange(fields);
}
public Dictionary<string, object> Parse(byte[] data)
{
using var stream = new MemoryStream(data);
return Parse(stream);
}
public Dictionary<string, object> Parse(Stream stream)
{
using var reader = new BinaryReader(stream);
return Parse(reader, new Context());
}
public Dictionary<string, object> Parse(BinaryReader reader, Context context)
{
var result = new Dictionary<string, object>();
foreach (var field in _fields)
{
var value = field.Read(reader, context);
result[field.Name] = value;
}
return result;
}
public byte[] Build(Dictionary<string, object> values)
{
using var memoryStream = new MemoryStream();
Build(memoryStream, values);
return memoryStream.ToArray();
}
public void Build(Stream stream, Dictionary<string, object> values)
{
using var writer = new BinaryWriter(stream);
Build(writer, new Context(), values);
}
public void Build(BinaryWriter writer, Context context, Dictionary<string, object> 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<ContextAccess, object> 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<ContextAccess, object> 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<ContextAccess, object> 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<ContextAccess, object> 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<ContextAccess, object> countExpression) => new(name, element, countExpression);
public static StructField Child(string name, Struct nestedStruct) => new(name, nestedStruct);
public static StructField Child(string name, Func<Struct> nestedStructExpression) => new(name, nestedStructExpression);
public static SwitchField Switch(string name, int index, Func<int, Field> switchExpression) => new(name, index, switchExpression);
public static SwitchField Switch(string name, Func<ContextAccess, object> indexExpression, Func<int, Field> 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<ContextAccess, object> 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<ContextAccess, object> 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<ContextAccess, object> minExpression, int max) => new(name, field, minExpression, max);
public static RangeField Range(string name, Field field, int min, Func<ContextAccess, object> maxExpression) => new(name, field, min, maxExpression);
public static RangeField Range(string name, Field field, Func<ContextAccess, object> minExpression, Func<ContextAccess, object> maxExpression) => new(name, field, minExpression, maxExpression);
public static RangeField GreedyRange(string name, Field field) => new(name, field, 0, int.MaxValue);
}

View File

@@ -1,8 +0,0 @@
namespace constructcs;
public enum Encoding
{
Utf8,
Utf16,
Ascii
}

View File

@@ -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);
}
}

View File

@@ -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());
}

View File

@@ -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();
}
}

View File

@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.0" />
</ItemGroup>
</Project>

View File

@@ -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<string, object>)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<string, object>
{
{ "signature", "PRD"u8.ToArray() },
{ "version", 3 },
{ "data", new Dictionary<string, object>
{
{ "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();
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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<string, object> data) : XmrLicenseStructs
{
public static XmrLicense Loads(byte[] data)
{
return new XmrLicense(XmrLicense.Parse(data));
}
public byte[] Dumps()
{
return XmrLicense.Build(data);
}
private static Dictionary<string, object> Locate(Dictionary<string, object> container)
{
while (true)
{
var flags = (ushort)container["flags"];
if (flags != 2 && flags != 3) return container;
container = (Dictionary<string, object>)container["data"];
}
}
public IEnumerable<Dictionary<string, object>> GetObject(ushort type)
{
foreach (Dictionary<string, object> obj in (List<object>)data["containers"])
{
var container = Locate(obj);
if ((ushort)container["type"] == type)
yield return (Dictionary<string, object>)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<string, object> GetData()
{
return data;
}
}

View File

@@ -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<string, object> 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<string, object>
{
{ "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<string, object>
{
{ "flags", (ushort)1 },
{ "tag", (ushort)1 },
{ "length", (uint)(DrmBCertBasicInfo.Build(basicInfo).Length + 8) },
{ "attribute", basicInfo }
};
var deviceInfo = new Dictionary<string, object>
{
{ "max_license", (uint)10240 },
{ "max_header", (uint)15360 },
{ "max_chain_depth", (uint)2 }
};
var deviceInfoAttribute = new Dictionary<string, object>
{
{ "flags", (ushort)1 },
{ "tag", (ushort)4 },
{ "length", (uint)(DrmBCertDeviceInfo.Build(deviceInfo).Length + 8) },
{ "attribute", deviceInfo }
};
var feature = new Dictionary<string, object>
{
{ "feature_count", 3 },
{ "features", new List<object>
{
// 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<string, object>
{
{ "flags", (ushort)1 },
{ "tag", (ushort)5 },
{ "length", (uint)(DrmBCertFeatureInfo.Build(feature).Length + 8) },
{ "attribute", feature }
};
var certKeySign = new Dictionary<string, object>
{
{ "type", (ushort)1 },
{ "length", (ushort)512 },
{ "flags", (uint)0 },
{ "key", signingKey.PublicBytes() },
{ "usages_count", (uint)1 },
{ "usages", new List<object>
{
(uint)1 // KEYUSAGE_SIGN
} },
};
var certKeyEncrypt = new Dictionary<string, object>
{
{ "type", (ushort)1 },
{ "length", (ushort)512 },
{ "flags", (uint)0 },
{ "key", encryptionKey.PublicBytes() },
{ "usages_count", (uint)1 },
{ "usages", new List<object>
{
(uint)2 // KEYUSAGE_ENCRYPT_KEY
} },
};
var keyInfo = new Dictionary<string, object>
{
{ "key_count", (uint)2 },
{ "cert_keys", new List<object>
{
certKeySign,
certKeyEncrypt
} },
};
var keyInfoAttribute = new Dictionary<string, object>
{
{ "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<string, object>
{
{ "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<object>
{
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<string, object>
{
{ "signature_type", (ushort)1 },
{ "signature_size", (ushort)signature.Length },
{ "signature", signature },
{ "signature_key_size", (uint)512 },
{ "signature_key", groupKey.PublicBytes() },
};
var signatureInfoAttribute = new Dictionary<string, object>
{
{ "flags", (ushort)1 },
{ "tag", (ushort)8 },
{ "length", (uint)(DrmBCertSignatureInfo.Build(signatureInfo).Length + 8) },
{ "attribute", signatureInfo }
};
((List<object>)newBCertContainer["attributes"]).Add(signatureInfoAttribute);
return new Certificate(newBCertContainer);
}
public Dictionary<string, object>? GetAttribute(ushort type)
{
foreach (Dictionary<string, object> attribute in (List<object>)data["attributes"])
{
if ((ushort)attribute["tag"] == type)
return attribute;
}
return null;
}
public uint? GetSecurityLevel()
{
var basicInfo = (Dictionary<string, object>)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<string, object>)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<string, object>)keyInfoObject["attribute"];
return ((List<object>)keyInfoAttribute["cert_keys"])
.Cast<Dictionary<string, object>>()
.Where(key => ((List<object>)key["usages"])
.Cast<uint>()
.Contains<uint>(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<string, object>)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<string, object> GetData()
{
return data;
}
}
public class CertificateChain(Dictionary<string, object> 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<object>)data["certificates"];
return new Certificate((Dictionary<string, object>)certificates[index]);
}
public uint Count()
{
return (uint)data["certificate_count"];
}
public void Append(Certificate bCert)
{
data["certificate_count"] = Count() + 1;
((List<object>)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<object>)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<object>)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<string, object> GetData()
{
return data;
}
}

View File

@@ -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<string, object> _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<string, object>)_data["playreadyHeader"];
var records = (List<object>)playreadyHeader["records"];
return records.Where(dict => (ushort)((Dictionary<string, object>)dict)["type"] == 1)
.Select(dict => Convert.ToString(((Dictionary<string, object>)dict)["data"])!)
.ToArray();
}
}

View File

@@ -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<Key> Keys = [];
}