Browse Source

upload start of project

pull/2/head
Kyle Spearrin 4 years ago
parent
commit
bcfbc819a3
  1. 109
      .editorconfig
  2. 3
      .gitattributes
  3. 219
      .gitignore
  4. 182
      LICENSE.txt
  5. 25
      bitwarden-crypto-agent.sln
  6. 35
      src/CryptoAgent/Controllers/MiscController.cs
  7. 84
      src/CryptoAgent/Controllers/UserKeysController.cs
  8. 18
      src/CryptoAgent/CryptoAgent.csproj
  9. 53
      src/CryptoAgent/CryptoAgentSettings.cs
  10. 9
      src/CryptoAgent/Models/IStoredItem.cs
  11. 11
      src/CryptoAgent/Models/UserKeyGetRequestModel.cs
  12. 17
      src/CryptoAgent/Models/UserKeyModel.cs
  13. 11
      src/CryptoAgent/Models/UserKeyRequestModel.cs
  14. 7
      src/CryptoAgent/Models/UserKeyResponseModel.cs
  15. 20
      src/CryptoAgent/Program.cs
  16. 28
      src/CryptoAgent/Properties/launchSettings.json
  17. 10
      src/CryptoAgent/Repositories/IApplicationDataRepository.cs
  18. 12
      src/CryptoAgent/Repositories/IRepository.cs
  19. 9
      src/CryptoAgent/Repositories/IUserKeyRepository.cs
  20. 26
      src/CryptoAgent/Repositories/JsonFile/ApplicationDataRepository.cs
  21. 49
      src/CryptoAgent/Repositories/JsonFile/Repository.cs
  22. 37
      src/CryptoAgent/Repositories/JsonFile/UserKeyRepository.cs
  23. 54
      src/CryptoAgent/Services/AzureKeyVaultCertificateProviderService.cs
  24. 90
      src/CryptoAgent/Services/AzureKeyVaultRsaKeyService.cs
  25. 32
      src/CryptoAgent/Services/AzureStorageCertificateProviderService.cs
  26. 99
      src/CryptoAgent/Services/CryptoFunctionService.cs
  27. 162
      src/CryptoAgent/Services/CryptoService.cs
  28. 22
      src/CryptoAgent/Services/FilesystemCertificateProviderService.cs
  29. 10
      src/CryptoAgent/Services/ICertificateProviderService.cs
  30. 20
      src/CryptoAgent/Services/ICryptoFunctionService.cs
  31. 19
      src/CryptoAgent/Services/ICryptoService.cs
  32. 13
      src/CryptoAgent/Services/IRsaKeyService.cs
  33. 81
      src/CryptoAgent/Services/LocalCertificateRsaKeyService.cs
  34. 38
      src/CryptoAgent/Services/StoreCertificateProviderService.cs
  35. 104
      src/CryptoAgent/Startup.cs
  36. 9
      src/CryptoAgent/appsettings.Development.json
  37. 15
      src/CryptoAgent/appsettings.json

109
.editorconfig

@ -0,0 +1,109 @@ @@ -0,0 +1,109 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Don't use tabs for indentation.
[*]
indent_style = space
# (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# JSON files
[*.json]
indent_size = 2
# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
# Prefix private members with underscore
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.prefix_underscore.capitalization = camel_case
dotnet_naming_style.prefix_underscore.required_prefix = _
# Async methods should have "Async" suffix
dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
dotnet_naming_rule.async_methods_end_in_async.severity = suggestion
dotnet_naming_symbols.any_async_methods.applicable_kinds = method
dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
dotnet_naming_symbols.any_async_methods.required_modifiers = async
dotnet_naming_style.end_in_async.required_prefix =
dotnet_naming_style.end_in_async.required_suffix = Async
dotnet_naming_style.end_in_async.capitalization = pascal_case
dotnet_naming_style.end_in_async.word_separator =
# CSharp code style settings:
[*.cs]
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Prefer method-like constructs to have a expression-body
csharp_style_expression_bodied_methods = true:none
csharp_style_expression_bodied_constructors = true:none
csharp_style_expression_bodied_operators = true:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
# All files
[*]
guidelines = 120

3
.gitattributes vendored

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
*.sh eol=lf
.dockerignore eol=lf
dockerfile eol=lf

219
.gitignore vendored

@ -0,0 +1,219 @@ @@ -0,0 +1,219 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Visual Studo 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
PublishProfiles/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
*.[Cc]ache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Other
project.lock.json
*.jfm
mail_dist/
*.refactorlog
*.scmp
src/Core/Properties/launchSettings.json
*.override.env
**/*.DS_Store
src/Admin/wwwroot/lib
src/Admin/wwwroot/css
.vscode/*
**/.vscode/*
bitwarden_license/src/Portal/wwwroot/lib
bitwarden_license/src/Portal/wwwroot/css
bitwarden_license/src/Sso/wwwroot/lib
bitwarden_license/src/Sso/wwwroot/css
.github/test/build.secrets
**/CoverageOutput/
.idea/*
**/**.swp

182
LICENSE.txt

