protected override void ProcessRecord()
        {
            if (!File.Exists(Path))
            {
                throw new PSArgumentException("CPIX file not found: " + Path, "Path");
            }

            var cpix = CpixDocument.Load(Path);

            if (!cpix.ContentKeysAreReadable)
            {
                throw new NotSupportedException("The content keys in the CPIX file are encrypted. This PowerShell command does not currently support decryption of encryted content keys.");
            }

            var communicationKey = Convert.FromBase64String(CommunicationKeyAsBase64);

            if (communicationKey.Length != 32)
            {
                throw new NotSupportedException("Communication key must be 256 bits long.");
            }

            foreach (var key in cpix.ContentKeys)
            {
                WriteVerbose("Adding key: " + key.Id);

                LicenseTokenLogic.AddKey(LicenseToken, key.Id, key.Value, communicationKey, KeyUsagePolicyName);
            }

            WriteObject(LicenseToken);
        }
Exemple #2
0
        private KeyDatabase()
        {
            var keys = new Dictionary <Guid, byte[]>();

            Keys = keys;

            var telemetry = new TelemetryClient();

            var appDataPath = HttpContext.Current.Server.MapPath("~/App_Data");

            telemetry.TrackTrace("Looking for keys in " + appDataPath);

            if (Directory.Exists(appDataPath))
            {
                foreach (var cpixFilePath in Directory.GetFiles(appDataPath, "*.xml"))
                {
                    var document = CpixDocument.Load(cpixFilePath);

                    if (!document.ContentKeysAreReadable)
                    {
                        continue;
                    }

                    foreach (var key in document.ContentKeys)
                    {
                        keys[key.Id] = key.Value;
                    }
                }
            }

            telemetry.TrackEvent("KeyDatabaseLoaded", null, new Dictionary <string, double>
            {
                { "KeyCount", keys.Count }
            });
        }
Exemple #3
0
        public void LoadDocument_WithXmlCommentsAddedAfterSigning_SuccessfullyValidatesSignature()
        {
            // The canonicalization we use excludes comments, so comments should have no effect on signature validity.

            var document = new CpixDocument();

            FillDocumentWithData(document);

            document.Recipients.AddSignature(TestHelpers.Certificate4WithPrivateKey);
            document.ContentKeys.AddSignature(TestHelpers.Certificate4WithPrivateKey);
            document.UsageRules.AddSignature(TestHelpers.Certificate4WithPrivateKey);
            document.SignedBy = TestHelpers.Certificate3WithPrivateKey;

            var buffer = new MemoryStream();

            document.Save(buffer);

            // Let's now sprinkle comments all over the place.
            var xmlDocument = new XmlDocument();

            buffer.Position = 0;
            xmlDocument.Load(buffer);

            var namespaces = XmlHelpers.CreateCpixNamespaceManager(xmlDocument);

            AddCommentAsChild(xmlDocument.DocumentElement);

            AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:DeliveryDataList", namespaces));
            AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:ContentKeyList", namespaces));
            AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:ContentKeyUsageRuleList", namespaces));

            AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:DeliveryDataList/cpix:DeliveryData", namespaces));
            AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:ContentKeyList/cpix:ContentKey", namespaces));
            AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:ContentKeyUsageRuleList/cpix:ContentKeyUsageRule", namespaces));

            buffer.SetLength(0);

            using (var writer = XmlWriter.Create(buffer, new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                CloseOutput = false,
            }))
            {
                xmlDocument.Save(writer);
            }

            buffer.Position = 0;
            document        = CpixDocument.Load(buffer);

            Assert.NotNull(document.SignedBy);
            Assert.Single(document.Recipients.SignedBy);
            Assert.Single(document.ContentKeys.SignedBy);
            Assert.Single(document.UsageRules.SignedBy);

            // And, of course, the data should still be there.
            Assert.Equal(2, document.ContentKeys.Count);
            Assert.Equal(2, document.Recipients.Count);
            Assert.Equal(2, document.UsageRules.Count);
        }
Exemple #4
0
        public void Load_WithCpixContainingManyHlsSignalingDataElementsAndOneWithoutPlaylistAttribute_Fails()
        {
            const string CpixWithOneSignalingDataElementWithoutPlaylistAttribute = "<?xml version=\"1.0\" encoding=\"utf-8\"?><CPIX xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:dashif:org:cpix\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:enc=\"http://www.w3.org/2001/04/xmlenc#\" xmlns:pskc=\"urn:ietf:params:xml:ns:keyprov:pskc\"><ContentKeyList><ContentKey kid=\"f8c80c25-690f-4736-8132-430e5c6994ce\"><Data><pskc:Secret><pskc:PlainValue>AQIDBAUGBwgJCgECAwQFBg==</pskc:PlainValue></pskc:Secret></Data></ContentKey></ContentKeyList><DRMSystemList><DRMSystem systemId=\"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\" kid=\"f8c80c25-690f-4736-8132-430e5c6994ce\"><HLSSignalingData playlist=\"master\">YWE=</HLSSignalingData><HLSSignalingData>YWE=</HLSSignalingData></DRMSystem></DRMSystemList></CPIX>";

            var ex = Assert.Throws <InvalidCpixDataException>(() =>
                                                              CpixDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(CpixWithOneSignalingDataElementWithoutPlaylistAttribute))));

            Assert.Contains("only one HLSSignalingData element", ex.Message);
        }
