From f1cd0e27b72b02c3ae257fb4a22379b5d4086663 Mon Sep 17 00:00:00 2001 From: feyris-tan <4116042+feyris-tan@users.noreply.github.com> Date: Sun, 4 Jan 2026 14:09:35 +0100 Subject: [PATCH] Added DRM Module code. --- Sophia.Net.DRM/Sophia.Net.DRM.csproj | 13 + Sophia.Net.DRM/Sophia.Net.DRM.sln | 31 +++ Sophia.Net.DRM/SophiaNetDrmApi.cs | 350 ++++++++++++++++++++++++ Sophia.Net.DRM/SophiaNetEntitlements.cs | 88 ++++++ 4 files changed, 482 insertions(+) create mode 100644 Sophia.Net.DRM/Sophia.Net.DRM.csproj create mode 100644 Sophia.Net.DRM/Sophia.Net.DRM.sln create mode 100644 Sophia.Net.DRM/SophiaNetDrmApi.cs create mode 100644 Sophia.Net.DRM/SophiaNetEntitlements.cs diff --git a/Sophia.Net.DRM/Sophia.Net.DRM.csproj b/Sophia.Net.DRM/Sophia.Net.DRM.csproj new file mode 100644 index 0000000..7867787 --- /dev/null +++ b/Sophia.Net.DRM/Sophia.Net.DRM.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Sophia.Net.DRM/Sophia.Net.DRM.sln b/Sophia.Net.DRM/Sophia.Net.DRM.sln new file mode 100644 index 0000000..66643b3 --- /dev/null +++ b/Sophia.Net.DRM/Sophia.Net.DRM.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36705.20 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sophia.Net.DRM", "Sophia.Net.DRM.csproj", "{30741C45-1A9C-4151-883F-B11965EE376A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sophia.Net.DRM.Tool", "..\Sophia.Net.DRM.Tool\Sophia.Net.DRM.Tool.csproj", "{B9D739C2-128B-42E0-8079-406105FB2C67}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {30741C45-1A9C-4151-883F-B11965EE376A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30741C45-1A9C-4151-883F-B11965EE376A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30741C45-1A9C-4151-883F-B11965EE376A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30741C45-1A9C-4151-883F-B11965EE376A}.Release|Any CPU.Build.0 = Release|Any CPU + {B9D739C2-128B-42E0-8079-406105FB2C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9D739C2-128B-42E0-8079-406105FB2C67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9D739C2-128B-42E0-8079-406105FB2C67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9D739C2-128B-42E0-8079-406105FB2C67}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {43A39C7F-8F28-4007-A6F1-81F50765BEF0} + EndGlobalSection +EndGlobal diff --git a/Sophia.Net.DRM/SophiaNetDrmApi.cs b/Sophia.Net.DRM/SophiaNetDrmApi.cs new file mode 100644 index 0000000..4ae94b3 --- /dev/null +++ b/Sophia.Net.DRM/SophiaNetDrmApi.cs @@ -0,0 +1,350 @@ +using System.Text; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.X509.Extension; +using X509Certificate = Org.BouncyCastle.X509.X509Certificate; + +namespace Sophia.Net.DRM +{ + public static class SophiaNetDrmApi + { + + public static X509Certificate CreateSelfSignedCertificate(string commonName, AsymmetricCipherKeyPair keyPair, string sophiaNetExtension = null, bool addSophiaNetPurpose = false) + { + CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator(); + SecureRandom random = new SecureRandom(randomGenerator); + + //When acting as a certificate authority, use a proper serial number here - as an argument to this function! + BigInteger serial = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random); + + X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator(); + certificateGenerator.SetSerialNumber(serial); + + X509Name subjectDn = new X509Name(String.Format("CN={0}", commonName)); + X509Name issuerDn = subjectDn; + certificateGenerator.SetSubjectDN(subjectDn); + certificateGenerator.SetIssuerDN(issuerDn); + certificateGenerator.SetPublicKey(keyPair.Public); + + DateTime notBefore = DateTime.Now.Date; + DateTime notAfter = notBefore.AddYears(100); + certificateGenerator.SetNotBefore(notBefore); + certificateGenerator.SetNotAfter(notAfter); + + certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(false)); + + + AsymmetricCipherKeyPair subjectKeyPair = keyPair; + AsymmetricCipherKeyPair issuerKeyPair = subjectKeyPair; + + SubjectKeyIdentifier subjectKeyIdentifier = X509ExtensionUtilities.CreateSubjectKeyIdentifier(keyPair.Public); + certificateGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier.Id, false, subjectKeyIdentifier); + + AuthorityKeyIdentifier authorityKeyIdentifier = X509ExtensionUtilities.CreateAuthorityKeyIdentifier(issuerKeyPair.Public); + certificateGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifier); + + if (!string.IsNullOrEmpty(sophiaNetExtension)) + { + DerObjectIdentifier derObjectIdentifier = new DerObjectIdentifier("1.3.6.1.3.545127833961986897372532.1"); + certificateGenerator.AddExtension(derObjectIdentifier, false, System.Text.Encoding.UTF8.GetBytes("sophia.net self-signed certificate")); + } + + List usageList = new List(); + usageList.Add(KeyPurposeID.id_kp_serverAuth); + usageList.Add(KeyPurposeID.id_kp_codeSigning); + if (addSophiaNetPurpose) + { + DerObjectIdentifier doi = new DerObjectIdentifier("1.3.6.1.3.545127833961986897372532.2"); + usageList.Add(doi); + } + + certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(usageList.ToArray())); + + const string signatureAlgorithm = "SHA256WithRSA"; + Asn1SignatureFactory asn1SignatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private, random); + X509Certificate x509Certificate = certificateGenerator.Generate(asn1SignatureFactory); + return x509Certificate; + } + + public static AsymmetricCipherKeyPair GenerateKeyPair(int strength = 9216, SecureRandom random = null) + { + if (random == null) + { + CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator(); + random = new SecureRandom(randomGenerator); + } + KeyGenerationParameters keyGenerationParameters = new KeyGenerationParameters(random, strength); + + RsaKeyPairGenerator rsaKeyPairGenerator = new RsaKeyPairGenerator(); + rsaKeyPairGenerator.Init(keyGenerationParameters); + return rsaKeyPairGenerator.GenerateKeyPair(); + } + + public static byte[] ExportPrivateKey(AsymmetricCipherKeyPair keypair) + { + MemoryStream memoryStream = new MemoryStream(); + StreamWriter streamWriter = new StreamWriter(memoryStream); + PemWriter pemWriter = new PemWriter(streamWriter); + pemWriter.WriteObject(keypair.Private); + streamWriter.Flush(); + memoryStream.Flush(); + return memoryStream.ToArray(); + } + + public static byte[] ExportPublicKey(AsymmetricCipherKeyPair keypair) + { + MemoryStream memoryStream = new MemoryStream(); + StreamWriter streamWriter = new StreamWriter(memoryStream); + PemWriter pemWriter = new PemWriter(streamWriter); + pemWriter.WriteObject(keypair.Public); + streamWriter.Flush(); + memoryStream.Flush(); + return memoryStream.ToArray(); + } + + public static AsymmetricCipherKeyPair ImportKeyPairFromPrivateKey(byte[] privateBytes) + { + MemoryStream memoryStream = new MemoryStream(privateBytes); + StreamReader streamReader = new StreamReader(memoryStream); + PemReader pemReader = new PemReader(streamReader); + object privateKey = pemReader.ReadObject(); + + AsymmetricCipherKeyPair result = privateKey as AsymmetricCipherKeyPair; + return result; + } + + public static byte[] ExportCertificate(X509Certificate certificate) + { + return certificate.GetEncoded(); + } + + public static byte[] ExportCertificateAsPkcs12(AsymmetricKeyParameter privateKey, string password, params X509Certificate[] certificate) + { + CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator(); + SecureRandom random = new SecureRandom(randomGenerator); + + Pkcs12StoreBuilder builder = new Pkcs12StoreBuilder(); + Pkcs12Store store = builder.Build(); + + string[] friendlyNames = new string[certificate.Length]; + X509CertificateEntry[] certificateEntries = new X509CertificateEntry[certificate.Length]; + for (int i = 0; i < certificate.Length; i++) + { + friendlyNames[i] = certificate[i].SubjectDN.ToString(); + certificateEntries[i] = new X509CertificateEntry(certificate[i]); + } + + store.SetCertificateEntry(friendlyNames[0], certificateEntries[0]); + store.SetKeyEntry(friendlyNames[0], new AsymmetricKeyEntry(privateKey), certificateEntries); + + MemoryStream ms = new MemoryStream(); + store.Save(ms, password.ToCharArray(), random); + return ms.ToArray(); + } + + public static X509Certificate CreateRootCertificate(string commonName, AsymmetricCipherKeyPair keyPair) + { + CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator(); + SecureRandom random = new SecureRandom(randomGenerator); + + //When issuing child certificates, use a proper serial number here - as an argument to this function! + BigInteger serial = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random); + + X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator(); + certificateGenerator.SetSerialNumber(serial); + + X509Name subjectDn = new X509Name(String.Format("CN={0}", commonName)); + X509Name issuerDn = subjectDn; + certificateGenerator.SetSubjectDN(subjectDn); + certificateGenerator.SetIssuerDN(issuerDn); + certificateGenerator.SetPublicKey(keyPair.Public); + + DateTime notBefore = DateTime.Now.Date; + DateTime notAfter = notBefore.AddYears(100); + certificateGenerator.SetNotBefore(notBefore); + certificateGenerator.SetNotAfter(notAfter); + + //BasicConstraints basicConstraints = new BasicConstraints(true); + //certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(true)); + certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(2)); + + AsymmetricCipherKeyPair subjectKeyPair = keyPair; + AsymmetricCipherKeyPair issuerKeyPair = subjectKeyPair; + + SubjectKeyIdentifier subjectKeyIdentifier = X509ExtensionUtilities.CreateSubjectKeyIdentifier(keyPair.Public); + certificateGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier.Id, false, subjectKeyIdentifier); + + AuthorityKeyIdentifier authorityKeyIdentifier = X509ExtensionUtilities.CreateAuthorityKeyIdentifier(issuerKeyPair.Public); + certificateGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifier); + + const string signatureAlgorithm = "SHA256WithRSA"; + Asn1SignatureFactory asn1SignatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private, random); + X509Certificate x509Certificate = certificateGenerator.Generate(asn1SignatureFactory); + return x509Certificate; + + } + + public static X509Certificate CreateChildCertificate(string commonName, X509Certificate issuerCert, AsymmetricCipherKeyPair issuerPrivateKey, AsymmetricKeyParameter childPublicKey, long serialNumber, string sophiaNetExtensionValue) + { + return CreateChildCertificate(commonName, issuerCert, issuerPrivateKey, childPublicKey, serialNumber, Encoding.UTF8.GetBytes(sophiaNetExtensionValue)); + } + + public static X509Certificate CreateChildCertificate(string commonName, X509Certificate issuerCert, AsymmetricCipherKeyPair issuerPrivateKey, AsymmetricKeyParameter childPublicKey, long serialNumber, SophiaNetEntitlementCollection entitlements) + { + byte[] serialize = entitlements.Serialize(); + return CreateChildCertificate(commonName, issuerCert, issuerPrivateKey, childPublicKey, serialNumber, serialize); + } + + public static X509Certificate CreateChildCertificate(string commonName, X509Certificate issuerCert, AsymmetricCipherKeyPair issuerPrivateKey, AsymmetricKeyParameter childPublicKey, long serialNumber, byte[] sophiaNetExtensionValue) + { + CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator(); + SecureRandom random = new SecureRandom(randomGenerator); + + BigInteger serial = BigInteger.ValueOf(serialNumber); + + X509Name subjectDn = new X509Name(String.Format("CN={0}", commonName)); + + DateTime notBefore = DateTime.Now.Date; + DateTime notAfter = issuerCert.NotAfter; + + DerObjectIdentifier sophiaNetExtensionId = new DerObjectIdentifier("1.3.6.1.3.545127833961986897372532.1"); + + X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator(); + certificateGenerator.SetSerialNumber(serial); + certificateGenerator.SetSubjectDN(subjectDn); + certificateGenerator.SetIssuerDN(issuerCert.SubjectDN); + certificateGenerator.SetPublicKey(childPublicKey); + certificateGenerator.SetNotBefore(notBefore); + certificateGenerator.SetNotAfter(notAfter); + certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(false)); + certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(KeyPurposeID.id_kp_clientAuth)); + certificateGenerator.AddExtension(sophiaNetExtensionId, false, sophiaNetExtensionValue); + + const string signatureAlgorithm = "SHA256WithRSA"; + Asn1SignatureFactory asn1SignatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey.Private, random); + X509Certificate x509Certificate = certificateGenerator.Generate(asn1SignatureFactory); + return x509Certificate; + } + + public static X509Certificate ImportCertificate(byte[] encoded) + { + X509Certificate result = new X509Certificate(encoded); + return result; + } + + public static bool ValidateCertificateChain(X509Certificate child, X509Certificate issuer) + { + try + { + child.Verify(issuer.GetPublicKey()); + return true; + } + catch (Exception e) + { + return false; + } + } + + public static X509Crl CreateCertificateRevocationList(X509Certificate issuer, AsymmetricCipherKeyPair issuerPrivateKey, params long[] revokedSerials) + { + CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator(); + SecureRandom random = new SecureRandom(randomGenerator); + + long notAfterSeconds = issuer.NotAfter.Ticks / TimeSpan.TicksPerSecond; + long nowSeconds = DateTime.Now.Ticks / TimeSpan.TicksPerSecond; + long secondsDifference = notAfterSeconds - nowSeconds; + if (secondsDifference > Int32.MaxValue) + secondsDifference = Int32.MaxValue; + long _30days = new TimeSpan(30, 0, 0, 0).Ticks / TimeSpan.TicksPerSecond; + + int max = (int)secondsDifference; + int min = (int)_30days; + int updateDelayInt = random.Next(min, max); + TimeSpan updateDelayTimespan = TimeSpan.FromSeconds(updateDelayInt); + + X509V2CrlGenerator crlGenerator = new X509V2CrlGenerator(); + crlGenerator.SetIssuerDN(issuer.IssuerDN); + crlGenerator.SetThisUpdate(DateTime.Now); + crlGenerator.SetNextUpdate(DateTime.Now + updateDelayTimespan); + crlGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, X509ExtensionUtilities.CreateAuthorityKeyIdentifier(issuer)); + crlGenerator.AddExtension(X509Extensions.CrlNumber, false, new CrlNumber(BigInteger.One)); + + foreach (long revokedSerial in revokedSerials) + { + crlGenerator.AddCrlEntry(BigInteger.ValueOf(revokedSerial),DateTime.Now, CrlReason.PrivilegeWithdrawn); + } + + const string signatureAlgorithm = "SHA256WithRSA"; + Asn1SignatureFactory signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey.Private, random); + X509Crl crl = crlGenerator.Generate(signatureFactory); + return crl; + } + + public static byte[] ExportCrl(X509Crl crl) + { + return crl.GetEncoded(); + } + + public static X509Crl ImportCrl(byte[] buffer) + { + X509Crl result = new X509Crl(buffer); + return result; + } + + public static bool ValidateCertificateAgainstCrl(X509Certificate certificate, X509Crl crl) + { + BigInteger mySerial = certificate.SerialNumber; + ISet x509CrlEntries = crl.GetRevokedCertificates(); + foreach (X509CrlEntry entry in x509CrlEntries) + { + BigInteger entrySerial = entry.SerialNumber; + if (entrySerial.Equals(mySerial)) + return false; + } + + return true; + } + + public static byte[] GetSophiaNetExtensionFromCertificate(X509Certificate cert) + { + DerObjectIdentifier sophiaNetExtensionId = new DerObjectIdentifier("1.3.6.1.3.545127833961986897372532.1"); + X509Extension x509Extension = cert.GetExtension(sophiaNetExtensionId); + Asn1OctetString octetString = x509Extension.Value; + Stream octetStream = octetString.Parser.GetOctetStream(); + Asn1InputStream asn1Reader = new Asn1InputStream(octetStream); + DerOctetString readObject = (DerOctetString)asn1Reader.ReadObject(); + byte[] result = readObject.GetOctets(); + return result; + } + + public static byte[] SignFile(byte[] toBeSigned, AsymmetricKeyParameter privateKey) + { + RsaDigestSigner signer = new RsaDigestSigner(new Sha256Digest()); + signer.Init(true, privateKey); + signer.BlockUpdate(toBeSigned, 0, toBeSigned.Length); + return signer.GenerateSignature(); + } + + public static bool ValidateSignedFile(byte[] signed, byte[] signature, AsymmetricKeyParameter publicKey) + { + RsaDigestSigner signer = new RsaDigestSigner(new Sha256Digest()); + signer.Init(false, publicKey); + signer.BlockUpdate(signed, 0, signed.Length); + + bool isValid = signer.VerifySignature(signature); + return isValid; + } + } +} diff --git a/Sophia.Net.DRM/SophiaNetEntitlements.cs b/Sophia.Net.DRM/SophiaNetEntitlements.cs new file mode 100644 index 0000000..7103b53 --- /dev/null +++ b/Sophia.Net.DRM/SophiaNetEntitlements.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sophia.Net.DRM +{ + public class SophiaNetEntitlementCollection + { + public SophiaNetEntitlementCollection(params Guid[] guids) + { + _backend = new Guid[guids.Length]; + Array.Copy(guids,0,_backend,0, guids.Length); + } + + public SophiaNetEntitlementCollection(byte[] buffer) + { + _backend = new Guid[buffer.Length / 16]; + for (int i = 0; i < buffer.Length; i += 16) + { + Span part = new Span(buffer, i, 16); + _backend[i / 16] = new Guid(part); + } + } + + public byte[] Serialize() + { + byte[] outBuffer = new byte[_backend.Length * 16]; + for (int i = 0; i < _backend.Length; i++) + { + byte[] iBuffer = _backend[i].ToByteArray(); + Array.Copy(iBuffer, 0, outBuffer, i * 16, 16); + } + + return outBuffer; + } + + private Guid[] _backend; + + public bool HasEntitlement(Guid check) + { + for (int i = 0; i < _backend.Length; i++) + { + if (_backend[i].Equals(check)) + return true; + } + return false; + } + + protected bool Equals(SophiaNetEntitlementCollection that) + { + if (this._backend.Length != that._backend.Length) + return false; + + for (int i = 0; i < this._backend.Length; i++) + { + Guid mine = this._backend[i]; + Guid theirs = that._backend[i]; + if (!mine.Equals(theirs)) + return false; + } + + return true; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != this.GetType()) + return false; + return Equals((SophiaNetEntitlementCollection)obj); + } + + public override int GetHashCode() + { + long result = 0; + for (int i = 0; i < _backend.Length; i++) + { + result += _backend[i].GetHashCode(); + } + return (int)result; + } + } +}