@ -0,0 +1,182 @@ @@ -0,0 +1,182 @@
BITWARDEN LICENSE AGREEMENT
Version 1, 4 September 2020
PLEASE CAREFULLY READ THIS BITWARDEN LICENSE AGREEMENT ("AGREEMENT"). THIS
AGREEMENT CONSTITUTES A LEGALLY BINDING AGREEMENT BETWEEN YOU AND BITWARDEN,
INC. ("BITWARDEN") AND GOVERNS YOUR USE OF THE COMMERCIAL MODULES (DEFINED
BELOW). BY COPYING OR USING THE COMMERCIAL MODULES, YOU AGREE TO THIS AGREEMENT.
IF YOU DO NOT AGREE WITH THIS AGREEMENT, YOU MAY NOT COPY OR USE THE COMMERCIAL
MODULES. IF YOU ARE COPYING OR USING THE COMMERCIAL MODULES ON BEHALF OF A LEGAL
ENTITY, YOU REPRESENT AND WARRANT THAT YOU HAVE AUTHORITY TO AGREE TO THIS
AGREEMENT ON BEHALF OF SUCH ENTITY. IF YOU DO NOT HAVE SUCH AUTHORITY, DO NOT
COPY OR USE THE COMMERCIAL MODULES IN ANY MANNER.
This Agreement is entered into by and between Bitwarden and you, or the legal
entity on behalf of whom you are acting (as applicable, "You" or "Your").
1. DEFINITIONS
"Bitwarden Software" means the Bitwarden server software, libraries, and
Commercial Modules.
"Commercial Modules" means the modules designed to work with and enhance the
Bitwarden Software to which this Agreement is linked, referenced, or appended.
2. LICENSES, RESTRICTIONS AND THIRD PARTY CODE
2.1 Commercial Module License. Subject to Your compliance with this Agreement,
Bitwarden hereby grants to You a limited, non-exclusive, non-transferable,
royalty-free license to use the Commercial Modules for the sole purposes of
internal development and internal testing, and only in a non-production
environment.
2.2 Reservation of Rights. As between Bitwarden and You, Bitwarden owns all
right, title and interest in and to the Bitwarden Software, and except as
expressly set forth in Sections 2.1, no other license to the Bitwarden Software
is granted to You under this Agreement, by implication, estoppel, or otherwise.
2.3 Restrictions. You agree not to: (i) except as expressly permitted in
Section 2.1, sell, rent, lease, distribute, sublicense, loan or otherwise
transfer the Commercial Modules to any third party; (ii) alter or remove any
trademarks, service mark, and logo included with the Commercial Modules, or
(iii) use the Commercial Modules to create a competing product or service.
Bitwarden is not obligated to provide maintenance and support services for the
Bitwarden Software licensed under this Agreement.
2.4 Third Party Software. The Commercial Modules may contain or be provided
with third party open source libraries, components, utilities and other open
source software (collectively, "Open Source Software"). Notwithstanding anything
to the contrary herein, use of the Open Source Software will be subject to the
license terms and conditions applicable to such Open Source Software. To the
extent any condition of this Agreement conflicts with any license to the Open
Source Software, the Open Source Software license will govern with respect to
such Open Source Software only.
2.5 This Agreement does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the notice
requirements in Section 2.3), and use of any Bitwarden trademarks must comply with
Bitwarden Trademark Guidelines
<https://github.com/bitwarden/server/blob/master/TRADEMARK_GUIDELINES.md>.
3. TERMINATION
3.1 Termination. This Agreement will automatically terminate upon notice from
Bitwarden, which notice may be by email or posting in the location where the
Commercial Modules are made available.
3.2 Effect of Termination. Upon any termination of this Agreement, for any
reason, You will promptly cease use of the Commercial Modules and destroy any
copies thereof. For the avoidance of doubt, termination of this Agreement will
not affect Your right to Bitwarden Software, other than the Commercial Modules,
made available pursuant to an Open Source Software license.
3.3 Survival. Sections 1, 2.2 -2.4, 3.2, 3.3, 4, and 5 will survive any
termination of this Agreement.
4. DISCLAIMER AND LIMITATION OF LIABILITY
4.1 Disclaimer of Warranties. TO THE MAXIMUM EXTENT PERMITTED UNDER APPLICABLE
LAW, THE BITWARDEN SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED REGARDING OR RELATING TO THE BITWARDEN SOFTWARE, INCLUDING
ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
TITLE, AND NON-INFRINGEMENT. FURTHER, BITWARDEN DOES NOT WARRANT RESULTS OF USE
OR THAT THE BITWARDEN SOFTWARE WILL BE ERROR FREE OR THAT THE USE OF THE
BITWARDEN SOFTWARE WILL BE UNINTERRUPTED.
4.2 Limitation of Liability. IN NO EVENT WILL BITWARDEN OR ITS LICENSORS BE
LIABLE TO YOU OR ANY THIRD PARTY UNDER THIS AGREEMENT FOR (I) ANY AMOUNTS IN
EXCESS OF US $25 OR (II) FOR ANY SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES OF
ANY KIND, INCLUDING FOR ANY LOSS OF PROFITS, LOSS OF USE, BUSINESS INTERRUPTION,
LOSS OF DATA, COST OF SUBSTITUTE GOODS OR SERVICES, WHETHER ALLEGED AS A BREACH
OF CONTRACT OR TORTIOUS CONDUCT, INCLUDING NEGLIGENCE, EVEN IF BITWARDEN HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
5. MISCELLANEOUS
5.1 Assignment. You may not assign or otherwise transfer this Agreement or any
rights or obligations hereunder, in whole or in part, whether by operation of
law or otherwise, to any third party without Bitwarden's prior written consent.
Any purported transfer, assignment or delegation without such prior written
consent will be null and void and of no force or effect. Bitwarden may assign
this Agreement to any successor to its business or assets to which this
Agreement relates, whether by merger, sale of assets, sale of stock,
reorganization or otherwise. Subject to this Section 5.1, this Agreement will be
binding upon and inure to the benefit of the parties hereto, and their
respective successors and permitted assigns.
5.2 Entire Agreement; Modification; Waiver. This Agreement represents the
entire agreement between the parties, and supersedes all prior agreements and
understandings, written or oral, with respect to the matters covered by this
Agreement, and is not intended to confer upon any third party any rights or
remedies hereunder. You acknowledge that You have not entered in this Agreement
based on any representations other than those contained herein. No modification
of or amendment to this Agreement, nor any waiver of any rights under this
Agreement, will be effective unless in writing and signed by both parties. The
waiver of one breach or default or any delay in exercising any rights will not
constitute a waiver of any subsequent breach or default.
5.3 Governing Law. This Agreement will in all respects be governed by the laws
of the State of California without reference to its principles of conflicts of
laws. The parties hereby agree that all disputes arising out of this Agreement
will be subject to the exclusive jurisdiction of and venue in the federal and
state courts within Los Angeles County, California. You hereby consent to the
personal and exclusive jurisdiction and venue of these courts. The parties
hereby disclaim and exclude the application hereto of the United Nations
Convention on Contracts for the International Sale of Goods.
5.4 Severability. If any provision of this Agreement is held invalid or
unenforceable under applicable law by a court of competent jurisdiction, it will
be replaced with the valid provision that most closely reflects the intent of
the parties and the remaining provisions of the Agreement will remain in full
force and effect.
5.5 Relationship of the Parties. Nothing in this Agreement is to be construed
as creating an agency, partnership, or joint venture relationship between the
parties hereto. Neither party will have any right or authority to assume or
create any obligations or to make any representations or warranties on behalf of
any other party, whether express or implied, or to bind the other party in any
respect whatsoever.
5.6 Notices. All notices permitted or required under this Agreement will be in
writing and will be deemed to have been given when delivered in person
(including by overnight courier), or three (3) business days after being mailed
by first class, registered or certified mail, postage prepaid, to the address of
the party specified in this Agreement or such other address as either party may
specify in writing.
5.7 U.S. Government Restricted Rights. If Commercial Modules is being licensed
by the U.S. Government, the Commercial Modules is deemed to be "commercial
computer software" and "commercial computer documentation" developed exclusively
at private expense, and (a) if acquired by or on behalf of a civilian agency,
will be subject solely to the terms of this computer software license as
specified in 48 C.F.R. 12.212 of the Federal Acquisition Regulations and its
successors; and (b) if acquired by or on behalf of units of the Department of
Defense ("DOD") will be subject to the terms of this commercial computer
software license as specified in 48 C.F.R. 227.7202-2, DOD FAR Supplement and
its successors.
5.8 Injunctive Relief. A breach or threatened breach by You of Section 2 may
cause irreparable harm for which damages at law may not provide adequate relief,
and therefore Bitwarden will be entitled to seek injunctive relief in any
applicable jurisdiction without being required to post a bond.
5.9 Export Law Assurances. You understand that the Commercial Modules is
subject to export control laws and regulations. You may not download or
otherwise export or re-export the Commercial Modules or any underlying
information or technology except in full compliance with all applicable laws and
regulations, in particular, but without limitation, United States export control
laws. None of the Commercial Modules or any underlying information or technology
may be downloaded or otherwise exported or re- exported: (a) into (or to a
national or resident of) any country to which the United States has embargoed
goods; or (b) to anyone on the U.S. Treasury Department's list of specially
designated nationals or the U.S. Commerce Department's list of prohibited
countries or debarred or denied persons or entities. You hereby agree to the
foregoing and represents and warrants that You are not located in, under control
of, or a national or resident of any such country or on any such list.
5.10 Construction. The titles and section headings used in this Agreement are
for ease of reference only and will not be used in the interpretation or
construction of this Agreement. No rule of construction resolving any ambiguity
in favor of the non-drafting party will be applied hereto. The word "including",
when used herein, is illustrative rather than exclusive and means "including,
without limitation."