Exemple #5
0
        public void Load_WithCpixContainingMultipleDrmSystemsWithIdenticalSystemIdAndKeyId_Fails()
        {
            const string CpixWithInvalidDrmSystems = "<?xml version=\"1.0\" encoding=\"utf-8\"?><CPIX xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:dashif:org:cpix\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:enc=\"http://www.w3.org/2001/04/xmlenc#\" xmlns:pskc=\"urn:ietf:params:xml:ns:keyprov:pskc\"><ContentKeyList><ContentKey kid=\"f8c80c25-690f-4736-8132-430e5c6994ce\"><Data><pskc:Secret><pskc:PlainValue>AQIDBAUGBwgJCgECAwQFBg==</pskc:PlainValue></pskc:Secret></Data></ContentKey></ContentKeyList><DRMSystemList><DRMSystem systemId=\"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\" kid=\"f8c80c25-690f-4736-8132-430e5c6994ce\"></DRMSystem><DRMSystem systemId=\"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\" kid=\"f8c80c25-690f-4736-8132-430e5c6994ce\"></DRMSystem></DRMSystemList></CPIX>";

            var ex = Assert.Throws <InvalidCpixDataException>(() =>
                                                              CpixDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(CpixWithInvalidDrmSystems))));

            Assert.Contains("multiple DRM system signaling entries", ex.Message);
        }
Exemple #6
0
        public void Load_WithCpixContainingDrmSystemElementThatReferencesNonExistentContentKey_Fails()
        {
            const string CpixWithDrmSystemReferencingNonExistentContentKey = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48Q1BJWCB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4c2Q9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxucz0idXJuOmRhc2hpZjpvcmc6Y3BpeCIgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiIHhtbG5zOmVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpwc2tjPSJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOmtleXByb3Y6cHNrYyI+PENvbnRlbnRLZXlMaXN0PjxDb250ZW50S2V5IGtpZD0iZTVlMjE1YmMtYmMwZS00NzZkLTg0MmYtZmExMjQyMDQzMDIwIj48RGF0YT48cHNrYzpTZWNyZXQ+PHBza2M6UGxhaW5WYWx1ZT5meitmZnpLTldwbm84Ymt3UWM1V0FnPT08L3Bza2M6UGxhaW5WYWx1ZT48L3Bza2M6U2VjcmV0PjwvRGF0YT48L0NvbnRlbnRLZXk+PC9Db250ZW50S2V5TGlzdD48RFJNU3lzdGVtTGlzdD48RFJNU3lzdGVtIHN5c3RlbUlkPSJhYzRmMDc3Ny1jOTU0LTRjMjEtYjdiNC0xOWM2MTMxYTQyOGYiIGtpZD0iNTY5YjdlNTUtMDMxNy00NTg5LTg4YWEtYmI3OGRiODA2Zjg4Ij48Q29udGVudFByb3RlY3Rpb25EYXRhPlBIUmxjM1ErUEM5MFpYTjBQZz09PC9Db250ZW50UHJvdGVjdGlvbkRhdGE+PC9EUk1TeXN0ZW0+PC9EUk1TeXN0ZW1MaXN0PjwvQ1BJWD4=";

            var ex = Assert.Throws <InvalidCpixDataException>(() =>
                                                              CpixDocument.Load(new MemoryStream(Convert.FromBase64String(CpixWithDrmSystemReferencingNonExistentContentKey))));

            Assert.Contains("keys referenced by DRM system", ex.Message);
        }
Exemple #7
0
        public static CpixDocument Reload(CpixDocument document, IReadOnlyCollection <X509Certificate2> decryptionCertificates = null)
        {
            var buffer = new MemoryStream();

            document.Save(buffer);
            buffer.Position = 0;

            return(CpixDocument.Load(buffer, decryptionCertificates));
        }
Exemple #8
0
        public void Load_WithCpixContainingSingleHlsSignalingDataElementWithoutPlaylistAttribute_SucceedsWithDataInterpretedAsMediaPlaylistData()
        {
            const string CpixWithHlsSignalingDataWithoutPlaylistAttribute = "<?xml version=\"1.0\" encoding=\"utf-8\"?><CPIX xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:dashif:org:cpix\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:enc=\"http://www.w3.org/2001/04/xmlenc#\" xmlns:pskc=\"urn:ietf:params:xml:ns:keyprov:pskc\"><ContentKeyList><ContentKey kid=\"f8c80c25-690f-4736-8132-430e5c6994ce\"><Data><pskc:Secret><pskc:PlainValue>AQIDBAUGBwgJCgECAwQFBg==</pskc:PlainValue></pskc:Secret></Data></ContentKey></ContentKeyList><DRMSystemList><DRMSystem systemId=\"edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\" kid=\"f8c80c25-690f-4736-8132-430e5c6994ce\"><HLSSignalingData>YWE=</HLSSignalingData></DRMSystem></DRMSystemList></CPIX>";

            var document  = CpixDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(CpixWithHlsSignalingDataWithoutPlaylistAttribute)));
            var drmSystem = document.DrmSystems.First();

            Assert.NotNull(drmSystem.HlsSignalingData.MediaPlaylistData);
            Assert.Null(drmSystem.HlsSignalingData.MasterPlaylistData);
        }
