Initial Commit
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
# csplayready
|
||||
C# implementation of Microsoft's Playready DRM CDM (Content Decryption Module)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace constructcs;
|
||||
|
||||
public enum Encoding
|
||||
{
|
||||
Utf8,
|
||||
Utf16,
|
||||
Ascii
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 = [];
|
||||
}
|
||||
Reference in New Issue
Block a user