25
bitwarden-crypto-agent.sln

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31205.134
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CryptoAgent", "src\CryptoAgent\CryptoAgent.csproj", "{CAD440BF-93C9-4DEC-B083-99FD49B50429}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CAD440BF-93C9-4DEC-B083-99FD49B50429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CAD440BF-93C9-4DEC-B083-99FD49B50429}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CAD440BF-93C9-4DEC-B083-99FD49B50429}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CAD440BF-93C9-4DEC-B083-99FD49B50429}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {40AD45DE-FF2B-4E64-9451-F368AA7C01E9}
EndGlobalSection
EndGlobal

35
src/CryptoAgent/Controllers/MiscController.cs

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
using Bit.CryptoAgent.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Controllers
{
public class MiscController : Controller
{
private readonly IRsaKeyService _rsaKeyService;
public MiscController(
IRsaKeyService rsaKeyService)
{
_rsaKeyService = rsaKeyService;
}
[HttpGet("~/alive")]
[HttpGet("~/now")]
[AllowAnonymous]
public DateTime GetAlive()
{
return DateTime.UtcNow;
}
[HttpGet("~/public-key")]
[AllowAnonymous]
public async Task<IActionResult> GetPublicKey()
{
var key = await _rsaKeyService.GetPublicKeyAsync();
return new OkObjectResult(new { PublicKey = Convert.ToBase64String(key) });
}
}
}

84
src/CryptoAgent/Controllers/UserKeysController.cs

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
using Bit.CryptoAgent.Models;
using Bit.CryptoAgent.Repositories;
using Bit.CryptoAgent.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Controllers
{
[Route("user-keys")]
public class UserKeysController : Controller
{
private readonly ILogger<UserKeysController> _logger;
private readonly ICryptoService _cryptoService;
private readonly IUserKeyRepository _userKeyRepository;
public UserKeysController(
ILogger<UserKeysController> logger,
IUserKeyRepository userKeyRepository,
ICryptoService cryptoService)
{
_logger = logger;
_cryptoService = cryptoService;
_userKeyRepository = userKeyRepository;
}
[HttpPost("{userId}/get")]
public async Task<IActionResult> Get(Guid userId, [FromBody] UserKeyGetRequestModel model)
{
var publicKey = Convert.FromBase64String(model.PublicKey);
var user = await _userKeyRepository.ReadAsync(userId);
if (user == null)
{
return new NotFoundResult();
}
user.LastAccessDate = DateTime.UtcNow;
await _userKeyRepository.UpdateAsync(user);
var key = await _cryptoService.AesDecryptAsync(user.Key);
var encKey = await _cryptoService.RsaEncryptAsync(key, publicKey);
var response = new UserKeyResponseModel
{
Key = Convert.ToBase64String(encKey)
};
return new JsonResult(response);
}
[HttpPost("{userId}")]
public async Task<IActionResult> Post(Guid userId, [FromBody] UserKeyRequestModel model)
{
var user = await _userKeyRepository.ReadAsync(userId);
if (user != null)
{
return new BadRequestResult();
}
var key = await _cryptoService.RsaDecryptAsync(Convert.FromBase64String(model.Key));
user = new UserKeyModel
{
Id = userId,
Key = await _cryptoService.AesEncryptToB64Async(key)
};
await _userKeyRepository.CreateAsync(user);
return new OkResult();
}
[HttpPut("{userId}")]
public async Task<IActionResult> Put(Guid userId, [FromBody] UserKeyRequestModel model)
{
var user = await _userKeyRepository.ReadAsync(userId);
if (user != null)
{
return new BadRequestResult();
}
var key = await _cryptoService.RsaDecryptAsync(Convert.FromBase64String(model.Key));
user = new UserKeyModel
{
Id = userId,
Key = await _cryptoService.AesEncryptToB64Async(key)
};
await _userKeyRepository.UpdateAsync(user);
return new OkResult();
}
}
}

