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