├── .editorconfig
├── .gitattributes
├── .gitignore
├── DataProtection.sln
├── LICENSE
├── NuGet.config
├── README.md
├── build
├── Source.ruleset
├── Test.ruleset
└── stylecop.json
├── global.json
├── src
└── DataProtectionExtensions
│ ├── CertificateEncryptionOptions.cs
│ ├── CertificateXmlDecryptor.cs
│ ├── CertificateXmlEncryptor.cs
│ ├── DataProtectionBuilderExtensions.cs
│ ├── DataProtectionExtensions.csproj
│ ├── Properties
│ └── AssemblyInfo.cs
│ └── XmlExtensions.cs
└── test
└── DataProtectionExtensions.Test
├── CertificateEncryptionOptionsFixture.cs
├── CertificateXmlDecryptorFixture.cs
├── CertificateXmlEncryptorFixture.cs
├── DataProtectionBuilderExtensionsFixture.cs
├── DataProtectionExtensions.Test.csproj
├── Properties
└── AssemblyInfo.cs
├── TestCertificate.cs
└── XmlExtensionsFixture.cs
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; EditorConfig to support per-solution formatting.
2 | ; Use the EditorConfig VS add-in to make this work.
3 | ; http://editorconfig.org/
4 | ; VS Add-In: http://visualstudiogallery.msdn.microsoft.com/c8bccfe2-650c-4b42-bc5c-845e21f96328
5 | ; Sublime Text Add-In: https://github.com/sindresorhus/editorconfig-sublime#readme
6 |
7 | ; Wildcards aren't used because there's a bug...
8 | ; https://github.com/editorconfig/editorconfig-visualstudio/issues/21
9 |
10 | ; This is the default for the codeline.
11 | root = true
12 |
13 | ; default - tab
14 | [*]
15 | end_of_line = CRLF
16 | indent_style = tab
17 | trim_trailing_whitespace = true
18 |
19 | ; space / 4
20 | [*.ascx]
21 | indent_style = space
22 | indent_size = 4
23 | [*.aspx]
24 | indent_style = space
25 | indent_size = 4
26 | [*.config]
27 | indent_style = space
28 | indent_size = 4
29 | [*.cshtml]
30 | indent_style = space
31 | indent_size = 4
32 | [*.html]
33 | indent_style = space
34 | indent_size = 4
35 | [*.proj]
36 | indent_style = space
37 | indent_size = 4
38 | [*.targets]
39 | indent_style = space
40 | indent_size = 4
41 | [*.wxi]
42 | indent_style = space
43 | indent_size = 4
44 | [*.wxl]
45 | indent_style = space
46 | indent_size = 4
47 | [*.wxs]
48 | indent_style = space
49 | indent_size = 4
50 | [*.xml]
51 | indent_style = space
52 | indent_size = 4
53 |
54 | ; space / 2
55 | [*.csproj]
56 | indent_style = space
57 | indent_size = 2
58 | [*.json]
59 | indent_style = space
60 | indent_size = 2
61 | [*.kproj]
62 | indent_style = space
63 | indent_size = 2
64 | [*.resx]
65 | indent_style = space
66 | indent_size = 2
67 | [*.rst]
68 | indent_style = space
69 | indent_size = 2
70 | [*.xproj]
71 | indent_style = space
72 | indent_size = 2
73 | [NuGet.Config]
74 | indent_style = space
75 | indent_size = 2
76 | [packages.config]
77 | indent_style = space
78 | indent_size = 2
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.doc diff=astextplain
2 | *.DOC diff=astextplain
3 | *.docx diff=astextplain
4 | *.DOCX diff=astextplain
5 | *.dot diff=astextplain
6 | *.DOT diff=astextplain
7 | *.pdf diff=astextplain
8 | *.PDF diff=astextplain
9 | *.rtf diff=astextplain
10 | *.RTF diff=astextplain
11 |
12 | *.jpg binary
13 | *.png binary
14 | *.gif binary
15 |
16 | *.cs text=auto diff=csharp
17 | *.vb text=auto
18 | *.resx text=auto
19 | *.c text=auto
20 | *.cpp text=auto
21 | *.cxx text=auto
22 | *.h text=auto
23 | *.hxx text=auto
24 | *.py text=auto
25 | *.rb text=auto
26 | *.java text=auto
27 | *.html text=auto
28 | *.htm text=auto
29 | *.css text=auto
30 | *.scss text=auto
31 | *.sass text=auto
32 | *.less text=auto
33 | *.js text=auto
34 | *.lisp text=auto
35 | *.clj text=auto
36 | *.sql text=auto
37 | *.php text=auto
38 | *.lua text=auto
39 | *.m text=auto
40 | *.asm text=auto
41 | *.erl text=auto
42 | *.fs text=auto
43 | *.fsx text=auto
44 | *.hs text=auto
45 |
46 | *.csproj text=auto
47 | *.vbproj text=auto
48 | *.fsproj text=auto
49 | *.dbproj text=auto
50 | *.sln text=auto eol=crlf
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Project specific files
5 | artifacts/
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.sln.docstates
11 | *.ide
12 | Index.dat
13 | Storage.dat
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Rr]elease/
18 | x64/
19 | [Bb]in/
20 | [Oo]bj/
21 |
22 | # Visual Studio 2015 cache/options directory
23 | .vs/
24 | .dotnet/
25 |
26 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
27 | !packages/*/build/
28 |
29 | # MSTest test Results
30 | [Tt]est[Rr]esult*/
31 | [Bb]uild[Ll]og.*
32 | *.TestResults.xml
33 |
34 | *_i.c
35 | *_p.c
36 | *.ilk
37 | *.meta
38 | *.obj
39 | *.pch
40 | *.pdb
41 | *.pgc
42 | *.pgd
43 | *.rsp
44 | *.sbr
45 | *.tlb
46 | *.tli
47 | *.tlh
48 | *.tmp
49 | *.tmp_proj
50 | *.log
51 | *.vspscc
52 | *.vssscc
53 | .builds
54 | *.pidb
55 | *.log
56 | *.scc
57 |
58 | # Visual C++ cache files
59 | ipch/
60 | *.aps
61 | *.ncb
62 | *.opensdf
63 | *.sdf
64 | *.cachefile
65 |
66 | # Visual Studio profiler
67 | *.psess
68 | *.vsp
69 | *.vspx
70 |
71 | # Guidance Automation Toolkit
72 | *.gpState
73 |
74 | # ReSharper is a .NET coding add-in
75 | _ReSharper*/
76 | *.[Rr]e[Ss]harper
77 |
78 | # TeamCity is a build add-in
79 | _TeamCity*
80 |
81 | # DotCover is a Code Coverage Tool
82 | *.dotCover
83 |
84 | # NCrunch
85 | *.ncrunch*
86 | .*crunch*.local.xml
87 |
88 | # Installshield output folder
89 | [Ee]xpress/
90 |
91 | # DocProject is a documentation generator add-in
92 | DocProject/buildhelp/
93 | DocProject/Help/*.HxT
94 | DocProject/Help/*.HxC
95 | DocProject/Help/*.hhc
96 | DocProject/Help/*.hhk
97 | DocProject/Help/*.hhp
98 | DocProject/Help/Html2
99 | DocProject/Help/html
100 |
101 | # Click-Once directory
102 | publish/
103 |
104 | # Publish Web Output
105 | *.Publish.xml
106 | *.pubxml
107 |
108 | # NuGet Packages Directory
109 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
110 | packages/
111 |
112 | # Core 1.0 wwwroot files
113 | /**/wwwroot/lib/
114 | /**/wwwroot/js/*.min.*
115 | /**/wwwroot/css/*.min.*
116 |
117 | # Windows Azure Build Output
118 | csx
119 | *.build.csdef
120 |
121 | # Windows Store app package directory
122 | AppPackages/
123 |
124 | # Others
125 | sql/
126 | *.Cache
127 | ClientBin/
128 | [Ss]tyle[Cc]op.*
129 | !stylecop.json
130 | ~$*
131 | *~
132 | *.dbmdl
133 | *.[Pp]ublish.xml
134 | *.publishsettings
135 | node_modules/
136 | bower_components/
137 | project.lock.json
138 |
139 | # RIA/Silverlight projects
140 | Generated_Code/
141 |
142 | # Backup & report files from converting an old project file to a newer
143 | # Visual Studio version. Backup files are not needed, because we have git ;-)
144 | _UpgradeReport_Files/
145 | Backup*/
146 | UpgradeLog*.XML
147 | UpgradeLog*.htm
148 |
149 | # SQL Server files
150 | App_Data/*.mdf
151 | App_Data/*.ldf
152 |
153 | # =========================
154 | # Windows detritus
155 | # =========================
156 |
157 | # Windows image file caches
158 | Thumbs.db
159 | ehthumbs.db
160 |
161 | # Folder config file
162 | Desktop.ini
163 |
164 | # Recycle Bin used on file shares
165 | $RECYCLE.BIN/
166 |
167 | # Mac crap
168 | .DS_Store
169 | TestResults.xml
170 |
--------------------------------------------------------------------------------
/DataProtection.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2027
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataProtectionExtensions", "src\DataProtectionExtensions\DataProtectionExtensions.csproj", "{2EBCD6F3-401D-4588-83B2-A202EA97D610}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataProtectionExtensions.Test", "test\DataProtectionExtensions.Test\DataProtectionExtensions.Test.csproj", "{23FF9E5A-C681-45FC-92CC-E892C7B7EFD0}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{21C9FD54-9CD8-4DE8-A8FA-F00303FF5344}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2CDC5141-323F-4379-A866-58D910FE0DE9}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {2EBCD6F3-401D-4588-83B2-A202EA97D610}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {2EBCD6F3-401D-4588-83B2-A202EA97D610}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {2EBCD6F3-401D-4588-83B2-A202EA97D610}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {2EBCD6F3-401D-4588-83B2-A202EA97D610}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {23FF9E5A-C681-45FC-92CC-E892C7B7EFD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {23FF9E5A-C681-45FC-92CC-E892C7B7EFD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {23FF9E5A-C681-45FC-92CC-E892C7B7EFD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {23FF9E5A-C681-45FC-92CC-E892C7B7EFD0}.Release|Any CPU.Build.0 = Release|Any CPU
28 | EndGlobalSection
29 | GlobalSection(SolutionProperties) = preSolution
30 | HideSolutionNode = FALSE
31 | EndGlobalSection
32 | GlobalSection(NestedProjects) = preSolution
33 | {2EBCD6F3-401D-4588-83B2-A202EA97D610} = {21C9FD54-9CD8-4DE8-A8FA-F00303FF5344}
34 | {23FF9E5A-C681-45FC-92CC-E892C7B7EFD0} = {2CDC5141-323F-4379-A866-58D910FE0DE9}
35 | EndGlobalSection
36 | GlobalSection(ExtensibilityGlobals) = postSolution
37 | SolutionGuid = {06EA5576-CCA2-4423-851F-48F701DFDE35}
38 | EndGlobalSection
39 | EndGlobal
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Travis Illig
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DataProtection
2 | Additional utilities and support for working with ASP.NET Core DataProtection.
3 |
4 | This repo is provided as an example for code that can help get ASP.NET Core data protection working in a non-Azure farm environment.
5 |
6 | Included:
7 |
8 | - **XML encryption/decryption using a certificate that isn't required to be in a machine certificate store.** This allows you to store the master certificate in a repository like Azure Key Vault. The default XML encryption via certificates currently requires the certificate be in a machine certificate store for decryption.
9 | - **Encrypted XML storage in Redis.** Well... this _was_ included until it was [finally added in the official packages](https://github.com/aspnet/DataProtection/blob/rel/1.1.0/src/Microsoft.AspNetCore.DataProtection.Redis/RedisXmlRepository.cs).
--------------------------------------------------------------------------------
/build/Source.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/build/Test.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/build/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "indentation": {
5 | "useTabs": true
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/src/DataProtectionExtensions/CertificateEncryptionOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Security.Cryptography.X509Certificates;
4 |
5 | namespace DataProtectionExtensions
6 | {
7 | ///
8 | /// Options used in certificate-based key encryption for data protection.
9 | ///
10 | ///
11 | ///
12 | /// This is valuable so you can register a certificate for use with
13 | /// the and
14 | /// without registering an directly in the
15 | /// DI container. That way if other functions in the system also need certificates
16 | /// there won't be any need to disambiguate.
17 | ///
18 | ///
19 | public class CertificateEncryptionOptions
20 | {
21 | ///
22 | /// Initializes a new instance of the class.
23 | ///
24 | ///
25 | /// The that contains the asymmetric key pair
26 | /// used to encrypt and decrypt the rotating data protection keys.
27 | ///
28 | ///
29 | /// Thrown if is .
30 | ///
31 | public CertificateEncryptionOptions(X509Certificate2 certificate)
32 | {
33 | if (certificate == null)
34 | {
35 | throw new ArgumentNullException(nameof(certificate));
36 | }
37 |
38 | this.Certificate = certificate;
39 | }
40 |
41 | ///
42 | /// Gets the encryption certificate.
43 | ///
44 | ///
45 | /// The that contains the asymmetric key pair
46 | /// used to encrypt and decrypt the rotating data protection keys.
47 | ///
48 | public X509Certificate2 Certificate { get; private set; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/DataProtectionExtensions/CertificateXmlDecryptor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 | using System.Linq;
4 | using System.Security.Cryptography;
5 | using System.Security.Cryptography.X509Certificates;
6 | using System.Security.Cryptography.Xml;
7 | using System.Xml.Linq;
8 | using Microsoft.AspNetCore.DataProtection.XmlEncryption;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.Extensions.Logging;
11 |
12 | namespace DataProtectionExtensions
13 | {
14 | ///
15 | /// XML decryptor that uses a provided certificate private key
16 | /// to handle decryption operations.
17 | ///
18 | ///
19 | [SuppressMessage("CA2213", "CA2213", Justification = "The RSA key provider is disposed with the certificate.")]
20 | public class CertificateXmlDecryptor : IXmlDecryptor
21 | {
22 | ///
23 | /// The name of the key used to decrypt the XML. This is the
24 | /// certificate thumbprint. If this doesn't match the name of
25 | /// the key used to encrypt the XML, the decryption will fail.
26 | ///
27 | private readonly string _keyName;
28 |
29 | ///
30 | /// The key provider containing the private key of the certificate
31 | /// used to decrypt data.
32 | ///
33 | private readonly RSA _keyProvider;
34 |
35 | ///
36 | /// Initializes a new instance of the class.
37 | ///
38 | ///
39 | /// The that will be used to locate
40 | /// required services.
41 | ///
42 | ///
43 | /// Thrown if the does not have an
44 | /// for this class; or if there is no
45 | /// registered.
46 | ///
47 | public CertificateXmlDecryptor(IServiceProvider serviceProvider)
48 | {
49 | // You have to use service location like this because
50 | // there are a lot of bugs and shortcomings in the way
51 | // DI is integrated into the data protection mechanism.
52 | // They don't support constructor injection.
53 | // https://github.com/aspnet/DataProtection/issues/134
54 | // https://github.com/aspnet/DataProtection/issues/154
55 | this.Logger = serviceProvider.GetRequiredService>();
56 | var options = serviceProvider.GetRequiredService();
57 |
58 | this._keyProvider = options.Certificate.GetRSAPrivateKey();
59 | this._keyName = options.Certificate.Thumbprint;
60 | }
61 |
62 | ///
63 | /// Gets the logger.
64 | ///
65 | ///
66 | /// An used to log diagnostic messages.
67 | ///
68 | public ILogger Logger { get; private set; }
69 |
70 | ///
71 | /// Decrypts the specified XML element.
72 | ///
73 | ///
74 | /// An encrypted XML element.
75 | ///
76 | ///
77 | /// The decrypted form of .
78 | ///
79 | ///
80 | /// Thrown if is .
81 | ///
82 | public XElement Decrypt(XElement encryptedElement)
83 | {
84 | if (encryptedElement == null)
85 | {
86 | throw new ArgumentNullException(nameof(encryptedElement));
87 | }
88 |
89 | this.Logger.LogDebug("Decrypting XML with certificate {0}.", this._keyName);
90 |
91 | // Create a faux XML document from the XElement so we can use EncryptedXml.
92 | var xmlDocument = encryptedElement.ToXmlDocumentWithRootNode();
93 |
94 | // Do the actual decryption. Algorithm based on MSDN docs:
95 | // https://msdn.microsoft.com/en-us/library/ms229746(v=vs.110).aspx
96 | var encryptedXml = new EncryptedXml(xmlDocument);
97 | encryptedXml.AddKeyNameMapping(this._keyName, this._keyProvider);
98 |
99 | try
100 | {
101 | encryptedXml.DecryptDocument();
102 | }
103 | catch (CryptographicException ex) when (ex.Message.IndexOf("bad key", StringComparison.OrdinalIgnoreCase) >= 0)
104 | {
105 | // If you get a CryptographicException with the message "Bad Key"
106 | // in it here, it means the certificate used to encrypt wasn't generated
107 | // with "-sky Exchange" in makecert.exe so the encrypt/decrypt functionality
108 | // isn't enabled for it.
109 | this.Logger.LogError("Bad key exception was encountered. Did you generate the certificate with '-sky Exchange' to enable encryption/decryption?");
110 | throw;
111 | }
112 |
113 | return xmlDocument.ElementToProcess().ToXElement();
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/DataProtectionExtensions/CertificateXmlEncryptor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.CodeAnalysis;
3 | using System.Linq;
4 | using System.Security.Cryptography;
5 | using System.Security.Cryptography.X509Certificates;
6 | using System.Security.Cryptography.Xml;
7 | using System.Xml.Linq;
8 | using Microsoft.AspNetCore.DataProtection.XmlEncryption;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.Extensions.Logging;
11 |
12 | namespace DataProtectionExtensions
13 | {
14 | ///
15 | /// XML encryptor that uses a provided certificate public key
16 | /// to handle encryption operations.
17 | ///
18 | ///
19 | [SuppressMessage("CA2213", "CA2213", Justification = "The RSA key provider is disposed with the certificate.")]
20 | public class CertificateXmlEncryptor : IXmlEncryptor, IDisposable
21 | {
22 | ///
23 | /// The ID of the element that contains the encrypted data. Used to tie
24 | /// the element to decryption information.
25 | ///
26 | private const string EncryptedElementId = "certificateEncryptedKey";
27 |
28 | ///
29 | /// The length of the session key used to encrypt data. Given the session
30 | /// key itself is encrypted, this doesn't have to be quite as large
31 | /// as the master key.
32 | ///
33 | private const int SessionKeySize = 256;
34 |
35 | ///
36 | /// The name of the key used to encrypt the XML. This is the
37 | /// certificate thumbprint. If this doesn't match the name of
38 | /// the key used to decrypt the XML, the decryption will fail.
39 | ///
40 | private readonly string _keyName;
41 |
42 | ///
43 | /// The key provider containing the public key of the certificate
44 | /// used to encrypt data.
45 | ///
46 | private readonly RSA _keyProvider;
47 |
48 | ///
49 | /// The session key used to encrypt data. The master certificate key
50 | /// is used to encrypt the session key; the session key is used to
51 | /// encrypt the data.
52 | ///
53 | private readonly RijndaelManaged _sessionKey;
54 |
55 | ///
56 | /// Flag indicating whether the object has been disposed.
57 | ///
58 | private bool _disposed = false;
59 |
60 | ///
61 | /// Initializes a new instance of the class.
62 | ///
63 | ///
64 | /// The that will be used to locate
65 | /// required services.
66 | ///
67 | ///
68 | /// Thrown if the does not have an
69 | /// for this class; or if there is no
70 | /// registered.
71 | ///
72 | public CertificateXmlEncryptor(IServiceProvider serviceProvider)
73 | {
74 | // You have to use service location like this because
75 | // there are a lot of bugs and shortcomings in the way
76 | // DI is integrated into the data protection mechanism.
77 | // They don't support constructor injection.
78 | // https://github.com/aspnet/Home/issues/2523
79 | this.Logger = serviceProvider.GetRequiredService>();
80 | var options = serviceProvider.GetRequiredService();
81 |
82 | // Get the data we need out of the certificate and we don't
83 | // have to keep a reference to the whole thing.
84 | this._keyProvider = options.Certificate.GetRSAPublicKey();
85 | this._keyName = options.Certificate.Thumbprint;
86 |
87 | // Create a session key to encrypt the data. This will be included
88 | // and encrypted inside the overall package.
89 | this._sessionKey = new RijndaelManaged
90 | {
91 | KeySize = SessionKeySize,
92 | };
93 | }
94 |
95 | ///
96 | /// Gets the logger.
97 | ///
98 | ///
99 | /// An used to log diagnostic messages.
100 | ///
101 | public ILogger Logger { get; private set; }
102 |
103 | ///
104 | /// Performs application-defined tasks associated with freeing, releasing,
105 | /// or resetting unmanaged resources.
106 | ///
107 | public void Dispose()
108 | {
109 | this.Dispose(true);
110 | GC.SuppressFinalize(this);
111 | }
112 |
113 | ///
114 | /// Encrypts the specified XML element.
115 | ///
116 | /// The plaintext to encrypt.
117 | ///
118 | /// An that contains the encrypted value of
119 | /// along with information about how to
120 | /// decrypt it.
121 | ///
122 | ///
123 | /// Thrown if is .
124 | ///
125 | ///
126 | /// Thrown if this method is called after is called.
127 | ///
128 | public EncryptedXmlInfo Encrypt(XElement plaintextElement)
129 | {
130 | if (plaintextElement == null)
131 | {
132 | throw new ArgumentNullException(nameof(plaintextElement));
133 | }
134 |
135 | if (this._disposed)
136 | {
137 | throw new ObjectDisposedException("Unable to encrypt after the object has been disposed.");
138 | }
139 |
140 | this.Logger.LogDebug("Encrypting XML with certificate {0}.", this._keyName);
141 |
142 | // Create a faux XML document from the XElement so we can use EncryptedXml.
143 | var xmlDocument = plaintextElement.ToXmlDocumentWithRootNode();
144 | var elementToEncrypt = xmlDocument.ElementToProcess();
145 |
146 | // Do the actual encryption. Algorithm based on MSDN docs:
147 | // https://msdn.microsoft.com/en-us/library/ms229746(v=vs.110).aspx
148 | var encryptedXml = new EncryptedXml();
149 | var encryptedElement = encryptedXml.EncryptData(elementToEncrypt, this._sessionKey, false);
150 |
151 | // Build the wrapper elements that provide information about
152 | // the algorithms used, the name of the key used, and so on.
153 | var encryptedData = new EncryptedData
154 | {
155 | Type = EncryptedXml.XmlEncElementUrl,
156 | Id = EncryptedElementId,
157 | EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url),
158 | };
159 |
160 | var encryptedKey = new EncryptedKey
161 | {
162 | CipherData = new CipherData(EncryptedXml.EncryptKey(this._sessionKey.Key, this._keyProvider, false)),
163 | EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncRSA15Url),
164 | };
165 |
166 | // "Connect" the encrypted data and encrypted key with
167 | // element references.
168 | var encryptedElementDataReference = new DataReference
169 | {
170 | Uri = "#" + EncryptedElementId,
171 | };
172 | encryptedKey.AddReference(encryptedElementDataReference);
173 | encryptedData.KeyInfo.AddClause(new KeyInfoEncryptedKey(encryptedKey));
174 |
175 | var keyName = new KeyInfoName
176 | {
177 | Value = this._keyName,
178 | };
179 | encryptedKey.KeyInfo.AddClause(keyName);
180 |
181 | encryptedData.CipherData.CipherValue = encryptedElement;
182 |
183 | // Swap the plaintext element for the encrypted element.
184 | EncryptedXml.ReplaceElement(elementToEncrypt, encryptedData, false);
185 |
186 | return new EncryptedXmlInfo(xmlDocument.ElementToProcess().ToXElement(), typeof(CertificateXmlDecryptor));
187 | }
188 |
189 | ///
190 | /// Releases unmanaged and - optionally - managed resources.
191 | ///
192 | ///
193 | /// to release both managed and unmanaged resources;
194 | /// to release only unmanaged resources.
195 | ///
196 | protected virtual void Dispose(bool disposing)
197 | {
198 | if (!this._disposed)
199 | {
200 | if (disposing)
201 | {
202 | this._sessionKey.Dispose();
203 | }
204 |
205 | this._disposed = true;
206 | }
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/DataProtectionExtensions/DataProtectionBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Security.Cryptography.X509Certificates;
4 | using Microsoft.AspNetCore.DataProtection;
5 | using Microsoft.AspNetCore.DataProtection.KeyManagement;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Microsoft.Extensions.Options;
8 |
9 | namespace DataProtectionExtensions
10 | {
11 | ///
12 | /// Extension methods for for configuring
13 | /// data protection options.
14 | ///
15 | public static class DataProtectionBuilderExtensions
16 | {
17 | ///
18 | /// Sets up data protection to protect session keys with a provided certificate.
19 | ///
20 | /// The used to set up data protection options.
21 | /// The certificate to use for session key encryption.
22 | ///
23 | /// The for continued configuration.
24 | ///
25 | ///
26 | /// Thrown if or is .
27 | ///
28 | ///
29 | ///
30 | /// The standard certificate encryption allows you to pass in a certificate for
31 | /// encryption, but during decryption requires the certificate to be in the
32 | /// machine certificate store. This version uses only the certificate provided
33 | /// and does not look at the certificate store.
34 | ///
35 | ///
36 | public static IDataProtectionBuilder ProtectKeysWithProvidedCertificate(this IDataProtectionBuilder builder, X509Certificate2 certificate)
37 | {
38 | if (builder == null)
39 | {
40 | throw new ArgumentNullException(nameof(builder));
41 | }
42 |
43 | if (certificate == null)
44 | {
45 | throw new ArgumentNullException(nameof(certificate));
46 | }
47 |
48 | builder.Services
49 | .AddSingleton()
50 | .AddSingleton(new CertificateEncryptionOptions(certificate))
51 | .AddSingleton>(provider =>
52 | {
53 | return new ConfigureOptions(opts =>
54 | {
55 | opts.XmlEncryptor = provider.GetRequiredService();
56 | });
57 | });
58 |
59 | return builder;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/DataProtectionExtensions/DataProtectionExtensions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | 0.1.0
4 | netstandard2.0
5 | Travis Illig
6 |
7 | DataProtection Extensions
8 | ASP.NET Core extensions and utilities for working with DataProtection in a farm.
9 | https://github.com/tillig/DataProtection.git
10 | git
11 | ../../build/Source.ruleset
12 | IOperation
13 | $(DefineConstants);CODE_ANALYSIS
14 | true
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | All
23 |
24 |
25 | All
26 |
27 |
28 |
29 |
30 | All
31 |
32 |
33 |
34 | All
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/DataProtectionExtensions/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: ComVisible(false)]
5 | [assembly: Guid("b5736a04-af6c-4b4b-8dd0-7fb01852a5e6")]
6 |
--------------------------------------------------------------------------------
/src/DataProtectionExtensions/XmlExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Security.Cryptography.Xml;
4 | using System.Xml;
5 | using System.Xml.Linq;
6 |
7 | namespace DataProtectionExtensions
8 | {
9 | ///
10 | /// Extension methods used for working with XML in the context
11 | /// of data protection operations.
12 | ///
13 | ///
14 | /// In working with , we need to convert nodes back and forth
15 | /// between and . These extensions help in that conversion.
16 | ///
17 | public static class XmlExtensions
18 | {
19 | ///
20 | /// Gets the that should be processed
21 | /// by after conversion from
22 | /// to .
23 | ///
24 | ///
25 | /// The document converted by .
26 | ///
27 | ///
28 | /// The corresponding to the
29 | /// that was originally converted.
30 | ///
31 | ///
32 | /// Thrown if is .
33 | ///
34 | public static XmlElement ElementToProcess(this XmlDocument document)
35 | {
36 | if (document == null)
37 | {
38 | throw new ArgumentNullException(nameof(document));
39 | }
40 |
41 | return (XmlElement)document.DocumentElement.FirstChild;
42 | }
43 |
44 | ///
45 | /// Takes an and converts it to an .
46 | ///
47 | ///
48 | /// The node to convert.
49 | ///
50 | ///
51 | /// An with the
52 | /// XML contents.
53 | ///
54 | ///
55 | /// Thrown if is .
56 | ///
57 | public static XElement ToXElement(this XmlNode node)
58 | {
59 | if (node == null)
60 | {
61 | throw new ArgumentNullException(nameof(node));
62 | }
63 |
64 | return XElement.Load(node.CreateNavigator().ReadSubtree());
65 | }
66 |
67 | ///
68 | /// Converts an to an
69 | /// that can be processed by .
70 | ///
71 | ///
72 | /// The to encrypt using .
73 | ///
74 | ///
75 | /// An that can be used along with
76 | /// to encrypt the
77 | /// with .
78 | ///
79 | ///
80 | /// Thrown if is .
81 | ///
82 | public static XmlDocument ToXmlDocumentWithRootNode(this XElement element)
83 | {
84 | if (element == null)
85 | {
86 | throw new ArgumentNullException(nameof(element));
87 | }
88 |
89 | // EncryptedXml needs an XmlDocument so we create a dummy doc with a
90 | // element.
91 | var xmlDocument = new XmlDocument() { XmlResolver = null };
92 | xmlDocument.Load(new XElement("root", element).CreateReader());
93 | return xmlDocument;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/test/DataProtectionExtensions.Test/CertificateEncryptionOptionsFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Security.Cryptography.X509Certificates;
4 | using DataProtectionExtensions;
5 | using Xunit;
6 |
7 | namespace DataProtectionExtensions.Test
8 | {
9 | public class CertificateEncryptionOptionsFixture
10 | {
11 | [Fact]
12 | public void Ctor_NullCertificate()
13 | {
14 | Assert.Throws(() => new CertificateEncryptionOptions(null));
15 | }
16 |
17 | [Fact]
18 | public void Ctor_SetsCertificate()
19 | {
20 | var cert = new X509Certificate2();
21 | var options = new CertificateEncryptionOptions(cert);
22 | Assert.Same(cert, options.Certificate);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/test/DataProtectionExtensions.Test/CertificateXmlDecryptorFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Xml.Linq;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Logging;
6 | using Moq;
7 | using Xunit;
8 |
9 | namespace DataProtectionExtensions.Test
10 | {
11 | public class CertificateXmlDecryptorFixture
12 | {
13 | [Fact]
14 | public void CertificateXmlDecryptor_Integration()
15 | {
16 | // This verifies the round-trip through encryption/decrpytion
17 | // using a provided certificate.
18 | var toEncrypt = XElement.Parse("value");
19 | var encryptor = CreateEncryptor();
20 | var encrypted = encryptor.Encrypt(toEncrypt);
21 | var decryptor = CreateDecryptor();
22 | var decrypted = decryptor.Decrypt(encrypted.EncryptedElement);
23 | Assert.Equal("value", decrypted.ToString(SaveOptions.DisableFormatting));
24 | }
25 |
26 | [Fact]
27 | public void Ctor_MissingLogger()
28 | {
29 | var services = new ServiceCollection();
30 | var options = new CertificateEncryptionOptions(TestCertificate.GetCertificate());
31 | services.AddSingleton(options);
32 | var provider = services.BuildServiceProvider();
33 | Assert.Throws(() => new CertificateXmlDecryptor(provider));
34 | }
35 |
36 | [Fact]
37 | public void Ctor_MissingOptions()
38 | {
39 | var services = new ServiceCollection();
40 | var logger = Mock.Of>();
41 | services.AddSingleton>(logger);
42 | var provider = services.BuildServiceProvider();
43 | Assert.Throws(() => new CertificateXmlDecryptor(provider));
44 | }
45 |
46 | [Fact]
47 | public void Decrypt_NullElement()
48 | {
49 | var decryptor = CreateDecryptor();
50 | Assert.Throws(() => decryptor.Decrypt(null));
51 | }
52 |
53 | private static CertificateXmlDecryptor CreateDecryptor()
54 | {
55 | var logger = Mock.Of>();
56 | var options = new CertificateEncryptionOptions(TestCertificate.GetCertificate());
57 | var services = new ServiceCollection();
58 | services.AddSingleton>(logger);
59 | services.AddSingleton(options);
60 | var provider = services.BuildServiceProvider();
61 | return new CertificateXmlDecryptor(provider);
62 | }
63 |
64 | private static CertificateXmlEncryptor CreateEncryptor()
65 | {
66 | var logger = Mock.Of>();
67 | var options = new CertificateEncryptionOptions(TestCertificate.GetCertificate());
68 | var services = new ServiceCollection();
69 | services.AddSingleton>(logger);
70 | services.AddSingleton(options);
71 | var provider = services.BuildServiceProvider();
72 | return new CertificateXmlEncryptor(provider);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/test/DataProtectionExtensions.Test/CertificateXmlEncryptorFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Xml.Linq;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Logging;
6 | using Moq;
7 | using Xunit;
8 |
9 | namespace DataProtectionExtensions.Test
10 | {
11 | public class CertificateXmlEncryptorFixture
12 | {
13 | [Fact]
14 | public void Ctor_MissingLogger()
15 | {
16 | var services = new ServiceCollection();
17 | var options = new CertificateEncryptionOptions(TestCertificate.GetCertificate());
18 | services.AddSingleton(options);
19 | var provider = services.BuildServiceProvider();
20 | Assert.Throws(() => new CertificateXmlEncryptor(provider));
21 | }
22 |
23 | [Fact]
24 | public void Ctor_MissingOptions()
25 | {
26 | var services = new ServiceCollection();
27 | var logger = Mock.Of>();
28 | services.AddSingleton>(logger);
29 | var provider = services.BuildServiceProvider();
30 | Assert.Throws(() => new CertificateXmlEncryptor(provider));
31 | }
32 |
33 | [Fact]
34 | public void Dispose_MultipleCallsSucceed()
35 | {
36 | var encryptor = CreateEncryptor();
37 | encryptor.Dispose();
38 | encryptor.Dispose();
39 | }
40 |
41 | [Fact]
42 | public void Encrypt_DecryptorType()
43 | {
44 | var toEncrypt = XElement.Parse("value");
45 | var encryptor = CreateEncryptor();
46 | var encrypted = encryptor.Encrypt(toEncrypt);
47 | Assert.Equal(typeof(CertificateXmlDecryptor), encrypted.DecryptorType);
48 | }
49 |
50 | [Fact]
51 | public void Encrypt_FailsAfterDispose()
52 | {
53 | var toEncrypt = XElement.Parse("value");
54 | var encryptor = CreateEncryptor();
55 | encryptor.Dispose();
56 | Assert.Throws(() => encryptor.Encrypt(toEncrypt));
57 | }
58 |
59 | [Fact]
60 | public void Encrypt_NullElement()
61 | {
62 | var encryptor = CreateEncryptor();
63 | Assert.Throws(() => encryptor.Encrypt(null));
64 | }
65 |
66 | private static CertificateXmlEncryptor CreateEncryptor()
67 | {
68 | var logger = Mock.Of>();
69 | var options = new CertificateEncryptionOptions(TestCertificate.GetCertificate());
70 | var services = new ServiceCollection();
71 | services.AddSingleton>(logger);
72 | services.AddSingleton(options);
73 | var provider = services.BuildServiceProvider();
74 | return new CertificateXmlEncryptor(provider);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/test/DataProtectionExtensions.Test/DataProtectionBuilderExtensionsFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using Microsoft.AspNetCore.DataProtection.Internal;
4 | using Microsoft.AspNetCore.DataProtection.KeyManagement;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Options;
7 | using Xunit;
8 |
9 | namespace DataProtectionExtensions.Test
10 | {
11 | public class DataProtectionBuilderExtensionsFixture
12 | {
13 | [Fact]
14 | public void ProtectKeysWithProvidedCertificate_NullBuilder()
15 | {
16 | Assert.Throws(() => DataProtectionBuilderExtensions.ProtectKeysWithProvidedCertificate(null, TestCertificate.GetCertificate()));
17 | }
18 |
19 | [Fact]
20 | public void ProtectKeysWithProvidedCertificate_NullCertificate()
21 | {
22 | var builder = new DataProtectionBuilder(new ServiceCollection());
23 | Assert.Throws(() => DataProtectionBuilderExtensions.ProtectKeysWithProvidedCertificate(builder, null));
24 | }
25 |
26 | [Fact]
27 | public void ProtectKeysWithProvidedCertificate_PreparesDecryptor()
28 | {
29 | var certificate = TestCertificate.GetCertificate();
30 | var services = new ServiceCollection();
31 | services
32 | .AddLogging()
33 | .AddDataProtection()
34 | .ProtectKeysWithProvidedCertificate(certificate);
35 |
36 | var provider = services.BuildServiceProvider();
37 |
38 | // Shouldn't throw.
39 | var decryptor = new CertificateXmlDecryptor(provider);
40 | }
41 |
42 | [Fact]
43 | public void ProtectKeysWithProvidedCertificate_SetsOptions()
44 | {
45 | var certificate = TestCertificate.GetCertificate();
46 | var services = new ServiceCollection();
47 | services
48 | .AddLogging()
49 | .AddDataProtection()
50 | .ProtectKeysWithProvidedCertificate(certificate);
51 |
52 | var provider = services.BuildServiceProvider();
53 | var options = provider.GetRequiredService>();
54 | Assert.IsType(options.Value.XmlEncryptor);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/test/DataProtectionExtensions.Test/DataProtectionExtensions.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | 0.1.0
4 | netcoreapp2.0
5 | Travis Illig
6 |
7 | DataProtection Extensions
8 | Unit tests for the ASP.NET DataProtection example extensions.
9 | https://github.com/tillig/DataProtection.git
10 | git
11 | ../../build/Test.ruleset
12 | IOperation
13 | $(DefineConstants);CODE_ANALYSIS
14 | $(NoWarn),1573,1591
15 | true
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | All
28 |
29 |
30 | All
31 |
32 |
33 | All
34 |
35 |
36 | All
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/test/DataProtectionExtensions.Test/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: ComVisible(false)]
5 | [assembly: Guid("f1eeca92-05b7-46d6-9f95-cca45e925b60")]
6 |
--------------------------------------------------------------------------------
/test/DataProtectionExtensions.Test/TestCertificate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Security.Cryptography.X509Certificates;
4 |
5 | namespace DataProtectionExtensions.Test
6 | {
7 | public static class TestCertificate
8 | {
9 | private const string _certificateBytes = "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";
10 |
11 | ///
12 | /// Gets an embedded test certificate to check encryption/decryption functions.
13 | ///
14 | ///
15 | /// An that can be used for encryption/decryption.
16 | ///
17 | public static X509Certificate2 GetCertificate()
18 | {
19 | // Certificate originally generated with
20 | // makecert -sv privatekey.pvk -n "CN=Key Protection" keyprotection.cer -sky Exchange
21 | // pvk2pfx /pvk privatekey.pvk /spc keyprotection.cer /pfx keyprotection.pfx
22 | // The above bytes are the keyprotection.pfx certificate.
23 | var bytes = Convert.FromBase64String(_certificateBytes);
24 | return new X509Certificate2(bytes);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/DataProtectionExtensions.Test/XmlExtensionsFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Xml;
4 | using System.Xml.Linq;
5 | using DataProtectionExtensions;
6 | using Xunit;
7 |
8 | namespace DataProtectionExtensions.Test
9 | {
10 | public class XmlExtensionsFixture
11 | {
12 | [Fact]
13 | public void ElementToProcess_GetsCorrectElement()
14 | {
15 | var doc = new XmlDocument() { XmlResolver = null };
16 | var sreader = new System.IO.StringReader("text");
17 | var reader = new XmlTextReader(sreader) { DtdProcessing = DtdProcessing.Prohibit };
18 | doc.Load(reader);
19 | var element = doc.ElementToProcess();
20 | Assert.Equal("text", element.OuterXml);
21 | }
22 |
23 | [Fact]
24 | public void ElementToProcess_NullDocument()
25 | {
26 | Assert.Throws(() => XmlExtensions.ElementToProcess(null));
27 | }
28 |
29 | [Fact]
30 | public void ToXElement_ConvertsNode()
31 | {
32 | string xml = "text";
33 | var doc = new XmlDocument() { XmlResolver = null };
34 | var sreader = new System.IO.StringReader(xml);
35 | var reader = new XmlTextReader(sreader) { DtdProcessing = DtdProcessing.Prohibit };
36 | doc.Load(reader);
37 | var el = doc.ToXElement();
38 | Assert.Equal(xml, el.ToString(SaveOptions.DisableFormatting));
39 | }
40 |
41 | [Fact]
42 | public void ToXElement_NullNode()
43 | {
44 | Assert.Throws(() => XmlExtensions.ToXElement(null));
45 | }
46 |
47 | [Fact]
48 | public void ToXmlDocumentWithRootNode_ElementConverted()
49 | {
50 | var element = XElement.Parse("text");
51 | var doc = element.ToXmlDocumentWithRootNode();
52 | Assert.Equal("text", doc.OuterXml);
53 | }
54 |
55 | [Fact]
56 | public void ToXmlDocumentWithRootNode_NullElement()
57 | {
58 | Assert.Throws(() => XmlExtensions.ToXmlDocumentWithRootNode(null));
59 | }
60 |
61 | [Fact]
62 | public void XmlExtensions_Integration()
63 | {
64 | // These extensions work together during encryption/decryption
65 | // operations so this test validates their interaction.
66 | var expected = "text";
67 | var actual = XElement.Parse(expected)
68 | .ToXmlDocumentWithRootNode()
69 | .ElementToProcess()
70 | .ToXElement()
71 | .ToString(SaveOptions.DisableFormatting);
72 |
73 | Assert.Equal(expected, actual);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------