18
src/CryptoAgent/CryptoAgent.csproj

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>Bit.CryptoAgent</RootNamespace>
<UserSecretsId>116f49e5-0b50-4080-856f-7e812413e723</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.4.1" />
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.2.0" />
<PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.2.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.2.0" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.9.1" />
<PackageReference Include="JsonFlatFileDataStore" Version="2.2.3" />
</ItemGroup>
</Project>

53
src/CryptoAgent/CryptoAgentSettings.cs

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
namespace Bit.CryptoAgent
{
public class CryptoAgentSettings
{
public DatabaseSettings Database { get; set; }
public CertificateSettings Certificate { get; set; }
public RsaKeySettings RsaKey { get; set; }
public class CertificateSettings
{
// Filesystem
public string FilesystemPath { get; set; }
public string FilesystemPassword { get; set; }
// Local store
public string StoreThumbprint { get; set; }
// Azure blob storage
public string AzureStorageConnectionString { get; set; }
public string AzureStorageContainer { get; set; }
public string AzureStorageFileName { get; set; }
public string AzureStorageFilePassword { get; set; }
// Azure key vault
public string AzureKeyvaultUri { get; set; }
public string AzureKeyvaultCertificateName { get; set; }
public string AzureKeyvaultAdTenantId { get; set; }
public string AzureKeyvaultAdAppId { get; set; }
public string AzureKeyvaultAdSecret { get; set; }
}
public class RsaKeySettings
{
// Local certificate provider
public string Provider { get; set; }
// Azure key vault
public string AzureKeyvaultUri { get; set; }
public string AzureKeyvaultKeyName { get; set; }
public string AzureKeyvaultAdTenantId { get; set; }
public string AzureKeyvaultAdAppId { get; set; }
public string AzureKeyvaultAdSecret { get; set; }
// GCP...
// AWS...
// Hashicorp Vault...
// Other HSMs...
}
public class DatabaseSettings
{
public string JsonFilePath { get; set; }
public string SqlServerConnectionString { get; set; }
public string MySqlConnectionString { get; set; }
public string PostgreSqlConnectionString { get; set; }
}
}
}

9
src/CryptoAgent/Models/IStoredItem.cs

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
using System;
namespace Bit.CryptoAgent.Models
{
public interface IStoredItem<TId> where TId : IEquatable<TId>
{
public TId Id { get; set; }
}
}

11
src/CryptoAgent/Models/UserKeyGetRequestModel.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Bit.CryptoAgent.Models
{
public class UserKeyGetRequestModel
{
[Required]
public string PublicKey { get; set; }
}
}

17
src/CryptoAgent/Models/UserKeyModel.cs

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
using System;
namespace Bit.CryptoAgent.Models
{
public class UserKeyModel : BaseUserKeyModel, IStoredItem<Guid>
{
public Guid Id { get; set; }
}
public abstract class BaseUserKeyModel
{
public string Key { get; set; }
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
public DateTime? RevisionDate { get; set; }
public DateTime? LastAccessDate { get; set; }
}
}

11
src/CryptoAgent/Models/UserKeyRequestModel.cs

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
using Bit.CryptoAgent.Services;
using System;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Models
{
public class UserKeyRequestModel
{
public string Key { get; set; }
}
}

7
src/CryptoAgent/Models/UserKeyResponseModel.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
namespace Bit.CryptoAgent.Models
{
public class UserKeyResponseModel
{
public string Key { get; set; }
}
}

20
src/CryptoAgent/Program.cs

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace Bit.CryptoAgent
{
public class Program
{
public static void Main(string[] args)
{
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build()
.Run();
}
}
}

28
src/CryptoAgent/Properties/launchSettings.json

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:10253",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"SsoAgent": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

10
src/CryptoAgent/Repositories/IApplicationDataRepository.cs

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Repositories
{
public interface IApplicationDataRepository
{
Task<string> ReadSymmetricKeyAsync();
Task UpdateSymmetricKeyAsync(string key);
}
}

12
src/CryptoAgent/Repositories/IRepository.cs

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Repositories
{
public interface IRepository<TItem, TId>
{
Task CreateAsync(TItem item);
Task<TItem> ReadAsync(TId id);
Task UpdateAsync(TItem item);
Task DeleteAsync(TId id);
}
}

9
src/CryptoAgent/Repositories/IUserKeyRepository.cs

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
using Bit.CryptoAgent.Models;
using System;
namespace Bit.CryptoAgent.Repositories
{
public interface IUserKeyRepository : IRepository<UserKeyModel, Guid>
{
}
}

26
src/CryptoAgent/Repositories/JsonFile/ApplicationDataRepository.cs

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
using JsonFlatFileDataStore;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Repositories.JsonFile
{
public class ApplicationDataRepository : IApplicationDataRepository
{
public ApplicationDataRepository(IDataStore dataStore)
{
DataStore = dataStore;
}
protected IDataStore DataStore { get; private set; }
public Task<string> ReadSymmetricKeyAsync()
{
var item = DataStore.GetItem("symmetricKey");
return Task.FromResult(item as string);
}
public async Task UpdateSymmetricKeyAsync(string key)
{
await DataStore.ReplaceItemAsync("symmetricKey", key, true);
}
}
}

