diff options
author | Krzysztof Wicher <mordotymoja@gmail.com> | 2017-04-05 22:48:58 +0300 |
---|---|---|
committer | Jeremy Barton <jbarton@microsoft.com> | 2017-04-05 22:48:58 +0300 |
commit | 8a61763fcc683c64d21db2dd8d62170557c4ba37 (patch) | |
tree | e434701b6f14c3b664b8a8944a24cee9684b3ce4 /src/System.Security.Cryptography.Xml | |
parent | 7ed2b62dcd1ea79f03be973d2d4349390fcef829 (diff) |
Add XmlLicenseTransform E2E test (urn:mpeg:mpeg21:2003:01-REL-R-NS:licenseTransform) (#17953)
Diffstat (limited to 'src/System.Security.Cryptography.Xml')
3 files changed, 387 insertions, 2 deletions
diff --git a/src/System.Security.Cryptography.Xml/tests/System.Security.Cryptography.Xml.Tests.csproj b/src/System.Security.Cryptography.Xml/tests/System.Security.Cryptography.Xml.Tests.csproj index eabccb5b52..488e674c5d 100644 --- a/src/System.Security.Cryptography.Xml/tests/System.Security.Cryptography.Xml.Tests.csproj +++ b/src/System.Security.Cryptography.Xml/tests/System.Security.Cryptography.Xml.Tests.csproj @@ -49,6 +49,7 @@ <Compile Include="XmlDsigExcC14NWithCommentsTransformTest.cs" /> <Compile Include="XmlDsigXPathTransformTest.cs" /> <Compile Include="XmlDsigXsltTransformTest.cs" /> + <Compile Include="XmlLicenseEncryptedRef.cs" /> <Compile Include="XmlLicenseTransformTest.cs" /> </ItemGroup> <ItemGroup> diff --git a/src/System.Security.Cryptography.Xml/tests/XmlLicenseEncryptedRef.cs b/src/System.Security.Cryptography.Xml/tests/XmlLicenseEncryptedRef.cs new file mode 100644 index 0000000000..e1e0b75df1 --- /dev/null +++ b/src/System.Security.Cryptography.Xml/tests/XmlLicenseEncryptedRef.cs @@ -0,0 +1,213 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace System.Security.Cryptography.Xml.Tests +{ + public class XmlLicenseEncryptedRef : IRelDecryptor + { + List<AsymmetricAlgorithm> _asymmetricKeys = new List<AsymmetricAlgorithm>(); + + public XmlLicenseEncryptedRef() + { + } + + public void AddAsymmetricKey(AsymmetricAlgorithm key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + + _asymmetricKeys.Add(key); + } + + private static bool PublicKeysEqual(RSAParameters a, RSAParameters b) + { + return a.Exponent.SequenceEqual(b.Exponent) && a.Modulus.SequenceEqual(b.Modulus); + } + + public Stream Decrypt(EncryptionMethod encryptionMethod, KeyInfo keyInfo, Stream toDecrypt) + { + Assert.NotNull(encryptionMethod); + Assert.NotNull(keyInfo); + Assert.NotNull(toDecrypt); + Assert.True(encryptionMethod.KeyAlgorithm == EncryptedXml.XmlEncAES128Url + || encryptionMethod.KeyAlgorithm == EncryptedXml.XmlEncAES192Url + || encryptionMethod.KeyAlgorithm == EncryptedXml.XmlEncAES256Url); + + Assert.Equal(keyInfo.Count, 1); + + byte[] decryptedKey = null; + + foreach (KeyInfoClause clause in keyInfo) + { + if (clause is KeyInfoEncryptedKey) + { + KeyInfoEncryptedKey encryptedKeyInfo = clause as KeyInfoEncryptedKey; + EncryptedKey encryptedKey = encryptedKeyInfo.EncryptedKey; + + Assert.Equal(encryptedKey.EncryptionMethod.KeyAlgorithm, EncryptedXml.XmlEncRSAOAEPUrl); + Assert.Equal(encryptedKey.KeyInfo.Count, 1); + Assert.NotEqual(_asymmetricKeys.Count, 0); + + RSAParameters rsaParams = new RSAParameters(); + RSAParameters rsaInputParams = new RSAParameters(); + + foreach (KeyInfoClause rsa in encryptedKey.KeyInfo) + { + if (rsa is RSAKeyValue) + { + rsaParams = (rsa as RSAKeyValue).Key.ExportParameters(false); + break; + } + else + { + Assert.True(false, "Invalid License - MalformedKeyInfoClause"); + } + } + + bool keyMismatch = true; + foreach (AsymmetricAlgorithm key in _asymmetricKeys) + { + RSA rsaKey = key as RSA; + Assert.NotNull(rsaKey); + + rsaInputParams = rsaKey.ExportParameters(false); + + if (!PublicKeysEqual(rsaParams, rsaInputParams)) + { + continue; + } + + keyMismatch = false; + + // Decrypt session key + byte[] encryptedKeyValue = encryptedKey.CipherData.CipherValue; + + if (encryptedKeyValue == null) + throw new CryptographicException("MissingKeyCipher"); + + decryptedKey = EncryptedXml.DecryptKey(encryptedKeyValue, + rsaKey, true); + break; + } + + if (keyMismatch) + { + throw new Exception("Invalid License - AsymmetricKeyMismatch"); + } + } + else if (clause is KeyInfoName) + { + Assert.True(false, "This test should not have KeyInfoName clauses"); + } + else + { + throw new CryptographicException("MalformedKeyInfoClause"); + } + + break; + } + + if (decryptedKey == null) + { + throw new CryptographicException("KeyDecryptionFailure"); + } + + using (Aes aes = Aes.Create()) + { + aes.Key = decryptedKey; + aes.Padding = PaddingMode.PKCS7; + aes.Mode = CipherMode.CBC; + return DecryptStream(toDecrypt, aes); + } + } + + private static Stream DecryptStream(Stream toDecrypt, SymmetricAlgorithm alg) + { + Assert.NotNull(alg); + + byte[] IV = new byte[alg.BlockSize / 8]; + + // Get the IV from the encrypted content. + toDecrypt.Read(IV, 0, IV.Length); + byte[] encryptedContentValue = new byte[toDecrypt.Length - IV.Length]; + + // Get the encrypted content following the IV. + toDecrypt.Read(encryptedContentValue, 0, encryptedContentValue.Length); + + var msDecrypt = new MemoryStream(); + + using (ICryptoTransform decryptor = alg.CreateDecryptor(alg.Key, IV)) + using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write, leaveOpen: true)) + { + csDecrypt.Write(encryptedContentValue, 0, encryptedContentValue.Length); + } + + msDecrypt.Position = 0; + return msDecrypt; + } + + public static void Encrypt(Stream toEncrypt, RSA key, out KeyInfo keyInfo, out EncryptionMethod encryptionMethod, out CipherData cipherData) + { + using (Aes sessionKey = Aes.Create()) + { + sessionKey.KeySize = 128; + encryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES128Url); + keyInfo = new KeyInfo(); + + EncryptedKey encKey; + keyInfo.AddClause( + new KeyInfoEncryptedKey( + encKey = new EncryptedKey() + { + CipherData = new CipherData(EncryptedXml.EncryptKey(sessionKey.Key, key, useOAEP: true)), + EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSAOAEPUrl) + })); + + encKey.KeyInfo.AddClause(new RSAKeyValue(key)); + + byte[] dataToEncrypt = new byte[toEncrypt.Length]; + toEncrypt.Read(dataToEncrypt, 0, (int)toEncrypt.Length); + + var encryptedXml = new EncryptedXml(); + encryptedXml.Padding = PaddingMode.PKCS7; + encryptedXml.Mode = CipherMode.CBC; + byte[] encryptedData = encryptedXml.EncryptData(dataToEncrypt, sessionKey); + cipherData = new CipherData(encryptedData); + } + } + + [Fact] + public static void ItRoundTrips() + { + byte[] input = new byte[] { 1, 2, 7, 4 }; + MemoryStream ms = new MemoryStream(input); + KeyInfo keyInfo; + EncryptionMethod encMethod; + CipherData cipherData; + using (RSA rsa = RSA.Create()) + { + Encrypt(ms, rsa, out keyInfo, out encMethod, out cipherData); + + XmlLicenseEncryptedRef decr = new XmlLicenseEncryptedRef(); + decr.AddAsymmetricKey(rsa); + using (var encrypted = new MemoryStream(cipherData.CipherValue)) + using (Stream decrypted = decr.Decrypt(encMethod, keyInfo, encrypted)) + { + byte[] decryptedBytes = new byte[decrypted.Length]; + decrypted.Read(decryptedBytes, 0, (int)decrypted.Length); + Assert.Equal(input, decryptedBytes); + } + } + } + } +} diff --git a/src/System.Security.Cryptography.Xml/tests/XmlLicenseTransformTest.cs b/src/System.Security.Cryptography.Xml/tests/XmlLicenseTransformTest.cs index e5aeed2560..9fdca09a8c 100644 --- a/src/System.Security.Cryptography.Xml/tests/XmlLicenseTransformTest.cs +++ b/src/System.Security.Cryptography.Xml/tests/XmlLicenseTransformTest.cs @@ -46,6 +46,8 @@ namespace System.Security.Cryptography.Xml.Tests public class XmlLicenseTransformTest { + public const string LicenseTransformNsUrl = "urn:mpeg:mpeg21:2003:01-REL-R-NS"; + public const string LicenseTransformUrl = LicenseTransformNsUrl + ":licenseTransform"; private UnprotectedXmlLicenseTransform transform; public XmlLicenseTransformTest() @@ -56,8 +58,7 @@ namespace System.Security.Cryptography.Xml.Tests [Fact] // ctor () public void Constructor1() { - Assert.Equal("urn:mpeg:mpeg21:2003:01-REL-R-NS:licenseTransform", - transform.Algorithm); + Assert.Equal(LicenseTransformUrl, transform.Algorithm); Assert.Null(transform.Decryptor); Type[] input = transform.InputTypes; @@ -155,6 +156,39 @@ namespace System.Security.Cryptography.Xml.Tests { Assert.Throws<ArgumentException>(() => transform.GetOutput(typeof(string))); } + + [Fact] + public static void ItDecryptsLicense() + { + using (var key = RSA.Create()) + { + string expected; + string encryptedLicenseWithGrants = GenerateLicenseXmlWithEncryptedGrants(key, out expected); + + Assert.Contains("hello", expected); + Assert.DoesNotContain("hello", encryptedLicenseWithGrants); + + XmlNamespaceManager nsManager; + XmlDocument toDecrypt = LoadXmlWithLicenseNs(encryptedLicenseWithGrants, out nsManager); + + var decryptor = new XmlLicenseEncryptedRef(); + var transform = new XmlLicenseTransform() + { + Decryptor = decryptor, + Context = FindLicenseTransformContext(toDecrypt, nsManager) + }; + + decryptor.AddAsymmetricKey(key); + + // Context is the input for this transform, argument is always ignored + transform.LoadInput(null); + + XmlDocument decryptedDoc = transform.GetOutput() as XmlDocument; + Assert.NotNull(decryptedDoc); + string decrypted = decryptedDoc.OuterXml; + Assert.Equal(expected, decrypted); + } + } private XmlDocument GetDocumentFromResource(string resourceName) { @@ -168,6 +202,143 @@ namespace System.Security.Cryptography.Xml.Tests return doc; } + + private static string GenerateLicenseXmlWithEncryptedGrants(RSA key, out string plainTextLicense) + { + plainTextLicense = @"<r:license xmlns:r=""urn:mpeg:mpeg21:2003:01-REL-R-NS""> + <r:title>Test License</r:title> + <r:grant> + <r:forAll varName=""licensor"" /> + <r:forAll varName=""property"" /> + <r:forAll varName=""p0""> + <r:propertyPossessor> + <r:propertyAbstract varRef=""property"" /> + </r:propertyPossessor> + </r:forAll> + <r:keyHolder varRef=""licensor"" /> + <r:issue /> + <r:grant> + <r:principal varRef=""p0"" /> + <x:bar xmlns:x=""urn:foo"" /> + <r:digitalResource> + <testItem>hello</testItem> + </r:digitalResource> + <renderer xmlns=""urn:mpeg:mpeg21:2003:01-REL-MX-NS""> + <mx:wildcard xmlns:mx=""urn:mpeg:mpeg21:2003:01-REL-MX-NS""> + <r:anXmlExpression>some-xpath-expression</r:anXmlExpression> + </mx:wildcard> + <mx:wildcard xmlns:mx=""urn:mpeg:mpeg21:2003:01-REL-MX-NS""> + <r:anXmlExpression>some-other-xpath-expression</r:anXmlExpression> + </mx:wildcard> + </renderer> + </r:grant> + <validityIntervalFloating xmlns=""urn:mpeg:mpeg21:2003:01-REL-SX-NS""> + <sx:duration xmlns:sx=""urn:mpeg:mpeg21:2003:01-REL-SX-NS"">P2D</sx:duration> + </validityIntervalFloating> + </r:grant> + <r:grant> + <r:possessProperty /> + <emailName xmlns=""urn:mpeg:mpeg21:2003:01-REL-SX-NS"">test@test</emailName> + </r:grant> + <r:issuer xmlns:r=""urn:mpeg:mpeg21:2003:01-REL-R-NS""> + <r:details> + <r:timeOfIssue>2099-11-11T11:11:11Z</r:timeOfIssue> + </r:details> + </r:issuer> +</r:license>".Replace("\r\n", "\n"); + + XmlNamespaceManager nsManager; + XmlDocument doc = LoadXmlWithLicenseNs(plainTextLicense, out nsManager); + + EncryptLicense(FindLicenseTransformContext(doc, nsManager), key); + + return doc.OuterXml; + } + + private static XmlElement FindLicenseTransformContext(XmlDocument doc, XmlNamespaceManager nsManager) + { + XmlNodeList issuerList = doc.SelectNodes("//r:issuer", nsManager); + return issuerList[0] as XmlElement; + } + + private static XmlDocument LoadXmlWithLicenseNs(string xml, out XmlNamespaceManager nsManager) + { + XmlDocument doc = new XmlDocument(); + doc.PreserveWhitespace = true; + nsManager = new XmlNamespaceManager(doc.NameTable); + nsManager.AddNamespace("r", LicenseTransformNsUrl); + doc.LoadXml(xml); + return doc; + } + + private static void EncryptGrant(XmlNode grant, RSA key, XmlNamespaceManager nsMgr) + { + using (var ms = new MemoryStream()) + using (var sw = new StreamWriter(ms)) + { + sw.Write(grant.InnerXml); + sw.Flush(); + ms.Position = 0; + + KeyInfo keyInfo; + EncryptionMethod encryptionMethod; + CipherData cipherData; + XmlLicenseEncryptedRef.Encrypt(ms, key, out keyInfo, out encryptionMethod, out cipherData); + grant.RemoveAll(); + XmlDocument doc = grant.OwnerDocument; + XmlElement encryptedGrant = doc.CreateElement("encryptedGrant", LicenseTransformNsUrl); + grant.AppendChild(encryptedGrant); + + encryptedGrant.AppendChild(doc.ImportNode(keyInfo.GetXml(), true)); + encryptedGrant.AppendChild(doc.ImportNode(encryptionMethod.GetXml(), true)); + encryptedGrant.AppendChild(doc.ImportNode(cipherData.GetXml(), true)); + } + } + + private static void EncryptLicense(XmlElement context, RSA key) + { + XmlDocument doc = context.OwnerDocument; + + var nsMgr = new XmlNamespaceManager(doc.NameTable); + nsMgr.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl); + nsMgr.AddNamespace("enc", EncryptedXml.XmlEncNamespaceUrl); + nsMgr.AddNamespace("r", LicenseTransformNsUrl); + + XmlElement currentIssuerContext = context.SelectSingleNode("ancestor-or-self::r:issuer[1]", nsMgr) as XmlElement; + Assert.NotEqual(currentIssuerContext, null); + + XmlElement signatureNode = currentIssuerContext.SelectSingleNode("descendant-or-self::dsig:Signature[1]", nsMgr) as XmlElement; + if (signatureNode != null) + { + signatureNode.ParentNode.RemoveChild(signatureNode); + } + + XmlElement currentLicenseContext = currentIssuerContext.SelectSingleNode("ancestor-or-self::r:license[1]", nsMgr) as XmlElement; + Assert.NotEqual(currentLicenseContext, null); + + XmlNodeList issuerList = currentLicenseContext.SelectNodes("descendant-or-self::r:license[1]/r:issuer", nsMgr); + for (int i = 0; i < issuerList.Count; i++) + { + XmlNode issuer = issuerList[i]; + if (issuer == currentIssuerContext) + { + continue; + } + + if (issuer.LocalName == "issuer" + && issuer.NamespaceURI == LicenseTransformNsUrl) + { + issuer.ParentNode.RemoveChild(issuer); + } + } + + XmlNodeList encryptedGrantList = currentLicenseContext.SelectNodes("/r:license/r:grant", nsMgr); + + for (int i = 0; i < encryptedGrantList.Count; i++) + { + EncryptGrant(encryptedGrantList[i], key, nsMgr); + } + } } } |