Exemple #9
0
        public void LoadDocument_WithUtf16EncodedInput_Succeeds()
        {
            // We ensure that some non-ASCII text survives the encoding/decoding/signing process intact.
            const string canary = "滆 柦柋牬 趉軨鄇 鶊鵱, 緳廞徲 鋑鋡髬 溮煡煟 綡蒚";

            var document = new CpixDocument();

            FillDocumentWithData(document);

            document.UsageRules.First().LabelFilters = new[]
            {
                new LabelFilter
                {
                    Label = canary
                }
            };

            document.Recipients.AddSignature(TestHelpers.Certificate4WithPrivateKey);
            document.ContentKeys.AddSignature(TestHelpers.Certificate4WithPrivateKey);
            document.UsageRules.AddSignature(TestHelpers.Certificate4WithPrivateKey);
            document.SignedBy = TestHelpers.Certificate3WithPrivateKey;

            var buffer = new MemoryStream();

            document.Save(buffer);

            // Now we have a basic UTF-8 document in the buffer. Convert to UTF-16!
            // Using XmlDocument here to do it in a "smart" way with all the XML processing.
            var xmlDocument = new XmlDocument();

            buffer.Position = 0;
            xmlDocument.Load(buffer);

            buffer.SetLength(0);

            using (var writer = XmlWriter.Create(buffer, new XmlWriterSettings
            {
                Encoding = Encoding.Unicode,
                CloseOutput = false,
            }))
            {
                xmlDocument.Save(writer);
            }

            buffer.Position = 0;

            // Okay, does it load? It should!
            document = CpixDocument.Load(buffer);

            Assert.Equal(2, document.ContentKeys.Count);
            Assert.Equal(2, document.Recipients.Count);
            Assert.Equal(2, document.UsageRules.Count);

            Assert.Equal(canary, document.UsageRules.First().LabelFilters.Single().Label);
        }
Exemple #10
0
        private static void MappingContentKeysExample()
        {
            // Scenario: we take a CPIX document with content keys and usage rules for audio and video.
            // Then we map these content keys to content key contexts containing audio and video that we want to encrypt.
            // A suitable input document is the one generated by the "modifying CPIX" quick start example.

            CpixDocument document;

            using (var myCertificateAndPrivateKey = new X509Certificate2("Cert2.pfx", "Cert2"))
                document = CpixDocument.Load("cpix.xml", myCertificateAndPrivateKey);

            if (!document.ContentKeysAreReadable)
            {
                throw new Exception("The content keys were encrypted and we did not have a delivery key.");
            }

            // Let's imagine we have stereo audio at 32 kbps.
            var audioKey = document.ResolveContentKey(new ContentKeyContext
            {
                Type = ContentKeyContextType.Audio,

                Bitrate           = 32 * 1000,
                AudioChannelCount = 2
            });

            // Let's imagine we have both SD and HD video.
            var sdVideoKey = document.ResolveContentKey(new ContentKeyContext
            {
                Type = ContentKeyContextType.Video,

                Bitrate              = 1 * 1000 * 1000,
                PicturePixelCount    = 640 * 480,
                WideColorGamut       = false,
                HighDynamicRange     = false,
                VideoFramesPerSecond = 30
            });

            var hdVideoKey = document.ResolveContentKey(new ContentKeyContext
            {
                Type = ContentKeyContextType.Video,

                Bitrate              = 4 * 1000 * 1000,
                PicturePixelCount    = 1920 * 1080,
                WideColorGamut       = false,
                HighDynamicRange     = false,
                VideoFramesPerSecond = 30
            });

            Console.WriteLine("Key to use for audio: " + audioKey.Id);
            Console.WriteLine("Key to use for SD video: " + sdVideoKey.Id);
            Console.WriteLine("Key to use for HD video: " + hdVideoKey.Id);
        }