49
src/CryptoAgent/Repositories/JsonFile/Repository.cs

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
using Bit.CryptoAgent.Models;
using JsonFlatFileDataStore;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Repositories.JsonFile
{
public class Repository<TItem, TId> : IRepository<TItem, TId>
where TId : IEquatable<TId>
where TItem : class, IStoredItem<TId>
{
public Repository(
IDataStore dataStore,
string collectionName)
{
DataStore = dataStore;
CollectionName = collectionName;
}
protected IDataStore DataStore { get; private set; }
protected string CollectionName { get; private set; }
public virtual async Task CreateAsync(TItem item)
{
var collection = DataStore.GetCollection<TItem>(CollectionName);
await collection.InsertOneAsync(item);
}
public virtual Task<TItem> ReadAsync(TId id)
{
var collection = DataStore.GetCollection<TItem>(CollectionName);
var item = collection.AsQueryable().FirstOrDefault(i => i.Id.Equals(id));
return Task.FromResult(item);
}
public virtual async Task UpdateAsync(TItem item)
{
var collection = DataStore.GetCollection<TItem>(CollectionName);
await collection.ReplaceOneAsync(item.Id, item);
}
public virtual async Task DeleteAsync(TId id)
{
var collection = DataStore.GetCollection<TItem>(CollectionName);
await collection.DeleteOneAsync(id);
}
}
}

37
src/CryptoAgent/Repositories/JsonFile/UserKeyRepository.cs

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
using Bit.CryptoAgent.Models;
using JsonFlatFileDataStore;
using System;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Repositories.JsonFile
{
public class UserKeyRepository : Repository<UserKeyModel, Guid>, IUserKeyRepository
{
public UserKeyRepository(IDataStore dataStore)
: base(dataStore, "userKey")
{ }
public override async Task CreateAsync(UserKeyModel item)
{
var collection = DataStore.GetCollection<JsonUserKeyModel>(CollectionName);
await collection.InsertOneAsync(new JsonUserKeyModel(item));
}
// New model is required since JsonFlatFileDataStore doesn't handle Guid id types
public class JsonUserKeyModel : BaseUserKeyModel
{
public JsonUserKeyModel() { }
public JsonUserKeyModel(UserKeyModel model)
{
Id = model.Id.ToString();
Key = model.Key;
CreationDate = model.CreationDate;
RevisionDate = model.RevisionDate;
LastAccessDate = model.LastAccessDate;
}
public string Id { get; set; }
}
}
}

54
src/CryptoAgent/Services/AzureKeyVaultCertificateProviderService.cs

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
using Azure.Identity;
using Azure.Security.KeyVault.Certificates;
using Azure.Security.KeyVault.Secrets;
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public class AzureKeyVaultCertificateProviderService : ICertificateProviderService
{
private readonly CryptoAgentSettings _settings;
public AzureKeyVaultCertificateProviderService(CryptoAgentSettings settings)
{
_settings = settings;
}
public async Task<X509Certificate2> GetCertificateAsync()
{
var credential = new ClientSecretCredential(_settings.Certificate.AzureKeyvaultAdTenantId,
_settings.Certificate.AzureKeyvaultAdAppId, _settings.Certificate.AzureKeyvaultAdSecret);
var keyVaultUri = new Uri(_settings.Certificate.AzureKeyvaultUri);
var certificateClient = new CertificateClient(keyVaultUri, credential);
var certificateResponse = await certificateClient.GetCertificateAsync(
_settings.Certificate.AzureKeyvaultCertificateName);
var certificate = certificateResponse.Value;
if (certificate.Policy?.Exportable == true && certificate.Policy?.KeyType == CertificateKeyType.Rsa)
{
var secretName = ParseSecretName(certificate.SecretId);
var secretClient = new SecretClient(keyVaultUri, credential);
var secretResponse = await secretClient.GetSecretAsync(secretName);
var secret = secretResponse.Value;
if (string.Equals(secret.Properties.ContentType, CertificateContentType.Pkcs12.ToString(),
StringComparison.InvariantCultureIgnoreCase))
{
var pfxBytes = Convert.FromBase64String(secret.Value);
return new X509Certificate2(pfxBytes);
}
}
return null;
}
private string ParseSecretName(Uri secretId)
{
if (secretId.Segments.Length < 3)
{
throw new InvalidOperationException($@"The secret ""{secretId}"" does not contain a valid name.");
}
return secretId.Segments[2].TrimEnd('/');
}
}
}

90
src/CryptoAgent/Services/AzureKeyVaultRsaKeyService.cs

@ -0,0 +1,90 @@ @@ -0,0 +1,90 @@
using Azure.Identity;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
using System;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public class AzureKeyVaultRsaKeyService : IRsaKeyService
{
private readonly CryptoAgentSettings _settings;
private KeyVaultKey _key;
private CryptographyClient _cryptographyClient;
private ClientSecretCredential _credential;
public AzureKeyVaultRsaKeyService(
CryptoAgentSettings settings)
{
_settings = settings;
}
public async Task<byte[]> EncryptAsync(byte[] data)
{
var client = await GetCryptographyClientAsync();
var result = await client.EncryptAsync(EncryptionAlgorithm.RsaOaep, data);
return result.Ciphertext;
}
public async Task<byte[]> DecryptAsync(byte[] data)
{
var client = await GetCryptographyClientAsync();
var result = await client.DecryptAsync(EncryptionAlgorithm.RsaOaep, data);
return result.Plaintext;
}
public async Task<byte[]> SignAsync(byte[] data)
{
var client = await GetCryptographyClientAsync();
var result = await client.SignAsync(SignatureAlgorithm.RS256, data);
return result.Signature;
}
public async Task<bool> VerifyAsync(byte[] data, byte[] signature)
{
var client = await GetCryptographyClientAsync();
var result = await client.VerifyDataAsync(SignatureAlgorithm.RS256, data, signature);
return result.IsValid;
}
public async Task<byte[]> GetPublicKeyAsync()
{
var key = await GetKeyAsync();
return key.Key.ToRSA().ExportRSAPublicKey();
}
private async Task<CryptographyClient> GetCryptographyClientAsync()
{
if (_cryptographyClient == null)
{
var key = await GetKeyAsync();
var credential = GetCredential();
_cryptographyClient = new CryptographyClient(key.Id, credential);
}
return _cryptographyClient;
}
private async Task<KeyVaultKey> GetKeyAsync()
{
if (_key == null)
{
var credential = GetCredential();
var keyVaultUri = new Uri(_settings.RsaKey.AzureKeyvaultUri);
var keyClient = new KeyClient(keyVaultUri, credential);
_key = await keyClient.GetKeyAsync(_settings.RsaKey.AzureKeyvaultKeyName);
}
return _key;
}
private ClientSecretCredential GetCredential()
{
if (_credential == null)
{
_credential = new ClientSecretCredential(_settings.RsaKey.AzureKeyvaultAdTenantId,
_settings.RsaKey.AzureKeyvaultAdAppId, _settings.RsaKey.AzureKeyvaultAdSecret);
}
return _credential;
}
}
}

