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); }
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 } }); }
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); }
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); }
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); }
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); }
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)); }
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); }
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); }
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); }
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."); } }
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); }
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); } }
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"); }
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! }
public void LoadDocument_WithWeakCertificate_Fails() { Assert.Throws <WeakCertificateException>(() => CpixDocument.Load(new MemoryStream(), TestHelpers.WeakSha1CertificateWithPrivateKey)); Assert.Throws <WeakCertificateException>(() => CpixDocument.Load(new MemoryStream(), TestHelpers.WeakSmallKeyCertificateWithPrivateKey)); }
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; } } }