using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using System.Text; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Encodings; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.OpenSsl; using ProtoBuf; namespace CRD.Utils.DRM; public struct ContentDecryptionModule{ public byte[] privateKey{ get; set; } public byte[] identifierBlob{ get; set; } } public class DerivedKeys{ public byte[] Auth1{ get; set; } public byte[] Auth2{ get; set; } public byte[] Enc{ get; set; } } public class Session{ public byte[] WIDEVINE_SYSTEM_ID = new byte[]{ 237, 239, 139, 169, 121, 214, 74, 206, 163, 200, 39, 220, 213, 29, 33, 237 }; private RSA _devicePrivateKey; private ClientIdentification _identifierBlob; private byte[] _identifier; private byte[] _pssh; private byte[] _rawLicenseRequest; private byte[] _sessionKey; private DerivedKeys _derivedKeys; private OaepEncoding _decryptEngine; public List ContentKeys { get; set; } = new List(); public dynamic InitData{ get; set; } private AsymmetricCipherKeyPair DeviceKeys{ get; set; } public Session(ContentDecryptionModule contentDecryptionModule, byte[] pssh){ _devicePrivateKey = CreatePrivateKeyFromPem(contentDecryptionModule.privateKey); using var reader = new StringReader(Encoding.UTF8.GetString(contentDecryptionModule.privateKey)); DeviceKeys = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject(); _identifierBlob = Serializer.Deserialize(new MemoryStream(contentDecryptionModule.identifierBlob)); _identifier = GenerateIdentifier(); _pssh = pssh; InitData = ParseInitData(pssh); _decryptEngine = new OaepEncoding(new RsaEngine()); _decryptEngine.Init(false, DeviceKeys.Private); } private RSA CreatePrivateKeyFromPem(byte[] pemKey){ RSA rsa = RSA.Create(); string s = System.Text.Encoding.UTF8.GetString(pemKey); rsa.ImportFromPem(s); return rsa; } private byte[] GenerateIdentifier(){ // Generate 8 random bytes byte[] randomBytes = RandomNumberGenerator.GetBytes(8); // Convert to hex string string hex = BitConverter.ToString(randomBytes).Replace("-", "").ToLower(); // Concatenate with '01' and '00000000000000' string identifier = hex + "01" + "00000000000000"; // Convert the final string to a byte array return Encoding.UTF8.GetBytes(identifier); } public byte[] GetLicenseRequest(){ dynamic licenseRequest; if (InitData is WidevineCencHeader){ licenseRequest = new SignedLicenseRequest{ Type = SignedLicenseRequest.MessageType.LicenseRequest, Msg = new LicenseRequest{ Type = LicenseRequest.RequestType.New, KeyControlNonce = 1093602366, ProtocolVersion = ProtocolVersion.Current, RequestTime = uint.Parse((DateTime.Now - DateTime.UnixEpoch).TotalSeconds.ToString().Split(",")[0]), ContentId = new LicenseRequest.ContentIdentification{ CencId = new LicenseRequest.ContentIdentification.Cenc{ LicenseType = LicenseType.Default, RequestId = _identifier, Pssh = InitData } } } }; } else{ licenseRequest = new SignedLicenseRequestRaw{ Type = SignedLicenseRequestRaw.MessageType.LicenseRequest, Msg = new LicenseRequestRaw{ Type = LicenseRequestRaw.RequestType.New, KeyControlNonce = 1093602366, ProtocolVersion = ProtocolVersion.Current, RequestTime = uint.Parse((DateTime.Now - DateTime.UnixEpoch).TotalSeconds.ToString().Split(",")[0]), ContentId = new LicenseRequestRaw.ContentIdentification{ CencId = new LicenseRequestRaw.ContentIdentification.Cenc{ LicenseType = LicenseType.Default, RequestId = _identifier, Pssh = InitData } } } }; } licenseRequest.Msg.ClientId = _identifierBlob; //Logger.Debug("Signing license request"); using (var memoryStream = new MemoryStream()){ Serializer.Serialize(memoryStream, licenseRequest.Msg); byte[] data = memoryStream.ToArray(); _rawLicenseRequest = data; licenseRequest.Signature = Sign(data); } byte[] requestBytes; using (var memoryStream = new MemoryStream()){ Serializer.Serialize(memoryStream, licenseRequest); requestBytes = memoryStream.ToArray(); } return requestBytes; } static WidevineCencHeader ParseInitData(byte[] initData){ WidevineCencHeader cencHeader; try{ cencHeader = Serializer.Deserialize(new MemoryStream(initData[32..])); } catch{ try{ //needed for HBO Max PSSHBox psshBox = PSSHBox.FromByteArray(initData); cencHeader = Serializer.Deserialize(new MemoryStream(psshBox.Data)); } catch{ //Logger.Verbose("Unable to parse, unsupported init data format"); return null; } } return cencHeader; } public byte[] Sign(byte[] data){ PssSigner eng = new PssSigner(new RsaEngine(), new Sha1Digest()); eng.Init(true, DeviceKeys.Private); eng.BlockUpdate(data, 0, data.Length); return eng.GenerateSignature(); } public byte[] Decrypt(byte[] data){ int blockSize = _decryptEngine.GetInputBlockSize(); List plainText = new List(); // Process the data in blocks for (int chunkPosition = 0; chunkPosition < data.Length; chunkPosition += blockSize){ int chunkSize = Math.Min(blockSize, data.Length - chunkPosition); byte[] decryptedChunk = _decryptEngine.ProcessBlock(data, chunkPosition, chunkSize); plainText.AddRange(decryptedChunk); } return plainText.ToArray(); } public void ProvideLicense(byte[] license){ SignedLicense signedLicense; try{ signedLicense = Serializer.Deserialize(new MemoryStream(license)); } catch{ throw new Exception("Unable to parse license"); } try{ var sessionKey = Decrypt(signedLicense.SessionKey); if (sessionKey.Length != 16){ throw new Exception("Unable to decrypt session key"); } _sessionKey = sessionKey; } catch{ throw new Exception("Unable to decrypt session key"); } _derivedKeys = DeriveKeys(_rawLicenseRequest, _sessionKey); byte[] licenseBytes; using (var memoryStream = new MemoryStream()){ Serializer.Serialize(memoryStream, signedLicense.Msg); licenseBytes = memoryStream.ToArray(); } byte[] hmacHash = CryptoUtils.GetHMACSHA256Digest(licenseBytes, _derivedKeys.Auth1); if (!hmacHash.SequenceEqual(signedLicense.Signature)){ throw new Exception("License signature mismatch"); } foreach (License.KeyContainer key in signedLicense.Msg.Keys){ string type = key.Type.ToString(); if (type == "Signing") continue; byte[] keyId; byte[] encryptedKey = key.Key; byte[] iv = key.Iv; keyId = key.Id; if (keyId == null){ keyId = Encoding.ASCII.GetBytes(key.Type.ToString()); } byte[] decryptedKey; using MemoryStream mstream = new MemoryStream(); using AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider{ Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 }; using CryptoStream cryptoStream = new CryptoStream(mstream, aesProvider.CreateDecryptor(_derivedKeys.Enc, iv), CryptoStreamMode.Write); cryptoStream.Write(encryptedKey, 0, encryptedKey.Length); decryptedKey = mstream.ToArray(); List permissions = new List(); if (type == "OperatorSession"){ foreach (PropertyInfo perm in key._OperatorSessionKeyPermissions.GetType().GetProperties()){ if ((uint)perm.GetValue(key._OperatorSessionKeyPermissions) == 1){ permissions.Add(perm.Name); } } } ContentKeys.Add(new ContentKey{ KeyID = keyId, Type = type, Bytes = decryptedKey, Permissions = permissions }); } } public static DerivedKeys DeriveKeys(byte[] message, byte[] key){ byte[] encKeyBase = Encoding.UTF8.GetBytes("ENCRYPTION").Concat(new byte[]{ 0x0, }).Concat(message).Concat(new byte[]{ 0x0, 0x0, 0x0, 0x80 }).ToArray(); byte[] authKeyBase = Encoding.UTF8.GetBytes("AUTHENTICATION").Concat(new byte[]{ 0x0, }).Concat(message).Concat(new byte[]{ 0x0, 0x0, 0x2, 0x0 }).ToArray(); byte[] encKey = new byte[]{ 0x01 }.Concat(encKeyBase).ToArray(); byte[] authKey1 = new byte[]{ 0x01 }.Concat(authKeyBase).ToArray(); byte[] authKey2 = new byte[]{ 0x02 }.Concat(authKeyBase).ToArray(); byte[] authKey3 = new byte[]{ 0x03 }.Concat(authKeyBase).ToArray(); byte[] authKey4 = new byte[]{ 0x04 }.Concat(authKeyBase).ToArray(); byte[] encCmacKey = CryptoUtils.GetCMACDigest(encKey, key); byte[] authCmacKey1 = CryptoUtils.GetCMACDigest(authKey1, key); byte[] authCmacKey2 = CryptoUtils.GetCMACDigest(authKey2, key); byte[] authCmacKey3 = CryptoUtils.GetCMACDigest(authKey3, key); byte[] authCmacKey4 = CryptoUtils.GetCMACDigest(authKey4, key); byte[] authCmacCombined1 = authCmacKey1.Concat(authCmacKey2).ToArray(); byte[] authCmacCombined2 = authCmacKey3.Concat(authCmacKey4).ToArray(); return new DerivedKeys{ Auth1 = authCmacCombined1, Auth2 = authCmacCombined2, Enc = encCmacKey }; } // public KeyContainer ParseLicense(byte[] rawLicense){ // if (_rawLicenseRequest == null){ // throw new InvalidOperationException("Please request a license first."); // } // // // Assuming SignedMessage and License have Decode methods that deserialize the respective types // var signedLicense = Serializer.Deserialize(new MemoryStream(rawLicense)); // byte[] sessionKey = _devicePrivateKey.Decrypt(signedLicense.SessionKey, RSAEncryptionPadding.OaepSHA256); // // var cmac = new AesCmac(sessionKey); // var encKeyBase = Concat("ENCRYPTION\x00", _rawLicenseRequest, "\x00\x00\x00\x80"); // var authKeyBase = Concat("AUTHENTICATION\x00", _rawLicenseRequest, "\x00\x00\x02\x00"); // // byte[] encKey = cmac.ComputeHash(Concat("\x01", encKeyBase)); // byte[] serverKey = Concat( // cmac.ComputeHash(Concat("\x01", authKeyBase)), // cmac.ComputeHash(Concat("\x02", authKeyBase)) // ); // // using var hmac = new HMACSHA256(serverKey); // byte[] calculatedSignature = hmac.ComputeHash(signedLicense.Msg); // // if (!calculatedSignature.SequenceEqual(signedLicense.Signature)){ // throw new InvalidOperationException("Signatures do not match."); // } // // var license = License.Decode(signedLicense.Msg); // // return license.Key.Select(keyContainer => { // string keyId = keyContainer.Id.Length > 0 ? BitConverter.ToString(keyContainer.Id).Replace("-", "").ToLower() : keyContainer.Type.ToString(); // using var aes = Aes.Create(); // aes.Key = encKey; // aes.IV = keyContainer.Iv; // aes.Mode = CipherMode.CBC; // // using var decryptor = aes.CreateDecryptor(); // byte[] decryptedKey = decryptor.TransformFinalBlock(keyContainer.Key, 0, keyContainer.Key.Length); // // return new KeyContainer{ // Kid = keyId, // Key = BitConverter.ToString(decryptedKey).Replace("-", "").ToLower() // }; // }).ToArray(); // } }