32
src/CryptoAgent/Services/AzureStorageCertificateProviderService.cs

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
using Azure.Storage.Blobs;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public class AzureStorageCertificateProviderService : ICertificateProviderService
{
private readonly CryptoAgentSettings _settings;
public AzureStorageCertificateProviderService(CryptoAgentSettings settings)
{
_settings = settings;
}
public async Task<X509Certificate2> GetCertificateAsync()
{
var container = new BlobContainerClient(_settings.Certificate.AzureStorageConnectionString,
_settings.Certificate.AzureStorageContainer);
await container.CreateIfNotExistsAsync();
var blobClient = container.GetBlobClient(_settings.Certificate.AzureStorageFileName);
if (await blobClient.ExistsAsync())
{
using var stream = new MemoryStream();
await blobClient.DownloadToAsync(stream);
return new X509Certificate2(stream.ToArray(), _settings.Certificate.AzureStorageFilePassword);
}
return null;
}
}
}

99
src/CryptoAgent/Services/CryptoFunctionService.cs

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
using System;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public class CryptoFunctionService : ICryptoFunctionService
{
public async Task<byte[]> AesGcmEncryptAsync(byte[] data, byte[] key)
{
using var aes = new AesGcm(key);
var iv = await GetRandomBytesAsync(AesGcm.NonceByteSizes.MaxSize);
var tag = new byte[AesGcm.TagByteSizes.MaxSize];
var encData = new byte[data.Length];
aes.Encrypt(iv, data, encData, tag);
var encResult = new byte[encData.Length + tag.Length + iv.Length];
encData.CopyTo(encResult, 0);
tag.CopyTo(encResult, encData.Length);
iv.CopyTo(encResult, encData.Length + tag.Length);
return encResult;
}
public Task<byte[]> AesGcmDecryptAsync(byte[] data, byte[] key)
{
using var aes = new AesGcm(key);
var endDataLength = data.Length - AesGcm.TagByteSizes.MaxSize - AesGcm.NonceByteSizes.MaxSize;
var encData = new ArraySegment<byte>(data, 0, endDataLength);
var tag = new ArraySegment<byte>(data, endDataLength, AesGcm.TagByteSizes.MaxSize);
var iv = new ArraySegment<byte>(data, endDataLength + AesGcm.TagByteSizes.MaxSize, AesGcm.NonceByteSizes.MaxSize);
var plainData = new byte[endDataLength];
aes.Decrypt(iv, encData, tag, plainData);
return Task.FromResult(plainData);
}
public Task<byte[]> RsaEncryptAsync(byte[] data, byte[] publicKey)
{
using var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(publicKey, out var bytesRead);
return RsaEncryptAsync(data, rsa);
}
public Task<byte[]> RsaEncryptAsync(byte[] data, RSA publicKey)
{
var encData = publicKey.Encrypt(data, RSAEncryptionPadding.OaepSHA1);
return Task.FromResult(encData);
}
public Task<byte[]> RsaDecryptAsync(byte[] data, byte[] privateKey)
{
using var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(privateKey, out var bytesRead);
return RsaDecryptAsync(data, rsa);
}
public Task<byte[]> RsaDecryptAsync(byte[] data, RSA privateKey)
{
var encData = privateKey.Decrypt(data, RSAEncryptionPadding.OaepSHA1);
return Task.FromResult(encData);
}
public Task<bool> RsaVerifyAsync(byte[] data, byte[] signature, byte[] publicKey)
{
using var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(publicKey, out var bytesRead);
return RsaVerifyAsync(data, signature, rsa);
}
public Task<bool> RsaVerifyAsync(byte[] data, byte[] signature, RSA publicKey)
{
var valid = publicKey.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Task.FromResult(valid);
}
public Task<byte[]> RsaSignAsync(byte[] data, byte[] privateKey)
{
using var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(privateKey, out var bytesRead);
return RsaSignAsync(data, rsa);
}
public Task<byte[]> RsaSignAsync(byte[] data, RSA privateKey)
{
var signature = privateKey.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Task.FromResult(signature);
}
public Task<byte[]> GetRandomBytesAsync(int size)
{
var bytes = new byte[size];
RandomNumberGenerator.Fill(bytes);
return Task.FromResult(bytes);
}
}
}

162
src/CryptoAgent/Services/CryptoService.cs