Exemple #11
0
        private static void ReadingCpixExample()
        {
            // A suitable input document is the one generated by the "writing CPIX" quick start example.

            CpixDocument document;

            // Optional: any private keys referenced by the certificate(s) you provide to Load() will be used for
            // decrypting any encrypted content keys. Even if you do not have a matching private key, the document
            // will still be successfully loaded but you will simply not have access to the values of the content keys.
            using (var myCertificateAndPrivateKey = new X509Certificate2("Cert2.pfx", "Cert2"))
                document = CpixDocument.Load("cpix.xml", myCertificateAndPrivateKey);

            if (document.ContentKeysAreReadable)
            {
                Console.WriteLine("We have access to the content key values.");
            }
            else
            {
                Console.WriteLine("The content keys are encrypted and we do not have a delivery key.");
            }

            var firstKey          = document.ContentKeys.FirstOrDefault();
            var firstSignerOfKeys = document.ContentKeys.SignedBy.FirstOrDefault();

            if (firstKey != null)
            {
                Console.WriteLine("First content key ID: " + firstKey.Id);
            }
            else
            {
                Console.WriteLine("No content keys in document.");
            }

            if (firstSignerOfKeys != null)
            {
                Console.WriteLine("Content keys first signed by: " + firstSignerOfKeys.SubjectName.Format(false));
            }
            else
            {
                Console.WriteLine("The content keys collection was not signed.");
            }

            if (document.SignedBy != null)
            {
                Console.WriteLine("Document signed by: " + document.SignedBy.SubjectName.Format(false));
            }
            else
            {
                Console.WriteLine("The document as a whole was not signed.");
            }
        }
Exemple #12
0
        public void LoadAndWriteAndReload_DocumentWithUnusualNamespacePrefixes_WorksJustFineAndDandy()
        {
            const string cpixPrefix    = "hagihuaa44444";
            const string pskcPrefix    = "se5o8jmmb7";
            const string xmlencPrefix  = "sbearbwabbbbb";
            const string xmldsigPrefix = "h878r88919919199198919";
            const string xsiPrefix     = "qqqqqq";
            const string xsdPrefix     = "irfui";

            // We create a blank document that predefines some unusual namespaces prefixes, then make sure all still works.
            var xmlDocument = new XmlDocument();

            xmlDocument.AppendChild(xmlDocument.CreateElement(cpixPrefix, "CPIX", Constants.CpixNamespace));

            // We delcare a default namespace that will not be used for anything.
            var attribute = xmlDocument.CreateAttribute(null, "xmlns", Constants.XmlnsNamespace);

            attribute.Value = "http://nonsense.com.example.org";
            xmlDocument.DocumentElement.Attributes.Append(attribute);

            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, xmldsigPrefix, Constants.XmlDigitalSignatureNamespace);
            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, xmlencPrefix, Constants.XmlEncryptionNamespace);
            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, pskcPrefix, Constants.PskcNamespace);
            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, xsiPrefix, "http://www.w3.org/2001/XMLSchema-instance");
            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, xsdPrefix, "http://www.w3.org/2001/XMLSchema");

            var buffer = new MemoryStream();

            xmlDocument.Save(buffer);

            // Now we have our document. Let's add stuff to it and see what happens.
            buffer.Position = 0;
            var document = CpixDocument.Load(buffer);

            FillDocumentWithData(document);

            document = TestHelpers.Reload(document);

            Assert.Equal(2, document.ContentKeys.Count);
            Assert.Equal(2, document.Recipients.Count);
            Assert.Equal(2, document.UsageRules.Count);
        }
Exemple #13
0
        static bool IsValidCpix(Stream stream)
        {
            stream.Position = 0;

            try
            {
                CpixDocument.Load(stream, new[]
                {
                    TestHelpers.Certificate1WithPrivateKey,
                    TestHelpers.Certificate2WithPrivateKey,
                    TestHelpers.Certificate3WithPrivateKey,
                    TestHelpers.Certificate4WithPrivateKey,
                });

                return(true);
            }
            catch
            {
                return(false);
            }
        }
Exemple #14
0
        private static void ModifyingCpixExample()
        {
            // Scenario: we take an input document containing some content keys and define usage rules for those keys.
            // A suitable input document is the one generated by the "writing CPIX" quick start example.

            var document = CpixDocument.Load("cpix.xml");

            if (document.ContentKeys.Count() < 2)
            {
                throw new Exception("This example assumes at least 2 content keys to be present in the CPIX document.");
            }

            // We are modifying the document, so we must first remove any document signature.
            document.SignedBy = null;

            // We are going to add some usage rules, so remove any signature on usage rules.
            document.UsageRules.RemoveAllSignatures();

            // If any usage rules already exist, get rid of them all.
            document.UsageRules.Clear();

            // Assign the first content key to all audio streams.
            document.UsageRules.Add(new UsageRule
            {
                KeyId = document.ContentKeys.First().Id,

                AudioFilters = new[] { new AudioFilter() }
            });

            // Assign the second content key to all video streams.
            document.UsageRules.Add(new UsageRule
            {
                KeyId = document.ContentKeys.Skip(1).First().Id,

                VideoFilters = new[] { new VideoFilter() }
            });

            // Save all changes. Note that we do not sign or re-sign anything in this example (although we could).
            document.Save("cpix.xml");
        }
