332 lines
13 KiB
C#
332 lines
13 KiB
C#
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<ContentKey> ContentKeys { get; set; } = new List<ContentKey>();
|
|
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<ClientIdentification>(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<WidevineCencHeader>(new MemoryStream(initData[32..]));
|
|
} catch{
|
|
try{
|
|
//needed for HBO Max
|
|
|
|
PSSHBox psshBox = PSSHBox.FromByteArray(initData);
|
|
cencHeader = Serializer.Deserialize<WidevineCencHeader>(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<byte> plainText = new List<byte>();
|
|
|
|
// 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<SignedLicense>(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<string> permissions = new List<string>();
|
|
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<SignedMessage>(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();
|
|
// }
|
|
} |