@ -0,0 +1,162 @@ @@ -0,0 +1,162 @@
using Bit.CryptoAgent.Repositories;
using System;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public class CryptoService : ICryptoService
{
private readonly IRsaKeyService _rsaKeyService;
private readonly ICryptoFunctionService _cryptoFunctionService;
private readonly IApplicationDataRepository _applicationDataRepository;
private byte[] _symmetricKey;
public CryptoService(
IRsaKeyService rsaKeyService,
ICryptoFunctionService cryptoFunctionService,
IApplicationDataRepository applicationDataRepository)
{
_rsaKeyService = rsaKeyService;
_cryptoFunctionService = cryptoFunctionService;
_applicationDataRepository = applicationDataRepository;
}
// AES Decrypt
public async Task<byte[]> AesDecryptAsync(byte[] data, byte[] key = null)
{
if (data == null)
{
return null;
}
if (key == null)
{
key = await GetSymmetricKeyAsync();
}
var plainData = await _cryptoFunctionService.AesGcmDecryptAsync(data, key);
return plainData;
}
public async Task<string> AesDecryptToB64Async(byte[] data, byte[] key = null)
{
var plainData = await AesDecryptAsync(data, key);
return Convert.ToBase64String(plainData);
}
public async Task<byte[]> AesDecryptAsync(string b64Data, byte[] key = null)
{
var data = Convert.FromBase64String(b64Data);
var plainData = await AesDecryptAsync(data, key);
return plainData;
}
public async Task<string> AesDecryptToB64Async(string b64Data, byte[] key = null)
{
var data = Convert.FromBase64String(b64Data);
var plainData = await AesDecryptToB64Async(data, key);
return plainData;
}
// AES Encrypt
public async Task<byte[]> AesEncryptAsync(byte[] data, byte[] key = null)
{
if (data == null)
{
return null;
}
if (key == null)
{
key = await GetSymmetricKeyAsync();
}
var encData = await _cryptoFunctionService.AesGcmEncryptAsync(data, key);
return encData;
}
public async Task<byte[]> AesEncryptAsync(string b64Data, byte[] key = null)
{
var data = Convert.FromBase64String(b64Data);
var encData = await AesEncryptAsync(data, key);
return encData;
}
public async Task<string> AesEncryptToB64Async(byte[] data, byte[] key = null)
{
var encData = await AesEncryptAsync(data, key);
return Convert.ToBase64String(encData);
}
public async Task<string> AesEncryptToB64Async(string b64Data, byte[] key = null)
{
var encData = await AesEncryptAsync(b64Data, key);
return Convert.ToBase64String(encData);
}
// RSA Encrypt
public async Task<byte[]> RsaEncryptAsync(byte[] data, byte[] publicKey = null)
{
if (data == null)
{
return null;
}
if (publicKey == null)
{
return await _rsaKeyService.EncryptAsync(data);
}
var encData = await _cryptoFunctionService.RsaEncryptAsync(data, publicKey);
return encData;
}
// RSA Decrypt
public async Task<byte[]> RsaDecryptAsync(byte[] data)
{
if (data == null)
{
return null;
}
return await _rsaKeyService.DecryptAsync(data);
}
// RSA Verify
public async Task<bool> RsaVerifyAsync(byte[] data, byte[] signature, byte[] publicKey = null)
{
if (data == null || signature == null)
{
return false;
}
if (publicKey == null)
{
return await _rsaKeyService.VerifyAsync(data, signature);
}
return await _cryptoFunctionService.RsaVerifyAsync(data, signature, publicKey);
}
// Helpers
private async Task<byte[]> GetSymmetricKeyAsync()
{
if (_symmetricKey == null)
{
var encKey = await _applicationDataRepository.ReadSymmetricKeyAsync();
if (encKey != null)
{
var decodedEncKey = Convert.FromBase64String(encKey);
_symmetricKey = await RsaDecryptAsync(decodedEncKey);
}
else
{
_symmetricKey = await _cryptoFunctionService.GetRandomBytesAsync(32);
var decodedEncKey = await RsaEncryptAsync(_symmetricKey);
encKey = Convert.ToBase64String(decodedEncKey);
await _applicationDataRepository.UpdateSymmetricKeyAsync(encKey);
}
}
return _symmetricKey;
}
}
}

22
src/CryptoAgent/Services/FilesystemCertificateProviderService.cs

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public class FilesystemCertificateProviderService : ICertificateProviderService
{
private readonly CryptoAgentSettings _settings;
public FilesystemCertificateProviderService(CryptoAgentSettings settings)
{
_settings = settings;
}
public Task<X509Certificate2> GetCertificateAsync()
{
var cert = new X509Certificate2(_settings.Certificate.FilesystemPath,
_settings.Certificate.FilesystemPassword);
return Task.FromResult(cert);
}
}
}

10
src/CryptoAgent/Services/ICertificateProviderService.cs

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public interface ICertificateProviderService
{
Task<X509Certificate2> GetCertificateAsync();
}
}

20
src/CryptoAgent/Services/ICryptoFunctionService.cs

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public interface ICryptoFunctionService
{
Task<byte[]> AesGcmDecryptAsync(byte[] data, byte[] key);
Task<byte[]> AesGcmEncryptAsync(byte[] data, byte[] key);
Task<byte[]> RsaDecryptAsync(byte[] data, byte[] privateKey);
Task<byte[]> RsaDecryptAsync(byte[] data, RSA privateKey);
Task<byte[]> RsaEncryptAsync(byte[] data, byte[] publicKey);
Task<byte[]> RsaEncryptAsync(byte[] data, RSA publicKey);
Task<byte[]> RsaSignAsync(byte[] data, byte[] privateKey);
Task<byte[]> RsaSignAsync(byte[] data, RSA privateKey);
Task<bool> RsaVerifyAsync(byte[] data, byte[] signature, byte[] publicKey);
Task<bool> RsaVerifyAsync(byte[] data, byte[] signature, RSA publicKey);
Task<byte[]> GetRandomBytesAsync(int size);
}
}

19
src/CryptoAgent/Services/ICryptoService.cs

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public interface ICryptoService
{
Task<byte[]> AesDecryptAsync(byte[] data, byte[] key = null);
Task<byte[]> AesDecryptAsync(string b64Data, byte[] key = null);
Task<string> AesDecryptToB64Async(byte[] data, byte[] key = null);
Task<string> AesDecryptToB64Async(string b64Data, byte[] key = null);
Task<byte[]> AesEncryptAsync(byte[] data, byte[] key = null);
Task<byte[]> AesEncryptAsync(string b64Data, byte[] key = null);
Task<string> AesEncryptToB64Async(byte[] data, byte[] key = null);
Task<string> AesEncryptToB64Async(string b64Data, byte[] key = null);
Task<byte[]> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
Task<byte[]> RsaDecryptAsync(byte[] data);
Task<bool> RsaVerifyAsync(byte[] data, byte[] signature, byte[] publicKey = null);
}
}

13
src/CryptoAgent/Services/IRsaKeyService.cs

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public interface IRsaKeyService
{
Task<byte[]> DecryptAsync(byte[] data);
Task<byte[]> EncryptAsync(byte[] data);
Task<byte[]> SignAsync(byte[] data);
Task<bool> VerifyAsync(byte[] data, byte[] signature);
Task<byte[]> GetPublicKeyAsync();
}
}