Exemple #15
0
        public void SignAndLoadAndSave_DocumentWithWhitespaceAndIndentation_DoesNotBreakSignatures()
        {
            // Signatures are sensitive to even whitespace changes. While this library deliberately avoids generating
            // whitespace to keep it simple, we cannot assume that all input is without whitespace.
            // The library must be capable of preserving signed parts of existing documents that contain whitespace.

            var cpixStream = GenerateNontrivialCpixStream();

            // Now create a nicely formatted copy.
            var formattedCpixStream = new MemoryStream();

            XmlHelpers.PrettyPrintXml(cpixStream, formattedCpixStream);

            // Now sign it!
            var document = new XmlDocument();

            document.PreserveWhitespace = true;

            formattedCpixStream.Position = 0;
            document.Load(formattedCpixStream);

            // Note that the collections are not given IDs as they were not signed on save.
            // We need to manually give them IDs. That's fine - we can also verify that we have no ID format dependencies!
            var namespaces = XmlHelpers.CreateCpixNamespaceManager(document);

            const string recipientsId  = "id-for-recipients----";
            const string contentKeysId = "_id_for_content_keys";
            const string usageRulesId  = "a.0a.0a.0a.0a.0a.a0.0a0.0404040......";

            SetElementId(document, namespaces, "/cpix:CPIX/cpix:DeliveryDataList", recipientsId);
            SetElementId(document, namespaces, "/cpix:CPIX/cpix:ContentKeyList", contentKeysId);
            SetElementId(document, namespaces, "/cpix:CPIX/cpix:ContentKeyUsageRuleList", usageRulesId);

            CryptographyHelpers.SignXmlElement(document, recipientsId, TestHelpers.Certificate1WithPrivateKey);
            CryptographyHelpers.SignXmlElement(document, contentKeysId, TestHelpers.Certificate1WithPrivateKey);
            CryptographyHelpers.SignXmlElement(document, usageRulesId, TestHelpers.Certificate1WithPrivateKey);
            CryptographyHelpers.SignXmlElement(document, usageRulesId, TestHelpers.Certificate2WithPrivateKey);
            CryptographyHelpers.SignXmlElement(document, "", TestHelpers.Certificate1WithPrivateKey);

            // Okay, that's fine. Save!
            var signedCpixStream = new MemoryStream();

            using (var writer = XmlWriter.Create(signedCpixStream, new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                CloseOutput = false
            }))
            {
                document.Save(writer);
            }

            signedCpixStream.Position = 0;

            // Now it should be a nice valid and signed CPIX document.
            var cpix = CpixDocument.Load(signedCpixStream);

            Assert.NotNull(cpix.SignedBy);
            Assert.Single(cpix.Recipients.SignedBy);
            Assert.Single(cpix.ContentKeys.SignedBy);
            Assert.Equal(2, cpix.UsageRules.SignedBy.Count());

            // And save/load should preserve all the niceness.
            cpix = TestHelpers.Reload(cpix);

            Assert.NotNull(cpix.SignedBy);
            Assert.Single(cpix.Recipients.SignedBy);
            Assert.Single(cpix.ContentKeys.SignedBy);
            Assert.Equal(2, cpix.UsageRules.SignedBy.Count());

            // And, of course, the data should still be there.
            Assert.Equal(2, cpix.ContentKeys.Count);
            Assert.Equal(2, cpix.Recipients.Count);
            Assert.Equal(2, cpix.UsageRules.Count);

            // No exception? Success!
        }
Exemple #16
0
 public void LoadDocument_WithWeakCertificate_Fails()
 {
     Assert.Throws <WeakCertificateException>(() => CpixDocument.Load(new MemoryStream(), TestHelpers.WeakSha1CertificateWithPrivateKey));
     Assert.Throws <WeakCertificateException>(() => CpixDocument.Load(new MemoryStream(), TestHelpers.WeakSmallKeyCertificateWithPrivateKey));
 }
