├── .gitignore
├── ConfigCrypter.Console
├── ConfigCrypter.Console.csproj
├── Options
│ ├── CommandlineOptions.cs
│ ├── DecryptOptions.cs
│ └── EncryptOptions.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
└── logo.png
├── ConfigCrypter.Tests
├── ConfigCrypter.Tests.csproj
├── ConfigCrypters
│ └── Json
│ │ └── JsonConfigCrypterTests.cs
├── ConfigProviders
│ └── EncryptedJsonConfigSourceTests.cs
├── Crypters
│ └── RSACrypterTests.cs
├── Mocks.cs
├── TestAppSettings.cs
├── appsettings.json
├── appsettings_decrypted.json
├── config.json
├── config_decrypted.json
└── test-certificate.pfx
├── ConfigCrypter.sln
├── ConfigCrypter
├── CertificateLoaders
│ ├── FilesystemCertificateLoader.cs
│ ├── ICertificateLoader.cs
│ └── StoreCertificateLoader.cs
├── ConfigCrypter.csproj
├── ConfigCrypters
│ ├── IConfigCrypter.cs
│ └── Json
│ │ └── JsonConfigCrypter.cs
├── ConfigFileCrypter.cs
├── ConfigFileCrypterOptions.cs
├── ConfigProviders
│ └── Json
│ │ ├── EncryptedJsonConfigProvider.cs
│ │ └── EncryptedJsonConfigSource.cs
├── Crypters
│ ├── ICrypter.cs
│ └── RSACrypter.cs
├── Extensions
│ └── ConfigurationBuilderExtensions.cs
├── Properties
│ └── launchSettings.json
└── logo.png
├── Example.WebApp
├── Controllers
│ └── ConfigController.cs
├── Example.WebApp.csproj
├── NestedSettings.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Startup.cs
├── appsettings.Development.json
├── appsettings.Production.json
├── appsettings.json
└── cert.pfx
├── LICENSE
├── README.md
├── cert.pfx
└── devattic_configcrypter-normal256.png
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudio,csharp
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,csharp
4 |
5 | ### Csharp ###
6 | ## Ignore Visual Studio temporary files, build results, and
7 | ## files generated by popular Visual Studio add-ons.
8 | ##
9 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
10 |
11 | # User-specific files
12 | *.rsuser
13 | *.suo
14 | *.user
15 | *.userosscache
16 | *.sln.docstates
17 |
18 | # User-specific files (MonoDevelop/Xamarin Studio)
19 | *.userprefs
20 |
21 | # Mono auto generated files
22 | mono_crash.*
23 |
24 | # Build results
25 | [Dd]ebug/
26 | [Dd]ebugPublic/
27 | [Rr]elease/
28 | [Rr]eleases/
29 | x64/
30 | x86/
31 | [Aa][Rr][Mm]/
32 | [Aa][Rr][Mm]64/
33 | bld/
34 | [Bb]in/
35 | [Oo]bj/
36 | [Ll]og/
37 | [Ll]ogs/
38 |
39 | # Visual Studio 2015/2017 cache/options directory
40 | .vs/
41 | # Uncomment if you have tasks that create the project's static files in wwwroot
42 | #wwwroot/
43 |
44 | # Visual Studio 2017 auto generated files
45 | Generated\ Files/
46 |
47 | # MSTest test Results
48 | [Tt]est[Rr]esult*/
49 | [Bb]uild[Ll]og.*
50 |
51 | # NUnit
52 | *.VisualState.xml
53 | TestResult.xml
54 | nunit-*.xml
55 |
56 | # Build Results of an ATL Project
57 | [Dd]ebugPS/
58 | [Rr]eleasePS/
59 | dlldata.c
60 |
61 | # Benchmark Results
62 | BenchmarkDotNet.Artifacts/
63 |
64 | # .NET Core
65 | project.lock.json
66 | project.fragment.lock.json
67 | artifacts/
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*[.json, .xml, .info]
147 |
148 | # Visual Studio code coverage results
149 | *.coverage
150 | *.coveragexml
151 |
152 | # NCrunch
153 | _NCrunch_*
154 | .*crunch*.local.xml
155 | nCrunchTemp_*
156 |
157 | # MightyMoose
158 | *.mm.*
159 | AutoTest.Net/
160 |
161 | # Web workbench (sass)
162 | .sass-cache/
163 |
164 | # Installshield output folder
165 | [Ee]xpress/
166 |
167 | # DocProject is a documentation generator add-in
168 | DocProject/buildhelp/
169 | DocProject/Help/*.HxT
170 | DocProject/Help/*.HxC
171 | DocProject/Help/*.hhc
172 | DocProject/Help/*.hhk
173 | DocProject/Help/*.hhp
174 | DocProject/Help/Html2
175 | DocProject/Help/html
176 |
177 | # Click-Once directory
178 | publish/
179 |
180 | # Publish Web Output
181 | *.[Pp]ublish.xml
182 | *.azurePubxml
183 | # Note: Comment the next line if you want to checkin your web deploy settings,
184 | # but database connection strings (with potential passwords) will be unencrypted
185 | *.pubxml
186 | *.publishproj
187 |
188 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
189 | # checkin your Azure Web App publish settings, but sensitive information contained
190 | # in these scripts will be unencrypted
191 | PublishScripts/
192 |
193 | # NuGet Packages
194 | *.nupkg
195 | # NuGet Symbol Packages
196 | *.snupkg
197 | # The packages folder can be ignored because of Package Restore
198 | **/[Pp]ackages/*
199 | # except build/, which is used as an MSBuild target.
200 | !**/[Pp]ackages/build/
201 | # Uncomment if necessary however generally it will be regenerated when needed
202 | #!**/[Pp]ackages/repositories.config
203 | # NuGet v3's project.json files produces more ignorable files
204 | *.nuget.props
205 | *.nuget.targets
206 |
207 | # Microsoft Azure Build Output
208 | csx/
209 | *.build.csdef
210 |
211 | # Microsoft Azure Emulator
212 | ecf/
213 | rcf/
214 |
215 | # Windows Store app package directories and files
216 | AppPackages/
217 | BundleArtifacts/
218 | Package.StoreAssociation.xml
219 | _pkginfo.txt
220 | *.appx
221 | *.appxbundle
222 | *.appxupload
223 |
224 | # Visual Studio cache files
225 | # files ending in .cache can be ignored
226 | *.[Cc]ache
227 | # but keep track of directories ending in .cache
228 | !?*.[Cc]ache/
229 |
230 | # Others
231 | ClientBin/
232 | ~$*
233 | *~
234 | *.dbmdl
235 | *.dbproj.schemaview
236 | *.jfm
237 | *.pfx
238 | *.publishsettings
239 | orleans.codegen.cs
240 |
241 | # Including strong name files can present a security risk
242 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
243 | #*.snk
244 |
245 | # Since there are multiple workflows, uncomment next line to ignore bower_components
246 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
247 | #bower_components/
248 |
249 | # RIA/Silverlight projects
250 | Generated_Code/
251 |
252 | # Backup & report files from converting an old project file
253 | # to a newer Visual Studio version. Backup files are not needed,
254 | # because we have git ;-)
255 | _UpgradeReport_Files/
256 | Backup*/
257 | UpgradeLog*.XML
258 | UpgradeLog*.htm
259 | ServiceFabricBackup/
260 | *.rptproj.bak
261 |
262 | # SQL Server files
263 | *.mdf
264 | *.ldf
265 | *.ndf
266 |
267 | # Business Intelligence projects
268 | *.rdl.data
269 | *.bim.layout
270 | *.bim_*.settings
271 | *.rptproj.rsuser
272 | *- [Bb]ackup.rdl
273 | *- [Bb]ackup ([0-9]).rdl
274 | *- [Bb]ackup ([0-9][0-9]).rdl
275 |
276 | # Microsoft Fakes
277 | FakesAssemblies/
278 |
279 | # GhostDoc plugin setting file
280 | *.GhostDoc.xml
281 |
282 | # Node.js Tools for Visual Studio
283 | .ntvs_analysis.dat
284 | node_modules/
285 |
286 | # Visual Studio 6 build log
287 | *.plg
288 |
289 | # Visual Studio 6 workspace options file
290 | *.opt
291 |
292 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
293 | *.vbw
294 |
295 | # Visual Studio LightSwitch build output
296 | **/*.HTMLClient/GeneratedArtifacts
297 | **/*.DesktopClient/GeneratedArtifacts
298 | **/*.DesktopClient/ModelManifest.xml
299 | **/*.Server/GeneratedArtifacts
300 | **/*.Server/ModelManifest.xml
301 | _Pvt_Extensions
302 |
303 | # Paket dependency manager
304 | .paket/paket.exe
305 | paket-files/
306 |
307 | # FAKE - F# Make
308 | .fake/
309 |
310 | # CodeRush personal settings
311 | .cr/personal
312 |
313 | # Python Tools for Visual Studio (PTVS)
314 | __pycache__/
315 | *.pyc
316 |
317 | # Cake - Uncomment if you are using it
318 | # tools/**
319 | # !tools/packages.config
320 |
321 | # Tabs Studio
322 | *.tss
323 |
324 | # Telerik's JustMock configuration file
325 | *.jmconfig
326 |
327 | # BizTalk build output
328 | *.btp.cs
329 | *.btm.cs
330 | *.odx.cs
331 | *.xsd.cs
332 |
333 | # OpenCover UI analysis results
334 | OpenCover/
335 |
336 | # Azure Stream Analytics local run output
337 | ASALocalRun/
338 |
339 | # MSBuild Binary and Structured Log
340 | *.binlog
341 |
342 | # NVidia Nsight GPU debugger configuration file
343 | *.nvuser
344 |
345 | # MFractors (Xamarin productivity tool) working folder
346 | .mfractor/
347 |
348 | # Local History for Visual Studio
349 | .localhistory/
350 |
351 | # BeatPulse healthcheck temp database
352 | healthchecksdb
353 |
354 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
355 | MigrationBackup/
356 |
357 | # Ionide (cross platform F# VS Code tools) working folder
358 | .ionide/
359 |
360 | !ConfigCrypter.Tests/test-certificate.pfx
361 |
362 | ### VisualStudio ###
363 |
364 | # User-specific files
365 |
366 | # User-specific files (MonoDevelop/Xamarin Studio)
367 |
368 | # Mono auto generated files
369 |
370 | # Build results
371 |
372 | # Visual Studio 2015/2017 cache/options directory
373 | # Uncomment if you have tasks that create the project's static files in wwwroot
374 |
375 | # Visual Studio 2017 auto generated files
376 |
377 | # MSTest test Results
378 |
379 | # NUnit
380 |
381 | # Build Results of an ATL Project
382 |
383 | # Benchmark Results
384 |
385 | # .NET Core
386 |
387 | # StyleCop
388 |
389 | # Files built by Visual Studio
390 |
391 | # Chutzpah Test files
392 |
393 | # Visual C++ cache files
394 |
395 | # Visual Studio profiler
396 |
397 | # Visual Studio Trace Files
398 |
399 | # TFS 2012 Local Workspace
400 |
401 | # Guidance Automation Toolkit
402 |
403 | # ReSharper is a .NET coding add-in
404 |
405 | # TeamCity is a build add-in
406 |
407 | # DotCover is a Code Coverage Tool
408 |
409 | # AxoCover is a Code Coverage Tool
410 |
411 | # Coverlet is a free, cross platform Code Coverage Tool
412 |
413 | # Visual Studio code coverage results
414 |
415 | # NCrunch
416 |
417 | # MightyMoose
418 |
419 | # Web workbench (sass)
420 |
421 | # Installshield output folder
422 |
423 | # DocProject is a documentation generator add-in
424 |
425 | # Click-Once directory
426 |
427 | # Publish Web Output
428 | # Note: Comment the next line if you want to checkin your web deploy settings,
429 | # but database connection strings (with potential passwords) will be unencrypted
430 |
431 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
432 | # checkin your Azure Web App publish settings, but sensitive information contained
433 | # in these scripts will be unencrypted
434 |
435 | # NuGet Packages
436 | # NuGet Symbol Packages
437 | # The packages folder can be ignored because of Package Restore
438 | # except build/, which is used as an MSBuild target.
439 | # Uncomment if necessary however generally it will be regenerated when needed
440 | # NuGet v3's project.json files produces more ignorable files
441 |
442 | # Microsoft Azure Build Output
443 |
444 | # Microsoft Azure Emulator
445 |
446 | # Windows Store app package directories and files
447 |
448 | # Visual Studio cache files
449 | # files ending in .cache can be ignored
450 | # but keep track of directories ending in .cache
451 |
452 | # Others
453 |
454 | # Including strong name files can present a security risk
455 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
456 |
457 | # Since there are multiple workflows, uncomment next line to ignore bower_components
458 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
459 |
460 | # RIA/Silverlight projects
461 |
462 | # Backup & report files from converting an old project file
463 | # to a newer Visual Studio version. Backup files are not needed,
464 | # because we have git ;-)
465 |
466 | # SQL Server files
467 |
468 | # Business Intelligence projects
469 |
470 | # Microsoft Fakes
471 |
472 | # GhostDoc plugin setting file
473 |
474 | # Node.js Tools for Visual Studio
475 |
476 | # Visual Studio 6 build log
477 |
478 | # Visual Studio 6 workspace options file
479 |
480 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
481 |
482 | # Visual Studio LightSwitch build output
483 |
484 | # Paket dependency manager
485 |
486 | # FAKE - F# Make
487 |
488 | # CodeRush personal settings
489 |
490 | # Python Tools for Visual Studio (PTVS)
491 |
492 | # Cake - Uncomment if you are using it
493 | # tools/**
494 | # !tools/packages.config
495 |
496 | # Tabs Studio
497 |
498 | # Telerik's JustMock configuration file
499 |
500 | # BizTalk build output
501 |
502 | # OpenCover UI analysis results
503 |
504 | # Azure Stream Analytics local run output
505 |
506 | # MSBuild Binary and Structured Log
507 |
508 | # NVidia Nsight GPU debugger configuration file
509 |
510 | # MFractors (Xamarin productivity tool) working folder
511 |
512 | # Local History for Visual Studio
513 |
514 | # BeatPulse healthcheck temp database
515 |
516 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
517 |
518 | # Ionide (cross platform F# VS Code tools) working folder
519 |
520 | # End of https://www.toptal.com/developers/gitignore/api/visualstudio,csharp
521 |
522 | !cert.pfx
523 |
--------------------------------------------------------------------------------
/ConfigCrypter.Console/ConfigCrypter.Console.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | true
7 | config-crypter
8 | ./nupkg
9 | logo.png
10 | DevAttic.ConfigCrypter.Console
11 | devattic
12 | DevAttic ConfigCrypter Console
13 | DevAttic ConfigCrypter Console is a dotnet tool used to encrypt and decrypt keys in configuration files.
14 | MIT
15 |
16 | https://github.com/devattic/ConfigCrypter
17 | DevAttic
18 | config appsettings encryption tool netcore
19 | 1.1.0
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | True
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/ConfigCrypter.Console/Options/CommandlineOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 |
3 | namespace ConfigCrypter.Console.Options
4 | {
5 | public class CommandlineOptions
6 | {
7 | [Option('p', "path", Required = true, HelpText = "Path of the certificate.", Group = "CertLocation")]
8 | public string CertificatePath { get; set; }
9 |
10 | [Option('n', "name", Required = true, HelpText = "The subject name of the certificate (CN). This can only be used in Windows environments.", Group = "CertLocation")]
11 | public string CertSubjectName { get; set; }
12 |
13 | [Option('s', "password", Required = false, HelpText = "Password of the certificate (if available).", Default = null)]
14 | public string CertificatePassword { get; set; }
15 |
16 | [Option('k', "key", Required = true, HelpText = "The key to encrypt in the config file.")]
17 | public string Key { get; set; }
18 |
19 | [Option('f', "file", Required = true, HelpText = "The path to the config file.")]
20 | public string ConfigFile { get; set; }
21 |
22 | [Option('r', "replace", HelpText = "Replaces the original file if passed as parameter.", Default = false)]
23 | public bool Replace { get; set; }
24 |
25 | [Option("format", Default = ConfigFormat.Json, HelpText = "The format of the config file.")]
26 | public ConfigFormat ConfigFormat { get; set; }
27 | }
28 |
29 | public enum ConfigFormat
30 | {
31 | Json
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ConfigCrypter.Console/Options/DecryptOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 |
3 | namespace ConfigCrypter.Console.Options
4 | {
5 | [Verb("decrypt", HelpText = "Decrypts a key in the config file.")]
6 | class DecryptOptions : CommandlineOptions { }
7 | }
8 |
--------------------------------------------------------------------------------
/ConfigCrypter.Console/Options/EncryptOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 |
3 | namespace ConfigCrypter.Console.Options
4 | {
5 | [Verb("encrypt", HelpText = "Encrypts a key in the config file.")]
6 | public class EncryptOptions : CommandlineOptions { }
7 | }
8 |
--------------------------------------------------------------------------------
/ConfigCrypter.Console/Program.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using ConfigCrypter.Console.Options;
3 | using DevAttic.ConfigCrypter;
4 | using DevAttic.ConfigCrypter.CertificateLoaders;
5 | using DevAttic.ConfigCrypter.ConfigCrypters.Json;
6 | using DevAttic.ConfigCrypter.Crypters;
7 |
8 | namespace ConfigCrypter.Console
9 | {
10 | class Program
11 | {
12 | static void Main(string[] args)
13 | {
14 | Parser.Default.ParseArguments(args)
15 | .WithParsed(opts =>
16 | {
17 | var crypter = CreateCrypter(opts);
18 | crypter.EncryptKeyInFile(opts.ConfigFile, opts.Key);
19 | })
20 | .WithParsed(opts =>
21 | {
22 | var crypter = CreateCrypter(opts);
23 | crypter.DecryptKeyInFile(opts.ConfigFile, opts.Key);
24 | });
25 | }
26 |
27 | private static ConfigFileCrypter CreateCrypter(CommandlineOptions options)
28 | {
29 | ICertificateLoader certLoader = null;
30 |
31 | if (!string.IsNullOrEmpty(options.CertificatePath))
32 | {
33 | certLoader = new FilesystemCertificateLoader(options.CertificatePath, options.CertificatePassword);
34 | }
35 | else if (!string.IsNullOrEmpty(options.CertSubjectName))
36 | {
37 | certLoader = new StoreCertificateLoader(options.CertSubjectName);
38 | }
39 |
40 | var configCrypter = new JsonConfigCrypter(new RSACrypter(certLoader));
41 |
42 | var fileCrypter = new ConfigFileCrypter(configCrypter, new ConfigFileCrypterOptions()
43 | {
44 | ReplaceCurrentConfig = options.Replace
45 | });
46 |
47 | return fileCrypter;
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/ConfigCrypter.Console/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ConfigCrypter.Console": {
4 | "commandName": "Project"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/ConfigCrypter.Console/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/ConfigCrypter.Console/logo.png
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/ConfigCrypter.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 | false
7 |
8 | DevAttic.ConfigCrypter.Tests
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | PreserveNewest
20 |
21 |
22 | PreserveNewest
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | all
36 | runtime; build; native; contentfiles; analyzers; buildtransitive
37 |
38 |
39 | all
40 | runtime; build; native; contentfiles; analyzers; buildtransitive
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/ConfigCrypters/Json/JsonConfigCrypterTests.cs:
--------------------------------------------------------------------------------
1 | using DevAttic.ConfigCrypter.ConfigCrypters.Json;
2 | using Newtonsoft.Json;
3 | using Xunit;
4 |
5 | namespace DevAttic.ConfigCrypter.Tests.ConfigCrypters.Json
6 | {
7 | public class JsonConfigCrypterTests
8 | {
9 | [Fact]
10 | public void EncryptKey_WithValidJson_CallsEncryptStringOnCrypter()
11 | {
12 | var crypterMock = Mocks.Crypter;
13 | var jsonCrypter = new JsonConfigCrypter(crypterMock.Object);
14 | var json = JsonConvert.SerializeObject(new TestAppSettings() { Key = "ValueToEncrypt" });
15 |
16 | var encryptedJson = jsonCrypter.EncryptKey(json, "Key");
17 | var parsedJson = JsonConvert.DeserializeObject(encryptedJson);
18 |
19 | crypterMock.Verify(crypter => crypter.EncryptString("ValueToEncrypt"));
20 |
21 | // Additionally we test if the test crypter does its job.
22 | Assert.Equal("ValueToEncrypt_encrypted", parsedJson.Key);
23 | }
24 |
25 | [Fact]
26 | public void DecryptKey_WithValidJson_CallsDecryptStringOnCrypter()
27 | {
28 | var crypterMock = Mocks.Crypter;
29 | var jsonCrypter = new JsonConfigCrypter(crypterMock.Object);
30 | var json = JsonConvert.SerializeObject(new { Key = "ValueToEncrypt_encrypted" });
31 |
32 | var decryptedJson = jsonCrypter.DecryptKey(json, "Key");
33 | var parsedJson = JsonConvert.DeserializeObject(decryptedJson);
34 |
35 | crypterMock.Verify(crypter => crypter.DecryptString("ValueToEncrypt_encrypted"));
36 | Assert.Equal("ValueToEncrypt", parsedJson.Key);
37 | }
38 |
39 | [Fact]
40 | public void Dispose_CallsDisposeOnCrypter()
41 | {
42 | var crypterMock = Mocks.Crypter;
43 | var jsonCrypter = new JsonConfigCrypter(crypterMock.Object);
44 |
45 | jsonCrypter.Dispose();
46 |
47 | crypterMock.Verify(crypter => crypter.Dispose());
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/ConfigProviders/EncryptedJsonConfigSourceTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using DevAttic.ConfigCrypter.Extensions;
3 | using Microsoft.Extensions.Configuration;
4 | using Xunit;
5 |
6 | namespace DevAttic.ConfigCrypter.Tests.ConfigProviders
7 | {
8 | public class EncryptedJsonConfigSourceTests
9 | {
10 | [Fact]
11 | public void AddEncryptedAppSettings_DecryptsValuesOnTheFly()
12 | {
13 | var certLoaderMock = Mocks.CertificateLoader;
14 | var configBuilder = new ConfigurationBuilder();
15 | configBuilder.AddEncryptedAppSettings(config =>
16 | {
17 | config.KeysToDecrypt = new List { "Test:ToBeEncrypted" };
18 | config.CertificateLoader = certLoaderMock.Object;
19 | });
20 | var configuration = configBuilder.Build();
21 |
22 | var decryptedValue = configuration["Test:ToBeEncrypted"];
23 |
24 | Assert.Equal("This is going to be encrypted", decryptedValue);
25 | }
26 |
27 | [Fact]
28 | public void AddEncryptedJsonConfig_DecryptsValuesOnTheFly()
29 | {
30 | var certLoaderMock = Mocks.CertificateLoader;
31 | var configBuilder = new ConfigurationBuilder();
32 | configBuilder.AddEncryptedJsonConfig(config =>
33 | {
34 | config.KeysToDecrypt = new List { "KeyToEncrypt" };
35 | config.CertificateLoader = certLoaderMock.Object;
36 | config.Path = "config.json";
37 | });
38 | var configuration = configBuilder.Build();
39 |
40 | var decryptedValue = configuration["KeyToEncrypt"];
41 |
42 | Assert.Equal("This will be encrypted.", decryptedValue);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/Crypters/RSACrypterTests.cs:
--------------------------------------------------------------------------------
1 | using DevAttic.ConfigCrypter.Crypters;
2 | using Xunit;
3 |
4 | namespace DevAttic.ConfigCrypter.Tests.Crypters
5 | {
6 | public class RSACrypterTests
7 | {
8 | [Fact]
9 | public void Constructor_CallsLoadCertificate()
10 | {
11 | var certificateLoaderMock = Mocks.CertificateLoader;
12 |
13 | var rsaCrypter = new RSACrypter(certificateLoaderMock.Object);
14 |
15 | certificateLoaderMock.Verify(loader => loader.LoadCertificate());
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/Mocks.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Reflection;
4 | using System.Security.Cryptography.X509Certificates;
5 | using DevAttic.ConfigCrypter.CertificateLoaders;
6 | using DevAttic.ConfigCrypter.Crypters;
7 | using Moq;
8 |
9 | namespace DevAttic.ConfigCrypter.Tests
10 | {
11 | public static class Mocks
12 | {
13 | public static Mock CertificateLoader
14 | {
15 | get
16 | {
17 | var certLoaderMock = new Mock();
18 | certLoaderMock.Setup(loader => loader.LoadCertificate()).Returns(() =>
19 | {
20 | using var certStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("DevAttic.ConfigCrypter.Tests.test-certificate.pfx");
21 | using var ms = new MemoryStream();
22 | certStream!.CopyTo(ms);
23 |
24 | return new X509Certificate2(ms.ToArray());
25 | });
26 |
27 | return certLoaderMock;
28 | }
29 | }
30 |
31 | public static Mock Crypter
32 | {
33 | get
34 | {
35 | var crypterMock = new Mock();
36 | crypterMock.Setup(crypter => crypter.EncryptString(It.IsAny()))
37 | .Returns(input => $"{input}_encrypted");
38 | crypterMock.Setup(crypter => crypter.DecryptString(It.IsAny()))
39 | .Returns(input =>
40 | {
41 | var encryptedIndex = input.LastIndexOf("_encrypted", StringComparison.Ordinal);
42 |
43 | return encryptedIndex > -1
44 | ? input.Substring(0, encryptedIndex)
45 | : input.Substring(0, input.Length);
46 | });
47 |
48 | return crypterMock;
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/TestAppSettings.cs:
--------------------------------------------------------------------------------
1 | namespace DevAttic.ConfigCrypter.Tests
2 | {
3 | public class TestAppSettings
4 | {
5 | public string Key { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Test": {
3 | "Encrypted": "",
4 | "ToBeEncrypted": "lCZp1TKiMk5zp+nMKs9FAmpzkrfrtB9mvHKg3FRNXpT0VNHx1CurOji2va3k159/LZY80/mYe1Crmh38WFZjxCuASinFHjsB3U0REmMmazsFr04qnxMRYDS+nOwphIK+mao0YNKajLq3WPh6voIFt0F8KBmYetSlGE3u6oztzDvalzDHQqupkI5qc/yFLdOHOfzpn5QkyE92kSXGiZyCkNkM/SJLJhlzf7SB538U2d7VGswy+3iJC0Rv10gjjHF0uzywIv8pxdpDd9rhSkS6x4x1XsDyUV/132Q0QGfBwvPramNrgbkgIePxwudTnrMQkaphblkyKOeThJHMUS2u1g=="
5 | }
6 | }
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/appsettings_decrypted.json:
--------------------------------------------------------------------------------
1 | {
2 | "Test": {
3 | "Encrypted": "",
4 | "ToBeEncrypted": "This is going to be encrypted"
5 | }
6 | }
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "KeyToEncrypt": "fvQIaoOW4jVGzNUs7QilDFSBWtjACTdxNTA/d8xF6MIwuKVjhkBKBAGL+FIXk+vA9YRqfLid/OvucaH3Bhs/9ur+9/dKGuCklFRYCWEo9sQoihBHDYZ/67YSCOVzZp2zvtJI3ZjRwefjHpOqSOjcAmgZQYF519tbLzLQOUK0DOEzC6hP+1pMt6qgVYBi1F0/gjUaqXSsiwmkZFmu1rgA9LfRY6RRi+Cy4OVfcXON4tYwiyAyYQ59bT2eggv5DqlrKGLYAZOHpmaCasjxMbyYuFrIg/uehErVtSMMVSq5qdaysxo7wFNuqbCrC/jPNkCMKE+c1+9JgVM52bmU01eg9A=="
3 | }
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/config_decrypted.json:
--------------------------------------------------------------------------------
1 | {
2 | "KeyToEncrypt": "This will be encrypted."
3 | }
4 |
--------------------------------------------------------------------------------
/ConfigCrypter.Tests/test-certificate.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/ConfigCrypter.Tests/test-certificate.pfx
--------------------------------------------------------------------------------
/ConfigCrypter.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30309.148
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCrypter.Tests", "ConfigCrypter.Tests\ConfigCrypter.Tests.csproj", "{BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCrypter.Console", "ConfigCrypter.Console\ConfigCrypter.Console.csproj", "{A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigCrypter", "ConfigCrypter\ConfigCrypter.csproj", "{AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.WebApp", "Example.WebApp\Example.WebApp.csproj", "{88CE2CCB-5A74-4400-8B00-04B1E10DE540}"
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 | {BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {BD01DA11-CEEB-4B80-A0E7-6E45AAAE2062}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {A8441866-F5B4-4C7D-A9AC-F6D8BA403AA8}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {AE7941FB-9A0E-4A6A-873B-A3391A3BEF7C}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {88CE2CCB-5A74-4400-8B00-04B1E10DE540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {88CE2CCB-5A74-4400-8B00-04B1E10DE540}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {88CE2CCB-5A74-4400-8B00-04B1E10DE540}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {88CE2CCB-5A74-4400-8B00-04B1E10DE540}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {BF324722-EE3E-4143-8BCD-5FED5BBA04FD}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/ConfigCrypter/CertificateLoaders/FilesystemCertificateLoader.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Cryptography.X509Certificates;
2 |
3 | namespace DevAttic.ConfigCrypter.CertificateLoaders
4 | {
5 | ///
6 | /// Loader that loads a certificate from the filesystem.
7 | ///
8 | public class FilesystemCertificateLoader : ICertificateLoader
9 | {
10 | private readonly string _certificatePath;
11 | private readonly string _certificatePassword;
12 |
13 | ///
14 | /// Creates an instance of the certificate loader.
15 | ///
16 | /// Fully qualified path to the certificate (.pfx file).
17 | /// Password of the certificate, if available.
18 | public FilesystemCertificateLoader(string certificatePath, string certificatePassword = null)
19 | {
20 | _certificatePath = certificatePath;
21 | _certificatePassword = certificatePassword;
22 | }
23 |
24 | ///
25 | /// Loads a certificate from the given location on the filesystem.
26 | ///
27 | /// A X509Certificate2 instance.
28 | public X509Certificate2 LoadCertificate()
29 | {
30 | return string.IsNullOrEmpty(_certificatePassword) ?
31 | new X509Certificate2(_certificatePath) :
32 | new X509Certificate2(_certificatePath, _certificatePassword);
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/ConfigCrypter/CertificateLoaders/ICertificateLoader.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Cryptography.X509Certificates;
2 |
3 | namespace DevAttic.ConfigCrypter.CertificateLoaders
4 | {
5 | ///
6 | /// Responsible for loading a certificate.
7 | ///
8 | /// Custom certificate loaders can be implemented by implementing this interface.
9 | public interface ICertificateLoader
10 | {
11 | ///
12 | /// Loads a certificate.
13 | ///
14 | /// A X509Certificate2 instance.
15 | X509Certificate2 LoadCertificate();
16 | }
17 | }
--------------------------------------------------------------------------------
/ConfigCrypter/CertificateLoaders/StoreCertificateLoader.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Security.Cryptography.X509Certificates;
3 |
4 | namespace DevAttic.ConfigCrypter.CertificateLoaders
5 | {
6 | ///
7 | /// Loader that loads a certificate from the Windows certificate store.
8 | ///
9 | public class StoreCertificateLoader : ICertificateLoader
10 | {
11 | private readonly string _subjectName;
12 |
13 | public StoreCertificateLoader(string subjectName)
14 | {
15 | _subjectName = subjectName;
16 | }
17 |
18 | ///
19 | /// Loads a certificate by subject name from the store.
20 | ///
21 | /// A X509Certificate2 instance.
22 | /// The loader looks for the certificate in the own certificates of the local machine store. It uses the FindBySubjectName find type.
23 | public X509Certificate2 LoadCertificate()
24 | {
25 | using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
26 | {
27 | store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
28 |
29 | var certs = store.Certificates.Find(X509FindType.FindBySubjectName, _subjectName, false);
30 | var cert = certs.Cast().FirstOrDefault();
31 | store.Close();
32 |
33 | return cert;
34 | }
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/ConfigCrypter/ConfigCrypter.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | DevAttic.ConfigCrypter
6 | devattic
7 | DevAttic
8 | DevAttic ConfigCrypter
9 | DevAttic.ConfigCrypter
10 | DevAttic.ConfigCrypter
11 | logo.png
12 | DevAttic ConfigCrypter is a library that enables the user to work with decrypted configuration files.
13 |
14 | https://github.com/devattic/ConfigCrypter
15 | netcore aspnetcore netstandard config appsettings encryption decryption
16 | MIT
17 | 1.1.0.0
18 | 1.1.0
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | True
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/ConfigCrypter/ConfigCrypters/IConfigCrypter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DevAttic.ConfigCrypter.ConfigCrypters
4 | {
5 | ///
6 | /// Encrypts/Decrypts keys in configuration files.
7 | ///
8 | public interface IConfigCrypter : IDisposable
9 | {
10 | ///
11 | /// Decrypts the key in the given content of a config file.
12 | ///
13 | /// String content of a config file.
14 | /// Key of the config entry.
15 | /// The content of the config file where the key has been decrypted.
16 | /// It up to the implementer how to interpret the format of the config key.
17 | string DecryptKey(string configFileContent, string configKey);
18 |
19 | ///
20 | /// Encrypts the key in the given content of a config file.
21 | ///
22 | /// String content of a config file.
23 | /// Key of the config entry.
24 | /// The content of the config file where the key has been encrypted.
25 | /// It up to the implementer how to interpret the format of the config key.
26 | string EncryptKey(string configFileContent, string configKey);
27 | }
28 | }
--------------------------------------------------------------------------------
/ConfigCrypter/ConfigCrypters/Json/JsonConfigCrypter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using DevAttic.ConfigCrypter.Crypters;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 |
6 | namespace DevAttic.ConfigCrypter.ConfigCrypters.Json
7 | {
8 | ///
9 | /// Config crypter that encrypts and decrypts keys in JSON config files.
10 | ///
11 | public class JsonConfigCrypter : IConfigCrypter
12 | {
13 | private readonly ICrypter _crypter;
14 |
15 | ///
16 | /// Creates an instance of the JsonConfigCrypter.
17 | ///
18 | /// An ICrypter instance.
19 | public JsonConfigCrypter(ICrypter crypter)
20 | {
21 | _crypter = crypter;
22 | }
23 |
24 | ///
25 | /// Decrypts the key in the given content of a config file.
26 | ///
27 | /// String content of a config file.
28 | /// Key of the config entry. The key has to be in JSONPath format.
29 | /// The content of the config file where the key has been decrypted.
30 | public string DecryptKey(string configFileContent, string configKey)
31 | {
32 | var (parsedConfig, settingsToken) = ParseConfig(configFileContent, configKey);
33 |
34 | var encryptedValue = _crypter.DecryptString(settingsToken.Value());
35 | settingsToken.Replace(encryptedValue);
36 | var newConfigContent = parsedConfig.ToString(Formatting.Indented);
37 |
38 | return newConfigContent;
39 | }
40 |
41 | public void Dispose()
42 | {
43 | Dispose(true);
44 | GC.SuppressFinalize(this);
45 | }
46 |
47 | ///
48 | /// Encrypts the key in the given content of a config file.
49 | ///
50 | /// String content of a config file.
51 | /// Key of the config entry. The key has to be in JSONPath format.
52 | /// The content of the config file where the key has been encrypted.
53 | public string EncryptKey(string configFileContent, string configKey)
54 | {
55 | var (parsedConfig, settingsToken) = ParseConfig(configFileContent, configKey);
56 |
57 | var encryptedValue = _crypter.EncryptString(settingsToken.Value());
58 | settingsToken.Replace(encryptedValue);
59 | var newConfigContent = parsedConfig.ToString(Formatting.Indented);
60 |
61 | return newConfigContent;
62 | }
63 |
64 | protected virtual void Dispose(bool disposing)
65 | {
66 | if (disposing)
67 | {
68 | _crypter?.Dispose();
69 | }
70 | }
71 |
72 | private (JObject ParsedConfig, JToken Key) ParseConfig(string json, string configKey)
73 | {
74 | var parsedJson = JObject.Parse(json);
75 | var keyToken = parsedJson.SelectToken(configKey);
76 |
77 | if (keyToken == null)
78 | {
79 | throw new InvalidOperationException($"The key {configKey} could not be found.");
80 | }
81 |
82 | return (parsedJson, keyToken);
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/ConfigCrypter/ConfigFileCrypter.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using DevAttic.ConfigCrypter.ConfigCrypters;
3 |
4 | namespace DevAttic.ConfigCrypter
5 | {
6 | ///
7 | /// Configuration crypter that reads the configuration file from the filesystem.
8 | ///
9 | public class ConfigFileCrypter
10 | {
11 | private readonly IConfigCrypter _configCrypter;
12 | private readonly ConfigFileCrypterOptions _options;
13 |
14 | ///
15 | /// Creates an instance of the ConfigFileCrypter.
16 | ///
17 | /// A config crypter instance.
18 | /// Options used for encrypting and decrypting.
19 | public ConfigFileCrypter(IConfigCrypter configCrypter, ConfigFileCrypterOptions options)
20 | {
21 | _configCrypter = configCrypter;
22 | _options = options;
23 | }
24 |
25 | ///
26 | /// Decrypts the given key in the config file.
27 | ///
28 | /// If the "ReplaceCurrentConfig" setting has been set in the options the file is getting replaced.
29 | /// If the setting has not been set a new file with the "DecryptedConfigPostfix" appended to the current file name will be created.
30 | ///
31 | /// Path of the configuration file.
32 | /// Key to decrypt, passed in a format the underlying config crypter understands.
33 | public void DecryptKeyInFile(string filePath, string configKey)
34 | {
35 | var configContent = File.ReadAllText(filePath);
36 | var decryptedConfigContent = _configCrypter.DecryptKey(configContent, configKey);
37 |
38 | var targetFilePath = GetDestinationConfigPath(filePath, _options.DecryptedConfigPostfix);
39 | File.WriteAllText(targetFilePath, decryptedConfigContent);
40 | }
41 |
42 | ///
43 | /// Encrypts the given key in the config file.
44 | ///
45 | /// If the "ReplaceCurrentConfig" setting has been set in the options the file is getting replaced.
46 | /// If the setting has not been set a new file with the "EncryptedConfigPostfix" appended to the current file name will be created.
47 | ///
48 | /// Path of the configuration file.
49 | /// Key to encrypt, passed in a format the underlying config crypter understands.
50 | public void EncryptKeyInFile(string filePath, string configKey)
51 | {
52 | var configContent = File.ReadAllText(filePath);
53 | var encryptedConfigContent = _configCrypter.EncryptKey(configContent, configKey);
54 |
55 | var targetFilePath = GetDestinationConfigPath(filePath, _options.EncryptedConfigPostfix);
56 | File.WriteAllText(targetFilePath, encryptedConfigContent);
57 | }
58 |
59 | private string GetDestinationConfigPath(string currentConfigFilePath, string postfix)
60 | {
61 | if (_options.ReplaceCurrentConfig)
62 | {
63 | return currentConfigFilePath;
64 | }
65 |
66 | var currentConfigDirectory = Path.GetDirectoryName(currentConfigFilePath);
67 | var newFilename =
68 | $"{Path.GetFileNameWithoutExtension(currentConfigFilePath)}{postfix}{Path.GetExtension(currentConfigFilePath)}";
69 | var targetFile = Path.Combine(currentConfigDirectory, newFilename);
70 |
71 | return targetFile;
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/ConfigCrypter/ConfigFileCrypterOptions.cs:
--------------------------------------------------------------------------------
1 | namespace DevAttic.ConfigCrypter
2 | {
3 | ///
4 | /// Options to configure the ConfigFileCrypter.
5 | ///
6 | public class ConfigFileCrypterOptions
7 | {
8 | ///
9 | /// Name of the postfix that should be appended when a file has been decrypted and "ReplaceCurrentConfig" is set to true.
10 | ///
11 | public string DecryptedConfigPostfix { get; set; } = "_decrypted";
12 | ///
13 | /// Name of the postfix that should be appended when a file has been encrypted and "ReplaceCurrentConfig" is set to true.
14 | ///
15 | public string EncryptedConfigPostfix { get; set; } = "_encrypted";
16 | ///
17 | /// Defines if the original config file should be overriden or a new file should be created.
18 | ///
19 | public bool ReplaceCurrentConfig { get; set; }
20 | }
21 | }
--------------------------------------------------------------------------------
/ConfigCrypter/ConfigProviders/Json/EncryptedJsonConfigProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration.Json;
2 |
3 | namespace DevAttic.ConfigCrypter.ConfigProviders.Json
4 | {
5 | ///
6 | /// JSON configuration provider that uses the underlying crypter to decrypt the given keys.
7 | ///
8 | public class EncryptedJsonConfigProvider : JsonConfigurationProvider
9 | {
10 | private readonly EncryptedJsonConfigSource _jsonConfigSource;
11 |
12 | ///
13 | /// Creates an instance of the EncryptedJsonConfigProvider.
14 | ///
15 | /// EncryptedJsonConfigSource that is used to configure the provider.
16 | public EncryptedJsonConfigProvider(EncryptedJsonConfigSource source) : base(source)
17 | {
18 | _jsonConfigSource = source;
19 | }
20 |
21 | ///
22 | /// Loads the JSON configuration file and decrypts all configured keys with the given crypter.
23 | ///
24 | public override void Load()
25 | {
26 | base.Load();
27 |
28 | using (var crypter = _jsonConfigSource.CrypterFactory(_jsonConfigSource))
29 | {
30 | foreach (var key in _jsonConfigSource.KeysToDecrypt)
31 | {
32 | if (Data.TryGetValue(key, out var encryptedValue))
33 | {
34 | Data[key] = crypter.DecryptString(encryptedValue);
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/ConfigCrypter/ConfigProviders/Json/EncryptedJsonConfigSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using DevAttic.ConfigCrypter.CertificateLoaders;
4 | using DevAttic.ConfigCrypter.Crypters;
5 | using Microsoft.Extensions.Configuration;
6 | using Microsoft.Extensions.Configuration.Json;
7 |
8 | namespace DevAttic.ConfigCrypter.ConfigProviders.Json
9 | {
10 | ///
11 | /// ConfigurationSource for encrypted JSON config files.
12 | ///
13 | public class EncryptedJsonConfigSource : JsonConfigurationSource
14 | {
15 | ///
16 | /// A certificate loader instance. Custom loaders can be used.
17 | ///
18 | public ICertificateLoader CertificateLoader { get; set; }
19 | ///
20 | /// The fully qualified path of the certificate.
21 | ///
22 | public string CertificatePath { get; set; }
23 | ///
24 | /// The subject name of the certificate (Issued for).
25 | ///
26 | public string CertificateSubjectName { get; set; }
27 | ///
28 | /// The password of the certificate or null, if the certificate has no password.
29 | ///
30 | public string CertificatePassword { get; set; } = null;
31 | ///
32 | /// Factory function that is used to create an instance of the crypter.
33 | /// The default factory uses the RSACrypter and passes it the given certificate loader.
34 | ///
35 | public Func CrypterFactory { get; set; } =
36 | cfg => new RSACrypter(cfg.CertificateLoader);
37 |
38 | ///
39 | /// List of keys that should be decrypted. Hierarchical keys need to be separated by colon.
40 | /// Example: "Nested:Key"
41 | ///
42 | public List KeysToDecrypt { get; set; } = new List();
43 |
44 | public EncryptedJsonConfigSource()
45 | {
46 | ReloadOnChange = true;
47 | }
48 |
49 | ///
50 | /// Creates an instance of the EncryptedJsonConfigProvider.
51 | ///
52 | /// IConfigurationBuilder instance.
53 | /// An EncryptedJsonConfigProvider instance.
54 | public override IConfigurationProvider Build(IConfigurationBuilder builder)
55 | {
56 | base.Build(builder);
57 | return new EncryptedJsonConfigProvider(this);
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/ConfigCrypter/Crypters/ICrypter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace DevAttic.ConfigCrypter.Crypters
4 | {
5 | ///
6 | /// A crypter that is used to encrypt and decrypt simple strings.
7 | ///
8 | public interface ICrypter : IDisposable
9 | {
10 | ///
11 | /// Decrypts the given string.
12 | ///
13 | /// String to decrypt.
14 | /// Encrypted string.
15 | string DecryptString(string value);
16 |
17 | ///
18 | /// Encrypts the given string.
19 | ///
20 | /// String to encrypt.
21 | /// Encrypted string.
22 | string EncryptString(string value);
23 | }
24 | }
--------------------------------------------------------------------------------
/ConfigCrypter/Crypters/RSACrypter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Security.Cryptography;
3 | using System.Security.Cryptography.X509Certificates;
4 | using System.Text;
5 | using DevAttic.ConfigCrypter.CertificateLoaders;
6 |
7 | namespace DevAttic.ConfigCrypter.Crypters
8 | {
9 | ///
10 | /// RSA based crypter that uses the public and private key of a certificate to encrypt and decrypt strings.
11 | ///
12 | public class RSACrypter : ICrypter
13 | {
14 | private readonly ICertificateLoader _certificateLoader;
15 | private RSA _privateKey;
16 | private RSA _publicKey;
17 |
18 | ///
19 | /// Creates an instance of the RSACrypter.
20 | ///
21 | /// A certificate loader instance.
22 | public RSACrypter(ICertificateLoader certificateLoader)
23 | {
24 | _certificateLoader = certificateLoader;
25 | InitKeys();
26 | }
27 |
28 | ///
29 | /// Encrypts the given string with the private key of the loaded certificate.
30 | ///
31 | /// String to decrypt.
32 | /// Encrypted string.
33 | public string DecryptString(string value)
34 | {
35 | var decodedBase64 = Convert.FromBase64String(value);
36 | var decryptedValue = _privateKey.Decrypt(decodedBase64, RSAEncryptionPadding.OaepSHA256);
37 |
38 | return Encoding.UTF8.GetString(decryptedValue);
39 | }
40 |
41 | ///
42 | /// Disposes the instance.
43 | ///
44 | public void Dispose()
45 | {
46 | Dispose(true);
47 | GC.SuppressFinalize(this);
48 | }
49 |
50 | ///
51 | /// Decrypts the given string with the public key of the loaded certificate.
52 | ///
53 | /// String to encrypt.
54 | /// Encrypted string.
55 | public string EncryptString(string value)
56 | {
57 | var encryptedValue = _publicKey.Encrypt(Encoding.UTF8.GetBytes(value), RSAEncryptionPadding.OaepSHA256);
58 |
59 | return Convert.ToBase64String(encryptedValue);
60 | }
61 |
62 | ///
63 | /// Disposes the underlying keys.
64 | ///
65 | /// True if called from user code, false if called by finalizer.
66 | protected virtual void Dispose(bool disposing)
67 | {
68 | if (disposing)
69 | {
70 | _privateKey?.Dispose();
71 | _publicKey?.Dispose();
72 | }
73 | }
74 |
75 | private void InitKeys()
76 | {
77 | using (var certificate = _certificateLoader.LoadCertificate())
78 | {
79 | _privateKey = certificate.GetRSAPrivateKey();
80 | _publicKey = certificate.GetRSAPublicKey();
81 | }
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/ConfigCrypter/Extensions/ConfigurationBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using DevAttic.ConfigCrypter.CertificateLoaders;
2 | using DevAttic.ConfigCrypter.ConfigProviders.Json;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.Hosting;
5 | using System;
6 |
7 | namespace DevAttic.ConfigCrypter.Extensions
8 | {
9 | public static class ConfigurationBuilderExtensions
10 | {
11 | ///
12 | /// Adds a provider to decrypt keys in the appsettings.json file.
13 | ///
14 | /// A ConfigurationBuilder instance.
15 | /// An action used to configure the configuration source.
16 | /// The current ConfigurationBuilder instance.
17 | public static IConfigurationBuilder AddEncryptedAppSettings(
18 | this IConfigurationBuilder builder, Action configAction)
19 |
20 | {
21 | if (builder is null)
22 | {
23 | throw new ArgumentNullException(nameof(builder));
24 | }
25 |
26 | var configSource = new EncryptedJsonConfigSource { Path = "appsettings.json" };
27 | configAction?.Invoke(configSource);
28 |
29 | return AddEncryptedJsonConfig(builder, configSource);
30 | }
31 |
32 | ///
33 | /// Adds a provider to decrypt keys in the appsettings.json and the corresponding environment appsettings files.
34 | ///
35 | /// A ConfigurationBuilder instance.
36 | /// An action used to configure the configuration source.
37 | /// The current host environment. Used to add environment specific appsettings files. (appsettings.Development.json, appsettings.Production.json)
38 | /// The current ConfigurationBuilder instance.
39 | public static IConfigurationBuilder AddEncryptedAppSettings(
40 | this IConfigurationBuilder builder, IHostEnvironment hostEnvironment, Action configAction)
41 | {
42 | if (builder is null)
43 | {
44 | throw new ArgumentNullException(nameof(builder));
45 | }
46 |
47 | var appSettingSource = new EncryptedJsonConfigSource { Path = "appsettings.json" };
48 | var environmentSource = new EncryptedJsonConfigSource { Path = $"appsettings.{hostEnvironment.EnvironmentName}.json", Optional = true };
49 | configAction?.Invoke(appSettingSource);
50 | configAction?.Invoke(environmentSource);
51 |
52 | AddEncryptedJsonConfig(builder, appSettingSource);
53 | AddEncryptedJsonConfig(builder, environmentSource);
54 |
55 | return builder;
56 | }
57 |
58 | ///
59 | /// Adds a provider to decrypt keys in the given json config file.
60 | ///
61 | /// A ConfigurationBuilder instance.
62 | /// An action used to configure the configuration source.
63 | /// The current ConfigurationBuilder instance.
64 | public static IConfigurationBuilder AddEncryptedJsonConfig(
65 | this IConfigurationBuilder builder, Action configAction)
66 |
67 | {
68 | if (builder is null)
69 | {
70 | throw new ArgumentNullException(nameof(builder));
71 | }
72 |
73 | var configSource = new EncryptedJsonConfigSource();
74 | configAction?.Invoke(configSource);
75 |
76 | InitializeCertificateLoader(configSource);
77 |
78 | builder.Add(configSource);
79 | return builder;
80 | }
81 |
82 | ///
83 | /// Adds a provider to decrypt keys in the given json config file by using the passed EncryptedJsonConfigSource.
84 | ///
85 | /// A ConfigurationBuilder instance.
86 | /// The fully configured config source.
87 | /// The current ConfigurationBuilder instance.
88 | public static IConfigurationBuilder AddEncryptedJsonConfig(this IConfigurationBuilder builder, EncryptedJsonConfigSource configSource)
89 | {
90 | InitializeCertificateLoader(configSource);
91 | builder.Add(configSource);
92 |
93 | return builder;
94 | }
95 |
96 | private static void InitializeCertificateLoader(EncryptedJsonConfigSource config)
97 | {
98 | if (!string.IsNullOrEmpty(config.CertificatePath))
99 | {
100 | config.CertificateLoader = new FilesystemCertificateLoader(config.CertificatePath, config.CertificatePassword);
101 | }
102 | else if (!string.IsNullOrEmpty(config.CertificateSubjectName))
103 | {
104 | config.CertificateLoader = new StoreCertificateLoader(config.CertificateSubjectName);
105 | }
106 |
107 | if (config.CertificateLoader == null)
108 | {
109 | throw new InvalidOperationException(
110 | "Either CertificatePath or CertificateSubjectName has to be provided if CertificateLoader has not been set manually.");
111 | }
112 |
113 | if (string.IsNullOrEmpty(config.Path))
114 | {
115 | throw new InvalidOperationException(
116 | "The \"Path\" property has to be set to the path of a config file.");
117 | }
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/ConfigCrypter/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ConfigFileEncrypter": {
4 | "commandName": "Project"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/ConfigCrypter/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/ConfigCrypter/logo.png
--------------------------------------------------------------------------------
/Example.WebApp/Controllers/ConfigController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.Extensions.Configuration;
3 | using Microsoft.Extensions.Options;
4 |
5 | namespace Example.WebApp.Controllers
6 | {
7 | [Route("api/[controller]")]
8 | [ApiController]
9 | public class ConfigController : ControllerBase
10 | {
11 | private readonly IConfiguration _configuration;
12 | private readonly NestedSettings _settingsSnapshot;
13 |
14 | public ConfigController(IConfiguration configuration, IOptionsSnapshot settingsSnapshot)
15 | {
16 | _configuration = configuration;
17 | _settingsSnapshot = settingsSnapshot.Value;
18 | }
19 |
20 | [HttpGet]
21 | public IActionResult GetEncryptedKey()
22 | {
23 | var content = new
24 | {
25 | FromSnapshot = _settingsSnapshot.KeyToEncrypt,
26 | FromConfiguration = _configuration["Nested:KeyToEncrypt"]
27 | };
28 |
29 | return Ok(content);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Example.WebApp/Example.WebApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Example.WebApp/NestedSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Example.WebApp
2 | {
3 | public class NestedSettings
4 | {
5 | public string KeyToEncrypt { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Example.WebApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using DevAttic.ConfigCrypter.Extensions;
3 | using Microsoft.AspNetCore.Hosting;
4 | using Microsoft.Extensions.Configuration;
5 | using Microsoft.Extensions.Hosting;
6 |
7 | namespace Example.WebApp
8 | {
9 | public class Program
10 | {
11 | public static void Main(string[] args)
12 | {
13 | CreateHostBuilder(args).Build().Run();
14 | }
15 |
16 | public static IHostBuilder CreateHostBuilder(string[] args) =>
17 | Host.CreateDefaultBuilder(args)
18 | .ConfigureWebHostDefaults(webBuilder =>
19 | {
20 | webBuilder.UseStartup();
21 | })
22 | .ConfigureAppConfiguration((hostingContext, cfg) =>
23 | {
24 | cfg.AddEncryptedAppSettings(hostingContext.HostingEnvironment, crypter =>
25 | {
26 | crypter.CertificatePath = "cert.pfx";
27 | crypter.KeysToDecrypt = new List { "Nested:KeyToEncrypt" };
28 | });
29 | });
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Example.WebApp/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:55213",
7 | "sslPort": 0
8 | }
9 | },
10 | "$schema": "http://json.schemastore.org/launchsettings.json",
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "weatherforecast",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "Example.WebApp": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "api/config",
24 | "environmentVariables": {
25 | "ASPNETCORE_ENVIRONMENT": "Development"
26 | },
27 | "applicationUrl": "http://localhost:5000"
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/Example.WebApp/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 |
7 | namespace Example.WebApp
8 | {
9 | public class Startup
10 | {
11 | public Startup(IConfiguration configuration)
12 | {
13 | Configuration = configuration;
14 | }
15 |
16 | public IConfiguration Configuration { get; }
17 |
18 | // This method gets called by the runtime. Use this method to add services to the container.
19 | public void ConfigureServices(IServiceCollection services)
20 | {
21 | services.AddControllers();
22 | services.Configure(Configuration.GetSection("Nested"));
23 | }
24 |
25 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
26 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
27 | {
28 | if (env.IsDevelopment())
29 | {
30 | app.UseDeveloperExceptionPage();
31 | }
32 |
33 | app.UseRouting();
34 |
35 | app.UseAuthorization();
36 |
37 | app.UseEndpoints(endpoints =>
38 | {
39 | endpoints.MapControllers();
40 | });
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Example.WebApp/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "Nested": {
10 | "KeyToEncrypt": "jQ42kYnVFiu2Fpod4jnaHWfDjhFbxqucGLlUi5HqSnNAVDhwwFLAwvoBQVLfwpN1TC8WBwbWynN2Ej7EO2uRA+cNZUCGxFj+LY+YGJnwT8u1a59gG4QDKgUHp53KtU/UoTWNxPiXWCCRoumvZt1vkPVzO9qYlhRCR6UAPMHQc6lu8UdakW8uCU1sitvtLOdiVGvIk3yLOFg3iJ4XfGn+f8Myvu773v9jLM6541x5Jsyv7mFyNklVBnihwsyBg3WzpjYicms2ioxWBsU+nPGZbX8fyzLTBVvEa1WxyAg8M2IbetdREEl5zwwnI0Ak1MR/lhXN9s9eks1paYloKh9lxg=="
11 | }
12 | }
--------------------------------------------------------------------------------
/Example.WebApp/appsettings.Production.json:
--------------------------------------------------------------------------------
1 | {
2 | "Nested": {
3 | "KeyToEncrypt": "lFd454PZda3cx/d5YUEKw/dt4yy0rN08niRqCIAmVU8vmPufuhkrTF1K4eyZGQAo1H8UsiQxQO+7CvOSEgODznm6hcO4TofOlyMbiBR/1xswZ0QVFtOpN6JWOtdFJcu/ROrV+T0jl/dcfB5JRLFSJvdsJRMPpwOCrkPeKJ9sQftoOt6V3M258lV5bItdwdYfqCUPS7lq1VPeT4i4HM9ZxqlNU3BxAaAm4YkyXIVpTvMwrvUU2QO7/jbASpWdRxBa1l9FHamhYbMcePinjj3v16MjptYyHl84Wc6XZBBXFm11QHTEAOcFmGEsoxrbDEB6ipGsC+xUoy6IwLtmOxpDTw=="
4 | }
5 | }
--------------------------------------------------------------------------------
/Example.WebApp/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*",
10 | "Nested": {
11 | "KeyToEncrypt": "XPTvTMWCczyrVC3ZcyH3ZC91ueBI1a2FFjrs8pwDC47HRriqdV4tUthERw2WEji3XGQ5rJAy2J0fJz9GRUdxZ8TvSp2LqZi50tns7YkL1HLNVLG+h0BBOzOV9zW4EXZKyNgsjLCsnEQplqpe9dTKcRZLg+ATT17A3C92GP9TO4eoyAPQu5iBt/VVckjM/Cd727yfSBoMlgTx4GeUI+je+aWDSqCdC9m+Cn7wROaK3hp1CVtOsnOqAzwQUKychewMw9VNbzHxBgNaSQ3IqeVFwtL1G2vvE9/+MudXqhTUSRO2Nv7bcva9xPzybdKPUy4u7OIRM8F+WRGieP01nYU79A=="
12 | }
13 | }
--------------------------------------------------------------------------------
/Example.WebApp/cert.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/Example.WebApp/cert.pfx
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 devattic
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DevAttic ConfigCrypter
2 | 
3 |
4 | ## What is DevAttic ConfigCrypter
5 | The DevAttic ConfigCrypter makes it easy for you to encrypt and decrypt config files. It consists of two parts. A command line utility that lets you encrypt keys in your JSON configuration files and a library that decrypts them on the fly in your .NET Standard applications.
6 |
7 | ## WARNING
8 | Encrypted configuration files will not make your server infrastructure unhackable. Usually the certificate to decrypt is hosted on the same server as your web application. This means an attacker could decrypt your config if your server is not secure and the attacker gains access.
9 |
10 | Additional security could be achieved by:
11 | - Storing your certficate in the windows certificate store (supported by ConfigCrypter) and restricting access to it.
12 | - Protect your certificate with a password that is embedded in your source code (currently not supported, but could be easily implemented).
13 |
14 | Also these methods would not be perfectly safe. In fact it only makes things harder for an attacker.
15 |
16 | Other possibilities would be using Environment variables or the Secret Manager tool, but in these variants the settings are completely unencrypted. For production scenarios Microsoft recommends using the Azure Key Vault, but also this is not perfectly safe and forces you to use Azure.
17 |
18 | It is definitely required to secure your server infrastructure, as this is the only way to protect your connection strings and other sensitive configuration values.
19 |
20 | ## Advantages
21 | - Lets you encrypt only certain keys in your config, so the rest of the config is still readable.
22 | - Access the encrypted values the same way you are used to in your .NET Core applications.
23 | - Lets you share config files or even check them in in your VCS without the need to remove sensitive information.
24 |
25 | ## Command line tool usage
26 | To use DevAttic ConfigCrypter you will first need to create a self signed X509 certificate that is being used for the encryption and decryption. An easy way to do this is being described in this guide: [Link](https://www.claudiobernasconi.ch/2016/04/17/creating-a-self-signed-x509-certificate-using-openssl-on-windows/)
27 |
28 | In fact you can follow every guide as long as the result is a certificate in .pfx format containing a public and private key.
29 |
30 | If you now have your certificate you need to decide what keys you want to encrypt. Lets assume we have a JSON file that looks like this:
31 |
32 | ```json
33 | {
34 | "Nested": {
35 | "KeyToEncrypt": "This will be encrypted"
36 | }
37 | }
38 | ```
39 |
40 | We want to encrypt the value with the key `Nested.KeyToEncrypt`. Notice the separation of the keys with a dot. How the key is interprated and what kind of syntax is used to define your key is up to the IConfigCrypter implementation.
41 |
42 | Currently only JSON is supported and the `JsonConfigCrypter` is using the JSONPath Syntax to define your keys ([Link](https://goessner.net/articles/JsonPath/)). Altough JSONPath usually needs a $ to define the root of the object you can leave it out here.
43 |
44 | To install the crypter [command line utility](https://www.nuget.org/packages/DevAttic.ConfigCrypter.Console/) just execute `dotnet tool install -g DevAttic.ConfigCrypter.Console`. After that you can use it with the command `config-crypter` from your command line.
45 |
46 | To encrypt our key from above we simple execute:
47 | `config-crypter encrypt -p c:\path\to\cert.pfx -f c:\path\to\config.json -k "Nested.KeyToEncrypt"`.
48 | After that a new files named `config_encrypted.json` should be created at the same folder as your original config file. This file is now the same as the original one except for the fact that the value for the passed key has been encrypted.
49 |
50 | If you want to prevent the creation of a new file you can simply pass --replace (-r) as additional paramter to the command and the original file will be replaced.
51 |
52 | To decrypt the file again you can simply execute:
53 | `config-crypter decrypt -p c:\path\to\cert.pfx -f c:\path\to\config_encrypted.json -k "Nested.KeyToEncrypt"`
54 |
55 | ## Command line arguments
56 | The following command line arguments can be passed for the encrypt and decrypt command.
57 | ```
58 | -p, --path (Group: CertLocation) Path of the certificate.
59 | -n, --name (Group: CertLocation) The subject name of the certificate (CN). This can only be used in Windows environments.
60 | -k, --key Required. The key to encrypt in the config file.
61 | -f, --file Required. The path to the config file.
62 | -r, --replace (Default: false) Replaces the original file if passed as parameter.
63 | --format (Default: Json) The format of the config file.
64 | --help Display this help screen.
65 | --version Display version information.
66 | ```
67 |
68 | ## .NET Core integration
69 | Install the nuget package [DevAttic.ConfigCrypter](https://www.nuget.org/packages/DevAttic.ConfigCrypter/)
70 |
71 | To use your encrypted configuration file in your .NET Core applications, DevAttic ConfigCrypter provides convenient extension methods to register the needed configuration providers.
72 |
73 | There are two extension methods that can be used to integrate encrypted configuration files.
74 |
75 | - `AddEncryptedAppSettings`: Adds a configuration provider that decrypts certain keys of the appsettings.json file.
76 | - `AddEncryptedJsonConfig`: Adds a configuration provider that decrypts certain keys of a given JSON configuration file.
77 |
78 | ## ASP.NET Core example
79 | If you encrypted a key in the appsettings.json file of your ASP.NET Core web application you can use the following to enable the decryption.
80 |
81 | The easiest way to enable decryption is to modify the `CreateHostBuilder` function in your `Program.cs` file.
82 |
83 | ```csharp
84 | public static IHostBuilder CreateHostBuilder(string[] args) =>
85 | Host.CreateDefaultBuilder(args)
86 | .ConfigureWebHostDefaults(webBuilder =>
87 | {
88 | webBuilder.UseStartup();
89 | })
90 | .ConfigureAppConfiguration(cfg =>
91 | {
92 | cfg.AddEncryptedAppSettings(crypter =>
93 | {
94 | crypter.CertificatePath = "cert.pfx";
95 | crypter.KeysToDecrypt = new List { "Nested:KeyToEncrypt" };
96 | });
97 | });
98 | ```
99 |
100 | As you can see enabling decryption is as simple as adding a line of code. You just need to specify the path to the certificate and the keys that should be decrypted.
101 |
102 | If you want to also use the environment specific appsettings files (appsettings.Development.json, appsettings.Production.json) please use the overloaded method to configure as shown below:
103 |
104 | ```csharp
105 | public static IHostBuilder CreateHostBuilder(string[] args) =>
106 | Host.CreateDefaultBuilder(args)
107 | .ConfigureWebHostDefaults(webBuilder =>
108 | {
109 | webBuilder.UseStartup();
110 | })
111 | .ConfigureAppConfiguration((hostingContext, cfg) =>
112 | {
113 | cfg.AddEncryptedAppSettings(hostingContext.HostingEnvironment, crypter =>
114 | {
115 | crypter.CertificatePath = "cert.pfx";
116 | crypter.KeysToDecrypt = new List { "Nested:KeyToEncrypt" };
117 | });
118 | });
119 | ```
120 | Notice how to `HostingEnvironment` property of the `hostingContext` variable is used.
121 |
122 | Notice that nested keys have to be separated with colons in this case, because this is the default way to access nested configuration properties in .NET Core ([Link](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#configuration-keys-and-values)).
123 |
124 | ## How to generate a certificate
125 | Certificates can be generated by using Openssl.
126 | An example certificate is already in the project and the encrypted string in the example appsettings files has been encrypted with it.
127 | To generate a certificate you could use the following commands:
128 |
129 | `openssl genrsa 2048 > private.key`
130 |
131 | `openssl req -new -x509 -nodes -sha1 -days 365 -key private.key > public.cer`
132 |
133 | `openssl pkcs12 -export -in public.cer -inkey private.key -out cert.pfx`
134 |
--------------------------------------------------------------------------------
/cert.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/cert.pfx
--------------------------------------------------------------------------------
/devattic_configcrypter-normal256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devattic/ConfigCrypter/51bc225c4129dd75f152ecdb36e01f47455fadde/devattic_configcrypter-normal256.png
--------------------------------------------------------------------------------