diff --git a/.azure/pipelines/ci-official.yml b/.azure/pipelines/ci-official.yml
new file mode 100644
index 0000000000..e23c1fa545
--- /dev/null
+++ b/.azure/pipelines/ci-official.yml
@@ -0,0 +1,225 @@
+trigger:
+ branches:
+ include:
+ - master
+ - release/*
+ exclude:
+ - release/2.0
+
+phases:
+- phase: Windows
+ queue:
+ name: DotNetCore-Windows
+ timeoutInMinutes: 120
+ matrix:
+ Release:
+ BuildConfiguration: Release
+ variables:
+ CI: true
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+
+ # This variable is required by MicroBuildSigningPlugin to determine permissions for codesigning.
+ TeamName: AspNetCore
+
+ # SignType = { test, real }
+ # This is prefixed underscore because variables automatically become environment variables (and therefore MSBuild properties),
+ # and this one was causing issues in MSBuild projects which use the $(SignType) MSbuild prop.
+ _SignType: real
+
+ steps:
+ - task: NodeTool@0
+ displayName: Install Node 10.x
+ inputs:
+ versionSpec: 10.x
+ - task: MicroBuildSigningPlugin@1
+ displayName: Install MicroBuild plugin
+ condition: and(succeeded(), in(variables['_SignType'], 'test', 'real'))
+ inputs:
+ signType: $(_SignType)
+ zipSources: false
+ # TODO: configure build.cmd to build both x64 and x86 in one invocation
+ - script: build.cmd -ci /p:SkipTests=true /p:Configuration=$(BuildConfiguration) /p:BuildNumber=$(Build.BuildNumber) /t:Build /t:BuildSharedFx /p:SharedFxRID=win-x64 /t:BuildFallbackArchive
+ displayName: Build NuGet packages and win-x64 runtime
+ - script: build.cmd -ci /p:SkipTests=true /p:Configuration=$(BuildConfiguration) /p:BuildNumber=$(Build.BuildNumber) /t:BuildSharedFx /p:SharedFxRID=win-x86
+ displayName: Build win-x86 runtime
+ - powershell: >
+ src/Installers/Windows/clone_and_build_ancm.ps1
+ -GitCredential '$(dn-bot-devdiv-build-rw-code-rw)'
+ -Config $(BuildConfiguration)
+ -BuildNumber $(Build.BuildNumber)
+ -SignType $(_SignType)
+ displayName: Build ANCM installers
+ # TODO: configure harvesting to run as a part of build.cmd
+ - powershell: >
+ src/Installers/Windows/build.ps1
+ -x64 artifacts/runtime/aspnetcore-runtime-internal-3.0.0-alpha1-$(Build.BuildNumber)-win-x64.zip
+ -x86 artifacts/runtime/aspnetcore-runtime-internal-3.0.0-alpha1-$(Build.BuildNumber)-win-x86.zip
+ -Config $(BuildConfiguration)
+ -BuildNumber $(Build.BuildNumber)
+ -SignType $(_SignType)
+ displayName: Build Windows installers
+ - task: PublishTestResults@2
+ displayName: Publish test results
+ condition: always()
+ inputs:
+ testRunner: vstest
+ testResultsFiles: 'artifacts/logs/**/*.trx'
+ - task: PublishBuildArtifacts@1
+ displayName: Upload artifacts
+ condition: eq(variables['system.pullrequest.isfork'], false)
+ inputs:
+ pathtoPublish: ./artifacts/
+ artifactName: artifacts-Windows-Release
+ artifactType: Container
+ # Detect OSS Components in use in the product. Only needs to run on one OS in the matrix.
+ - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
+ displayName: 'Component Detection'
+ inputs:
+ # This funky GUID represents the product "ASP.NET and EF Core"
+ governanceProduct: 'c641993b-8380-e811-80c3-0004ffb4789e'
+ snapshotForceEnabled: true
+ - task: MicroBuildCleanup@1
+ displayName: Cleanup MicroBuild tasks
+ condition: always()
+
+- phase: macOS
+ dependsOn: Windows
+ queue:
+ name: Hosted macOS Preview
+ matrix:
+ Release:
+ BuildConfiguration: Release
+ variables:
+ CI: true
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ steps:
+ - task: DownloadBuildArtifacts@0
+ displayName: Download artifacts
+ inputs:
+ artifactName: artifacts-Windows-Release
+ downloadPath: $(Build.SourcesDirectory)/.deps
+ itemPattern: '**/*.nupkg'
+ # Workaround https://github.com/Microsoft/vsts-tasks/issues/6739
+ - task: CopyFiles@2
+ displayName: Copy package assets to correct folder
+ inputs:
+ sourceFolder: $(Build.SourcesDirectory)/.deps/artifacts-Windows-Release
+ targetFolder: $(Build.SourcesDirectory)/.deps
+ - script: >
+ ./build.sh
+ --ci
+ /t:Prepare
+ /t:Restore
+ /t:GeneratePropsFiles
+ /t:BuildSharedFx
+ /p:SharedFxRID=osx-x64
+ /p:BuildNumber=$(Build.BuildNumber)
+ displayName: Build osx-x64 runtime
+ - task: PublishTestResults@2
+ displayName: Publish test results
+ condition: always()
+ inputs:
+ testRunner: vstest
+ testResultsFiles: 'artifacts/logs/**/*.trx'
+ - task: PublishBuildArtifacts@1
+ displayName: Upload artifacts
+ condition: eq(variables['system.pullrequest.isfork'], false)
+ inputs:
+ pathtoPublish: ./artifacts/
+ artifactName: artifacts-macOS-Release
+ artifactType: Container
+
+- phase: Linux
+ dependsOn:
+ - Windows
+ - macOS
+ queue:
+ name: DotNetCore-Linux
+ matrix:
+ Release:
+ BuildConfiguration: Release
+ variables:
+ CI: true
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ steps:
+ - task: DownloadBuildArtifacts@0
+ displayName: Download artifacts
+ inputs:
+ artifactName: artifacts-Windows-Release
+ downloadPath: $(Build.SourcesDirectory)/.deps
+ itemPattern: '**/*.nupkg'
+ - task: DownloadBuildArtifacts@0
+ displayName: Download Windows artifacts
+ inputs:
+ artifactName: artifacts-Windows-Release
+ downloadPath: $(Build.SourcesDirectory)/.r
+ itemPattern: '**/aspnetcore-runtime-*'
+ - task: DownloadBuildArtifacts@0
+ displayName: Download macOS artifacts
+ inputs:
+ artifactName: artifacts-macOS-Release
+ downloadPath: $(Build.SourcesDirectory)/.r
+ itemPattern: '**/aspnetcore-runtime-*'
+ # Workaround https://github.com/Microsoft/vsts-tasks/issues/6739
+ - task: CopyFiles@2
+ displayName: Copy package assets to correct folder
+ inputs:
+ sourceFolder: $(Build.SourcesDirectory)/.deps/artifacts-Windows-Release
+ targetFolder: $(Build.SourcesDirectory)/.deps
+ # TODO: Make the cumulative zips build in their own step
+ - task: CopyFiles@2
+ displayName: Copy package assets to correct folder
+ inputs:
+ sourceFolder: $(Build.SourcesDirectory)/.r/artifacts-Windows-Release
+ targetFolder: $(Build.SourcesDirectory)/artifacts/
+ - task: CopyFiles@2
+ displayName: Copy package assets to correct folder
+ inputs:
+ sourceFolder: $(Build.SourcesDirectory)/.r/artifacts-macOS-Release
+ targetFolder: $(Build.SourcesDirectory)/artifacts/
+ - script: >
+ ./build.sh
+ --ci
+ /t:Prepare
+ /t:Restore
+ /t:GeneratePropsFiles
+ /t:BuildSharedFx
+ /p:SharedFxRID=linux-x64
+ /p:BuildNumber=$(Build.BuildNumber)
+ displayName: Build linux-x64 runtime
+ - script: >
+ ./build.sh
+ --ci
+ /t:BuildSharedFx
+ /p:SharedFxRID=linux-arm
+ /p:BuildNumber=$(Build.BuildNumber)
+ displayName: Build linux-arm runtime
+ - script: >
+ ./dockerbuild.sh
+ alpine
+ /t:Prepare
+ /t:GeneratePropsFiles
+ /t:BuildSharedFx
+ /p:SharedFxRID=linux-musl-x64
+ /p:BuildNumber=$(Build.BuildNumber)
+ displayName: Build linux-musl-x64 runtime
+ # TODO: configure installers to run in one build.sh invocation
+ - script: >
+ ./build.sh
+ --ci
+ /t:BuildInstallers
+ /p:_SharedFxSourceDir=$(Build.SourcesDirectory)/artifacts/runtime/
+ displayName: Build linux installers
+ - task: PublishTestResults@2
+ displayName: Publish test results
+ condition: always()
+ inputs:
+ testRunner: vstest
+ testResultsFiles: 'artifacts/logs/**/*.trx'
+ - task: PublishBuildArtifacts@1
+ displayName: Upload artifacts
+ condition: eq(variables['system.pullrequest.isfork'], false)
+ inputs:
+ pathtoPublish: ./artifacts/
+ artifactName: artifacts-Linux-Release
+ artifactType: Container
diff --git a/.azure/pipelines/e2e-tests.yml b/.azure/pipelines/e2e-tests.yml
new file mode 100644
index 0000000000..fecf598d59
--- /dev/null
+++ b/.azure/pipelines/e2e-tests.yml
@@ -0,0 +1,134 @@
+trigger: none
+phases:
+- phase: Host_Windows
+ queue:
+ name: Hosted VS2017
+ parallel: 8
+ matrix:
+ Portable_Node8:
+ Test.RuntimeIdentifier: none
+ Node.Version: 8.x
+ Portable_Node10:
+ Test.RuntimeIdentifier: none
+ Node.Version: 10.x
+ SelfContainedWindows_Node8:
+ Test.RuntimeIdentifier: win-x64
+ Node.Version: 8.x
+ SelfContainedWindows_Node10:
+ Test.RuntimeIdentifier: win-x64
+ Node.Version: 10.x
+ SelfContainedLinux_Node8:
+ Test.RuntimeIdentifier: linux-x64
+ Node.Version: 8.x
+ SelfContainedLinux_Node10:
+ Test.RuntimeIdentifier: linux-x64
+ Node.Version: 10.x
+ SelfContainedMacOs_Node8:
+ Test.RuntimeIdentifier: osx-x64
+ Node.Version: 8.x
+ SelfContainedMacOs_Node10:
+ Test.RuntimeIdentifier: osx-x64
+ Node.Version: 10.x
+ steps:
+ - task: NodeTool@0
+ displayName: Install Node $(Node.Version)
+ inputs:
+ versionSpec: $(Node.Version)
+ - powershell: |
+ test/Cli.FunctionalTests/run-tests.ps1 -ci -ProdConManifestUrl $env:PRODCONMANIFESTURL -TestRuntimeIdentifier $(Test.RuntimeIdentifier) -AdditionalRestoreSources $env:ADDITIONALRESTORESOURCES
+ condition: ne(variables['PB_SkipTests'], 'true')
+ displayName: Run E2E tests
+ - task: PublishTestResults@2
+ displayName: Publish test results
+ condition: always()
+ inputs:
+ testRunner: vstest
+ testResultsFiles: 'artifacts/logs/**/*.trx'
+- phase: Host_macOS
+ queue:
+ name: Hosted macOS
+ parallel: 8
+ matrix:
+ Portable_Node8:
+ Test.RuntimeIdentifier: none
+ Node.Version: 8.x
+ Portable_Node10:
+ Test.RuntimeIdentifier: none
+ Node.Version: 10.x
+ SelfContainedWindows_Node8:
+ Test.RuntimeIdentifier: win-x64
+ Node.Version: 8.x
+ SelfContainedWindows_Node10:
+ Test.RuntimeIdentifier: win-x64
+ Node.Version: 10.x
+ SelfContainedLinux_Node8:
+ Test.RuntimeIdentifier: linux-x64
+ Node.Version: 8.x
+ SelfContainedLinux_Node10:
+ Test.RuntimeIdentifier: linux-x64
+ Node.Version: 10.x
+ SelfContainedMacOs_Node8:
+ Test.RuntimeIdentifier: osx-x64
+ Node.Version: 8.x
+ SelfContainedMacOs_Node10:
+ Test.RuntimeIdentifier: osx-x64
+ Node.Version: 10.x
+ steps:
+ - task: NodeTool@0
+ displayName: Install Node $(Node.Version)
+ inputs:
+ versionSpec: $(Node.Version)
+ - powershell: |
+ test/Cli.FunctionalTests/run-tests.ps1 -ci -ProdConManifestUrl $env:PRODCONMANIFESTURL -TestRuntimeIdentifier $(Test.RuntimeIdentifier) -AdditionalRestoreSources $env:ADDITIONALRESTORESOURCES
+ condition: ne(variables['PB_SkipTests'], 'true')
+ displayName: Run E2E tests
+ - task: PublishTestResults@2
+ displayName: Publish test results
+ condition: always()
+ inputs:
+ testRunner: vstest
+ testResultsFiles: 'artifacts/logs/**/*.trx'
+- phase: Host_Linux
+ queue:
+ name: Hosted Linux Preview
+ parallel: 8
+ matrix:
+ Portable_Node8:
+ Test.RuntimeIdentifier: none
+ Node.Version: 8.x
+ Portable_Node10:
+ Test.RuntimeIdentifier: none
+ Node.Version: 10.x
+ SelfContainedWindows_Node8:
+ Test.RuntimeIdentifier: win-x64
+ Node.Version: 8.x
+ SelfContainedWindows_Node10:
+ Test.RuntimeIdentifier: win-x64
+ Node.Version: 10.x
+ SelfContainedLinux_Node8:
+ Test.RuntimeIdentifier: linux-x64
+ Node.Version: 8.x
+ SelfContainedLinux_Node10:
+ Test.RuntimeIdentifier: linux-x64
+ Node.Version: 10.x
+ SelfContainedMacOs_Node8:
+ Test.RuntimeIdentifier: osx-x64
+ Node.Version: 8.x
+ SelfContainedMacOs_Node10:
+ Test.RuntimeIdentifier: osx-x64
+ Node.Version: 10.x
+ steps:
+ - task: NodeTool@0
+ displayName: Install Node $(Node.Version)
+ inputs:
+ versionSpec: $(Node.Version)
+ - powershell: |
+ test/Cli.FunctionalTests/run-tests.ps1 -ci -ProdConManifestUrl $env:PRODCONMANIFESTURL -TestRuntimeIdentifier $(Test.RuntimeIdentifier) -AdditionalRestoreSources $env:ADDITIONALRESTORESOURCES
+ condition: ne(variables['PB_SkipTests'], 'true')
+ displayName: Run E2E tests
+ - task: PublishTestResults@2
+ displayName: Publish test results
+ condition: always()
+ inputs:
+ testRunner: vstest
+ testResultsFiles: 'artifacts/logs/**/*.trx'
diff --git a/.azure/pipelines/fast-pr-validation.yml b/.azure/pipelines/fast-pr-validation.yml
new file mode 100644
index 0000000000..2126e161d9
--- /dev/null
+++ b/.azure/pipelines/fast-pr-validation.yml
@@ -0,0 +1,28 @@
+trigger:
+- master
+- release/*
+
+# See https://github.com/aspnet/BuildTools
+resources:
+ repositories:
+ - repository: buildtools
+ type: github
+ endpoint: DotNet-Bot GitHub Connection
+ name: aspnet/BuildTools
+ ref: refs/heads/master
+
+phases:
+- template: .vsts-pipelines/templates/project-ci.yml@buildtools
+ parameters:
+ buildArgs: "/t:CheckUniverse"
+- phase: DataProtection
+ queue: Hosted VS2017
+ steps:
+ - script: src/DataProtection/build.cmd -ci
+ displayName: Run src/DataProtection/build.cmd
+ - task: PublishTestResults@2
+ displayName: Publish test results
+ condition: always()
+ inputs:
+ testRunner: vstest
+ testResultsFiles: 'src/DataProtection/artifacts/logs/**/*.trx'
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000..4eb7559fce
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,29 @@
+; EditorConfig to support per-solution formatting.
+; Use the EditorConfig VS add-in to make this work.
+; http://editorconfig.org/
+
+; This is the default for the codeline.
+root = true
+
+[*]
+indent_style = space
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{cs}]
+indent_size = 4
+dotnet_sort_system_directives_first = true:warning
+
+[*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}]
+indent_size = 2
+
+[*.json]
+indent_size = 2
+
+[*.{ps1,psm1}]
+indent_size = 4
+
+[*.sh]
+indent_size = 4
+end_of_line = lf
diff --git a/.gitattributes b/.gitattributes
index bdaa5ba982..ebc27b82d2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,50 +1,68 @@
-*.doc diff=astextplain
-*.DOC diff=astextplain
-*.docx diff=astextplain
-*.DOCX diff=astextplain
-*.dot diff=astextplain
-*.DOT diff=astextplain
-*.pdf diff=astextplain
-*.PDF diff=astextplain
-*.rtf diff=astextplain
-*.RTF diff=astextplain
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
-*.jpg binary
-*.png binary
-*.gif binary
+###############################################################################
+# Make sh files under the build directory always have LF as line endings
+###############################################################################
+*.sh eol=lf
-*.cs text=auto diff=csharp
-*.vb text=auto
-*.resx text=auto
-*.c text=auto
-*.cpp text=auto
-*.cxx text=auto
-*.h text=auto
-*.hxx text=auto
-*.py text=auto
-*.rb text=auto
-*.java text=auto
-*.html text=auto
-*.htm text=auto
-*.css text=auto
-*.scss text=auto
-*.sass text=auto
-*.less text=auto
-*.js text=auto
-*.lisp text=auto
-*.clj text=auto
-*.sql text=auto
-*.php text=auto
-*.lua text=auto
-*.m text=auto
-*.asm text=auto
-*.erl text=auto
-*.fs text=auto
-*.fsx text=auto
-*.hs text=auto
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
-*.csproj text=auto
-*.vbproj text=auto
-*.fsproj text=auto
-*.dbproj text=auto
-*.sln text=auto eol=crlf
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 331b644f16..0000000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,45 +0,0 @@
-To address your issues more quickly, please open the issue in the most appropriate repository.
-
-If you're not sure exactly where to log the bug, please log the issue here and we will redirect it.
-
-ASP.NET Core:
- - General issues: https://github.com/aspnet/Home (this repo)
- - MVC and Razor Pages: https://github.com/aspnet/Mvc
- - SignalR: https://github.com/aspnet/SignalR
- - Kestrel HTTP Server: https://github.com/aspnet/KestrelHttpServer
- - Docker: https://github.com/aspnet/aspnet-docker
- - Documentation: https://github.com/aspnet/Docs
- - Microsoft.NET.Sdk.Web: https://github.com/aspnet/websdk
- - See a full list here: https://github.com/aspnet
- - Note: Some repos do not have active issue trackers, so if you see such a warning, please log the issue here
-
-ASP.NET 4.x:
- - ASP.NET MVC/Web API (not Core): https://github.com/aspnet/AspNetWebStack
- - Katana: https://github.com/aspnet/AspNetKatana
-
-Entity Framework:
- - Entity Framework Core: https://github.com/aspnet/EntityFrameworkCore
- - Entity Framework 6: https://github.com/aspnet/EntityFramework6
-
-Other common projects:
- - .NET Core CLI and SDK: https://github.com/dotnet/core
- - .NET Core runtime: https://github.com/dotnet/coreclr
- - .NET Core libraries: https://github.com/dotnet/corefx
- - NuGet: https://github.com/NuGet/home
- - Visual Studio: https://developercommunity.visualstudio.com
- - Visual Studio Code: https://github.com/microsoft/vscode
- - Omnisharp (C# support for VS Code): https://github.com/omnisharp/omnisharp-vscode
-
-If you believe you have an issue that affects the security of the platform please do NOT create an issue and instead email your issue details to secure@microsoft.com. Your report may be eligible for our [bug bounty](https://technet.microsoft.com/en-us/mt764065.aspx).
-
----
-
-Tips for opening great bugs:
-
-1. Try enabling logging (in the most verbose level) and see if the details help you in fixing the issue you are seeing. Share the logs too if it helps in diagnosing the issue faster. More info: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging
-2. If you are seeing an exception, include the full exceptions details (message and stack trace). More info: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling
-3. Please try to include as much information as possible:
- 1. Description of the problem:
- 2. Steps to reproduce (preferrably a link to a GitHub repo with a repro project)
- 3. The version of `Microsoft.AspNetCore.App` or `Microsoft.AspNetCore.All`
- 4. The output of `dotnet --info`
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000..825102492c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,24 @@
+---
+name: Bug report
+about: Create a report about something that is not working
+---
+
+### Describe the bug
+A clear and concise description of what the bug is.
+
+### To Reproduce
+Steps to reproduce the behavior:
+1. Using this version of ASP.NET Core '...'
+2. Run this code '....'
+3. With these arguments '....'
+4. See error
+
+### Expected behavior
+A clear and concise description of what you expected to happen.
+
+### Screenshots
+If applicable, add screenshots to help explain your problem.
+
+### Additional context
+Add any other context about the problem here.
+Include the output of `dotnet --info`
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000000..3b4122bc71
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,17 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+---
+
+### Is your feature request related to a problem? Please describe.
+A clear and concise description of what the problem is.
+Example. I'm am trying to do [...] but [...]
+
+### Describe the solution you'd like
+A clear and concise description of what you want to happen.
+
+### Describe alternatives you've considered
+A clear and concise description of any alternative solutions or features you've considered.
+
+### Additional context
+Add any other context or screenshots about the feature request here.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000000..c07fa37b2b
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,5 @@
+Summary of the changes (Less than 80 chars)
+ - Detail 1
+ - Detail 2
+
+Addresses #bugnumber (in this specific format)
diff --git a/.gitignore b/.gitignore
index ec80ffa907..ba949609e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,27 +1,26 @@
-[Oo]bj/
-[Bb]in/
-TestResults/
-.nuget/
+bin/
+obj/
.vs/
-_ReSharper.*/
-packages/
-artifacts/
-PublishProfiles/
-*.user
*.suo
-*.cache
-*.docstates
+*.user
_ReSharper.*
-nuget.exe
-*net45.csproj
-*k10.csproj
-*.psess
-*.vsp
-*.pidb
+*.DS_Store
*.userprefs
-*DS_Store
-*.ncrunchsolution
-*.*sdf
-*.ipch
-*.sln.ide
-project.lock.json
\ No newline at end of file
+*.pidb
+*.vspx
+*.psess
+*.binlog
+*.log
+artifacts/
+StyleCop.Cache
+node_modules/
+*.snk
+.nuget
+.r
+.w
+.deps
+msbuild.ProjectImports.zip
+.env
+scripts/tmp/
+.dotnet/
+.tools/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000..6436140b01
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,176 @@
+[submodule "modules/AADIntegration"]
+ path = modules/AADIntegration
+ url = https://github.com/aspnet/AADIntegration.git
+ branch = master
+[submodule "modules/Antiforgery"]
+ path = modules/Antiforgery
+ url = https://github.com/aspnet/Antiforgery.git
+ branch = master
+[submodule "modules/AuthSamples"]
+ path = modules/AuthSamples
+ url = https://github.com/aspnet/AuthSamples.git
+ branch = master
+[submodule "modules/AzureIntegration"]
+ path = modules/AzureIntegration
+ url = https://github.com/aspnet/AzureIntegration.git
+ branch = master
+[submodule "modules/BasicMiddleware"]
+ path = modules/BasicMiddleware
+ url = https://github.com/aspnet/BasicMiddleware.git
+ branch = master
+[submodule "modules/BrowserLink"]
+ path = modules/BrowserLink
+ url = https://github.com/aspnet/BrowserLink.git
+ branch = master
+[submodule "modules/Caching"]
+ path = modules/Caching
+ url = https://github.com/aspnet/Caching.git
+ branch = master
+[submodule "modules/Common"]
+ path = modules/Common
+ url = https://github.com/aspnet/Common.git
+ branch = master
+[submodule "modules/Configuration"]
+ path = modules/Configuration
+ url = https://github.com/aspnet/Configuration.git
+ branch = master
+[submodule "modules/CORS"]
+ path = modules/CORS
+ url = https://github.com/aspnet/CORS.git
+ branch = master
+[submodule "modules/DependencyInjection"]
+ path = modules/DependencyInjection
+ url = https://github.com/aspnet/DependencyInjection.git
+ branch = master
+[submodule "modules/Diagnostics"]
+ path = modules/Diagnostics
+ url = https://github.com/aspnet/Diagnostics.git
+ branch = master
+[submodule "modules/DotNetTools"]
+ path = modules/DotNetTools
+ url = https://github.com/aspnet/DotNetTools.git
+ branch = master
+[submodule "modules/EntityFrameworkCore"]
+ path = modules/EntityFrameworkCore
+ url = https://github.com/aspnet/EntityFrameworkCore.git
+ branch = master
+[submodule "modules/EventNotification"]
+ path = modules/EventNotification
+ url = https://github.com/aspnet/EventNotification.git
+ branch = master
+[submodule "modules/FileSystem"]
+ path = modules/FileSystem
+ url = https://github.com/aspnet/FileSystem.git
+ branch = master
+[submodule "modules/Hosting"]
+ path = modules/Hosting
+ url = https://github.com/aspnet/Hosting.git
+ branch = master
+[submodule "modules/HtmlAbstractions"]
+ path = modules/HtmlAbstractions
+ url = https://github.com/aspnet/HtmlAbstractions.git
+ branch = master
+[submodule "modules/HttpAbstractions"]
+ path = modules/HttpAbstractions
+ url = https://github.com/aspnet/HttpAbstractions.git
+ branch = master
+[submodule "modules/HttpClientFactory"]
+ path = modules/HttpClientFactory
+ url = https://github.com/aspnet/HttpClientFactory.git
+ branch = master
+[submodule "modules/HttpSysServer"]
+ path = modules/HttpSysServer
+ url = https://github.com/aspnet/HttpSysServer.git
+ branch = master
+[submodule "modules/Identity"]
+ path = modules/Identity
+ url = https://github.com/aspnet/Identity.git
+ branch = master
+[submodule "modules/IISIntegration"]
+ path = modules/IISIntegration
+ url = https://github.com/aspnet/IISIntegration.git
+ branch = master
+[submodule "modules/JavaScriptServices"]
+ path = modules/JavaScriptServices
+ url = https://github.com/aspnet/JavaScriptServices.git
+ branch = master
+[submodule "modules/JsonPatch"]
+ path = modules/JsonPatch
+ url = https://github.com/aspnet/JsonPatch.git
+ branch = master
+[submodule "modules/KestrelHttpServer"]
+ path = modules/KestrelHttpServer
+ url = https://github.com/aspnet/KestrelHttpServer.git
+ branch = master
+[submodule "modules/Localization"]
+ path = modules/Localization
+ url = https://github.com/aspnet/Localization.git
+ branch = master
+[submodule "modules/Logging"]
+ path = modules/Logging
+ url = https://github.com/aspnet/Logging.git
+ branch = master
+[submodule "modules/MetaPackages"]
+ path = modules/MetaPackages
+ url = https://github.com/aspnet/MetaPackages.git
+ branch = master
+[submodule "modules/Microsoft.Data.Sqlite"]
+ path = modules/Microsoft.Data.Sqlite
+ url = https://github.com/aspnet/Microsoft.Data.Sqlite.git
+ branch = master
+[submodule "modules/MusicStore"]
+ path = modules/MusicStore
+ url = https://github.com/aspnet/MusicStore.git
+ branch = master
+[submodule "modules/Mvc"]
+ path = modules/Mvc
+ url = https://github.com/aspnet/Mvc.git
+ branch = master
+[submodule "modules/Options"]
+ path = modules/Options
+ url = https://github.com/aspnet/Options.git
+ branch = master
+[submodule "modules/Razor"]
+ path = modules/Razor
+ url = https://github.com/aspnet/Razor.git
+ branch = master
+[submodule "modules/ResponseCaching"]
+ path = modules/ResponseCaching
+ url = https://github.com/aspnet/ResponseCaching.git
+ branch = master
+[submodule "modules/Routing"]
+ path = modules/Routing
+ url = https://github.com/aspnet/Routing.git
+ branch = master
+[submodule "modules/Scaffolding"]
+ path = modules/Scaffolding
+ url = https://github.com/aspnet/Scaffolding.git
+ branch = master
+[submodule "modules/Security"]
+ path = modules/Security
+ url = https://github.com/aspnet/Security.git
+ branch = master
+[submodule "modules/ServerTests"]
+ path = modules/ServerTests
+ url = https://github.com/aspnet/ServerTests.git
+ branch = master
+[submodule "modules/Session"]
+ path = modules/Session
+ url = https://github.com/aspnet/Session.git
+ branch = master
+[submodule "modules/SignalR"]
+ path = modules/SignalR
+ url = https://github.com/aspnet/SignalR.git
+ branch = master
+[submodule "modules/StaticFiles"]
+ path = modules/StaticFiles
+ url = https://github.com/aspnet/StaticFiles.git
+ branch = master
+[submodule "modules/Templating"]
+ path = modules/Templating
+ url = https://github.com/aspnet/Templating.git
+ branch = master
+[submodule "modules/WebSockets"]
+ path = modules/WebSockets
+ url = https://github.com/aspnet/WebSockets.git
+ branch = master
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000000..a869097c1c
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,7 @@
+{
+ "recommendations": [
+ "ms-vscode.csharp",
+ "ms-vscode.PowerShell",
+ "EditorConfig.EditorConfig"
+ ]
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000..f967d60789
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,17 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "PowerShell",
+ "request": "launch",
+ "name": "ps: Interactive Session",
+ "cwd": "${workspaceRoot}"
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach",
+ "processId": "${command:pickProcess}"
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..89a3c7cca0
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,9 @@
+{
+ "files.trimTrailingWhitespace": true,
+ "files.associations": {
+ "*.*proj": "xml",
+ "*.props": "xml",
+ "*.targets": "xml",
+ "*.tasks": "xml"
+ }
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d0cae1018a..c0e07c6cc6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,28 +1,22 @@
# How to contribute
-One of the easiest ways to contribute is to participate in discussions and discuss issues. You can also contribute by submitting pull requests with code changes.
+One of the easiest ways to contribute is to participate in discussions on GitHub issues. You can also contribute by submitting pull requests with code changes.
## General feedback and discussions?
-Please start a discussion on the [Home repo issue tracker](https://github.com/aspnet/Home/issues).
+Please start a discussion on the [repo issue tracker](https://github.com/aspnet/AspNetCore/issues).
## Bugs and feature requests?
For non-security related bugs please log a new issue in the appropriate GitHub repo. Here are some of the most common repos:
-* [DependencyInjection](https://github.com/aspnet/DependencyInjection)
* [Docs](https://github.com/aspnet/Docs)
-* [EntityFramework](https://github.com/aspnet/EntityFramework)
-* [Identity](https://github.com/aspnet/Identity)
-* [MVC](https://github.com/aspnet/Mvc)
-* [Razor](https://github.com/aspnet/Razor)
-* [Templating](https://github.com/aspnet/templating)
+* [Entity Framework Core](https://github.com/aspnet/EntityFrameworkCore)
* [Tooling](https://github.com/aspnet/Tooling)
-* [SignalR](https://github.com/aspnet/SignalR)
+* [Extensions](https://github.com/aspnet/Extensions)
Or browse the full list of repos in the [aspnet](https://github.com/aspnet/) organization.
-
## Reporting security issues and bugs
Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) secure@microsoft.com. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://technet.microsoft.com/en-us/security/ff852094.aspx).
@@ -34,53 +28,18 @@ Our team members also monitor several other discussion forums:
* [Stack Overflow](https://stackoverflow.com/) with the [`asp.net-core`](https://stackoverflow.com/questions/tagged/asp.net-core), [`asp.net-core-mvc`](https://stackoverflow.com/questions/tagged/asp.net-core-mvc), or [`entity-framework-core`](https://stackoverflow.com/questions/tagged/entity-framework-core) tags.
-## Filing issues
-When filing issues, please use our [bug filing templates](https://github.com/aspnet/Home/wiki/Functional-bug-template).
-The best way to get your bug fixed is to be as detailed as you can be about the problem.
-Providing a minimal project with steps to reproduce the problem is ideal.
-Here are questions you can answer before you file a bug to make sure you're not missing any important information.
-
-1. Did you read the [documentation](https://github.com/aspnet/home/wiki)?
-2. Did you include the snippet of broken code in the issue?
-3. What are the *EXACT* steps to reproduce this problem?
-4. What package versions are you using (you can see these in the `.csproj` file)?
-5. What operating system are you using?
-6. What version of IIS are you using?
-
-GitHub supports [markdown](https://help.github.com/articles/github-flavored-markdown/), so when filing bugs make sure you check the formatting before clicking submit.
-
-
## Contributing code and content
### Identifying the scale
If you would like to contribute to one of our repositories, first identify the scale of what you would like to contribute. If it is small (grammar/spelling or a bug fix) feel free to start working on a fix. If you are submitting a feature or substantial code contribution, please discuss it with the team and ensure it follows the product roadmap. You might also read these two blogs posts on contributing code: [Open Source Contribution Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) by Miguel de Icaza and [Don't "Push" Your Pull Requests](https://www.igvita.com/2011/12/19/dont-push-your-pull-requests/) by Ilya Grigorik. Note that all code submissions will be rigorously reviewed and tested by the ASP.NET and Entity Framework teams, and only those that meet an extremely high bar for both quality and design/roadmap appropriateness will be merged into the source.
-### Obtaining the source code
-
-If you are an outside contributor, please fork the ASP.NET repository you would like to contribute to your account. See the GitHub documentation for [forking a repo](https://help.github.com/articles/fork-a-repo/) if you have any questions about this.
-
-### Building our Repositories
-
-As our repositories use the latest bits of our code, we have a custom build script to fetch and use them. Please go through [building our repositories from source](https://github.com/aspnet/Home/wiki/Building-from-source) to understand and fix any issues.
-
### Submitting a pull request
You will need to sign a [Contributor License Agreement](https://cla.dotnetfoundation.org/) when submitting your pull request. To complete the Contributor License Agreement (CLA), you will need to follow the instructions provided by the CLA bot when you send the pull request. This needs to only be done once for any .NET Foundation OSS project.
-If you don't know what a pull request is read this article: https://help.github.com/articles/using-pull-requests. Make sure the respository can build and all tests pass. Familiarize yourself with the project workflow and our coding conventions. The coding, style, and general engineering guidelines are published on the [Engineering guidelines](https://github.com/aspnet/Home/wiki/Engineering-guidelines) page.
+If you don't know what a pull request is read this article: https://help.github.com/articles/using-pull-requests. Make sure the respository can build and all tests pass. Familiarize yourself with the project workflow and our coding conventions. The coding, style, and general engineering guidelines are published on the [Engineering guidelines](https://github.com/aspnet/AspNetCore/wiki/Engineering-guidelines) page.
-Pull requests should all be done to the **release/2.2** (for the next release) or **master** (for 3.0 work) branch.
-
-### Commit/Pull Request Format
-
-```
-Summary of the changes (Less than 80 chars)
- - Detail 1
- - Detail 2
-
-Addresses #bugnumber (in this specific format)
-```
### Tests
@@ -91,7 +50,7 @@ Addresses #bugnumber (in this specific format)
### Feedback
-Your pull request will now go through extensive checks by the subject matter experts on our team. Please be patient; we have hundreds of pull requests across all of our repositories. Update your pull request according to feedback until it is approved by one of the ASP.NET team members. After that, one of our team members will add the pull request to **release/2.2** or **master**.
+Your pull request will now go through extensive checks by the subject matter experts on our team. Please be patient; we have hundreds of pull requests across all of our repositories. Update your pull request according to feedback until it is approved by one of the ASP.NET team members. After that, one of our team members may adjust the branch you merge into based on the expected release schedule.
## Code of conduct
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000000..531b5d8745
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,28 @@
+
+
+
+
+ Microsoft ASP.NET Core
+ $(MSBuildThisFileDirectory)
+ https://github.com/aspnet/Universe
+ git
+ $(MSBuildThisFileDirectory)eng\AspNetCore.snk
+ true
+ true
+
+
+
+
+
+
+ $(RepositoryRoot)artifacts\
+ $(ArtifactsDir)obj\
+ $(ArtifactsDir)$(Configuration)\
+ $(ArtifactsConfigurationDir)bin\
+ $(ArtifactsConfigurationDir)packages\
+
+
+
+
+
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000000..6bd26321ea
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,14 @@
+
+
+ $(MicrosoftNETCoreApp20PackageVersion)
+ $(MicrosoftNETCoreApp21PackageVersion)
+ $(MicrosoftNETCoreApp22PackageVersion)
+ $(MicrosoftNETCoreApp30PackageVersion)
+ $(NETStandardLibrary20PackageVersion)
+
+ 99.9
+
+
+
+
+
diff --git a/Microsoft.AspNetCore.sln b/Microsoft.AspNetCore.sln
new file mode 100644
index 0000000000..809b017e25
--- /dev/null
+++ b/Microsoft.AspNetCore.sln
@@ -0,0 +1,39 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{EE2CAA71-82AA-41C0-AE87-5B4FB77D6CFE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedFx.UnitTests", "test\SharedFx.UnitTests\SharedFx.UnitTests.csproj", "{99CC38EC-902B-4B3F-AD33-177018110199}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Debug|x64.Build.0 = Debug|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Debug|x86.Build.0 = Debug|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Release|Any CPU.Build.0 = Release|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Release|x64.ActiveCfg = Release|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Release|x64.Build.0 = Release|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Release|x86.ActiveCfg = Release|Any CPU
+ {99CC38EC-902B-4B3F-AD33-177018110199}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {99CC38EC-902B-4B3F-AD33-177018110199} = {EE2CAA71-82AA-41C0-AE87-5B4FB77D6CFE}
+ EndGlobalSection
+EndGlobal
diff --git a/NuGet.config b/NuGet.config
new file mode 100644
index 0000000000..cec9479a72
--- /dev/null
+++ b/NuGet.config
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 2bda91b8ce..1e8a4ef412 100644
--- a/README.md
+++ b/README.md
@@ -1,84 +1,40 @@
-
-# ASP.NET Core
-
-[app-metapackage-nuget]: https://nuget.org/packages/Microsoft.AspNetCore.All
-[app-metapackage-nuget-badge]: http://img.shields.io/nuget/v/Microsoft.AspNetCore.All.svg?style=flat-square&label=aspnet@stable
-[app-metapackage-myget]: https://dotnet.myget.org/feed/dotnet-core/package/nuget/Microsoft.AspNetCore.App
-[app-metapackage-myget-badge]: http://img.shields.io/dotnet.myget/dotnet-core/v/Microsoft.AspNetCore.App.svg?style=flat-square&label=aspnet@preview
-
-[![][app-metapackage-nuget-badge]][app-metapackage-nuget]
-[![][app-metapackage-myget-badge]][app-metapackage-myget]
-
-[](https://gitter.im/aspnet/Home?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-
-This is the home page of the ASP.NET Core source code repositories and is intended for those contributing to ASP.NET Core or using bleeding edge nightly builds.
-
-ASP.NET Core is a new open-source and cross-platform framework for building modern cloud based internet connected applications, such as web apps, IoT apps and mobile backends. ASP.NET Core apps can run on .NET Core or on the full .NET Framework. It was architected to provide an optimized development framework for apps that are deployed to the cloud or run on-premises. It consists of modular components with minimal overhead, so you retain flexibility while constructing your solutions. You can develop and run your ASP.NET Core apps cross-platform on Windows, Mac and Linux. [Learn more about ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/).
-
-ASP.NET Core 1.1 is now available! See the [release notes](https://github.com/aspnet/Home/releases/tag/1.1.0) for further details.
-
-ASP.NET Core 2.0 is now available! See the [release notes](https://github.com/aspnet/Home/releases/tag/2.0.0) for further details and check our [releases](https://github.com/aspnet/home/releases) for the latest patch release.
-
-ASP.NET Core 2.1 is now available! See the [release notes](https://github.com/aspnet/Home/releases/tag/2.1.0) for further details and check our [releases](https://github.com/aspnet/Home/releases/) for the latest patch release.
-
-## Get Started
-
-Follow the [Getting Started](https://docs.microsoft.com/en-us/aspnet/core/getting-started) instructions in the [ASP.NET Core docs](https://docs.microsoft.com/en-us/aspnet/index).
-
-Also checkout the [.NET Homepage](https://www.microsoft.com/net) for released versions of .NET, getting started guides, and learning resources.
-
-## Daily builds
-
-If you want to use the latest daily build then you need to:
-
-- Obtain the latest [build of the .NET Core SDK](https://github.com/dotnet/cli#installers-and-binaries)
-- Add a NuGet.Config to your app with the following content:
-
- ```xml
-
-
-
-
-
-
-
-
- ```
-
- *NOTE: This NuGet.Config should be with your application unless you want nightly packages to potentially start being restored for other apps on the machine.*
-
-Prerelease tooling builds for Visual Studio are available in the [Visual Studio Preview](https://www.visualstudio.com/vs/preview/).
-
-
-## Community and roadmap
-
-To follow along with the development of ASP.NET Core:
-
-- [Community Standup](http://live.asp.net): The community standup is held every week and streamed live to YouTube. You can view past standups in the linked playlist.
-- [Roadmap](https://github.com/aspnet/Home/wiki/Roadmap): The schedule and milestone themes for ASP.NET Core.
-
-## Repos and projects
-
-These are some of the most common repos:
-
-* [DependencyInjection](https://github.com/aspnet/DependencyInjection) - basic dependency injection infrastructure and default implementation
-* [Docs](https://github.com/aspnet/Docs) - documentation sources for https://docs.microsoft.com/en-us/aspnet/core/
-* [EntityFrameworkCore](https://github.com/aspnet/EntityFrameworkCore) - data access technology
-* [Identity](https://github.com/aspnet/Identity) - users and membership system
-* [MVC](https://github.com/aspnet/Mvc) - MVC framework for web apps and services
-* [Razor](https://github.com/aspnet/Razor) - template language and syntax for CSHTML files
-* [SignalR](https://github.com/aspnet/SignalR) - library to add real-time web functionality
-* [Templating](https://github.com/aspnet/Templating) - project templates for Visual Studio and the .NET Core SDK
-* [Tooling](https://github.com/aspnet/Tooling) - Visual Studio tooling, editors, and dialogs
-
-## NuGet feeds and branches
-
-See the [NuGet feeds](https://github.com/aspnet/Home/wiki/NuGet-feeds) wiki page.
-
-# Feedback
-
-Check out the [contributing](CONTRIBUTING.md) page to see the best places to log issues and start discussions.
-
-# Code of conduct
-
-This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+ASP.NET Core
+============
+
+ASP.NET Core is an open-source and cross-platform framework for building modern cloud based internet connected applications, such as web apps, IoT apps and mobile backends. ASP.NET Core apps can run on .NET Core or on the full .NET Framework. It was architected to provide an optimized development framework for apps that are deployed to the cloud or run on-premises. It consists of modular components with minimal overhead, so you retain flexibility while constructing your solutions. You can develop and run your ASP.NET Core apps cross-platform on Windows, Mac and Linux. [Learn more about ASP.NET Core](https://docs.microsoft.com/aspnet/core/).
+
+ASP.NET Core 2.1 is now available! See the [release notes](https://github.com/aspnet/AspNetCore/releases/tag/2.1.0) for further details and check our [releases](https://github.com/aspnet/AspNetCore/releases/) for the latest patch release.
+
+## Get Started
+
+Follow the [Getting Started](https://docs.microsoft.com/aspnet/core/getting-started) instructions in the [ASP.NET Core docs](https://docs.microsoft.com/aspnet/index).
+
+Also check out the [.NET Homepage](https://www.microsoft.com/net) for released versions of .NET, getting started guides, and learning resources.
+
+## How to Engage, Contribute, and Give Feedback
+
+Some of the best ways to contribute are to try things out, file issues, join in design conversations,
+and make pull-requests.
+
+* [Download our latest daily builds](./docs/daily-builds.md)
+* Follow along with the development of ASP.NET Core:
+ * [Community Standup](http://live.asp.net): The community standup is held every week and streamed live to YouTube. You can view past standups in the linked playlist.
+ * [Roadmap](https://github.com/aspnet/AspNetCore/wiki/Roadmap): The schedule and milestone themes for ASP.NET Core.
+* [Build ASP.NET Core source code](./docs/build-from-source.md)
+* Check out the [contributing](CONTRIBUTING.md) page to see the best places to log issues and start discussions.
+
+## Reporting security issues and bugs
+
+Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) secure@microsoft.com. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://technet.microsoft.com/en-us/security/ff852094.aspx).
+
+## Related projects
+
+These are some other repos for related projects:
+
+* [Documentation](https://github.com/aspnet/Docs) - documentation sources for https://docs.microsoft.com/aspnet/core/
+* [Entity Framework Core](https://github.com/aspnet/EntityFrameworkCore) - data access technology
+* [Tooling](https://github.com/aspnet/Tooling) - Visual Studio tooling, editors, and dialogs
+
+## Code of conduct
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt
new file mode 100644
index 0000000000..d1bf499032
--- /dev/null
+++ b/THIRD-PARTY-NOTICES.txt
@@ -0,0 +1,86 @@
+.NET Core uses third-party libraries or other resources that may be
+distributed under licenses different than the .NET Core software.
+
+In the event that we accidentally failed to list a required notice, please
+bring it to our attention. Post an issue or email us:
+
+ dotnet@microsoft.com
+
+The attached notices are provided for information only.
+
+License notice for dotnet-deb-tool
+------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) .NET Foundation and Contributors
+
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+License notice for IIS-Common
+------------------------------------
+
+MIT License
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE
+
+License notice for IIS-Setup
+------------------------------------
+
+MIT License
+
+Copyright (c) Microsoft Corporation. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE
diff --git a/build.cmd b/build.cmd
new file mode 100644
index 0000000000..c0050bda12
--- /dev/null
+++ b/build.cmd
@@ -0,0 +1,2 @@
+@ECHO OFF
+PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE"
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000000..98a4b22765
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+# Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs)
+chmod +x "$DIR/run.sh"; sync
+"$DIR/run.sh" default-build "$@"
diff --git a/build/AzureIntegration.targets b/build/AzureIntegration.targets
new file mode 100644
index 0000000000..8df9536eb7
--- /dev/null
+++ b/build/AzureIntegration.targets
@@ -0,0 +1,30 @@
+
+
+ $(MSBuildThisFileDirectory)..\modules\AzureIntegration\
+
+
+
+
+
+ AspNetUniverseBuildOffline=true;
+ RepositoryRoot=$(AzureIntegrationProjectRoot);
+ DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath);
+ DotNetPackageVersionPropsPath=$(GeneratedPackageVersionPropsPath);
+ BuildNumber=$(BuildNumber);
+ Configuration=$(Configuration);
+ IsFinalBuild=$(IsFinalBuild);
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/PackageArchive.targets b/build/PackageArchive.targets
new file mode 100644
index 0000000000..bb892d102c
--- /dev/null
+++ b/build/PackageArchive.targets
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath);
+ DotNetPackageVersionPropsPath=$(GeneratedPackageVersionPropsPath);
+ OutputPath=$(ArtifactsDir)lzma\;
+ _BuildToolsAssembly=$(_BuildToolsAssembly)
+
+
+
+
+
+
+
+
+
+
diff --git a/build/Publish.targets b/build/Publish.targets
new file mode 100644
index 0000000000..3a62447d1c
--- /dev/null
+++ b/build/Publish.targets
@@ -0,0 +1,330 @@
+
+
+
+ $(RepositoryRoot).deps\assets\
+ $(RepositoryRoot).deps\packages\
+
+ aspnetcore_base_runtime.version
+ $(IntermediateDir)$(BaseRuntimeVersionFileName)
+ latest.version
+ $(IntermediateDir)$(LatestRuntimeVersionFileName)
+
+
+ ResolveCommitHash;
+ PrepareOutputPaths;
+ GetFilesToPublish;
+ PublishToLocalFolder;
+ PublishToAzureFeed;
+ PublishToTransportFeed;
+ PublishToMyGet;
+
+
+
+ 10
+ false
+ 8
+ assets
+ $(BlobFileRelativePathBase)/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ aspnetcore/Runtime/$(PackageVersion)/
+ aspnetcore/npm/
+ aspnetcore/jar/
+ aspnetcore/Runtime/$(SharedFxCliBlobChannel)/
+ runtime-site-extension-internal-$(PackageVersion).zip
+ aspnetcore-runtime-$(PackageVersion)
+ aspnetcore-runtime-latest
+ aspnetcore-runtime-internal-$(PackageVersion)
+ dotnet-hosting-$(PackageVersion)-win.exe
+
+
+
+
+
+ $(BlobBasePath)$(WindowsHostingBundleInstallerFileName)
+ ShipInstaller=dotnetcli
+
+
+
+
+ $(BlobBasePath)nuGetPackagesArchive-ci-server-$(PackageVersion).zip
+ ShipInstaller=dotnetcli
+
+
+
+ $(BlobBasePath)nuGetPackagesArchive-ci-server-$(PackageVersion).patch.zip
+ ShipInstaller=dotnetcli
+
+
+
+ $(BlobBasePath)nuGetPackagesArchive-ci-server-compat-$(PackageVersion).patch.zip
+ ShipInstaller=dotnetcli
+
+
+
+
+ $(BlobBasePath)$(SiteExtensionArchiveFileName)
+ ShipInstaller=dotnetcli
+
+
+
+ $(BlobBasePath)nuGetPackagesArchive-$(PackageVersion).lzma
+ ShipInstaller=dotnetcli
+
+
+
+ $(BlobBasePath)$(IntermediateInstallerBaseFileName)-%(IntermediateInstaller.Identity)%(IntermediateInstaller.FileExt)
+ ShipInstaller=dotnetcli
+
+
+
+ $(BlobBasePath)$(BaseRuntimeVersionFileName)
+ ShipInstaller=dotnetcli
+ text/plain
+
+
+
+
+ $(BlobBasePath)$(InstallerBaseFileName)-%(NativeInstaller.Identity)%(NativeInstaller.FileExt)
+ ShipInstaller=dotnetcli
+
+
+
+
+ $(AliasBlobBasePath)$(InstallerAliasBaseFileName)-%(NativeInstaller.Identity)%(NativeInstaller.FileExt)
+ ShipInstaller=dotnetcli
+ true
+
+ true
+
+
+
+ $(AliasBlobBasePath)%(SharedFxVersionBadge.FileName)%(SharedFxVersionBadge.Extension)
+ ShipInstaller=dotnetcli
+ no-cache, no-store, must-revalidate
+ image/svg+xml
+ true
+
+
+
+ $(AliasBlobBasePath)$(LatestRuntimeVersionFileName)
+ ShipInstaller=dotnetcli
+ no-cache, no-store, must-revalidate
+ text/plain
+ true
+
+
+
+ <_PackageArtifactInfo
+ Include="@(ArtifactInfo)"
+ ArtifactPath="$(DependencyPackagesDir)%(ArtifactInfo.PackageId).%(ArtifactInfo.Version).nupkg"
+ Condition="'%(ArtifactInfo.ArtifactType)' == 'NuGetPackage'" />
+
+ <_SymbolsPackageArtifactInfo
+ Include="@(ArtifactInfo)"
+ ArtifactPath="$(DependencyPackagesDir)%(ArtifactInfo.PackageId).%(ArtifactInfo.Version).symbols.nupkg"
+ Condition="'%(ArtifactInfo.ArtifactType)' == 'NuGetSymbolsPackage'" />
+
+
+ $(JarBlobBasePath)%(ArtifactInfo.FileName)%(ArtifactInfo.Extension)
+ Type=JavaJar
+
+
+
+ $(JarBlobBasePath)%(ArtifactInfo.FileName)%(ArtifactInfo.Extension)
+
+
+
+ $(NpmBlobBasePath)%(ArtifactInfo.PackageId)/%(ArtifactInfo.FileName)%(ArtifactInfo.Extension)
+ Type=NpmPackage
+ application/tar+gzip
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_MissingArtifactFile Include="@(FilesToPublish)" Condition="!Exists(%(FilesToPublish.Identity))" />
+ <_MissingArtifactFile Include="@(NpmPackageToPublish)" Condition="!Exists(%(NpmPackageToPublish.Identity))" />
+ <_MissingArtifactFile Include="@(PackageToPublish)" Condition="!Exists(%(PackageToPublish.Identity))" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(PublishMyGetNpmRegistryUrl.Replace("https:", "")):_authToken
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(AzureBlobRelativePathBase)/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/RepositoryBuild.targets b/build/RepositoryBuild.targets
new file mode 100644
index 0000000000..95e08cb4cb
--- /dev/null
+++ b/build/RepositoryBuild.targets
@@ -0,0 +1,191 @@
+
+
+
+
+ false
+
+ <_NoBuildRepos>$(NoBuild)
+ <_BuildScriptToExecute Condition="'$(OS)'!='Windows_NT'">build.sh
+ <_BuildScriptToExecute Condition="'$(OS)'=='Windows_NT'">build.cmd
+
+
+
+
+
+ $(SubmoduleRoot)%(Identity)\
+
+
+ %(RepositoryBuildOrder.Order)
+ %(RepositoryBuildOrder.Identity)
+
+ RepositoryToBuild=%(RepositoryBuildOrder.Identity);
+ BuildRepositoryRoot=$([MSBuild]::NormalizeDirectory(%(RepositoryBuildOrder.RootPath)))
+
+
+
+
+
+
+
+
+
+ <_NoBuildRepos>true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+ $(RepositoryBuildArguments) -ci
+ $(RepositoryBuildArguments) --dotnet-home '$(DOTNET_HOME)'
+ $(RepositoryBuildArguments) -DotNetHome '$(DOTNET_HOME)'
+
+ $(RepositoryBuildArguments) /p:AspNetUniverseBuildOffline=true
+
+ $(RepositoryBuildArguments) /p:DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath)
+ $(RepositoryBuildArguments) /p:DotNetPackageVersionPropsPath=$(GeneratedPackageVersionPropsPath)
+
+ $(RepositoryBuildArguments) /p:SignType=$(SignType)
+ $(RepositoryBuildArguments) /p:BuildNumber=$(BuildNumber)
+ $(RepositoryBuildArguments) /p:Configuration=$(Configuration)
+ $(RepositoryBuildArguments) /p:IsFinalBuild=$(IsFinalBuild)
+ $(RepositoryBuildArguments) /noconsolelogger '/l:RepoTasks.FlowLogger,$(MSBuildThisFileDirectory)tasks\bin\publish\RepoTasks.dll;Summary;FlowId=$(RepositoryToBuild)'
+ $(RepositoryBuildArguments) '/p:DotNetAssetRootAccessTokenSuffix=$(DotNetAssetRootAccessTokenSuffix)'
+ $(RepositoryBuildArguments) '/p:DotNetAssetRootUrl=$(DotNetAssetRootUrl)'
+ $(RepositoryBuildArguments) /p:SkipAspNetCoreRuntimeInstall=true
+
+ $(RepositoryRoot)korebuild-lock.txt
+ $(BuildRepositoryRoot)korebuild-lock.txt
+ $(IntermediateDir)$(RepositoryToBuild)-korebuild-lock.txt
+ $(BuildRepositoryRoot)global.json
+ $(IntermediateDir)$(RepositoryToBuild)-global.json
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /t:CleanArtifacts /t:Build /p:SkipTests=true $(RepositoryBuildArguments)
+ $(BuildArguments) /bl:$(LogOutputDir)$(RepositoryToBuild).build.binlog
+ $(BuildRepositoryRoot)artifacts\
+ $(RepositoryArtifactsRoot)build\
+ $(RepositoryArtifactsRoot)msbuild\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /t:Test /p:NoBuild=$(_NoBuildRepos) $(RepositoryBuildArguments)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/SharedFx.props b/build/SharedFx.props
new file mode 100644
index 0000000000..ca4aba4602
--- /dev/null
+++ b/build/SharedFx.props
@@ -0,0 +1,79 @@
+
+
+
+ <_WorkRoot>$(RepositoryRoot).w\$(SharedFxRID)\
+ <_WorkLayoutDir>$(_WorkRoot).l\
+ <_WorkOutputDir>$(_WorkRoot).o\
+ <_MetapackageSrcRoot>$(RepositoryRoot)src\Packages\
+ <_TemplatesDir>$(MSBuildThisFileDirectory)tools\templates\
+ <_DockerDir>$(MSBuildThisFileDirectory)tools\docker\
+ <_PackagingDir>$(MSBuildThisFileDirectory)tools\packaging\
+ <_SharedFxSourceDir>$(RepositoryRoot).deps\Signed\SharedFx\
+ <_InstallerSourceDir>$(RepositoryRoot).deps\Installers\
+ <_SymbolsSourceDir>$(RepositoryRoot).deps\symbols\
+ <_DockerRootDir>/opt/code/
+ <_InstallersOutputDir>$(ArtifactsDir)installers\
+
+ :
+ %3B
+ .tar.gz
+ .zip
+
+ lib
+ .so
+ .dll
+ .dylib
+ .exe
+ netcoreapp3.0
+
+
+ aspnetcore-runtime
+
+ $(SharedFxInstallerName)-internal
+ $(_PackagingDir)debian_config.json.in
+ https://dotnetcli.blob.core.windows.net/dotnet/
+
+
+ $(PublicCoreFeedPrefix)
+
+ dotnet-runtime-$(MicrosoftNETCoreAppPackageVersion)
+ $(DotNetAssetRootUrl)Runtime/$(MicrosoftNETCoreAppPackageVersion)/$(DotnetRuntimeFileNamePrefix)
+
+ $(_SharedFxSourceDir)$(SharedFxIntermediateArchiveBaseName)-$(PackageVersion)
+
+
+
+ $(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion)
+ $(InstallerIdVersion).$(AspNetCorePatchVersion)
+
+ $(InstallerPackageVersion)
+ $(DebInstallerPackageVersion)~$(PackageVersionSuffix)
+ 1
+
+ $(PackageRevision)
+ 0.1.$(PackageVersionSuffix)
+ $([System.String]::Copy('$(RpmPackageRevision)').Replace('-', '_'))
+
+
+ Microsoft
+ nugetaspnet@microsoft.com
+ https://www.asp.net/
+ /usr/share/dotnet
+ Apache-2.0
+ Microsoft ASP.NET Core $(PackageVersion) Shared Framework
+ Shared Framework for hosting of Microsoft ASP.NET Core applications. It is open source, cross-platform and is supported by Microsoft. We hope you enjoy using it! If you do, please consider joining the active community of developers that are contributing to the project on GitHub (https://github.com/aspnet/home). We happily accept issues and PRs.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/SharedFx.targets b/build/SharedFx.targets
new file mode 100644
index 0000000000..9987e815a5
--- /dev/null
+++ b/build/SharedFx.targets
@@ -0,0 +1,566 @@
+
+
+
+
+ $([MSBuild]::NormalizeDirectory($(ArtifactsDir)))runtime\
+
+
+
+
+ <_MetapackageProject Include="$(RepositoryRoot)src\Packages\Microsoft.AspNetCore.All\Microsoft.AspNetCore.All.csproj" />
+ <_MetapackageProject Include="$(RepositoryRoot)src\Packages\Microsoft.AspNetCore.App\Microsoft.AspNetCore.App.csproj" />
+ <_MetapackageProject Include="$(RepositoryRoot)src\Packages\Microsoft.AspNetCore.Analyzers\Microsoft.AspNetCore.Analyzers.csproj" />
+
+
+
+
+
+
+
+
+ <_SharedFrameworkSymbolsPackage Include="@(SharedFrameworkName)" Condition="'%(AllSharedFxRIDs.CrossgenSymbols)' != 'false' AND '%(AllSharedFxRIDs.Crossgen)' != 'false'">
+ %(AllSharedFxRIDs.Identity)
+
+ <_SharedFrameworkSymbolsPackage Update="@(_SharedFrameworkSymbolsPackage)" PackageId="runtime.%(Rid).%(Identity)" />
+
+ NuGetSymbolsPackage
+ %(_SharedFrameworkSymbolsPackage.PackageId)
+ $(PackageVersion)
+ shipoob
+
+
+
+
+
+
+ $(_MetapackageSrcRoot)$(MetapackageName)\
+ $(_WorkRoot)pkg\$(MetapackageName)\
+
+ $(CommonProps);Configuration=$(Configuration)
+ $(CommonProps);DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath)
+ $(CommonProps);DotNetBuildOffline=true
+ $(CommonProps);AspNetUniverseBuildOffline=true
+ $(CommonProps);RuntimeFrameworkVersion=$(MicrosoftNETCoreApp30PackageVersion)
+ $(CommonProps);AppMetapackageVersion=$(PackageVersion)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_MetapackageBuilderProject Include="$(MSBuildProjectFullPath)">
+
+ MetapackageName=Microsoft.AspNetCore.App;
+ MetapackageReferenceType=AppMetapackage;
+ MetapackageDependencyVersionRangeType=MajorMinor
+
+
+
+ <_MetapackageBuilderProject Include="$(MSBuildProjectFullPath)">
+
+ MetapackageName=Microsoft.AspNetCore.All;
+ MetapackageReferenceType=AllMetapackage;
+ MetapackageDependencyVersionRangeType=Minimum
+
+
+
+ <_MetapackageBuilderProject Include="$(MSBuildProjectFullPath)">
+
+ MetapackageName=Microsoft.AspNetCore.Analyzers;
+ MetapackageReferenceType=Analyzer;
+ MetapackageDependencyVersionRangeType=Minimum
+
+
+
+
+
+
+
+
+
+ true
+ false
+ true
+ false
+ true
+
+
+
+
+
+ $(_WorkRoot)AppSharedFx\
+ $(_WorkRoot)AllSharedFx\
+ $(_WorkRoot)Publish\
+ $(_WorkRoot)CrossGen\
+ $(_WorkRoot)CrossGenSymbols\
+ $(_WorkRoot)CrossGenTool\
+ $(_WorkRoot)CrossGenRsp\
+ $(SharedFxIntermediateOutputPath)shared\Microsoft.AspNetCore.App\$(PackageVersion)\
+ $(SharedFxIntermediateOutputPath)shared\Microsoft.AspNetCore.All\$(PackageVersion)\
+ $(SharedFxRID)
+
+
+ :
+ %3B
+
+ Configuration=$(Configuration);RuntimeIdentifier=$(SharedFxRestoreRid)
+ $(CommonSharedFxProps);DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath)
+ $(CommonSharedFxProps);DotNetBuildOffline=true
+ $(CommonSharedFxProps);AspNetUniverseBuildOffline=true
+
+
+
+
+
+ $(CommonSharedFxProps)
+ $(RestoreProps);SharedFxPackage=$(SharedFxPackage)
+ $(RestoreProps);SharedFxPackageVersion=$(PackageVersion)
+ $(RestoreProps);SharedFxBase=$(SharedFxBase)
+ $(RestoreProps);SharedFxBaseVersion=$(SharedFxBaseVersion)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RepositoryCommit=$(RepositoryCommit);SharedFxWorkDirectory=$(AppSharedFxWorkDirectory)
+ $(AppSharedFxProps);RuntimeFrameworkVersion=$(MicrosoftNETCoreApp30PackageVersion)
+ $(AppSharedFxProps);SharedFxPackage=Microsoft.AspNetCore.App
+ RepositoryCommit=$(RepositoryCommit);SharedFxWorkDirectory=$(AllSharedFxWorkDirectory)
+ $(AllSharedFxProps);RuntimeFrameworkVersion=$(MicrosoftNETCoreApp30PackageVersion)
+ $(AllSharedFxProps);SharedFxPackage=Microsoft.AspNetCore.All
+ $(AllSharedFxProps);SharedFxDep=Microsoft.AspNetCore.App
+ $(AllSharedFxProps);SharedFxDepVersion=$(PackageVersion)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ win
+ linux
+ osx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SharedFxPackage=Microsoft.AspNetCore.App
+ $(AppSharedFxProps);SharedFxWorkDirectory=$(AppSharedFxWorkDirectory)
+ $(AppSharedFxProps);SharedFxPublishDirectory=$(AppSharedFxPublishDirectory)
+ SharedFxPackage=Microsoft.AspNetCore.All
+ $(AllSharedFxProps);SharedFxWorkDirectory=$(AllSharedFxWorkDirectory)
+ $(AllSharedFxProps);SharedFxPublishDirectory=$(AllSharedFxPublishDirectory)
+
+
+
+
+
+
+
+
+
+
+ Microsoft.NETCore.App
+ crossgen
+ $(CrossGenTool).exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_AppRuntimeAssemblies Include="@(AppPublishAssemblies)" Condition="'%(AssetType)' == 'runtime'">
+ %(PackageName).%(PackageVersion).symbols.nupkg
+ %(RootDir)%(Directory)%(Filename).pdb
+
+ <_AllRuntimeAssemblies Include="@(AllPublishAssemblies)" Exclude="@(_AppRuntimeAssemblies)" Condition="'%(AssetType)' == 'runtime'">
+ %(PackageName).%(PackageVersion).symbols.nupkg
+ %(RootDir)%(Directory)%(Filename).pdb
+
+
+ <_AssembliesToCrossgen Include="$(SharedFxIntermediateOutputPath)**\*.dll" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %(FullPath)
+ $(SharedFxCrossGenRspDirectory)%(RecursiveDir)%(Filename).rsp
+ $(SharedFxCrossGenRspDirectory)%(RecursiveDir)%(Filename).symbols.rsp
+ $(SharedFxCrossGenDirectory)%(RecursiveDir)%(Filename)%(Extension)
+ $(SharedFxCrossGenDirectory)%(RecursiveDir)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CreatePerfMap
+ CreatePDB
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime.$(SharedFxRID).$(SymbolsNuspecIdSuffix)
+
+
+
+ <_SymbolFiles Include="$(SymbolsWorkDir)**\*.pdb;$(SymbolsWorkDir)**\*.map;$(SymbolsWorkDir)**\*.dll" />
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SharedFxCrossGenDirectory)shared\Microsoft.AspNetCore.App\$(PackageVersion)\
+ $(SharedFxCrossGenDirectory)shared\Microsoft.AspNetCore.All\$(PackageVersion)\
+ $(_WorkRoot)Symbols\Microsoft.AspNetCore.App\
+ $(_WorkRoot)Symbols\Microsoft.AspNetCore.All\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SymbolsWorkDir=$(AppSharedFxSymbolsDirectory)
+ $(AppSymbolsArguments);SymbolsNuspecIdSuffix=Microsoft.AspNetCore.App
+ $(AppSymbolsArguments);Description=Symbol packages for Microsoft.AspNetCore.App shared framework
+ SymbolsWorkDir=$(AllSharedFxSymbolsDirectory)
+ $(AllSymbolsArguments);SymbolsNuspecIdSuffix=Microsoft.AspNetCore.All
+ $(AllSymbolsArguments);Description=Symbol packages for Microsoft.AspNetCore.All shared framework
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(RepositoryRoot)\test\SharedFx.UnitTests\SharedFx.UnitTests.csproj
+
+
+ $(LogOutputDir)SharedFx-UnitTests-$(Version).trx
+
+ $(UnitTestFxTrxLogFile)
+ $(HostMachineRepositoryRoot)/artifacts/logs/SharedFx-UnitTests.trx
+
+
+ DotNetRestoreSourcePropsPath=$(GeneratedRestoreSourcesPropsPath);
+ DotNetPackageVersionPropsPath=$(GeneratedPackageVersionPropsPath);
+ SharedFxOutputPath=$(SharedFxIntermediateOutputPath);
+ RepositoryCommit=$(RepositoryCommit);
+ VSTestLogger=$([MSBuild]::Escape('trx;LogFileName=$(UnitTestFxTrxLogFile)'));
+ SharedFxRuntimeIdentifier=$(SharedFXRid)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/SharedFxInstaller.targets b/build/SharedFxInstaller.targets
new file mode 100644
index 0000000000..50cfb4d5a5
--- /dev/null
+++ b/build/SharedFxInstaller.targets
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .tar.gz
+ .zip
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DATE=$([System.DateTime]::UtcNow.ToString(ddd MMM dd yyyy))
+ $(ChangeLogProps);MAINTAINER_NAME=$(RpmMaintainerName)
+ $(ChangeLogProps);MAINTAINER_EMAIL=$(RpmMaintainerEmail)
+ $(ChangeLogProps);PACKAGE_VERSION=$(RpmPackageVersion)
+ $(ChangeLogProps);PACKAGE_REVISION=$(RpmRevision)
+
+
+
+
+
+
+
+
+
+
+
+ rhel.7
+ .NET Foundation
+ /opt/rh/rh-dotnet20/root/usr/lib64/dotnet
+ dotnet-runtime-$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion)
+ $(MicrosoftNETCoreAppPackageVersion.Split('-')[0])
+
+
+
+
+
+
+
+
+
+
+
+
+ @(RpmSharedFxDependencies->' -d "%(Identity) >= %(Version)"', ' ')
+ $(RpmSharedFxArguments) @(RpmRHSharedFxDirectories->' --directories "%(FullPath)"', ' ')
+ $(RpmSharedFxArguments) @(RpmGenericSharedFxDirectories->' --directories "%(FullPath)"', ' ')
+
+ Image=$(Image);RpmVendor=$(RpmVendor);RpmName=$(SharedFxInstallerName)
+ $(RpmCommonProps);RpmMaintainerName=$(MaintainerName);RpmMaintainerEmail=$(MaintainerEmail)
+ $(RpmCommonProps);RpmHomepage=$(Homepage);RpmRevision=$(RpmPackageRevision)
+ $(RpmCommonProps);RpmLicense=$(LicenseType)
+ $(RpmCommonProps);SharedFxArchive=$(SharedFxIntermediateArchiveFilePrefix)-linux-x64.tar.gz
+ $(RpmCommonProps);RpmMSummary=$(SharedFxSummary);RpmDescription=$(SharedFxDescription)
+ RpmInstallRoot=$(InstallRoot)
+ RpmInstallRoot=$(RHInstallRoot)
+ RpmIdVersion=$(InstallerIdVersion);RpmPackageVersion=$(InstallerPackageVersion);RpmFileVersion=$(PackageVersion)
+
+ $(RpmCommonProps);$(RpmGenericProps);$(RpmProps);RpmArguments=$(RpmGenericSharedFxArguments);RpmFileSuffix=x64.rpm
+ $(RpmCommonProps);$(RpmRHProps);$(RpmProps);RpmArguments=$(RpmRHSharedFxArguments);RpmFileSuffix=rh.rhel.7-x64.rpm
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(RepositoryRoot)src/Installers/Debian/build.sh
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MAINTAINER_NAME=$(MaintainerName)
+ $(DebConfigProps);MAINTAINER_EMAIL=$(MaintainerEmail)
+ $(DebConfigProps);HOMEPAGE=$(Homepage)
+ $(DebConfigProps);INSTALL_ROOT=$(InstallRoot)
+ $(DebConfigProps);PACKAGE_NAME=$(DebPrefix)
+ $(DebConfigProps);PACKAGE_REVISION=$(PackageRevision)
+ $(DebConfigProps);LICENSE_TYPE=$(LicenseType)
+ $(DebConfigProps);SHORT_DESCRIPTION=$(DebSummary)
+ $(DebConfigProps);LONG_DESCRIPTION=$(DebDescription)
+ $(DebConfigProps);DEBIAN_DEPENDENCIES=$(DebDependencies)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ dotnet-runtime-$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion)
+ $(MicrosoftNETCoreAppPackageVersion)
+
+ $(Deb_DotnetRuntimeDependencyVersion.Substring(0, $(Deb_DotnetRuntimeDependencyVersion.IndexOf('-'))))~$(Deb_DotnetRuntimeDependencyVersion.Substring($([MSBuild]::Add($(Deb_DotnetRuntimeDependencyVersion.IndexOf('-')), 1))))
+
+
+
+ <_DebSharedFxDependencies Include="$(Deb_DotnetRuntimeDependencyId)" Version="$(Deb_DotnetRuntimeDependencyVersion)"/>
+
+
+
+ ubuntu.14.04
+
+ @(_DebSharedFxDependencies->'"%(Identity)": { "package_version": "%(Version)" }', ', ')
+
+ Image=$(Image);DebPrefix=$(SharedFxInstallerName)
+ $(DebCommonProps);DebSummary=$(SharedFxSummary);DebDescription=$(SharedFxDescription)
+ $(DebCommonProps);SharedFxArchive=$(SharedFxIntermediateArchiveFilePrefix)-linux-x64.tar.gz
+
+ DebIdVersion=$(InstallerIdVersion);DebPackageVersion=$(DebInstallerPackageVersion);DebFileVersion=$(PackageVersion);DebDependencies=$(DebSharedFxDependencies)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/artifacts.props b/build/artifacts.props
new file mode 100644
index 0000000000..1265fb285a
--- /dev/null
+++ b/build/artifacts.props
@@ -0,0 +1,298 @@
+
+
+
+
+
+ false
+
+ false
+
+ false
+
+ Dependency
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/buildorder.props b/build/buildorder.props
new file mode 100644
index 0000000000..073ba5c494
--- /dev/null
+++ b/build/buildorder.props
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/dependencies.props b/build/dependencies.props
new file mode 100644
index 0000000000..3caec25f6b
--- /dev/null
+++ b/build/dependencies.props
@@ -0,0 +1,184 @@
+
+
+
+ 4.6.0-preview1-26907-04
+ 3.0.0-preview1-26907-05
+
+ 3.0.0-preview1-26907-05
+ 3.0.0-preview1-26907-05
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 1.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26717-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 1.7.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.10.0-preview1-26907-04
+ 4.6.0-preview1-26907-04
+ 4.6.0-preview1-26829-04
+
+
+
+
+
+
+ $(MicrosoftNETCoreAppPackageVersion)
+
+
+ $(KoreBuildVersion)
+ $(KoreBuildVersion)
+ $(KoreBuildVersion)
+
+
+
+
+ 0.9.9
+ 0.10.13
+
+
+ 2.1.1
+ 2.1.1
+ 2.1.1
+ 0.43.0
+ 2.1.1.1
+ 2.1.1
+
+ 4.2.1
+ 2.2.0-preview1-03124-01
+ 2.0.0
+ 8.7.0
+ 4.2.1
+ 3.1.0
+ 1.10.0
+ 1.7.3.4
+ 2.1.1
+ 2.2.1
+ 5.2.6
+ 1.7.1
+ 2.3.2
+ 1.1.3
+ 1.0.1
+ 15.8.166
+ 15.8.166
+ 15.8.166
+ 15.8.166
+ 15.8.166
+ 2.8.0
+ 2.8.0
+ 2.8.0
+ 1.7.0
+ 0.2.0-beta-63019-01
+ 1.0.0-rc3-003121
+ 1.1.0
+ 3.19.8
+ 5.3.0
+ 5.3.0
+ 2.1.1
+ 1.0.12
+ 1.1.9
+ 2.0.9
+ 2.1.3
+ 2.2.0-preview3-27014-02
+ $(MicrosoftNETCoreApp21PackageVersion)
+ $(MicrosoftNETCoreApp22PackageVersion)
+ 1.0.1
+ 15.6.1
+ 3.0.1
+ 3.0.1
+ 3.0.1
+ 15.8.525
+ 15.8.525
+ 15.8.28010
+ 15.8.525
+ 15.8.525
+ 7.10.6071
+ 15.8.243
+ 2.0.6142705
+ 15.8.243
+ 15.8.28010
+ 10.0.30320
+ 11.0.61031
+ 12.0.30111
+ 8.0.50728
+ 9.0.30730
+ 7.10.6072
+ 15.8.525
+ 15.8.168
+ 11.1.0
+ 1.4.0
+ 4.3.0.1
+ 1.3.8
+ 1.0.1
+ 4.10.0
+ 1.6.1
+ 2.0.3
+ 1.15.1
+ 1.15.0
+ 1.15.0-pre001
+ 1.0.1
+ 11.0.2
+ 4.7.0
+ 12.2.1100
+ 2.0.1
+ 6.0.1
+ 2.2.0
+ 0.20.0
+ 3.12.1
+ 17.17134.0
+ 3.12.1
+ 1.4.0
+ 4.0.0
+ 1.1.11
+ 1.1.11
+ 1.1.11
+ 1.2.6
+ 2.0.513
+ 1.4.128
+ 1.0.0
+ 5.3.0
+ 3.2.0
+ 4.3.2
+ 3.1.1
+ 4.3.0
+ 4.3.0
+ 1.3.7
+ 9.0.1
+ 2.9.0-beta4-62911-02
+ 2.9.0-beta4-62911-02
+ 2.9.0-beta4-62911-02
+ 2.9.0-beta4-62911-02
+ 2.9.0-beta4-62911-02
+ 2.9.0-beta4-62911-02
+ 2.9.0-beta4-62911-02
+ 2.9.0-beta4-62911-02
+ 2.9.0-beta4-62911-02
+ 2.9.0-beta4-62911-02
+ 8.1.4
+ 2.0.1
+ 0.10.0
+ 2.3.1
+ 2.3.1
+ 2.3.1
+ 2.3.1
+ 2.3.1
+ 2.4.0
+
+
diff --git a/build/docker/alpine.Dockerfile b/build/docker/alpine.Dockerfile
new file mode 100644
index 0000000000..ae9cbab56f
--- /dev/null
+++ b/build/docker/alpine.Dockerfile
@@ -0,0 +1,27 @@
+FROM microsoft/dotnet:2.1.0-preview1-runtime-deps-alpine
+ARG USER
+ARG USER_ID
+ARG GROUP_ID
+
+WORKDIR /code/build
+RUN mkdir -p "/home/$USER" && chown "${USER_ID}:${GROUP_ID}" "/home/$USER"
+ENV HOME "/home/$USER"
+
+RUN apk add --no-cache \
+ bash \
+ wget \
+ git \
+ jq \
+ curl \
+ icu-libs \
+ openssl
+
+USER $USER_ID:$GROUP_ID
+
+# Disable the invariant mode (set in base image)
+ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
+ENV LC_ALL en_US.UTF-8
+ENV LANG en_US.UTF-8
+
+# Skip package initilization
+ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
diff --git a/build/external-dependencies.props b/build/external-dependencies.props
new file mode 100644
index 0000000000..a980476968
--- /dev/null
+++ b/build/external-dependencies.props
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+ false
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/lineups/Internal.AspNetCore.Universe.Lineup.nuspec b/build/lineups/Internal.AspNetCore.Universe.Lineup.nuspec
new file mode 100644
index 0000000000..cc35b59699
--- /dev/null
+++ b/build/lineups/Internal.AspNetCore.Universe.Lineup.nuspec
@@ -0,0 +1,16 @@
+
+
+
+ Internal.AspNetCore.Universe.Lineup
+ $version$
+ Microsoft
+ This package used to unify ASP.NET Core package versions across all Universe repos. Internal use only.
+
+
+
+
+
+
+
+
+
diff --git a/build/repo.beforecommon.props b/build/repo.beforecommon.props
new file mode 100644
index 0000000000..0550358981
--- /dev/null
+++ b/build/repo.beforecommon.props
@@ -0,0 +1,10 @@
+
+
+
+ true
+
+
diff --git a/build/repo.props b/build/repo.props
new file mode 100644
index 0000000000..719d646fe7
--- /dev/null
+++ b/build/repo.props
@@ -0,0 +1,83 @@
+
+
+
+ true
+
+ false
+ true
+ false
+
+ $(RepositoryRoot)modules\
+
+ $(RepositoryRoot).deps\build\
+ $(RepositoryRoot).deps\Signed\Packages\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+ true
+
+
+ true
+
+
+
+
+
+
+
diff --git a/build/repo.targets b/build/repo.targets
new file mode 100644
index 0000000000..f4a116d48d
--- /dev/null
+++ b/build/repo.targets
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+
+
+
+
+ $(IntermediateDir)dependencies.g.props
+ $(IntermediateDir)sources.g.props
+ $(IntermediateDir)branding.g.props
+
+ SetTeamCityBuildNumberToVersion;$(PrepareDependsOn);VerifyPackageArtifactConfig;VerifyExternalDependencyConfig;PrepareOutputPaths
+ $(CleanDependsOn);CleanArtifacts;CleanUniverseArtifacts
+ $(RestoreDependsOn);InstallDotNet
+ $(CompileDependsOn);BuildRepositories
+ $(PackageDependsOn);BuildMetapackages;CheckExpectedPackagesExist
+ $(TestDependsOn);_TestRepositories
+ $(GetArtifactInfoDependsOn);ResolveRepoInfo
+
+
+
+
+
+
+
+
+
+ MicrosoftNETCoreAppPackageVersion=$(MicrosoftNETCoreAppPackageVersion);
+ $(DesignTimeBuildProps);MicrosoftNETCoreApp30PackageVersion=$(MicrosoftNETCoreApp30PackageVersion);
+ $(DesignTimeBuildProps);MicrosoftNETCoreApp22PackageVersion=$(MicrosoftNETCoreApp22PackageVersion);
+ $(DesignTimeBuildProps);MicrosoftNETCoreApp21PackageVersion=$(MicrosoftNETCoreApp21PackageVersion);
+ $(DesignTimeBuildProps);MicrosoftNETCoreApp20PackageVersion=$(MicrosoftNETCoreApp20PackageVersion);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_Temp Remove="@(_Temp)" />
+ <_Temp Include="@(PackageArtifact)" />
+
+
+
+
+
+
+
+
+
+
+
+ <_PackageArtifactWithoutMatchingInfo Include="@(_Temp)" Exclude="@(PackageArtifact)" />
+
+
+
+
+
+
+
+ %(ShippedArtifactInfo.Version)
+
+
+
+ <_PackageArtifactSpec Include="@(PackageArtifact)" />
+
+
+
+
+ <_ShippedSolution Update="@(_ShippedSolution)" Build="false" IsPatching="false" />
+ <_NoBuildSolution Update="@(_NoBuildSolution)" Build="false" />
+
+
+
+
+
+
+
+
+
+ NuGetPackage
+ Internal.AspNetCore.Universe.Lineup
+ $(PackageVersion)
+ noship
+ true
+
+
+
+
+
+
+ <_LineupPackages Include="@(ExternalDependency)" />
+ <_LineupPackages Include="%(ArtifactInfo.PackageId)" Version="%(ArtifactInfo.Version)" Condition=" '%(ArtifactInfo.ArtifactType)' == 'NuGetPackage' " />
+
+ <_RestoreSources Include="$(DependencyPackageDir)" Condition="'$(DependencyPackageDir)' != '' AND Exists('$(DependencyPackageDir)')" />
+ <_RestoreSources Include="$(SignedDependencyPackageDir)" Condition="'$(SignedDependencyPackageDir)' != '' AND Exists('$(SignedDependencyPackageDir)')" />
+ <_RestoreSources Include="$(BuildDir)" />
+ <_RestoreSources Include="$(RestoreSources)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(AspNetCoreMajorVersion)
+ $(AspNetCoreMinorVersion)
+ $(AspNetCorePatchVersion)
+ $(PreReleaseLabel)
+ $(BuildNumber)
+ $(PackageBrandingVersion)
+
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_RepositoriesToInclude Include="$(KOREBUILD_REPOSITORY_INCLUDE)" />
+
+
+
+
+
+ <_RepositoriesToExclude Include="$(KOREBUILD_REPOSITORY_EXCLUDE)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_UndeclaredPackageArtifact Include="%(ArtifactInfo.PackageId)" Condition="'%(ArtifactInfo.ArtifactType)' == 'NuGetPackage'" />
+ <_UndeclaredPackageArtifact Remove="@(PackageArtifact)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/sources.props b/build/sources.props
new file mode 100644
index 0000000000..5b458ddf09
--- /dev/null
+++ b/build/sources.props
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ $(DotNetAdditionalRestoreSources);
+ $(DotNetRestoreSources);
+
+
+ $(RestoreSources);
+ https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json;
+ https://api.nuget.org/v3/index.json;
+
+
+ $(RestoreSources);
+ https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
+ https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
+ https://dotnet.myget.org/F/aspnetcore-master/api/v3/index.json;
+ https://dotnet.myget.org/F/roslyn/api/v3/index.json;
+ https://vside.myget.org/F/vssdk/api/v3/index.json;
+ https://vside.myget.org/F/vsmac/api/v3/index.json
+
+
+
diff --git a/build/submodules.props b/build/submodules.props
new file mode 100644
index 0000000000..8f60821673
--- /dev/null
+++ b/build/submodules.props
@@ -0,0 +1,87 @@
+
+
+
+ true
+
+
+ ProductChangesOnly
+
+
+
+ false
+ ProductChangesOnly
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/tasks/AddMetapackageReferences.cs b/build/tasks/AddMetapackageReferences.cs
new file mode 100644
index 0000000000..3cb11666ab
--- /dev/null
+++ b/build/tasks/AddMetapackageReferences.cs
@@ -0,0 +1,134 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Xml;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using NuGet.Versioning;
+using RepoTasks.Utilities;
+
+namespace RepoTasks
+{
+ public class AddMetapackageReferences : Task
+ {
+ [Required]
+ public string ReferencePackagePath { get; set; }
+
+ [Required]
+ public string MetapackageReferenceType { get; set; }
+
+ [Required]
+ public string DependencyVersionRangeType { get; set; }
+
+ // MSBuild doesn't allow binding to enums directly.
+ private enum VersionRangeType
+ {
+ Minimum, // [1.1.1, )
+ MajorMinor, // [1.1.1, 1.2.0)
+ }
+
+ [Required]
+ public ITaskItem[] PackageArtifacts { get; set; }
+
+ [Required]
+ public ITaskItem[] ExternalDependencies { get; set; }
+
+ public override bool Execute()
+ {
+ if (!Enum.TryParse(DependencyVersionRangeType, out var dependencyVersionType))
+ {
+ Log.LogError("Unexpected value {0} for DependencyVersionRangeType", DependencyVersionRangeType);
+ return false;
+ }
+
+ // Parse input
+ var metapackageArtifacts = PackageArtifacts.Where(p => p.GetMetadata(MetapackageReferenceType) == "true");
+ var externalArtifacts = ExternalDependencies.Where(p => p.GetMetadata(MetapackageReferenceType) == "true");
+
+ var xmlDoc = new XmlDocument();
+ xmlDoc.Load(ReferencePackagePath);
+
+ // Project
+ var projectElement = xmlDoc.FirstChild;
+
+ // Items
+ var itemGroupElement = xmlDoc.CreateElement("ItemGroup");
+ Log.LogMessage(MessageImportance.High, $"{MetapackageReferenceType} will include the following packages");
+
+ foreach (var package in metapackageArtifacts)
+ {
+ var packageName = package.ItemSpec;
+ var packageVersion = package.GetMetadata("Version");
+ if (string.IsNullOrEmpty(packageVersion))
+ {
+ Log.LogError("Missing version information for package {0}", packageName);
+ continue;
+ }
+
+ var packageVersionValue = GetDependencyVersion(dependencyVersionType, packageName, packageVersion);
+ Log.LogMessage(MessageImportance.High, $" - Package: {packageName} Version: {packageVersionValue}");
+
+ var packageReferenceElement = xmlDoc.CreateElement("PackageReference");
+ packageReferenceElement.SetAttribute("Include", packageName);
+ packageReferenceElement.SetAttribute("Version", packageVersionValue);
+ packageReferenceElement.SetAttribute("PrivateAssets", "None");
+
+ itemGroupElement.AppendChild(packageReferenceElement);
+ }
+
+ foreach (var package in externalArtifacts)
+ {
+ var packageName = package.ItemSpec;
+ var packageVersion = package.GetMetadata("Version");
+
+ if (string.IsNullOrEmpty(packageVersion))
+ {
+ Log.LogError("Missing version information for package {0}", packageName);
+ continue;
+ }
+
+ var packageVersionValue =
+ Enum.TryParse(package.GetMetadata("MetapackageVersionRangeType"), out var packageVersionType)
+ ? GetDependencyVersion(packageVersionType, packageName, packageVersion)
+ : GetDependencyVersion(dependencyVersionType, packageName, packageVersion);
+
+ Log.LogMessage(MessageImportance.High, $" - Package: {packageName} Version: {packageVersionValue}");
+
+ var packageReferenceElement = xmlDoc.CreateElement("PackageReference");
+ packageReferenceElement.SetAttribute("Include", packageName);
+ packageReferenceElement.SetAttribute("Version", packageVersionValue);
+ packageReferenceElement.SetAttribute("PrivateAssets", "None");
+
+ itemGroupElement.AppendChild(packageReferenceElement);
+ }
+
+ projectElement.AppendChild(itemGroupElement);
+
+ // Save updated file
+ xmlDoc.AppendChild(projectElement);
+ xmlDoc.Save(ReferencePackagePath);
+
+ return !Log.HasLoggedErrors;
+ }
+
+ private string GetDependencyVersion(VersionRangeType dependencyVersionType, string packageName, string packageVersion)
+ {
+ switch (dependencyVersionType)
+ {
+ case VersionRangeType.MajorMinor:
+ if (!NuGetVersion.TryParse(packageVersion, out var nugetVersion))
+ {
+ Log.LogError("Invalid NuGet version '{0}' for package {1}", packageVersion, packageName);
+ return null;
+ }
+ return $"[{packageVersion}, {nugetVersion.Major}.{nugetVersion.Minor + 1}.0)";
+ case VersionRangeType.Minimum:
+ return packageVersion;
+ default:
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/build/tasks/CheckExpectedPackagesExist.cs b/build/tasks/CheckExpectedPackagesExist.cs
new file mode 100644
index 0000000000..0df55e5d47
--- /dev/null
+++ b/build/tasks/CheckExpectedPackagesExist.cs
@@ -0,0 +1,69 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.Build.Framework;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+using RepoTasks.Utilities;
+
+namespace RepoTasks
+{
+ public class CheckExpectedPackagesExist : Microsoft.Build.Utilities.Task
+ {
+ ///
+ /// The item group containing the nuget packages to split in different folders.
+ ///
+ [Required]
+ public ITaskItem[] Packages { get; set; }
+
+ [Required]
+ public ITaskItem[] Files { get; set; }
+
+ public override bool Execute()
+ {
+ if (Files?.Length == 0)
+ {
+ Log.LogError("No packages were found.");
+ return false;
+ }
+
+ var expectedPackages = new HashSet(Packages.Select(i => i.ItemSpec), StringComparer.OrdinalIgnoreCase);
+
+ foreach (var file in Files)
+ {
+ PackageIdentity identity;
+ using (var reader = new PackageArchiveReader(file.ItemSpec))
+ {
+ identity = reader.GetIdentity();
+ }
+
+ if (!expectedPackages.Contains(identity.Id))
+ {
+ Log.LogError($"Unexpected package artifact with id: {identity.Id}");
+ continue;
+ }
+
+ expectedPackages.Remove(identity.Id);
+ }
+
+ if (expectedPackages.Count != 0)
+ {
+ var error = new StringBuilder();
+ foreach (var id in expectedPackages)
+ {
+ error.Append(" - ").AppendLine(id);
+ }
+
+ Log.LogError($"Expected the following packages, but they were not found:" + error.ToString());
+ return false;
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/build/tasks/CheckVersionOverrides.cs b/build/tasks/CheckVersionOverrides.cs
new file mode 100644
index 0000000000..ad75aa8b2b
--- /dev/null
+++ b/build/tasks/CheckVersionOverrides.cs
@@ -0,0 +1,45 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using Microsoft.Build.Construction;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace RepoTasks
+{
+ public class CheckVersionOverrides : Task
+ {
+ [Required]
+ public string DotNetPackageVersionPropsPath { get; set; }
+
+ [Required]
+ public string DependenciesFile { get; set; }
+
+ public override bool Execute()
+ {
+ Log.LogMessage($"Verifying versions set in {DotNetPackageVersionPropsPath} match expected versions set in {DependenciesFile}");
+
+ var versionOverrides = ProjectRootElement.Open(DotNetPackageVersionPropsPath);
+ var dependencies = ProjectRootElement.Open(DependenciesFile);
+ var pinnedVersions = dependencies.PropertyGroups
+ .Where(p => !string.Equals("Package Versions: Auto", p.Label))
+ .SelectMany(p => p.Properties)
+ .ToDictionary(p => p.Name, p => p.Value, StringComparer.OrdinalIgnoreCase);
+
+ foreach (var prop in versionOverrides.Properties)
+ {
+ if (pinnedVersions.TryGetValue(prop.Name, out var pinnedVersion))
+ {
+ if (!string.Equals(pinnedVersion, prop.Value, StringComparison.OrdinalIgnoreCase))
+ {
+ Log.LogError($"The imported package version props file conflicts with a pinned version variable {prop.Name}. Imported value: {prop.Value}, Pinned value: {pinnedVersion}");
+ }
+ }
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/build/tasks/CreateLzma.cs b/build/tasks/CreateLzma.cs
new file mode 100644
index 0000000000..96e3d403b7
--- /dev/null
+++ b/build/tasks/CreateLzma.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.DotNet.Archive;
+
+namespace RepoTasks
+{
+ public class CreateLzma : Task, ICancelableTask
+ {
+ private readonly CancellationTokenSource _cts = new CancellationTokenSource();
+
+ [Required]
+ public string OutputPath { get; set; }
+
+ [Required]
+ public string[] Sources { get; set; }
+
+ public void Cancel() => _cts.Cancel();
+
+ public override bool Execute()
+ {
+ var progress = new MSBuildProgressReport(Log, _cts.Token);
+ using (var archive = new IndexedArchive())
+ {
+ foreach (var source in Sources)
+ {
+ if (Directory.Exists(source))
+ {
+ var trimmedSource = source.TrimEnd(new []{ '\\', '/' });
+ Log.LogMessage(MessageImportance.High, $"Adding directory: {trimmedSource}");
+ archive.AddDirectory(trimmedSource, progress);
+ }
+ else
+ {
+ Log.LogMessage(MessageImportance.High, $"Adding file: {source}");
+ archive.AddFile(source, Path.GetFileName(source));
+ }
+ }
+
+ archive.Save(OutputPath, progress);
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+
+ private class MSBuildProgressReport : IProgress
+ {
+ private TaskLoggingHelper _log;
+ private readonly CancellationToken _cancellationToken;
+
+ public MSBuildProgressReport(TaskLoggingHelper log, CancellationToken cancellationToken)
+ {
+ _log = log;
+ _cancellationToken = cancellationToken;
+ }
+
+ public void Report(ProgressReport value)
+ {
+ var complete = (double)value.Ticks / value.Total;
+ _log.LogMessage(MessageImportance.Low, $"Progress: {value.Phase} - {complete:P}");
+ _cancellationToken.ThrowIfCancellationRequested(); // because LZMA apis don't take a cancellation token, throw from the logger (yes, its ugly, but it works.)
+ }
+ }
+ }
+}
diff --git a/build/tasks/GenerateRestoreSourcesPropsFile.cs b/build/tasks/GenerateRestoreSourcesPropsFile.cs
new file mode 100644
index 0000000000..2d27ba4e6a
--- /dev/null
+++ b/build/tasks/GenerateRestoreSourcesPropsFile.cs
@@ -0,0 +1,55 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.IO;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace RepoTasks
+{
+ public class GenerateRestoreSourcesPropsFile : Task
+ {
+ [Required]
+ public ITaskItem[] Sources { get; set; }
+
+ [Required]
+ public string OutputPath { get; set; }
+
+ public override bool Execute()
+ {
+ OutputPath = OutputPath.Replace('\\', '/');
+ Directory.CreateDirectory(Path.GetDirectoryName(OutputPath));
+
+ var sources = new XElement("DotNetRestoreSources");
+ var propertyGroup = new XElement("PropertyGroup", sources);
+ var doc = new XDocument(new XElement("Project", propertyGroup));
+
+ propertyGroup.Add(new XElement("MSBuildAllProjects", "$(MSBuildAllProjects);$(MSBuildThisFileFullPath)"));
+
+ var sb = new StringBuilder();
+
+ foreach (var source in Sources)
+ {
+ sb.Append(source.ItemSpec).AppendLine(";");
+ }
+
+ sources.SetValue(sb.ToString());
+
+ var settings = new XmlWriterSettings
+ {
+ OmitXmlDeclaration = true,
+ };
+ using (var writer = XmlWriter.Create(OutputPath, settings))
+ {
+ Log.LogMessage(MessageImportance.Normal, $"Generate {OutputPath}");
+ doc.Save(writer);
+ }
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/build/tasks/GetDocXmlFiles.cs b/build/tasks/GetDocXmlFiles.cs
new file mode 100644
index 0000000000..9053f0c6ad
--- /dev/null
+++ b/build/tasks/GetDocXmlFiles.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Build.Framework;
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace RepoTasks
+{
+ ///
+ /// Filters a list of .xml files to only those that are .NET Xml docs files
+ ///
+ public class GetDocXmlFiles : Microsoft.Build.Utilities.Task
+ {
+ [Required]
+ public ITaskItem[] Files { get; set; }
+
+ [Output]
+ public ITaskItem[] XmlDocFiles { get; set; }
+
+ public override bool Execute()
+ {
+ var xmlDocs = new ConcurrentBag();
+ Parallel.ForEach(Files, f =>
+ {
+ try
+ {
+ using (var file = File.OpenRead(f.ItemSpec))
+ using (var reader = new StreamReader(file))
+ {
+ string line;
+ for (var i = 0; i < 2; i++)
+ {
+ line = reader.ReadLine();
+ if (i == 0 && line.StartsWith("") + 2);
+ }
+
+ if (line.StartsWith("", StringComparison.OrdinalIgnoreCase) || line.StartsWith(" GetKeyValue(LeftKey, item),
+ item => GetKeyValue(RightKey, item),
+ (left, right) =>
+ {
+ // If including all metadata from left items and none from right items, just return left items directly
+ if (useAllLeftMetadata &&
+ string.IsNullOrEmpty(LeftKey) &&
+ string.IsNullOrEmpty(LeftItemSpec) &&
+ (RightMetadata == null || RightMetadata.Length == 0))
+ {
+ return left;
+ }
+
+ // If including all metadata from right items and none from left items, just return the right items directly
+ if (useAllRightMetadata &&
+ string.IsNullOrEmpty(RightKey) &&
+ string.IsNullOrEmpty(LeftItemSpec) &&
+ (LeftMetadata == null || LeftMetadata.Length == 0))
+ {
+ return right;
+ }
+
+ var ret = new TaskItem(GetKeyValue(newItemSpec, left));
+
+ // Weird ordering here is to prefer left metadata in all cases, as CopyToMetadata doesn't overwrite any existing metadata
+ if (useAllLeftMetadata)
+ {
+ // CopyMetadata adds an OriginalItemSpec, which we don't want. So we subsequently remove it
+ left.CopyMetadataTo(ret);
+ ret.RemoveMetadata("OriginalItemSpec");
+ }
+
+ if (!useAllRightMetadata && RightMetadata != null)
+ {
+ foreach (string name in RightMetadata)
+ {
+ ret.SetMetadata(name, right.GetMetadata(name));
+ }
+ }
+
+ if (!useAllLeftMetadata && LeftMetadata != null)
+ {
+ foreach (string name in LeftMetadata)
+ {
+ ret.SetMetadata(name, left.GetMetadata(name));
+ }
+ }
+
+ if (useAllRightMetadata)
+ {
+ // CopyMetadata adds an OriginalItemSpec, which we don't want. So we subsequently remove it
+ right.CopyMetadataTo(ret);
+ ret.RemoveMetadata("OriginalItemSpec");
+ }
+
+ return (ITaskItem)ret;
+ },
+ StringComparer.OrdinalIgnoreCase).ToArray();
+
+ return true;
+ }
+
+ static string GetKeyValue(string key, ITaskItem item)
+ {
+ if (string.IsNullOrEmpty(key))
+ {
+ return item.ItemSpec;
+ }
+ else
+ {
+ return item.GetMetadata(key);
+ }
+ }
+ }
+}
diff --git a/build/tasks/Logger/FlowLogger.cs b/build/tasks/Logger/FlowLogger.cs
new file mode 100644
index 0000000000..e6a214fdff
--- /dev/null
+++ b/build/tasks/Logger/FlowLogger.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging;
+
+namespace RepoTasks
+{
+ public class FlowLogger : ConsoleLogger
+ {
+ private volatile bool _initialized;
+
+ public FlowLogger()
+ {
+ }
+
+ public override void Initialize(IEventSource eventSource, int nodeCount)
+ {
+ PreInit(eventSource);
+ base.Initialize(eventSource, nodeCount);
+ }
+
+ public override void Initialize(IEventSource eventSource)
+ {
+ PreInit(eventSource);
+ base.Initialize(eventSource);
+ }
+
+ private void PreInit(IEventSource eventSource)
+ {
+ if (_initialized) return;
+ _initialized = true;
+
+ var flowId = GetFlowId();
+ var prefix = $"{flowId,-22}| ";
+ var write = WriteHandler;
+ WriteHandler = msg => write(prefix + msg);
+
+ eventSource.BuildStarted += (o, e) =>
+ {
+ WriteHandler(e.Message + Environment.NewLine);
+ };
+ }
+
+ private string GetFlowId()
+ {
+ var parameters = Parameters?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
+ if (parameters == null || parameters.Length == 0)
+ {
+ return null;
+ }
+
+ const string flowIdParamName = "FlowId=";
+ return parameters
+ .FirstOrDefault(p => p.StartsWith(flowIdParamName, StringComparison.Ordinal))
+ ?.Substring(flowIdParamName.Length);
+ }
+ }
+}
diff --git a/build/tasks/Logger/MSBuildLogger.cs b/build/tasks/Logger/MSBuildLogger.cs
new file mode 100644
index 0000000000..458bd698d9
--- /dev/null
+++ b/build/tasks/Logger/MSBuildLogger.cs
@@ -0,0 +1,200 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using NuGet.Common;
+
+namespace NuGet.Build
+{
+ ///
+ /// TaskLoggingHelper -> ILogger
+ ///
+ internal class MSBuildLogger : LoggerBase, Common.ILogger
+ {
+ private readonly TaskLoggingHelper _taskLogging;
+
+ private delegate void LogMessageWithDetails(string subcategory,
+ string code,
+ string helpKeyword,
+ string file,
+ int lineNumber,
+ int columnNumber,
+ int endLineNumber,
+ int endColumnNumber,
+ MessageImportance importance,
+ string message,
+ params object[] messageArgs);
+
+ private delegate void LogErrorWithDetails(string subcategory,
+ string code,
+ string helpKeyword,
+ string file,
+ int lineNumber,
+ int columnNumber,
+ int endLineNumber,
+ int endColumnNumber,
+ string message,
+ params object[] messageArgs);
+
+ private delegate void LogMessageAsString(MessageImportance importance,
+ string message,
+ params object[] messageArgs);
+
+ private delegate void LogErrorAsString(string message,
+ params object[] messageArgs);
+
+ public MSBuildLogger(TaskLoggingHelper taskLogging)
+ {
+ _taskLogging = taskLogging ?? throw new ArgumentNullException(nameof(taskLogging));
+ }
+
+ public override void Log(ILogMessage message)
+ {
+ if (DisplayMessage(message.Level))
+ {
+ if (RuntimeEnvironmentHelper.IsMono)
+ {
+ LogForMono(message);
+ return;
+ }
+ else
+ {
+ var logMessage = message as IRestoreLogMessage;
+
+ if (logMessage == null)
+ {
+ logMessage = new RestoreLogMessage(message.Level, message.Message)
+ {
+ Code = message.Code,
+ FilePath = message.ProjectPath
+ };
+ }
+ LogForNonMono(logMessage);
+ }
+ }
+ }
+
+ ///
+ /// Log using with metadata for non mono platforms.
+ ///
+ private void LogForNonMono(IRestoreLogMessage message)
+ {
+ switch (message.Level)
+ {
+ case LogLevel.Error:
+ LogError(message, _taskLogging.LogError, _taskLogging.LogError);
+ break;
+
+ case LogLevel.Warning:
+ LogError(message, _taskLogging.LogWarning, _taskLogging.LogWarning);
+ break;
+
+ case LogLevel.Minimal:
+ LogMessage(message, MessageImportance.High, _taskLogging.LogMessage, _taskLogging.LogMessage);
+ break;
+
+ case LogLevel.Information:
+ LogMessage(message, MessageImportance.Normal, _taskLogging.LogMessage, _taskLogging.LogMessage);
+ break;
+
+ case LogLevel.Debug:
+ case LogLevel.Verbose:
+ default:
+ // Default to LogLevel.Debug and low importance
+ LogMessage(message, MessageImportance.Low, _taskLogging.LogMessage, _taskLogging.LogMessage);
+ break;
+ }
+ }
+
+ ///
+ /// Log using basic methods to avoid missing methods on mono.
+ ///
+ private void LogForMono(ILogMessage message)
+ {
+ switch (message.Level)
+ {
+ case LogLevel.Error:
+ _taskLogging.LogError(message.Message);
+ break;
+
+ case LogLevel.Warning:
+ _taskLogging.LogWarning(message.Message);
+ break;
+
+ case LogLevel.Minimal:
+ _taskLogging.LogMessage(MessageImportance.High, message.Message);
+ break;
+
+ case LogLevel.Information:
+ _taskLogging.LogMessage(MessageImportance.Normal, message.Message);
+ break;
+
+ case LogLevel.Debug:
+ case LogLevel.Verbose:
+ default:
+ // Default to LogLevel.Debug and low importance
+ _taskLogging.LogMessage(MessageImportance.Low, message.Message);
+ break;
+ }
+
+ return;
+ }
+
+ private void LogMessage(IRestoreLogMessage logMessage,
+ MessageImportance importance,
+ LogMessageWithDetails logWithDetails,
+ LogMessageAsString logAsString)
+ {
+ if (logMessage.Code > NuGetLogCode.Undefined)
+ {
+ // NuGet does not currently have a subcategory while throwing logs, hence string.Empty
+ logWithDetails(string.Empty,
+ Enum.GetName(typeof(NuGetLogCode), logMessage.Code),
+ Enum.GetName(typeof(NuGetLogCode), logMessage.Code),
+ logMessage.FilePath,
+ logMessage.StartLineNumber,
+ logMessage.StartColumnNumber,
+ logMessage.EndLineNumber,
+ logMessage.EndColumnNumber,
+ importance,
+ logMessage.Message);
+ }
+ else
+ {
+ logAsString(importance, logMessage.Message);
+ }
+ }
+
+ private void LogError(IRestoreLogMessage logMessage,
+ LogErrorWithDetails logWithDetails,
+ LogErrorAsString logAsString)
+ {
+ if (logMessage.Code > NuGetLogCode.Undefined)
+ {
+ // NuGet does not currently have a subcategory while throwing logs, hence string.Empty
+ logWithDetails(string.Empty,
+ Enum.GetName(typeof(NuGetLogCode), logMessage.Code),
+ Enum.GetName(typeof(NuGetLogCode), logMessage.Code),
+ logMessage.FilePath,
+ logMessage.StartLineNumber,
+ logMessage.StartColumnNumber,
+ logMessage.EndLineNumber,
+ logMessage.EndColumnNumber,
+ logMessage.Message);
+ }
+ else
+ {
+ logAsString(logMessage.Message);
+ }
+ }
+
+ public override System.Threading.Tasks.Task LogAsync(ILogMessage message)
+ {
+ Log(message);
+
+ return System.Threading.Tasks.Task.FromResult(0);
+ }
+ }
+}
diff --git a/build/tasks/OrderBy.cs b/build/tasks/OrderBy.cs
new file mode 100644
index 0000000000..a761d6f093
--- /dev/null
+++ b/build/tasks/OrderBy.cs
@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace RepoTasks
+{
+ public class OrderBy : Task
+ {
+ [Required]
+ [Output]
+ public ITaskItem[] Items { get; set; }
+
+ public string Key { get; set; }
+
+ public override bool Execute()
+ {
+ var key = string.IsNullOrEmpty(Key)
+ ? "Identity"
+ : Key;
+ Items = Items.OrderBy(k => k.GetMetadata(key)).ToArray();
+ return true;
+ }
+ }
+}
diff --git a/build/tasks/ProcessSharedFrameworkDeps.cs b/build/tasks/ProcessSharedFrameworkDeps.cs
new file mode 100644
index 0000000000..87250780a8
--- /dev/null
+++ b/build/tasks/ProcessSharedFrameworkDeps.cs
@@ -0,0 +1,76 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Sourced from https://github.com/dotnet/core-setup/tree/be8d8e3486b2bf598ed69d39b1629a24caaba45e/tools-local/tasks, needs to be kept in sync
+
+using System;
+using System.IO;
+using System.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.Extensions.DependencyModel;
+using NuGet.Common;
+using NuGet.ProjectModel;
+using RepoTasks.Utilities;
+
+namespace RepoTasks
+{
+ public partial class ProcessSharedFrameworkDeps : Task
+ {
+ [Required]
+ public string AssetsFilePath { get; set; }
+
+ [Required]
+ public string DepsFilePath { get; set; }
+
+ public string[] PackagesToRemove { get; set; }
+
+ [Required]
+ public string Runtime { get; set; }
+
+ public override bool Execute()
+ {
+ ExecuteCore();
+
+ return true;
+ }
+
+ private void ExecuteCore()
+ {
+ DependencyContext context;
+ using (var depsStream = File.OpenRead(DepsFilePath))
+ {
+ context = new DependencyContextJsonReader().Read(depsStream);
+ }
+
+ LockFile lockFile = LockFileUtilities.GetLockFile(AssetsFilePath, NullLogger.Instance);
+ if (lockFile == null)
+ {
+ throw new ArgumentException($"Could not load a LockFile at '{AssetsFilePath}'.", nameof(AssetsFilePath));
+ }
+
+ var manager = new RuntimeGraphManager();
+ var graph = manager.Collect(lockFile);
+ var expandedGraph = manager.Expand(graph, Runtime);
+
+ var trimmedRuntimeLibraries = context.RuntimeLibraries;
+
+ if (PackagesToRemove != null && PackagesToRemove.Any())
+ {
+ trimmedRuntimeLibraries = RuntimeReference.RemoveReferences(context.RuntimeLibraries, PackagesToRemove);
+ }
+
+ context = new DependencyContext(
+ context.Target,
+ context.CompilationOptions,
+ context.CompileLibraries,
+ trimmedRuntimeLibraries,
+ expandedGraph
+ );
+
+ using (var depsStream = File.Create(DepsFilePath))
+ {
+ new DependencyContextWriter().Write(context, depsStream);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build/tasks/PublishToAzureBlob.cs b/build/tasks/PublishToAzureBlob.cs
new file mode 100644
index 0000000000..86150d0be3
--- /dev/null
+++ b/build/tasks/PublishToAzureBlob.cs
@@ -0,0 +1,144 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Blob;
+
+namespace RepoTasks
+{
+ ///
+ /// Publish files to an Azure storage blob
+ ///
+ public class PublishToAzureBlob : Microsoft.Build.Utilities.Task, ICancelableTask
+ {
+ private CancellationTokenSource _cts = new CancellationTokenSource();
+
+ ///
+ /// The files to publish.
+ ///
+ [Required]
+ public ITaskItem[] Files { get; set; }
+
+ ///
+ /// The Azure blob storage account name.
+ ///
+ [Required]
+ public string AccountName { get; set; }
+
+ ///
+ /// The SAS token used to write to Azure.
+ ///
+ [Required]
+ public string SharedAccessToken { get; set; }
+
+ ///
+ /// The Azure blob storage container name
+ ///
+ [Required]
+ public string ContainerName { get; set; }
+
+ ///
+ /// The maximum number of parallel pushes.
+ ///
+ public int MaxParallelism { get; set; } = 8;
+
+ public void Cancel() => _cts.Cancel();
+
+ public override bool Execute()
+ => ExecuteAsync().Result;
+
+ private async Task ExecuteAsync()
+ {
+ var connectionString = $"BlobEndpoint=https://{AccountName}.blob.core.windows.net;SharedAccessSignature={SharedAccessToken}";
+
+ var account = CloudStorageAccount.Parse(connectionString);
+ var client = account.CreateCloudBlobClient();
+ var container = client.GetContainerReference(ContainerName);
+
+ var ctx = new OperationContext();
+ var tasks = new List();
+
+ using (var throttler = new SemaphoreSlim(MaxParallelism))
+ {
+ foreach (var item in Files)
+ {
+ _cts.Token.ThrowIfCancellationRequested();
+ await throttler.WaitAsync( _cts.Token);
+ tasks.Add(
+ Task.Run(async () =>
+ {
+ try
+ {
+ await PushFileAsync(ctx, container, item, _cts.Token);
+ }
+ finally
+ {
+ throttler.Release();
+ }
+ }));
+ }
+
+ await Task.WhenAll(tasks);
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+
+ private async Task PushFileAsync(OperationContext ctx, CloudBlobContainer container, ITaskItem item, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // normalize slashes
+ var dest = item.GetMetadata("RelativeBlobPath")
+ .Replace('\\', '/')
+ .Replace("//", "/");
+ var contentType = item.GetMetadata("ContentType");
+ var cacheControl = item.GetMetadata("CacheControl");
+
+ if (string.IsNullOrEmpty(dest))
+ {
+ Log.LogError($"Item {item.ItemSpec} is missing required metadata 'RelativeBlobPath'");
+ return;
+ }
+
+ var blob = container.GetBlockBlobReference(dest);
+
+ if (!string.IsNullOrEmpty(cacheControl))
+ {
+ blob.Properties.CacheControl = cacheControl;
+ }
+
+ if (!string.IsNullOrEmpty(contentType))
+ {
+ blob.Properties.ContentType = contentType;
+ }
+
+ Log.LogMessage(MessageImportance.High, $"Beginning push of {item.ItemSpec} to https://{AccountName}.blob.core.windows.net/{ContainerName}/{dest}");
+
+ var accessCondition = bool.TryParse(item.GetMetadata("Overwrite"), out var overwrite) && overwrite
+ ? AccessCondition.GenerateEmptyCondition()
+ : AccessCondition.GenerateIfNotExistsCondition();
+
+ try
+ {
+ await blob.UploadFromFileAsync(item.ItemSpec, accessCondition, new BlobRequestOptions(), ctx, cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ Log.LogError($"Error publishing {item.ItemSpec}: {ex}");
+ return;
+ }
+ finally
+ {
+ Log.LogMessage(MessageImportance.High, $"Done publishing {item.ItemSpec} to https://{AccountName}.blob.core.windows.net/{ContainerName}/{dest}");
+ }
+ }
+ }
+}
diff --git a/build/tasks/RepoTasks.csproj b/build/tasks/RepoTasks.csproj
new file mode 100644
index 0000000000..6be1f375d2
--- /dev/null
+++ b/build/tasks/RepoTasks.csproj
@@ -0,0 +1,17 @@
+
+
+
+
+ netcoreapp2.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/tasks/RepoTasks.tasks b/build/tasks/RepoTasks.tasks
new file mode 100644
index 0000000000..5339c01d01
--- /dev/null
+++ b/build/tasks/RepoTasks.tasks
@@ -0,0 +1,29 @@
+
+
+ <_RepoTaskAssembly>$(MSBuildThisFileDirectory)bin\publish\RepoTasks.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(NUGET_PACKAGES)
+ $(USERPROFILE)\.nuget\packages\
+ $(HOME)\.nuget\packages\
+ $([MSBuild]::NormalizeDirectory($(NuGetPackagesPath)))
+ <_MicrosoftDotNetBuildTasksFeedTaskDir>$(NuGetPackagesPath)microsoft.dotnet.build.tasks.feed\$(DevDependency_MicrosoftDotNetBuildTasksFeedPackageVersion.ToLowerInvariant())\build\netstandard1.5\
+
+
+
+
diff --git a/build/tasks/ResolveSymbolsRecursivePath.cs b/build/tasks/ResolveSymbolsRecursivePath.cs
new file mode 100644
index 0000000000..21dd795fcd
--- /dev/null
+++ b/build/tasks/ResolveSymbolsRecursivePath.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using RepoTasks.Utilities;
+
+namespace RepoTasks
+{
+ public class ResolveSymbolsRecursivePath : Task
+ {
+ [Required]
+ [Output]
+ public ITaskItem[] Symbols { get; set; }
+
+ public override bool Execute()
+ {
+ foreach (var symbol in Symbols)
+ {
+ var fullPath = symbol.GetMetadata("PortablePDB");
+ symbol.SetMetadata("SymbolsRecursivePath", fullPath.Substring(fullPath.IndexOf($"{Path.DirectorySeparatorChar}lib{Path.DirectorySeparatorChar}")));
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/build/tasks/TrimDeps.cs b/build/tasks/TrimDeps.cs
new file mode 100644
index 0000000000..b63bdb35cc
--- /dev/null
+++ b/build/tasks/TrimDeps.cs
@@ -0,0 +1,60 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace RepoTasks
+{
+ public class TrimDeps : Task
+ {
+ [Required]
+ public ITaskItem[] DepsFiles { get; set; }
+
+ public override bool Execute()
+ {
+ foreach (var depsFile in DepsFiles)
+ {
+ ChangeEntryPointLibraryName(depsFile.GetMetadata("Identity"));
+ }
+
+ // Parse input
+ return true;
+ }
+
+
+ private void ChangeEntryPointLibraryName(string depsFile)
+ {
+ JToken deps;
+ using (var file = File.OpenText(depsFile))
+ using (JsonTextReader reader = new JsonTextReader(file))
+ {
+ deps = JObject.ReadFrom(reader);
+ }
+
+ foreach (JProperty target in deps["targets"])
+ {
+ var targetLibrary = target.Value.Children().FirstOrDefault();
+ if (targetLibrary == null)
+ {
+ continue;
+ }
+
+ targetLibrary.Remove();
+ }
+
+ var library = deps["libraries"].Children().First();
+ library.Remove();
+
+ using (var file = File.CreateText(depsFile))
+ using (var writer = new JsonTextWriter(file) { Formatting = Formatting.Indented })
+ {
+ deps.WriteTo(writer);
+ }
+ }
+ }
+}
diff --git a/build/tasks/Utilities/RuntimeGraphManager.cs b/build/tasks/Utilities/RuntimeGraphManager.cs
new file mode 100644
index 0000000000..d3552f36be
--- /dev/null
+++ b/build/tasks/Utilities/RuntimeGraphManager.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Sourced from https://github.com/dotnet/core-setup/tree/be8d8e3486b2bf598ed69d39b1629a24caaba45e/tools-local/tasks, needs to be kept in sync
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Extensions.DependencyModel;
+using NuGet.Frameworks;
+using NuGet.Packaging;
+using NuGet.ProjectModel;
+using NuGet.RuntimeModel;
+
+namespace RepoTasks.Utilities
+{
+ internal class RuntimeGraphManager
+ {
+ private const string RuntimeJsonFileName = "runtime.json";
+
+ public RuntimeGraph Collect(LockFile lockFile)
+ {
+ string userPackageFolder = lockFile.PackageFolders.FirstOrDefault()?.Path;
+ var fallBackFolders = lockFile.PackageFolders.Skip(1).Select(f => f.Path);
+ var packageResolver = new FallbackPackagePathResolver(userPackageFolder, fallBackFolders);
+
+ var graph = RuntimeGraph.Empty;
+ foreach (var library in lockFile.Libraries)
+ {
+ if (string.Equals(library.Type, "package", StringComparison.OrdinalIgnoreCase))
+ {
+ var runtimeJson = library.Files.FirstOrDefault(f => f == RuntimeJsonFileName);
+ if (runtimeJson != null)
+ {
+ var libraryPath = packageResolver.GetPackageDirectory(library.Name, library.Version);
+ var runtimeJsonFullName = Path.Combine(libraryPath, runtimeJson);
+ graph = RuntimeGraph.Merge(graph, JsonRuntimeFormat.ReadRuntimeGraph(runtimeJsonFullName));
+ }
+ }
+ }
+ return graph;
+ }
+
+ public IEnumerable Expand(RuntimeGraph runtimeGraph, string runtime)
+ {
+ var importers = FindImporters(runtimeGraph, runtime);
+ foreach (var importer in importers)
+ {
+ // ExpandRuntime return runtime itself as first item so we are skiping it
+ yield return new RuntimeFallbacks(importer, runtimeGraph.ExpandRuntime(importer).Skip(1));
+ }
+ }
+
+ private IEnumerable FindImporters(RuntimeGraph runtimeGraph, string runtime)
+ {
+ foreach (var runtimePair in runtimeGraph.Runtimes)
+ {
+ var expanded = runtimeGraph.ExpandRuntime(runtimePair.Key);
+ if (expanded.Contains(runtime))
+ {
+ yield return runtimePair.Key;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build/tasks/Utilities/RuntimeReference.cs b/build/tasks/Utilities/RuntimeReference.cs
new file mode 100644
index 0000000000..4907a73723
--- /dev/null
+++ b/build/tasks/Utilities/RuntimeReference.cs
@@ -0,0 +1,63 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+// Sourced from https://github.com/dotnet/core-setup/tree/be8d8e3486b2bf598ed69d39b1629a24caaba45e/tools-local/tasks, needs to be kept in sync
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.DependencyModel;
+
+namespace RepoTasks.Utilities
+{
+ internal class RuntimeReference
+ {
+ public static List RemoveReferences(IReadOnlyList runtimeLibraries, IEnumerable packages)
+ {
+ List result = new List();
+
+ foreach (var runtimeLib in runtimeLibraries)
+ {
+ if (string.IsNullOrEmpty(packages.FirstOrDefault(elem => runtimeLib.Name.Equals(elem, StringComparison.OrdinalIgnoreCase))))
+ {
+ List toRemoveDependecy = new List();
+ foreach (var dependency in runtimeLib.Dependencies)
+ {
+ if (!string.IsNullOrEmpty(packages.FirstOrDefault(elem => dependency.Name.Equals(elem, StringComparison.OrdinalIgnoreCase))))
+ {
+ toRemoveDependecy.Add(dependency);
+ }
+ }
+
+ if (toRemoveDependecy.Count > 0)
+ {
+ List modifiedDependencies = new List();
+ foreach (var dependency in runtimeLib.Dependencies)
+ {
+ if (!toRemoveDependecy.Contains(dependency))
+ {
+ modifiedDependencies.Add(dependency);
+ }
+ }
+
+
+ result.Add(new RuntimeLibrary(runtimeLib.Type,
+ runtimeLib.Name,
+ runtimeLib.Version,
+ runtimeLib.Hash,
+ runtimeLib.RuntimeAssemblyGroups,
+ runtimeLib.NativeLibraryGroups,
+ runtimeLib.ResourceAssemblies,
+ modifiedDependencies,
+ runtimeLib.Serviceable));
+
+ }
+ else if (string.IsNullOrEmpty(packages.FirstOrDefault(elem => runtimeLib.Name.Equals(elem, StringComparison.OrdinalIgnoreCase))))
+ {
+ result.Add(runtimeLib);
+ }
+ }
+ }
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/build/tasks/tasks.sln b/build/tasks/tasks.sln
new file mode 100644
index 0000000000..917ff4ff83
--- /dev/null
+++ b/build/tasks/tasks.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RepoTasks", "RepoTasks.csproj", "{A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|x64.Build.0 = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Debug|x86.Build.0 = Debug|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|x64.ActiveCfg = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|x64.Build.0 = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|x86.ActiveCfg = Release|Any CPU
+ {A114791F-35B7-4E5B-8E5B-9A91E0B6E4AE}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/build/tools/docker/rhel.7/Dockerfile b/build/tools/docker/rhel.7/Dockerfile
new file mode 100644
index 0000000000..331cb6335b
--- /dev/null
+++ b/build/tools/docker/rhel.7/Dockerfile
@@ -0,0 +1,23 @@
+#
+# Copyright (c) .NET Foundation. All rights reserved.
+# Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+#
+
+# Dockerfile that creates a container suitable to build dotnet-cli
+FROM microsoft/dotnet-buildtools-prereqs:rhel-7-rpmpkg-e1b4a89-20175311035359
+
+# Setup User to match Host User, and give superuser permissions
+ARG USER_ID=0
+RUN useradd -m code_executor -u ${USER_ID} -g root
+RUN echo 'code_executor ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
+
+# With the User Change, we need to change permssions on these directories
+RUN chmod -R a+rwx /usr/local
+RUN chmod -R a+rwx /home
+RUN chown root:root /usr/bin/sudo && chmod 4755 /usr/bin/sudo
+
+# Set user to the one we just created
+USER ${USER_ID}
+
+# Set working directory
+WORKDIR /opt/code
diff --git a/build/tools/docker/ubuntu.14.04/Dockerfile b/build/tools/docker/ubuntu.14.04/Dockerfile
new file mode 100644
index 0000000000..f039506971
--- /dev/null
+++ b/build/tools/docker/ubuntu.14.04/Dockerfile
@@ -0,0 +1,52 @@
+#
+# Copyright (c) .NET Foundation. All rights reserved.
+# Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+#
+
+# Dockerfile that creates a container suitable to build dotnet-cli
+FROM ubuntu:14.04
+
+# Misc Dependencies for build
+RUN apt-get update && \
+ apt-get -qqy install \
+ curl \
+ unzip \
+ gettext \
+ sudo \
+ libunwind8 \
+ libkrb5-3 \
+ libicu52 \
+ liblttng-ust0 \
+ libssl1.0.0 \
+ zlib1g \
+ libuuid1 \
+ debhelper \
+ build-essential \
+ devscripts \
+ git \
+ cmake \
+ clang-3.5 \
+ lldb-3.6 \
+ wget && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
+
+# Use clang as c++ compiler
+RUN update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-3.5 100
+RUN update-alternatives --set c++ /usr/bin/clang++-3.5
+
+# Setup User to match Host User, and give superuser permissions
+ARG USER_ID=0
+RUN useradd -m code_executor -u ${USER_ID} -g sudo
+RUN echo 'code_executor ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
+
+# With the User Change, we need to change permissions on these directories
+RUN chmod -R a+rwx /usr/local
+RUN chmod -R a+rwx /home
+RUN chmod -R 755 /usr/lib/sudo
+
+# Set user to the one we just created
+USER ${USER_ID}
+
+# Set working directory
+WORKDIR /opt/code
diff --git a/build/tools/packaging/changelog.in b/build/tools/packaging/changelog.in
new file mode 100644
index 0000000000..9196011924
--- /dev/null
+++ b/build/tools/packaging/changelog.in
@@ -0,0 +1,2 @@
+* ${DATE} ${MAINTAINER_NAME} <${MAINTAINER_EMAIL}> - ${PACKAGE_VERSION}-${PACKAGE_REVISION}
+-
\ No newline at end of file
diff --git a/build/tools/packaging/debian_config.json.in b/build/tools/packaging/debian_config.json.in
new file mode 100644
index 0000000000..d7449760c4
--- /dev/null
+++ b/build/tools/packaging/debian_config.json.in
@@ -0,0 +1,34 @@
+{
+ "maintainer_name": "${MAINTAINER_NAME}",
+ "maintainer_email": "${MAINTAINER_EMAIL}",
+
+ "package_name": "${PACKAGE_NAME}",
+ "install_root": "${INSTALL_ROOT}",
+
+ "short_description": "${SHORT_DESCRIPTION}",
+ "long_description": "${LONG_DESCRIPTION}",
+ "homepage": "${HOMEPAGE}",
+
+ "release":{
+ "package_version":"0.0.0.0",
+ "package_revision":"${PACKAGE_REVISION}",
+ "urgency" : "low",
+ "changelog_message" : ""
+ },
+
+ "control": {
+ "priority":"standard",
+ "section":"devel",
+ "architecture":"any"
+ },
+
+ "copyright": "Microsoft",
+ "license": {
+ "type": "${LICENSE_TYPE}",
+ "full_text": "Copyright (c) .NET Foundation. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthese files except in compliance with the License. You may obtain a copy of the\nLicense at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License."
+ },
+
+ "debian_dependencies": {
+ ${DEBIAN_DEPENDENCIES}
+ }
+}
\ No newline at end of file
diff --git a/build/tools/templates/SharedFx/SharedFx.csproj b/build/tools/templates/SharedFx/SharedFx.csproj
new file mode 100644
index 0000000000..8070ebbfe6
--- /dev/null
+++ b/build/tools/templates/SharedFx/SharedFx.csproj
@@ -0,0 +1,35 @@
+
+
+
+ netcoreapp3.0
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build/tools/templates/SharedFxSymbols/SharedFrameworkSymbols.nuspec b/build/tools/templates/SharedFxSymbols/SharedFrameworkSymbols.nuspec
new file mode 100644
index 0000000000..1ba4b6c85d
--- /dev/null
+++ b/build/tools/templates/SharedFxSymbols/SharedFrameworkSymbols.nuspec
@@ -0,0 +1,18 @@
+
+
+
+ $ID$
+ $VERSION$
+ $DESCRIPTION$
+ Microsoft
+ Microsoft
+ Copyright © Microsoft Corporation
+ https://raw.githubusercontent.com/aspnet/Home/2.0.0/LICENSE.txt
+ https://go.microsoft.com/fwlink/?LinkID=288859
+ https://asp.net
+ true
+ true
+ aspnetcore
+
+
+
diff --git a/dockerbuild.sh b/dockerbuild.sh
new file mode 100755
index 0000000000..d401522921
--- /dev/null
+++ b/dockerbuild.sh
@@ -0,0 +1,112 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+#
+# variables
+#
+
+RESET="\033[0m"
+RED="\033[0;31m"
+YELLOW="\033[0;33m"
+MAGENTA="\033[0;95m"
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+build_args=()
+docker_args=()
+
+#
+# Functions
+#
+__usage() {
+ echo "Usage: $(basename "${BASH_SOURCE[0]}") [options] [[--] ...]"
+ echo ""
+ echo "Arguments:"
+ echo " image The docker image to use."
+ echo " ... Arguments passed to the command. Variable number of arguments allowed."
+ echo ""
+ echo "Options:"
+ echo " -v, --volume An additional volume mount to add to the build container"
+ echo ""
+ echo "Description:"
+ echo " This will run build.sh inside the dockerfile as defined in build/docker/\$image.Dockerfile."
+
+ if [[ "${1:-}" != '--no-exit' ]]; then
+ exit 2
+ fi
+}
+
+
+__error() {
+ echo -e "${RED}error: $*${RESET}" 1>&2
+}
+
+__warn() {
+ echo -e "${YELLOW}warning: $*${RESET}"
+}
+
+__machine_has() {
+ hash "$1" > /dev/null 2>&1
+ return $?
+}
+
+#
+# main
+#
+
+image="${1:-}"
+shift || True
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ -\?|-h|--help)
+ __usage --no-exit
+ exit 0
+ ;;
+ -v|--volume)
+ shift
+ volume_spec="${1:-}"
+ [ -z "$volume_spec" ] && __error "Missing value for parameter --volume" && __usage
+ docker_args[${#docker_args[*]}]="--volume"
+ docker_args[${#docker_args[*]}]="$volume_spec"
+ ;;
+ *)
+ build_args[${#build_args[*]}]="$1"
+ ;;
+ esac
+ shift
+done
+
+if [ -z "$image" ]; then
+ __usage --no-exit
+ __error 'Missing required argument: image'
+ exit 1
+fi
+
+if ! __machine_has docker; then
+ __error 'Missing required command: docker'
+ exit 1
+fi
+
+dockerfile="$DIR/build/docker/$image.Dockerfile"
+tagname="universe-build-$image"
+
+docker build "$(dirname "$dockerfile")" \
+ --build-arg "USER=$(whoami)" \
+ --build-arg "USER_ID=$(id -u)" \
+ --build-arg "GROUP_ID=$(id -g)" \
+ --tag $tagname \
+ -f "$dockerfile"
+
+docker run \
+ --rm \
+ -t \
+ -e CI \
+ -e TEAMCITY_VERSION \
+ -e DOTNET_CLI_TELEMETRY_OPTOUT \
+ -e Configuration \
+ -v "$DIR:/code/build" \
+ ${docker_args[@]+"${docker_args[@]}"} \
+ $tagname \
+ ./build.sh \
+ ${build_args[@]+"${build_args[@]}"} \
+ "-p:HostMachineRepositoryRoot=$DIR"
diff --git a/docs/CrossRepoBreakingChanges.md b/docs/CrossRepoBreakingChanges.md
new file mode 100644
index 0000000000..09299b4b37
--- /dev/null
+++ b/docs/CrossRepoBreakingChanges.md
@@ -0,0 +1,33 @@
+## A pattern for making cross-repo breaking changes
+
+The engineering team has come up with a pattern for making cross-repo breaking changes without destabilizing local \ CI builds using feature branches. I’ll explain it in terms of a breaking change in Configuration that affects Options:
+
+1) Start by making a feature branch in the Configuration repo. A feature branch is any branch that starts with the prefix "feature/" e.g. feature/bring-back-web-config
+
+`git checkout feature/bring-back-web-config`
+
+2) Make your changes in this feature branch and push it to GitHub. You can ordinarily continue using this branch to get your PR reviewed.
+3) TeamCity's individual Project configuration (http://aspnetci/project.html?projectId=Lite&tab=projectOverview) has always built feature branches. We've enabled an additional step to it that pushes packages produced from feature branches to https://dotnet.myget.org/f/aspnetcore-dev.
+Packages produced from feature branches will have a branch name suffix in their release label to distinguish them from our regular builds. For instance, a package produced for the branch pushed earlier might look like '2.1.0-preview2-bring-back-web-config-10012'.
+
+4) In the Options repo, create a working branch like you normally do:
+
+`git checkout prkrishn/react-to-config`
+
+5) Once again in the options repo, edit build/dependencies.props to reference the feature branch package that got produced.
+a) If `build/dependencies.props` already has a reference to Configuration, update the version of the Options package in `build/dependencies.props` to point to the package produced from the feature branch.
+b) If `build/dependencies.props` does not have a reference to the package version of Configuration, i.e. the package is transitively referenced:
+ * Add a new entry in `build/dependencies.props`
+ * And a PackageReference to the feature branch package in your project.
+
+```xml
+// build/dependencies.props
+2.1.0-preview2-bring-back-web-config-10012
+```
+
+5) Now that you reference the package with breaking changes, make your fixup changes to Options.
+6) Get your code reviewed
+7) Check in to dev both sets of changes i.e. the feature branch from Configuration and your reaction changes in Options including changes to build/dependencies.props.
+7) File a tracking task in Options to clean up build/dependencies.props. Build automation should fix up this version for you when it does it weekly update of dependencies.props, but it's good to manually verify that this is fixed up before we branch or create tags.
+
+**tl,dr**: Push feature branches. TeamCity will build packages with release labels derived from branch name. You can edit and check in changes to build/dependencies.props to reference these packages.
diff --git a/docs/PackageArchives.md b/docs/PackageArchives.md
new file mode 100644
index 0000000000..99ebe79cc3
--- /dev/null
+++ b/docs/PackageArchives.md
@@ -0,0 +1,68 @@
+Package Archives
+================
+
+This repo builds multiple package archives which contain a NuGet fallback folder (also known as the fallback or offline package cache). The fallback folder is a set of NuGet packages that is bundled in the .NET Core SDK installers and available in Azure Web App service.
+
+Package archives are available in four varieties.
+
+* **LZMA** - `nuGetPackagesArchive-$(Version).lzma` - The LZMA is a compressed file format, similar to a .zip. On first use or on install, the .NET Core CLI will expand this LZMA file, extracting the packages inside to %DOTNET_INSTALL_DIR%/sdk/NuGetFallbackFolder. This contains all NuGet packages and their complete contents.
+* **ci-server** - `nuGetPackagesArchive-ci-server-$(Version).zip` - this archive is optimized for CI server environments. It contains the same contents as the LZMA, but trimmed of xml docs (these are only required for IDEs) and .nupkg files (not required for NuGet as for the 2.0 SDK).
+* **ci-server-patch** - `nuGetPackagesArchive-ci-server-$(Version).patch.zip` - this archive is the same as the ci-server archive, but each release is incremental - i.e. it does not bundle files that were present in a previous ci-server archive release. This can be used by first starting with a baseline `nuGetPackagesArchive-ci-server-$(Version).zip` and then applying the `.patch.zip` version for subsequent updates.
+* **ci-server-compat-patch** - `nuGetPackagesArchive-ci-server-compat-$(Version).patch.zip` - similar to the ci-server-patch archive, but this includes .nupkg files to satisfy CI environments that may have older NuGet clients.
+
+These archives are built using the projects in [src/PackageArchive/Archive.\*/](/src/PackageArchive/).
+
+## Using a fallback folder
+
+NuGet restore takes a list of fallback folders in the MSBuild property `RestoreAdditionalProjectFallbackFolders`. Unlike a folder restore source, restore will not copy the packages from a fallback folder into the global NuGet cache.
+
+By default, the .NET Core SDK adds `$(DotNetInstallRoot)/sdk/NuGetFallbackFolder/` to this list. The .NET Core CLI expands its bundled `nuGetPackagesArchive.lzma` file into this location on first use or when the installers run. (See [Microsoft.NET.NuGetOfflineCache.targets](https://github.com/dotnet/sdk/blob/v2.1.300/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.NuGetOfflineCache.targets)).
+
+## Scenarios
+
+The following scenarios are used to determine which packages go into the fallback package cache.
+These requirements are formalized as project files in [src/PackageArchive/Scenario.\*/](/src/PackageArchive/).
+
+ - A user should be able to restore the following templates and only use packages from the offline cache:
+ - `dotnet new console`
+ - `dotnet new library`
+ - `dotnet new web`
+ - `dotnet new razor`
+ - `dotnet new mvc`
+
+The following packages are NOT included in the offline cache.
+ - Packages required for standalone publishing, aka projects that set a Runtime Identifier during restore
+ - Packages required for F# and VB templates
+ - Packages required for Visual Studio code generation in ASP.NET Core projects
+ - Packages required to restore .NET Framework projects
+ - Packages required to restore test projects, such as xunit, MSTest, NUnit
+
+The result of this typically means including the transitive graph of the following packages:
+
+ - Packages that match bundled runtimes
+ - Microsoft.NETCore.App
+ - Microsoft.AspNetCore.App
+ - Microsoft.AspNetCore.All
+ - Packages that Microsoft.NET.Sdk adds implicitly
+ - Microsoft.NETCore.App
+ - NETStandard.Library
+ - Packages that are a PackageReference/DotNetCliToolReference in basic ASP.NET Core templates. In addition to packages above, this typically includes:
+ - Microsoft.EntityFrameworkCore.Tools{.DotNet}
+ - Microsoft.VisualStudio.Web.CodeGeneration.Design
+ - Microsoft.VisualStudio.Web.BrowserLink
+
+### Example
+
+Given the following parameters:
+ - LatestNETCoreAppTFM = netcoreapp2.1
+ - DefaultRuntimeVersion = 2.1
+ - BundledRuntimeVersion = 2.1.8
+ - BundledAspNetRuntimeVersion = 2.1.7
+ - LatestNETStandardLibraryTFM = netstandard2.0
+ - BundledNETStandardLibraryVersion = 2.0.1
+
+The LZMA should contain
+ - Microsoft.NETCore.App/2.1.0 + netcoreapp2.1 dependencies (Microsoft.NET.Sdk will implicitly reference "2.1", which NuGet to 2.1.0)
+ - Microsoft.NETCore.App/2.1.8 + netcoreapp2.1 dependencies (Matches the runtime in shared/Microsoft.NETCore.App/2.1.8/)
+ - Microsoft.AspNetCore.All/2.1.7 + netcoreapp2.1 dependencies (Matches the runtime in shared/Microsoft.AspNetCore.All/2.1.7/)
+ - NETStandard.Library/2.0.1 + netstandard2.0 dependencies (Microsoft.NET.Sdk will implicitly reference "2.0.1")
diff --git a/docs/Submodules.md b/docs/Submodules.md
new file mode 100644
index 0000000000..501e7884d3
--- /dev/null
+++ b/docs/Submodules.md
@@ -0,0 +1,84 @@
+Submodules
+==========
+
+This repository composes ASP.NET Core repos from a collection of many submodules.
+Working with submodules in git requires using commands not used in a normal git workflow.
+Here are some tips for working with submodules.
+
+For full information, see the [official docs for git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules).
+
+## Fundamental concept
+
+The parent repo (aspnet/Universe) stores two pieces of info about each submodule.
+
+1. Where to clone the submodule from. This is stored in the .gitmodules file
+2. The commit hash of the submodule to use.
+
+This means you cannot commit a submodule's branch or a tag to the parent repo.
+Other info may appear in the .gitmodules file, but it is only used when attempting to
+[update a submodule.](#updating-submodules)
+
+## Cloning
+
+By default, submodules will not be present. Use `--recursive` to clone all submodules.
+
+ git clone https://github.com/aspnet/Universe.git --recursive
+
+If you have already cloned, run this to initialize all submodules.
+
+ git submodule update --init
+
+## Pulling updates
+
+When you execute `git pull`, submodules do not automatically snap to the version
+used in new commits of the parent repo. Update all submodules to match the parent repo's
+expected version by running this command.
+
+ git submodule update
+
+## Executing a command on each submodule
+
+ git submodule foreach ''
+
+For example, to clean and reset each submodule:
+
+ git submodule foreach 'git reset --hard; git clean -xfd'
+
+## Updating submodules
+
+Updating all submodules to newer versions can be done like this.
+
+ git submodule update --remote
+
+Updating just one subumodule.
+
+ git submodule update --remote modules/EntityFrameworkCore/
+
+This uses the remote url and branch info configuration stored in .gitmodules to pull new commits.
+This does not guarantee the commit is going to be a fast-forward commit.
+
+## Diff
+
+You can see which commits have changed in submodules from the last parent repo commit by adding `--submodule` to git-diff.
+
+ git diff --submodule
+
+## Saving an update to a submodule
+
+To move the parent repo to use a new commit, you must create a commit in the parent repo
+that contains the new commit.
+
+ git submodule update --remote modules/KestrelHttpServer/
+ git add modules/KestrelhttpServer/
+ git commit -m "Update Kestrel to latest version"
+
+## PowerShell is slow in aspnet/Universe
+
+Many users have post-git, and extension that shows git status on the prompt line. Because `git status` with submodules
+on Windows is very slow, it can make PowerShell unbearable to use.
+
+To workaround this, disable checking git-status for each prompt.
+```ps1
+$GitPromptSettings.EnableFileStatus = $false
+```
+You can disable this permanently by adding to your `$PROFILE` file. (`notepad $PROFILE`)
diff --git a/docs/build-from-source.md b/docs/build-from-source.md
new file mode 100644
index 0000000000..31977ae751
--- /dev/null
+++ b/docs/build-from-source.md
@@ -0,0 +1,129 @@
+Build ASP.NET Core from Source
+==============================
+
+Building ASP.NET Core from source allows you tweak and customize ASP.NET Core, and
+to contribute your improvements back to the project.
+
+## Install pre-requistes
+
+### Windows
+
+Building ASP.NET Core on Windows requires:
+
+* Windows 7 or higher
+* At least 10 GB of disk space and a good internet connection (our build scripts download a lot of tools and dependencies)
+* Visual Studio 2017.
+* Git.
+* (Optional) some optional components, like the SignalR Java client, may require
+ * NodeJS
+ * Java Development Kit 10 or newer. Either:
+ * OpenJDK
+ * Oracle's JDK
+
+### macOS/Linux
+
+Building ASP.NET Core on macOS or Linux requires:
+
+* If using macOS, you need macOS Sierra or newer.
+* If using Linux, you need a machine with all .NET Core Linux prerequisites:
+* At least 10 GB of disk space and a good internet connection (our build scripts download a lot of tools and dependencies)
+* Git
+* (Optional) some optional components, like the SignalR Java client, may require
+ * NodeJS
+ * Java Development Kit 10 or newer. Either:
+ * OpenJDK
+ * Oracle's JDK
+
+## Clone the source code
+
+ASP.NET Core uses git submodules to include source from a few other projects.
+
+For a new copy of the project, run:
+```
+git clone --recursive https://github.com/aspnet/AspNetCore
+```
+
+To update an existing copy, run:
+```
+git submodule update --init --recursive
+```
+
+## Building in Visual Studio / Code
+
+Before opening our .sln files in Visual Studio or VS Code, executing the following on command-line:
+```
+.\build.cmd /t:Restore
+```
+This will download required tools.
+
+#### PATH
+
+For VS Code and Visual Studio to work correctly, you must place the following location in your PATH.
+```
+Windows: %USERPROFILE%\.dotnet\x64
+Linux/macOS: $HOME/.dotnet
+```
+This must come **before** any other installation of `dotnet`. In Windows, we recommend removing `C:\Program Files\dotnet` from PATH in system variables and adding `%USERPROFILE%\.dotnet\x64` to PATH in user variables.
+
+
+
+## Building on command-line
+
+You can also build the entire project on command line with the `build.cmd`/`.sh` scripts.
+
+On Windows:
+```
+.\build.cmd
+```
+
+On macOS/Linux:
+```
+./build.sh
+```
+
+#### Build properties
+
+Additional properties can be added as an argument in the form `/property:$name=$value`, or `/p:$name=$value` for short. For example:
+```
+.\build.cmd /p:Configuration=Release
+```
+
+Common properties include:
+
+Property | Description
+-------------------------|---------------------------------------------------------
+BuildNumber | (string). A specific build number, typically from a CI counter
+Configuration | `Debug` or `Release`. Default = `Debug`.
+SkipTests | `true` or `false`. When true, builds without running tests.
+NoBuild | `true` or `false`. Runs tests without rebuilding.
+
+## Use the result of your build
+
+After building ASP.NET Core from source, you will need to install and use your local version of ASP.NET Core.
+
+- Run the installers produced in `artifacts/installers/` for your platform.
+- Add a NuGet.Config to your project directory with the following content:
+
+ ```xml
+
+
+
+
+
+
+
+
+ ```
+
+ *NOTE: This NuGet.Config should be with your application unless you want nightly packages to potentially start being restored for other apps on the machine.*
+
+- Update the versions on `PackageReference` items in your .csproj project file to point to the version from your local build.
+ ```xml
+
+
+
+ ```
+
+
+Some features, such as new target frameworks, may require prerelease tooling builds for Visual Studio.
+These are available in the [Visual Studio Preview](https://www.visualstudio.com/vs/preview/).
diff --git a/docs/daily-builds.md b/docs/daily-builds.md
new file mode 100644
index 0000000000..c1c04db7ee
--- /dev/null
+++ b/docs/daily-builds.md
@@ -0,0 +1,99 @@
+How to get daily builds of ASP.NET Core
+=======================================
+
+Daily builds include the latest source code changes. They are not supported for production use and are subject to frequent changes, but we strive to make sure daily builds function correctly.
+
+If you want to download the latest daily build and use it in a project, then you need to:
+
+- Obtain the latest [build of the .NET Core SDK](https://github.com/dotnet/core-sdk#installers-and-binaries)
+- Add a NuGet.Config to your project directory with the following content:
+
+ ```xml
+
+
+
+
+
+
+
+
+ ```
+
+ *NOTE: This NuGet.Config should be with your application unless you want nightly packages to potentially start being restored for other apps on the machine.*
+
+Some features, such as new target frameworks, may require prerelease tooling builds for Visual Studio.
+These are available in the [Visual Studio Preview](https://www.visualstudio.com/vs/preview/).
+
+## NuGet packages
+
+Daily builds of ackages can be found on . This feed may include
+packages that will not be supported in a officially released build.
+
+Commonly referenced packages:
+
+[app-metapackage-myget]: https://dotnet.myget.org/feed/dotnet-core/package/nuget/Microsoft.AspNetCore.App
+[app-metapackage-myget-badge]: https://img.shields.io/dotnet.myget/dotnet-core/vpre/Microsoft.AspNetCore.App.svg?style=flat-square&label=myget
+
+[metapackage-myget]: https://dotnet.myget.org/feed/dotnet-core/package/nuget/Microsoft.AspNetCore
+[metapackage-myget-badge]: https://img.shields.io/dotnet.myget/dotnet-core/vpre/Microsoft.AspNetCore.svg?style=flat-square&label=myget
+
+Package | MyGet
+:---------------------------------|:---------------------------------------------------------
+Microsoft.AspNetCore.App | [![][app-metapackage-myget-badge]][app-metapackage-myget]
+Microsoft.AspNetCore | [![][metapackage-myget-badge]][metapackage-myget]
+
+## Runtime installers
+
+Updated versions of the ASP.NET Core runtime can be installed separately from SDK updates. Runtime-only installers can be downloaded here:
+
+[badge-master]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-win-x64-version-badge.svg
+[win-x64-zip]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-win-x64.zip
+[win-x64-exe]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-win-x64.exe
+[win-x86-zip]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-win-x86.zip
+[win-x86-exe]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-win-x86.exe
+[linux-x64-tar]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-linux-x64.tar.gz
+[linux-arm-tar]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-linux-arm.tar.gz
+[linux-arm64-tar]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-linux-arm64.tar.gz
+[osx-x64-tar]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-osx-x64.tar.gz
+[debian-x64-deb]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-x64.deb
+[redhat-x64-rpm]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-x64.rpm
+[linux-musl-x64-tar]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/master/aspnetcore-runtime-latest-linux-musl-x64.tar.gz
+
+[badge-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-win-x64-version-badge.svg
+[win-x64-zip-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-win-x64.zip
+[win-x64-exe-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-win-x64.exe
+[win-x86-zip-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-win-x86.zip
+[win-x86-exe-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-win-x86.exe
+[linux-x64-tar-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-linux-x64.tar.gz
+[osx-x64-tar-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-osx-x64.tar.gz
+[debian-x64-deb-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-x64.deb
+[redhat-x64-rpm-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-x64.rpm
+[linux-arm-tar-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-linux-arm.tar.gz
+[linux-musl-x64-tar-rel-22]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.2/aspnetcore-runtime-latest-linux-musl-x64.tar.gz
+
+[badge-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-win-x64-version-badge.svg
+[win-x64-zip-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-win-x64.zip
+[win-x64-exe-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-win-x64.exe
+[win-x86-zip-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-win-x86.zip
+[win-x86-exe-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-win-x86.exe
+[linux-x64-tar-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-linux-x64.tar.gz
+[osx-x64-tar-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-osx-x64.tar.gz
+[debian-x64-deb-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-x64.deb
+[redhat-x64-rpm-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-x64.rpm
+[linux-arm-tar-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-linux-arm.tar.gz
+[linux-musl-x64-tar-rel-21]: https://dotnetcli.blob.core.windows.net/dotnet/aspnetcore/Runtime/release/2.1/aspnetcore-runtime-latest-linux-musl-x64.tar.gz
+
+Platform | Latest (master branch)
![][badge-master] | release/2.2
![][badge-rel-22] | release/2.1
![][badge-rel-21]
+:---------------------|:----------------------------------------------------------------|:------------------------------------------------------------------------- |:-------------------------------------------------------------------------
+Channel name1 | `master` | `release/2.2` | `release/2.1`
+Windows (x64) | [Installer (exe)][win-x64-exe]
[Archive (zip)][win-x64-zip] | [Installer (exe)][win-x64-exe-rel-22]
[Archive (zip)][win-x64-zip-rel-22] | [Installer (exe)][win-x64-exe-rel-21]
[Archive (zip)][win-x64-zip-rel-21]
+Windows (x86) | [Installer (exe)][win-x86-exe]
[Archive (zip)][win-x86-zip] | [Installer (exe)][win-x86-exe-rel-22]
[Archive (zip)][win-x86-zip-rel-22] | [Installer (exe)][win-x86-exe-rel-21]
[Archive (zip)][win-x86-zip-rel-21]
+macOS (x64) | [Archive (tar.gz)][osx-x64-tar] | [Archive (tar.gz)][osx-x64-tar-rel-22] | [Archive (tar.gz)][osx-x64-tar-rel-21]
+Linux (x64)
_(for glibc based OS - most common)_ | [Archive (tar.gz)][linux-x64-tar] | [Archive (tar.gz)][linux-x64-tar-rel-22] | [Archive (tar.gz)][linux-x64-tar-rel-21]
+Linux (x64 - musl)
_(for musl based OS, such as Alpine Linux)_ | [Archive (tar.gz)][linux-musl-x64-tar] | [Archive (tar.gz)][linux-musl-x64-tar-rel-22] | [Archive (tar.gz)][linux-musl-x64-tar-rel-21]
+Linux (arm32) | [Archive (tar.gz)][linux-arm-tar] | [Archive (tar.gz)][linux-arm-tar-rel-22] | [Archive (tar.gz)][linux-arm-tar-rel-21]
+Linux (arm64) | [Archive (tar.gz)][linux-arm64-tar] | |
+Debian/Ubuntu (x64) | [Installer (deb)][debian-x64-deb] | [Installer (deb)][debian-x64-deb-rel-22] | [Installer (deb)][debian-x64-deb-rel-21]
+RedHat/Fedora (x64) | [Installer (rpm)][redhat-x64-rpm] | [Installer (rpm)][redhat-x64-rpm-rel-22] | [Installer (rpm)][redhat-x64-rpm-rel-21]
+
+> 1 For use with the `-Channel` argument in [dotnet-install.ps1/sh](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script).
diff --git a/eng/AspNetCore.snk b/eng/AspNetCore.snk
new file mode 100644
index 0000000000..e10e4889c1
Binary files /dev/null and b/eng/AspNetCore.snk differ
diff --git a/eng/targets/Cpp.Common.props b/eng/targets/Cpp.Common.props
new file mode 100644
index 0000000000..f295dbafad
--- /dev/null
+++ b/eng/targets/Cpp.Common.props
@@ -0,0 +1,11 @@
+
+
+
+
+ true
+
+
+
+
+
+
diff --git a/eng/targets/Cpp.Common.targets b/eng/targets/Cpp.Common.targets
new file mode 100644
index 0000000000..f2cad0d8c0
--- /dev/null
+++ b/eng/targets/Cpp.Common.targets
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/eng/targets/MicroBuild.Plugin.props b/eng/targets/MicroBuild.Plugin.props
new file mode 100644
index 0000000000..0101904811
--- /dev/null
+++ b/eng/targets/MicroBuild.Plugin.props
@@ -0,0 +1,12 @@
+
+
+
+
+ $(MicroBuildOverridePluginDirectory)
+
+
+ $(NuGetPackageRoot)
+ $(NUGET_PACKAGES)
+ $(USERPROFILE)\.nuget\packages
+
+
diff --git a/eng/targets/Wix.Common.props b/eng/targets/Wix.Common.props
new file mode 100644
index 0000000000..d958514aba
--- /dev/null
+++ b/eng/targets/Wix.Common.props
@@ -0,0 +1,32 @@
+
+
+
+
+ 2.0
+ 3.11
+ 3.11.1
+
+
+
+
+ net461
+ $(BaseIntermediateOutputPath)
+ $(MSBuildProjectDir)\obj\
+ $(MSBuildExtensionsPath)\..\Common7\IDE\CommonExtensions\Microsoft\NuGet\NuGet.targets
+
+
+
+
+
+
+
+
+
+ true
+ $(WixExtDir)dark.exe
+
+
+
+
+
+
diff --git a/eng/targets/Wix.Common.targets b/eng/targets/Wix.Common.targets
new file mode 100644
index 0000000000..4b07606e19
--- /dev/null
+++ b/eng/targets/Wix.Common.targets
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+ Microsoft400
+
+
+ Microsoft400
+
+
+ Microsoft400
+
+
+ Microsoft400
+
+
+
+
+
+
+
+
+
+ yes
+ $(OutputName.Replace('-', '_')).cab
+ $(ProductName)
+
+ $(DefineConstants);Debug
+ $(DefineConstants);EmbedCab=$(EmbedCab)
+ $(DefineConstants);Cabinet=$(Cabinet)
+
+
+
+ en-US
+ $(Culture)
+ $(Platform)
+ $(Platform)
+ $(OutputPath)
+ $(DefineConstants);BinPath=$(OutputPath)$(Culture)\
+ $(WixVariables);$(DefineConstants)
+
+
+
+
+
+
+
+
diff --git a/global.json b/global.json
new file mode 100644
index 0000000000..ffa7db4c53
--- /dev/null
+++ b/global.json
@@ -0,0 +1,8 @@
+{
+ "sdk": {
+ "version": "2.2.100-preview2-009404"
+ },
+ "msbuild-sdks": {
+ "Internal.AspNetCore.Sdk": "3.0.0-alpha1-20181011.11"
+ }
+}
diff --git a/korebuild-lock.txt b/korebuild-lock.txt
new file mode 100644
index 0000000000..27b605e3c6
--- /dev/null
+++ b/korebuild-lock.txt
@@ -0,0 +1,2 @@
+version:3.0.0-alpha1-20181011.11
+commithash:f57aa8ddda0abdd74ada55853587bedb4f364065
diff --git a/korebuild.json b/korebuild.json
new file mode 100644
index 0000000000..2a75c43d76
--- /dev/null
+++ b/korebuild.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json",
+ "channel": "master",
+ "toolsets": {
+ "nodejs": {
+ "minVersion": "8.0",
+ "required": true
+ }
+ }
+}
diff --git a/modules/AADIntegration b/modules/AADIntegration
new file mode 160000
index 0000000000..1ebe668a96
--- /dev/null
+++ b/modules/AADIntegration
@@ -0,0 +1 @@
+Subproject commit 1ebe668a965de3e4a759e5eadc57c4c742ae825c
diff --git a/modules/Antiforgery b/modules/Antiforgery
new file mode 160000
index 0000000000..926b3dfe97
--- /dev/null
+++ b/modules/Antiforgery
@@ -0,0 +1 @@
+Subproject commit 926b3dfe97df76d9a0c35db197f130147e3675e8
diff --git a/modules/AuthSamples b/modules/AuthSamples
new file mode 160000
index 0000000000..7c3be6c7f8
--- /dev/null
+++ b/modules/AuthSamples
@@ -0,0 +1 @@
+Subproject commit 7c3be6c7f8d6692d6c87ed7f1cea7eec2fc8bb7a
diff --git a/modules/AzureIntegration b/modules/AzureIntegration
new file mode 160000
index 0000000000..1e6e066006
--- /dev/null
+++ b/modules/AzureIntegration
@@ -0,0 +1 @@
+Subproject commit 1e6e0660064a7e0438fea2d42af45ad5b92204bf
diff --git a/modules/BasicMiddleware b/modules/BasicMiddleware
new file mode 160000
index 0000000000..6339351ddc
--- /dev/null
+++ b/modules/BasicMiddleware
@@ -0,0 +1 @@
+Subproject commit 6339351ddc1a6815e2cb8fe32da6fa341e877173
diff --git a/modules/BrowserLink b/modules/BrowserLink
new file mode 160000
index 0000000000..0b083d1503
--- /dev/null
+++ b/modules/BrowserLink
@@ -0,0 +1 @@
+Subproject commit 0b083d15036e5643b2b6a9ea6e3d995c51a42e5a
diff --git a/modules/CORS b/modules/CORS
new file mode 160000
index 0000000000..f074714eb0
--- /dev/null
+++ b/modules/CORS
@@ -0,0 +1 @@
+Subproject commit f074714eb0a558d27c97f04c60de6b8730b4fd9c
diff --git a/modules/Caching b/modules/Caching
new file mode 160000
index 0000000000..b7cda012bc
--- /dev/null
+++ b/modules/Caching
@@ -0,0 +1 @@
+Subproject commit b7cda012bca34704e5a835ace53a0f879b5f2a2d
diff --git a/modules/Common b/modules/Common
new file mode 160000
index 0000000000..b90ec23956
--- /dev/null
+++ b/modules/Common
@@ -0,0 +1 @@
+Subproject commit b90ec23956e81c462ab61fd9ee56b975464b834c
diff --git a/modules/Configuration b/modules/Configuration
new file mode 160000
index 0000000000..fdeabf6016
--- /dev/null
+++ b/modules/Configuration
@@ -0,0 +1 @@
+Subproject commit fdeabf601682d6d85b1f37a79449972f8d469e8a
diff --git a/modules/DependencyInjection b/modules/DependencyInjection
new file mode 160000
index 0000000000..ca90dc6871
--- /dev/null
+++ b/modules/DependencyInjection
@@ -0,0 +1 @@
+Subproject commit ca90dc687106ab6ecb46e4943f3fe5ceac3e6ef8
diff --git a/modules/Diagnostics b/modules/Diagnostics
new file mode 160000
index 0000000000..0ce437875e
--- /dev/null
+++ b/modules/Diagnostics
@@ -0,0 +1 @@
+Subproject commit 0ce437875e8feeb88bb935c7de07e96218be8be3
diff --git a/modules/DotNetTools b/modules/DotNetTools
new file mode 160000
index 0000000000..581b3160f4
--- /dev/null
+++ b/modules/DotNetTools
@@ -0,0 +1 @@
+Subproject commit 581b3160f422180b3411d84b4458c10128c8cacb
diff --git a/modules/EntityFrameworkCore b/modules/EntityFrameworkCore
new file mode 160000
index 0000000000..12855cd0ed
--- /dev/null
+++ b/modules/EntityFrameworkCore
@@ -0,0 +1 @@
+Subproject commit 12855cd0ed48d77f13ae7f6e9ef67cbbcac71a22
diff --git a/modules/EventNotification b/modules/EventNotification
new file mode 160000
index 0000000000..08c615f093
--- /dev/null
+++ b/modules/EventNotification
@@ -0,0 +1 @@
+Subproject commit 08c615f0939fe0a7d7399eaa02be708dc92d9a10
diff --git a/modules/FileSystem b/modules/FileSystem
new file mode 160000
index 0000000000..3cc768b93f
--- /dev/null
+++ b/modules/FileSystem
@@ -0,0 +1 @@
+Subproject commit 3cc768b93fafb14aa74f93b050cfb4aba8ae6dfb
diff --git a/modules/Hosting b/modules/Hosting
new file mode 160000
index 0000000000..5a3c664566
--- /dev/null
+++ b/modules/Hosting
@@ -0,0 +1 @@
+Subproject commit 5a3c6645667ec24e09d6200fb1e949f2a2417b9a
diff --git a/modules/HtmlAbstractions b/modules/HtmlAbstractions
new file mode 160000
index 0000000000..45b7e2ddb7
--- /dev/null
+++ b/modules/HtmlAbstractions
@@ -0,0 +1 @@
+Subproject commit 45b7e2ddb796f0cb909d8f04bc4b5a5cf13b5e46
diff --git a/modules/HttpAbstractions b/modules/HttpAbstractions
new file mode 160000
index 0000000000..3fe17b9faf
--- /dev/null
+++ b/modules/HttpAbstractions
@@ -0,0 +1 @@
+Subproject commit 3fe17b9faf0ae413ecd5bea1d42c784854acfb57
diff --git a/modules/HttpClientFactory b/modules/HttpClientFactory
new file mode 160000
index 0000000000..4dfcf7485e
--- /dev/null
+++ b/modules/HttpClientFactory
@@ -0,0 +1 @@
+Subproject commit 4dfcf7485e62819d0d73c00d7e7eb53d75abdbd3
diff --git a/modules/HttpSysServer b/modules/HttpSysServer
new file mode 160000
index 0000000000..1d989c2a50
--- /dev/null
+++ b/modules/HttpSysServer
@@ -0,0 +1 @@
+Subproject commit 1d989c2a50c62069b16879eda901e79e37276c2d
diff --git a/modules/IISIntegration b/modules/IISIntegration
new file mode 160000
index 0000000000..2bd2c1870a
--- /dev/null
+++ b/modules/IISIntegration
@@ -0,0 +1 @@
+Subproject commit 2bd2c1870a236df4af389dbb74f04daca78d62b1
diff --git a/modules/Identity b/modules/Identity
new file mode 160000
index 0000000000..81928f6c31
--- /dev/null
+++ b/modules/Identity
@@ -0,0 +1 @@
+Subproject commit 81928f6c31a2e47e72f631c1bb5cf1bb1a3f8cf6
diff --git a/modules/JavaScriptServices b/modules/JavaScriptServices
new file mode 160000
index 0000000000..9c6a1b577f
--- /dev/null
+++ b/modules/JavaScriptServices
@@ -0,0 +1 @@
+Subproject commit 9c6a1b577f04d845152532c66252811260b3f36a
diff --git a/modules/JsonPatch b/modules/JsonPatch
new file mode 160000
index 0000000000..0b76599c31
--- /dev/null
+++ b/modules/JsonPatch
@@ -0,0 +1 @@
+Subproject commit 0b76599c31b3fa367dc172272e81cee87a634ba9
diff --git a/modules/KestrelHttpServer b/modules/KestrelHttpServer
new file mode 160000
index 0000000000..8fc57cd0a1
--- /dev/null
+++ b/modules/KestrelHttpServer
@@ -0,0 +1 @@
+Subproject commit 8fc57cd0a1e7aefc5151cb11249e117b1cc1743a
diff --git a/modules/Localization b/modules/Localization
new file mode 160000
index 0000000000..9ecc9abd23
--- /dev/null
+++ b/modules/Localization
@@ -0,0 +1 @@
+Subproject commit 9ecc9abd23d2264095aba5ab34624905d51aa79c
diff --git a/modules/Logging b/modules/Logging
new file mode 160000
index 0000000000..68748131c5
--- /dev/null
+++ b/modules/Logging
@@ -0,0 +1 @@
+Subproject commit 68748131c5f591309ccf619fa564220b1704a473
diff --git a/modules/MetaPackages b/modules/MetaPackages
new file mode 160000
index 0000000000..3e0ee5a711
--- /dev/null
+++ b/modules/MetaPackages
@@ -0,0 +1 @@
+Subproject commit 3e0ee5a711556581ebda53082d11cab1c0c8d197
diff --git a/modules/Microsoft.Data.Sqlite b/modules/Microsoft.Data.Sqlite
new file mode 160000
index 0000000000..c4dadf19d8
--- /dev/null
+++ b/modules/Microsoft.Data.Sqlite
@@ -0,0 +1 @@
+Subproject commit c4dadf19d8a5e2bc2a64552c1c1c8899a72af33c
diff --git a/modules/MusicStore b/modules/MusicStore
new file mode 160000
index 0000000000..5fe4ff7c27
--- /dev/null
+++ b/modules/MusicStore
@@ -0,0 +1 @@
+Subproject commit 5fe4ff7c27d3a811e3d505208097a6ca69d18125
diff --git a/modules/Mvc b/modules/Mvc
new file mode 160000
index 0000000000..5f42d5063e
--- /dev/null
+++ b/modules/Mvc
@@ -0,0 +1 @@
+Subproject commit 5f42d5063e1ecc4f6f1ce0af85c624841e479044
diff --git a/modules/Options b/modules/Options
new file mode 160000
index 0000000000..621cd9d38a
--- /dev/null
+++ b/modules/Options
@@ -0,0 +1 @@
+Subproject commit 621cd9d38a6a8c05d379de7bbbcd59c119827d7d
diff --git a/modules/Razor b/modules/Razor
new file mode 160000
index 0000000000..71ed050fa4
--- /dev/null
+++ b/modules/Razor
@@ -0,0 +1 @@
+Subproject commit 71ed050fa4ea7756ed69d3f029c02bf58f31fb55
diff --git a/modules/ResponseCaching b/modules/ResponseCaching
new file mode 160000
index 0000000000..e7f8210970
--- /dev/null
+++ b/modules/ResponseCaching
@@ -0,0 +1 @@
+Subproject commit e7f8210970bd5566795e6f87155a8fd7c7ea4ab0
diff --git a/modules/Routing b/modules/Routing
new file mode 160000
index 0000000000..1643b535b2
--- /dev/null
+++ b/modules/Routing
@@ -0,0 +1 @@
+Subproject commit 1643b535b26ff857ed80d36526625588ff61ee3f
diff --git a/modules/Scaffolding b/modules/Scaffolding
new file mode 160000
index 0000000000..a4fca717da
--- /dev/null
+++ b/modules/Scaffolding
@@ -0,0 +1 @@
+Subproject commit a4fca717da581df687d7853e0d18cbd92fb87c06
diff --git a/modules/Security b/modules/Security
new file mode 160000
index 0000000000..4dd9c6deef
--- /dev/null
+++ b/modules/Security
@@ -0,0 +1 @@
+Subproject commit 4dd9c6deefba64aa7721430e7d1dc72d6e002416
diff --git a/modules/ServerTests b/modules/ServerTests
new file mode 160000
index 0000000000..d5b83e32c6
--- /dev/null
+++ b/modules/ServerTests
@@ -0,0 +1 @@
+Subproject commit d5b83e32c62383a04003c269026c3f0e8b5d3522
diff --git a/modules/Session b/modules/Session
new file mode 160000
index 0000000000..d5e4687a6f
--- /dev/null
+++ b/modules/Session
@@ -0,0 +1 @@
+Subproject commit d5e4687a6fda304b2e15c79abe9de76dd8367f58
diff --git a/modules/SignalR b/modules/SignalR
new file mode 160000
index 0000000000..df88a82a80
--- /dev/null
+++ b/modules/SignalR
@@ -0,0 +1 @@
+Subproject commit df88a82a8084b2775ed880f01c2a1a50787088d1
diff --git a/modules/StaticFiles b/modules/StaticFiles
new file mode 160000
index 0000000000..4c8df8e5a4
--- /dev/null
+++ b/modules/StaticFiles
@@ -0,0 +1 @@
+Subproject commit 4c8df8e5a4e3abe5e7db51685dbd892d160c1e35
diff --git a/modules/SubmoduleGraph.dgml b/modules/SubmoduleGraph.dgml
new file mode 100644
index 0000000000..2cd4f5c57e
--- /dev/null
+++ b/modules/SubmoduleGraph.dgml
@@ -0,0 +1,384 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/Templating b/modules/Templating
new file mode 160000
index 0000000000..bcc6a17c3d
--- /dev/null
+++ b/modules/Templating
@@ -0,0 +1 @@
+Subproject commit bcc6a17c3d653faf129f33dbf5868b2f82946742
diff --git a/modules/WebSockets b/modules/WebSockets
new file mode 160000
index 0000000000..34f161321a
--- /dev/null
+++ b/modules/WebSockets
@@ -0,0 +1 @@
+Subproject commit 34f161321a5d3bd9f7c83e968c34dcd98416f061
diff --git a/run.cmd b/run.cmd
new file mode 100644
index 0000000000..d52d5c7e68
--- /dev/null
+++ b/run.cmd
@@ -0,0 +1,2 @@
+@ECHO OFF
+PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE"
diff --git a/run.ps1 b/run.ps1
new file mode 100644
index 0000000000..6b7e36c6cf
--- /dev/null
+++ b/run.ps1
@@ -0,0 +1,251 @@
+#!/usr/bin/env powershell
+#requires -version 4
+
+<#
+.SYNOPSIS
+Executes KoreBuild commands.
+
+.DESCRIPTION
+Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`.
+
+.PARAMETER Command
+The KoreBuild command to run.
+
+.PARAMETER Path
+The folder to build. Defaults to the folder containing this script.
+
+.PARAMETER LockFile
+The path to the korebuild-lock.txt file. Defaults to $Path/korebuild-lock.txt
+
+.PARAMETER Channel
+The channel of KoreBuild to download. Overrides the value from the config file.
+
+.PARAMETER DotNetHome
+The directory where .NET Core tools will be stored.
+
+.PARAMETER ToolsSource
+The base url where build tools can be downloaded. Overrides the value from the config file.
+
+.PARAMETER Update
+Updates KoreBuild to the latest version even if a lock file is present.
+
+.PARAMETER Reinstall
+Re-installs KoreBuild
+
+.PARAMETER ConfigFile
+The path to the configuration file that stores values. Defaults to korebuild.json.
+
+.PARAMETER CI
+Sets up CI specific settings and variables.
+
+.PARAMETER PackageVersionPropsUrl
+(optional) the url of the package versions props path containing dependency versions.
+
+.PARAMETER AssetRootUrl
+(optional) the base url for acquiring build assets from an orchestrated build
+
+.PARAMETER AccessTokenSuffix
+(optional) the query string to append to any blob store access for PackageVersionPropsUrl, if any.
+
+.PARAMETER RestoreSources
+(optional) Semi-colon delimited list of additional NuGet feeds to use as part of restore.
+
+.PARAMETER ProductBuildId
+(optional) The product build ID for correlation with orchestrated builds.
+
+.PARAMETER MSBuildArguments
+Additional MSBuild arguments to be passed through.
+
+.NOTES
+This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be.
+When the lockfile is not present, KoreBuild will create one using latest available version from $Channel.
+
+The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set
+in the file are overridden by command line parameters.
+
+.EXAMPLE
+Example config file:
+```json
+{
+ "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json",
+ "channel": "master",
+ "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools"
+}
+```
+#>
+[CmdletBinding(PositionalBinding = $false)]
+param(
+ [Parameter(Mandatory=$true, Position = 0)]
+ [string]$Command,
+ [string]$Path = $PSScriptRoot,
+ [string]$LockFile,
+ [Alias('c')]
+ [string]$Channel,
+ [Alias('d')]
+ [string]$DotNetHome,
+ [Alias('s')]
+ [string]$ToolsSource,
+ [Alias('u')]
+ [switch]$Update,
+ [switch]$Reinstall,
+ [string]$ConfigFile = $null,
+ [switch]$CI,
+ [string]$PackageVersionPropsUrl = $null,
+ [string]$AccessTokenSuffix = $null,
+ [string]$RestoreSources = $null,
+ [string]$AssetRootUrl = $null,
+ [string]$ProductBuildId = $null,
+ [Parameter(ValueFromRemainingArguments = $true)]
+ [string[]]$MSBuildArguments
+)
+
+Set-StrictMode -Version 2
+$ErrorActionPreference = 'Stop'
+
+#
+# Functions
+#
+
+function Get-KoreBuild {
+
+ if (!(Test-Path $LockFile) -or $Update) {
+ Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $LockFile
+ }
+
+ $version = Get-Content $LockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1
+ if (!$version) {
+ Write-Error "Failed to parse version from $LockFile. Expected a line that begins with 'version:'"
+ }
+ $version = $version.TrimStart('version:').Trim()
+ $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version)
+
+ if ($Reinstall -and (Test-Path $korebuildPath)) {
+ Remove-Item -Force -Recurse $korebuildPath
+ }
+
+ if (!(Test-Path $korebuildPath)) {
+ Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version"
+ New-Item -ItemType Directory -Path $korebuildPath | Out-Null
+ $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip"
+
+ try {
+ $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip"
+ Get-RemoteFile $remotePath $tmpfile
+ if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) {
+ # Use built-in commands where possible as they are cross-plat compatible
+ Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath
+ }
+ else {
+ # Fallback to old approach for old installations of PowerShell
+ Add-Type -AssemblyName System.IO.Compression.FileSystem
+ [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath)
+ }
+ }
+ catch {
+ Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore
+ throw
+ }
+ finally {
+ Remove-Item $tmpfile -ErrorAction Ignore
+ }
+ }
+
+ return $korebuildPath
+}
+
+function Join-Paths([string]$path, [string[]]$childPaths) {
+ $childPaths | ForEach-Object { $path = Join-Path $path $_ }
+ return $path
+}
+
+function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) {
+ if ($RemotePath -notlike 'http*') {
+ Copy-Item $RemotePath $LocalPath
+ return
+ }
+
+ $retries = 10
+ while ($retries -gt 0) {
+ $retries -= 1
+ try {
+ Invoke-WebRequest -UseBasicParsing -Uri $RemotePath -OutFile $LocalPath
+ return
+ }
+ catch {
+ Write-Verbose "Request failed. $retries retries remaining"
+ }
+ }
+
+ Write-Error "Download failed: '$RemotePath'."
+}
+
+#
+# Main
+#
+
+# Load configuration or set defaults
+
+$Path = Resolve-Path $Path
+if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' }
+
+if (Test-Path $ConfigFile) {
+ try {
+ $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json
+ if ($config) {
+ if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel }
+ if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource}
+ }
+ } catch {
+ Write-Warning "$ConfigFile could not be read. Its settings will be ignored."
+ Write-Warning $Error[0]
+ }
+}
+
+if (!$DotNetHome) {
+ $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } `
+ elseif ($CI) { Join-Path $PSScriptRoot '.dotnet' } `
+ elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} `
+ elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}`
+ else { Join-Path $PSScriptRoot '.dotnet'}
+}
+
+if (!$LockFile) { $LockFile = Join-Path $Path 'korebuild-lock.txt' }
+if (!$Channel) { $Channel = 'master' }
+if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' }
+
+if ($PackageVersionPropsUrl) {
+ $IntermediateDir = Join-Path $PSScriptRoot 'obj'
+ $PropsFilePath = Join-Path $IntermediateDir 'external-dependencies.props'
+ New-Item -ItemType Directory $IntermediateDir -ErrorAction Ignore | Out-Null
+ Get-RemoteFile "${PackageVersionPropsUrl}${AccessTokenSuffix}" $PropsFilePath
+ $MSBuildArguments += "-p:DotNetPackageVersionPropsPath=$PropsFilePath"
+}
+
+if ($RestoreSources) {
+ $MSBuildArguments += "-p:DotNetAdditionalRestoreSources=$RestoreSources"
+}
+
+if ($AssetRootUrl) {
+ $MSBuildArguments += "-p:DotNetAssetRootUrl=$AssetRootUrl"
+}
+
+if ($AccessTokenSuffix) {
+ $MSBuildArguments += "-p:DotNetAssetRootAccessTokenSuffix=$AccessTokenSuffix"
+}
+
+if ($ProductBuildId) {
+ $MSBuildArguments += "-p:DotNetProductBuildId=$ProductBuildId"
+}
+
+# Execute
+
+$korebuildPath = Get-KoreBuild
+Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1')
+
+try {
+ Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI
+ Invoke-KoreBuildCommand $Command @MSBuildArguments
+}
+finally {
+ Remove-Module 'KoreBuild' -ErrorAction Ignore
+}
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000000..cfc0a36904
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,314 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+#
+# variables
+#
+
+RESET="\033[0m"
+RED="\033[0;31m"
+YELLOW="\033[0;33m"
+MAGENTA="\033[0;95m"
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+verbose=false
+update=false
+reinstall=false
+repo_path="$DIR"
+lockfile_path=''
+channel=''
+tools_source=''
+ci=false
+package_version_props_url=''
+asset_root_url=''
+access_token_suffix=''
+restore_sources=''
+product_build_id=''
+msbuild_args=()
+
+#
+# Functions
+#
+__usage() {
+ echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]"
+ echo ""
+ echo "Arguments:"
+ echo " command The command to be run."
+ echo " ... Arguments passed to the command. Variable number of arguments allowed."
+ echo ""
+ echo "Options:"
+ echo " --verbose Show verbose output."
+ echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.."
+ echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json."
+ echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet."
+ echo " --path The directory to build. Defaults to the directory containing the script."
+ echo " --lockfile The path to the korebuild-lock.txt file. Defaults to \$repo_path/korebuild-lock.txt"
+ echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file."
+ echo " --package-version-props-url The url of the package versions props path containing dependency versions."
+ echo " --access-token The query string to append to any blob store access for PackageVersionPropsUrl, if any."
+ echo " --restore-sources Semi-colon delimited list of additional NuGet feeds to use as part of restore."
+ echo " --product-build-id The product build ID for correlation with orchestrated builds."
+ echo " -u|--update Update to the latest KoreBuild even if the lock file is present."
+ echo " --reinstall Reinstall KoreBuild."
+ echo " --ci Apply CI specific settings and environment variables."
+ echo ""
+ echo "Description:"
+ echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be."
+ echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel."
+
+ if [[ "${1:-}" != '--no-exit' ]]; then
+ exit 2
+ fi
+}
+
+get_korebuild() {
+ local version
+ if [ ! -f "$lockfile_path" ] || [ "$update" = true ]; then
+ __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lockfile_path"
+ fi
+ version="$(grep 'version:*' -m 1 "$lockfile_path")"
+ if [[ "$version" == '' ]]; then
+ __error "Failed to parse version from $lockfile_path. Expected a line that begins with 'version:'"
+ return 1
+ fi
+ version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+ local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version"
+
+ if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then
+ rm -rf "$korebuild_path"
+ fi
+
+ {
+ if [ ! -d "$korebuild_path" ]; then
+ mkdir -p "$korebuild_path"
+ local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip"
+ tmpfile="$(mktemp)"
+ echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}"
+ if __get_remote_file "$remote_path" "$tmpfile"; then
+ unzip -q -d "$korebuild_path" "$tmpfile"
+ fi
+ rm "$tmpfile" || true
+ fi
+
+ source "$korebuild_path/KoreBuild.sh"
+ } || {
+ if [ -d "$korebuild_path" ]; then
+ echo "Cleaning up after failed installation"
+ rm -rf "$korebuild_path" || true
+ fi
+ return 1
+ }
+}
+
+__error() {
+ echo -e "${RED}error: $*${RESET}" 1>&2
+}
+
+__warn() {
+ echo -e "${YELLOW}warning: $*${RESET}"
+}
+
+__machine_has() {
+ hash "$1" > /dev/null 2>&1
+ return $?
+}
+
+__get_remote_file() {
+ local remote_path=$1
+ local local_path=$2
+
+ if [[ "$remote_path" != 'http'* ]]; then
+ cp "$remote_path" "$local_path"
+ return 0
+ fi
+
+ local failed=false
+ if __machine_has wget; then
+ wget --tries 10 --quiet -O "$local_path" "$remote_path" || failed=true
+ else
+ failed=true
+ fi
+
+ if [ "$failed" = true ] && __machine_has curl; then
+ failed=false
+ curl --retry 10 -sSL -f --create-dirs -o "$local_path" "$remote_path" || failed=true
+ fi
+
+ if [ "$failed" = true ]; then
+ __error "Download failed: $remote_path" 1>&2
+ return 1
+ fi
+}
+
+#
+# main
+#
+
+command="${1:-}"
+shift
+
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ -\?|-h|--help)
+ __usage --no-exit
+ exit 0
+ ;;
+ -c|--channel|-Channel)
+ shift
+ channel="${1:-}"
+ [ -z "$channel" ] && __error "Missing value for parameter --channel" && __usage
+ ;;
+ --config-file|-ConfigFile)
+ shift
+ config_file="${1:-}"
+ [ -z "$config_file" ] && __error "Missing value for parameter --config-file" && __usage
+ if [ ! -f "$config_file" ]; then
+ __error "Invalid value for --config-file. $config_file does not exist."
+ exit 1
+ fi
+ ;;
+ -d|--dotnet-home|-DotNetHome)
+ shift
+ DOTNET_HOME="${1:-}"
+ [ -z "$DOTNET_HOME" ] && __error "Missing value for parameter --dotnet-home" && __usage
+ ;;
+ --path|-Path)
+ shift
+ repo_path="${1:-}"
+ [ -z "$repo_path" ] && __error "Missing value for parameter --path" && __usage
+ ;;
+ --[Ll]ock[Ff]ile)
+ shift
+ lockfile_path="${1:-}"
+ [ -z "$lockfile_path" ] && __error "Missing value for parameter --lockfile" && __usage
+ ;;
+ -s|--tools-source|-ToolsSource)
+ shift
+ tools_source="${1:-}"
+ [ -z "$tools_source" ] && __error "Missing value for parameter --tools-source" && __usage
+ ;;
+ --package-version-props-url|-PackageVersionPropsUrl)
+ shift
+ # This parameter can be an empty string, but it should be set
+ [ -z "${1+x}" ] && __error "Missing value for parameter --package-version-props-url" && __usage
+ package_version_props_url="$1"
+ ;;
+ --access-token-suffix|-AccessTokenSuffix)
+ shift
+ # This parameter can be an empty string, but it should be set
+ [ -z "${1+x}" ] && __error "Missing value for parameter --access-token-suffix" && __usage
+ access_token_suffix="$1"
+ ;;
+ --restore-sources|-RestoreSources)
+ shift
+ # This parameter can be an empty string, but it should be set
+ [ -z "${1+x}" ] && __error "Missing value for parameter --restore-sources" && __usage
+ restore_sources="$1"
+ ;;
+ --asset-root-url|-AssetRootUrl)
+ shift
+ # This parameter can be an empty string, but it should be set
+ [ -z "${1+x}" ] && __error "Missing value for parameter --asset-root-url" && __usage
+ asset_root_url="$1"
+ ;;
+ --product-build-id|-ProductBuildId)
+ shift
+ # This parameter can be an empty string, but it should be set
+ [ -z "${1+x}" ] && __error "Missing value for parameter --product-build-id" && __usage
+ product_build_id="$1"
+ ;;
+ -u|--update|-Update)
+ update=true
+ ;;
+ --reinstall|-Reinstall)
+ reinstall=true
+ ;;
+ --ci|-[Cc][Ii])
+ ci=true
+ if [[ -z "${DOTNET_HOME:-}" ]]; then
+ DOTNET_HOME="$DIR/.dotnet"
+ fi
+ ;;
+ --verbose|-Verbose)
+ verbose=true
+ ;;
+ *)
+ msbuild_args[${#msbuild_args[*]}]="$1"
+ ;;
+ esac
+ shift
+done
+
+if ! __machine_has unzip; then
+ __error 'Missing required command: unzip'
+ exit 1
+fi
+
+if ! __machine_has curl && ! __machine_has wget; then
+ __error 'Missing required command. Either wget or curl is required.'
+ exit 1
+fi
+
+[ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json"
+if [ -f "$config_file" ]; then
+ if __machine_has jq ; then
+ if jq '.' "$config_file" >/dev/null ; then
+ config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")"
+ config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")"
+ else
+ __error "$config_file is invalid JSON. Its settings will be ignored."
+ exit 1
+ fi
+ elif __machine_has python ; then
+ if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then
+ config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")"
+ config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")"
+ else
+ __error "$config_file is invalid JSON. Its settings will be ignored."
+ exit 1
+ fi
+ else
+ __error 'Missing required command: jq or python. Could not parse the JSON file. Its settings will be ignored.'
+ exit 1
+ fi
+
+ [ ! -z "${config_channel:-}" ] && channel="$config_channel"
+ [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source"
+fi
+
+[ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet"
+
+if [ ! -z "$package_version_props_url" ]; then
+ intermediate_dir="$repo_path/obj"
+ props_file_path="$intermediate_dir/external-dependencies.props"
+ mkdir -p "$intermediate_dir"
+ __get_remote_file "$package_version_props_url" "$props_file_path"
+ msbuild_args[${#msbuild_args[*]}]="-p:DotNetPackageVersionPropsPath=$props_file_path"
+fi
+
+if [ ! -z "$restore_sources" ]; then
+ msbuild_args[${#msbuild_args[*]}]="-p:DotNetAdditionalRestoreSources=$restore_sources"
+fi
+
+if [ ! -z "$asset_root_url" ]; then
+ msbuild_args[${#msbuild_args[*]}]="-p:DotNetAssetRootUrl=$asset_root_url"
+fi
+
+if [ ! -z "$access_token_suffix" ]; then
+ msbuild_args[${#msbuild_args[*]}]="-p:DotNetAssetRootAccessTokenSuffix=$access_token_suffix"
+fi
+
+if [ ! -z "$product_build_id" ]; then
+ msbuild_args[${#msbuild_args[*]}]="-p:DotNetProductBuildId=$product_build_id"
+fi
+
+[ -z "$lockfile_path" ] && lockfile_path="$repo_path/korebuild-lock.txt"
+[ -z "$channel" ] && channel='master'
+[ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools'
+
+get_korebuild
+set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci"
+
+# This incantation avoids unbound variable issues if msbuild_args is empty
+# https://stackoverflow.com/questions/7577052/bash-empty-array-expansion-with-set-u
+invoke_korebuild_command "$command" ${msbuild_args[@]+"${msbuild_args[@]}"}
diff --git a/scripts/GenerateTags.ps1 b/scripts/GenerateTags.ps1
new file mode 100755
index 0000000000..a0dc354f44
--- /dev/null
+++ b/scripts/GenerateTags.ps1
@@ -0,0 +1,130 @@
+#!/usr/bin/env pwsh
+
+<#
+.SYNOPSIS
+ Generates a tag on this repo and adds a tag for each submodule that corresponds
+ to the value in version.props
+.PARAMETER Push
+ Push the tag to origin
+.PARAMETER OutFile
+ When specified, generate a .csv with repo names and tags
+.PARAMETER WhatIf
+ Dry run
+#>
+[cmdletbinding(PositionalBinding = $false, SupportsShouldProcess = $true)]
+param(
+ [switch]$Push,
+ [string]$OutFile
+)
+
+$ErrorActionPreference = 'Stop'
+Import-Module -Scope Local -Force "$PSScriptRoot/common.psm1"
+Set-StrictMode -Version 1
+
+function New-GitTag {
+ [cmdletbinding(SupportsShouldProcess = $true)]
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$Repo,
+ [Parameter(Mandatory = $true)]
+ [string]$Tag
+ )
+
+ Push-Location $Repo
+ try {
+ git show-ref --tags --verify "refs/tags/$Tag" -q
+ $existingTag = $?
+
+ if ($existingTag) {
+ Write-Warning "${Repo}: Tag '$Tag' already exists. Skipped adding tag"
+ }
+ else {
+ if ($PSCmdlet.ShouldProcess($Repo, "Tag $Tag")) {
+ Invoke-Block { & git tag -m "v$Tag" $Tag HEAD }
+ Write-Host -f Magenta "${Repo}: added tag '$Tag'"
+ }
+ }
+
+ if ($Push -and $PSCmdlet.ShouldProcess($Repo, "Push tag $Tag to origin")) {
+ Invoke-Block { & git push origin refs/tags/$Tag }
+ }
+ }
+ finally {
+ Pop-Location
+ }
+}
+
+#
+# Gets the package version by invoking KoreBuild on a repo with a custom target that spits out the package version
+#
+function Get-PackageVersion([string]$repoRoot) {
+ $buildScript = if (-not $IsCoreCLR -or $IsWindows) { 'build.ps1' } else { 'build.sh' }
+ $inspectTarget = "/p:CustomAfterKoreBuildTargets=$PSScriptRoot/GetPackageVersion.targets"
+ Write-Verbose "Running `"$repoRoot/$buildScript`" $inspectTarget /v:m /p:IsFinalBuild=true /t:Noop /t:GetPackageVersion /p:BuildNumber=t000"
+ # Add the /t:Noop target which may be used by the bootstrapper to skip unimportant initialization
+ $output = & "$repoRoot/$buildScript" $inspectTarget /v:m /p:IsFinalBuild=true /t:Noop /t:GetPackageVersion /p:BuildNumber=t000
+ $output | out-string | Write-Verbose
+ if (-not $? -or $LASTEXITCODE -ne 0) {
+ throw "$buildScript failed on $repoRoot. Exit code $LASTEXITCODE"
+ }
+ $packageVersion = $output | where-object { $_ -like '*PackageVersion=*' } | select-object -first 1
+ $packageVersion = $packageVersion -replace 'PackageVersion=', ''
+ $packageVersion = $packageVersion -replace '-final', ''
+ $packageVersion = $packageVersion -replace '-t000', ''
+ if ($packageVersion) { $packageVersion = $packageVersion.Trim() }
+ if (-not $packageVersion) {
+ throw "Could not determine final package version for $repoRoot"
+ }
+ return $packageVersion.Trim()
+}
+
+$repoRoot = Resolve-Path "$PSScriptRoot/../"
+
+Write-Warning "Make sure you have run ``git submodule update`` first to pin the submodules to the correct commit"
+if (-not $PSCmdlet.ShouldContinue("Continue?", "This will apply tags to all submodules")) {
+ Write-Host "Exiting"
+ exit 1
+}
+
+
+$universeTag = Get-PackageVersion $repoRoot
+New-GitTag $repoRoot $universeTag -WhatIf:$WhatIfPreference
+
+$tags = @([pscustomobject] @{
+ repo = $(git config remote.origin.url)
+ tag = $universeTag
+ commit = $(git rev-parse HEAD)
+ })
+
+Get-Submodules $repoRoot | ForEach-Object {
+ $modPath = $_.path
+ $module = $_.module
+ if (-not (Test-Path (Join-Path $_.path 'version.props'))) {
+ Write-Warning "$module does not have a version.props file. Skipping"
+ return
+ }
+
+ try {
+ $tag = Get-PackageVersion $_.path
+ if ($tag -ne $universeTag) {
+ Write-Warning "${module}: version ($tag) does not match universe ($universeTag)"
+ }
+ $tags += [pscustomobject] @{
+ repo = $_.remote
+ tag = $tag
+ commit = $_.commit
+ }
+ }
+ catch {
+ Write-Warning "${module}: Could not automatically determine tag for $modPath. Skipping"
+ return
+ }
+
+ New-GitTag $_.path $tag -WhatIf:$WhatIfPreference
+}
+
+$tags | Format-Table
+
+if ($OutFile) {
+ $tags | Select-Object -Property * | Export-Csv -Path $OutFile -WhatIf:$false -NoTypeInformation
+}
diff --git a/scripts/GetPackageVersion.targets b/scripts/GetPackageVersion.targets
new file mode 100644
index 0000000000..061c114027
--- /dev/null
+++ b/scripts/GetPackageVersion.targets
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/scripts/ListRepoVersions.ps1 b/scripts/ListRepoVersions.ps1
new file mode 100644
index 0000000000..06ba399166
--- /dev/null
+++ b/scripts/ListRepoVersions.ps1
@@ -0,0 +1,23 @@
+#!/usr/bin/env pwsh
+
+<#
+.SYNOPSIS
+ Lists the version of all submodules and this repo
+.PARAMETER Shipping
+ Only list repos that are shipping
+#>
+[cmdletbinding(SupportsShouldProcess = $true)]
+param(
+ [switch]$Shipping = $false
+)
+
+Set-StrictMode -Version 2
+$ErrorActionPreference = 'Stop'
+
+Import-Module -Scope Local -Force "$PSScriptRoot/common.psm1"
+
+Assert-Git
+
+$RepoRoot = Resolve-Path "$PSScriptRoot/../"
+
+Get-Submodules $RepoRoot -Shipping:$Shipping | Format-Table -Property 'module','versionPrefix'
diff --git a/scripts/PatchVersionPrefix.ps1 b/scripts/PatchVersionPrefix.ps1
new file mode 100755
index 0000000000..d8cce86487
--- /dev/null
+++ b/scripts/PatchVersionPrefix.ps1
@@ -0,0 +1,111 @@
+#!/usr/bin/env pwsh -c
+
+<#
+.SYNOPSIS
+ Updates the version.props file in repos to a newer patch version
+.PARAMETER Repos
+ A list of the repositories that should be patched
+.PARAMETER Mode
+ Version bump options: Major, Minor, Patch
+.PARAMETER VersionSuffix
+ The version suffix to use
+#>
+[cmdletbinding(SupportsShouldProcess = $true)]
+param(
+ [Parameter(Mandatory = $true)]
+ [string[]]$Repos,
+ [Parameter(Mandatory = $true)]
+ [ValidateSet('Major', 'Minor', 'Patch')]
+ [string]$Mode,
+ [string]$VersionSuffix = $null,
+ [switch]$NoCommit
+)
+
+$ErrorActionPreference = 'Stop'
+
+Import-Module -Scope Local -Force "$PSScriptRoot/common.psm1"
+
+function SetVersionSuffix([System.Xml.XmlNode]$node) {
+ if (-not $node) {
+ return
+ }
+ $node.InnerText = $VersionSuffix
+ return "Setting $($node.Name) to $VersionSuffix"
+}
+
+function BumpVersion([System.Xml.XmlNode]$node) {
+ if (-not $node) {
+ return
+ }
+ [version] $version = $node.InnerText
+
+ $experimental = $version.Major -eq 0
+
+ switch ($mode) {
+ { ($_ -ne 'Patch') -and $experimental} {
+ $node.InnerText = "{0}.{1}.{2}" -f $version.Major, ($version.Minor + 1), 0
+ }
+ { ($_ -eq 'Major') -and -not $experimental } {
+ $node.InnerText = "{0}.{1}.{2}" -f ($version.Major + 1), 0, 0
+ }
+ { ($_ -eq 'Minor') -and -not $experimental } {
+ $node.InnerText = "{0}.{1}.{2}" -f $version.Major, ($version.Minor + 1), 0
+ }
+ 'Patch' {
+ $node.InnerText = "{0}.{1}.{2}" -f $version.Major, $version.Minor, ($version.Build + 1)
+ }
+ default {
+ throw "Could not figure out how to apply patch policy $mode"
+ }
+ }
+ return "Bumping version from $version to $($node.InnerText)"
+}
+
+foreach ($repo in $Repos) {
+ $repoPath = "$PSScriptRoot/../modules/$repo"
+ Push-Location $repoPath
+ try
+ {
+ $path = "$repoPath/version.props"
+ Write-Host -ForegroundColor Magenta "Updating $repo"
+ if (-not (Test-Path $path)) {
+ Write-Warning "$path does not exist"
+ continue
+ }
+ $path = Resolve-Path $path
+ Write-Verbose "$path"
+ [xml] $xml = LoadXml $path
+
+ $suffix = $xml.SelectSingleNode('/Project/PropertyGroup/VersionSuffix')
+ if (-not $suffix) {
+ write-error "$path does not have VersionSuffix"
+ }
+
+ if ($VersionSuffix) {
+ SetVersionSuffix $xml.SelectSingleNode('/Project/PropertyGroup/VersionSuffix') | write-host
+ SetVersionSuffix $xml.SelectSingleNode('/Project/PropertyGroup/ExperimentalProjectVersionSuffix') | write-host
+ SetVersionSuffix $xml.SelectSingleNode('/Project/PropertyGroup/ExperimentalVersionSuffix') | write-host
+ }
+
+ $versionPrefix = $xml.SelectSingleNode('/Project/PropertyGroup/VersionPrefix')
+ $epxVersionPrefix = $xml.SelectSingleNode('/Project/PropertyGroup/ExperimentalProjectVersionPrefix')
+ $exVersionPrefix = $xml.SelectSingleNode('/Project/PropertyGroup/ExperimentalVersionPrefix')
+ BumpVersion $epxVersionPrefix | write-host
+ BumpVersion $exVersionPrefix | write-host
+ $message = BumpVersion $versionPrefix
+ Write-Host $message
+
+ if ($PSCmdlet.ShouldProcess("Update $path")) {
+ SaveXml $xml $path
+ if (-not $NoCommit) {
+ Invoke-Block { & git add $path }
+ Invoke-Block { & git commit -m $message }
+ }
+ }
+ }
+ finally
+ {
+ Pop-Location
+ }
+}
+
diff --git a/scripts/Tag-TeamCityBuild.ps1 b/scripts/Tag-TeamCityBuild.ps1
new file mode 100755
index 0000000000..d624d4f50c
--- /dev/null
+++ b/scripts/Tag-TeamCityBuild.ps1
@@ -0,0 +1,37 @@
+#!/usr/bin/env pwsh
+
+<#
+.SYNOPSIS
+ Tags the given TeamCity build with the given tag.
+.PARAMETER BuildId
+ The BuildId of the build to be tagged.
+.PARAMETER Tag
+ The tag to put on this build.
+#>
+
+[cmdletbinding(SupportsShouldProcess = $true)]
+param(
+ [Parameter(Mandatory = $true)]
+ [string]$BuildId,
+ [Parameter(Mandatory = $true)]
+ [string]$Tag,
+ [Parameter(Mandatory = $true)]
+ [string]$UserName,
+ [Parameter(Mandatory = $true)]
+ [string]$Password
+)
+
+$ErrorActionPreference = 'Stop'
+Set-StrictMode -Version 2
+
+$authInfo = "${UserName}:$Password"
+$authEncoded = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($authInfo))
+$basicAuthValue = "Basic $authEncoded"
+
+$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
+$headers.Add("Authorization", $basicAuthValue)
+$headers.Add("Content-Type", "text/plain")
+
+$uri = "http://aspnetci/app/rest/builds/$BuildId/tags/"
+
+Invoke-WebRequest -Uri $uri -Method 'POST' -Headers $headers -Body $Tag -ErrorAction SilentlyContinue
diff --git a/scripts/TagRepos.ps1 b/scripts/TagRepos.ps1
new file mode 100644
index 0000000000..2bb595027c
--- /dev/null
+++ b/scripts/TagRepos.ps1
@@ -0,0 +1,66 @@
+#!/usr/bin/env pwsh
+
+<#
+.SYNOPSIS
+ Tags each repo according to VersionPrefix in version.props of that repo
+.PARAMETER Push
+ Push all updated tags
+.PARAMETER ForceUpdateTag
+ This will call git tag --force
+#>
+[cmdletbinding(SupportsShouldProcess = $true)]
+param(
+ [switch]$Push = $false,
+ [switch]$ForceUpdateTag = $false
+)
+
+Set-StrictMode -Version 2
+$ErrorActionPreference = 'Stop'
+
+Import-Module -Scope Local -Force "$PSScriptRoot/common.psm1"
+
+Assert-Git
+
+$RepoRoot = Resolve-Path "$PSScriptRoot/../"
+
+Get-Submodules $RepoRoot -Shipping | % {
+ Push-Location $_.path | Out-Null
+ try {
+
+ if (-not $_.versionPrefix) {
+ Write-Warning "Could not determine tag version for $(_.path)"
+ }
+ else {
+ $tag = $_.versionPrefix
+ Write-Host "$($_.module) => $tag"
+
+ $gitTagArgs = @()
+ if ($ForceUpdateTag) {
+ $gitTagArgs += '--force'
+ }
+
+ Invoke-Block { & git tag @gitTagArgs $tag }
+
+ if ($Push) {
+ $gitPushArgs = @()
+ if ($WhatIfPreference) {
+ $gitPushArgs += '--dry-run'
+ }
+ Invoke-Block { & git push @gitPushArgs origin "refs/tags/${tag}" }
+ }
+
+ if ($WhatIfPreference) {
+ Invoke-Block { & git tag -d $tag } | Out-Null
+ }
+ }
+ }
+ catch {
+ Write-Host -ForegroundColor Red "Could not update $_"
+ throw
+ }
+ finally {
+ Pop-Location
+ }
+}
+
+
diff --git a/scripts/UpdateBuildTools.ps1 b/scripts/UpdateBuildTools.ps1
new file mode 100755
index 0000000000..efcda1e03b
--- /dev/null
+++ b/scripts/UpdateBuildTools.ps1
@@ -0,0 +1,99 @@
+#!/usr/bin/env pwsh
+
+<#
+.SYNOPSIS
+ Updates the build tools version and generates a commit message with the list of changes
+.PARAMETER RepoRoot
+ The directory containing the repo
+.PARAMETER GitAuthorName
+ The author name to use in the commit message. (Optional)
+.PARAMETER GitAuthorEmail
+ The author email to use in the commit message. (Optional)
+.PARAMETER GitCommitArgs
+ Additional arguments to pass into git-commit
+.PARAMETER NoCommit
+ Make changes without executing git-commit
+.PARAMETER ToolsSource
+ The location of the build tools
+.PARAMETER Force
+ Specified this to make a commit with any changes
+#>
+[cmdletbinding(SupportsShouldProcess = $true)]
+param(
+ [string]$RepoRoot,
+ [string]$GitAuthorName = $null,
+ [string]$GitAuthorEmail = $null,
+ [string[]]$GitCommitArgs = @(),
+ [string]$ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools',
+ [switch]$NoCommit,
+ [switch]$Force
+)
+
+$ErrorActionPreference = 'Stop'
+Set-StrictMode -Version 2
+
+if (-not $RepoRoot) {
+ $RepoRoot = Resolve-Path "$PSScriptRoot\.."
+}
+
+Import-Module "$PSScriptRoot/common.psm1" -Scope Local -Force
+
+function Get-KoreBuildVersion {
+ $lockFile = "$RepoRoot/korebuild-lock.txt"
+ if (!(Test-Path $lockFile)) {
+ return ''
+ }
+ $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1
+ if (!$version) {
+ Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'"
+ }
+ $version = $version.TrimStart('version:').Trim()
+ return $version
+}
+
+Push-Location $RepoRoot
+try {
+ Assert-Git
+
+ $oldVersion = Get-KoreBuildVersion
+
+ & "$RepoRoot/run.ps1" -Update -ToolsSource $ToolsSource -Command noop | Out-Null
+
+ $newVersion = Get-KoreBuildVersion
+
+ if ($oldVersion -eq $newVersion) {
+ Write-Host -ForegroundColor Magenta 'No changes to build tools'
+ exit 0
+ }
+
+ Invoke-Block { git add "$RepoRoot/global.json" }
+ Invoke-Block { git add "$RepoRoot/korebuild-lock.txt" }
+ Invoke-Block { git add "$RepoRoot/build/dependencies.props" }
+
+ $shortMessage = "Updating BuildTools from $oldVersion to $newVersion"
+ # add this to the commit message to make it possible to filter commit triggers based on message
+ $message = "$shortMessage`n`n[auto-updated: buildtools]"
+
+ if (-not $NoCommit -and ($Force -or ($PSCmdlet.ShouldContinue($shortMessage, 'Create a new commit with these changes?')))) {
+
+ $gitConfigArgs = @()
+ if ($GitAuthorName) {
+ $gitConfigArgs += '-c', "user.name=$GitAuthorName"
+ }
+
+ if ($GitAuthorEmail) {
+ $gitConfigArgs += '-c', "user.email=$GitAuthorEmail"
+ }
+
+ Invoke-Block { git @gitConfigArgs commit -m $message @GitCommitArgs }
+ }
+ else {
+ # If composing this script with others, return the message that would have been used
+ return @{
+ message = $message
+ }
+ }
+}
+finally {
+ Pop-Location
+}
diff --git a/scripts/UpdateDependencies.ps1 b/scripts/UpdateDependencies.ps1
new file mode 100755
index 0000000000..7aeb8fd3f8
--- /dev/null
+++ b/scripts/UpdateDependencies.ps1
@@ -0,0 +1,81 @@
+#!/usr/bin/env pwsh -c
+<#
+.PARAMETER BuildXml
+ The URL or file path to a build.xml file that defines package versions to be used
+#>
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory = $true)]
+ $BuildXml,
+ [switch]$NoCommit,
+ [string]$GithubUpstreamBranch,
+ [string]$GithubEmail,
+ [string]$GithubUsername,
+ [string]$GithubToken
+)
+
+$ErrorActionPreference = 'Stop'
+Import-Module -Scope Local -Force "$PSScriptRoot/common.psm1"
+Set-StrictMode -Version 1
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+if (-not $NoCommit) {
+ Set-GitHubInfo $GithubToken $GithubUsername $GithubEmail
+}
+
+$depsPath = Resolve-Path "$PSScriptRoot/../build/dependencies.props"
+[xml] $dependencies = LoadXml $depsPath
+
+if ($BuildXml -like 'http*') {
+ $url = $BuildXml
+ New-Item -Type Directory "$PSScriptRoot/../obj/" -ErrorAction Ignore
+ $BuildXml = "$PSScriptRoot/../obj/build.xml"
+ Write-Verbose "Downloading from $url to $BuildXml"
+ Invoke-WebRequest -OutFile $BuildXml $url
+}
+
+[xml] $remoteDeps = LoadXml $BuildXml
+
+$variables = @{}
+
+foreach ($package in $remoteDeps.SelectNodes('//Package')) {
+ $packageId = $package.Id
+ $packageVersion = $package.Version
+ $varName = PackageIdVarName $packageId
+ Write-Verbose "Found {id: $packageId, version: $packageVersion, varName: $varName }"
+
+ if ($variables[$varName]) {
+ if ($variables[$varName].Where( {$_ -eq $packageVersion}, 'First').Count -eq 0) {
+ $variables[$varName] += $packageVersion
+ }
+ }
+ else {
+ $variables[$varName] = @($packageVersion)
+ }
+}
+
+if (-not $NoCommit) {
+ $currentBranch = Invoke-Block { & git rev-parse --abbrev-ref HEAD }
+
+ $destinationBranch = "dotnetbot/UpdateDeps"
+ Invoke-Block { & git checkout -tb $destinationBranch "origin/$GithubUpstreamBranch" }
+}
+
+try {
+ $updatedVars = UpdateVersions $variables $dependencies $depsPath
+
+ if ($NoCommit) {
+ exit 0
+ }
+
+ $body = CommitUpdatedVersions $updatedVars $dependencies $depsPath
+
+ if ($body) {
+ CreatePR "aspnet" $GithubUsername $GithubUpstreamBranch $destinationBranch $body $GithubToken
+ }
+}
+finally {
+ if (-not $NoCommit) {
+ Invoke-Block { & git checkout $currentBranch }
+ }
+}
diff --git a/scripts/UpdateDependenciesCoreFx.ps1 b/scripts/UpdateDependenciesCoreFx.ps1
new file mode 100644
index 0000000000..03be551f8a
--- /dev/null
+++ b/scripts/UpdateDependenciesCoreFx.ps1
@@ -0,0 +1,134 @@
+
+[CmdletBinding()]
+param(
+ [switch]$NoCommit,
+ [string]$GithubEmail,
+ [string]$GithubUsername,
+ [string]$GithubToken
+)
+# This script only works against master at the moment because only master prod-con builds allow you to access their results before the entire chain is finished.
+
+$ErrorActionPreference = 'Stop'
+Import-Module -Scope Local -Force "$PSScriptRoot/common.psm1"
+Set-StrictMode -Version 1
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+$githubRaw = "https://raw.githubusercontent.com"
+$versionsRepo = "dotnet/versions"
+$versionsBranch = "master"
+
+$coreSetupRepo = "dotnet/core-setup"
+$coreFxRepo = "dotnet/corefx"
+
+$coreSetupVersions = "$githubRaw/$versionsRepo/$versionsBranch/build-info/$coreSetupRepo/master/Latest_Packages.txt"
+
+$tempDir = "$PSScriptRoot/../obj"
+
+mkdir -Path $tempDir -ErrorAction Ignore
+
+$localCoreSetupVersions = "$tempDir/coresetup.packages"
+Write-Host "Downloading $coreSetupVersions to $localCoreSetupVersions"
+Invoke-WebRequest -OutFile $localCoreSetupVersions -Uri $coreSetupVersions
+
+$msNetCoreAppPackageVersion = $null
+$msNetCoreAppPackageName = "Microsoft.NETCore.App"
+
+Set-GitHubInfo $GithubToken $GithubUsername $GithubEmail
+
+$variables = @{}
+
+foreach ($line in Get-Content $localCoreSetupVersions) {
+ if ($line.StartsWith("$msNetCoreAppPackageName ")) {
+ $msNetCoreAppPackageVersion = $line.Trim("$msNetCoreAppPackageName ")
+ }
+ $parts = $line.Split(' ')
+ $packageName = $parts[0]
+
+ $varName = "$packageName" + "PackageVersion"
+ $varName = $varName.Replace('.', '')
+
+ $packageVersion = $parts[1]
+ if ($variables[$varName]) {
+ if ($variables[$varName].Where( {$_ -eq $packageVersion}, 'First').Count -eq 0) {
+ $variables[$varName] += $packageVersion
+ }
+ }
+ else {
+ $variables[$varName] = @($packageVersion)
+ }
+}
+
+if (!$msNetCoreAppPackageVersion) {
+ throw "$msNetCoreAppPackageName was not in $coreSetupVersions"
+}
+
+$coreAppDownloadLink = "https://dotnet.myget.org/F/dotnet-core/api/v2/package/$msNetCoreAppPackageName/$msNetCoreAppPackageVersion"
+$netCoreAppNupkg = "$tempDir/microsoft.netcore.app.zip"
+Invoke-WebRequest -OutFile $netCoreAppNupkg -Uri $coreAppDownloadLink
+$expandedNetCoreApp = "$tempDir/microsoft.netcore.app/"
+Expand-Archive -Path $netCoreAppNupkg -DestinationPath $expandedNetCoreApp -Force
+$versionsTxt = "$expandedNetCoreApp/$msNetCoreAppPackageName.versions.txt"
+
+$versionsCoreFxCommit = $null
+foreach ($line in Get-Content $versionsTxt) {
+ if ($line.StartsWith("dotnet/versions/corefx")) {
+ $versionsCoreFxCommit = $line.Split(' ')[1]
+ break
+ }
+}
+
+if (!$versionsCoreFxCommit) {
+ Throw "no 'dotnet/versions/corefx' in versions.txt of Microsoft.NETCore.App"
+}
+
+$coreFxVersionsUrl = "$githubRaw/$versionsRepo/$versionsCoreFxCommit/build-info/$coreFxRepo/$versionsBranch/Latest_Packages.txt"
+$localCoreFxVersions = "$tempDir/$corefx.packages"
+Invoke-WebRequest -OutFile $localCoreFxVersions -Uri $coreFxVersionsUrl
+
+foreach ($line in Get-Content $localCoreFxVersions) {
+ $parts = $line.Split(' ')
+
+ $packageName = $parts[0]
+
+ $varName = "$packageName" + "PackageVersion"
+ $varName = $varName.Replace('.', '')
+ $packageVersion = $parts[1]
+ if ($variables[$varName]) {
+ if ($variables[$varName].Where( {$_ -eq $packageVersion}, 'First').Count -eq 0) {
+ $variables[$varName] += $packageVersion
+ }
+ }
+ else {
+ $variables[$varName] = @($packageVersion)
+ }
+}
+
+$depsPath = Resolve-Path "$PSScriptRoot/../build/dependencies.props"
+Write-Host "Loading deps from $depsPath"
+[xml] $dependencies = LoadXml $depsPath
+
+if (-not $NoCommit) {
+ $baseBranch = "master"
+ Invoke-Block { & git fetch origin }
+
+ $currentBranch = Invoke-Block { & git rev-parse --abbrev-ref HEAD }
+ $destinationBranch = "upgrade-netcore-deps"
+
+ Invoke-Block { & git checkout -tb $destinationBranch "origin/$baseBranch" }
+}
+
+try {
+ $updatedVars = UpdateVersions $variables $dependencies $depsPath
+ if (-not $NoCommit) {
+ $body = CommitUpdatedVersions $updatedVars $dependencies $depsPath "Upgrade to .NET Core $msNetCoreAppPackageVersion"
+
+ if ($body) {
+ CreatePR "aspnet" $GithubUsername $baseBranch $destinationBranch $body $GithubToken
+ }
+ }
+}
+finally {
+ if (-not $NoCommit) {
+ Invoke-Block { & git checkout $currentBranch }
+ }
+}
diff --git a/scripts/UpdateRepos.ps1 b/scripts/UpdateRepos.ps1
new file mode 100755
index 0000000000..69d23232d8
--- /dev/null
+++ b/scripts/UpdateRepos.ps1
@@ -0,0 +1,148 @@
+#!/usr/bin/env pwsh
+
+<#
+.SYNOPSIS
+ Updates each repo Universe builds to new dependencies.props.
+.PARAMETER Source
+ The NuGet package source to find the lineup on.
+.PARAMETER LineupID
+ The ID of the Lineup to determine which versions to use.
+.PARAMETER LineupVersion
+ The version of the Lineup to be used.
+.PARAMETER NoPush
+ Make commits without pusing.
+.PARAMETER GitAuthorName
+ The author name to use in the commit message. (Optional)
+.PARAMETER GitAuthorEmail
+ The author email to use in the commit message. (Optional)
+.PARAMETER Force
+ Specified this to push commits without prompting.
+.PARAMETER GitCommitArgs
+ Any remaining arguments are passed as arguments to 'git commit' actions in each repo.
+#>
+[cmdletbinding(SupportsShouldProcess = $true)]
+param(
+ [Parameter(Mandatory = $true)]
+ [string]$Source,
+ [Parameter(Mandatory = $true)]
+ [string]$LineupID,
+ [Parameter(Mandatory = $true)]
+ [string]$LineupVersion,
+ [switch]$NoPush,
+ [string]$GitAuthorName = $null,
+ [string]$GitAuthorEmail = $null,
+ [switch]$Force,
+ [string[]]$GitCommitArgs = @()
+)
+
+$ErrorActionPreference = 'Stop'
+Set-StrictMode -Version 2
+
+Import-Module "$PSScriptRoot/common.psm1" -Scope Local -Force
+
+$RepoRoot = Resolve-Path "$PSScriptRoot\.."
+$ModuleDirectory = Join-Path $RepoRoot "modules"
+
+$gitConfigArgs = @()
+if ($GitAuthorName) {
+ $gitConfigArgs += '-c', "user.name=$GitAuthorName"
+}
+
+if ($GitAuthorEmail) {
+ $gitConfigArgs += '-c', "user.email=$GitAuthorEmail"
+}
+
+Push-Location $ModuleDirectory
+try {
+
+ $build_errors = @()
+ $submodules = Get-Submodules $RepoRoot
+ foreach ($submodule in $submodules) {
+ Push-Location $submodule.path
+ try {
+ Invoke-Block { & git fetch }
+ Invoke-Block { & git checkout origin/$($submodule.branch) }
+ $depsFile = Join-Path (Join-Path $($submodule.path) "build") "dependencies.props"
+
+ if (!(Test-Path $depsFile)) {
+ Write-Warning "No build\dependencies.props file exists for '$($submodule.module)'."
+ continue
+ }
+
+ $koreBuildLock = "korebuild-lock.txt"
+
+ $universeKoreBuildLock = (Join-Path $RepoRoot $koreBuildLock)
+ $submoduleKoreBuildLock = (Join-Path $submodule.path $koreBuildLock)
+
+ Copy-Item $universeKoreBuildLock $submoduleKoreBuildLock -Force
+
+ Write-Verbose "About to update dependencies.props for $($submodule.module)"
+ & .\run.ps1 upgrade deps --source $Source --id $LineupID --version $LineupVersion --deps-file $depsFile
+
+ Invoke-Block { & git @gitConfigArgs add $depsFile $koreBuildLock }
+
+ # If there were any changes test and push.
+ & git diff --cached --quiet ./
+ if ($LASTEXITCODE -ne 0) {
+ Invoke-Block { & git @gitConfigArgs commit --quiet -m "Update dependencies.props`n`n[auto-updated: dependencies]" @GitCommitArgs }
+
+ # Prepare this submodule for push
+ $sshUrl = "git@github.com:aspnet/$($submodule.module)"
+ Invoke-Block { & git remote set-url --push origin $sshUrl }
+
+ # Test the submodule
+ try {
+ Invoke-Block { & .\run.ps1 default-build /p:SkipTests=true }
+ }
+ catch {
+ Write-Warning "Error in $($submodule.module): $_"
+ $build_errors += @{
+ Repo = $submodule.module
+ Message = $_
+ }
+ continue
+ }
+
+ # Push the changes
+ if (-not $NoPush -and ($Force -or ($PSCmdlet.ShouldContinue("Pushing updates to repos.", 'Push the changes to these repos?')))) {
+ try {
+ $newBranch = "maestro/$($submodule.branch)"
+ Invoke-Block { & git @gitConfigArgs push origin HEAD:refs/heads/$newBranch}
+ }
+ catch {
+ Write-Warning "Error in pushing $($newBranch): $_"
+ $build_errors += @{
+ Repo = $submodule.module
+ Message = $_
+ }
+ continue
+ }
+ }
+ }
+ else {
+ Write-Host "No changes in $($submodule.module)"
+ }
+ }
+ catch {
+ Write-Warning "Error in $($submodule.module): $_"
+ $build_errors += @{
+ Repo = $submodule.module
+ Message = $_
+ }
+ }
+ finally {
+ Pop-Location
+ }
+ }
+
+ if ($build_errors.Count -gt 0 ) {
+ Write-Warning "The following repos failed:"
+ foreach ($error in $build_errors) {
+ Write-Warning " - $($error.Repo)"
+ }
+ throw "Failed to build"
+ }
+}
+finally {
+ Pop-Location
+}
diff --git a/scripts/UpdateSubmodules.ps1 b/scripts/UpdateSubmodules.ps1
new file mode 100755
index 0000000000..11058328f0
--- /dev/null
+++ b/scripts/UpdateSubmodules.ps1
@@ -0,0 +1,144 @@
+#!/usr/bin/env pwsh -c
+
+<#
+.SYNOPSIS
+ Updates git submodules and generates a commit message with the list of changes
+.PARAMETER GitAuthorName
+ The author name to use in the commit message. (Optional)
+.PARAMETER GitAuthorEmail
+ The author email to use in the commit message. (Optional)
+.PARAMETER GitCommitArgs
+ Additional arguments to pass into git-commit
+.PARAMETER NoCommit
+ Make changes without executing git-commit
+.PARAMETER Force
+ Specified this to make a commit with any changes
+.PARAMETER IgnoredRepos
+ Repos to not update (likely because they are temporarily broken).
+#>
+[cmdletbinding(SupportsShouldProcess = $true)]
+param(
+ [string]$GitAuthorName = $null,
+ [string]$GitAuthorEmail = $null,
+ [string[]]$GitCommitArgs = @(),
+ [switch]$NoCommit,
+ [switch]$Force,
+ [string[]]$IgnoredRepos = @()
+)
+
+$ErrorActionPreference = 'Stop'
+Set-StrictMode -Version 2
+
+$RepoRoot = Resolve-Path "$PSScriptRoot\.."
+$ModuleDirectory = Join-Path $RepoRoot "modules"
+
+Import-Module "$PSScriptRoot/common.psm1" -Scope Local -Force
+
+function Get-GitChanges([string]$Path) {
+ Write-Verbose "git diff --cached --quiet $Path"
+ & git diff --cached --quiet $Path | Out-Null
+ if ($LastExitCode -ne 0) {
+ return $true
+ }
+ Write-Verbose "git diff --quiet $Path"
+ & git diff --quiet $Path | Out-Null
+ return $LastExitCode -ne 0
+}
+
+Push-Location $RepoRoot | Out-Null
+try {
+ Assert-Git
+
+ Write-Host "Checking that submodules are in a clean state first..."
+ if (Get-GitChanges $ModuleDirectory) {
+ Write-Error "$RepoRoot/modules is in an unclean state. Reset submodules first by running ``git submodule update``"
+ exit 1
+ }
+
+ $submodules = Get-Submodules $RepoRoot -Verbose:$VerbosePreference
+
+ foreach ($submodule in $submodules) {
+ $submoduleName = $submodule.module
+ if ($IgnoredRepos.Contains($submoduleName))
+ {
+ Write-Host "Skipping $submoduleName due to IgnoredRepos."
+ continue
+ }
+
+ $submodulePath = $submodule.path
+ Write-Host "Updating $submodulePath"
+
+ $vcs_name = "BUILD_VCS_NUMBER_" + ($submodule.module -replace '\.','_')
+ $newCommit = [environment]::GetEnvironmentVariable($vcs_name)
+
+ if (-not $newCommit) {
+ if ($env:TEAMCITY_PROJECT_NAME) {
+ throw "TeamCity env variable '$vcs_name' not found. Make sure to configure a VCS root for $submodulePath"
+ }
+ Invoke-Block { & git submodule update --remote $submodulePath }
+ Push-Location $submodulePath | Out-Null
+ try {
+ $newCommit = $(git rev-parse HEAD)
+ }
+ finally {
+ Pop-Location | Out-Null
+ }
+ }
+ else {
+ Push-Location $submodulePath | Out-Null
+ try {
+ Invoke-Block { & git checkout $newCommit }
+ }
+ finally {
+ Pop-Location | Out-Null
+ }
+ }
+
+ $submodule.newCommit = $newCommit
+ if ($newCommit -ne $submodule.commit) {
+ $submodule.changed = $true
+ Write-Host -ForegroundColor Cyan "`t=> $($submodule.module) updated to $($submodule.newCommit)"
+ }
+ else {
+ Write-Host -ForegroundColor Magenta "`t$($submodule.module) did not change"
+ }
+ }
+
+ $changes = $submodules `
+ | ? { $_.changed } `
+ | % {
+ Invoke-Block { & git add $_.path }
+ "$($_.module) => $($_.newCommit)"
+ }
+
+ if ($changes) {
+ $shortMessage = "Updating submodule(s) `n`n$( $changes -join "`n" )"
+ # add this to the commit message to make it possible to filter commit triggers based on message
+ $message = "$shortMessage`n`n[auto-updated: submodules]"
+ if (-not $NoCommit -and ($Force -or ($PSCmdlet.ShouldContinue($shortMessage, 'Create a new commit with these changes?')))) {
+
+ $gitConfigArgs = @()
+ if ($GitAuthorName) {
+ $gitConfigArgs += '-c',"user.name=$GitAuthorName"
+ }
+
+ if ($GitAuthorEmail) {
+ $gitConfigArgs += '-c',"user.email=$GitAuthorEmail"
+ }
+
+ Invoke-Block { & git @gitConfigArgs commit -m $message @GitCommitArgs }
+ }
+ else {
+ # If composing this script with others, return the message that would have been used
+ return @{
+ message = $message
+ }
+ }
+ }
+ else {
+ Write-Host -ForegroundColor Magenta 'No changes detected in git submodules'
+ }
+}
+finally {
+ Pop-Location
+}
diff --git a/scripts/common.psm1 b/scripts/common.psm1
new file mode 100644
index 0000000000..5225c6d7b8
--- /dev/null
+++ b/scripts/common.psm1
@@ -0,0 +1,314 @@
+$ErrorActionPreference = 'Stop'
+# Update the default TLS support to 1.2
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+function Assert-Git {
+ if (!(Get-Command git -ErrorAction Ignore)) {
+ Write-Error 'git is required to execute this script'
+ exit 1
+ }
+}
+
+function Invoke-Block([scriptblock]$cmd, [string]$WorkingDir = $null) {
+ if ($WorkingDir) {
+ Push-Location $WorkingDir
+ }
+
+ try {
+
+ $cmd | Out-String | Write-Verbose
+ & $cmd
+
+ # Need to check both of these cases for errors as they represent different items
+ # - $?: did the powershell script block throw an error
+ # - $lastexitcode: did a windows command executed by the script block end in error
+ if ((-not $?) -or ($lastexitcode -ne 0)) {
+ if ($error -ne $null)
+ {
+ Write-Warning $error[0]
+ }
+ throw "Command failed to execute: $cmd"
+ }
+ }
+ finally {
+ if ($WorkingDir) {
+ Pop-Location
+ }
+ }
+}
+
+function Get-Submodules {
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$RepoRoot,
+ [switch]$Shipping
+ )
+
+ $moduleConfigFile = Join-Path $RepoRoot ".gitmodules"
+ $submodules = @()
+
+ [xml] $submoduleConfig = Get-Content "$RepoRoot/build/submodules.props"
+ $repos = $submoduleConfig.Project.ItemGroup.Repository | % { $_.Include }
+
+ Get-ChildItem "$RepoRoot/modules/*" -Directory `
+ | ? { (-not $Shipping) -or $($repos -contains $($_.Name)) -or $_.Name -eq 'Templating' } `
+ | % {
+ Push-Location $_ | Out-Null
+ Write-Verbose "Attempting to get submodule info for $_"
+
+ if (Test-Path 'version.props') {
+ [xml] $versionXml = Get-Content 'version.props'
+ $versionPrefix = $versionXml.Project.PropertyGroup.VersionPrefix | select-object -first 1
+ $versionSuffix = $versionXml.Project.PropertyGroup.VersionSuffix | select-object -first 1
+ }
+ else {
+ $versionPrefix = ''
+ $versionSuffix = ''
+ }
+
+ try {
+ $data = [PSCustomObject] @{
+ path = $_
+ module = $_.Name
+ commit = $(git rev-parse HEAD)
+ newCommit = $null
+ changed = $false
+ remote = $(git config remote.origin.url)
+ branch = $(git config -f $moduleConfigFile --get submodule.modules/$($_.Name).branch )
+ versionPrefix = $versionPrefix
+ versionSuffix = $versionSuffix
+ }
+
+ $submodules += $data
+ }
+ finally {
+ Pop-Location | Out-Null
+ }
+ }
+
+ return $submodules
+}
+
+function SaveXml([xml]$xml, [string]$path) {
+ Write-Verbose "Saving to $path"
+ $ErrorActionPreference = 'stop'
+
+ $settings = New-Object System.XML.XmlWriterSettings
+ $settings.OmitXmlDeclaration = $true
+ $settings.Encoding = New-Object System.Text.UTF8Encoding( $true )
+ $writer = [System.XML.XMLTextWriter]::Create($path, $settings)
+ $xml.Save($writer)
+ $writer.Close()
+}
+
+function LoadXml([string]$path) {
+ Write-Verbose "Reading from $path"
+
+ $ErrorActionPreference = 'stop'
+ $obj = new-object xml
+ $obj.PreserveWhitespace = $true
+ $obj.Load($path)
+ return $obj
+}
+
+function PackageIdVarName([string]$packageId) {
+ $canonicalVarName = ''
+ $upperCaseNext = $true
+ for ($i = 0; $i -lt $packageId.Length; $i++) {
+ $ch = $packageId[$i]
+ if (-not [System.Char]::IsLetterOrDigit(($ch))) {
+ $upperCaseNext = $true
+ continue
+ }
+ if ($upperCaseNext) {
+ $ch = [System.Char]::ToUpperInvariant($ch)
+ $upperCaseNext = $false
+ }
+ $canonicalVarName += $ch
+ }
+ $canonicalVarName += "PackageVersion"
+ return $canonicalVarName
+}
+
+function Ensure-Hub() {
+ $tmpDir = "$PSScriptRoot\tmp"
+ $zipDir = "$tmpDir\Hub"
+ $hubLocation = "$zipDir\bin\hub.exe"
+
+ if (-Not (Test-Path $hubLocation) ) {
+ $source = "https://github.com/github/hub/releases/download/v2.3.0-pre9/hub-windows-amd64-2.3.0-pre9.zip"
+ $zipLocation = "$tmpDir\hub.zip"
+
+ mkdir -Path $tmpDir -ErrorAction Ignore | Out-Null
+
+ Invoke-WebRequest -OutFile $zipLocation -Uri $source
+
+ Expand-Archive -Path $zipLocation -DestinationPath $zipDir -Force
+ if (-Not (Test-Path $hubLocation)) {
+ throw "Hub couldn't be downloaded"
+ }
+ }
+
+ return $hubLocation
+}
+
+function CreatePR(
+ [string]$baseFork,
+ [string]$headFork,
+ [string]$baseBranch,
+ [string]$destinationBranch,
+ [string]$body,
+ [string]$gitHubToken) {
+ $hubLocation = Ensure-Hub
+
+ Invoke-Block { git push -f https://$gitHubToken@github.com/$headFork/Universe.git $destinationBranch }
+ & $hubLocation pull-request -f -b "${baseFork}:$baseBranch" -h "${headFork}:$destinationBranch" -m $body
+}
+
+function Set-GithubInfo(
+ [string]$GitHubPassword,
+ [string]$GitHubUser,
+ [string]$GitHubEmail)
+{
+ $Env:GITHUB_TOKEN = $GitHubPassword
+ $Env:GITHUB_USER = $GitHubUser
+ $Env:GITHUB_EMAIL = $GitHubEmail
+}
+function CommitUpdatedVersions(
+ [hashtable]$updatedVars,
+ [xml]$dependencies,
+ [string]$depsPath,
+ [string]$subject = 'Updating external dependencies')
+{
+ $count = $updatedVars.Count
+ if ($count -gt 0) {
+ & git add build\dependencies.props
+
+ $gitConfigArgs = @()
+ if ($env:GITHUB_USER) {
+ $gitConfigArgs += '-c',"user.name=$env:GITHUB_USER"
+ }
+
+ if ($env:GITHUB_EMAIL) {
+ $gitConfigArgs += '-c',"user.email=$env:GITHUB_EMAIL"
+ }
+
+ Invoke-Block { & git @gitConfigArgs commit -m $subject } | Out-Null
+
+ $body = "$subject`n`n"
+
+ $body += "New versions:`n"
+
+ foreach ($var in $updatedVars.GetEnumerator()) {
+ $body += " $($var.Name)`n"
+ }
+
+ return $body
+ }
+}
+
+function UpdateVersions([hashtable]$variables, [xml]$dependencies, [string]$depsPath) {
+ $updatedVars = @{}
+
+ foreach ($varName in ($variables.Keys | sort)) {
+ $packageVersions = $variables[$varName]
+ if ($packageVersions.Length -gt 1) {
+ Write-Warning "Skipped $varName. Multiple version found. { $($packageVersions -join ', ') }."
+ continue
+ }
+
+ $packageVersion = $packageVersions | Select-Object -First 1
+
+ $depVarNode = $dependencies.SelectSingleNode("//PropertyGroup[`@Label=`"Package Versions: Auto`"]/$varName")
+ if ($depVarNode -and $depVarNode.InnerText -ne $packageVersion) {
+ $depVarNode.InnerText = $packageVersion
+ Write-Host -f DarkGray " Updating $varName to $packageVersion"
+ $updatedVars[$varName] = $packageVersion
+ }
+ elseif ($depVarNode) {
+ Write-Host -f DarkBlue " Didn't update $varName to $packageVersion because it was $($depVarNode.InnerText)"
+ }
+ else {
+ # This isn't a dependency we use
+ }
+ }
+
+ if ($updatedVars.Count -gt 0) {
+ Write-Host -f Cyan "Updating version variables in $depsPath"
+ SaveXml $dependencies $depsPath
+ }
+ else {
+ Write-Host -f Green "No changes found"
+ }
+
+ return $updatedVars
+}
+
+function Get-MSBuildPath {
+ param(
+ [switch]$Prerelease,
+ [string[]]$Requires
+ )
+
+ $vsInstallDir = $null
+ if ($env:VSINSTALLDIR -and (Test-Path $env:VSINSTALLDIR)) {
+ $vsInstallDir = $env:VSINSTALLDIR
+ Write-Verbose "Using VSINSTALLDIR=$vsInstallDir"
+ }
+ else {
+ $vswhere = "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe"
+ Write-Verbose "Using vswhere.exe from $vswhere"
+
+ if (-not (Test-Path $vswhere)) {
+ Write-Error "Missing prerequisite: could not find vswhere"
+ }
+
+ [string[]] $vswhereArgs = @()
+
+ if ($Prerelease) {
+ $vswhereArgs += '-prerelease'
+ }
+
+ if ($Requires) {
+ foreach ($r in $Requires) {
+ $vswhereArgs += '-requires', $r
+ }
+ }
+
+ $installs = & $vswhere -format json -version '[15.0, 16.0)' -latest -products * @vswhereArgs | ConvertFrom-Json
+ if (!$installs) {
+ Write-Error "Missing prerequisite: could not find any installations of Visual Studio"
+ }
+
+ $vs = $installs | Select-Object -First 1
+ $vsInstallDir = $vs.installationPath
+ Write-Host "Using $($vs.displayName)"
+ }
+
+ $msbuild = Join-Path $vsInstallDir 'MSBuild/15.0/bin/msbuild.exe'
+ if (!(Test-Path $msbuild)) {
+ Write-Error "Missing prerequisite: could not find msbuild.exe"
+ }
+ return $msbuild
+}
+
+function Get-RemoteFile([string]$RemotePath, [string]$LocalPath) {
+ if ($RemotePath -notlike 'http*') {
+ Copy-Item $RemotePath $LocalPath
+ return
+ }
+
+ $retries = 10
+ while ($retries -gt 0) {
+ $retries -= 1
+ try {
+ Invoke-WebRequest -UseBasicParsing -Uri $RemotePath -OutFile $LocalPath
+ return
+ }
+ catch {
+ Write-Verbose "Request failed. $retries retries remaining"
+ }
+ }
+
+ Write-Error "Download failed: '$RemotePath'."
+}
diff --git a/scripts/requirements.txt b/scripts/requirements.txt
new file mode 100644
index 0000000000..a550da3e64
--- /dev/null
+++ b/scripts/requirements.txt
@@ -0,0 +1,2 @@
+PyYAML==3.12
+termcolor==1.1.0
diff --git a/scripts/update_travis_appveyor_yml.py b/scripts/update_travis_appveyor_yml.py
new file mode 100755
index 0000000000..07fde1a9bf
--- /dev/null
+++ b/scripts/update_travis_appveyor_yml.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+
+import collections
+import glob
+import yaml
+from os import path
+from termcolor import colored, cprint
+from yaml.constructor import Constructor
+
+def dump_format(dump, tag, mapping, flow_style=None):
+ """
+ Better output formatting for YAML dictionaries
+ """
+ value = []
+ node = yaml.MappingNode(tag, value, flow_style=flow_style)
+ if dump.alias_key is not None:
+ dump.represented_objects[dump.alias_key] = node
+ best_style = True
+ if hasattr(mapping, 'items'):
+ mapping = mapping.items()
+ for item_key, item_value in mapping:
+ node_key = dump.represent_data(item_key)
+ node_value = dump.represent_data(item_value)
+ if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
+ best_style = False
+ if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style):
+ best_style = False
+ value.append((node_key, node_value))
+ if flow_style is None:
+ if dump.default_flow_style is not None:
+ node.flow_style = dump.default_flow_style
+ else:
+ node.flow_style = best_style
+ return node
+
+
+def add_bool_as_scalar(self, node):
+ """
+ Don't auto-parse boolean values
+ """
+ if node.value == 'true' or node.value == 'false' :
+ return self.construct_yaml_bool(node)
+ return self.construct_scalar(node)
+
+_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
+
+def dict_representer(dumper, data):
+ return dumper.represent_mapping(_mapping_tag, data.iteritems())
+
+
+def dict_constructor(loader, node):
+ return collections.OrderedDict(loader.construct_pairs(node))
+
+def update(pattern, updater):
+ print('\n\n\n')
+ cprint(pattern, 'magenta')
+
+ for f in glob.glob(path.join(repo_root, "modules", "*", pattern)):
+ yml = path.join(repo_root, f)
+
+ if not path.exists(yml):
+ cprint("File does not exist: {}".format(yml), 'red')
+ continue
+
+ print("Updating {}".format(yml))
+ document = yaml.load(open(yml, 'r'))
+ document = updater(document)
+ yml_file = open(yml, 'w')
+ yml_file.write(yaml.safe_dump(document, default_flow_style=False, indent=2))
+ yml_file.close()
+
+#
+# Config yaml parser
+#
+
+# Do not reorder keys in yaml file
+yaml.add_representer(collections.OrderedDict, dict_representer)
+yaml.add_constructor(_mapping_tag, dict_constructor)
+# Pretty print dictionaries
+yaml.SafeDumper.add_representer(collections.OrderedDict,
+ lambda dumper, value: dump_format(dumper, u'tag:yaml.org,2002:map', value))
+# Don't parse booleans - treat them as scalars
+yaml.Loader.add_constructor(u'tag:yaml.org,2002:bool', add_bool_as_scalar)
+yaml.SafeLoader.add_constructor(u'tag:yaml.org,2002:bool', add_bool_as_scalar)
+
+#
+# Main
+#
+
+repo_root = path.dirname(path.dirname(path.abspath(__file__)))
+
+def transform_yaml_doc(document):
+ if not 'branches' in document:
+ document['branches'] = {}
+ document['branches']['only'] = [
+ 'master', '/^release\/.*$/', '/^(.*\/)?ci-.*$/']
+ return document
+
+update(".travis.yml", transform_yaml_doc)
+update(".appveyor.yml", transform_yaml_doc)
diff --git a/src/DataProtection/DataProtection.sln b/src/DataProtection/DataProtection.sln
new file mode 100644
index 0000000000..3e9512f1d9
--- /dev/null
+++ b/src/DataProtection/DataProtection.sln
@@ -0,0 +1,333 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26814.1
+MinimumVisualStudioVersion = 15.0.26730.03
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{60336AB3-948D-4D15-A5FB-F32A2B91E814}"
+ ProjectSection(SolutionItems) = preProject
+ test\CreateTestCert.ps1 = test\CreateTestCert.ps1
+ test\Directory.Build.props = test\Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5A3A5DE3-49AD-431C-971D-B01B62D94AE2}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E1D86B1B-41D8-43C9-97FD-C2BF65C414E2}"
+ ProjectSection(SolutionItems) = preProject
+ .appveyor.yml = .appveyor.yml
+ .gitattributes = .gitattributes
+ .gitignore = .gitignore
+ .travis.yml = .travis.yml
+ CONTRIBUTING.md = CONTRIBUTING.md
+ build\dependencies.props = build\dependencies.props
+ Directory.Build.props = Directory.Build.props
+ Directory.Build.targets = Directory.Build.targets
+ korebuild.json = korebuild.json
+ LICENSE.txt = LICENSE.txt
+ NuGet.config = NuGet.config
+ NuGetPackageVerifier.json = NuGetPackageVerifier.json
+ Provision-AutoGenKeys.ps1 = Provision-AutoGenKeys.ps1
+ README.md = README.md
+ version.props = version.props
+ EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection", "src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj", "{1E570CD4-6F12-44F4-961E-005EE2002BC2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Test", "test\Microsoft.AspNetCore.DataProtection.Test\Microsoft.AspNetCore.DataProtection.Test.csproj", "{7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cryptography.Internal", "src\Microsoft.AspNetCore.Cryptography.Internal\Microsoft.AspNetCore.Cryptography.Internal.csproj", "{E2779976-A28C-4365-A4BB-4AD854FAF23E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cryptography.KeyDerivation", "src\Microsoft.AspNetCore.Cryptography.KeyDerivation\Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj", "{421F0383-34B1-402D-807B-A94542513ABA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cryptography.KeyDerivation.Test", "test\Microsoft.AspNetCore.Cryptography.KeyDerivation.Test\Microsoft.AspNetCore.Cryptography.KeyDerivation.Test.csproj", "{42C97F52-8D56-46BD-A712-4F22BED157A7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cryptography.Internal.Test", "test\Microsoft.AspNetCore.Cryptography.Internal.Test\Microsoft.AspNetCore.Cryptography.Internal.Test.csproj", "{37053D5F-5B61-47CE-8B72-298CE007FFB0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Abstractions", "src\Microsoft.AspNetCore.DataProtection.Abstractions\Microsoft.AspNetCore.DataProtection.Abstractions.csproj", "{4B115BDE-B253-46A6-97BF-A8B37B344FF2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Abstractions.Test", "test\Microsoft.AspNetCore.DataProtection.Abstractions.Test\Microsoft.AspNetCore.DataProtection.Abstractions.Test.csproj", "{FF650A69-DEE4-4B36-9E30-264EE7CFB478}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.SystemWeb", "src\Microsoft.AspNetCore.DataProtection.SystemWeb\Microsoft.AspNetCore.DataProtection.SystemWeb.csproj", "{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Extensions.Test", "test\Microsoft.AspNetCore.DataProtection.Extensions.Test\Microsoft.AspNetCore.DataProtection.Extensions.Test.csproj", "{04AA8E60-A053-4D50-89FE-E76C3DF45200}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.Extensions", "src\Microsoft.AspNetCore.DataProtection.Extensions\Microsoft.AspNetCore.DataProtection.Extensions.csproj", "{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.AzureStorage", "src\Microsoft.AspNetCore.DataProtection.AzureStorage\Microsoft.AspNetCore.DataProtection.AzureStorage.csproj", "{CC799B57-81E2-4F45-8A32-0D5F49753C3F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureBlob", "samples\AzureBlob\AzureBlob.csproj", "{B07435B3-CD81-4E3B-88A5-6384821E1C01}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.AzureStorage.Test", "test\Microsoft.AspNetCore.DataProtection.AzureStorage.Test\Microsoft.AspNetCore.DataProtection.AzureStorage.Test.csproj", "{8C41240E-48F8-402F-9388-74CFE27F4D76}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Redis", "samples\Redis\Redis.csproj", "{24AAEC96-DF46-4F61-B2FF-3D5E056685D9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NonDISample", "samples\NonDISample\NonDISample.csproj", "{32CF970B-E2F1-4CD9-8DB3-F5715475373A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyManagementSample", "samples\KeyManagementSample\KeyManagementSample.csproj", "{6E066F8D-2910-404F-8949-F58125E28495}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomEncryptorSample", "samples\CustomEncryptorSample\CustomEncryptorSample.csproj", "{F4D59BBD-6145-4EE0-BA6E-AD03605BF151}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.AzureKeyVault", "src\Microsoft.AspNetCore.DataProtection.AzureKeyVault\Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj", "{4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureKeyVault", "samples\AzureKeyVault\AzureKeyVault.csproj", "{295E8539-5450-4764-B3F5-51F968628022}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test", "test\Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test\Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test.csproj", "{C85ED942-8121-453F-8308-9DB730843B63}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test", "test\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.Test.csproj", "{06728BF2-C5EB-44C7-9F30-14FAA5649E14}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore", "src\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore\Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj", "{3E4CA7FE-741B-4C78-A775-220E0E3C1B03}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EntityFrameworkCoreSample", "samples\EntityFrameworkCoreSample\EntityFrameworkCoreSample.csproj", "{22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.StackExchangeRedis", "src\Microsoft.AspNetCore.DataProtection.StackExchangeRedis\Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj", "{57713B23-CCAB-44DB-A08D-55F9D236D05B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection.StackExchangeRedis.Test", "test\Microsoft.AspNetCore.DataProtection.StackExchangeRedis.Test\Microsoft.AspNetCore.DataProtection.StackExchangeRedis.Test.csproj", "{33BB1B86-64BF-45BB-A334-3E1A4802253C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2}.Release|x86.ActiveCfg = Release|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF}.Release|x86.ActiveCfg = Release|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Debug|x86.Build.0 = Debug|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Release|x86.ActiveCfg = Release|Any CPU
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E}.Release|x86.Build.0 = Release|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Debug|x86.Build.0 = Debug|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Release|x86.ActiveCfg = Release|Any CPU
+ {421F0383-34B1-402D-807B-A94542513ABA}.Release|x86.Build.0 = Release|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Debug|x86.Build.0 = Debug|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Release|x86.ActiveCfg = Release|Any CPU
+ {42C97F52-8D56-46BD-A712-4F22BED157A7}.Release|x86.Build.0 = Release|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Debug|x86.Build.0 = Debug|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|x86.ActiveCfg = Release|Any CPU
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0}.Release|x86.Build.0 = Release|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Debug|x86.Build.0 = Debug|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|x86.ActiveCfg = Release|Any CPU
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2}.Release|x86.Build.0 = Release|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Debug|x86.Build.0 = Debug|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|x86.ActiveCfg = Release|Any CPU
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478}.Release|x86.Build.0 = Release|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Debug|x86.Build.0 = Debug|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|x86.ActiveCfg = Release|Any CPU
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|x86.Build.0 = Release|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|x86.Build.0 = Debug|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|Any CPU.Build.0 = Release|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|x86.ActiveCfg = Release|Any CPU
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|x86.Build.0 = Release|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|x86.Build.0 = Debug|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|x86.ActiveCfg = Release|Any CPU
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|x86.Build.0 = Release|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Debug|x86.Build.0 = Debug|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Release|x86.ActiveCfg = Release|Any CPU
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F}.Release|x86.Build.0 = Release|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Debug|x86.Build.0 = Debug|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Release|x86.ActiveCfg = Release|Any CPU
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01}.Release|x86.Build.0 = Release|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Debug|x86.Build.0 = Debug|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Release|x86.ActiveCfg = Release|Any CPU
+ {8C41240E-48F8-402F-9388-74CFE27F4D76}.Release|x86.Build.0 = Release|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Debug|x86.Build.0 = Debug|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Release|x86.ActiveCfg = Release|Any CPU
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9}.Release|x86.Build.0 = Release|Any CPU
+ {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Debug|x86.Build.0 = Debug|Any CPU
+ {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Release|x86.ActiveCfg = Release|Any CPU
+ {32CF970B-E2F1-4CD9-8DB3-F5715475373A}.Release|x86.Build.0 = Release|Any CPU
+ {6E066F8D-2910-404F-8949-F58125E28495}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6E066F8D-2910-404F-8949-F58125E28495}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6E066F8D-2910-404F-8949-F58125E28495}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6E066F8D-2910-404F-8949-F58125E28495}.Debug|x86.Build.0 = Debug|Any CPU
+ {6E066F8D-2910-404F-8949-F58125E28495}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6E066F8D-2910-404F-8949-F58125E28495}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6E066F8D-2910-404F-8949-F58125E28495}.Release|x86.ActiveCfg = Release|Any CPU
+ {6E066F8D-2910-404F-8949-F58125E28495}.Release|x86.Build.0 = Release|Any CPU
+ {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Debug|x86.Build.0 = Debug|Any CPU
+ {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Release|x86.ActiveCfg = Release|Any CPU
+ {F4D59BBD-6145-4EE0-BA6E-AD03605BF151}.Release|x86.Build.0 = Release|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Debug|x86.Build.0 = Debug|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Release|x86.ActiveCfg = Release|Any CPU
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9}.Release|x86.Build.0 = Release|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Debug|x86.Build.0 = Debug|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Release|Any CPU.Build.0 = Release|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Release|x86.ActiveCfg = Release|Any CPU
+ {295E8539-5450-4764-B3F5-51F968628022}.Release|x86.Build.0 = Release|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Debug|x86.Build.0 = Debug|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Release|x86.ActiveCfg = Release|Any CPU
+ {C85ED942-8121-453F-8308-9DB730843B63}.Release|x86.Build.0 = Release|Any CPU
+ {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Debug|x86.Build.0 = Debug|Any CPU
+ {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|Any CPU.Build.0 = Release|Any CPU
+ {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|x86.ActiveCfg = Release|Any CPU
+ {06728BF2-C5EB-44C7-9F30-14FAA5649E14}.Release|x86.Build.0 = Release|Any CPU
+ {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Debug|x86.Build.0 = Debug|Any CPU
+ {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|x86.ActiveCfg = Release|Any CPU
+ {3E4CA7FE-741B-4C78-A775-220E0E3C1B03}.Release|x86.Build.0 = Release|Any CPU
+ {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Debug|x86.Build.0 = Debug|Any CPU
+ {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Release|Any CPU.Build.0 = Release|Any CPU
+ {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Release|x86.ActiveCfg = Release|Any CPU
+ {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76}.Release|x86.Build.0 = Release|Any CPU
+ {57713B23-CCAB-44DB-A08D-55F9D236D05B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {57713B23-CCAB-44DB-A08D-55F9D236D05B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {57713B23-CCAB-44DB-A08D-55F9D236D05B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {57713B23-CCAB-44DB-A08D-55F9D236D05B}.Debug|x86.Build.0 = Debug|Any CPU
+ {57713B23-CCAB-44DB-A08D-55F9D236D05B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {57713B23-CCAB-44DB-A08D-55F9D236D05B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {57713B23-CCAB-44DB-A08D-55F9D236D05B}.Release|x86.ActiveCfg = Release|Any CPU
+ {57713B23-CCAB-44DB-A08D-55F9D236D05B}.Release|x86.Build.0 = Release|Any CPU
+ {33BB1B86-64BF-45BB-A334-3E1A4802253C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {33BB1B86-64BF-45BB-A334-3E1A4802253C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {33BB1B86-64BF-45BB-A334-3E1A4802253C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {33BB1B86-64BF-45BB-A334-3E1A4802253C}.Debug|x86.Build.0 = Debug|Any CPU
+ {33BB1B86-64BF-45BB-A334-3E1A4802253C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {33BB1B86-64BF-45BB-A334-3E1A4802253C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {33BB1B86-64BF-45BB-A334-3E1A4802253C}.Release|x86.ActiveCfg = Release|Any CPU
+ {33BB1B86-64BF-45BB-A334-3E1A4802253C}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {1E570CD4-6F12-44F4-961E-005EE2002BC2} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {7A637185-2BA1-437D-9D4C-7CC4F94CF7BF} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {E2779976-A28C-4365-A4BB-4AD854FAF23E} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {421F0383-34B1-402D-807B-A94542513ABA} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {42C97F52-8D56-46BD-A712-4F22BED157A7} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {37053D5F-5B61-47CE-8B72-298CE007FFB0} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {4B115BDE-B253-46A6-97BF-A8B37B344FF2} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {FF650A69-DEE4-4B36-9E30-264EE7CFB478} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {E3552DEB-4173-43AE-BF69-3C10DFF3BAB6} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {04AA8E60-A053-4D50-89FE-E76C3DF45200} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {BF8681DB-C28B-441F-BD92-0DCFE9537A9F} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {CC799B57-81E2-4F45-8A32-0D5F49753C3F} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {B07435B3-CD81-4E3B-88A5-6384821E1C01} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {8C41240E-48F8-402F-9388-74CFE27F4D76} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {24AAEC96-DF46-4F61-B2FF-3D5E056685D9} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {32CF970B-E2F1-4CD9-8DB3-F5715475373A} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {6E066F8D-2910-404F-8949-F58125E28495} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {F4D59BBD-6145-4EE0-BA6E-AD03605BF151} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {4E76B2A8-9DC3-46E6-B5FC-097A1D1DFBE9} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {295E8539-5450-4764-B3F5-51F968628022} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {C85ED942-8121-453F-8308-9DB730843B63} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {06728BF2-C5EB-44C7-9F30-14FAA5649E14} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ {3E4CA7FE-741B-4C78-A775-220E0E3C1B03} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {22BA4EAB-641E-42B2-BB37-9C3BCFD99F76} = {5A3A5DE3-49AD-431C-971D-B01B62D94AE2}
+ {57713B23-CCAB-44DB-A08D-55F9D236D05B} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
+ {33BB1B86-64BF-45BB-A334-3E1A4802253C} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {DD305D75-BD1B-43AE-BF04-869DA6A0858F}
+ EndGlobalSection
+EndGlobal
diff --git a/src/DataProtection/Directory.Build.props b/src/DataProtection/Directory.Build.props
new file mode 100644
index 0000000000..deb7bb4ee6
--- /dev/null
+++ b/src/DataProtection/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/NuGetPackageVerifier.json b/src/DataProtection/NuGetPackageVerifier.json
new file mode 100644
index 0000000000..22ef3c09c0
--- /dev/null
+++ b/src/DataProtection/NuGetPackageVerifier.json
@@ -0,0 +1,7 @@
+{
+ "Default": {
+ "rules": [
+ "DefaultCompositeRule"
+ ]
+ }
+}
diff --git a/src/DataProtection/Provision-AutoGenKeys.ps1 b/src/DataProtection/Provision-AutoGenKeys.ps1
new file mode 100644
index 0000000000..9be7e1601d
--- /dev/null
+++ b/src/DataProtection/Provision-AutoGenKeys.ps1
@@ -0,0 +1,117 @@
+param (
+ [Parameter(Mandatory = $True)]
+ [string] $appPoolName
+ )
+
+# Provisions the HKLM registry so that the specified user account can persist auto-generated machine keys.
+function Provision-AutoGenKeys {
+ [CmdletBinding()]
+ param (
+ [ValidateSet("2.0", "4.0")]
+ [Parameter(Mandatory = $True)]
+ [string] $frameworkVersion,
+ [ValidateSet("32", "64")]
+ [Parameter(Mandatory = $True)]
+ [string] $architecture,
+ [Parameter(Mandatory = $True)]
+ [string] $sid
+ )
+ process {
+ # We require administrative permissions to continue.
+ if (-Not (new-object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
+ Write-Error "This cmdlet requires Administrator permissions."
+ return
+ }
+ # Open HKLM with an appropriate view into the registry
+ if ($architecture -eq "32") {
+ $regView = [Microsoft.Win32.RegistryView]::Registry32;
+ } else {
+ $regView = [Microsoft.Win32.RegistryView]::Registry64;
+ }
+ $baseRegKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $regView)
+ # Open ASP.NET base key
+ if ($frameworkVersion -eq "2.0") {
+ $expandedVersion = "2.0.50727.0"
+ } else {
+ $expandedVersion = "4.0.30319.0"
+ }
+ $softwareMicrosoftKey = $baseRegKey.OpenSubKey("SOFTWARE\Microsoft\", $True);
+
+ $aspNetKey = $softwareMicrosoftKey.OpenSubKey("ASP.NET", $True);
+ if ($aspNetKey -eq $null)
+ {
+ $aspNetKey = $softwareMicrosoftKey.CreateSubKey("ASP.NET")
+ }
+
+ $aspNetBaseKey = $aspNetKey.OpenSubKey("$expandedVersion", $True);
+ if ($aspNetBaseKey -eq $null)
+ {
+ $aspNetBaseKey = $aspNetKey.CreateSubKey("$expandedVersion")
+ }
+
+ # Create AutoGenKeys subkey if it doesn't already exist
+ $autoGenBaseKey = $aspNetBaseKey.OpenSubKey("AutoGenKeys", $True)
+ if ($autoGenBaseKey -eq $null) {
+ $autoGenBaseKey = $aspNetBaseKey.CreateSubKey("AutoGenKeys")
+ }
+ # SYSTEM, ADMINISTRATORS, and the target SID get full access
+ $regSec = New-Object System.Security.AccessControl.RegistrySecurity
+ $regSec.SetSecurityDescriptorSddlForm("D:P(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)(A;OICI;GA;;;$sid)")
+ $userAutoGenKey = $autoGenBaseKey.OpenSubKey($sid, $True)
+ if ($userAutoGenKey -eq $null) {
+ # Subkey didn't exist; create and ACL appropriately
+ $userAutoGenKey = $autoGenBaseKey.CreateSubKey($sid, [Microsoft.Win32.RegistryKeyPermissionCheck]::Default, $regSec)
+ } else {
+ # Subkey existed; make sure ACLs are correct
+ $userAutoGenKey.SetAccessControl($regSec)
+ }
+ }
+}
+
+$ErrorActionPreference = "Stop"
+if (Get-Command Get-IISAppPool -errorAction SilentlyContinue)
+{
+ $processModel = (Get-IISAppPool $appPoolName).processModel
+}
+else
+{
+ Import-Module WebAdministration
+ $processModel = Get-ItemProperty -Path "IIS:\AppPools\$appPoolName" -Name "processModel"
+}
+
+$identityType = $processModel.identityType
+Write-Output "Pool process model: '$identityType'"
+
+Switch ($identityType)
+{
+ "LocalService" {
+ $userName = "LocalService";
+ }
+ "LocalSystem" {
+ $userName = "System";
+ }
+ "NetworkService" {
+ $userName = "NetworkService";
+ }
+ "ApplicationPoolIdentity" {
+ $userName = "IIS APPPOOL\$appPoolName";
+ }
+ "SpecificUser" {
+ $userName = $processModel.userName;
+ }
+}
+Write-Output "Pool user name: '$userName'"
+
+Try
+{
+ $poolSid = (New-Object System.Security.Principal.NTAccount($userName)).Translate([System.Security.Principal.SecurityIdentifier]).Value
+}
+Catch [System.Security.Principal.IdentityNotMappedException]
+{
+ Write-Error "Application pool '$appPoolName' account cannot be resolved."
+}
+
+Write-Output "Pool SID: '$poolSid'"
+
+Provision-AutoGenKeys "4.0" "32" $poolSid
+Provision-AutoGenKeys "4.0" "64" $poolSid
diff --git a/src/DataProtection/README.md b/src/DataProtection/README.md
new file mode 100644
index 0000000000..cd58074d9e
--- /dev/null
+++ b/src/DataProtection/README.md
@@ -0,0 +1,8 @@
+DataProtection
+==============
+
+Data Protection APIs for protecting and unprotecting data. You can find documentation for Data Protection in the [ASP.NET Core Documentation](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/).
+
+## Community Maintained Data Protection Providers & Projects
+
+ - [ASP.NET Core DataProtection for Service Fabric](https://github.com/MedAnd/AspNetCore.DataProtection.ServiceFabric)
diff --git a/src/DataProtection/build.cmd b/src/DataProtection/build.cmd
new file mode 100644
index 0000000000..f4169ea5e4
--- /dev/null
+++ b/src/DataProtection/build.cmd
@@ -0,0 +1,3 @@
+@ECHO OFF
+SET RepoRoot="%~dp0..\.."
+%RepoRoot%\build.cmd -LockFile %RepoRoot%\korebuild-lock.txt -Path %~dp0 %*
diff --git a/src/DataProtection/build.sh b/src/DataProtection/build.sh
new file mode 100755
index 0000000000..d5bb0cf631
--- /dev/null
+++ b/src/DataProtection/build.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+repo_root="$DIR/../.."
+"$repo_root/build.sh" --path "$DIR" --lockfile "$repo_root/korebuild-lock.txt" "$@"
diff --git a/src/DataProtection/build/repo.props b/src/DataProtection/build/repo.props
new file mode 100644
index 0000000000..3fa98a9b36
--- /dev/null
+++ b/src/DataProtection/build/repo.props
@@ -0,0 +1,10 @@
+
+
+
+
+ true
+
+
+
+
+
diff --git a/src/DataProtection/dependencies.props b/src/DataProtection/dependencies.props
new file mode 100644
index 0000000000..6633853e15
--- /dev/null
+++ b/src/DataProtection/dependencies.props
@@ -0,0 +1,33 @@
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+
+
+ 2.2.0-preview2-20181004.6
+ 3.0.0-alpha1-10584
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+ $(LastGoodAspBuildVersion)
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/AzureBlob/AzureBlob.csproj b/src/DataProtection/samples/AzureBlob/AzureBlob.csproj
new file mode 100644
index 0000000000..72bce10880
--- /dev/null
+++ b/src/DataProtection/samples/AzureBlob/AzureBlob.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netcoreapp3.0
+ exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/AzureBlob/Program.cs b/src/DataProtection/samples/AzureBlob/Program.cs
new file mode 100644
index 0000000000..cce8604648
--- /dev/null
+++ b/src/DataProtection/samples/AzureBlob/Program.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.WindowsAzure.Storage;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
+
+namespace AzureBlob
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
+ var client = storageAccount.CreateCloudBlobClient();
+ var container = client.GetContainerReference("key-container");
+
+ // The container must exist before calling the DataProtection APIs.
+ // The specific file within the container does not have to exist,
+ // as it will be created on-demand.
+
+ container.CreateIfNotExistsAsync().GetAwaiter().GetResult();
+
+ // Configure
+ using (var services = new ServiceCollection()
+ .AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
+ .AddDataProtection()
+ .PersistKeysToAzureBlobStorage(container, "keys.xml")
+ .Services
+ .BuildServiceProvider())
+ {
+ // Run a sample payload
+
+ var protector = services.GetDataProtector("sample-purpose");
+ var protectedData = protector.Protect("Hello world!");
+ Console.WriteLine(protectedData);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj b/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj
new file mode 100644
index 0000000000..faa7d75be0
--- /dev/null
+++ b/src/DataProtection/samples/AzureKeyVault/AzureKeyVault.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp3.0
+ exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/AzureKeyVault/Program.cs b/src/DataProtection/samples/AzureKeyVault/Program.cs
new file mode 100644
index 0000000000..7d6299f3e5
--- /dev/null
+++ b/src/DataProtection/samples/AzureKeyVault/Program.cs
@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace ConsoleApplication
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var builder = new ConfigurationBuilder();
+ builder.SetBasePath(Directory.GetCurrentDirectory());
+ builder.AddJsonFile("settings.json");
+ var config = builder.Build();
+
+ var store = new X509Store(StoreLocation.CurrentUser);
+ store.Open(OpenFlags.ReadOnly);
+ var cert = store.Certificates.Find(X509FindType.FindByThumbprint, config["CertificateThumbprint"], false);
+
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddLogging();
+ serviceCollection.AddDataProtection()
+ .PersistKeysToFileSystem(new DirectoryInfo("."))
+ .ProtectKeysWithAzureKeyVault(config["KeyId"], config["ClientId"], cert.OfType().Single());
+
+ var serviceProvider = serviceCollection.BuildServiceProvider();
+
+ var loggerFactory = serviceProvider.GetService();
+ loggerFactory.AddConsole();
+
+ var protector = serviceProvider.GetDataProtector("Test");
+
+ Console.WriteLine(protector.Protect("Hello world"));
+ }
+ }
+}
diff --git a/src/DataProtection/samples/AzureKeyVault/settings.json b/src/DataProtection/samples/AzureKeyVault/settings.json
new file mode 100644
index 0000000000..ef7d4d81b8
--- /dev/null
+++ b/src/DataProtection/samples/AzureKeyVault/settings.json
@@ -0,0 +1,5 @@
+{
+ "CertificateThumbprint": "",
+ "KeyId": "",
+ "ClientId": ""
+}
\ No newline at end of file
diff --git a/src/DataProtection/samples/CustomEncryptorSample/CustomBuilderExtensions.cs b/src/DataProtection/samples/CustomEncryptorSample/CustomBuilderExtensions.cs
new file mode 100644
index 0000000000..faa99a4a5d
--- /dev/null
+++ b/src/DataProtection/samples/CustomEncryptorSample/CustomBuilderExtensions.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.AspNetCore.DataProtection.XmlEncryption;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace CustomEncryptorSample
+{
+ public static class CustomBuilderExtensions
+ {
+ public static IDataProtectionBuilder UseXmlEncryptor(
+ this IDataProtectionBuilder builder,
+ Func factory)
+ {
+ builder.Services.AddSingleton>(serviceProvider =>
+ {
+ var instance = factory(serviceProvider);
+ return new ConfigureOptions(options =>
+ {
+ options.XmlEncryptor = instance;
+ });
+ });
+
+ return builder;
+ }
+ }
+}
diff --git a/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj b/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj
new file mode 100644
index 0000000000..f55e580fed
--- /dev/null
+++ b/src/DataProtection/samples/CustomEncryptorSample/CustomEncryptorSample.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net461;netcoreapp3.0
+ exe
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/CustomEncryptorSample/CustomXmlDecryptor.cs b/src/DataProtection/samples/CustomEncryptorSample/CustomXmlDecryptor.cs
new file mode 100644
index 0000000000..a8925f12f6
--- /dev/null
+++ b/src/DataProtection/samples/CustomEncryptorSample/CustomXmlDecryptor.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.XmlEncryption;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace CustomEncryptorSample
+{
+ public class CustomXmlDecryptor : IXmlDecryptor
+ {
+ private readonly ILogger _logger;
+
+ public CustomXmlDecryptor(IServiceProvider services)
+ {
+ _logger = services.GetRequiredService().CreateLogger();
+ }
+
+ public XElement Decrypt(XElement encryptedElement)
+ {
+ if (encryptedElement == null)
+ {
+ throw new ArgumentNullException(nameof(encryptedElement));
+ }
+
+ return new XElement(encryptedElement.Elements().Single());
+ }
+ }
+}
diff --git a/src/DataProtection/samples/CustomEncryptorSample/CustomXmlEncryptor.cs b/src/DataProtection/samples/CustomEncryptorSample/CustomXmlEncryptor.cs
new file mode 100644
index 0000000000..f6653f776a
--- /dev/null
+++ b/src/DataProtection/samples/CustomEncryptorSample/CustomXmlEncryptor.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.XmlEncryption;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace CustomEncryptorSample
+{
+ public class CustomXmlEncryptor : IXmlEncryptor
+ {
+ private readonly ILogger _logger;
+
+ public CustomXmlEncryptor(IServiceProvider services)
+ {
+ _logger = services.GetRequiredService().CreateLogger();
+ }
+
+ public EncryptedXmlInfo Encrypt(XElement plaintextElement)
+ {
+ if (plaintextElement == null)
+ {
+ throw new ArgumentNullException(nameof(plaintextElement));
+ }
+
+ _logger.LogInformation("Not encrypting key");
+
+ var newElement = new XElement("unencryptedKey",
+ new XComment(" This key is not encrypted. "),
+ new XElement(plaintextElement));
+ var encryptedTextElement = new EncryptedXmlInfo(newElement, typeof(CustomXmlDecryptor));
+
+ return encryptedTextElement;
+ }
+ }
+}
diff --git a/src/DataProtection/samples/CustomEncryptorSample/Program.cs b/src/DataProtection/samples/CustomEncryptorSample/Program.cs
new file mode 100644
index 0000000000..9079aeee3f
--- /dev/null
+++ b/src/DataProtection/samples/CustomEncryptorSample/Program.cs
@@ -0,0 +1,36 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace CustomEncryptorSample
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys");
+ using (var services = new ServiceCollection()
+ .AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
+ .AddDataProtection()
+ .PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
+ .UseXmlEncryptor(s => new CustomXmlEncryptor(s))
+ .Services.BuildServiceProvider())
+ {
+ var protector = services.GetDataProtector("SamplePurpose");
+
+ // protect the payload
+ var protectedPayload = protector.Protect("Hello World!");
+ Console.WriteLine($"Protect returned: {protectedPayload}");
+
+ // unprotect the payload
+ var unprotectedPayload = protector.Unprotect(protectedPayload);
+ Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/samples/EntityFrameworkCoreSample/EntityFrameworkCoreSample.csproj b/src/DataProtection/samples/EntityFrameworkCoreSample/EntityFrameworkCoreSample.csproj
new file mode 100644
index 0000000000..4b8bf7da45
--- /dev/null
+++ b/src/DataProtection/samples/EntityFrameworkCoreSample/EntityFrameworkCoreSample.csproj
@@ -0,0 +1,18 @@
+
+
+
+ exe
+ net461;netcoreapp3.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/EntityFrameworkCoreSample/Program.cs b/src/DataProtection/samples/EntityFrameworkCoreSample/Program.cs
new file mode 100644
index 0000000000..d4e978a7b8
--- /dev/null
+++ b/src/DataProtection/samples/EntityFrameworkCoreSample/Program.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace EntityFrameworkCoreSample
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ // Configure
+ var services = new ServiceCollection()
+ .AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
+ .AddDbContext(o =>
+ {
+ o.UseInMemoryDatabase("DataProtection_EntityFrameworkCore");
+ o.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
+ o.EnableSensitiveDataLogging();
+ })
+ .AddDataProtection()
+ .PersistKeysToDbContext()
+ .SetDefaultKeyLifetime(TimeSpan.FromDays(7))
+ .Services
+ .BuildServiceProvider(validateScopes: true);
+
+ using(services)
+ {
+ // Run a sample payload
+ var protector = services.GetDataProtector("sample-purpose");
+ var protectedData = protector.Protect("Hello world!");
+ Console.WriteLine(protectedData);
+ }
+ }
+ }
+
+ class DataProtectionKeyContext : DbContext, IDataProtectionKeyContext
+ {
+ public DataProtectionKeyContext(DbContextOptions options) : base(options) { }
+
+ public DbSet DataProtectionKeys { get; set; }
+ }
+}
diff --git a/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj b/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj
new file mode 100644
index 0000000000..d97d58f988
--- /dev/null
+++ b/src/DataProtection/samples/KeyManagementSample/KeyManagementSample.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net461;netcoreapp3.0
+ exe
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/KeyManagementSample/Program.cs b/src/DataProtection/samples/KeyManagementSample/Program.cs
new file mode 100644
index 0000000000..be128aa11c
--- /dev/null
+++ b/src/DataProtection/samples/KeyManagementSample/Program.cs
@@ -0,0 +1,66 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace KeyManagementSample
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys");
+ var serviceCollection = new ServiceCollection();
+ var builder = serviceCollection
+ .AddDataProtection()
+ // point at a specific folder and use DPAPI to encrypt keys
+ .PersistKeysToFileSystem(new DirectoryInfo(keysFolder));
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ builder.ProtectKeysWithDpapi();
+ }
+
+ using (var services = serviceCollection.BuildServiceProvider())
+ {
+ // perform a protect operation to force the system to put at least
+ // one key in the key ring
+ services.GetDataProtector("Sample.KeyManager.v1").Protect("payload");
+ Console.WriteLine("Performed a protect operation.");
+
+ // get a reference to the key manager
+ var keyManager = services.GetService();
+
+ // list all keys in the key ring
+ var allKeys = keyManager.GetAllKeys();
+ Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
+ foreach (var key in allKeys)
+ {
+ Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
+ }
+
+ // revoke all keys in the key ring
+ keyManager.RevokeAllKeys(DateTimeOffset.Now, reason: "Revocation reason here.");
+ Console.WriteLine("Revoked all existing keys.");
+
+ // add a new key to the key ring with immediate activation and a 1-month expiration
+ keyManager.CreateNewKey(
+ activationDate: DateTimeOffset.Now,
+ expirationDate: DateTimeOffset.Now.AddMonths(1));
+ Console.WriteLine("Added a new key.");
+
+ // list all keys in the key ring
+ allKeys = keyManager.GetAllKeys();
+ Console.WriteLine($"The key ring contains {allKeys.Count} key(s).");
+ foreach (var key in allKeys)
+ {
+ Console.WriteLine($"Key {key.KeyId:B}: Created = {key.CreationDate:u}, IsRevoked = {key.IsRevoked}");
+ }
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/samples/NonDISample/NonDISample.csproj b/src/DataProtection/samples/NonDISample/NonDISample.csproj
new file mode 100644
index 0000000000..6371351b01
--- /dev/null
+++ b/src/DataProtection/samples/NonDISample/NonDISample.csproj
@@ -0,0 +1,12 @@
+
+
+
+ net461;netcoreapp3.0
+ exe
+
+
+
+
+
+
+
diff --git a/src/DataProtection/samples/NonDISample/Program.cs b/src/DataProtection/samples/NonDISample/Program.cs
new file mode 100644
index 0000000000..f9ccd92603
--- /dev/null
+++ b/src/DataProtection/samples/NonDISample/Program.cs
@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel;
+
+namespace NonDISample
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys");
+
+ // instantiate the data protection system at this folder
+ var dataProtectionProvider = DataProtectionProvider.Create(
+ new DirectoryInfo(keysFolder),
+ configuration =>
+ {
+ configuration.SetApplicationName("my app name");
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ configuration.ProtectKeysWithDpapi();
+ }
+ });
+
+ var protector = dataProtectionProvider.CreateProtector("Program.No-DI");
+
+ // protect the payload
+ var protectedPayload = protector.Protect("Hello World!");
+ Console.WriteLine($"Protect returned: {protectedPayload}");
+
+ // unprotect the payload
+ var unprotectedPayload = protector.Unprotect(protectedPayload);
+ Console.WriteLine($"Unprotect returned: {unprotectedPayload}");
+ }
+ }
+}
diff --git a/src/DataProtection/samples/Redis/Program.cs b/src/DataProtection/samples/Redis/Program.cs
new file mode 100644
index 0000000000..57d910ae8f
--- /dev/null
+++ b/src/DataProtection/samples/Redis/Program.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.DataProtection.StackExchangeRedis;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using StackExchange.Redis;
+
+namespace RedisSample
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ // Connect
+ var redis = ConnectionMultiplexer.Connect("localhost:6379");
+
+ // Configure
+ using (var services = new ServiceCollection()
+ .AddLogging(o => o.AddConsole().SetMinimumLevel(LogLevel.Debug))
+ .AddDataProtection()
+ .PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys")
+ .Services
+ .BuildServiceProvider())
+ {
+ // Run a sample payload
+ var protector = services.GetDataProtector("sample-purpose");
+ var protectedData = protector.Protect("Hello world!");
+ Console.WriteLine(protectedData);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/samples/Redis/Redis.csproj b/src/DataProtection/samples/Redis/Redis.csproj
new file mode 100644
index 0000000000..7b1433e9fe
--- /dev/null
+++ b/src/DataProtection/samples/Redis/Redis.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net461;netcoreapp3.0
+ exe
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/shared/EncodingUtil.cs b/src/DataProtection/shared/EncodingUtil.cs
new file mode 100644
index 0000000000..67b99eac3b
--- /dev/null
+++ b/src/DataProtection/shared/EncodingUtil.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ internal static class EncodingUtil
+ {
+ // UTF8 encoding that fails on invalid chars
+ public static readonly UTF8Encoding SecureUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
+ }
+}
diff --git a/src/DataProtection/shared/ExceptionExtensions.cs b/src/DataProtection/shared/ExceptionExtensions.cs
new file mode 100644
index 0000000000..f441935d13
--- /dev/null
+++ b/src/DataProtection/shared/ExceptionExtensions.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security.Cryptography;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ internal static class ExceptionExtensions
+ {
+ ///
+ /// Determines whether an exception must be homogenized by being wrapped inside a
+ /// CryptographicException before being rethrown.
+ ///
+ public static bool RequiresHomogenization(this Exception ex)
+ {
+ return !(ex is CryptographicException);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Directory.Build.props b/src/DataProtection/src/Directory.Build.props
new file mode 100644
index 0000000000..4b89a431e7
--- /dev/null
+++ b/src/DataProtection/src/Directory.Build.props
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.cs
new file mode 100644
index 0000000000..0c074b8280
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO.cs
@@ -0,0 +1,38 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/cc562981(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
+ {
+ public uint cbSize;
+ public uint dwInfoVersion;
+ public byte* pbNonce;
+ public uint cbNonce;
+ public byte* pbAuthData;
+ public uint cbAuthData;
+ public byte* pbTag;
+ public uint cbTag;
+ public byte* pbMacContext;
+ public uint cbMacContext;
+ public uint cbAAD;
+ public ulong cbData;
+ public uint dwFlags;
+
+ // corresponds to the BCRYPT_INIT_AUTH_MODE_INFO macro in bcrypt.h
+ public static void Init(out BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO info)
+ {
+ const uint BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION = 1;
+ info = new BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
+ {
+ cbSize = (uint)sizeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO),
+ dwInfoVersion = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION
+ };
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_KEY_LENGTHS_STRUCT.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_KEY_LENGTHS_STRUCT.cs
new file mode 100644
index 0000000000..0d4139018f
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCRYPT_KEY_LENGTHS_STRUCT.cs
@@ -0,0 +1,46 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.AspNetCore.Cryptography.Internal;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375525(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct BCRYPT_KEY_LENGTHS_STRUCT
+ {
+ // MSDN says these fields represent the key length in bytes.
+ // It's wrong: these key lengths are all actually in bits.
+ internal uint dwMinLength;
+ internal uint dwMaxLength;
+ internal uint dwIncrement;
+
+ public void EnsureValidKeyLength(uint keyLengthInBits)
+ {
+ if (!IsValidKeyLength(keyLengthInBits))
+ {
+ string message = Resources.FormatBCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength(keyLengthInBits, dwMinLength, dwMaxLength, dwIncrement);
+ throw new ArgumentOutOfRangeException(nameof(keyLengthInBits), message);
+ }
+ CryptoUtil.Assert(keyLengthInBits % 8 == 0, "keyLengthInBits % 8 == 0");
+ }
+
+ private bool IsValidKeyLength(uint keyLengthInBits)
+ {
+ // If the step size is zero, then the key length must be exactly the min or the max. Otherwise,
+ // key length must be between min and max (inclusive) and a whole number of increments away from min.
+ if (dwIncrement == 0)
+ {
+ return (keyLengthInBits == dwMinLength || keyLengthInBits == dwMaxLength);
+ }
+ else
+ {
+ return (dwMinLength <= keyLengthInBits)
+ && (keyLengthInBits <= dwMaxLength)
+ && ((keyLengthInBits - dwMinLength) % dwIncrement == 0);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBuffer.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBuffer.cs
new file mode 100644
index 0000000000..c091859729
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBuffer.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375368(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct BCryptBuffer
+ {
+ public uint cbBuffer; // Length of buffer, in bytes
+ public BCryptKeyDerivationBufferType BufferType; // Buffer type
+ public IntPtr pvBuffer; // Pointer to buffer
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBufferDesc.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBufferDesc.cs
new file mode 100644
index 0000000000..8fd699643e
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptBufferDesc.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375370(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct BCryptBufferDesc
+ {
+ private const int BCRYPTBUFFER_VERSION = 0;
+
+ public uint ulVersion; // Version number
+ public uint cBuffers; // Number of buffers
+ public BCryptBuffer* pBuffers; // Pointer to array of buffers
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Initialize(ref BCryptBufferDesc bufferDesc)
+ {
+ bufferDesc.ulVersion = BCRYPTBUFFER_VERSION;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptEncryptFlags.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptEncryptFlags.cs
new file mode 100644
index 0000000000..81ae0105cc
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptEncryptFlags.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ [Flags]
+ internal enum BCryptEncryptFlags
+ {
+ BCRYPT_BLOCK_PADDING = 0x00000001,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptGenRandomFlags.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptGenRandomFlags.cs
new file mode 100644
index 0000000000..ed20fec309
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptGenRandomFlags.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // from bcrypt.h
+ [Flags]
+ internal enum BCryptGenRandomFlags
+ {
+ BCRYPT_RNG_USE_ENTROPY_IN_BUFFER = 0x00000001,
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptKeyDerivationBufferType.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptKeyDerivationBufferType.cs
new file mode 100644
index 0000000000..a68569e799
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptKeyDerivationBufferType.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ // from bcrypt.h
+ internal enum BCryptKeyDerivationBufferType
+ {
+ KDF_HASH_ALGORITHM = 0x0,
+ KDF_SECRET_PREPEND = 0x1,
+ KDF_SECRET_APPEND = 0x2,
+ KDF_HMAC_KEY = 0x3,
+ KDF_TLS_PRF_LABEL = 0x4,
+ KDF_TLS_PRF_SEED = 0x5,
+ KDF_SECRET_HANDLE = 0x6,
+ KDF_TLS_PRF_PROTOCOL = 0x7,
+ KDF_ALGORITHMID = 0x8,
+ KDF_PARTYUINFO = 0x9,
+ KDF_PARTYVINFO = 0xA,
+ KDF_SUPPPUBINFO = 0xB,
+ KDF_SUPPPRIVINFO = 0xC,
+ KDF_LABEL = 0xD,
+ KDF_CONTEXT = 0xE,
+ KDF_SALT = 0xF,
+ KDF_ITERATION_COUNT = 0x10,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptUtil.cs
new file mode 100644
index 0000000000..86c86c64a8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/BCryptUtil.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ ///
+ /// Wraps utility BCRYPT APIs that don't work directly with handles.
+ ///
+ internal unsafe static class BCryptUtil
+ {
+ ///
+ /// Fills a buffer with cryptographically secure random data.
+ ///
+ public static void GenRandom(byte* pbBuffer, uint cbBuffer)
+ {
+ if (cbBuffer != 0)
+ {
+ int ntstatus = UnsafeNativeMethods.BCryptGenRandom(
+ hAlgorithm: IntPtr.Zero,
+ pbBuffer: pbBuffer,
+ cbBuffer: cbBuffer,
+ dwFlags: BCryptGenRandomFlags.BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/CachedAlgorithmHandles.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/CachedAlgorithmHandles.cs
new file mode 100644
index 0000000000..48e63685fd
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/CachedAlgorithmHandles.cs
@@ -0,0 +1,93 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ ///
+ /// Provides cached CNG algorithm provider instances, as calling BCryptOpenAlgorithmProvider is expensive.
+ /// Callers should use caution never to dispose of the algorithm provider instances returned by this type.
+ ///
+ internal static class CachedAlgorithmHandles
+ {
+ private static CachedAlgorithmInfo _aesCbc = new CachedAlgorithmInfo(() => GetAesAlgorithm(chainingMode: Constants.BCRYPT_CHAIN_MODE_CBC));
+ private static CachedAlgorithmInfo _aesGcm = new CachedAlgorithmInfo(() => GetAesAlgorithm(chainingMode: Constants.BCRYPT_CHAIN_MODE_GCM));
+ private static CachedAlgorithmInfo _hmacSha1 = new CachedAlgorithmInfo(() => GetHmacAlgorithm(algorithm: Constants.BCRYPT_SHA1_ALGORITHM));
+ private static CachedAlgorithmInfo _hmacSha256 = new CachedAlgorithmInfo(() => GetHmacAlgorithm(algorithm: Constants.BCRYPT_SHA256_ALGORITHM));
+ private static CachedAlgorithmInfo _hmacSha512 = new CachedAlgorithmInfo(() => GetHmacAlgorithm(algorithm: Constants.BCRYPT_SHA512_ALGORITHM));
+ private static CachedAlgorithmInfo _pbkdf2 = new CachedAlgorithmInfo(GetPbkdf2Algorithm);
+ private static CachedAlgorithmInfo _sha1 = new CachedAlgorithmInfo(() => GetHashAlgorithm(algorithm: Constants.BCRYPT_SHA1_ALGORITHM));
+ private static CachedAlgorithmInfo _sha256 = new CachedAlgorithmInfo(() => GetHashAlgorithm(algorithm: Constants.BCRYPT_SHA256_ALGORITHM));
+ private static CachedAlgorithmInfo _sha512 = new CachedAlgorithmInfo(() => GetHashAlgorithm(algorithm: Constants.BCRYPT_SHA512_ALGORITHM));
+ private static CachedAlgorithmInfo _sp800_108_ctr_hmac = new CachedAlgorithmInfo(GetSP800_108_CTR_HMACAlgorithm);
+
+ public static BCryptAlgorithmHandle AES_CBC => CachedAlgorithmInfo.GetAlgorithmHandle(ref _aesCbc);
+
+ public static BCryptAlgorithmHandle AES_GCM => CachedAlgorithmInfo.GetAlgorithmHandle(ref _aesGcm);
+
+ public static BCryptAlgorithmHandle HMAC_SHA1 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha1);
+
+ public static BCryptAlgorithmHandle HMAC_SHA256 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha256);
+
+ public static BCryptAlgorithmHandle HMAC_SHA512 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _hmacSha512);
+
+ // Only available on Win8+.
+ public static BCryptAlgorithmHandle PBKDF2 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _pbkdf2);
+
+ public static BCryptAlgorithmHandle SHA1 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha1);
+
+ public static BCryptAlgorithmHandle SHA256 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha256);
+
+ public static BCryptAlgorithmHandle SHA512 => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sha512);
+
+ // Only available on Win8+.
+ public static BCryptAlgorithmHandle SP800_108_CTR_HMAC => CachedAlgorithmInfo.GetAlgorithmHandle(ref _sp800_108_ctr_hmac);
+
+ private static BCryptAlgorithmHandle GetAesAlgorithm(string chainingMode)
+ {
+ var algHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(Constants.BCRYPT_AES_ALGORITHM);
+ algHandle.SetChainingMode(chainingMode);
+ return algHandle;
+ }
+
+ private static BCryptAlgorithmHandle GetHashAlgorithm(string algorithm)
+ {
+ return BCryptAlgorithmHandle.OpenAlgorithmHandle(algorithm, hmac: false);
+ }
+
+ private static BCryptAlgorithmHandle GetHmacAlgorithm(string algorithm)
+ {
+ return BCryptAlgorithmHandle.OpenAlgorithmHandle(algorithm, hmac: true);
+ }
+
+ private static BCryptAlgorithmHandle GetPbkdf2Algorithm()
+ {
+ return BCryptAlgorithmHandle.OpenAlgorithmHandle(Constants.BCRYPT_PBKDF2_ALGORITHM, implementation: Constants.MS_PRIMITIVE_PROVIDER);
+ }
+
+ private static BCryptAlgorithmHandle GetSP800_108_CTR_HMACAlgorithm()
+ {
+ return BCryptAlgorithmHandle.OpenAlgorithmHandle(Constants.BCRYPT_SP800108_CTR_HMAC_ALGORITHM, implementation: Constants.MS_PRIMITIVE_PROVIDER);
+ }
+
+ // Warning: mutable struct!
+ private struct CachedAlgorithmInfo
+ {
+ private WeakReference _algorithmHandle;
+ private readonly Func _factory;
+
+ public CachedAlgorithmInfo(Func factory)
+ {
+ _algorithmHandle = null;
+ _factory = factory;
+ }
+
+ public static BCryptAlgorithmHandle GetAlgorithmHandle(ref CachedAlgorithmInfo cachedAlgorithmInfo)
+ {
+ return WeakReferenceHelpers.GetSharedInstance(ref cachedAlgorithmInfo._algorithmHandle, cachedAlgorithmInfo._factory);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/NCryptEncryptFlags.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/NCryptEncryptFlags.cs
new file mode 100644
index 0000000000..a0c1bc0fc4
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/NCryptEncryptFlags.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ [Flags]
+ internal enum NCryptEncryptFlags
+ {
+ NCRYPT_NO_PADDING_FLAG = 0x00000001,
+ NCRYPT_PAD_PKCS1_FLAG = 0x00000002,
+ NCRYPT_PAD_OAEP_FLAG = 0x00000004,
+ NCRYPT_PAD_PSS_FLAG = 0x00000008,
+ NCRYPT_SILENT_FLAG = 0x00000040,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/OSVersionUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/OSVersionUtil.cs
new file mode 100644
index 0000000000..0d09a0b6f8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Cng/OSVersionUtil.cs
@@ -0,0 +1,66 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.Cng
+{
+ internal static class OSVersionUtil
+ {
+ private static readonly OSVersion _osVersion = GetOSVersion();
+
+ private static OSVersion GetOSVersion()
+ {
+ const string BCRYPT_LIB = "bcrypt.dll";
+ SafeLibraryHandle bcryptLibHandle = null;
+ try
+ {
+ bcryptLibHandle = SafeLibraryHandle.Open(BCRYPT_LIB);
+ }
+ catch
+ {
+ // we'll handle the exceptional case later
+ }
+
+ if (bcryptLibHandle != null)
+ {
+ using (bcryptLibHandle)
+ {
+ if (bcryptLibHandle.DoesProcExist("BCryptKeyDerivation"))
+ {
+ // We're running on Win8+.
+ return OSVersion.Win8OrLater;
+ }
+ else
+ {
+ // We're running on Win7+.
+ return OSVersion.Win7OrLater;
+ }
+ }
+ }
+ else
+ {
+ // Not running on Win7+.
+ return OSVersion.NotWindows;
+ }
+ }
+
+ public static bool IsWindows()
+ {
+ return (_osVersion >= OSVersion.Win7OrLater);
+ }
+
+ public static bool IsWindows8OrLater()
+ {
+ return (_osVersion >= OSVersion.Win8OrLater);
+ }
+
+ private enum OSVersion
+ {
+ NotWindows = 0,
+ Win7OrLater = 1,
+ Win8OrLater = 2
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Constants.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Constants.cs
new file mode 100644
index 0000000000..44b0568aa8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Constants.cs
@@ -0,0 +1,88 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ // The majority of these are from bcrypt.h
+ internal static class Constants
+ {
+ internal const int MAX_STACKALLOC_BYTES = 256; // greatest number of bytes that we'll ever allow to stackalloc in a single frame
+
+ // BCrypt(Import/Export)Key BLOB types
+ internal const string BCRYPT_OPAQUE_KEY_BLOB = "OpaqueKeyBlob";
+ internal const string BCRYPT_KEY_DATA_BLOB = "KeyDataBlob";
+ internal const string BCRYPT_AES_WRAP_KEY_BLOB = "Rfc3565KeyWrapBlob";
+
+ // Microsoft built-in providers
+ internal const string MS_PRIMITIVE_PROVIDER = "Microsoft Primitive Provider";
+ internal const string MS_PLATFORM_CRYPTO_PROVIDER = "Microsoft Platform Crypto Provider";
+
+ // Common algorithm identifiers
+ internal const string BCRYPT_RSA_ALGORITHM = "RSA";
+ internal const string BCRYPT_RSA_SIGN_ALGORITHM = "RSA_SIGN";
+ internal const string BCRYPT_DH_ALGORITHM = "DH";
+ internal const string BCRYPT_DSA_ALGORITHM = "DSA";
+ internal const string BCRYPT_RC2_ALGORITHM = "RC2";
+ internal const string BCRYPT_RC4_ALGORITHM = "RC4";
+ internal const string BCRYPT_AES_ALGORITHM = "AES";
+ internal const string BCRYPT_DES_ALGORITHM = "DES";
+ internal const string BCRYPT_DESX_ALGORITHM = "DESX";
+ internal const string BCRYPT_3DES_ALGORITHM = "3DES";
+ internal const string BCRYPT_3DES_112_ALGORITHM = "3DES_112";
+ internal const string BCRYPT_MD2_ALGORITHM = "MD2";
+ internal const string BCRYPT_MD4_ALGORITHM = "MD4";
+ internal const string BCRYPT_MD5_ALGORITHM = "MD5";
+ internal const string BCRYPT_SHA1_ALGORITHM = "SHA1";
+ internal const string BCRYPT_SHA256_ALGORITHM = "SHA256";
+ internal const string BCRYPT_SHA384_ALGORITHM = "SHA384";
+ internal const string BCRYPT_SHA512_ALGORITHM = "SHA512";
+ internal const string BCRYPT_AES_GMAC_ALGORITHM = "AES-GMAC";
+ internal const string BCRYPT_AES_CMAC_ALGORITHM = "AES-CMAC";
+ internal const string BCRYPT_ECDSA_P256_ALGORITHM = "ECDSA_P256";
+ internal const string BCRYPT_ECDSA_P384_ALGORITHM = "ECDSA_P384";
+ internal const string BCRYPT_ECDSA_P521_ALGORITHM = "ECDSA_P521";
+ internal const string BCRYPT_ECDH_P256_ALGORITHM = "ECDH_P256";
+ internal const string BCRYPT_ECDH_P384_ALGORITHM = "ECDH_P384";
+ internal const string BCRYPT_ECDH_P521_ALGORITHM = "ECDH_P521";
+ internal const string BCRYPT_RNG_ALGORITHM = "RNG";
+ internal const string BCRYPT_RNG_FIPS186_DSA_ALGORITHM = "FIPS186DSARNG";
+ internal const string BCRYPT_RNG_DUAL_EC_ALGORITHM = "DUALECRNG";
+ internal const string BCRYPT_SP800108_CTR_HMAC_ALGORITHM = "SP800_108_CTR_HMAC";
+ internal const string BCRYPT_SP80056A_CONCAT_ALGORITHM = "SP800_56A_CONCAT";
+ internal const string BCRYPT_PBKDF2_ALGORITHM = "PBKDF2";
+ internal const string BCRYPT_CAPI_KDF_ALGORITHM = "CAPI_KDF";
+
+ // BCryptGetProperty strings
+ internal const string BCRYPT_OBJECT_LENGTH = "ObjectLength";
+ internal const string BCRYPT_ALGORITHM_NAME = "AlgorithmName";
+ internal const string BCRYPT_PROVIDER_HANDLE = "ProviderHandle";
+ internal const string BCRYPT_CHAINING_MODE = "ChainingMode";
+ internal const string BCRYPT_BLOCK_LENGTH = "BlockLength";
+ internal const string BCRYPT_KEY_LENGTH = "KeyLength";
+ internal const string BCRYPT_KEY_OBJECT_LENGTH = "KeyObjectLength";
+ internal const string BCRYPT_KEY_STRENGTH = "KeyStrength";
+ internal const string BCRYPT_KEY_LENGTHS = "KeyLengths";
+ internal const string BCRYPT_BLOCK_SIZE_LIST = "BlockSizeList";
+ internal const string BCRYPT_EFFECTIVE_KEY_LENGTH = "EffectiveKeyLength";
+ internal const string BCRYPT_HASH_LENGTH = "HashDigestLength";
+ internal const string BCRYPT_HASH_OID_LIST = "HashOIDList";
+ internal const string BCRYPT_PADDING_SCHEMES = "PaddingSchemes";
+ internal const string BCRYPT_SIGNATURE_LENGTH = "SignatureLength";
+ internal const string BCRYPT_HASH_BLOCK_LENGTH = "HashBlockLength";
+ internal const string BCRYPT_AUTH_TAG_LENGTH = "AuthTagLength";
+ internal const string BCRYPT_PRIMITIVE_TYPE = "PrimitiveType";
+ internal const string BCRYPT_IS_KEYED_HASH = "IsKeyedHash";
+ internal const string BCRYPT_IS_REUSABLE_HASH = "IsReusableHash";
+ internal const string BCRYPT_MESSAGE_BLOCK_LENGTH = "MessageBlockLength";
+
+ // Property Strings
+ internal const string BCRYPT_CHAIN_MODE_NA = "ChainingModeN/A";
+ internal const string BCRYPT_CHAIN_MODE_CBC = "ChainingModeCBC";
+ internal const string BCRYPT_CHAIN_MODE_ECB = "ChainingModeECB";
+ internal const string BCRYPT_CHAIN_MODE_CFB = "ChainingModeCFB";
+ internal const string BCRYPT_CHAIN_MODE_CCM = "ChainingModeCCM";
+ internal const string BCRYPT_CHAIN_MODE_GCM = "ChainingModeGCM";
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/CryptoUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/CryptoUtil.cs
new file mode 100644
index 0000000000..e60673634d
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/CryptoUtil.cs
@@ -0,0 +1,99 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.Internal;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ internal unsafe static class CryptoUtil
+ {
+ // This isn't a typical Debug.Assert; the check is always performed, even in retail builds.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Assert(bool condition, string message)
+ {
+ if (!condition)
+ {
+ Fail(message);
+ }
+ }
+
+ // This isn't a typical Debug.Assert; the check is always performed, even in retail builds.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void AssertSafeHandleIsValid(SafeHandle safeHandle)
+ {
+ Assert(safeHandle != null && !safeHandle.IsInvalid, "Safe handle is invalid.");
+ }
+
+ // Asserts that the current platform is Windows; throws PlatformNotSupportedException otherwise.
+ public static void AssertPlatformIsWindows()
+ {
+ if (!OSVersionUtil.IsWindows())
+ {
+ throw new PlatformNotSupportedException(Resources.Platform_Windows7Required);
+ }
+ }
+
+ // Asserts that the current platform is Windows 8 or above; throws PlatformNotSupportedException otherwise.
+ public static void AssertPlatformIsWindows8OrLater()
+ {
+ if (!OSVersionUtil.IsWindows8OrLater())
+ {
+ throw new PlatformNotSupportedException(Resources.Platform_Windows8Required);
+ }
+ }
+
+ // This isn't a typical Debug.Fail; an error always occurs, even in retail builds.
+ // This method doesn't return, but since the CLR doesn't allow specifying a 'never'
+ // return type, we mimic it by specifying our return type as Exception. That way
+ // callers can write 'throw Fail(...);' to make the C# compiler happy, as the
+ // throw keyword is implicitly of type O.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static Exception Fail(string message)
+ {
+ Debug.Fail(message);
+ throw new CryptographicException("Assertion failed: " + message);
+ }
+
+ // Allows callers to write "var x = Method() ?? Fail(message);" as a convenience to guard
+ // against a method returning null unexpectedly.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static T Fail(string message) where T : class
+ {
+ throw Fail(message);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static bool TimeConstantBuffersAreEqual(byte* bufA, byte* bufB, uint count)
+ {
+ bool areEqual = true;
+ for (uint i = 0; i < count; i++)
+ {
+ areEqual &= (bufA[i] == bufB[i]);
+ }
+ return areEqual;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ public static bool TimeConstantBuffersAreEqual(byte[] bufA, int offsetA, int countA, byte[] bufB, int offsetB, int countB)
+ {
+ // Technically this is an early exit scenario, but it means that the caller did something bizarre.
+ // An error at the call site isn't usable for timing attacks.
+ Assert(countA == countB, "countA == countB");
+
+ bool areEqual = true;
+ for (int i = 0; i < countA; i++)
+ {
+ areEqual &= (bufA[offsetA + i] == bufB[offsetB + i]);
+ }
+ return areEqual;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/DATA_BLOB.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/DATA_BLOB.cs
new file mode 100644
index 0000000000..3c307bebce
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/DATA_BLOB.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa381414(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct DATA_BLOB
+ {
+ public uint cbData;
+ public byte* pbData;
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Microsoft.AspNetCore.Cryptography.Internal.csproj b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Microsoft.AspNetCore.Cryptography.Internal.csproj
new file mode 100644
index 0000000000..ff4ef3babe
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Microsoft.AspNetCore.Cryptography.Internal.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Infrastructure for ASP.NET Core cryptographic packages. Applications and libraries should not reference this package directly.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ true
+ aspnetcore;dataprotection
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..62865ae945
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/AssemblyInfo.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// we only ever p/invoke into DLLs known to be in the System32 folder
+[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cryptography.Internal.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cryptography.KeyDerivation, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cryptography.KeyDerivation.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..df010bc683
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Properties/Resources.Designer.cs
@@ -0,0 +1,86 @@
+//
+namespace Microsoft.AspNetCore.Cryptography.Internal
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.Cryptography.Internal.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// A provider could not be found for algorithm '{0}'.
+ ///
+ internal static string BCryptAlgorithmHandle_ProviderNotFound
+ {
+ get => GetString("BCryptAlgorithmHandle_ProviderNotFound");
+ }
+
+ ///
+ /// A provider could not be found for algorithm '{0}'.
+ ///
+ internal static string FormatBCryptAlgorithmHandle_ProviderNotFound(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("BCryptAlgorithmHandle_ProviderNotFound"), p0);
+
+ ///
+ /// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
+ ///
+ internal static string BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength
+ {
+ get => GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength");
+ }
+
+ ///
+ /// The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
+ ///
+ internal static string FormatBCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength(object p0, object p1, object p2, object p3)
+ => string.Format(CultureInfo.CurrentCulture, GetString("BCRYPT_KEY_LENGTHS_STRUCT_InvalidKeyLength"), p0, p1, p2, p3);
+
+ ///
+ /// This operation requires Windows 7 / Windows Server 2008 R2 or later.
+ ///
+ internal static string Platform_Windows7Required
+ {
+ get => GetString("Platform_Windows7Required");
+ }
+
+ ///
+ /// This operation requires Windows 7 / Windows Server 2008 R2 or later.
+ ///
+ internal static string FormatPlatform_Windows7Required()
+ => GetString("Platform_Windows7Required");
+
+ ///
+ /// This operation requires Windows 8 / Windows Server 2012 or later.
+ ///
+ internal static string Platform_Windows8Required
+ {
+ get => GetString("Platform_Windows8Required");
+ }
+
+ ///
+ /// This operation requires Windows 8 / Windows Server 2012 or later.
+ ///
+ internal static string FormatPlatform_Windows8Required()
+ => GetString("Platform_Windows8Required");
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Resources.resx b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Resources.resx
new file mode 100644
index 0000000000..125f619abb
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/Resources.resx
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ A provider could not be found for algorithm '{0}'.
+
+
+ The key length {0} is invalid. Valid key lengths are {1} to {2} bits (step size {3}).
+
+
+ This operation requires Windows 7 / Windows Server 2008 R2 or later.
+
+
+ This operation requires Windows 8 / Windows Server 2012 or later.
+
+
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptAlgorithmHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptAlgorithmHandle.cs
new file mode 100644
index 0000000000..45f4c4e041
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptAlgorithmHandle.cs
@@ -0,0 +1,170 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.Internal;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ ///
+ /// Represents a handle to a BCrypt algorithm provider from which keys and hashes can be created.
+ ///
+ internal unsafe sealed class BCryptAlgorithmHandle : BCryptHandle
+ {
+ // Called by P/Invoke when returning SafeHandles
+ private BCryptAlgorithmHandle() { }
+
+ ///
+ /// Creates an unkeyed hash handle from this hash algorithm.
+ ///
+ public BCryptHashHandle CreateHash()
+ {
+ return CreateHashCore(null, 0);
+ }
+
+ private BCryptHashHandle CreateHashCore(byte* pbKey, uint cbKey)
+ {
+ BCryptHashHandle retVal;
+ int ntstatus = UnsafeNativeMethods.BCryptCreateHash(this, out retVal, IntPtr.Zero, 0, pbKey, cbKey, dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(retVal);
+
+ retVal.SetAlgorithmProviderHandle(this);
+ return retVal;
+ }
+
+ ///
+ /// Creates an HMAC hash handle from this hash algorithm.
+ ///
+ public BCryptHashHandle CreateHmac(byte* pbKey, uint cbKey)
+ {
+ Debug.Assert(pbKey != null);
+ return CreateHashCore(pbKey, cbKey);
+ }
+
+ ///
+ /// Imports a key into a symmetric encryption or KDF algorithm.
+ ///
+ public BCryptKeyHandle GenerateSymmetricKey(byte* pbSecret, uint cbSecret)
+ {
+ BCryptKeyHandle retVal;
+ int ntstatus = UnsafeNativeMethods.BCryptGenerateSymmetricKey(this, out retVal, IntPtr.Zero, 0, pbSecret, cbSecret, 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(retVal);
+
+ retVal.SetAlgorithmProviderHandle(this);
+ return retVal;
+ }
+
+ ///
+ /// Gets the name of this BCrypt algorithm.
+ ///
+ public string GetAlgorithmName()
+ {
+ // First, calculate how many characters are in the name.
+ uint byteLengthOfNameWithTerminatingNull = GetProperty(Constants.BCRYPT_ALGORITHM_NAME, null, 0);
+ CryptoUtil.Assert(byteLengthOfNameWithTerminatingNull % sizeof(char) == 0 && byteLengthOfNameWithTerminatingNull > sizeof(char), "byteLengthOfNameWithTerminatingNull % sizeof(char) == 0 && byteLengthOfNameWithTerminatingNull > sizeof(char)");
+ uint numCharsWithoutNull = (byteLengthOfNameWithTerminatingNull - 1) / sizeof(char);
+
+ if (numCharsWithoutNull == 0)
+ {
+ return String.Empty; // degenerate case
+ }
+
+ // Allocate a string object and write directly into it (CLR team approves of this mechanism).
+ string retVal = new String((char)0, checked((int)numCharsWithoutNull));
+ uint numBytesCopied;
+ fixed (char* pRetVal = retVal)
+ {
+ numBytesCopied = GetProperty(Constants.BCRYPT_ALGORITHM_NAME, pRetVal, byteLengthOfNameWithTerminatingNull);
+ }
+ CryptoUtil.Assert(numBytesCopied == byteLengthOfNameWithTerminatingNull, "numBytesCopied == byteLengthOfNameWithTerminatingNull");
+ return retVal;
+ }
+
+ ///
+ /// Gets the cipher block length (in bytes) of this block cipher algorithm.
+ ///
+ public uint GetCipherBlockLength()
+ {
+ uint cipherBlockLength;
+ uint numBytesCopied = GetProperty(Constants.BCRYPT_BLOCK_LENGTH, &cipherBlockLength, sizeof(uint));
+ CryptoUtil.Assert(numBytesCopied == sizeof(uint), "numBytesCopied == sizeof(uint)");
+ return cipherBlockLength;
+ }
+
+ ///
+ /// Gets the hash block length (in bytes) of this hash algorithm.
+ ///
+ public uint GetHashBlockLength()
+ {
+ uint hashBlockLength;
+ uint numBytesCopied = GetProperty(Constants.BCRYPT_HASH_BLOCK_LENGTH, &hashBlockLength, sizeof(uint));
+ CryptoUtil.Assert(numBytesCopied == sizeof(uint), "numBytesCopied == sizeof(uint)");
+ return hashBlockLength;
+ }
+
+ ///
+ /// Gets the key lengths (in bits) supported by this algorithm.
+ ///
+ public BCRYPT_KEY_LENGTHS_STRUCT GetSupportedKeyLengths()
+ {
+ BCRYPT_KEY_LENGTHS_STRUCT supportedKeyLengths;
+ uint numBytesCopied = GetProperty(Constants.BCRYPT_KEY_LENGTHS, &supportedKeyLengths, (uint)sizeof(BCRYPT_KEY_LENGTHS_STRUCT));
+ CryptoUtil.Assert(numBytesCopied == sizeof(BCRYPT_KEY_LENGTHS_STRUCT), "numBytesCopied == sizeof(BCRYPT_KEY_LENGTHS_STRUCT)");
+ return supportedKeyLengths;
+ }
+
+ ///
+ /// Gets the digest length (in bytes) of this hash algorithm provider.
+ ///
+ public uint GetHashDigestLength()
+ {
+ uint digestLength;
+ uint numBytesCopied = GetProperty(Constants.BCRYPT_HASH_LENGTH, &digestLength, sizeof(uint));
+ CryptoUtil.Assert(numBytesCopied == sizeof(uint), "numBytesCopied == sizeof(uint)");
+ return digestLength;
+ }
+
+ public static BCryptAlgorithmHandle OpenAlgorithmHandle(string algorithmId, string implementation = null, bool hmac = false)
+ {
+ // from bcrypt.h
+ const uint BCRYPT_ALG_HANDLE_HMAC_FLAG = 0x00000008;
+
+ // from ntstatus.h
+ const int STATUS_NOT_FOUND = unchecked((int)0xC0000225);
+
+ BCryptAlgorithmHandle algHandle;
+ int ntstatus = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algHandle, algorithmId, implementation, dwFlags: (hmac) ? BCRYPT_ALG_HANDLE_HMAC_FLAG : 0);
+
+ // error checking
+ if (ntstatus == STATUS_NOT_FOUND)
+ {
+ string message = String.Format(CultureInfo.CurrentCulture, Resources.BCryptAlgorithmHandle_ProviderNotFound, algorithmId);
+ throw new CryptographicException(message);
+ }
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(algHandle);
+
+ return algHandle;
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ return (UnsafeNativeMethods.BCryptCloseAlgorithmProvider(handle, dwFlags: 0) == 0);
+ }
+
+ public void SetChainingMode(string chainingMode)
+ {
+ fixed (char* pszChainingMode = chainingMode ?? String.Empty)
+ {
+ SetProperty(Constants.BCRYPT_CHAINING_MODE, pszChainingMode, checked((uint)(chainingMode.Length + 1 /* null terminator */) * sizeof(char)));
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHandle.cs
new file mode 100644
index 0000000000..66b2c1dbd4
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHandle.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ internal unsafe abstract class BCryptHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ protected BCryptHandle()
+ : base(ownsHandle: true)
+ {
+ }
+
+ protected uint GetProperty(string pszProperty, void* pbOutput, uint cbOutput)
+ {
+ uint retVal;
+ int ntstatus = UnsafeNativeMethods.BCryptGetProperty(this, pszProperty, pbOutput, cbOutput, out retVal, dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ return retVal;
+ }
+
+ protected void SetProperty(string pszProperty, void* pbInput, uint cbInput)
+ {
+ int ntstatus = UnsafeNativeMethods.BCryptSetProperty(this, pszProperty, pbInput, cbInput, dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHashHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHashHandle.cs
new file mode 100644
index 0000000000..dace0f23ae
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptHashHandle.cs
@@ -0,0 +1,71 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ internal unsafe sealed class BCryptHashHandle : BCryptHandle
+ {
+ private BCryptAlgorithmHandle _algProviderHandle;
+
+ // Called by P/Invoke when returning SafeHandles
+ private BCryptHashHandle() { }
+
+ ///
+ /// Duplicates this hash handle, including any existing hashed state.
+ ///
+ public BCryptHashHandle DuplicateHash()
+ {
+ BCryptHashHandle duplicateHandle;
+ int ntstatus = UnsafeNativeMethods.BCryptDuplicateHash(this, out duplicateHandle, IntPtr.Zero, 0, 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(duplicateHandle);
+
+ duplicateHandle._algProviderHandle = this._algProviderHandle;
+ return duplicateHandle;
+ }
+
+ ///
+ /// Calculates the cryptographic hash over a set of input data.
+ ///
+ public void HashData(byte* pbInput, uint cbInput, byte* pbHashDigest, uint cbHashDigest)
+ {
+ int ntstatus;
+ if (cbInput > 0)
+ {
+ ntstatus = UnsafeNativeMethods.BCryptHashData(
+ hHash: this,
+ pbInput: pbInput,
+ cbInput: cbInput,
+ dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+
+ ntstatus = UnsafeNativeMethods.BCryptFinishHash(
+ hHash: this,
+ pbOutput: pbHashDigest,
+ cbOutput: cbHashDigest,
+ dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ return (UnsafeNativeMethods.BCryptDestroyHash(handle) == 0);
+ }
+
+ // We don't actually need to hold a reference to the algorithm handle, as the native CNG library
+ // already holds the reference for us. But once we create a hash from an algorithm provider, odds
+ // are good that we'll create another hash from the same algorithm provider at some point in the
+ // future. And since algorithm providers are expensive to create, we'll hold a strong reference
+ // to all known in-use providers. This way the cached algorithm provider handles utility class
+ // doesn't keep creating providers over and over.
+ internal void SetAlgorithmProviderHandle(BCryptAlgorithmHandle algProviderHandle)
+ {
+ _algProviderHandle = algProviderHandle;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptKeyHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptKeyHandle.cs
new file mode 100644
index 0000000000..cd7d05f8e3
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/BCryptKeyHandle.cs
@@ -0,0 +1,33 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ internal sealed class BCryptKeyHandle : BCryptHandle
+ {
+ private BCryptAlgorithmHandle _algProviderHandle;
+
+ // Called by P/Invoke when returning SafeHandles
+ private BCryptKeyHandle() { }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ _algProviderHandle = null;
+ return (UnsafeNativeMethods.BCryptDestroyKey(handle) == 0);
+ }
+
+ // We don't actually need to hold a reference to the algorithm handle, as the native CNG library
+ // already holds the reference for us. But once we create a key from an algorithm provider, odds
+ // are good that we'll create another key from the same algorithm provider at some point in the
+ // future. And since algorithm providers are expensive to create, we'll hold a strong reference
+ // to all known in-use providers. This way the cached algorithm provider handles utility class
+ // doesn't keep creating providers over and over.
+ internal void SetAlgorithmProviderHandle(BCryptAlgorithmHandle algProviderHandle)
+ {
+ _algProviderHandle = algProviderHandle;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/LocalAllocHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/LocalAllocHandle.cs
new file mode 100644
index 0000000000..852c5d1594
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/LocalAllocHandle.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ ///
+ /// Represents a handle returned by LocalAlloc.
+ ///
+ internal class LocalAllocHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ // Called by P/Invoke when returning SafeHandles
+ protected LocalAllocHandle()
+ : base(ownsHandle: true) { }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ Marshal.FreeHGlobal(handle); // actually calls LocalFree
+ return true;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/NCryptDescriptorHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/NCryptDescriptorHandle.cs
new file mode 100644
index 0000000000..3a181cf06b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/NCryptDescriptorHandle.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ internal unsafe sealed class NCryptDescriptorHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ private NCryptDescriptorHandle()
+ : base(ownsHandle: true)
+ {
+ }
+
+ public string GetProtectionDescriptorRuleString()
+ {
+ // from ncryptprotect.h
+ const int NCRYPT_PROTECTION_INFO_TYPE_DESCRIPTOR_STRING = 0x00000001;
+
+ LocalAllocHandle ruleStringHandle;
+ int ntstatus = UnsafeNativeMethods.NCryptGetProtectionDescriptorInfo(
+ hDescriptor: this,
+ pMemPara: IntPtr.Zero,
+ dwInfoType: NCRYPT_PROTECTION_INFO_TYPE_DESCRIPTOR_STRING,
+ ppvInfo: out ruleStringHandle);
+ UnsafeNativeMethods.ThrowExceptionForNCryptStatus(ntstatus);
+ CryptoUtil.AssertSafeHandleIsValid(ruleStringHandle);
+
+ using (ruleStringHandle)
+ {
+ return new String((char*)ruleStringHandle.DangerousGetHandle());
+ }
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ return (UnsafeNativeMethods.NCryptCloseProtectionDescriptor(handle) == 0);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SafeLibraryHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SafeLibraryHandle.cs
new file mode 100644
index 0000000000..ccd0b99c79
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SafeLibraryHandle.cs
@@ -0,0 +1,176 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ ///
+ /// Represents a handle to a Windows module (DLL).
+ ///
+ internal unsafe sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid
+ {
+ // Called by P/Invoke when returning SafeHandles
+ private SafeLibraryHandle()
+ : base(ownsHandle: true)
+ { }
+
+ ///
+ /// Returns a value stating whether the library exports a given proc.
+ ///
+ public bool DoesProcExist(string lpProcName)
+ {
+ IntPtr pfnProc = UnsafeNativeMethods.GetProcAddress(this, lpProcName);
+ return (pfnProc != IntPtr.Zero);
+ }
+
+ ///
+ /// Forbids this library from being unloaded. The library will remain loaded until process termination,
+ /// regardless of how many times FreeLibrary is called.
+ ///
+ public void ForbidUnload()
+ {
+ // from winbase.h
+ const uint GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x00000004U;
+ const uint GET_MODULE_HANDLE_EX_FLAG_PIN = 0x00000001U;
+
+ IntPtr unused;
+ bool retVal = UnsafeNativeMethods.GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, this, out unused);
+ if (!retVal)
+ {
+ UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
+ }
+ }
+
+ ///
+ /// Formats a message string using the resource table in the specified library.
+ ///
+ public string FormatMessage(int messageId)
+ {
+ // from winbase.h
+ const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
+ const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
+ const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
+ const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
+
+ LocalAllocHandle messageHandle;
+ int numCharsOutput = UnsafeNativeMethods.FormatMessage(
+ dwFlags: FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ lpSource: this,
+ dwMessageId: (uint)messageId,
+ dwLanguageId: 0 /* ignore current culture */,
+ lpBuffer: out messageHandle,
+ nSize: 0 /* unused */,
+ Arguments: IntPtr.Zero /* unused */);
+
+ if (numCharsOutput != 0 && messageHandle != null && !messageHandle.IsInvalid)
+ {
+ // Successfully retrieved the message.
+ using (messageHandle)
+ {
+ return new String((char*)messageHandle.DangerousGetHandle(), 0, numCharsOutput).Trim();
+ }
+ }
+ else
+ {
+ // Message not found - that's fine.
+ return null;
+ }
+ }
+
+ ///
+ /// Gets a delegate pointing to a given export from this library.
+ ///
+ public TDelegate GetProcAddress(string lpProcName, bool throwIfNotFound = true) where TDelegate : class
+ {
+ IntPtr pfnProc = UnsafeNativeMethods.GetProcAddress(this, lpProcName);
+ if (pfnProc == IntPtr.Zero)
+ {
+ if (throwIfNotFound)
+ {
+ UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ return Marshal.GetDelegateForFunctionPointer(pfnProc);
+ }
+
+ ///
+ /// Opens a library. If 'filename' is not a fully-qualified path, the default search path is used.
+ ///
+ public static SafeLibraryHandle Open(string filename)
+ {
+ const uint LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800U; // from libloaderapi.h
+
+ SafeLibraryHandle handle = UnsafeNativeMethods.LoadLibraryEx(filename, IntPtr.Zero, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (handle == null || handle.IsInvalid)
+ {
+ UnsafeNativeMethods.ThrowExceptionForLastWin32Error();
+ }
+ return handle;
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ return UnsafeNativeMethods.FreeLibrary(handle);
+ }
+
+ [SuppressUnmanagedCodeSecurity]
+ private static class UnsafeNativeMethods
+ {
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx
+ [DllImport("kernel32.dll", EntryPoint = "FormatMessageW", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)]
+ public static extern int FormatMessage(
+ [In] uint dwFlags,
+ [In] SafeLibraryHandle lpSource,
+ [In] uint dwMessageId,
+ [In] uint dwLanguageId,
+ [Out] out LocalAllocHandle lpBuffer,
+ [In] uint nSize,
+ [In] IntPtr Arguments
+ );
+
+ // http://msdn.microsoft.com/en-us/library/ms683152(v=vs.85).aspx
+ [return: MarshalAs(UnmanagedType.Bool)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
+ internal static extern bool FreeLibrary(IntPtr hModule);
+
+ // http://msdn.microsoft.com/en-us/library/ms683200(v=vs.85).aspx
+ [return: MarshalAs(UnmanagedType.Bool)]
+ [DllImport("kernel32.dll", EntryPoint = "GetModuleHandleExW", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ internal static extern bool GetModuleHandleEx(
+ [In] uint dwFlags,
+ [In] SafeLibraryHandle lpModuleName, // can point to a location within the module if GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS is set
+ [Out] out IntPtr phModule);
+
+ // http://msdn.microsoft.com/en-us/library/ms683212(v=vs.85).aspx
+ [DllImport("kernel32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ internal static extern IntPtr GetProcAddress(
+ [In] SafeLibraryHandle hModule,
+ [In, MarshalAs(UnmanagedType.LPStr)] string lpProcName);
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx
+ [DllImport("kernel32.dll", EntryPoint = "LoadLibraryExW", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ internal static extern SafeLibraryHandle LoadLibraryEx(
+ [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
+ [In] IntPtr hFile,
+ [In] uint dwFlags);
+
+ internal static void ThrowExceptionForLastWin32Error()
+ {
+ int hr = Marshal.GetHRForLastWin32Error();
+ Marshal.ThrowExceptionForHR(hr);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SecureLocalAllocHandle.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SecureLocalAllocHandle.cs
new file mode 100644
index 0000000000..ac1f3c6172
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/SafeHandles/SecureLocalAllocHandle.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.ConstrainedExecution;
+
+namespace Microsoft.AspNetCore.Cryptography.SafeHandles
+{
+ ///
+ /// Represents a handle returned by LocalAlloc.
+ /// The memory will be zeroed out before it's freed.
+ ///
+ internal unsafe sealed class SecureLocalAllocHandle : LocalAllocHandle
+ {
+ private readonly IntPtr _cb;
+
+ private SecureLocalAllocHandle(IntPtr cb)
+ {
+ _cb = cb;
+ }
+
+ public IntPtr Length
+ {
+ get
+ {
+ return _cb;
+ }
+ }
+
+ ///
+ /// Allocates some amount of memory using LocalAlloc.
+ ///
+ public static SecureLocalAllocHandle Allocate(IntPtr cb)
+ {
+ SecureLocalAllocHandle newHandle = new SecureLocalAllocHandle(cb);
+ newHandle.AllocateImpl(cb);
+ return newHandle;
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ private void AllocateImpl(IntPtr cb)
+ {
+ handle = Marshal.AllocHGlobal(cb); // actually calls LocalAlloc
+ }
+
+ public SecureLocalAllocHandle Duplicate()
+ {
+ SecureLocalAllocHandle duplicateHandle = Allocate(_cb);
+ UnsafeBufferUtil.BlockCopy(from: this, to: duplicateHandle, length: _cb);
+ return duplicateHandle;
+ }
+
+ // Do not provide a finalizer - SafeHandle's critical finalizer will call ReleaseHandle for you.
+ protected override bool ReleaseHandle()
+ {
+ UnsafeBufferUtil.SecureZeroMemory((byte*)handle, _cb); // compiler won't optimize this away
+ return base.ReleaseHandle();
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeBufferUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeBufferUtil.cs
new file mode 100644
index 0000000000..681adb8bc3
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeBufferUtil.cs
@@ -0,0 +1,179 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Threading;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ internal unsafe static class UnsafeBufferUtil
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void BlockCopy(void* from, void* to, int byteCount)
+ {
+ BlockCopy(from, to, checked((uint)byteCount)); // will be checked before invoking the delegate
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void BlockCopy(void* from, void* to, uint byteCount)
+ {
+ if (byteCount != 0)
+ {
+ BlockCopyCore((byte*)from, (byte*)to, byteCount);
+ }
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public static void BlockCopy(LocalAllocHandle from, void* to, uint byteCount)
+ {
+ bool refAdded = false;
+ try
+ {
+ from.DangerousAddRef(ref refAdded);
+ BlockCopy((void*)from.DangerousGetHandle(), to, byteCount);
+ }
+ finally
+ {
+ if (refAdded)
+ {
+ from.DangerousRelease();
+ }
+ }
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public static void BlockCopy(void* from, LocalAllocHandle to, uint byteCount)
+ {
+ bool refAdded = false;
+ try
+ {
+ to.DangerousAddRef(ref refAdded);
+ BlockCopy(from, (void*)to.DangerousGetHandle(), byteCount);
+ }
+ finally
+ {
+ if (refAdded)
+ {
+ to.DangerousRelease();
+ }
+ }
+ }
+
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
+ public static void BlockCopy(LocalAllocHandle from, LocalAllocHandle to, IntPtr length)
+ {
+ if (length == IntPtr.Zero)
+ {
+ return;
+ }
+
+ bool fromRefAdded = false;
+ bool toRefAdded = false;
+ try
+ {
+ from.DangerousAddRef(ref fromRefAdded);
+ to.DangerousAddRef(ref toRefAdded);
+ if (sizeof(IntPtr) == 4)
+ {
+ BlockCopyCore(from: (byte*)from.DangerousGetHandle(), to: (byte*)to.DangerousGetHandle(), byteCount: (uint)length.ToInt32());
+ }
+ else
+ {
+ BlockCopyCore(from: (byte*)from.DangerousGetHandle(), to: (byte*)to.DangerousGetHandle(), byteCount: (ulong)length.ToInt64());
+ }
+ }
+ finally
+ {
+ if (fromRefAdded)
+ {
+ from.DangerousRelease();
+ }
+ if (toRefAdded)
+ {
+ to.DangerousRelease();
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void BlockCopyCore(byte* from, byte* to, uint byteCount)
+ {
+ Buffer.MemoryCopy(from, to, (ulong)byteCount, (ulong)byteCount);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void BlockCopyCore(byte* from, byte* to, ulong byteCount)
+ {
+ Buffer.MemoryCopy(from, to, byteCount, byteCount);
+ }
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void SecureZeroMemory(byte* buffer, int byteCount)
+ {
+ SecureZeroMemory(buffer, checked((uint)byteCount));
+ }
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void SecureZeroMemory(byte* buffer, uint byteCount)
+ {
+ if (byteCount != 0)
+ {
+ do
+ {
+ buffer[--byteCount] = 0;
+ } while (byteCount != 0);
+
+ // Volatile to make sure the zero-writes don't get optimized away
+ Volatile.Write(ref *buffer, 0);
+ }
+ }
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void SecureZeroMemory(byte* buffer, ulong byteCount)
+ {
+ if (byteCount != 0)
+ {
+ do
+ {
+ buffer[--byteCount] = 0;
+ } while (byteCount != 0);
+
+ // Volatile to make sure the zero-writes don't get optimized away
+ Volatile.Write(ref *buffer, 0);
+ }
+ }
+
+ ///
+ /// Securely clears a memory buffer.
+ ///
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ public static void SecureZeroMemory(byte* buffer, IntPtr length)
+ {
+ if (sizeof(IntPtr) == 4)
+ {
+ SecureZeroMemory(buffer, (uint)length.ToInt32());
+ }
+ else
+ {
+ SecureZeroMemory(buffer, (ulong)length.ToInt64());
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeNativeMethods.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeNativeMethods.cs
new file mode 100644
index 0000000000..3a5a4d8db3
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/UnsafeNativeMethods.cs
@@ -0,0 +1,346 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Cryptography;
+using System.Threading;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+using Microsoft.Win32.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ [SuppressUnmanagedCodeSecurity]
+ internal unsafe static class UnsafeNativeMethods
+ {
+ private const string BCRYPT_LIB = "bcrypt.dll";
+ private static readonly Lazy _lazyBCryptLibHandle = GetLazyLibraryHandle(BCRYPT_LIB);
+
+ private const string CRYPT32_LIB = "crypt32.dll";
+ private static readonly Lazy _lazyCrypt32LibHandle = GetLazyLibraryHandle(CRYPT32_LIB);
+
+ private const string NCRYPT_LIB = "ncrypt.dll";
+ private static readonly Lazy _lazyNCryptLibHandle = GetLazyLibraryHandle(NCRYPT_LIB);
+
+ private static Lazy GetLazyLibraryHandle(string libraryName)
+ {
+ // We don't need to worry about race conditions: SafeLibraryHandle will clean up after itself
+ return new Lazy(() => SafeLibraryHandle.Open(libraryName), LazyThreadSafetyMode.PublicationOnly);
+ }
+
+ /*
+ * BCRYPT.DLL
+ */
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375377(v=vs.85).aspx
+ internal static extern int BCryptCloseAlgorithmProvider(
+ [In] IntPtr hAlgorithm,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375383(v=vs.85).aspx
+ internal static extern int BCryptCreateHash(
+ [In] BCryptAlgorithmHandle hAlgorithm,
+ [Out] out BCryptHashHandle phHash,
+ [In] IntPtr pbHashObject,
+ [In] uint cbHashObject,
+ [In] byte* pbSecret,
+ [In] uint cbSecret,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375391(v=vs.85).aspx
+ internal static extern int BCryptDecrypt(
+ [In] BCryptKeyHandle hKey,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] void* pPaddingInfo,
+ [In] byte* pbIV,
+ [In] uint cbIV,
+ [In] byte* pbOutput,
+ [In] uint cbOutput,
+ [Out] out uint pcbResult,
+ [In] BCryptEncryptFlags dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/dd433795(v=vs.85).aspx
+ internal static extern int BCryptDeriveKeyPBKDF2(
+ [In] BCryptAlgorithmHandle hPrf,
+ [In] byte* pbPassword,
+ [In] uint cbPassword,
+ [In] byte* pbSalt,
+ [In] uint cbSalt,
+ [In] ulong cIterations,
+ [In] byte* pbDerivedKey,
+ [In] uint cbDerivedKey,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375399(v=vs.85).aspx
+ internal static extern int BCryptDestroyHash(
+ [In] IntPtr hHash);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375404(v=vs.85).aspx
+ internal static extern int BCryptDestroyKey(
+ [In] IntPtr hKey);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375413(v=vs.85).aspx
+ internal static extern int BCryptDuplicateHash(
+ [In] BCryptHashHandle hHash,
+ [Out] out BCryptHashHandle phNewHash,
+ [In] IntPtr pbHashObject,
+ [In] uint cbHashObject,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375421(v=vs.85).aspx
+ internal static extern int BCryptEncrypt(
+ [In] BCryptKeyHandle hKey,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] void* pPaddingInfo,
+ [In] byte* pbIV,
+ [In] uint cbIV,
+ [In] byte* pbOutput,
+ [In] uint cbOutput,
+ [Out] out uint pcbResult,
+ [In] BCryptEncryptFlags dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375443(v=vs.85).aspx
+ internal static extern int BCryptFinishHash(
+ [In] BCryptHashHandle hHash,
+ [In] byte* pbOutput,
+ [In] uint cbOutput,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375453(v=vs.85).aspx
+ internal static extern int BCryptGenerateSymmetricKey(
+ [In] BCryptAlgorithmHandle hAlgorithm,
+ [Out] out BCryptKeyHandle phKey,
+ [In] IntPtr pbKeyObject,
+ [In] uint cbKeyObject,
+ [In] byte* pbSecret,
+ [In] uint cbSecret,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375458(v=vs.85).aspx
+ internal static extern int BCryptGenRandom(
+ [In] IntPtr hAlgorithm,
+ [In] byte* pbBuffer,
+ [In] uint cbBuffer,
+ [In] BCryptGenRandomFlags dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375464(v=vs.85).aspx
+ internal static extern int BCryptGetProperty(
+ [In] BCryptHandle hObject,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszProperty,
+ [In] void* pbOutput,
+ [In] uint cbOutput,
+ [Out] out uint pcbResult,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375468(v=vs.85).aspx
+ internal static extern int BCryptHashData(
+ [In] BCryptHashHandle hHash,
+ [In] byte* pbInput,
+ [In] uint cbInput,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh448506(v=vs.85).aspx
+ internal static extern int BCryptKeyDerivation(
+ [In] BCryptKeyHandle hKey,
+ [In] BCryptBufferDesc* pParameterList,
+ [In] byte* pbDerivedKey,
+ [In] uint cbDerivedKey,
+ [Out] out uint pcbResult,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375479(v=vs.85).aspx
+ internal static extern int BCryptOpenAlgorithmProvider(
+ [Out] out BCryptAlgorithmHandle phAlgorithm,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszAlgId,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszImplementation,
+ [In] uint dwFlags);
+
+ [DllImport(BCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375504(v=vs.85).aspx
+ internal static extern int BCryptSetProperty(
+ [In] BCryptHandle hObject,
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pszProperty,
+ [In] void* pbInput,
+ [In] uint cbInput,
+ [In] uint dwFlags);
+
+ /*
+ * CRYPT32.DLL
+ */
+
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261(v=vs.85).aspx
+ internal static extern bool CryptProtectData(
+ [In] DATA_BLOB* pDataIn,
+ [In] IntPtr szDataDescr,
+ [In] DATA_BLOB* pOptionalEntropy,
+ [In] IntPtr pvReserved,
+ [In] IntPtr pPromptStruct,
+ [In] uint dwFlags,
+ [Out] out DATA_BLOB pDataOut);
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380262(v=vs.85).aspx
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ public static extern bool CryptProtectMemory(
+ [In] SafeHandle pData,
+ [In] uint cbData,
+ [In] uint dwFlags);
+
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380882(v=vs.85).aspx
+ internal static extern bool CryptUnprotectData(
+ [In] DATA_BLOB* pDataIn,
+ [In] IntPtr ppszDataDescr,
+ [In] DATA_BLOB* pOptionalEntropy,
+ [In] IntPtr pvReserved,
+ [In] IntPtr pPromptStruct,
+ [In] uint dwFlags,
+ [Out] out DATA_BLOB pDataOut);
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380890(v=vs.85).aspx
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ public static extern bool CryptUnprotectMemory(
+ [In] byte* pData,
+ [In] uint cbData,
+ [In] uint dwFlags);
+
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa380890(v=vs.85).aspx
+ [DllImport(CRYPT32_LIB, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
+ public static extern bool CryptUnprotectMemory(
+ [In] SafeHandle pData,
+ [In] uint cbData,
+ [In] uint dwFlags);
+
+ /*
+ * NCRYPT.DLL
+ */
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706799(v=vs.85).aspx
+ internal static extern int NCryptCloseProtectionDescriptor(
+ [In] IntPtr hDescriptor);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706800(v=vs.85).aspx
+ internal static extern int NCryptCreateProtectionDescriptor(
+ [In, MarshalAs(UnmanagedType.LPWStr)] string pwszDescriptorString,
+ [In] uint dwFlags,
+ [Out] out NCryptDescriptorHandle phDescriptor);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/hh706801(v=vs.85).aspx
+ internal static extern int NCryptGetProtectionDescriptorInfo(
+ [In] NCryptDescriptorHandle hDescriptor,
+ [In] IntPtr pMemPara,
+ [In] uint dwInfoType,
+ [Out] out LocalAllocHandle ppvInfo);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706802(v=vs.85).aspx
+ internal static extern int NCryptProtectSecret(
+ [In] NCryptDescriptorHandle hDescriptor,
+ [In] uint dwFlags,
+ [In] byte* pbData,
+ [In] uint cbData,
+ [In] IntPtr pMemPara,
+ [In] IntPtr hWnd,
+ [Out] out LocalAllocHandle ppbProtectedBlob,
+ [Out] out uint pcbProtectedBlob);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706811(v=vs.85).aspx
+ internal static extern int NCryptUnprotectSecret(
+ [In] IntPtr phDescriptor,
+ [In] uint dwFlags,
+ [In] byte* pbProtectedBlob,
+ [In] uint cbProtectedBlob,
+ [In] IntPtr pMemPara,
+ [In] IntPtr hWnd,
+ [Out] out LocalAllocHandle ppbData,
+ [Out] out uint pcbData);
+
+ [DllImport(NCRYPT_LIB, CallingConvention = CallingConvention.Winapi)]
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706811(v=vs.85).aspx
+ internal static extern int NCryptUnprotectSecret(
+ [Out] out NCryptDescriptorHandle phDescriptor,
+ [In] uint dwFlags,
+ [In] byte* pbProtectedBlob,
+ [In] uint cbProtectedBlob,
+ [In] IntPtr pMemPara,
+ [In] IntPtr hWnd,
+ [Out] out LocalAllocHandle ppbData,
+ [Out] out uint pcbData);
+
+ /*
+ * HELPER FUNCTIONS
+ */
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void ThrowExceptionForBCryptStatus(int ntstatus)
+ {
+ // This wrapper method exists because 'throw' statements won't always be inlined.
+ if (ntstatus != 0)
+ {
+ ThrowExceptionForBCryptStatusImpl(ntstatus);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowExceptionForBCryptStatusImpl(int ntstatus)
+ {
+ string message = _lazyBCryptLibHandle.Value.FormatMessage(ntstatus);
+ throw new CryptographicException(message);
+ }
+
+ public static void ThrowExceptionForLastCrypt32Error()
+ {
+ int lastError = Marshal.GetLastWin32Error();
+ Debug.Assert(lastError != 0, "This method should only be called if there was an error.");
+
+ string message = _lazyCrypt32LibHandle.Value.FormatMessage(lastError);
+ throw new CryptographicException(message);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void ThrowExceptionForNCryptStatus(int ntstatus)
+ {
+ // This wrapper method exists because 'throw' statements won't always be inlined.
+ if (ntstatus != 0)
+ {
+ ThrowExceptionForNCryptStatusImpl(ntstatus);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void ThrowExceptionForNCryptStatusImpl(int ntstatus)
+ {
+ string message = _lazyNCryptLibHandle.Value.FormatMessage(ntstatus);
+ throw new CryptographicException(message);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/WeakReferenceHelpers.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/WeakReferenceHelpers.cs
new file mode 100644
index 0000000000..71b77a58e5
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/WeakReferenceHelpers.cs
@@ -0,0 +1,59 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Microsoft.AspNetCore.Cryptography
+{
+ internal static class WeakReferenceHelpers
+ {
+ public static T GetSharedInstance(ref WeakReference weakReference, Func factory)
+ where T : class, IDisposable
+ {
+ // First, see if the WR already exists and points to a live object.
+ WeakReference existingWeakRef = Volatile.Read(ref weakReference);
+ T newTarget = null;
+ WeakReference newWeakRef = null;
+
+ while (true)
+ {
+ if (existingWeakRef != null)
+ {
+ T existingTarget;
+ if (weakReference.TryGetTarget(out existingTarget))
+ {
+ // If we created a new target on a previous iteration of the loop but we
+ // weren't able to store the target into the desired location, dispose of it now.
+ newTarget?.Dispose();
+ return existingTarget;
+ }
+ }
+
+ // If the existing WR didn't point anywhere useful and this is our
+ // first iteration through the loop, create the new target and WR now.
+ if (newTarget == null)
+ {
+ newTarget = factory();
+ Debug.Assert(newTarget != null);
+ newWeakRef = new WeakReference(newTarget);
+ }
+ Debug.Assert(newWeakRef != null);
+
+ // Try replacing the existing WR with our newly-created one.
+ WeakReference currentWeakRef = Interlocked.CompareExchange(ref weakReference, newWeakRef, existingWeakRef);
+ if (ReferenceEquals(currentWeakRef, existingWeakRef))
+ {
+ // success, 'weakReference' now points to our newly-created WR
+ return newTarget;
+ }
+
+ // If we got to this point, somebody beat us to creating a new WR.
+ // We'll loop around and check it for validity.
+ Debug.Assert(currentWeakRef != null);
+ existingWeakRef = currentWeakRef;
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/baseline.netcore.json
new file mode 100644
index 0000000000..01daa339ee
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.Internal/baseline.netcore.json
@@ -0,0 +1,4 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Cryptography.Internal, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": []
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivation.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivation.cs
new file mode 100644
index 0000000000..67ff1ca420
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivation.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation
+{
+ ///
+ /// Provides algorithms for performing key derivation.
+ ///
+ public static class KeyDerivation
+ {
+ ///
+ /// Performs key derivation using the PBKDF2 algorithm.
+ ///
+ /// The password from which to derive the key.
+ /// The salt to be used during the key derivation process.
+ /// The pseudo-random function to be used in the key derivation process.
+ /// The number of iterations of the pseudo-random function to apply
+ /// during the key derivation process.
+ /// The desired length (in bytes) of the derived key.
+ /// The derived key.
+ ///
+ /// The PBKDF2 algorithm is specified in RFC 2898.
+ ///
+ public static byte[] Pbkdf2(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ if (password == null)
+ {
+ throw new ArgumentNullException(nameof(password));
+ }
+
+ if (salt == null)
+ {
+ throw new ArgumentNullException(nameof(salt));
+ }
+
+ // parameter checking
+ if (prf < KeyDerivationPrf.HMACSHA1 || prf > KeyDerivationPrf.HMACSHA512)
+ {
+ throw new ArgumentOutOfRangeException(nameof(prf));
+ }
+ if (iterationCount <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(iterationCount));
+ }
+ if (numBytesRequested <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(numBytesRequested));
+ }
+
+ return Pbkdf2Util.Pbkdf2Provider.DeriveKey(password, salt, prf, iterationCount, numBytesRequested);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivationPrf.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivationPrf.cs
new file mode 100644
index 0000000000..57e740f04b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/KeyDerivationPrf.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation
+{
+ ///
+ /// Specifies the PRF which should be used for the key derivation algorithm.
+ ///
+ public enum KeyDerivationPrf
+ {
+ ///
+ /// The HMAC algorithm (RFC 2104) using the SHA-1 hash function (FIPS 180-4).
+ ///
+ HMACSHA1,
+
+ ///
+ /// The HMAC algorithm (RFC 2104) using the SHA-256 hash function (FIPS 180-4).
+ ///
+ HMACSHA256,
+
+ ///
+ /// The HMAC algorithm (RFC 2104) using the SHA-512 hash function (FIPS 180-4).
+ ///
+ HMACSHA512,
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj
new file mode 100644
index 0000000000..04dec66482
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Microsoft.AspNetCore.Cryptography.KeyDerivation.csproj
@@ -0,0 +1,15 @@
+
+
+
+ ASP.NET Core utilities for key derivation.
+ netstandard2.0;netcoreapp3.0
+ true
+ true
+ aspnetcore;dataprotection
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/IPbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/IPbkdf2Provider.cs
new file mode 100644
index 0000000000..8be8a5e809
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/IPbkdf2Provider.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// Internal interface used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
+ ///
+ internal interface IPbkdf2Provider
+ {
+ byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested);
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/ManagedPbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/ManagedPbkdf2Provider.cs
new file mode 100644
index 0000000000..bf81ae65c5
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/ManagedPbkdf2Provider.cs
@@ -0,0 +1,103 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// A PBKDF2 provider which utilizes the managed hash algorithm classes as PRFs.
+ /// This isn't the preferred provider since the implementation is slow, but it is provided as a fallback.
+ ///
+ internal sealed class ManagedPbkdf2Provider : IPbkdf2Provider
+ {
+ public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ Debug.Assert(password != null);
+ Debug.Assert(salt != null);
+ Debug.Assert(iterationCount > 0);
+ Debug.Assert(numBytesRequested > 0);
+
+ // PBKDF2 is defined in NIST SP800-132, Sec. 5.3.
+ // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
+
+ byte[] retVal = new byte[numBytesRequested];
+ int numBytesWritten = 0;
+ int numBytesRemaining = numBytesRequested;
+
+ // For each block index, U_0 := Salt || block_index
+ byte[] saltWithBlockIndex = new byte[checked(salt.Length + sizeof(uint))];
+ Buffer.BlockCopy(salt, 0, saltWithBlockIndex, 0, salt.Length);
+
+ using (var hashAlgorithm = PrfToManagedHmacAlgorithm(prf, password))
+ {
+ for (uint blockIndex = 1; numBytesRemaining > 0; blockIndex++)
+ {
+ // write the block index out as big-endian
+ saltWithBlockIndex[saltWithBlockIndex.Length - 4] = (byte)(blockIndex >> 24);
+ saltWithBlockIndex[saltWithBlockIndex.Length - 3] = (byte)(blockIndex >> 16);
+ saltWithBlockIndex[saltWithBlockIndex.Length - 2] = (byte)(blockIndex >> 8);
+ saltWithBlockIndex[saltWithBlockIndex.Length - 1] = (byte)blockIndex;
+
+ // U_1 = PRF(U_0) = PRF(Salt || block_index)
+ // T_blockIndex = U_1
+ byte[] U_iter = hashAlgorithm.ComputeHash(saltWithBlockIndex); // this is U_1
+ byte[] T_blockIndex = U_iter;
+
+ for (int iter = 1; iter < iterationCount; iter++)
+ {
+ U_iter = hashAlgorithm.ComputeHash(U_iter);
+ XorBuffers(src: U_iter, dest: T_blockIndex);
+ // At this point, the 'U_iter' variable actually contains U_{iter+1} (due to indexing differences).
+ }
+
+ // At this point, we're done iterating on this block, so copy the transformed block into retVal.
+ int numBytesToCopy = Math.Min(numBytesRemaining, T_blockIndex.Length);
+ Buffer.BlockCopy(T_blockIndex, 0, retVal, numBytesWritten, numBytesToCopy);
+ numBytesWritten += numBytesToCopy;
+ numBytesRemaining -= numBytesToCopy;
+ }
+ }
+
+ // retVal := T_1 || T_2 || ... || T_n, where T_n may be truncated to meet the desired output length
+ return retVal;
+ }
+
+ private static KeyedHashAlgorithm PrfToManagedHmacAlgorithm(KeyDerivationPrf prf, string password)
+ {
+ byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
+ try
+ {
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ return new HMACSHA1(passwordBytes);
+ case KeyDerivationPrf.HMACSHA256:
+ return new HMACSHA256(passwordBytes);
+ case KeyDerivationPrf.HMACSHA512:
+ return new HMACSHA512(passwordBytes);
+ default:
+ throw CryptoUtil.Fail("Unrecognized PRF.");
+ }
+ }
+ finally
+ {
+ // The HMAC ctor makes a duplicate of this key; we clear original buffer to limit exposure to the GC.
+ Array.Clear(passwordBytes, 0, passwordBytes.Length);
+ }
+ }
+
+ private static void XorBuffers(byte[] src, byte[] dest)
+ {
+ // Note: dest buffer is mutated.
+ Debug.Assert(src.Length == dest.Length);
+ for (int i = 0; i < src.Length; i++)
+ {
+ dest[i] ^= src[i];
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/NetCorePbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/NetCorePbkdf2Provider.cs
new file mode 100644
index 0000000000..a8ce1772eb
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/NetCorePbkdf2Provider.cs
@@ -0,0 +1,71 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#if NETCOREAPP3_0
+// Rfc2898DeriveBytes in .NET Standard 2.0 only supports SHA1
+
+using System;
+using System.Diagnostics;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// Implements Pbkdf2 using .
+ ///
+ internal sealed class NetCorePbkdf2Provider : IPbkdf2Provider
+ {
+ private static readonly ManagedPbkdf2Provider _fallbackProvider = new ManagedPbkdf2Provider();
+
+ public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ Debug.Assert(password != null);
+ Debug.Assert(salt != null);
+ Debug.Assert(iterationCount > 0);
+ Debug.Assert(numBytesRequested > 0);
+
+ if (salt.Length < 8)
+ {
+ // Rfc2898DeriveBytes enforces the 8 byte recommendation.
+ // To maintain compatibility, we call into ManagedPbkdf2Provider for salts shorter than 8 bytes
+ // because we can't use Rfc2898DeriveBytes with this salt.
+ return _fallbackProvider.DeriveKey(password, salt, prf, iterationCount, numBytesRequested);
+ }
+ else
+ {
+ return DeriveKeyImpl(password, salt, prf, iterationCount, numBytesRequested);
+ }
+ }
+
+ private static byte[] DeriveKeyImpl(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ HashAlgorithmName algorithmName;
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ algorithmName = HashAlgorithmName.SHA1;
+ break;
+ case KeyDerivationPrf.HMACSHA256:
+ algorithmName = HashAlgorithmName.SHA256;
+ break;
+ case KeyDerivationPrf.HMACSHA512:
+ algorithmName = HashAlgorithmName.SHA512;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ var passwordBytes = Encoding.UTF8.GetBytes(password);
+ using (var rfc = new Rfc2898DeriveBytes(passwordBytes, salt, iterationCount, algorithmName))
+ {
+ return rfc.GetBytes(numBytesRequested);
+ }
+ }
+ }
+}
+
+#elif NETSTANDARD2_0
+#else
+#error Update target frameworks
+#endif
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Pbkdf2Util.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Pbkdf2Util.cs
new file mode 100644
index 0000000000..d8139c92f7
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Pbkdf2Util.cs
@@ -0,0 +1,46 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Cryptography.Cng;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// Internal base class used for abstracting away the PBKDF2 implementation since the implementation is OS-specific.
+ ///
+ internal static class Pbkdf2Util
+ {
+ public static readonly IPbkdf2Provider Pbkdf2Provider = GetPbkdf2Provider();
+
+ private static IPbkdf2Provider GetPbkdf2Provider()
+ {
+ // In priority order, our three implementations are Win8, Win7, and "other".
+ if (OSVersionUtil.IsWindows8OrLater())
+ {
+ // fastest implementation
+ return new Win8Pbkdf2Provider();
+ }
+ else if (OSVersionUtil.IsWindows())
+ {
+ // acceptable implementation
+ return new Win7Pbkdf2Provider();
+ }
+#if NETCOREAPP3_0
+ else
+ {
+ // fastest implementation on .NET Core for Linux/macOS.
+ // Not supported on .NET Framework
+ return new NetCorePbkdf2Provider();
+ }
+#elif NETSTANDARD2_0
+ else
+ {
+ // slowest implementation
+ return new ManagedPbkdf2Provider();
+ }
+#else
+#error Update target frameworks
+#endif
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win7Pbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win7Pbkdf2Provider.cs
new file mode 100644
index 0000000000..4c359b80f4
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win7Pbkdf2Provider.cs
@@ -0,0 +1,100 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Text;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// A PBKDF2 provider which utilizes the Win7 API BCryptDeriveKeyPBKDF2.
+ ///
+ internal unsafe sealed class Win7Pbkdf2Provider : IPbkdf2Provider
+ {
+ public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ Debug.Assert(password != null);
+ Debug.Assert(salt != null);
+ Debug.Assert(iterationCount > 0);
+ Debug.Assert(numBytesRequested > 0);
+
+ byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers
+
+ // Don't dispose of this algorithm instance; it is cached and reused!
+ var algHandle = PrfToCachedCngAlgorithmInstance(prf);
+
+ // Convert password string to bytes.
+ // Allocate on the stack whenever we can to save allocations.
+ int cbPasswordBuffer = Encoding.UTF8.GetMaxByteCount(password.Length);
+ fixed (byte* pbHeapAllocatedPasswordBuffer = (cbPasswordBuffer > Constants.MAX_STACKALLOC_BYTES) ? new byte[cbPasswordBuffer] : null)
+ {
+ byte* pbPasswordBuffer = pbHeapAllocatedPasswordBuffer;
+ if (pbPasswordBuffer == null)
+ {
+ if (cbPasswordBuffer == 0)
+ {
+ pbPasswordBuffer = &dummy;
+ }
+ else
+ {
+ byte* pbStackAllocPasswordBuffer = stackalloc byte[cbPasswordBuffer]; // will be released when the frame unwinds
+ pbPasswordBuffer = pbStackAllocPasswordBuffer;
+ }
+ }
+
+ try
+ {
+ int cbPasswordBufferUsed; // we're not filling the entire buffer, just a partial buffer
+ fixed (char* pszPassword = password)
+ {
+ cbPasswordBufferUsed = Encoding.UTF8.GetBytes(pszPassword, password.Length, pbPasswordBuffer, cbPasswordBuffer);
+ }
+
+ fixed (byte* pbHeapAllocatedSalt = salt)
+ {
+ byte* pbSalt = (pbHeapAllocatedSalt != null) ? pbHeapAllocatedSalt : &dummy;
+
+ byte[] retVal = new byte[numBytesRequested];
+ fixed (byte* pbRetVal = retVal)
+ {
+ int ntstatus = UnsafeNativeMethods.BCryptDeriveKeyPBKDF2(
+ hPrf: algHandle,
+ pbPassword: pbPasswordBuffer,
+ cbPassword: (uint)cbPasswordBufferUsed,
+ pbSalt: pbSalt,
+ cbSalt: (uint)salt.Length,
+ cIterations: (ulong)iterationCount,
+ pbDerivedKey: pbRetVal,
+ cbDerivedKey: (uint)retVal.Length,
+ dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+ }
+ return retVal;
+ }
+ }
+ finally
+ {
+ UnsafeBufferUtil.SecureZeroMemory(pbPasswordBuffer, cbPasswordBuffer);
+ }
+ }
+ }
+
+ private static BCryptAlgorithmHandle PrfToCachedCngAlgorithmInstance(KeyDerivationPrf prf)
+ {
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ return CachedAlgorithmHandles.HMAC_SHA1;
+ case KeyDerivationPrf.HMACSHA256:
+ return CachedAlgorithmHandles.HMAC_SHA256;
+ case KeyDerivationPrf.HMACSHA512:
+ return CachedAlgorithmHandles.HMAC_SHA512;
+ default:
+ throw CryptoUtil.Fail("Unrecognized PRF.");
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win8Pbkdf2Provider.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win8Pbkdf2Provider.cs
new file mode 100644
index 0000000000..296e85b7dd
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2/Win8Pbkdf2Provider.cs
@@ -0,0 +1,211 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Microsoft.AspNetCore.Cryptography.Cng;
+using Microsoft.AspNetCore.Cryptography.SafeHandles;
+
+namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
+{
+ ///
+ /// A PBKDF2 provider which utilizes the Win8 API BCryptKeyDerivation.
+ ///
+ internal unsafe sealed class Win8Pbkdf2Provider : IPbkdf2Provider
+ {
+ public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
+ {
+ Debug.Assert(password != null);
+ Debug.Assert(salt != null);
+ Debug.Assert(iterationCount > 0);
+ Debug.Assert(numBytesRequested > 0);
+
+ string algorithmName = PrfToCngAlgorithmId(prf);
+ fixed (byte* pbHeapAllocatedSalt = salt)
+ {
+ byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers
+ byte* pbSalt = (pbHeapAllocatedSalt != null) ? pbHeapAllocatedSalt : &dummy;
+
+ byte[] retVal = new byte[numBytesRequested];
+ using (BCryptKeyHandle keyHandle = PasswordToPbkdfKeyHandle(password, CachedAlgorithmHandles.PBKDF2, prf))
+ {
+ fixed (byte* pbRetVal = retVal)
+ {
+ DeriveKeyCore(keyHandle, algorithmName, pbSalt, (uint)salt.Length, (ulong)iterationCount, pbRetVal, (uint)retVal.Length);
+ }
+ return retVal;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint GetTotalByteLengthIncludingNullTerminator(string input)
+ {
+ if (input == null)
+ {
+ // degenerate case
+ return 0;
+ }
+ else
+ {
+ uint numChars = (uint)input.Length + 1U; // no overflow check necessary since Length is signed
+ return checked(numChars * sizeof(char));
+ }
+ }
+
+ private static BCryptKeyHandle PasswordToPbkdfKeyHandle(string password, BCryptAlgorithmHandle pbkdf2AlgHandle, KeyDerivationPrf prf)
+ {
+ byte dummy; // CLR doesn't like pinning zero-length buffers, so this provides a valid memory address when working with zero-length buffers
+
+ // Convert password string to bytes.
+ // Allocate on the stack whenever we can to save allocations.
+ int cbPasswordBuffer = Encoding.UTF8.GetMaxByteCount(password.Length);
+ fixed (byte* pbHeapAllocatedPasswordBuffer = (cbPasswordBuffer > Constants.MAX_STACKALLOC_BYTES) ? new byte[cbPasswordBuffer] : null)
+ {
+ byte* pbPasswordBuffer = pbHeapAllocatedPasswordBuffer;
+ if (pbPasswordBuffer == null)
+ {
+ if (cbPasswordBuffer == 0)
+ {
+ pbPasswordBuffer = &dummy;
+ }
+ else
+ {
+ byte* pbStackAllocPasswordBuffer = stackalloc byte[cbPasswordBuffer]; // will be released when the frame unwinds
+ pbPasswordBuffer = pbStackAllocPasswordBuffer;
+ }
+ }
+
+ try
+ {
+ int cbPasswordBufferUsed; // we're not filling the entire buffer, just a partial buffer
+ fixed (char* pszPassword = password)
+ {
+ cbPasswordBufferUsed = Encoding.UTF8.GetBytes(pszPassword, password.Length, pbPasswordBuffer, cbPasswordBuffer);
+ }
+
+ return PasswordToPbkdfKeyHandleStep2(pbkdf2AlgHandle, pbPasswordBuffer, (uint)cbPasswordBufferUsed, prf);
+ }
+ finally
+ {
+ UnsafeBufferUtil.SecureZeroMemory(pbPasswordBuffer, cbPasswordBuffer);
+ }
+ }
+ }
+
+ private static BCryptKeyHandle PasswordToPbkdfKeyHandleStep2(BCryptAlgorithmHandle pbkdf2AlgHandle, byte* pbPassword, uint cbPassword, KeyDerivationPrf prf)
+ {
+ const uint PBKDF2_MAX_KEYLENGTH_IN_BYTES = 2048; // GetSupportedKeyLengths() on a Win8 box; value should never be lowered in any future version of Windows
+ if (cbPassword <= PBKDF2_MAX_KEYLENGTH_IN_BYTES)
+ {
+ // Common case: the password is small enough to be consumed directly by the PBKDF2 algorithm.
+ return pbkdf2AlgHandle.GenerateSymmetricKey(pbPassword, cbPassword);
+ }
+ else
+ {
+ // Rare case: password is very long; we must hash manually.
+ // PBKDF2 uses the PRFs in HMAC mode, and when the HMAC input key exceeds the hash function's
+ // block length the key is hashed and run back through the key initialization function.
+
+ BCryptAlgorithmHandle prfAlgorithmHandle; // cached; don't dispose
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ prfAlgorithmHandle = CachedAlgorithmHandles.SHA1;
+ break;
+ case KeyDerivationPrf.HMACSHA256:
+ prfAlgorithmHandle = CachedAlgorithmHandles.SHA256;
+ break;
+ case KeyDerivationPrf.HMACSHA512:
+ prfAlgorithmHandle = CachedAlgorithmHandles.SHA512;
+ break;
+ default:
+ throw CryptoUtil.Fail("Unrecognized PRF.");
+ }
+
+ // Final sanity check: don't hash the password if the HMAC key initialization function wouldn't have done it for us.
+ if (cbPassword <= prfAlgorithmHandle.GetHashBlockLength() /* in bytes */)
+ {
+ return pbkdf2AlgHandle.GenerateSymmetricKey(pbPassword, cbPassword);
+ }
+
+ // Hash the password and use the hash as input to PBKDF2.
+ uint cbPasswordDigest = prfAlgorithmHandle.GetHashDigestLength();
+ CryptoUtil.Assert(cbPasswordDigest > 0, "cbPasswordDigest > 0");
+ fixed (byte* pbPasswordDigest = new byte[cbPasswordDigest])
+ {
+ try
+ {
+ using (var hashHandle = prfAlgorithmHandle.CreateHash())
+ {
+ hashHandle.HashData(pbPassword, cbPassword, pbPasswordDigest, cbPasswordDigest);
+ }
+ return pbkdf2AlgHandle.GenerateSymmetricKey(pbPasswordDigest, cbPasswordDigest);
+ }
+ finally
+ {
+ UnsafeBufferUtil.SecureZeroMemory(pbPasswordDigest, cbPasswordDigest);
+ }
+ }
+ }
+ }
+
+ private static void DeriveKeyCore(BCryptKeyHandle pbkdf2KeyHandle, string hashAlgorithm, byte* pbSalt, uint cbSalt, ulong iterCount, byte* pbDerivedBytes, uint cbDerivedBytes)
+ {
+ // First, build the buffers necessary to pass (hash alg, salt, iter count) into the KDF
+ BCryptBuffer* pBuffers = stackalloc BCryptBuffer[3];
+
+ pBuffers[0].BufferType = BCryptKeyDerivationBufferType.KDF_ITERATION_COUNT;
+ pBuffers[0].pvBuffer = (IntPtr)(&iterCount);
+ pBuffers[0].cbBuffer = sizeof(ulong);
+
+ pBuffers[1].BufferType = BCryptKeyDerivationBufferType.KDF_SALT;
+ pBuffers[1].pvBuffer = (IntPtr)pbSalt;
+ pBuffers[1].cbBuffer = cbSalt;
+
+ fixed (char* pszHashAlgorithm = hashAlgorithm)
+ {
+ pBuffers[2].BufferType = BCryptKeyDerivationBufferType.KDF_HASH_ALGORITHM;
+ pBuffers[2].pvBuffer = (IntPtr)pszHashAlgorithm;
+ pBuffers[2].cbBuffer = GetTotalByteLengthIncludingNullTerminator(hashAlgorithm);
+
+ // Add the header which points to the buffers
+ BCryptBufferDesc bufferDesc = default(BCryptBufferDesc);
+ BCryptBufferDesc.Initialize(ref bufferDesc);
+ bufferDesc.cBuffers = 3;
+ bufferDesc.pBuffers = pBuffers;
+
+ // Finally, import the KDK into the KDF algorithm, then invoke the KDF
+ uint numBytesDerived;
+ int ntstatus = UnsafeNativeMethods.BCryptKeyDerivation(
+ hKey: pbkdf2KeyHandle,
+ pParameterList: &bufferDesc,
+ pbDerivedKey: pbDerivedBytes,
+ cbDerivedKey: cbDerivedBytes,
+ pcbResult: out numBytesDerived,
+ dwFlags: 0);
+ UnsafeNativeMethods.ThrowExceptionForBCryptStatus(ntstatus);
+
+ // Final sanity checks before returning control to caller.
+ CryptoUtil.Assert(numBytesDerived == cbDerivedBytes, "numBytesDerived == cbDerivedBytes");
+ }
+ }
+
+ private static string PrfToCngAlgorithmId(KeyDerivationPrf prf)
+ {
+ switch (prf)
+ {
+ case KeyDerivationPrf.HMACSHA1:
+ return Constants.BCRYPT_SHA1_ALGORITHM;
+ case KeyDerivationPrf.HMACSHA256:
+ return Constants.BCRYPT_SHA256_ALGORITHM;
+ case KeyDerivationPrf.HMACSHA512:
+ return Constants.BCRYPT_SHA512_ALGORITHM;
+ default:
+ throw CryptoUtil.Fail("Unrecognized PRF.");
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..2ca6553c5d
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cryptography.KeyDerivation.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/baseline.netcore.json
new file mode 100644
index 0000000000..ceddb40cc2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/baseline.netcore.json
@@ -0,0 +1,78 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.Cryptography.KeyDerivation, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivation",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Pbkdf2",
+ "Parameters": [
+ {
+ "Name": "password",
+ "Type": "System.String"
+ },
+ {
+ "Name": "salt",
+ "Type": "System.Byte[]"
+ },
+ {
+ "Name": "prf",
+ "Type": "Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivationPrf"
+ },
+ {
+ "Name": "iterationCount",
+ "Type": "System.Int32"
+ },
+ {
+ "Name": "numBytesRequested",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivationPrf",
+ "Visibility": "Public",
+ "Kind": "Enumeration",
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Field",
+ "Name": "HMACSHA1",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "0"
+ },
+ {
+ "Kind": "Field",
+ "Name": "HMACSHA256",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "1"
+ },
+ {
+ "Kind": "Field",
+ "Name": "HMACSHA512",
+ "Parameters": [],
+ "GenericParameter": [],
+ "Literal": "2"
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/CryptoUtil.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/CryptoUtil.cs
new file mode 100644
index 0000000000..e3e361a3a8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/CryptoUtil.cs
@@ -0,0 +1,33 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ internal static class CryptoUtil
+ {
+ // This isn't a typical Debug.Fail; an error always occurs, even in retail builds.
+ // This method doesn't return, but since the CLR doesn't allow specifying a 'never'
+ // return type, we mimic it by specifying our return type as Exception. That way
+ // callers can write 'throw Fail(...);' to make the C# compiler happy, as the
+ // throw keyword is implicitly of type O.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static Exception Fail(string message)
+ {
+ Debug.Fail(message);
+ throw new CryptographicException("Assertion failed: " + message);
+ }
+
+ // Allows callers to write "var x = Method() ?? Fail(message);" as a convenience to guard
+ // against a method returning null unexpectedly.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static T Fail(string message) where T : class
+ {
+ throw Fail(message);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/DataProtectionCommonExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/DataProtectionCommonExtensions.cs
new file mode 100644
index 0000000000..f4fd8801ae
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/DataProtectionCommonExtensions.cs
@@ -0,0 +1,244 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Microsoft.AspNetCore.DataProtection.Abstractions;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Helpful extension methods for data protection APIs.
+ ///
+ public static class DataProtectionCommonExtensions
+ {
+ ///
+ /// Creates an given a list of purposes.
+ ///
+ /// The from which to generate the purpose chain.
+ /// The list of purposes which contribute to the purpose chain. This list must
+ /// contain at least one element, and it may not contain null elements.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which chains together several calls to
+ /// . See that method's
+ /// documentation for more information.
+ ///
+ public static IDataProtector CreateProtector(this IDataProtectionProvider provider, IEnumerable purposes)
+ {
+ if (provider == null)
+ {
+ throw new ArgumentNullException(nameof(provider));
+ }
+
+ if (purposes == null)
+ {
+ throw new ArgumentNullException(nameof(purposes));
+ }
+
+ bool collectionIsEmpty = true;
+ IDataProtectionProvider retVal = provider;
+ foreach (string purpose in purposes)
+ {
+ if (purpose == null)
+ {
+ throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesCollection, nameof(purposes));
+ }
+ retVal = retVal.CreateProtector(purpose) ?? CryptoUtil.Fail("CreateProtector returned null.");
+ collectionIsEmpty = false;
+ }
+
+ if (collectionIsEmpty)
+ {
+ throw new ArgumentException(Resources.DataProtectionExtensions_NullPurposesCollection, nameof(purposes));
+ }
+
+ Debug.Assert(retVal is IDataProtector); // CreateProtector is supposed to return an instance of this interface
+ return (IDataProtector)retVal;
+ }
+
+ ///
+ /// Creates an given a list of purposes.
+ ///
+ /// The from which to generate the purpose chain.
+ /// The primary purpose used to create the .
+ /// An optional list of secondary purposes which contribute to the purpose chain.
+ /// If this list is provided it cannot contain null elements.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which chains together several calls to
+ /// . See that method's
+ /// documentation for more information.
+ ///
+ public static IDataProtector CreateProtector(this IDataProtectionProvider provider, string purpose, params string[] subPurposes)
+ {
+ if (provider == null)
+ {
+ throw new ArgumentNullException(nameof(provider));
+ }
+
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ // The method signature isn't simply CreateProtector(this IDataProtectionProvider, params string[] purposes)
+ // because we don't want the code provider.CreateProtector() [parameterless] to inadvertently compile.
+ // The actual signature for this method forces at least one purpose to be provided at the call site.
+
+ IDataProtector protector = provider.CreateProtector(purpose);
+ if (subPurposes != null && subPurposes.Length > 0)
+ {
+ protector = protector?.CreateProtector((IEnumerable)subPurposes);
+ }
+ return protector ?? CryptoUtil.Fail("CreateProtector returned null.");
+ }
+
+ ///
+ /// Retrieves an from an .
+ ///
+ /// The service provider from which to retrieve the .
+ /// An . This method is guaranteed never to return null.
+ /// If no service exists in .
+ public static IDataProtectionProvider GetDataProtectionProvider(this IServiceProvider services)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ // We have our own implementation of GetRequiredService since we don't want to
+ // take a dependency on DependencyInjection.Interfaces.
+ IDataProtectionProvider provider = (IDataProtectionProvider)services.GetService(typeof(IDataProtectionProvider));
+ if (provider == null)
+ {
+ throw new InvalidOperationException(Resources.FormatDataProtectionExtensions_NoService(typeof(IDataProtectionProvider).FullName));
+ }
+ return provider;
+ }
+
+ ///
+ /// Retrieves an from an given a list of purposes.
+ ///
+ /// An which contains the
+ /// from which to generate the purpose chain.
+ /// The list of purposes which contribute to the purpose chain. This list must
+ /// contain at least one element, and it may not contain null elements.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which calls
+ /// then . See those methods'
+ /// documentation for more information.
+ ///
+ public static IDataProtector GetDataProtector(this IServiceProvider services, IEnumerable purposes)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (purposes == null)
+ {
+ throw new ArgumentNullException(nameof(purposes));
+ }
+
+ return services.GetDataProtectionProvider().CreateProtector(purposes);
+ }
+
+ ///
+ /// Retrieves an from an given a list of purposes.
+ ///
+ /// An which contains the
+ /// from which to generate the purpose chain.
+ /// The primary purpose used to create the .
+ /// An optional list of secondary purposes which contribute to the purpose chain.
+ /// If this list is provided it cannot contain null elements.
+ /// An tied to the provided purpose chain.
+ ///
+ /// This is a convenience method which calls
+ /// then . See those methods'
+ /// documentation for more information.
+ ///
+ public static IDataProtector GetDataProtector(this IServiceProvider services, string purpose, params string[] subPurposes)
+ {
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ return services.GetDataProtectionProvider().CreateProtector(purpose, subPurposes);
+ }
+
+ ///
+ /// Cryptographically protects a piece of plaintext data.
+ ///
+ /// The data protector to use for this operation.
+ /// The plaintext data to protect.
+ /// The protected form of the plaintext data.
+ public static string Protect(this IDataProtector protector, string plaintext)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ try
+ {
+ byte[] plaintextAsBytes = EncodingUtil.SecureUtf8Encoding.GetBytes(plaintext);
+ byte[] protectedDataAsBytes = protector.Protect(plaintextAsBytes);
+ return WebEncoders.Base64UrlEncode(protectedDataAsBytes);
+ }
+ catch (Exception ex) when (ex.RequiresHomogenization())
+ {
+ // Homogenize exceptions to CryptographicException
+ throw Error.CryptCommon_GenericError(ex);
+ }
+ }
+
+ ///
+ /// Cryptographically unprotects a piece of protected data.
+ ///
+ /// The data protector to use for this operation.
+ /// The protected data to unprotect.
+ /// The plaintext form of the protected data.
+ ///
+ /// Thrown if is invalid or malformed.
+ ///
+ public static string Unprotect(this IDataProtector protector, string protectedData)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ try
+ {
+ byte[] protectedDataAsBytes = WebEncoders.Base64UrlDecode(protectedData);
+ byte[] plaintextAsBytes = protector.Unprotect(protectedDataAsBytes);
+ return EncodingUtil.SecureUtf8Encoding.GetString(plaintextAsBytes);
+ }
+ catch (Exception ex) when (ex.RequiresHomogenization())
+ {
+ // Homogenize exceptions to CryptographicException
+ throw Error.CryptCommon_GenericError(ex);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Error.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Error.cs
new file mode 100644
index 0000000000..18b93c0ac7
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Error.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.DataProtection.Abstractions;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ internal static class Error
+ {
+ public static CryptographicException CryptCommon_GenericError(Exception inner = null)
+ {
+ return new CryptographicException(Resources.CryptCommon_GenericError, inner);
+ }
+
+ public static CryptographicException CryptCommon_PayloadInvalid()
+ {
+ string message = Resources.CryptCommon_PayloadInvalid;
+ return new CryptographicException(message);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtectionProvider.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtectionProvider.cs
new file mode 100644
index 0000000000..02f772724b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtectionProvider.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// An interface that can be used to create instances.
+ ///
+ public interface IDataProtectionProvider
+ {
+ ///
+ /// Creates an given a purpose.
+ ///
+ ///
+ /// The purpose to be assigned to the newly-created .
+ ///
+ /// An IDataProtector tied to the provided purpose.
+ ///
+ /// The parameter must be unique for the intended use case; two
+ /// different instances created with two different
+ /// values will not be able to decipher each other's payloads. The parameter
+ /// value is not intended to be kept secret.
+ ///
+ IDataProtector CreateProtector(string purpose);
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtector.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtector.cs
new file mode 100644
index 0000000000..1d9c8c3946
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/IDataProtector.cs
@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// An interface that can provide data protection services.
+ ///
+ public interface IDataProtector : IDataProtectionProvider
+ {
+ ///
+ /// Cryptographically protects a piece of plaintext data.
+ ///
+ /// The plaintext data to protect.
+ /// The protected form of the plaintext data.
+ byte[] Protect(byte[] plaintext);
+
+ ///
+ /// Cryptographically unprotects a piece of protected data.
+ ///
+ /// The protected data to unprotect.
+ /// The plaintext form of the protected data.
+ ///
+ /// Thrown if the protected data is invalid or malformed.
+ ///
+ byte[] Unprotect(byte[] protectedData);
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Infrastructure/IApplicationDiscriminator.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Infrastructure/IApplicationDiscriminator.cs
new file mode 100644
index 0000000000..d8c3af376f
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Infrastructure/IApplicationDiscriminator.cs
@@ -0,0 +1,25 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel;
+
+namespace Microsoft.AspNetCore.DataProtection.Infrastructure
+{
+ ///
+ /// Provides information used to discriminate applications.
+ ///
+ ///
+ /// This type supports the data protection system and is not intended to be used
+ /// by consumers.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public interface IApplicationDiscriminator
+ {
+ ///
+ /// An identifier that uniquely discriminates this application from all other
+ /// applications on the machine.
+ ///
+ string Discriminator { get; }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Microsoft.AspNetCore.DataProtection.Abstractions.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Microsoft.AspNetCore.DataProtection.Abstractions.csproj
new file mode 100644
index 0000000000..24bd9f5fb6
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Microsoft.AspNetCore.DataProtection.Abstractions.csproj
@@ -0,0 +1,21 @@
+
+
+
+ ASP.NET Core data protection abstractions.
+Commonly used types:
+Microsoft.AspNetCore.DataProtection.IDataProtectionProvider
+Microsoft.AspNetCore.DataProtection.IDataProtector
+ netstandard2.0
+ true
+ aspnetcore;dataprotection
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..838462a81d
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+// for unit testing
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..7f8422cf6b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Properties/Resources.Designer.cs
@@ -0,0 +1,86 @@
+//
+namespace Microsoft.AspNetCore.DataProtection.Abstractions
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.DataProtection.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// The payload was invalid.
+ ///
+ internal static string CryptCommon_PayloadInvalid
+ {
+ get => GetString("CryptCommon_PayloadInvalid");
+ }
+
+ ///
+ /// The payload was invalid.
+ ///
+ internal static string FormatCryptCommon_PayloadInvalid()
+ => GetString("CryptCommon_PayloadInvalid");
+
+ ///
+ /// The purposes collection cannot be null or empty and cannot contain null elements.
+ ///
+ internal static string DataProtectionExtensions_NullPurposesCollection
+ {
+ get => GetString("DataProtectionExtensions_NullPurposesCollection");
+ }
+
+ ///
+ /// The purposes collection cannot be null or empty and cannot contain null elements.
+ ///
+ internal static string FormatDataProtectionExtensions_NullPurposesCollection()
+ => GetString("DataProtectionExtensions_NullPurposesCollection");
+
+ ///
+ /// An error occurred during a cryptographic operation.
+ ///
+ internal static string CryptCommon_GenericError
+ {
+ get => GetString("CryptCommon_GenericError");
+ }
+
+ ///
+ /// An error occurred during a cryptographic operation.
+ ///
+ internal static string FormatCryptCommon_GenericError()
+ => GetString("CryptCommon_GenericError");
+
+ ///
+ /// No service for type '{0}' has been registered.
+ ///
+ internal static string DataProtectionExtensions_NoService
+ {
+ get => GetString("DataProtectionExtensions_NoService");
+ }
+
+ ///
+ /// No service for type '{0}' has been registered.
+ ///
+ internal static string FormatDataProtectionExtensions_NoService(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("DataProtectionExtensions_NoService"), p0);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Resources.resx b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Resources.resx
new file mode 100644
index 0000000000..daa9e2cbd9
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/Resources.resx
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The payload was invalid.
+
+
+ The purposes collection cannot be null or empty and cannot contain null elements.
+
+
+ An error occurred during a cryptographic operation.
+
+
+ No service for type '{0}' has been registered.
+
+
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/baseline.netcore.json
new file mode 100644
index 0000000000..eb6e5030fe
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Abstractions/baseline.netcore.json
@@ -0,0 +1,231 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.DataProtection.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.DataProtectionCommonExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "CreateProtector",
+ "Parameters": [
+ {
+ "Name": "provider",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider"
+ },
+ {
+ "Name": "purposes",
+ "Type": "System.Collections.Generic.IEnumerable"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "CreateProtector",
+ "Parameters": [
+ {
+ "Name": "provider",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider"
+ },
+ {
+ "Name": "purpose",
+ "Type": "System.String"
+ },
+ {
+ "Name": "subPurposes",
+ "Type": "System.String[]",
+ "IsParams": true
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDataProtectionProvider",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "System.IServiceProvider"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDataProtector",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "System.IServiceProvider"
+ },
+ {
+ "Name": "purposes",
+ "Type": "System.Collections.Generic.IEnumerable"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "GetDataProtector",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "System.IServiceProvider"
+ },
+ {
+ "Name": "purpose",
+ "Type": "System.String"
+ },
+ {
+ "Name": "subPurposes",
+ "Type": "System.String[]",
+ "IsParams": true
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtector"
+ },
+ {
+ "Name": "plaintext",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Unprotect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtector"
+ },
+ {
+ "Name": "protectedData",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "CreateProtector",
+ "Parameters": [
+ {
+ "Name": "purpose",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.IDataProtector",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "plaintext",
+ "Type": "System.Byte[]"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Unprotect",
+ "Parameters": [
+ {
+ "Name": "protectedData",
+ "Type": "System.Byte[]"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Discriminator",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs
new file mode 100644
index 0000000000..0701220b4b
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureDataProtectionBuilderExtensions.cs
@@ -0,0 +1,118 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.DataProtection.AzureKeyVault;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Clients.ActiveDirectory;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Contains Azure KeyVault-specific extension methods for modifying a .
+ ///
+ public static class AzureDataProtectionBuilderExtensions
+ {
+ ///
+ /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+ ///
+ /// The builder instance to modify.
+ /// The Azure KeyVault key identifier used for key encryption.
+ /// The application client id.
+ ///
+ /// The value .
+ public static IDataProtectionBuilder ProtectKeysWithAzureKeyVault(this IDataProtectionBuilder builder, string keyIdentifier, string clientId, X509Certificate2 certificate)
+ {
+ if (string.IsNullOrEmpty(clientId))
+ {
+ throw new ArgumentException(nameof(clientId));
+ }
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ KeyVaultClient.AuthenticationCallback callback =
+ (authority, resource, scope) => GetTokenFromClientCertificate(authority, resource, clientId, certificate);
+
+ return ProtectKeysWithAzureKeyVault(builder, new KeyVaultClient(callback), keyIdentifier);
+ }
+
+ private static async Task GetTokenFromClientCertificate(string authority, string resource, string clientId, X509Certificate2 certificate)
+ {
+ var authContext = new AuthenticationContext(authority);
+ var result = await authContext.AcquireTokenAsync(resource, new ClientAssertionCertificate(clientId, certificate));
+ return result.AccessToken;
+ }
+
+ ///
+ /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+ ///
+ /// The builder instance to modify.
+ /// The Azure KeyVault key identifier used for key encryption.
+ /// The application client id.
+ /// The client secret to use for authentication.
+ /// The value .
+ public static IDataProtectionBuilder ProtectKeysWithAzureKeyVault(this IDataProtectionBuilder builder, string keyIdentifier, string clientId, string clientSecret)
+ {
+ if (string.IsNullOrEmpty(clientId))
+ {
+ throw new ArgumentNullException(nameof(clientId));
+ }
+ if (string.IsNullOrEmpty(clientSecret))
+ {
+ throw new ArgumentNullException(nameof(clientSecret));
+ }
+
+ KeyVaultClient.AuthenticationCallback callback =
+ (authority, resource, scope) => GetTokenFromClientSecret(authority, resource, clientId, clientSecret);
+
+ return ProtectKeysWithAzureKeyVault(builder, new KeyVaultClient(callback), keyIdentifier);
+ }
+
+ private static async Task GetTokenFromClientSecret(string authority, string resource, string clientId, string clientSecret)
+ {
+ var authContext = new AuthenticationContext(authority);
+ var clientCred = new ClientCredential(clientId, clientSecret);
+ var result = await authContext.AcquireTokenAsync(resource, clientCred);
+ return result.AccessToken;
+ }
+
+ ///
+ /// Configures the data protection system to protect keys with specified key in Azure KeyVault.
+ ///
+ /// The builder instance to modify.
+ /// The to use for KeyVault access.
+ /// The Azure KeyVault key identifier used for key encryption.
+ /// The value .
+ public static IDataProtectionBuilder ProtectKeysWithAzureKeyVault(this IDataProtectionBuilder builder, KeyVaultClient client, string keyIdentifier)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+ if (string.IsNullOrEmpty(keyIdentifier))
+ {
+ throw new ArgumentException(nameof(keyIdentifier));
+ }
+
+ var vaultClientWrapper = new KeyVaultClientWrapper(client);
+
+ builder.Services.AddSingleton(vaultClientWrapper);
+ builder.Services.Configure(options =>
+ {
+ options.XmlEncryptor = new AzureKeyVaultXmlEncryptor(vaultClientWrapper, keyIdentifier);
+ });
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.cs
new file mode 100644
index 0000000000..b9942fa84f
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlDecryptor.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.XmlEncryption;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal class AzureKeyVaultXmlDecryptor: IXmlDecryptor
+ {
+ private readonly IKeyVaultWrappingClient _client;
+
+ public AzureKeyVaultXmlDecryptor(IServiceProvider serviceProvider)
+ {
+ _client = serviceProvider.GetService();
+ }
+
+ public XElement Decrypt(XElement encryptedElement)
+ {
+ return DecryptAsync(encryptedElement).GetAwaiter().GetResult();
+ }
+
+ private async Task DecryptAsync(XElement encryptedElement)
+ {
+ var kid = (string)encryptedElement.Element("kid");
+ var symmetricKey = Convert.FromBase64String((string)encryptedElement.Element("key"));
+ var symmetricIV = Convert.FromBase64String((string)encryptedElement.Element("iv"));
+
+ var encryptedValue = Convert.FromBase64String((string)encryptedElement.Element("value"));
+
+ var result = await _client.UnwrapKeyAsync(kid, AzureKeyVaultXmlEncryptor.DefaultKeyEncryption, symmetricKey);
+
+ byte[] decryptedValue;
+ using (var symmetricAlgorithm = AzureKeyVaultXmlEncryptor.DefaultSymmetricAlgorithmFactory())
+ {
+ using (var decryptor = symmetricAlgorithm.CreateDecryptor(result.Result, symmetricIV))
+ {
+ decryptedValue = decryptor.TransformFinalBlock(encryptedValue, 0, encryptedValue.Length);
+ }
+ }
+
+ using (var memoryStream = new MemoryStream(decryptedValue))
+ {
+ return XElement.Load(memoryStream);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs
new file mode 100644
index 0000000000..3451c3ded2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/AzureKeyVaultXmlEncryptor.cs
@@ -0,0 +1,77 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.XmlEncryption;
+using Microsoft.Azure.KeyVault.WebKey;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal class AzureKeyVaultXmlEncryptor : IXmlEncryptor
+ {
+ internal static string DefaultKeyEncryption = JsonWebKeyEncryptionAlgorithm.RSAOAEP;
+ internal static Func DefaultSymmetricAlgorithmFactory = Aes.Create;
+
+ private readonly RandomNumberGenerator _randomNumberGenerator;
+ private readonly IKeyVaultWrappingClient _client;
+ private readonly string _keyId;
+
+ public AzureKeyVaultXmlEncryptor(IKeyVaultWrappingClient client, string keyId)
+ : this(client, keyId, RandomNumberGenerator.Create())
+ {
+ }
+
+ internal AzureKeyVaultXmlEncryptor(IKeyVaultWrappingClient client, string keyId, RandomNumberGenerator randomNumberGenerator)
+ {
+ _client = client;
+ _keyId = keyId;
+ _randomNumberGenerator = randomNumberGenerator;
+ }
+
+ public EncryptedXmlInfo Encrypt(XElement plaintextElement)
+ {
+ return EncryptAsync(plaintextElement).GetAwaiter().GetResult();
+ }
+
+ private async Task EncryptAsync(XElement plaintextElement)
+ {
+ byte[] value;
+ using (var memoryStream = new MemoryStream())
+ {
+ plaintextElement.Save(memoryStream, SaveOptions.DisableFormatting);
+ value = memoryStream.ToArray();
+ }
+
+ using (var symmetricAlgorithm = DefaultSymmetricAlgorithmFactory())
+ {
+ var symmetricBlockSize = symmetricAlgorithm.BlockSize / 8;
+ var symmetricKey = new byte[symmetricBlockSize];
+ var symmetricIV = new byte[symmetricBlockSize];
+ _randomNumberGenerator.GetBytes(symmetricKey);
+ _randomNumberGenerator.GetBytes(symmetricIV);
+
+ byte[] encryptedValue;
+ using (var encryptor = symmetricAlgorithm.CreateEncryptor(symmetricKey, symmetricIV))
+ {
+ encryptedValue = encryptor.TransformFinalBlock(value, 0, value.Length);
+ }
+
+ var wrappedKey = await _client.WrapKeyAsync(_keyId, DefaultKeyEncryption, symmetricKey);
+
+ var element = new XElement("encryptedKey",
+ new XComment(" This key is encrypted with Azure KeyVault. "),
+ new XElement("kid", wrappedKey.Kid),
+ new XElement("key", Convert.ToBase64String(wrappedKey.Result)),
+ new XElement("iv", Convert.ToBase64String(symmetricIV)),
+ new XElement("value", Convert.ToBase64String(encryptedValue)));
+
+ return new EncryptedXmlInfo(element, typeof(AzureKeyVaultXmlDecryptor));
+ }
+
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs
new file mode 100644
index 0000000000..2347460dc3
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/IKeyVaultWrappingClient.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.Azure.KeyVault.Models;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal interface IKeyVaultWrappingClient
+ {
+ Task UnwrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText);
+ Task WrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText);
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs
new file mode 100644
index 0000000000..82fe0649e2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/KeyVaultClientWrapper.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.Azure.KeyVault;
+using Microsoft.Azure.KeyVault.Models;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureKeyVault
+{
+ internal class KeyVaultClientWrapper : IKeyVaultWrappingClient
+ {
+ private readonly KeyVaultClient _client;
+
+ public KeyVaultClientWrapper(KeyVaultClient client)
+ {
+ _client = client;
+ }
+
+ public Task UnwrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText)
+ {
+ return _client.UnwrapKeyAsync(keyIdentifier, algorithm, cipherText);
+ }
+
+ public Task WrapKeyAsync(string keyIdentifier, string algorithm, byte[] cipherText)
+ {
+ return _client.WrapKeyAsync(keyIdentifier, algorithm, cipherText);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
new file mode 100644
index 0000000000..0c7b084a2a
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Microsoft.AspNetCore.DataProtection.AzureKeyVault.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Microsoft Azure KeyVault key encryption support.
+ netstandard2.0
+ true
+ aspnetcore;dataprotection;azure;keyvault
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..c23a3410b7
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureKeyVault/Properties/AssemblyInfo.cs
@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.AzureKeyVault.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureBlobXmlRepository.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureBlobXmlRepository.cs
new file mode 100644
index 0000000000..e39babaa31
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureBlobXmlRepository.cs
@@ -0,0 +1,297 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.Repositories;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Blob;
+
+namespace Microsoft.AspNetCore.DataProtection.AzureStorage
+{
+ ///
+ /// An which is backed by Azure Blob Storage.
+ ///
+ ///
+ /// Instances of this type are thread-safe.
+ ///
+ public sealed class AzureBlobXmlRepository : IXmlRepository
+ {
+ private const int ConflictMaxRetries = 5;
+ private static readonly TimeSpan ConflictBackoffPeriod = TimeSpan.FromMilliseconds(200);
+
+ private static readonly XName RepositoryElementName = "repository";
+
+ private readonly Func _blobRefFactory;
+ private readonly Random _random;
+ private BlobData _cachedBlobData;
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ /// A factory which can create
+ /// instances. The factory must be thread-safe for invocation by multiple
+ /// concurrent threads, and each invocation must return a new object.
+ public AzureBlobXmlRepository(Func blobRefFactory)
+ {
+ if (blobRefFactory == null)
+ {
+ throw new ArgumentNullException(nameof(blobRefFactory));
+ }
+
+ _blobRefFactory = blobRefFactory;
+ _random = new Random();
+ }
+
+ ///
+ public IReadOnlyCollection GetAllElements()
+ {
+ var blobRef = CreateFreshBlobRef();
+
+ // Shunt the work onto a ThreadPool thread so that it's independent of any
+ // existing sync context or other potentially deadlock-causing items.
+
+ var elements = Task.Run(() => GetAllElementsAsync(blobRef)).GetAwaiter().GetResult();
+ return new ReadOnlyCollection(elements);
+ }
+
+ ///
+ public void StoreElement(XElement element, string friendlyName)
+ {
+ if (element == null)
+ {
+ throw new ArgumentNullException(nameof(element));
+ }
+
+ var blobRef = CreateFreshBlobRef();
+
+ // Shunt the work onto a ThreadPool thread so that it's independent of any
+ // existing sync context or other potentially deadlock-causing items.
+
+ Task.Run(() => StoreElementAsync(blobRef, element)).GetAwaiter().GetResult();
+ }
+
+ private XDocument CreateDocumentFromBlob(byte[] blob)
+ {
+ using (var memoryStream = new MemoryStream(blob))
+ {
+ var xmlReaderSettings = new XmlReaderSettings()
+ {
+ DtdProcessing = DtdProcessing.Prohibit, IgnoreProcessingInstructions = true
+ };
+
+ using (var xmlReader = XmlReader.Create(memoryStream, xmlReaderSettings))
+ {
+ return XDocument.Load(xmlReader);
+ }
+ }
+ }
+
+ private ICloudBlob CreateFreshBlobRef()
+ {
+ // ICloudBlob instances aren't thread-safe, so we need to make sure we're working
+ // with a fresh instance that won't be mutated by another thread.
+
+ var blobRef = _blobRefFactory();
+ if (blobRef == null)
+ {
+ throw new InvalidOperationException("The ICloudBlob factory method returned null.");
+ }
+
+ return blobRef;
+ }
+
+ private async Task> GetAllElementsAsync(ICloudBlob blobRef)
+ {
+ var data = await GetLatestDataAsync(blobRef);
+
+ if (data == null)
+ {
+ // no data in blob storage
+ return new XElement[0];
+ }
+
+ // The document will look like this:
+ //
+ //
+ //
+ //
+ // ...
+ //
+ //
+ // We want to return the first-level child elements to our caller.
+
+ var doc = CreateDocumentFromBlob(data.BlobContents);
+ return doc.Root.Elements().ToList();
+ }
+
+ private async Task GetLatestDataAsync(ICloudBlob blobRef)
+ {
+ // Set the appropriate AccessCondition based on what we believe the latest
+ // file contents to be, then make the request.
+
+ var latestCachedData = Volatile.Read(ref _cachedBlobData); // local ref so field isn't mutated under our feet
+ var accessCondition = (latestCachedData != null)
+ ? AccessCondition.GenerateIfNoneMatchCondition(latestCachedData.ETag)
+ : null;
+
+ try
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ await blobRef.DownloadToStreamAsync(
+ target: memoryStream,
+ accessCondition: accessCondition,
+ options: null,
+ operationContext: null);
+
+ // At this point, our original cache either didn't exist or was outdated.
+ // We'll update it now and return the updated value;
+
+ latestCachedData = new BlobData()
+ {
+ BlobContents = memoryStream.ToArray(),
+ ETag = blobRef.Properties.ETag
+ };
+
+ }
+ Volatile.Write(ref _cachedBlobData, latestCachedData);
+ }
+ catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 304)
+ {
+ // 304 Not Modified
+ // Thrown when we already have the latest cached data.
+ // This isn't an error; we'll return our cached copy of the data.
+ }
+ catch (StorageException ex) when (ex.RequestInformation.HttpStatusCode == 404)
+ {
+ // 404 Not Found
+ // Thrown when no file exists in storage.
+ // This isn't an error; we'll delete our cached copy of data.
+
+ latestCachedData = null;
+ Volatile.Write(ref _cachedBlobData, latestCachedData);
+ }
+
+ return latestCachedData;
+ }
+
+ private int GetRandomizedBackoffPeriod()
+ {
+ // returns a TimeSpan in the range [0.8, 1.0) * ConflictBackoffPeriod
+ // not used for crypto purposes
+ var multiplier = 0.8 + (_random.NextDouble() * 0.2);
+ return (int) (multiplier * ConflictBackoffPeriod.Ticks);
+ }
+
+ private async Task StoreElementAsync(ICloudBlob blobRef, XElement element)
+ {
+ // holds the last error in case we need to rethrow it
+ ExceptionDispatchInfo lastError = null;
+
+ for (var i = 0; i < ConflictMaxRetries; i++)
+ {
+ if (i > 1)
+ {
+ // If multiple conflicts occurred, wait a small period of time before retrying
+ // the operation so that other writers can make forward progress.
+ await Task.Delay(GetRandomizedBackoffPeriod());
+ }
+
+ if (i > 0)
+ {
+ // If at least one conflict occurred, make sure we have an up-to-date
+ // view of the blob contents.
+ await GetLatestDataAsync(blobRef);
+ }
+
+ // Merge the new element into the document. If no document exists,
+ // create a new default document and inject this element into it.
+
+ var latestData = Volatile.Read(ref _cachedBlobData);
+ var doc = (latestData != null)
+ ? CreateDocumentFromBlob(latestData.BlobContents)
+ : new XDocument(new XElement(RepositoryElementName));
+ doc.Root.Add(element);
+
+ // Turn this document back into a byte[].
+
+ var serializedDoc = new MemoryStream();
+ doc.Save(serializedDoc, SaveOptions.DisableFormatting);
+
+ // Generate the appropriate precondition header based on whether or not
+ // we believe data already exists in storage.
+
+ AccessCondition accessCondition;
+ if (latestData != null)
+ {
+ accessCondition = AccessCondition.GenerateIfMatchCondition(blobRef.Properties.ETag);
+ }
+ else
+ {
+ accessCondition = AccessCondition.GenerateIfNotExistsCondition();
+ blobRef.Properties.ContentType = "application/xml; charset=utf-8"; // set content type on first write
+ }
+
+ try
+ {
+ // Send the request up to the server.
+
+ var serializedDocAsByteArray = serializedDoc.ToArray();
+
+ await blobRef.UploadFromByteArrayAsync(
+ buffer: serializedDocAsByteArray,
+ index: 0,
+ count: serializedDocAsByteArray.Length,
+ accessCondition: accessCondition,
+ options: null,
+ operationContext: null);
+
+ // If we got this far, success!
+ // We can update the cached view of the remote contents.
+
+ Volatile.Write(ref _cachedBlobData, new BlobData()
+ {
+ BlobContents = serializedDocAsByteArray,
+ ETag = blobRef.Properties.ETag // was updated by Upload routine
+ });
+
+ return;
+ }
+ catch (StorageException ex)
+ when (ex.RequestInformation.HttpStatusCode == 409 || ex.RequestInformation.HttpStatusCode == 412)
+ {
+ // 409 Conflict
+ // This error is rare but can be thrown in very special circumstances,
+ // such as if the blob in the process of being created. We treat it
+ // as equivalent to 412 for the purposes of retry logic.
+
+ // 412 Precondition Failed
+ // We'll get this error if another writer updated the repository and we
+ // have an outdated view of its contents. If this occurs, we'll just
+ // refresh our view of the remote contents and try again up to the max
+ // retry limit.
+
+ lastError = ExceptionDispatchInfo.Capture(ex);
+ }
+ }
+
+ // if we got this far, something went awry
+ lastError.Throw();
+ }
+
+ private sealed class BlobData
+ {
+ internal byte[] BlobContents;
+ internal string ETag;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureDataProtectionBuilderExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureDataProtectionBuilderExtensions.cs
new file mode 100644
index 0000000000..8ff62929e2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/AzureDataProtectionBuilderExtensions.cs
@@ -0,0 +1,175 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.DataProtection.AzureStorage;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Auth;
+using Microsoft.WindowsAzure.Storage.Blob;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Contains Azure-specific extension methods for modifying a
+ /// .
+ ///
+ public static class AzureDataProtectionBuilderExtensions
+ {
+ ///
+ /// Configures the data protection system to persist keys to the specified path
+ /// in Azure Blob Storage.
+ ///
+ /// The builder instance to modify.
+ /// The which
+ /// should be utilized.
+ /// A relative path where the key file should be
+ /// stored, generally specified as "/containerName/[subDir/]keys.xml".
+ /// The value .
+ ///
+ /// The container referenced by must already exist.
+ ///
+ public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, CloudStorageAccount storageAccount, string relativePath)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (storageAccount == null)
+ {
+ throw new ArgumentNullException(nameof(storageAccount));
+ }
+ if (relativePath == null)
+ {
+ throw new ArgumentNullException(nameof(relativePath));
+ }
+
+ // Simply concatenate the root storage endpoint with the relative path,
+ // which includes the container name and blob name.
+
+ var uriBuilder = new UriBuilder(storageAccount.BlobEndpoint);
+ uriBuilder.Path = uriBuilder.Path.TrimEnd('/') + "/" + relativePath.TrimStart('/');
+
+ // We can create a CloudBlockBlob from the storage URI and the creds.
+
+ var blobAbsoluteUri = uriBuilder.Uri;
+ var credentials = storageAccount.Credentials;
+
+ return PersistKeystoAzureBlobStorageInternal(builder, () => new CloudBlockBlob(blobAbsoluteUri, credentials));
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the specified path
+ /// in Azure Blob Storage.
+ ///
+ /// The builder instance to modify.
+ /// The full URI where the key file should be stored.
+ /// The URI must contain the SAS token as a query string parameter.
+ /// The value .
+ ///
+ /// The container referenced by must already exist.
+ ///
+ public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, Uri blobUri)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (blobUri == null)
+ {
+ throw new ArgumentNullException(nameof(blobUri));
+ }
+
+ var uriBuilder = new UriBuilder(blobUri);
+
+ // The SAS token is present in the query string.
+
+ if (string.IsNullOrEmpty(uriBuilder.Query))
+ {
+ throw new ArgumentException(
+ message: "URI does not have a SAS token in the query string.",
+ paramName: nameof(blobUri));
+ }
+
+ var credentials = new StorageCredentials(uriBuilder.Query);
+ uriBuilder.Query = null; // no longer needed
+ var blobAbsoluteUri = uriBuilder.Uri;
+
+ return PersistKeystoAzureBlobStorageInternal(builder, () => new CloudBlockBlob(blobAbsoluteUri, credentials));
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the specified path
+ /// in Azure Blob Storage.
+ ///
+ /// The builder instance to modify.
+ /// The where the
+ /// key file should be stored.
+ /// The value .
+ ///
+ /// The container referenced by must already exist.
+ ///
+ public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, CloudBlockBlob blobReference)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (blobReference == null)
+ {
+ throw new ArgumentNullException(nameof(blobReference));
+ }
+
+ // We're basically just going to make a copy of this blob.
+ // Use (container, blobName) instead of (storageuri, creds) since the container
+ // is tied to an existing service client, which contains user-settable defaults
+ // like retry policy and secondary connection URIs.
+
+ var container = blobReference.Container;
+ var blobName = blobReference.Name;
+
+ return PersistKeystoAzureBlobStorageInternal(builder, () => container.GetBlockBlobReference(blobName));
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the specified path
+ /// in Azure Blob Storage.
+ ///
+ /// The builder instance to modify.
+ /// The in which the
+ /// key file should be stored.
+ /// The name of the key file, generally specified
+ /// as "[subdir/]keys.xml"
+ /// The value .
+ ///
+ /// The container referenced by must already exist.
+ ///
+ public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, CloudBlobContainer container, string blobName)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (container == null)
+ {
+ throw new ArgumentNullException(nameof(container));
+ }
+ if (blobName == null)
+ {
+ throw new ArgumentNullException(nameof(blobName));
+ }
+ return PersistKeystoAzureBlobStorageInternal(builder, () => container.GetBlockBlobReference(blobName));
+ }
+
+ // important: the Func passed into this method must return a new instance with each call
+ private static IDataProtectionBuilder PersistKeystoAzureBlobStorageInternal(IDataProtectionBuilder builder, Func blobRefFactory)
+ {
+ builder.Services.Configure(options =>
+ {
+ options.XmlRepository = new AzureBlobXmlRepository(blobRefFactory);
+ });
+ return builder;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj
new file mode 100644
index 0000000000..ceb83f3925
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/Microsoft.AspNetCore.DataProtection.AzureStorage.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Microsoft Azure Blob storrage support as key store.
+ netstandard2.0
+ true
+ true
+ aspnetcore;dataprotection;azure;blob
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/baseline.netcore.json
new file mode 100644
index 0000000000..09e208bfef
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.AzureStorage/baseline.netcore.json
@@ -0,0 +1,156 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.DataProtection.AzureStorage, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.AzureDataProtectionBuilderExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "PersistKeysToAzureBlobStorage",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder"
+ },
+ {
+ "Name": "storageAccount",
+ "Type": "Microsoft.WindowsAzure.Storage.CloudStorageAccount"
+ },
+ {
+ "Name": "relativePath",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "PersistKeysToAzureBlobStorage",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder"
+ },
+ {
+ "Name": "blobUri",
+ "Type": "System.Uri"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "PersistKeysToAzureBlobStorage",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder"
+ },
+ {
+ "Name": "blobReference",
+ "Type": "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "PersistKeysToAzureBlobStorage",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder"
+ },
+ {
+ "Name": "container",
+ "Type": "Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer"
+ },
+ {
+ "Name": "blobName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.AzureStorage.AzureBlobXmlRepository",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Sealed": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetAllElements",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IReadOnlyCollection",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "StoreElement",
+ "Parameters": [
+ {
+ "Name": "element",
+ "Type": "System.Xml.Linq.XElement"
+ },
+ {
+ "Name": "friendlyName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "blobRefFactory",
+ "Type": "System.Func"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs
new file mode 100644
index 0000000000..c236d5cb89
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/DataProtectionKey.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore
+{
+ ///
+ /// Code first model used by .
+ ///
+ public class DataProtectionKey
+ {
+ ///
+ /// The entity identifier of the .
+ ///
+ public int Id { get; set; }
+
+ ///
+ /// The friendly name of the .
+ ///
+ public string FriendlyName { get; set; }
+
+ ///
+ /// The XML representation of the .
+ ///
+ public string Xml { get; set; }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs
new file mode 100644
index 0000000000..ff24b58eb9
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreDataProtectionExtensions.cs
@@ -0,0 +1,39 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Extension method class for configuring instances of
+ ///
+ public static class EntityFrameworkCoreDataProtectionExtensions
+ {
+ ///
+ /// Configures the data protection system to persist keys to an EntityFrameworkCore datastore
+ ///
+ /// The instance to modify.
+ /// The value .
+ public static IDataProtectionBuilder PersistKeysToDbContext(this IDataProtectionBuilder builder)
+ where TContext : DbContext, IDataProtectionKeyContext
+ {
+ builder.Services.AddSingleton>(services =>
+ {
+ var loggerFactory = services.GetService() ?? NullLoggerFactory.Instance;
+ return new ConfigureOptions(options =>
+ {
+ options.XmlRepository = new EntityFrameworkCoreXmlRepository(services, loggerFactory);
+ });
+ });
+
+ return builder;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs
new file mode 100644
index 0000000000..62250cf3ef
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/EntityFrameworkCoreXmlRepository.cs
@@ -0,0 +1,81 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.DataProtection.Repositories;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore
+{
+ ///
+ /// An backed by an EntityFrameworkCore datastore.
+ ///
+ public class EntityFrameworkCoreXmlRepository : IXmlRepository
+ where TContext : DbContext, IDataProtectionKeyContext
+ {
+ private readonly IServiceProvider _services;
+ private readonly ILogger _logger;
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ ///
+ /// The .
+ public EntityFrameworkCoreXmlRepository(IServiceProvider services, ILoggerFactory loggerFactory)
+ {
+ if (loggerFactory == null)
+ {
+ throw new ArgumentNullException(nameof(loggerFactory));
+ }
+
+ _logger = loggerFactory.CreateLogger>();
+ _services = services ?? throw new ArgumentNullException(nameof(services));
+ }
+
+ ///
+ public virtual IReadOnlyCollection GetAllElements()
+ {
+ using (var scope = _services.CreateScope())
+ {
+ var context = scope.ServiceProvider.GetRequiredService();
+ return context.DataProtectionKeys.AsNoTracking().Select(key => TryParseKeyXml(key.Xml)).ToList().AsReadOnly();
+ }
+ }
+
+ ///
+ public void StoreElement(XElement element, string friendlyName)
+ {
+ using (var scope = _services.CreateScope())
+ {
+ var context = scope.ServiceProvider.GetRequiredService();
+ var newKey = new DataProtectionKey()
+ {
+ FriendlyName = friendlyName,
+ Xml = element.ToString(SaveOptions.DisableFormatting)
+ };
+
+ context.DataProtectionKeys.Add(newKey);
+ _logger.LogSavingKeyToDbContext(friendlyName, typeof(TContext).Name);
+ context.SaveChanges();
+ }
+ }
+
+ private XElement TryParseKeyXml(string xml)
+ {
+ try
+ {
+ return XElement.Parse(xml);
+ }
+ catch (Exception e)
+ {
+ _logger?.LogExceptionWhileParsingKeyXml(xml, e);
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/IDataProtectionKeyContext.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/IDataProtectionKeyContext.cs
new file mode 100644
index 0000000000..39998d2a79
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/IDataProtectionKeyContext.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.EntityFrameworkCore;
+
+namespace Microsoft.AspNetCore.DataProtection.EntityFrameworkCore
+{
+ ///
+ /// Interface used to store instances of in a
+ ///
+ public interface IDataProtectionKeyContext
+ {
+ ///
+ /// A collection of
+ ///
+ DbSet DataProtectionKeys { get; }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/LoggingExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/LoggingExtensions.cs
new file mode 100644
index 0000000000..d0aeb09271
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/LoggingExtensions.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.Extensions.Logging
+{
+ internal static class LoggingExtensions
+ {
+ private static readonly Action _anExceptionOccurredWhileParsingKeyXml;
+ private static readonly Action _savingKeyToDbContext;
+
+ static LoggingExtensions()
+ {
+ _anExceptionOccurredWhileParsingKeyXml = LoggerMessage.Define(
+ eventId: 1,
+ logLevel: LogLevel.Warning,
+ formatString: "An exception occurred while parsing the key xml '{Xml}'.");
+ _savingKeyToDbContext = LoggerMessage.Define(
+ eventId: 2,
+ logLevel: LogLevel.Debug,
+ formatString: "Saving key '{FriendlyName}' to '{DbContext}'.");
+ }
+
+ public static void LogExceptionWhileParsingKeyXml(this ILogger logger, string keyXml, Exception exception)
+ => _anExceptionOccurredWhileParsingKeyXml(logger, keyXml, exception);
+
+ public static void LogSavingKeyToDbContext(this ILogger logger, string friendlyName, string contextName)
+ => _savingKeyToDbContext(logger, friendlyName, contextName, null);
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj
new file mode 100644
index 0000000000..e1715d94f2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Support for storing keys using Entity Framework Core.
+ netstandard2.0
+ true
+ true
+ aspnetcore;dataprotection;entityframeworkcore
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/baseline.netcore.json
new file mode 100644
index 0000000000..9a9a7ebc1c
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/baseline.netcore.json
@@ -0,0 +1,203 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCoreDataProtectionExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "PersistKeysToDbContext",
+ "Parameters": [
+ {
+ "Name": "builder",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": [
+ {
+ "ParameterName": "TContext",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": [
+ "Microsoft.EntityFrameworkCore.DbContext",
+ "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.IDataProtectionKeyContext"
+ ]
+ }
+ ]
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_Id",
+ "Parameters": [],
+ "ReturnType": "System.Int32",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Id",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.Int32"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_FriendlyName",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_FriendlyName",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "get_Xml",
+ "Parameters": [],
+ "ReturnType": "System.String",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "set_Xml",
+ "Parameters": [
+ {
+ "Name": "value",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.EntityFrameworkCoreXmlRepository",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "GetAllElements",
+ "Parameters": [],
+ "ReturnType": "System.Collections.Generic.IReadOnlyCollection",
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "StoreElement",
+ "Parameters": [
+ {
+ "Name": "element",
+ "Type": "System.Xml.Linq.XElement"
+ },
+ {
+ "Name": "friendlyName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "System.Void",
+ "Sealed": true,
+ "Virtual": true,
+ "ImplementedInterface": "Microsoft.AspNetCore.DataProtection.Repositories.IXmlRepository",
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Constructor",
+ "Name": ".ctor",
+ "Parameters": [
+ {
+ "Name": "services",
+ "Type": "System.IServiceProvider"
+ },
+ {
+ "Name": "loggerFactory",
+ "Type": "Microsoft.Extensions.Logging.ILoggerFactory"
+ }
+ ],
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": [
+ {
+ "ParameterName": "TContext",
+ "ParameterPosition": 0,
+ "BaseTypeOrInterfaces": [
+ "Microsoft.EntityFrameworkCore.DbContext",
+ "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.IDataProtectionKeyContext"
+ ]
+ }
+ ]
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.IDataProtectionKeyContext",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "get_DataProtectionKeys",
+ "Parameters": [],
+ "ReturnType": "Microsoft.EntityFrameworkCore.DbSet",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/BitHelpers.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/BitHelpers.cs
new file mode 100644
index 0000000000..eb2063fbd8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/BitHelpers.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ internal static class BitHelpers
+ {
+ ///
+ /// Reads an unsigned 64-bit integer from
+ /// starting at offset . Data is read big-endian.
+ ///
+ public static ulong ReadUInt64(byte[] buffer, int offset)
+ {
+ return (((ulong)buffer[offset + 0]) << 56)
+ | (((ulong)buffer[offset + 1]) << 48)
+ | (((ulong)buffer[offset + 2]) << 40)
+ | (((ulong)buffer[offset + 3]) << 32)
+ | (((ulong)buffer[offset + 4]) << 24)
+ | (((ulong)buffer[offset + 5]) << 16)
+ | (((ulong)buffer[offset + 6]) << 8)
+ | (ulong)buffer[offset + 7];
+ }
+
+ ///
+ /// Writes an unsigned 64-bit integer to starting at
+ /// offset . Data is written big-endian.
+ ///
+ public static void WriteUInt64(byte[] buffer, int offset, ulong value)
+ {
+ buffer[offset + 0] = (byte)(value >> 56);
+ buffer[offset + 1] = (byte)(value >> 48);
+ buffer[offset + 2] = (byte)(value >> 40);
+ buffer[offset + 3] = (byte)(value >> 32);
+ buffer[offset + 4] = (byte)(value >> 24);
+ buffer[offset + 5] = (byte)(value >> 16);
+ buffer[offset + 6] = (byte)(value >> 8);
+ buffer[offset + 7] = (byte)(value);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionAdvancedExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionAdvancedExtensions.cs
new file mode 100644
index 0000000000..6e4c2aabac
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionAdvancedExtensions.cs
@@ -0,0 +1,169 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Helpful extension methods for data protection APIs.
+ ///
+ public static class DataProtectionAdvancedExtensions
+ {
+ ///
+ /// Cryptographically protects a piece of plaintext data, expiring the data after
+ /// the specified amount of time has elapsed.
+ ///
+ /// The protector to use.
+ /// The plaintext data to protect.
+ /// The amount of time after which the payload should no longer be unprotectable.
+ /// The protected form of the plaintext data.
+ public static byte[] Protect(this ITimeLimitedDataProtector protector, byte[] plaintext, TimeSpan lifetime)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ return protector.Protect(plaintext, DateTimeOffset.UtcNow + lifetime);
+ }
+
+ ///
+ /// Cryptographically protects a piece of plaintext data, expiring the data at
+ /// the chosen time.
+ ///
+ /// The protector to use.
+ /// The plaintext data to protect.
+ /// The time when this payload should expire.
+ /// The protected form of the plaintext data.
+ public static string Protect(this ITimeLimitedDataProtector protector, string plaintext, DateTimeOffset expiration)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ var wrappingProtector = new TimeLimitedWrappingProtector(protector) { Expiration = expiration };
+ return wrappingProtector.Protect(plaintext);
+ }
+
+ ///
+ /// Cryptographically protects a piece of plaintext data, expiring the data after
+ /// the specified amount of time has elapsed.
+ ///
+ /// The protector to use.
+ /// The plaintext data to protect.
+ /// The amount of time after which the payload should no longer be unprotectable.
+ /// The protected form of the plaintext data.
+ public static string Protect(this ITimeLimitedDataProtector protector, string plaintext, TimeSpan lifetime)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ return Protect(protector, plaintext, DateTimeOffset.Now + lifetime);
+ }
+
+ ///
+ /// Converts an into an
+ /// so that payloads can be protected with a finite lifetime.
+ ///
+ /// The to convert to a time-limited protector.
+ /// An .
+ public static ITimeLimitedDataProtector ToTimeLimitedDataProtector(this IDataProtector protector)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ return (protector as ITimeLimitedDataProtector) ?? new TimeLimitedDataProtector(protector);
+ }
+
+ ///
+ /// Cryptographically unprotects a piece of protected data.
+ ///
+ /// The protector to use.
+ /// The protected data to unprotect.
+ /// An 'out' parameter which upon a successful unprotect
+ /// operation receives the expiration date of the payload.
+ /// The plaintext form of the protected data.
+ ///
+ /// Thrown if is invalid, malformed, or expired.
+ ///
+ public static string Unprotect(this ITimeLimitedDataProtector protector, string protectedData, out DateTimeOffset expiration)
+ {
+ if (protector == null)
+ {
+ throw new ArgumentNullException(nameof(protector));
+ }
+
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ var wrappingProtector = new TimeLimitedWrappingProtector(protector);
+ string retVal = wrappingProtector.Unprotect(protectedData);
+ expiration = wrappingProtector.Expiration;
+ return retVal;
+ }
+
+ private sealed class TimeLimitedWrappingProtector : IDataProtector
+ {
+ public DateTimeOffset Expiration;
+ private readonly ITimeLimitedDataProtector _innerProtector;
+
+ public TimeLimitedWrappingProtector(ITimeLimitedDataProtector innerProtector)
+ {
+ _innerProtector = innerProtector;
+ }
+
+ public IDataProtector CreateProtector(string purpose)
+ {
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ throw new NotImplementedException();
+ }
+
+ public byte[] Protect(byte[] plaintext)
+ {
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ return _innerProtector.Protect(plaintext, Expiration);
+ }
+
+ public byte[] Unprotect(byte[] protectedData)
+ {
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ return _innerProtector.Unprotect(protectedData, out Expiration);
+ }
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionProvider.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionProvider.cs
new file mode 100644
index 0000000000..cc82fe9ef8
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/DataProtectionProvider.cs
@@ -0,0 +1,178 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Contains factory methods for creating an where keys are stored
+ /// at a particular location on the file system.
+ ///
+ /// Use these methods when not using dependency injection to provide the service to the application.
+ public static class DataProtectionProvider
+ {
+ ///
+ /// Creates a that store keys in a location based on
+ /// the platform and operating system.
+ ///
+ /// An identifier that uniquely discriminates this application from all other
+ /// applications on the machine.
+ public static IDataProtectionProvider Create(string applicationName)
+ {
+ if (string.IsNullOrEmpty(applicationName))
+ {
+ throw new ArgumentNullException(nameof(applicationName));
+ }
+
+ return CreateProvider(
+ keyDirectory: null,
+ setupAction: builder => { builder.SetApplicationName(applicationName); },
+ certificate: null);
+ }
+
+ ///
+ /// Creates an given a location at which to store keys.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ public static IDataProtectionProvider Create(DirectoryInfo keyDirectory)
+ {
+ if (keyDirectory == null)
+ {
+ throw new ArgumentNullException(nameof(keyDirectory));
+ }
+
+ return CreateProvider(keyDirectory, setupAction: builder => { }, certificate: null);
+ }
+
+ ///
+ /// Creates an given a location at which to store keys and an
+ /// optional configuration callback.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ /// An optional callback which provides further configuration of the data protection
+ /// system. See for more information.
+ public static IDataProtectionProvider Create(
+ DirectoryInfo keyDirectory,
+ Action setupAction)
+ {
+ if (keyDirectory == null)
+ {
+ throw new ArgumentNullException(nameof(keyDirectory));
+ }
+ if (setupAction == null)
+ {
+ throw new ArgumentNullException(nameof(setupAction));
+ }
+
+ return CreateProvider(keyDirectory, setupAction, certificate: null);
+ }
+
+ ///
+ /// Creates a that store keys in a location based on
+ /// the platform and operating system and uses the given to encrypt the keys.
+ ///
+ /// An identifier that uniquely discriminates this application from all other
+ /// applications on the machine.
+ /// The to be used for encryption.
+ public static IDataProtectionProvider Create(string applicationName, X509Certificate2 certificate)
+ {
+ if (string.IsNullOrEmpty(applicationName))
+ {
+ throw new ArgumentNullException(nameof(applicationName));
+ }
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ return CreateProvider(
+ keyDirectory: null,
+ setupAction: builder => { builder.SetApplicationName(applicationName); },
+ certificate: certificate);
+ }
+
+ ///
+ /// Creates an given a location at which to store keys
+ /// and a used to encrypt the keys.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ /// The to be used for encryption.
+ public static IDataProtectionProvider Create(
+ DirectoryInfo keyDirectory,
+ X509Certificate2 certificate)
+ {
+ if (keyDirectory == null)
+ {
+ throw new ArgumentNullException(nameof(keyDirectory));
+ }
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ return CreateProvider(keyDirectory, setupAction: builder => { }, certificate: certificate);
+ }
+
+ ///
+ /// Creates an given a location at which to store keys, an
+ /// optional configuration callback and a used to encrypt the keys.
+ ///
+ /// The in which keys should be stored. This may
+ /// represent a directory on a local disk or a UNC share.
+ /// An optional callback which provides further configuration of the data protection
+ /// system. See for more information.
+ /// The to be used for encryption.
+ public static IDataProtectionProvider Create(
+ DirectoryInfo keyDirectory,
+ Action setupAction,
+ X509Certificate2 certificate)
+ {
+ if (keyDirectory == null)
+ {
+ throw new ArgumentNullException(nameof(keyDirectory));
+ }
+ if (setupAction == null)
+ {
+ throw new ArgumentNullException(nameof(setupAction));
+ }
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
+
+ return CreateProvider(keyDirectory, setupAction, certificate);
+ }
+
+ internal static IDataProtectionProvider CreateProvider(
+ DirectoryInfo keyDirectory,
+ Action setupAction,
+ X509Certificate2 certificate)
+ {
+ // build the service collection
+ var serviceCollection = new ServiceCollection();
+ var builder = serviceCollection.AddDataProtection();
+
+ if (keyDirectory != null)
+ {
+ builder.PersistKeysToFileSystem(keyDirectory);
+ }
+
+ if (certificate != null)
+ {
+ builder.ProtectKeysWithCertificate(certificate);
+ }
+
+ setupAction(builder);
+
+ // extract the provider instance from the service collection
+ return serviceCollection.BuildServiceProvider().GetRequiredService();
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/ITimeLimitedDataProtector.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/ITimeLimitedDataProtector.cs
new file mode 100644
index 0000000000..71fa609f21
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/ITimeLimitedDataProtector.cs
@@ -0,0 +1,55 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// An interface that can provide data protection services where payloads have
+ /// a finite lifetime.
+ ///
+ ///
+ /// It is intended that payload lifetimes be somewhat short. Payloads protected
+ /// via this mechanism are not intended for long-term persistence (e.g., longer
+ /// than a few weeks).
+ ///
+ public interface ITimeLimitedDataProtector : IDataProtector
+ {
+ ///
+ /// Creates an given a purpose.
+ ///
+ ///
+ /// The purpose to be assigned to the newly-created .
+ ///
+ /// An tied to the provided purpose.
+ ///
+ /// The parameter must be unique for the intended use case; two
+ /// different instances created with two different
+ /// values will not be able to decipher each other's payloads. The parameter
+ /// value is not intended to be kept secret.
+ ///
+ new ITimeLimitedDataProtector CreateProtector(string purpose);
+
+ ///
+ /// Cryptographically protects a piece of plaintext data, expiring the data at
+ /// the chosen time.
+ ///
+ /// The plaintext data to protect.
+ /// The time when this payload should expire.
+ /// The protected form of the plaintext data.
+ byte[] Protect(byte[] plaintext, DateTimeOffset expiration);
+
+ ///
+ /// Cryptographically unprotects a piece of protected data.
+ ///
+ /// The protected data to unprotect.
+ /// An 'out' parameter which upon a successful unprotect
+ /// operation receives the expiration date of the payload.
+ /// The plaintext form of the protected data.
+ ///
+ /// Thrown if is invalid, malformed, or expired.
+ ///
+ byte[] Unprotect(byte[] protectedData, out DateTimeOffset expiration);
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Microsoft.AspNetCore.DataProtection.Extensions.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Microsoft.AspNetCore.DataProtection.Extensions.csproj
new file mode 100644
index 0000000000..44885e5711
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Microsoft.AspNetCore.DataProtection.Extensions.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Additional APIs for ASP.NET Core data protection.
+ netstandard2.0
+ true
+ aspnetcore;dataprotection
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/AssemblyInfo.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..022a5a3e6c
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DataProtection.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..8fba5cd9f2
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Properties/Resources.Designer.cs
@@ -0,0 +1,72 @@
+//
+namespace Microsoft.AspNetCore.DataProtection.Extensions
+{
+ using System.Globalization;
+ using System.Reflection;
+ using System.Resources;
+
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.AspNetCore.DataProtection.Extensions.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// An error occurred during a cryptographic operation.
+ ///
+ internal static string CryptCommon_GenericError
+ {
+ get => GetString("CryptCommon_GenericError");
+ }
+
+ ///
+ /// An error occurred during a cryptographic operation.
+ ///
+ internal static string FormatCryptCommon_GenericError()
+ => GetString("CryptCommon_GenericError");
+
+ ///
+ /// The payload expired at {0}.
+ ///
+ internal static string TimeLimitedDataProtector_PayloadExpired
+ {
+ get => GetString("TimeLimitedDataProtector_PayloadExpired");
+ }
+
+ ///
+ /// The payload expired at {0}.
+ ///
+ internal static string FormatTimeLimitedDataProtector_PayloadExpired(object p0)
+ => string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0);
+
+ ///
+ /// The payload is invalid.
+ ///
+ internal static string TimeLimitedDataProtector_PayloadInvalid
+ {
+ get => GetString("TimeLimitedDataProtector_PayloadInvalid");
+ }
+
+ ///
+ /// The payload is invalid.
+ ///
+ internal static string FormatTimeLimitedDataProtector_PayloadInvalid()
+ => GetString("TimeLimitedDataProtector_PayloadInvalid");
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+
+ System.Diagnostics.Debug.Assert(value != null);
+
+ if (formatterNames != null)
+ {
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Resources.resx b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Resources.resx
new file mode 100644
index 0000000000..b53d26e321
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/Resources.resx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ An error occurred during a cryptographic operation.
+
+
+ The payload expired at {0}.
+
+
+ The payload is invalid.
+
+
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/TimeLimitedDataProtector.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/TimeLimitedDataProtector.cs
new file mode 100644
index 0000000000..71e9c3c553
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/TimeLimitedDataProtector.cs
@@ -0,0 +1,149 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security.Cryptography;
+using System.Threading;
+using Microsoft.AspNetCore.DataProtection.Extensions;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Wraps an existing and appends a purpose that allows
+ /// protecting data with a finite lifetime.
+ ///
+ internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
+ {
+ private const string MyPurposeString = "Microsoft.AspNetCore.DataProtection.TimeLimitedDataProtector.v1";
+
+ private readonly IDataProtector _innerProtector;
+ private IDataProtector _innerProtectorWithTimeLimitedPurpose; // created on-demand
+
+ public TimeLimitedDataProtector(IDataProtector innerProtector)
+ {
+ _innerProtector = innerProtector;
+ }
+
+ public ITimeLimitedDataProtector CreateProtector(string purpose)
+ {
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ return new TimeLimitedDataProtector(_innerProtector.CreateProtector(purpose));
+ }
+
+ private IDataProtector GetInnerProtectorWithTimeLimitedPurpose()
+ {
+ // thread-safe lazy init pattern with multi-execution and single publication
+ var retVal = Volatile.Read(ref _innerProtectorWithTimeLimitedPurpose);
+ if (retVal == null)
+ {
+ var newValue = _innerProtector.CreateProtector(MyPurposeString); // we always append our purpose to the end of the chain
+ retVal = Interlocked.CompareExchange(ref _innerProtectorWithTimeLimitedPurpose, newValue, null) ?? newValue;
+ }
+ return retVal;
+ }
+
+ public byte[] Protect(byte[] plaintext, DateTimeOffset expiration)
+ {
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ // We prepend the expiration time (as a 64-bit UTC tick count) to the unprotected data.
+ byte[] plaintextWithHeader = new byte[checked(8 + plaintext.Length)];
+ BitHelpers.WriteUInt64(plaintextWithHeader, 0, (ulong)expiration.UtcTicks);
+ Buffer.BlockCopy(plaintext, 0, plaintextWithHeader, 8, plaintext.Length);
+
+ return GetInnerProtectorWithTimeLimitedPurpose().Protect(plaintextWithHeader);
+ }
+
+ public byte[] Unprotect(byte[] protectedData, out DateTimeOffset expiration)
+ {
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ return UnprotectCore(protectedData, DateTimeOffset.UtcNow, out expiration);
+ }
+
+ internal byte[] UnprotectCore(byte[] protectedData, DateTimeOffset now, out DateTimeOffset expiration)
+ {
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ try
+ {
+ byte[] plaintextWithHeader = GetInnerProtectorWithTimeLimitedPurpose().Unprotect(protectedData);
+ if (plaintextWithHeader.Length < 8)
+ {
+ // header isn't present
+ throw new CryptographicException(Resources.TimeLimitedDataProtector_PayloadInvalid);
+ }
+
+ // Read expiration time back out of the payload
+ ulong utcTicksExpiration = BitHelpers.ReadUInt64(plaintextWithHeader, 0);
+ DateTimeOffset embeddedExpiration = new DateTimeOffset(checked((long)utcTicksExpiration), TimeSpan.Zero /* UTC */);
+
+ // Are we expired?
+ if (now > embeddedExpiration)
+ {
+ throw new CryptographicException(Resources.FormatTimeLimitedDataProtector_PayloadExpired(embeddedExpiration));
+ }
+
+ // Not expired - split and return payload
+ byte[] retVal = new byte[plaintextWithHeader.Length - 8];
+ Buffer.BlockCopy(plaintextWithHeader, 8, retVal, 0, retVal.Length);
+ expiration = embeddedExpiration;
+ return retVal;
+ }
+ catch (Exception ex) when (ex.RequiresHomogenization())
+ {
+ // Homogenize all failures to CryptographicException
+ throw new CryptographicException(Resources.CryptCommon_GenericError, ex);
+ }
+ }
+
+ /*
+ * EXPLICIT INTERFACE IMPLEMENTATIONS
+ */
+
+ IDataProtector IDataProtectionProvider.CreateProtector(string purpose)
+ {
+ if (purpose == null)
+ {
+ throw new ArgumentNullException(nameof(purpose));
+ }
+
+ return CreateProtector(purpose);
+ }
+
+ byte[] IDataProtector.Protect(byte[] plaintext)
+ {
+ if (plaintext == null)
+ {
+ throw new ArgumentNullException(nameof(plaintext));
+ }
+
+ // MaxValue essentially means 'no expiration'
+ return Protect(plaintext, DateTimeOffset.MaxValue);
+ }
+
+ byte[] IDataProtector.Unprotect(byte[] protectedData)
+ {
+ if (protectedData == null)
+ {
+ throw new ArgumentNullException(nameof(protectedData));
+ }
+
+ DateTimeOffset expiration; // unused
+ return Unprotect(protectedData, out expiration);
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/baseline.netcore.json b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/baseline.netcore.json
new file mode 100644
index 0000000000..5bb3088d07
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.Extensions/baseline.netcore.json
@@ -0,0 +1,298 @@
+{
+ "AssemblyIdentity": "Microsoft.AspNetCore.DataProtection.Extensions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+ "Types": [
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.DataProtectionAdvancedExtensions",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector"
+ },
+ {
+ "Name": "plaintext",
+ "Type": "System.Byte[]"
+ },
+ {
+ "Name": "lifetime",
+ "Type": "System.TimeSpan"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector"
+ },
+ {
+ "Name": "plaintext",
+ "Type": "System.String"
+ },
+ {
+ "Name": "expiration",
+ "Type": "System.DateTimeOffset"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector"
+ },
+ {
+ "Name": "plaintext",
+ "Type": "System.String"
+ },
+ {
+ "Name": "lifetime",
+ "Type": "System.TimeSpan"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "ToTimeLimitedDataProtector",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.IDataProtector"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Unprotect",
+ "Parameters": [
+ {
+ "Name": "protector",
+ "Type": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector"
+ },
+ {
+ "Name": "protectedData",
+ "Type": "System.String"
+ },
+ {
+ "Name": "expiration",
+ "Type": "System.DateTimeOffset",
+ "Direction": "Out"
+ }
+ ],
+ "ReturnType": "System.String",
+ "Static": true,
+ "Extension": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.DataProtectionProvider",
+ "Visibility": "Public",
+ "Kind": "Class",
+ "Abstract": true,
+ "Static": true,
+ "Sealed": true,
+ "ImplementedInterfaces": [],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "applicationName",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "keyDirectory",
+ "Type": "System.IO.DirectoryInfo"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "keyDirectory",
+ "Type": "System.IO.DirectoryInfo"
+ },
+ {
+ "Name": "setupAction",
+ "Type": "System.Action"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "applicationName",
+ "Type": "System.String"
+ },
+ {
+ "Name": "certificate",
+ "Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "keyDirectory",
+ "Type": "System.IO.DirectoryInfo"
+ },
+ {
+ "Name": "certificate",
+ "Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Create",
+ "Parameters": [
+ {
+ "Name": "keyDirectory",
+ "Type": "System.IO.DirectoryInfo"
+ },
+ {
+ "Name": "setupAction",
+ "Type": "System.Action"
+ },
+ {
+ "Name": "certificate",
+ "Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.IDataProtectionProvider",
+ "Static": true,
+ "Visibility": "Public",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ },
+ {
+ "Name": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector",
+ "Visibility": "Public",
+ "Kind": "Interface",
+ "Abstract": true,
+ "ImplementedInterfaces": [
+ "Microsoft.AspNetCore.DataProtection.IDataProtector"
+ ],
+ "Members": [
+ {
+ "Kind": "Method",
+ "Name": "CreateProtector",
+ "Parameters": [
+ {
+ "Name": "purpose",
+ "Type": "System.String"
+ }
+ ],
+ "ReturnType": "Microsoft.AspNetCore.DataProtection.ITimeLimitedDataProtector",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Protect",
+ "Parameters": [
+ {
+ "Name": "plaintext",
+ "Type": "System.Byte[]"
+ },
+ {
+ "Name": "expiration",
+ "Type": "System.DateTimeOffset"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "GenericParameter": []
+ },
+ {
+ "Kind": "Method",
+ "Name": "Unprotect",
+ "Parameters": [
+ {
+ "Name": "protectedData",
+ "Type": "System.Byte[]"
+ },
+ {
+ "Name": "expiration",
+ "Type": "System.DateTimeOffset",
+ "Direction": "Out"
+ }
+ ],
+ "ReturnType": "System.Byte[]",
+ "GenericParameter": []
+ }
+ ],
+ "GenericParameters": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj
new file mode 100644
index 0000000000..1aa6874fff
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis/Microsoft.AspNetCore.DataProtection.StackExchangeRedis.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Support for storing data protection keys in Redis.
+ netstandard2.0
+ true
+ true
+ aspnetcore;dataprotection;redis
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis/RedisDataProtectionBuilderExtensions.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis/RedisDataProtectionBuilderExtensions.cs
new file mode 100644
index 0000000000..ead1b37db5
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis/RedisDataProtectionBuilderExtensions.cs
@@ -0,0 +1,79 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using StackExchange.Redis;
+using Microsoft.AspNetCore.DataProtection.KeyManagement;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.DataProtection.StackExchangeRedis;
+
+namespace Microsoft.AspNetCore.DataProtection
+{
+ ///
+ /// Contains Redis-specific extension methods for modifying a .
+ ///
+ public static class StackExchangeRedisDataProtectionBuilderExtensions
+ {
+ private const string DataProtectionKeysName = "DataProtection-Keys";
+
+ ///
+ /// Configures the data protection system to persist keys to specified key in Redis database
+ ///
+ /// The builder instance to modify.
+ /// The delegate used to create instances.
+ /// The used to store key list.
+ /// A reference to the after this operation has completed.
+ public static IDataProtectionBuilder PersistKeysToStackExchangeRedis(this IDataProtectionBuilder builder, Func databaseFactory, RedisKey key)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (databaseFactory == null)
+ {
+ throw new ArgumentNullException(nameof(databaseFactory));
+ }
+ return PersistKeysToStackExchangeRedisInternal(builder, databaseFactory, key);
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the default key ('DataProtection-Keys') in Redis database
+ ///
+ /// The builder instance to modify.
+ /// The for database access.
+ /// A reference to the after this operation has completed.
+ public static IDataProtectionBuilder PersistKeysToStackExchangeRedis(this IDataProtectionBuilder builder, IConnectionMultiplexer connectionMultiplexer)
+ {
+ return PersistKeysToStackExchangeRedis(builder, connectionMultiplexer, DataProtectionKeysName);
+ }
+
+ ///
+ /// Configures the data protection system to persist keys to the specified key in Redis database
+ ///
+ /// The builder instance to modify.
+ /// The for database access.
+ /// The used to store key list.
+ /// A reference to the after this operation has completed.
+ public static IDataProtectionBuilder PersistKeysToStackExchangeRedis(this IDataProtectionBuilder builder, IConnectionMultiplexer connectionMultiplexer, RedisKey key)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+ if (connectionMultiplexer == null)
+ {
+ throw new ArgumentNullException(nameof(connectionMultiplexer));
+ }
+ return PersistKeysToStackExchangeRedisInternal(builder, () => connectionMultiplexer.GetDatabase(), key);
+ }
+
+ private static IDataProtectionBuilder PersistKeysToStackExchangeRedisInternal(IDataProtectionBuilder builder, Func databaseFactory, RedisKey key)
+ {
+ builder.Services.Configure(options =>
+ {
+ options.XmlRepository = new RedisXmlRepository(databaseFactory, key);
+ });
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis/RedisXmlRepository.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis/RedisXmlRepository.cs
new file mode 100644
index 0000000000..2665fd1408
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.StackExchangeRedis/RedisXmlRepository.cs
@@ -0,0 +1,59 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using StackExchange.Redis;
+using Microsoft.AspNetCore.DataProtection.Repositories;
+
+namespace Microsoft.AspNetCore.DataProtection.StackExchangeRedis
+{
+ ///
+ /// An XML repository backed by a Redis list entry.
+ ///
+ public class RedisXmlRepository : IXmlRepository
+ {
+ private readonly Func _databaseFactory;
+ private readonly RedisKey _key;
+
+ ///
+ /// Creates a with keys stored at the given directory.
+ ///
+ /// The delegate used to create instances.
+ /// The used to store key list.
+ public RedisXmlRepository(Func databaseFactory, RedisKey key)
+ {
+ _databaseFactory = databaseFactory;
+ _key = key;
+ }
+
+ ///
+ public IReadOnlyCollection GetAllElements()
+ {
+ return GetAllElementsCore().ToList().AsReadOnly();
+ }
+
+ private IEnumerable GetAllElementsCore()
+ {
+ // Note: Inability to read any value is considered a fatal error (since the file may contain
+ // revocation information), and we'll fail the entire operation rather than return a partial
+ // set of elements. If a value contains well-formed XML but its contents are meaningless, we
+ // won't fail that operation here. The caller is responsible for failing as appropriate given
+ // that scenario.
+ var database = _databaseFactory();
+ foreach (var value in database.ListRange(_key))
+ {
+ yield return XElement.Parse(value);
+ }
+ }
+
+ ///
+ public void StoreElement(XElement element, string friendlyName)
+ {
+ var database = _databaseFactory();
+ database.ListRightPush(_key, element.ToString(SaveOptions.DisableFormatting));
+ }
+ }
+}
diff --git a/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.SystemWeb/CompatibilityDataProtector.cs b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.SystemWeb/CompatibilityDataProtector.cs
new file mode 100644
index 0000000000..739afe83bd
--- /dev/null
+++ b/src/DataProtection/src/Microsoft.AspNetCore.DataProtection.SystemWeb/CompatibilityDataProtector.cs
@@ -0,0 +1,133 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel;
+using System.Configuration;
+using System.Security.Cryptography;
+
+namespace Microsoft.AspNetCore.DataProtection.SystemWeb
+{
+ ///
+ /// A that can be used by ASP.NET 4.x to interact with ASP.NET Core's
+ /// DataProtection stack. This type is for internal use only and shouldn't be directly used by
+ /// developers.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class CompatibilityDataProtector : DataProtector
+ {
+ private static readonly Lazy _lazyProtectionProvider = new Lazy(CreateProtectionProvider);
+
+ [ThreadStatic]
+ private static bool _suppressPrimaryPurpose;
+
+ private readonly Lazy _lazyProtector;
+ private readonly Lazy _lazyProtectorSuppressedPrimaryPurpose;
+
+ public CompatibilityDataProtector(string applicationName, string primaryPurpose, string[] specificPurposes)
+ : base("application-name", "primary-purpose", null) // we feed dummy values to the base ctor
+ {
+ // We don't want to evaluate the IDataProtectionProvider factory quite yet,
+ // as we'd rather defer failures to the call to Protect so that we can bubble
+ // up a good error message to the developer.
+
+ _lazyProtector = new Lazy(() => _lazyProtectionProvider.Value.CreateProtector(primaryPurpose, specificPurposes));
+
+ // System.Web always provides "User.MachineKey.Protect" as the primary purpose for calls
+ // to MachineKey.Protect. Only in this case should we allow suppressing the primary
+ // purpose, as then we can easily map calls to MachineKey.Protect(userData, purposes)
+ // into calls to provider.GetProtector(purposes).Protect(userData).
+ if (primaryPurpose == "User.MachineKey.Protect")
+ {
+ _lazyProtectorSuppressedPrimaryPurpose = new Lazy(() => _lazyProtectionProvider.Value.CreateProtector(specificPurposes));
+ }
+ else
+ {
+ _lazyProtectorSuppressedPrimaryPurpose = _lazyProtector;
+ }
+ }
+
+ // We take care of flowing purposes ourselves.
+ protected override bool PrependHashedPurposeToPlaintext { get; } = false;
+
+ // Retrieves the appropriate protector (potentially with a suppressed primary purpose) for this operation.
+ private IDataProtector Protector => ((_suppressPrimaryPurpose) ? _lazyProtectorSuppressedPrimaryPurpose : _lazyProtector).Value;
+
+ private static IDataProtectionProvider CreateProtectionProvider()
+ {
+ // Read from the startup type we need to use, then create it
+ const string APPSETTINGS_KEY = "aspnet:dataProtectionStartupType";
+ string startupTypeName = ConfigurationManager.AppSettings[APPSETTINGS_KEY];
+ if (String.IsNullOrEmpty(startupTypeName))
+ {
+ // fall back to default startup type if one hasn't been specified in config
+ startupTypeName = typeof(DataProtectionStartup).AssemblyQualifiedName;
+ }
+ Type startupType = Type.GetType(startupTypeName, throwOnError: true);
+ var startupInstance = (DataProtectionStartup)Activator.CreateInstance(startupType);
+
+ // Use it to initialize the system.
+ return startupInstance.InternalConfigureServicesAndCreateProtectionProvider();
+ }
+
+ public override bool IsReprotectRequired(byte[] encryptedData)
+ {
+ // Nobody ever calls this.
+ return false;
+ }
+
+ protected override byte[] ProviderProtect(byte[] userData)
+ {
+ try
+ {
+ return Protector.Protect(userData);
+ }
+ catch (Exception ex)
+ {
+ // System.Web special-cases ConfigurationException errors and allows them to bubble
+ // up to the developer without being homogenized. Since a call to Protect should
+ // never fail, any exceptions here really do imply a misconfiguration.
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ throw new ConfigurationException(Resources.DataProtector_ProtectFailed, ex);
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+ }
+
+ protected override byte[] ProviderUnprotect(byte[] encryptedData)
+ {
+ return Protector.Unprotect(encryptedData);
+ }
+
+ ///
+ /// Invokes a delegate where calls to
+ /// and will ignore the primary
+ /// purpose and instead use only the sub-purposes.
+ ///
+ public static byte[] RunWithSuppressedPrimaryPurpose(Func