Exemple #17
0
        public void Generate(Stream outputStream)
        {
            // Implementation borrows heavily from UnusualInputTests.

            const string cpixPrefix    = "aa";
            const string pskcPrefix    = "bb";
            const string xmlencPrefix  = "cc";
            const string xmldsigPrefix = "dd";
            const string xsiPrefix     = "ee";
            const string xsdPrefix     = "ff";

            // We create a blank document that predefines the unusual namespaces prefixes.
            var xmlDocument = new XmlDocument();

            xmlDocument.AppendChild(xmlDocument.CreateElement(cpixPrefix, "CPIX", Constants.CpixNamespace));

            // We delcare a default namespace that will not be used for anything.
            var attribute = xmlDocument.CreateAttribute(null, "xmlns", Constants.XmlnsNamespace);

            attribute.Value = "⚽";
            xmlDocument.DocumentElement.Attributes.Append(attribute);

            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, xmldsigPrefix, Constants.XmlDigitalSignatureNamespace);
            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, xmlencPrefix, Constants.XmlEncryptionNamespace);
            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, pskcPrefix, Constants.PskcNamespace);
            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, xsiPrefix, "http://www.w3.org/2001/XMLSchema-instance");
            XmlHelpers.DeclareNamespace(xmlDocument.DocumentElement, xsdPrefix, "http://www.w3.org/2001/XMLSchema");

            var buffer = new MemoryStream();

            xmlDocument.Save(buffer);

            buffer.Position = 0;
            // Loading the blank document means we will now use the above prefixes.
            var document = CpixDocument.Load(buffer);

            const string complexLabel = "滆 柦柋牬 趉軨鄇 鶊鵱, 緳廞徲 鋑鋡髬 溮煡煟 綡蒚";

            document.ContentKeys.Add(new ContentKey
            {
                Id         = new Guid("152ae2e0-f455-486e-81d1-6df5fc5d7179"),
                Value      = Convert.FromBase64String("B+DoDP4r/j1NEr7b2aKXlw=="),
                ExplicitIv = Convert.FromBase64String("7qFwxoV85KBUNVXw8rgY3w==")
            });
            document.ContentKeys.Add(new ContentKey
            {
                Id         = new Guid("0cbe1c84-5c54-4ce8-8893-ff77f7d793e1"),
                Value      = Convert.FromBase64String("CedOXHsXc3xQ+HQuJOJZ+g=="),
                ExplicitIv = Convert.FromBase64String("zm4oYmRJWbLPrqXnhFS00w==")
            });
            document.ContentKeys.Add(new ContentKey
            {
                Id         = new Guid("486a8d08-29f7-42f5-9a9a-a1ab9b0685ad"),
                Value      = Convert.FromBase64String("ADq3douHS0QrY1omNB1njA=="),
                ExplicitIv = Convert.FromBase64String("wS4WCf4LgOe45BuG/qv5LA==")
            });
            document.ContentKeys.Add(new ContentKey
            {
                Id         = new Guid("84044421-a871-4999-8931-289aa6f4a607"),
                Value      = Convert.FromBase64String("JlZWL6tfkh6e8k9U1IOC8A=="),
                ExplicitIv = Convert.FromBase64String("9tlEFBcaX2S8/tfgQw9vAQ==")
            });

            DrmSignalingHelpers.AddDefaultSignalingForAllKeys(document);

            document.Recipients.Add(new Recipient(TestHelpers.Certificate1WithPublicKey));
            document.Recipients.Add(new Recipient(TestHelpers.Certificate2WithPublicKey));

            document.UsageRules.Add(new UsageRule
            {
                KeyId = document.ContentKeys.First().Id,

                AudioFilters = new[]
                {
                    new AudioFilter
                    {
                        MinChannels = 1,
                        MaxChannels = 2
                    },
                    new AudioFilter
                    {
                        MinChannels = 8,
                        MaxChannels = 10
                    }
                },
                BitrateFilters = new[]
                {
                    new BitrateFilter
                    {
                        MinBitrate = 1000,
                        MaxBitrate = 5 * 1000 * 1000
                    },
                    new BitrateFilter
                    {
                        MinBitrate = 10 * 1000 * 1000,
                        MaxBitrate = 32 * 1000 * 1000
                    }
                },
                LabelFilters = new[]
                {
                    new LabelFilter("EncryptedStream"),
                    new LabelFilter("CencStream"),
                    new LabelFilter(complexLabel),
                }
            });

            document.UsageRules.Add(new UsageRule
            {
                KeyId = document.ContentKeys.Last().Id,

                BitrateFilters = new[]
                {
                    new BitrateFilter
                    {
                        MinBitrate = 1000,
                        MaxBitrate = 5 * 1000 * 1000
                    },
                    new BitrateFilter
                    {
                        MinBitrate = 10 * 1000 * 1000,
                        MaxBitrate = 32 * 1000 * 1000
                    }
                },
                LabelFilters = new[]
                {
                    new LabelFilter("EncryptedStream"),
                    new LabelFilter("CencStream"),
                    new LabelFilter(complexLabel),
                },
                VideoFilters = new[]
                {
                    new VideoFilter
                    {
                        MinPixels          = 1000,
                        MaxPixels          = 1920 * 1080,
                        MinFramesPerSecond = 10,
                        MaxFramesPerSecond = 30,
                        WideColorGamut     = false,
                        HighDynamicRange   = true,
                    },
                    new VideoFilter
                    {
                        MinPixels          = 1000,
                        MaxPixels          = 4096 * 4096,
                        MinFramesPerSecond = 30,
                        MaxFramesPerSecond = 200,
                        WideColorGamut     = false,
                        HighDynamicRange   = false,
                    }
                }
            });

            // Save the XML, using funny prefixes and with complex data.
            buffer.SetLength(0);
            document.Save(buffer);

            // Now pretty-print the XML.
            var formattedStream = new MemoryStream();

            buffer.Position = 0;
            XmlHelpers.PrettyPrintXml(buffer, formattedStream);
            buffer = formattedStream;

            // Now modify the element IDs to be signed, sign the document, add comments and save as UTF-16.
            xmlDocument = new XmlDocument();
            xmlDocument.PreserveWhitespace = true;

            buffer.Position = 0;
            xmlDocument.Load(buffer);

            var namespaces = XmlHelpers.CreateCpixNamespaceManager(xmlDocument);

            const string recipientsId  = "id-for-recipients----";
            const string contentKeysId = "_id_for_content_keys";
            const string drmSystemsId  = "_id_for_drm_systems";
            const string usageRulesId  = "a.0a.0a.0a.0a.0a.a0.0a0.0404040......";

            UnusualInputTests.SetElementId(xmlDocument, namespaces, "/cpix:CPIX/cpix:DeliveryDataList", recipientsId);
            UnusualInputTests.SetElementId(xmlDocument, namespaces, "/cpix:CPIX/cpix:ContentKeyList", contentKeysId);
            UnusualInputTests.SetElementId(xmlDocument, namespaces, "/cpix:CPIX/cpix:DRMSystemList", drmSystemsId);
            UnusualInputTests.SetElementId(xmlDocument, namespaces, "/cpix:CPIX/cpix:ContentKeyUsageRuleList", usageRulesId);

            CryptographyHelpers.SignXmlElement(xmlDocument, recipientsId, TestHelpers.Certificate1WithPrivateKey);
            CryptographyHelpers.SignXmlElement(xmlDocument, contentKeysId, TestHelpers.Certificate1WithPrivateKey);
            CryptographyHelpers.SignXmlElement(xmlDocument, drmSystemsId, TestHelpers.Certificate1WithPrivateKey);
            CryptographyHelpers.SignXmlElement(xmlDocument, usageRulesId, TestHelpers.Certificate1WithPrivateKey);
            CryptographyHelpers.SignXmlElement(xmlDocument, usageRulesId, TestHelpers.Certificate2WithPrivateKey);
            CryptographyHelpers.SignXmlElement(xmlDocument, "", TestHelpers.Certificate1WithPrivateKey);

            // Add comments everywhere.
            namespaces = XmlHelpers.CreateCpixNamespaceManager(xmlDocument);

            UnusualInputTests.AddCommentAsChild(xmlDocument.DocumentElement);

            UnusualInputTests.AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:DeliveryDataList", namespaces));
            UnusualInputTests.AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:ContentKeyList", namespaces));
            UnusualInputTests.AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:DRMSystemList", namespaces));
            UnusualInputTests.AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:ContentKeyUsageRuleList", namespaces));

            UnusualInputTests.AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:DeliveryDataList/cpix:DeliveryData", namespaces));
            UnusualInputTests.AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:ContentKeyList/cpix:ContentKey", namespaces));
            UnusualInputTests.AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:DRMSystemList/cpix:DRMSystem", namespaces));
            UnusualInputTests.AddCommentAsChild((XmlElement)xmlDocument.SelectSingleNode("/cpix:CPIX/cpix:ContentKeyUsageRuleList/cpix:ContentKeyUsageRule", namespaces));

            // Save the signed document as UTF-16.
            using (var writer = XmlWriter.Create(outputStream, new XmlWriterSettings
            {
                Encoding = Encoding.Unicode,
                CloseOutput = false
            }))
            {
                xmlDocument.Save(writer);
            }

            // Phew. That's enough for now.
        }
        protected void Validate_Click(object sender, EventArgs e)
        {
            // If not provided, the file will be empty but not null.
            var cpixFile = Request.Files["cpixFile"];
            var pfxFile  = Request.Files["pfxFile"];

            var pfxPassword = Request.Form["pfxPassword"];

            // Form results (validation did not take place if any errors here).
            var formErrors = new List <string>();

            // Validation results.
            var errors   = new List <string>();
            var warnings = new List <string>();
            var messages = new List <string>();

            // If form errors occur, we do not show validation results.
            resultsPanel.Visible = false;

            X509Certificate2 recipientCertificate = null;

            try
            {
                if (pfxFile.ContentLength != 0)
                {
                    // If a PFX file is provided, a PFX password must be provided.
                    if (string.IsNullOrWhiteSpace("pfxPassword"))
                    {
                        formErrors.Add("You must provide the PFX password if you provide a PFX file.");
                        return;
                    }

                    byte[] pfxBytes;

                    using (var reader = new BinaryReader(pfxFile.InputStream))
                        pfxBytes = reader.ReadBytes(pfxFile.ContentLength);

                    try
                    {
                        // We use the MachineKeySet because ASP.NET AppPool default configuration tends not to load the
                        // user profile, so it cannot access the user key store and this will throw a "not found" exception.
                        recipientCertificate = new X509Certificate2(pfxBytes, pfxPassword, X509KeyStorageFlags.MachineKeySet);
                    }
                    catch (Exception ex)
                    {
                        formErrors.Add($"Unable to open the PFX file. {ex.GetType().Name}: {ex.Message}");
                        return;
                    }
                }
                else if (!string.IsNullOrWhiteSpace(pfxPassword))
                {
                    // You cannot provide only a PFX password with no PFX file - something went wrong.
                    formErrors.Add("You provided a PFX password but no PFX file.");
                    return;
                }

                // If we got this far, we got to the validation step and can show results.
                resultsPanel.Visible        = true;
                positiveResultPanel.Visible = false;
                negativeResultPanel.Visible = false;

                CpixDocument cpix;

                try
                {
                    // This can throw for a large number of reasons (most validation failures will come from here).
                    // It is slightly annoying because we just get the first error but this is also minimum-effort solution.

                    if (recipientCertificate != null)
                    {
                        cpix = CpixDocument.Load(cpixFile.InputStream, recipientCertificate);
                    }
                    else
                    {
                        cpix = CpixDocument.Load(cpixFile.InputStream);
                    }
                }
                catch (Exception ex)
                {
                    errors.Add($"{ex.GetType().Name}: {ex.Message}");
                    warnings.Add("Validation could not be completed. Fix the above error and try again to perform a full validation.");
                    return;
                }

                // If it loaded, let's do some more checks for logical consistency.

                if (cpix.ContentKeys.Any())
                {
                    if (cpix.ContentKeysAreReadable)
                    {
                        if (recipientCertificate == null)
                        {
                            warnings.Add("Content keys in the document are not encrypted. This CPIX document should not be used in production scenarios.");
                        }
                        else
                        {
                            messages.Add("Content keys in the document are encrypted. Decryption was successful.");
                        }
                    }
                    else
                    {
                        if (recipientCertificate == null)
                        {
                            // Keys are not readable and we did not have the key. This is fine but we cannot validate
                            // the key data like this, so emit a warning.
                            warnings.Add("Encrypted content keys are present in the document but cannot be validated without the recipient private key. Provide the PFX file with the recipient's key pair to validate encrypted data.");
                        }
                        else
                        {
                            // We had a certificate but still could not read the content keys? Wrong certificate!
                            errors.Add("The certificate in the PFX file was not defined as a valid recipient by the CPIX document.");
                        }
                    }
                }

                if (cpix.UsageRules.Any(r => r.ContainsUnsupportedFilters))
                {
                    messages.Add("The document includes usage rule filters that are not supported by this validator. These filters were skipped during inspection.");
                }

                // And finally, just dump some useful data.
                if (cpix.ContentKeys.Any())
                {
                    var keyIds = cpix.ContentKeys.Select(k => k.Id.ToString()).OrderBy(k => k);

                    messages.Add($"The document contains {cpix.ContentKeys.Count} content keys:{Environment.NewLine}{string.Join(Environment.NewLine, keyIds)}.");
                }
                else
                {
                    messages.Add("The document does not contain any content keys.");
                }

                if (cpix.Recipients.Any())
                {
                    var recipientNames = cpix.Recipients.Select(r => r.Certificate.Subject).OrderBy(r => r);

                    messages.Add($"The document defines {cpix.Recipients.Count} recipients:{Environment.NewLine}{string.Join(Environment.NewLine, recipientNames)}");
                }
                else
                {
                    messages.Add("The document does not define any valid recipients.");
                }

                if (cpix.UsageRules.Any())
                {
                    messages.Add($"The document defines {cpix.UsageRules.Count} usage rules.");
                }
                else
                {
                    messages.Add("The document does not define any usage rules.");
                }

                if (cpix.ContentKeys.SignedBy.Any())
                {
                    var signerNames = cpix.ContentKeys.SignedBy.Select(s => s.Subject).OrderBy(s => s);

                    messages.Add($"The set of content keys is digitally signed by:{Environment.NewLine}{string.Join(Environment.NewLine, signerNames)}");
                }
                else if (cpix.ContentKeys.Any())
                {
                    messages.Add("The set of content keys is not digitally signed.");
                }

                if (cpix.UsageRules.SignedBy.Any())
                {
                    var signerNames = cpix.UsageRules.SignedBy.Select(s => s.Subject).OrderBy(s => s);

                    messages.Add($"The set of usage rules is digitally signed by:{Environment.NewLine}{string.Join(Environment.NewLine, signerNames)}");
                }
                else if (cpix.UsageRules.Any())
                {
                    messages.Add("The set of usage rules is not digitally signed.");
                }

                if (cpix.Recipients.SignedBy.Any())
                {
                    var signerNames = cpix.Recipients.SignedBy.Select(s => s.Subject).OrderBy(s => s);

                    messages.Add($"The set of valid recipients is digitally signed by:{Environment.NewLine}{string.Join(Environment.NewLine, signerNames)}");
                }
                else if (cpix.Recipients.Any())
                {
                    messages.Add("The set of valid recipients is not digitally signed.");
                }

                if (cpix.SignedBy != null)
                {
                    messages.Add($"The document as a whole is signed by:{Environment.NewLine}{cpix.SignedBy.Subject}");
                }
                else
                {
                    messages.Add("The document as a whole is not digitally signed.");
                }
            }
            catch (Exception ex)
            {
                formErrors.Add($"Unexpected processing error. Please report this on GitHub! {ex.GetType().Name}: {ex.Message}");
            }
            finally
            {
                if (recipientCertificate != null)
                {
                    recipientCertificate.Dispose();
                }

                formErrorsList.Visible = formErrors.Any();

                if (formErrors.Any())
                {
                    formErrorsList.DataSource = formErrors;
                    formErrorsList.DataBind();
                }

                errorList.DataSource = errors;
                errorList.DataBind();

                warningList.DataSource = warnings;
                warningList.DataBind();

                messageList.DataSource = messages;
                messageList.DataBind();

                if (errors.Any())
                {
                    negativeResultPanel.Visible = true;
                }
                else
                {
                    positiveResultPanel.Visible = true;
                }
            }
        }