81
src/CryptoAgent/Services/LocalCertificateRsaKeyService.cs

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public class LocalCertificateRsaKeyService : IRsaKeyService
{
private readonly ICertificateProviderService _certificateProviderService;
private readonly ICryptoFunctionService _cryptoFunctionService;
private X509Certificate2 _certificate;
public LocalCertificateRsaKeyService(
ICertificateProviderService certificateProviderService,
ICryptoFunctionService cryptoFunctionService)
{
_certificateProviderService = certificateProviderService;
_cryptoFunctionService = cryptoFunctionService;
}
public async Task<byte[]> EncryptAsync(byte[] data)
{
if (data == null)
{
return null;
}
var encData = await _cryptoFunctionService.RsaEncryptAsync(data, await GetPublicKeyAsync());
return encData;
}
public async Task<byte[]> DecryptAsync(byte[] data)
{
if (data == null)
{
return null;
}
var plainData = await _cryptoFunctionService.RsaDecryptAsync(data, await GetPrivateKeyAsync());
return plainData;
}
public async Task<byte[]> SignAsync(byte[] data)
{
if (data == null)
{
return null;
}
return await _cryptoFunctionService.RsaSignAsync(data, await GetPrivateKeyAsync());
}
public async Task<bool> VerifyAsync(byte[] data, byte[] signature)
{
if (data == null || signature == null)
{
return false;
}
return await _cryptoFunctionService.RsaVerifyAsync(data, signature, await GetPublicKeyAsync());
}
public async Task<byte[]> GetPublicKeyAsync()
{
var certificate = await GetCertificateAsync();
return certificate.GetRSAPublicKey().ExportRSAPublicKey();
}
private async Task<X509Certificate2> GetCertificateAsync()
{
if (_certificate == null)
{
_certificate = await _certificateProviderService.GetCertificateAsync();
}
return _certificate;
}
private async Task<System.Security.Cryptography.RSA> GetPrivateKeyAsync()
{
var certificate = await GetCertificateAsync();
return certificate.GetRSAPrivateKey();
}
}
}

38
src/CryptoAgent/Services/StoreCertificateProviderService.cs

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Bit.CryptoAgent.Services
{
public class StoreCertificateProviderService : ICertificateProviderService
{
private readonly CryptoAgentSettings _settings;
public StoreCertificateProviderService(CryptoAgentSettings settings)
{
_settings = settings;
}
public Task<X509Certificate2> GetCertificateAsync()
{
X509Certificate2 cert = null;
var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint,
CleanThumbprint(_settings.Certificate.StoreThumbprint), false);
if (certCollection.Count > 0)
{
cert = certCollection[0];
}
certStore.Close();
return Task.FromResult(cert);
}
public static string CleanThumbprint(string thumbprint)
{
// Clean possible garbage characters from thumbprint copy/paste
// ref http://stackoverflow.com/questions/8448147/problems-with-x509store-certificates-find-findbythumbprint
return Regex.Replace(thumbprint, @"[^\da-fA-F]", string.Empty).ToUpper();
}
}
}

104
src/CryptoAgent/Startup.cs

@ -0,0 +1,104 @@ @@ -0,0 +1,104 @@
using Bit.CryptoAgent.Repositories;
using Bit.CryptoAgent.Services;
using JsonFlatFileDataStore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Globalization;
namespace Bit.CryptoAgent
{
public class Startup
{
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
Configuration = configuration;
Environment = env;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment Environment { get; set; }
public void ConfigureServices(IServiceCollection services)
{
var settings = new CryptoAgentSettings();
ConfigurationBinder.Bind(Configuration.GetSection("CryptoAgentSettings"), settings);
services.AddSingleton(s => settings);
var rsaKeyProvider = settings.RsaKey.Provider?.ToLowerInvariant();
if (rsaKeyProvider == "certificate")
{
services.AddSingleton<IRsaKeyService, LocalCertificateRsaKeyService>();
if (!string.IsNullOrWhiteSpace(settings.Certificate?.StoreThumbprint))
{
services.AddSingleton<ICertificateProviderService, StoreCertificateProviderService>();
}
else if (!string.IsNullOrWhiteSpace(settings.Certificate?.FilesystemPath))
{
services.AddSingleton<ICertificateProviderService, FilesystemCertificateProviderService>();
}
else if (!string.IsNullOrWhiteSpace(settings.Certificate?.AzureStorageConnectionString))
{
services.AddSingleton<ICertificateProviderService, AzureStorageCertificateProviderService>();
}
else if (!string.IsNullOrWhiteSpace(settings.Certificate?.AzureKeyvaultUri))
{
services.AddSingleton<ICertificateProviderService, AzureKeyVaultCertificateProviderService>();
}
else
{
throw new Exception("No certificate provider configured.");
}
}
else if (rsaKeyProvider == "azure")
{
if (!string.IsNullOrWhiteSpace(settings.RsaKey?.AzureKeyvaultUri))
{
services.AddSingleton<IRsaKeyService, AzureKeyVaultRsaKeyService>();
}
else
{
throw new Exception("No azure key vault configured.");
}
}
else
{
throw new Exception("Unknown rsa key provider.");
}
services.AddSingleton<ICryptoFunctionService, CryptoFunctionService>();
services.AddSingleton<ICryptoService, CryptoService>();
// JsonFlatFileDataStore
if (!string.IsNullOrWhiteSpace(settings.Database?.JsonFilePath))
{
// Assign foobar to keyProperty in order to not use incrementing Id functionality
services.AddSingleton<IDataStore>(new DataStore(settings.Database.JsonFilePath, keyProperty: "--foobar--"));
services.AddSingleton<IApplicationDataRepository, Repositories.JsonFile.ApplicationDataRepository>();
services.AddSingleton<IUserKeyRepository, Repositories.JsonFile.UserKeyRepository>();
}
else
{
throw new Exception("No database configured.");
}
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
}

9
src/CryptoAgent/appsettings.Development.json

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

15
src/CryptoAgent/appsettings.json

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"cryptoAgentSettings": {
"rsaKey": {
"provider": "certificate"
}
}
}
Loading…
Cancel
Save