From 92037699e37ad3a2a4ca56388e4a6b3fb3f1ab46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=20S=C3=B5mermaa?= Date: Thu, 6 May 2021 16:45:52 +0300 Subject: [PATCH 01/34] Initial commit --- example/LICENSE | 21 +++++++++++++++++++++ example/README.md | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 example/LICENSE create mode 100644 example/README.md diff --git a/example/LICENSE b/example/LICENSE new file mode 100644 index 0000000..9783de8 --- /dev/null +++ b/example/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 eID on platform Web + +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/example/README.md b/example/README.md new file mode 100644 index 0000000..29f06e9 --- /dev/null +++ b/example/README.md @@ -0,0 +1,2 @@ +# web-eid-asp-dotnet-example +Example ASP.NET web application that uses Web eID for secure authentication and digital signing with electronic ID smart cards From 0298f707fbea23cc4a5e5b41878f375dcb9f99ea Mon Sep 17 00:00:00 2001 From: Mati Agukas Date: Thu, 28 Oct 2021 02:02:40 +0300 Subject: [PATCH 02/34] feat: Implement example service using ASP.NET Core WE2-419 Signed-off-by: Mati Agukas mati.agukas@cgi.com --- example/.github/workflows/dotnet-build.yml | 40 + example/.gitignore | 363 ++++++++ example/README.md | 105 ++- example/src/.dockerignore | 25 + .../WebEid.AspNetCore.Example.Tests.csproj | 15 + example/src/WebEid.AspNetCore.Example.sln | 31 + .../Certificates/CertificateLoader.cs | 42 + .../Dev/TEST_of_ESTEID-SK_2015.cer | Bin 0 -> 1671 bytes .../Certificates/Dev/TEST_of_ESTEID2018.cer | Bin 0 -> 1408 bytes .../Certificates/Prod/ESTEID-SK_2015.cer | Bin 0 -> 1652 bytes .../Certificates/Prod/ESTEID2018.cer | Bin 0 -> 1371 bytes .../ClaimsIdentityExtensions.cs | 13 + .../Controllers/Api/AuthController.cs | 61 ++ .../Controllers/Api/BaseController.cs | 21 + .../Controllers/Api/ChallengeController.cs | 30 + .../Controllers/Api/SignController.cs | 57 ++ .../Controllers/WelcomeController.cs | 13 + .../DigiDoc/.gitkeep | 0 .../Dto/AuthenticateRequestDto.cs | 11 + .../Dto/CertificateDto.cs | 12 + .../Dto/ChallengeDto.cs | 7 + .../Dto/DigestDto.cs | 8 + .../WebEid.AspNetCore.Example/Dto/FileDto.cs | 12 + .../Dto/SignatureAlgorithmDto.cs | 9 + .../Dto/SignatureDto.cs | 12 + .../LoggedInAuthorizationHandler.cs | 21 + .../Pages/Index.cshtml | 117 +++ .../Pages/Welcome.cshtml | 131 +++ .../Pages/Welcome.cshtml.cs | 22 + .../Pages/_ViewStart.cshtml | 3 + .../src/WebEid.AspNetCore.Example/Program.cs | 20 + .../Properties/launchSettings.json | 27 + .../SessionBackedChallengeNonceStore.cs | 34 + .../Signing/DigiDocConfiguration.cs | 61 ++ .../Signing/SigningService.cs | 88 ++ .../src/WebEid.AspNetCore.Example/Startup.cs | 147 ++++ .../WebEid.AspNetCore.Example.csproj | 46 + .../appsettings.Development.json | 11 + .../appsettings.json | 11 + .../wwwroot/css/bootstrap.min.css | 7 + .../wwwroot/css/main.css | 69 ++ .../wwwroot/favicon.ico | Bin 0 -> 5430 bytes .../wwwroot/files/Web eID privacy policy.pdf | Bin 0 -> 76371 bytes .../wwwroot/files/example-for-signing.txt | 1 + .../wwwroot/img/eu-fund-flags.svg | 787 ++++++++++++++++++ .../wwwroot/js/errors.js | 59 ++ .../wwwroot/js/web-eid.js | 431 ++++++++++ 47 files changed, 2978 insertions(+), 2 deletions(-) create mode 100644 example/.github/workflows/dotnet-build.yml create mode 100644 example/.gitignore create mode 100644 example/src/.dockerignore create mode 100644 example/src/WebEid.AspNetCore.Example.Tests/WebEid.AspNetCore.Example.Tests.csproj create mode 100644 example/src/WebEid.AspNetCore.Example.sln create mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/CertificateLoader.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_ESTEID-SK_2015.cer create mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_ESTEID2018.cer create mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Prod/ESTEID-SK_2015.cer create mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Prod/ESTEID2018.cer create mode 100644 example/src/WebEid.AspNetCore.Example/ClaimsIdentityExtensions.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Controllers/Api/ChallengeController.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Controllers/WelcomeController.cs create mode 100644 example/src/WebEid.AspNetCore.Example/DigiDoc/.gitkeep create mode 100644 example/src/WebEid.AspNetCore.Example/Dto/AuthenticateRequestDto.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Dto/CertificateDto.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Dto/ChallengeDto.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Dto/DigestDto.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Dto/FileDto.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Dto/SignatureAlgorithmDto.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Dto/SignatureDto.cs create mode 100644 example/src/WebEid.AspNetCore.Example/LoggedInAuthorizationHandler.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Pages/Index.cshtml create mode 100644 example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml create mode 100644 example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Pages/_ViewStart.cshtml create mode 100644 example/src/WebEid.AspNetCore.Example/Program.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Properties/launchSettings.json create mode 100644 example/src/WebEid.AspNetCore.Example/SessionBackedChallengeNonceStore.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Signing/DigiDocConfiguration.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs create mode 100644 example/src/WebEid.AspNetCore.Example/Startup.cs create mode 100644 example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj create mode 100644 example/src/WebEid.AspNetCore.Example/appsettings.Development.json create mode 100644 example/src/WebEid.AspNetCore.Example/appsettings.json create mode 100644 example/src/WebEid.AspNetCore.Example/wwwroot/css/bootstrap.min.css create mode 100644 example/src/WebEid.AspNetCore.Example/wwwroot/css/main.css create mode 100644 example/src/WebEid.AspNetCore.Example/wwwroot/favicon.ico create mode 100644 example/src/WebEid.AspNetCore.Example/wwwroot/files/Web eID privacy policy.pdf create mode 100644 example/src/WebEid.AspNetCore.Example/wwwroot/files/example-for-signing.txt create mode 100644 example/src/WebEid.AspNetCore.Example/wwwroot/img/eu-fund-flags.svg create mode 100644 example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js create mode 100644 example/src/WebEid.AspNetCore.Example/wwwroot/js/web-eid.js diff --git a/example/.github/workflows/dotnet-build.yml b/example/.github/workflows/dotnet-build.yml new file mode 100644 index 0000000..06af20e --- /dev/null +++ b/example/.github/workflows/dotnet-build.yml @@ -0,0 +1,40 @@ +name: Dotnet build + +on: [ push, pull_request ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup dotnet + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x # SDK Version to use. + + - name: Cache Nuget packages + uses: actions/cache@v2 + with: + path: ~/.nuget/packages + # Look to see if there is a cache hit for the corresponding requirements file + key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} + restore-keys: ${{ runner.os }}-nuget + + - name: Install dependencies + run: dotnet restore src/WebEid.AspNetCore.Example.sln --source "https://gitlab.com/api/v4/projects/35362906/packages/nuget/index.json" --source "https://api.nuget.org/v3/index.json" + + - name: Download digidoc + run: wget https://gitlab.com/api/v4/projects/35362906/packages/generic/digidoc/1.0.0/digidoc.zip + + - name: Unzip digidoc + uses: montudor/action-zip@v1 + with: + args: unzip -qq digidoc.zip -d src/WebEid.AspNetCore.Example/DigiDoc + + - name: Build + run: dotnet build --configuration Release --no-restore src/WebEid.AspNetCore.Example.sln --verbosity normal + + - name: Test + run: dotnet test --no-restore --verbosity normal src/WebEid.AspNetCore.Example.sln diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..a52c158 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd +/TestsResults diff --git a/example/README.md b/example/README.md index 29f06e9..94e94e3 100644 --- a/example/README.md +++ b/example/README.md @@ -1,2 +1,103 @@ -# web-eid-asp-dotnet-example -Example ASP.NET web application that uses Web eID for secure authentication and digital signing with electronic ID smart cards +# Web eID ASP.NET example + +![European Regional Development Fund](https://github.com/open-eid/DigiDoc4-Client/blob/master/client/images/EL_Regionaalarengu_Fond.png) + +This project is an example ASP.NET web application that shows how to implement strong authentication and digital signing with electronic ID smart cards using Web eID. + +More information about the Web eID project is available on the project [website](https://web-eid.eu/). + +The ASP.NET web application makes use of the following technologies: + +- ASP.NET MVC, +- the Web eID authentication token validation library [_web-eid-authtoken-validation-dotnet_](https://github.com/web-eid/web-eid-authtoken-validation-dotnet), +- the Web eID JavaScript library [_web-eid.js_](https://github.com/web-eid/web-eid.js), +- the digital signing library [_libdigidocpp_](https://github.com/open-eid/libdigidocpp/tree/master/examples/DigiDocCSharp). + +## Quickstart + +Complete the steps below to run the example application in order to test authentication and digital signing with Web eID. + +### 1. Configure the origin URL + +One crucial step of the Web eID authentication token validation algorithm is verifying the token signature. The value that is signed contains the site origin URL (the URL serving the web application) to protect against man-in-the-middle attacks. Hence the site origin URL must be configured in application settings. + +To configure the origin URL, add `OriginUrl` field in the application settings file `appsettings.json` as follows: +```json +{ + "OriginUrl": "https://example.org" +} +``` +Note that the URL **must not end with a slash** `/`. + +### 2. Configure the trusted certificate authority certificates + +The algorithm, which performs the validation of the Web eID authentication token, needs to know which intermediate certificate authorities (CA) are trusted to issue the eID authentication certificates. CA certificates are loaded from `.cer` files in the profile-specific subdirectory of the [`Certificates` resource directory](https://github.com/web-eid/web-eid-asp-dotnet-example/src/WebEid.AspNetCore.Example/Certificates). By default, Estonian eID test CA certificates are included in the `Development` profile and production CA certificates in the `Production` profile. + +In case you need to provide your own CA certificates, add the `.cer` files to the `src/WebEid.AspNetCore.Example/Certificates/{Dev,Prod}` profile-specific directory. + +### 3. Setup the `libdigidocpp` library for signing +`libdigidocpp` is a library for creating, signing and verifying digitally signed documents according to XAdES and XML-DSIG standards. It is a C++ library that has [SWIG](http://swig.org/) bindings for C#. + +Set up the `libdigidocpp` library as follows: + +1. Install the _libdigidocpp-3.14.4.msi_ package or higher. The installation packages are available from [https://github.com/open-eid/libdigidocpp/releases](https://github.com/open-eid/libdigidocpp/releases). +2. Copy the C# source files from the `libdigidocpp` installation folder `include\digidocpp_csharp` to the `src\WebEid.AspNetCore.Example\DigiDoc` folder. +3. Copy all files from `libdigidocpp` installation folder `x64` subfolder to the example application build output folder `bin\...\net60` (after building, see next step). +4. When running in the `Development` profile, create an empty file named `EE_T.xml` for TSL cache as described in the [_Using test TSL lists_](https://github.com/open-eid/libdigidocpp/wiki/Using-test-TSL-lists#preconditions) section of the `libdigidocpp` wiki. + +Further information is available in the [libdigidocpp example C# application](https://github.com/open-eid/libdigidocpp/tree/master/examples/DigiDocCSharp) and in the [`libdigidocpp` wiki](https://github.com/open-eid/libdigidocpp/wiki). + +### 4. Build the application + +You need to have the [.NET 6.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) installed for building the application package. +Build the application by running the following command in a terminal window under the `src` directory: + +```cmd +dotnet build +``` + +### 5. Choose either the `Development` or `Production` profile + +If you have a test eID card, use the `Development` profile. In this case access to paid services is not required, but you need to upload the authentication and signing certificates of the test card to the test OCSP responder database as described in section _[Using DigiDoc4j in test mode with the `dev` profile](https://github.com/web-eid/web-eid-spring-boot-example#using-digidoc4j-in-test-mode-with-the-dev-profile)_ of the Web eID Java example application documentation. The`Development` profile is activated by default. + +If you only have a production eID card, use the `Production` profile. You can still test authentication without further configuration; however, for digital signing to work, you need access to a paid timestamping service as described in section [_Using DigiDoc4j in production mode with the `prod` profile_](https://github.com/web-eid/web-eid-spring-boot-example#using-digidoc4j-in-production-mode-with-the-prod-profile) of the Web eID Java example documentation. + +You can specify the profile as an environment variable `ASPNETCORE_ENVIRONMENT` when running the application. To set the profile for the current session before starting the app using [`dotnet run`](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-run), use the following command: +```cmd +set ASPNETCORE_ENVIRONMENT=Production +``` + +### 6. Run the application + +Run the application with the following command in a terminal window under the `src` directory: + +```cmd +dotnet run --project WebEid.AspNetCore.Example +``` + +This will activate the default `Development` profile and launch the built-in `kestrel` web server on HTTPS port 5001. + +When the application has started, open https://localhost:5001 in your preferred web browser and follow instructions on the front page. + +## Overview of the source code + +The `src\WebEid.AspNetCore.Example` directory contains the ASP.NET application source code and resources. The subdirectories therein have the following purpose: +- `wwwroot`: web server static content, including CSS and JavaScript files, +- `Certificates`: CA certificates in profile-specific subdirectories, +- `Controllers`: ASP.NET MVC controller for the welcome page and Web API controllers that provide endpoints for + - getting the challenge nonce used by the authentication token validation library, + - logging in, + - digital signing, +- `DigiDoc`: contains the C# binding files of the `libdigidocpp` library; these files must be copied from the `libdigidocpp` installation directory `\include\digidocpp_csharp`, +- `Pages`: Razor pages, +- `Services`: Web eID signing service implementation that uses `libdigidocpp`. + +## More information + +See the [Web eID Java example application documentation](https://github.com/web-eid/web-eid-spring-boot-example) for more information, including answers to questions not answered below. + +### Frequently asked questions + +#### Why do I get the `System.ApplicationException: Failed to verify OCSP Responder certificate` error during signing? + +You are running in the `Development` profile, but you have not created an empty file named `EE_T.xml` for TSL cache. Creating the file is mandatory and is described in more detail in the [_Using test TSL lists_](https://github.com/open-eid/libdigidocpp/wiki/Using-test-TSL-lists#preconditions) section of the `libdigidocpp` wiki. diff --git a/example/src/.dockerignore b/example/src/.dockerignore new file mode 100644 index 0000000..3729ff0 --- /dev/null +++ b/example/src/.dockerignore @@ -0,0 +1,25 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example.Tests/WebEid.AspNetCore.Example.Tests.csproj b/example/src/WebEid.AspNetCore.Example.Tests/WebEid.AspNetCore.Example.Tests.csproj new file mode 100644 index 0000000..8843685 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example.Tests/WebEid.AspNetCore.Example.Tests.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + + false + + + + + + + + + diff --git a/example/src/WebEid.AspNetCore.Example.sln b/example/src/WebEid.AspNetCore.Example.sln new file mode 100644 index 0000000..86e294d --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31912.275 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebEid.AspNetCore.Example", "WebEid.AspNetCore.Example\WebEid.AspNetCore.Example.csproj", "{573AD725-C52C-40DE-8E70-6DF4E4227120}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebEid.AspNetCore.Example.Tests", "WebEid.AspNetCore.Example.Tests\WebEid.AspNetCore.Example.Tests.csproj", "{E51FD3B1-1F87-457A-BA45-984001A5F1F4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {573AD725-C52C-40DE-8E70-6DF4E4227120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {573AD725-C52C-40DE-8E70-6DF4E4227120}.Debug|Any CPU.Build.0 = Debug|Any CPU + {573AD725-C52C-40DE-8E70-6DF4E4227120}.Release|Any CPU.ActiveCfg = Release|Any CPU + {573AD725-C52C-40DE-8E70-6DF4E4227120}.Release|Any CPU.Build.0 = Release|Any CPU + {E51FD3B1-1F87-457A-BA45-984001A5F1F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E51FD3B1-1F87-457A-BA45-984001A5F1F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E51FD3B1-1F87-457A-BA45-984001A5F1F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E51FD3B1-1F87-457A-BA45-984001A5F1F4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {551C3D4A-5A1B-427B-8753-923D6BD1A44B} + EndGlobalSection +EndGlobal diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/CertificateLoader.cs b/example/src/WebEid.AspNetCore.Example/Certificates/CertificateLoader.cs new file mode 100644 index 0000000..57c4b65 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Certificates/CertificateLoader.cs @@ -0,0 +1,42 @@ +namespace WebEid.AspNetCore.Example.Certificates +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Security.Cryptography.X509Certificates; + + internal static class CertificateLoader + { + public static X509Certificate2[] LoadTrustedCaCertificatesFromDisk(bool isTest = false) + { + return new FileReader(GetCertPath(isTest), "*.cer").ReadFiles() + .Select(file => new X509Certificate2(file)) + .ToArray(); + } + + private static string GetCertPath(bool isTest) + { + return isTest ? "Certificates/Dev" : "Certificates/Prod"; + } + } + + internal class FileReader + { + private readonly string path; + private readonly string searchPattern; + + public FileReader(string path, string searchPattern = null) + { + this.path = path; + this.searchPattern = searchPattern; + } + + public IEnumerable ReadFiles() + { + foreach (var file in Directory.EnumerateFiles(this.path, this.searchPattern)) + { + yield return File.ReadAllBytes(file); + } + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_ESTEID-SK_2015.cer b/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_ESTEID-SK_2015.cer new file mode 100644 index 0000000000000000000000000000000000000000..7749286c895084bf2d7bacb98b742a01cd684122 GIT binary patch literal 1671 zcmb7EX;4#F6wZ5ji4YT@LXe$^l>i-*`vL)3WD$Z;z=c%QO6{Uz|Nti5Ys|*EzrHLU@nPI%pFormh!Q(M}UdIaK zh5^P>8p=gE=t|&>IygG=jdVG{ z0pJd2jsw?)>%s>~|DWJeK-UnAqw1^;3*qtXQQ++83{VHu5}3bcZn(faG>jMEYabfO z-~f~h6tLCX4T4Anq5{?>;C>3MfFXkFHX*9@YOKZPhws_e%qlnOxqQNRwIF&n`~1KE|CGi>^}+HLlswpy~$=6&4-s8xoQu zPWZNaI-1?K_1m)=i%?y>INNxazUcC3=NFXg{Lh`Ad45@PS*LrIYTEetNUfHrv#Vuz z!^?qN&#WMG!<^jJDzbBy1YKXU#6!Pztk|T2UvZ`;T2i5xe=9g>AlSFJ&0{qDxBce} zGxzCtI8=KbySVY7swKF3L{S$NR�?itslb-bPA?nh!_1G3$MPD2FPu$I{g%r!&R2 z7Wd~~5Wl%Zb&m2WF~aUx^f0H!IT=uI@RRV8ox@z?+~Vy|8D@qT+J=qy&-q8w#2zEW z2o9}C(Yo!lu)kE2_u_8K=yLn6HRI5`n zsa6&tfKJg0aY#En=1H<22%?{K4E76 z{^(l5NYEX2cK46HW>pDOFcBg^RSKAfPlPtG#D~dzIf763E`#%ql=4$-y(^>3bedFk z*cwyfw%J$HX0C%6A_f7?lfEw4%`!hX+RE7Mx;}dwWi`;zyw`=GIRwF%l?)O!n1Kku zM*t;LJ|IH~)L}`4Ag~-PBWvN9L|OzxhV;#HxkTp5W~Zg4v0kka+h=_!puY74eg_fX zK?qo)$)m5ys|CpTg%39H*7z(CSFrN96F~4iumY4^Ey9}vta2Ii_J7@gsrh0B!T=WY zWQp1f7QA2~VE5j606|D5*a=q5V*n|QHPzw8C15GyH`NK_1Y)5~0zwwTV5Q%iuzz|W z#1%-!)-1L_oG9S&1n)=^jx>@a&`6TN$D5UaNm)Xv9QbHRTO#fNue{Q!(oxCzcXy?j zL@X1@#nN=PP?{`b%Q2Z;fMG(RR2YYUlKi%0;F?NQKoFtDWzSej1FN!l*U`=!Cn?sn zJBhYMN6%(Y5V8znN<+$f>|Emwh@a_k>IXiqZaCYfpj)CtH)li_+}E{7H<1W@=XS#^ zk5pEZ4tXR;>CsZo?QFTW^+PAsUsa#xxl6cvJp#_n^fag0JjuBf6Th2TRy19ex5>-q zfckldT}3N)JvnlfKYgd2=Q$TXbg#>X!ff?V+wV{=G&xpz=_SnV;64Ak!`EPhIYw8ESfeZIIag&l~psrr8d>b^~V literal 0 HcmV?d00001 diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_ESTEID2018.cer b/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_ESTEID2018.cer new file mode 100644 index 0000000000000000000000000000000000000000..6a96fb083686e6210a1c5f14cfd0cf7780227843 GIT binary patch literal 1408 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 literal 0 HcmV?d00001 diff --git a/example/src/WebEid.AspNetCore.Example/ClaimsIdentityExtensions.cs b/example/src/WebEid.AspNetCore.Example/ClaimsIdentityExtensions.cs new file mode 100644 index 0000000..c5ea8cc --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/ClaimsIdentityExtensions.cs @@ -0,0 +1,13 @@ +namespace WebEid.AspNetCore.Example +{ + using System.Linq; + using System.Security.Claims; + + public static class ClaimsIdentityExtensions + { + public static string GetIdCode(this ClaimsIdentity identity) + { + return identity.Claims.SingleOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value; + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs new file mode 100644 index 0000000..83a218f --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs @@ -0,0 +1,61 @@ +namespace WebEid.AspNetCore.Example.Controllers.Api +{ + using Microsoft.AspNetCore.Authentication; + using Microsoft.AspNetCore.Authentication.Cookies; + using Microsoft.AspNetCore.Mvc; + using Security.Util; + using Security.Validator; + using System.Collections.Generic; + using System.Security.Claims; + using System.Threading.Tasks; + using Security.Challenge; + using WebEid.AspNetCore.Example.Dto; + + [Route("[controller]")] + [ApiController] + public class AuthController : BaseController + { + private readonly IAuthTokenValidator authTokenValidator; + private readonly IChallengeNonceStore challengeNonceStore; + + public AuthController(IAuthTokenValidator authTokenValidator, IChallengeNonceStore challengeNonceStore) + { + this.authTokenValidator = authTokenValidator; + this.challengeNonceStore = challengeNonceStore; + } + + [HttpPost] + [Route("login")] + public async Task Login([FromBody] AuthenticateRequestDto authToken) + { + var certificate = await this.authTokenValidator.Validate(authToken.AuthToken, this.challengeNonceStore.GetAndRemove().Base64EncodedNonce); + var claims = new List + { + new Claim(ClaimTypes.GivenName, certificate.GetSubjectGivenName()), + new Claim(ClaimTypes.Surname, certificate.GetSubjectSurname()), + new Claim(ClaimTypes.NameIdentifier, certificate.GetSubjectIdCode()) + }; + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + + var authProperties = new AuthenticationProperties + { + AllowRefresh = true + }; + + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + authProperties); + } + + [HttpGet] + [Route("logout")] + public async Task Logout() + { + RemoveUserContainerFile(); + await HttpContext.SignOutAsync( + CookieAuthenticationDefaults.AuthenticationScheme); + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs new file mode 100644 index 0000000..b3a6b16 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs @@ -0,0 +1,21 @@ +namespace WebEid.AspNetCore.Example.Controllers.Api +{ + using System.Security; + using System.Security.Claims; + using Microsoft.AspNetCore.Mvc; + + public abstract class BaseController : ControllerBase + { + protected void RemoveUserContainerFile() + { + System.IO.File.Delete(GetUserContainerName()); + } + + protected string GetUserContainerName() + { + var identity = (ClaimsIdentity)this.HttpContext.User?.Identity ?? + throw new SecurityException("User is not logged in"); + return identity.GetIdCode(); + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/ChallengeController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/ChallengeController.cs new file mode 100644 index 0000000..13573cf --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/ChallengeController.cs @@ -0,0 +1,30 @@ +namespace WebEid.AspNetCore.Example.Controllers.Api +{ + using Microsoft.AspNetCore.Mvc; + using Security.Challenge; + using System; + using WebEid.AspNetCore.Example.Dto; + + [Route("auth")] + [ApiController] + public class ChallengeController : BaseController + { + private readonly IChallengeNonceGenerator challengeNonceGenerator; + + public ChallengeController(IChallengeNonceGenerator challengeNonceGenerator) + { + this.challengeNonceGenerator = challengeNonceGenerator; + } + + [HttpGet] + [Route("challenge")] + public ChallengeDto GetChallenge() + { + var challenge = new ChallengeDto + { + Nonce = challengeNonceGenerator.GenerateAndStoreNonce(TimeSpan.FromMinutes(5)).Base64EncodedNonce + }; + return challenge; + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs new file mode 100644 index 0000000..6c9ae04 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs @@ -0,0 +1,57 @@ +namespace WebEid.AspNetCore.Example.Controllers.Api +{ + using System; + using System.Security.Claims; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Logging; + using Services; + using WebEid.AspNetCore.Example.Dto; + + [Route("[controller]")] + [ApiController] + public class SignController : BaseController + { + private const string SignedFile = "example-for-signing.asice"; + private readonly SigningService signingService; + private readonly ILogger logger; + + public SignController(SigningService signingService, ILogger logger) + { + this.signingService = signingService; + this.logger = logger; + } + + [Route("prepare")] + [HttpPost] + public DigestDto Prepare([FromBody] CertificateDto data) + { + return signingService.PrepareContainer(data, (ClaimsIdentity)HttpContext.User.Identity, GetUserContainerName()); + } + + [Route("sign")] + [HttpPost] + public FileDto Sign([FromBody] SignatureDto data) + { + signingService.SignContainer(data, GetUserContainerName()); + return new FileDto(SignedFile); + } + + [Route("download")] + [HttpGet] + public async Task Download() + { + try + { + var content = await System.IO.File.ReadAllBytesAsync(GetUserContainerName()); + return File(content, "application/vnd.etsi.asic-e+zip", SignedFile); + } + catch (Exception ex) + { + logger?.LogError(ex, "Error occurred while downloading user container file"); + return BadRequest(); + } + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/WelcomeController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/WelcomeController.cs new file mode 100644 index 0000000..1a0d09b --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Controllers/WelcomeController.cs @@ -0,0 +1,13 @@ +namespace WebEid.AspNetCore.Example.Controllers +{ + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + + public class WelcomeController : Controller + { + public IActionResult Index() + { + return View(); + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/DigiDoc/.gitkeep b/example/src/WebEid.AspNetCore.Example/DigiDoc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/example/src/WebEid.AspNetCore.Example/Dto/AuthenticateRequestDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/AuthenticateRequestDto.cs new file mode 100644 index 0000000..19f1734 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Dto/AuthenticateRequestDto.cs @@ -0,0 +1,11 @@ +namespace WebEid.AspNetCore.Example.Dto +{ + using System.Text.Json.Serialization; + using Security.AuthToken; + + public class AuthenticateRequestDto + { + [JsonPropertyName("auth-token")] + public WebEidAuthToken AuthToken { get; set; } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Dto/CertificateDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/CertificateDto.cs new file mode 100644 index 0000000..c2bc73d --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Dto/CertificateDto.cs @@ -0,0 +1,12 @@ +namespace WebEid.AspNetCore.Example.Dto +{ + using System.Collections.Generic; + using System.Text.Json.Serialization; + + public class CertificateDto + { + [JsonPropertyName("certificate")] + public string CertificateBase64String { get; set; } + public IList SupportedSignatureAlgorithms { get; set; } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Dto/ChallengeDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/ChallengeDto.cs new file mode 100644 index 0000000..f1022c1 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Dto/ChallengeDto.cs @@ -0,0 +1,7 @@ +namespace WebEid.AspNetCore.Example.Dto +{ + public class ChallengeDto + { + public string Nonce { get; set; } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Dto/DigestDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/DigestDto.cs new file mode 100644 index 0000000..d6b29d2 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Dto/DigestDto.cs @@ -0,0 +1,8 @@ +namespace WebEid.AspNetCore.Example.Dto +{ + public class DigestDto + { + public string Hash { get; set; } + public string HashFunction { get; set; } + } +} \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/Dto/FileDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/FileDto.cs new file mode 100644 index 0000000..e6bc6d0 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Dto/FileDto.cs @@ -0,0 +1,12 @@ +namespace WebEid.AspNetCore.Example.Dto +{ + public class FileDto + { + public FileDto(string name) + { + this.Name = name; + } + + public string Name { get; } + } +} \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/Dto/SignatureAlgorithmDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/SignatureAlgorithmDto.cs new file mode 100644 index 0000000..7561afd --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Dto/SignatureAlgorithmDto.cs @@ -0,0 +1,9 @@ +namespace WebEid.AspNetCore.Example.Dto +{ + public class SignatureAlgorithmDto + { + public string CryptoAlgorithm { get; set; } + public string HashFunction { get; set; } + public string PaddingScheme { get; set; } + } +} \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/Dto/SignatureDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/SignatureDto.cs new file mode 100644 index 0000000..59479ad --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Dto/SignatureDto.cs @@ -0,0 +1,12 @@ +namespace WebEid.AspNetCore.Example.Dto +{ + public class SignatureDto + { + public SignatureAlgorithmDto SignatureAlgorithm { get; set; } + + /// + /// Base64 signature + /// + public string Signature { get; set; } + } +} \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/LoggedInAuthorizationHandler.cs b/example/src/WebEid.AspNetCore.Example/LoggedInAuthorizationHandler.cs new file mode 100644 index 0000000..bac3585 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/LoggedInAuthorizationHandler.cs @@ -0,0 +1,21 @@ +namespace WebEid.AspNetCore.Example +{ + using Microsoft.AspNetCore.Authorization; + using System.Threading.Tasks; + + public class LoggedInAuthorizationHandler : AuthorizationHandler + { + protected override Task HandleRequirementAsync( + AuthorizationHandlerContext context, LoggedInRequirement requirement) + { + if (context.User.Identity.IsAuthenticated) + { + context.Succeed(requirement); + } + return Task.CompletedTask; + } + } + + public class LoggedInRequirement : IAuthorizationRequirement { } + +} diff --git a/example/src/WebEid.AspNetCore.Example/Pages/Index.cshtml b/example/src/WebEid.AspNetCore.Example/Pages/Index.cshtml new file mode 100644 index 0000000..d25e828 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Pages/Index.cshtml @@ -0,0 +1,117 @@ +@page +@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf + + + + + + + Web eID: electronic ID smart cards on the Web + + + + +
+
+
+

Web eID: electronic ID smart cards on the Web

+

+ The Web eID project enables usage of European Union electronic identity (eID) smart cards for + secure authentication and digital signing of documents on the web using public-key cryptography. +

+

+ Estonian, Finnish, Latvian, Lithuanian and Croatian eID cards are supported in the first phase, but only + Estonian eID card support is currently enabled in the test application below. +

+

+ Please get in touch by email at help@ria.ee in case you need support with adding Web eID to your project + or want to add support for a new eID card to Web eID. +

+ +
+ +

+ More information about the Web eID project, including installation and usage instructions + is available on the project [website](https://web-eid.eu/). +

+

Click Authenticate below to test authentication and digital signing.

+ + +

+ +

+ +

+ The privacy policy of the test service is available here. +

+
+
+
+ +
+ EU fund flags +
+ + + + diff --git a/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml b/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml new file mode 100644 index 0000000..3f7eaad --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml @@ -0,0 +1,131 @@ +@page +@model WebEid.AspNetCore.Example.Pages.WelcomeModel +@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf + + + + + + + Welcome! + + + + +
+
+
+
+

Digital signing

+

Welcome, @Model.PrincipalName!

+
+ +
+ +
+

+ To test digital signing, you can sign the following document by clicking + Sign document below: +

+ +
+ + +

+ + +

+
+ +
+
+ + + + diff --git a/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs b/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs new file mode 100644 index 0000000..5345f36 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs @@ -0,0 +1,22 @@ +namespace WebEid.AspNetCore.Example.Pages +{ + using System.Linq; + using System.Security.Claims; + using Microsoft.AspNetCore.Mvc.RazorPages; + + public class WelcomeModel : PageModel + { + public string PrincipalName => GetPrincipalName((ClaimsIdentity)this.User.Identity); + + private static string GetPrincipalName(ClaimsIdentity identity) + { + var givenName = identity.Claims.Where(claim => claim.Type == ClaimTypes.GivenName) + .Select(claim => claim.Value) + .SingleOrDefault(); + var surname = identity.Claims.Where(claim => claim.Type == ClaimTypes.Surname) + .Select(claim => claim.Value) + .SingleOrDefault(); + return $"{givenName} {surname}"; + } + } +} \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/Pages/_ViewStart.cshtml b/example/src/WebEid.AspNetCore.Example/Pages/_ViewStart.cshtml new file mode 100644 index 0000000..be2a4f3 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Pages/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = null; +} diff --git a/example/src/WebEid.AspNetCore.Example/Program.cs b/example/src/WebEid.AspNetCore.Example/Program.cs new file mode 100644 index 0000000..1d41942 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Program.cs @@ -0,0 +1,20 @@ +namespace WebEid.AspNetCore.Example +{ + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Hosting; + + public static class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/Properties/launchSettings.json b/example/src/WebEid.AspNetCore.Example/Properties/launchSettings.json new file mode 100644 index 0000000..a2e5ff7 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:60500", + "sslPort": 44391 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "publishAllPorts": true, + "useSSL": true + } + } +} \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/SessionBackedChallengeNonceStore.cs b/example/src/WebEid.AspNetCore.Example/SessionBackedChallengeNonceStore.cs new file mode 100644 index 0000000..2ae5059 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/SessionBackedChallengeNonceStore.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Http; +using System.Text.Json; +using WebEid.Security.Challenge; + +namespace WebEid.AspNetCore.Example +{ + public class SessionBackedChallengeNonceStore : IChallengeNonceStore + { + private const string ChallengeNonceKey = "challenge-nonce"; + private readonly IHttpContextAccessor httpContextAccessor; + + public SessionBackedChallengeNonceStore(IHttpContextAccessor httpContextAccessor) + { + this.httpContextAccessor = httpContextAccessor; + } + + public void Put(ChallengeNonce challengeNonce) + { + this.httpContextAccessor.HttpContext.Session.SetString(ChallengeNonceKey, JsonSerializer.Serialize(challengeNonce)); + } + + public ChallengeNonce GetAndRemoveImpl() + { + var httpContext = this.httpContextAccessor.HttpContext; + var challenceNonceJson = httpContext.Session.GetString(ChallengeNonceKey); + if (!string.IsNullOrWhiteSpace(challenceNonceJson)) + { + httpContext.Session.Remove(ChallengeNonceKey); + return JsonSerializer.Deserialize(challenceNonceJson); + } + return null; + } + } +} \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/Signing/DigiDocConfiguration.cs b/example/src/WebEid.AspNetCore.Example/Signing/DigiDocConfiguration.cs new file mode 100644 index 0000000..71f2fef --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Signing/DigiDocConfiguration.cs @@ -0,0 +1,61 @@ +namespace WebEid.AspNetCore.Example.Services +{ + using digidoc; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Hosting; + using System; + + public class DigiDocConfiguration + { + /// + /// Base64 cert from https://open-eid.github.io/test-TL/trusted-test-tsl.crt + /// + private const string TestTslCert = @"MIIEvDCCAqQCCQCL/COUVyiGjTANBgkqhkiG9w0BAQUFADAgMQswCQYDVQQGEwJF +RTERMA8GA1UEAwwIVGVzdCBUU0wwHhcNMTgxMTE1MTI1MjU1WhcNMjgxMTEyMTI1 +MjU1WjAgMQswCQYDVQQGEwJFRTERMA8GA1UEAwwIVGVzdCBUU0wwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDfFK0fYeGrdngMZXZndDEpcl9pjGGNpbie +3+ch5mDqObUe+OL45b4+SfPapriVRNBa+m5T1TuijP7Kb8sTNS9U3WQYvY8bEstP +ZnaEvdQSSVRf4j9eVg+RTJ8Y4jjZ02GbLwrpELD2Qs+ohCl8e64G29qutchv6nJq +OdbL5U+d6DKyrzSpZyMRPA+UmB78KsBTs0o3wME7IA9J37YgtpUZifcC4LdgTWrX +2eBICGPqi7GGKzdnI5LDhCJZnHwzva+6lBwa8fW5aXQG69uPTFmd/pNNF6+8f2Fc +YGljQiD6FYVKAUfYRBlw9ymKaIbNmyh9bs71ezPrI4ltOLjmZLZFRouSIaeExfzj +2FkWERNG/iAuEIRolyyXjqjiQIuiEi8uo6sg1cPrD59EuWtTcMzTxuhVU8Ra37F6 +DrMEipqh84zQcnT0i/RNk4K723aB9uWwHJgJ5Y2/6cbta7ZkYsfQfjBC4nBRVyUl +BCpEFYNePbKttYF5Cf5FraMlGzAY0W/MSIUxvRmlkjCzBod4LA+K/hQxEiw7Xa8O +AZfw9l9lmSnia+fgRz3fLKxg3yklw6rA/2aISb83uVRvxgqKym3EeJ/+CsOQpwOb +lEBxWfQizah1Ct4NhsuKLmBbopxAXLqz25E+3BvvsM4nuwWVfoyvTVXYQ+k4V/hj +2iS5buJ5twIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQAGTw5MJurTeeWy+jQikGfi +vrxt9lzqt+uSV8D6V1GBzBAl8m4SqSY0U8KM/gtqh9bhmQwm0qgx/mKcDKzCUKaj +XPKm/NbR+pjZD9Lcx4Iy0iqi9rsxSKECGM2dYAmm7GXnXvz9QUxZjteTgYoRP2s6 +GfosvTQiUEr/cIrYAU3wC0/94pRb9/FLVVon/aVdsh+Dqb4j7BhKLzXNCNjkv1Sv +/YL1zpe/2SPxe0Bfymys97lcu1DB01e/MLfqQJThYOblMte/zGNZO24HcvROIkyo +UtYy5/H4F5rsamSGMNdBfauTtYxz7lOT7qQoDNyGMN9bfjWnkVi/lV2CVooeiHIs +7wLWEhYmU9DiAzcmODU9uMRRBlGOWK8UQg05exc518heICmudSbgSyQLGqzVoI4k +ybhmBA3w93KEXJSXlnU7hBzoYDP2d1g46Ay59UtvLycS1kxe0jVjxxRnh/f9aPbM +wUYBzEC0naUzMeJtElHLHgW4HT6PLgFImgLLFh8dnYJUzn35wz10g3YBA61YUJuO +DpapKHixn/2X/t/8Vf1vqr/VwiwUglNQj+P78Fdb3T56JsYRG1bdf6nz5dvv4qtL +oG+OjPI/tiLjh2ktqaMjeVmlQFchy/C5Lr48d9IGmo+x2ECYSWVvwzxI7PIbYBI4 +oaPjh2zKIrz/AlY2RmqMMA=="; + private const string TestTslUrl = "https://open-eid.github.io/test-TL/tl-mp-test-EE.xml"; + private const string TestTsUrl = "http://demo.sk.ee/tsa/"; + + private readonly IWebHostEnvironment env; + + public DigiDocConfiguration(IWebHostEnvironment env) + { + this.env = env; + } + + public void Initialize() + { + var conf = new DigiDocConf(""); + if (env.IsDevelopment()) + { + conf.setTSLUrl(TestTslUrl); + conf.setTSLCert(Convert.FromBase64String(TestTslCert)); + conf.setTSUrl(TestTsUrl); + } + DigiDocConf.init(conf); + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs new file mode 100644 index 0000000..a6d0ac2 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs @@ -0,0 +1,88 @@ +namespace WebEid.AspNetCore.Example.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; + using System.Security.Cryptography.X509Certificates; + using digidoc; + using Dto; + using Microsoft.Extensions.Logging; + using WebEid.Security.Util; + + public class SigningService + { + private const string FileToSign = @"wwwroot\files\example-for-signing.txt"; + private readonly DigiDocConfiguration configuration; + private readonly ILogger logger; + + public SigningService(DigiDocConfiguration configuration, ILogger logger) + { + this.configuration = configuration; + this.logger = logger; + } + + public DigestDto PrepareContainer(CertificateDto data, ClaimsIdentity identity, string tempContainerName) + { + var certificate = new X509Certificate(Convert.FromBase64String(data.CertificateBase64String)); + if (identity.GetIdCode() != certificate.GetSubjectIdCode()) + { + throw new ArgumentException( + "Authenticated subject ID code differs from signing certificate subject ID code"); + } + + configuration.Initialize(); + digidoc.initialize("WebEidExample"); + try + { + this.logger?.LogDebug("Creating container file: '{0}'", tempContainerName); + Container container = Container.create(tempContainerName); + container.addDataFile(FileToSign, "application/octet-stream"); + logger?.LogInformation("Preparing container for signing for file '{0}'", tempContainerName); + var signature = + container.prepareWebSignature(certificate.Export(X509ContentType.Cert), "BES/time-stamp"); + container.save(); + return new DigestDto + { + Hash = Convert.ToBase64String(signature.dataToSign()), + HashFunction = GetSupportedHashAlgorithm(data.SupportedSignatureAlgorithms) + }; + } + finally + { + digidoc.terminate(); + } + } + + + public void SignContainer(SignatureDto signatureDto, string tempContainerName) + { + configuration.Initialize(); + digidoc.initialize("WebEidExample"); + try + { + var container = Container.open(tempContainerName); + var signatureBytes = Convert.FromBase64String(signatureDto.Signature); + var signature = container.signatures().First(); // Container must have one signature as it was added in PrepareContainer + signature.setSignatureValue(signatureBytes); + signature.extendSignatureProfile("BES/time-stamp"); + container.save(); + } + finally + { + digidoc.terminate(); + } + } + + private static string GetSupportedHashAlgorithm(IList supportedSignatureAlgorithms) + { + var algorithmNames = supportedSignatureAlgorithms.Select(a => a.HashFunction).ToArray(); + return algorithmNames switch + { + var a when a.Contains("SHA-384") => "SHA-384", + var a when a.Contains("SHA-256") => "SHA-256", + _ => throw new ArgumentException("SHA-384 or SHA-256 algorithm must be supported") + }; + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/Startup.cs b/example/src/WebEid.AspNetCore.Example/Startup.cs new file mode 100644 index 0000000..fe6fedf --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Startup.cs @@ -0,0 +1,147 @@ +namespace WebEid.AspNetCore.Example +{ + using Certificates; + using Microsoft.AspNetCore.Authentication.Cookies; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Logging; + using Services; + using System; + using System.Security.Cryptography; + using Security.Challenge; + using Security.Validator; + using System.Configuration; + using Microsoft.AspNetCore.Authorization; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + + public class Startup + { + public Startup(IConfiguration configuration, IWebHostEnvironment environment) + { + Configuration = configuration; + CurrentEnvironment = environment; + } + + private IConfiguration Configuration { get; } + private IWebHostEnvironment CurrentEnvironment { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + }); + var logger = loggerFactory.CreateLogger("Web-eId ASP.NET Core Example"); + services.AddSingleton(logger); + + services.AddRazorPages(options => + { + options.Conventions.AuthorizePage("/welcome", "LoggedInOnly"); + }); + + services.AddAuthorization(options => + { + options.AddPolicy("LoggedInOnly", policy => + { + policy.AuthenticationSchemes.Add(CookieAuthenticationDefaults.AuthenticationScheme); + policy.RequireAuthenticatedUser(); + policy.Requirements.Add(new LoggedInRequirement()); + }); + }); + + services.AddSingleton(); + + services.AddControllers(); + services.AddMvc(options => + { + options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); + }); + + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => + { + options.Cookie.Name = "WebEid.AspNeCore.Example.Auth"; + options.Cookie.SameSite = SameSiteMode.Strict; + options.Events.OnRedirectToLogin = context => + { + context.Response.Redirect("/"); + return Task.CompletedTask; + }; + options.Events.OnRedirectToAccessDenied = context => + { + context.Response.Redirect("/"); + return Task.CompletedTask; + }; + }); + + services.AddSession(options => + { + options.Cookie.Name = "WebEid.AspNetCore.Example.Session"; + options.IdleTimeout = TimeSpan.FromSeconds(60); + options.Cookie.IsEssential = true; + }); + + var url = GetOriginUrl(Configuration); + + services.AddSingleton(new AuthTokenValidatorBuilder(logger) + .WithSiteOrigin(url) + .WithTrustedCertificateAuthorities(CertificateLoader.LoadTrustedCaCertificatesFromDisk(CurrentEnvironment.IsDevelopment())) + .Build()); + + services.AddSingleton(RandomNumberGenerator.Create()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddAntiforgery(); + } + + private static Uri GetOriginUrl(IConfiguration configuration) + { + var url = configuration["OriginUrl"]; + if (string.IsNullOrWhiteSpace(url)) + { + throw new ConfigurationErrorsException("OriginUrl is not configured"); + } + + return new Uri(url); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseAuthorization(); + app.UseAuthentication(); + app.UseSession(); + + app.UseEndpoints(endpoints => + { + endpoints.MapRazorPages(); + endpoints.MapControllers(); + }); + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj new file mode 100644 index 0000000..cd939e9 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj @@ -0,0 +1,46 @@ + + + + net6.0 + aspnet-web_eid_asp_dotnet_example-3FF31439-FDC0-4164-B154-03E005FE96CD + WebEid.AspNetCore.Example + false + Linux + WebEid.AspNetCore.Example + true + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + + + + PreserveNewest + + + + diff --git a/example/src/WebEid.AspNetCore.Example/appsettings.Development.json b/example/src/WebEid.AspNetCore.Example/appsettings.Development.json new file mode 100644 index 0000000..a0f43a0 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "OriginUrl": "https://localhost:44391", + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/example/src/WebEid.AspNetCore.Example/appsettings.json b/example/src/WebEid.AspNetCore.Example/appsettings.json new file mode 100644 index 0000000..7d0c71b --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/appsettings.json @@ -0,0 +1,11 @@ +{ + "OriginUrl": "https://localhost:44391", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/css/bootstrap.min.css b/example/src/WebEid.AspNetCore.Example/wwwroot/css/bootstrap.min.css new file mode 100644 index 0000000..7d2a868 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/wwwroot/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.5.0 (https://getbootstrap.com/) + * Copyright 2011-2020 The Bootstrap Authors + * Copyright 2011-2020 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;min-width:0;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item{display:-ms-flexbox;display:flex}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;-ms-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/css/main.css b/example/src/WebEid.AspNetCore.Example/wwwroot/css/main.css new file mode 100644 index 0000000..3de3421 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/wwwroot/css/main.css @@ -0,0 +1,69 @@ +body { + font-family: "Inv Maison Neue","Maison Neue",-apple-system,BlinkMacSystemFont,"Open Sans",open-sans,sans-serif; +} + +.has-advanced-upload { + outline: 2px dashed #92b0b3; + outline-offset: -10px; + + -webkit-transition: outline-offset .25s ease-in-out, background-color .25s linear; + transition: outline-offset .25s ease-in-out, background-color .25s linear; +} + +.adding-signature { + height: 14px; + color: #000000; + font-size: 32px; + font-weight: bold; + letter-spacing: 0; + line-height: 14px; +} + +.welcome-line { + height: 40px; + color: #000000; + font-size: 14px; + letter-spacing: 0; + line-height: 20px; + margin-top: 1rem; +} + +#webeid-logout-button { + height: 2.5rem; +} + +#file-drop-area { + margin: 0 10rem; + padding-top: 2rem; + box-sizing: border-box; + outline: 2px dashed #92b0b3; + outline-offset: -10px; + border-radius: 3px; + background-color: #F5F5F5; +} + +.is-dragover { + background-color: #b7dbde !important; + outline-offset: -20px !important; + outline-color: #ffffff !important; +} + +#file-name, #file-drop-area, .welcome-line { + text-align: center; +} + +.eu-logo-fixed { + display: block; + position: fixed; + background: #fff; + bottom: 0; + left: 0; + margin: 0; + padding: 5px 20px; + text-align: center; + z-index: 1000; +} + +.eu-logo-fixed img { + height: 86px; +} diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/favicon.ico b/example/src/WebEid.AspNetCore.Example/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..63e859b476eff5055e0e557aaa151ca8223fbeef GIT binary patch literal 5430 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 XZz9n9el{XBaPz`JLPBTs)j|IQPi<*H literal 0 HcmV?d00001 diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/files/example-for-signing.txt b/example/src/WebEid.AspNetCore.Example/wwwroot/files/example-for-signing.txt new file mode 100644 index 0000000..07a5be0 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/wwwroot/files/example-for-signing.txt @@ -0,0 +1 @@ +This is an example text file for testing digital signing. \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/img/eu-fund-flags.svg b/example/src/WebEid.AspNetCore.Example/wwwroot/img/eu-fund-flags.svg new file mode 100644 index 0000000..0e99b0d --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/wwwroot/img/eu-fund-flags.svg @@ -0,0 +1,787 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js b/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js new file mode 100644 index 0000000..7bc8c92 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 The Web eID Project + * + * 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. + */ + +"use strict"; + +const alertUi = { + alert: document.querySelector("#error-message"), + alertMessage: document.querySelector("#error-message .message"), + alertDetails: document.querySelector("#error-message .details") +}; + +export function hideErrorMessage() { + alertUi.alert.style.display = "none"; +} + +export function showErrorMessage(error) { + const message = "Authentication failed"; + const details = + `[Code]\n${error.code}` + + `\n\n[Message]\n${error.message}` + + (error.response ? `\n\n[response]\n${JSON.stringify(error.response, null, " ")}` : ""); + + alertUi.alertMessage.innerText = message; + alertUi.alertDetails.innerText = details; + alertUi.alert.style.display = "block"; +} + +export async function checkHttpError(response) { + if (!response.ok) { + let body; + try { + body = await response.text(); + } catch (error) { + body = "<>"; + } + const error = new Error("Server error: " + body); + error.code = response.status; + throw error; + } +} \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/js/web-eid.js b/example/src/WebEid.AspNetCore.Example/wwwroot/js/web-eid.js new file mode 100644 index 0000000..803c911 --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/wwwroot/js/web-eid.js @@ -0,0 +1,431 @@ +/** + * MIT License + * + * Copyright (c) 2020-2021 Estonian Information System Authority + * + * 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. + */ + +var Action; +(function (Action) { + Action["WARNING"] = "web-eid:warning"; + Action["STATUS"] = "web-eid:status"; + Action["STATUS_ACK"] = "web-eid:status-ack"; + Action["STATUS_SUCCESS"] = "web-eid:status-success"; + Action["STATUS_FAILURE"] = "web-eid:status-failure"; + Action["AUTHENTICATE"] = "web-eid:authenticate"; + Action["AUTHENTICATE_ACK"] = "web-eid:authenticate-ack"; + Action["AUTHENTICATE_SUCCESS"] = "web-eid:authenticate-success"; + Action["AUTHENTICATE_FAILURE"] = "web-eid:authenticate-failure"; + Action["GET_SIGNING_CERTIFICATE"] = "web-eid:get-signing-certificate"; + Action["GET_SIGNING_CERTIFICATE_ACK"] = "web-eid:get-signing-certificate-ack"; + Action["GET_SIGNING_CERTIFICATE_SUCCESS"] = "web-eid:get-signing-certificate-success"; + Action["GET_SIGNING_CERTIFICATE_FAILURE"] = "web-eid:get-signing-certificate-failure"; + Action["SIGN"] = "web-eid:sign"; + Action["SIGN_ACK"] = "web-eid:sign-ack"; + Action["SIGN_SUCCESS"] = "web-eid:sign-success"; + Action["SIGN_FAILURE"] = "web-eid:sign-failure"; +})(Action || (Action = {})); +var Action$1 = Action; + +var ErrorCode; +(function (ErrorCode) { + ErrorCode["ERR_WEBEID_ACTION_TIMEOUT"] = "ERR_WEBEID_ACTION_TIMEOUT"; + ErrorCode["ERR_WEBEID_USER_TIMEOUT"] = "ERR_WEBEID_USER_TIMEOUT"; + ErrorCode["ERR_WEBEID_SERVER_TIMEOUT"] = "ERR_WEBEID_SERVER_TIMEOUT"; + ErrorCode["ERR_WEBEID_VERSION_MISMATCH"] = "ERR_WEBEID_VERSION_MISMATCH"; + ErrorCode["ERR_WEBEID_VERSION_INVALID"] = "ERR_WEBEID_VERSION_INVALID"; + ErrorCode["ERR_WEBEID_EXTENSION_UNAVAILABLE"] = "ERR_WEBEID_EXTENSION_UNAVAILABLE"; + ErrorCode["ERR_WEBEID_NATIVE_UNAVAILABLE"] = "ERR_WEBEID_NATIVE_UNAVAILABLE"; + ErrorCode["ERR_WEBEID_UNKNOWN_ERROR"] = "ERR_WEBEID_UNKNOWN_ERROR"; + ErrorCode["ERR_WEBEID_CONTEXT_INSECURE"] = "ERR_WEBEID_CONTEXT_INSECURE"; + ErrorCode["ERR_WEBEID_PROTOCOL_INSECURE"] = "ERR_WEBEID_PROTOCOL_INSECURE"; + ErrorCode["ERR_WEBEID_TLS_CONNECTION_BROKEN"] = "ERR_WEBEID_TLS_CONNECTION_BROKEN"; + ErrorCode["ERR_WEBEID_TLS_CONNECTION_INSECURE"] = "ERR_WEBEID_TLS_CONNECTION_INSECURE"; + ErrorCode["ERR_WEBEID_TLS_CONNECTION_WEAK"] = "ERR_WEBEID_TLS_CONNECTION_WEAK"; + ErrorCode["ERR_WEBEID_CERTIFICATE_CHANGED"] = "ERR_WEBEID_CERTIFICATE_CHANGED"; + ErrorCode["ERR_WEBEID_ORIGIN_MISMATCH"] = "ERR_WEBEID_ORIGIN_MISMATCH"; + ErrorCode["ERR_WEBEID_SERVER_REJECTED"] = "ERR_WEBEID_SERVER_REJECTED"; + ErrorCode["ERR_WEBEID_USER_CANCELLED"] = "ERR_WEBEID_USER_CANCELLED"; + ErrorCode["ERR_WEBEID_NATIVE_INVALID_ARGUMENT"] = "ERR_WEBEID_NATIVE_INVALID_ARGUMENT"; + ErrorCode["ERR_WEBEID_NATIVE_FATAL"] = "ERR_WEBEID_NATIVE_FATAL"; + ErrorCode["ERR_WEBEID_ACTION_PENDING"] = "ERR_WEBEID_ACTION_PENDING"; + ErrorCode["ERR_WEBEID_MISSING_PARAMETER"] = "ERR_WEBEID_MISSING_PARAMETER"; +})(ErrorCode || (ErrorCode = {})); +var ErrorCode$1 = ErrorCode; + +class MissingParameterError extends Error { + constructor(message) { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_MISSING_PARAMETER; + } +} + +class ActionPendingError extends Error { + constructor(message = "same action for Web-eID browser extension is already pending") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_ACTION_PENDING; + } +} + +class ActionTimeoutError extends Error { + constructor(message = "extension message timeout") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_ACTION_TIMEOUT; + } +} + +const SECURE_CONTEXTS_INFO_URL = "https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts"; +class ContextInsecureError extends Error { + constructor(message = "Secure context required, see " + SECURE_CONTEXTS_INFO_URL) { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_CONTEXT_INSECURE; + } +} + +class ExtensionUnavailableError extends Error { + constructor(message = "Web-eID extension is not available") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_EXTENSION_UNAVAILABLE; + } +} + +var config = Object.freeze({ + VERSION: "2.0.0", + EXTENSION_HANDSHAKE_TIMEOUT: 1000, + NATIVE_APP_HANDSHAKE_TIMEOUT: 5 * 1000, + DEFAULT_USER_INTERACTION_TIMEOUT: 2 * 60 * 1000, + MAX_EXTENSION_LOAD_DELAY: 1000, +}); + +class NativeFatalError extends Error { + constructor(message = "native application terminated with a fatal error") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_NATIVE_FATAL; + } +} + +class NativeInvalidArgumentError extends Error { + constructor(message = "native application received an invalid argument") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_NATIVE_INVALID_ARGUMENT; + } +} + +class NativeUnavailableError extends Error { + constructor(message = "Web-eID native application is not available") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_NATIVE_UNAVAILABLE; + } +} + +class UnknownError extends Error { + constructor(message = "an unknown error occurred") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_UNKNOWN_ERROR; + } +} + +class UserCancelledError extends Error { + constructor(message = "request was cancelled by the user") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_USER_CANCELLED; + } +} + +class UserTimeoutError extends Error { + constructor(message = "user failed to respond in time") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_USER_TIMEOUT; + } +} + +class VersionInvalidError extends Error { + constructor(message = "invalid version string") { + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_VERSION_INVALID; + } +} + +function tmpl(strings, requiresUpdate) { + return `Update required for Web-eID ${requiresUpdate}`; +} +class VersionMismatchError extends Error { + constructor(message, versions, requiresUpdate) { + if (!message) { + if (!requiresUpdate) { + message = "requiresUpdate not provided"; + } + else if (requiresUpdate.extension && requiresUpdate.nativeApp) { + message = tmpl `${"extension and native app"}`; + } + else if (requiresUpdate.extension) { + message = tmpl `${"extension"}`; + } + else if (requiresUpdate.nativeApp) { + message = tmpl `${"native app"}`; + } + } + super(message); + this.name = this.constructor.name; + this.code = ErrorCode$1.ERR_WEBEID_VERSION_MISMATCH; + this.requiresUpdate = requiresUpdate; + if (versions) { + const { library, extension, nativeApp } = versions; + Object.assign(this, { library, extension, nativeApp }); + } + } +} + +const errorCodeToErrorClass = { + [ErrorCode$1.ERR_WEBEID_ACTION_PENDING]: ActionPendingError, + [ErrorCode$1.ERR_WEBEID_ACTION_TIMEOUT]: ActionTimeoutError, + [ErrorCode$1.ERR_WEBEID_CONTEXT_INSECURE]: ContextInsecureError, + [ErrorCode$1.ERR_WEBEID_EXTENSION_UNAVAILABLE]: ExtensionUnavailableError, + [ErrorCode$1.ERR_WEBEID_NATIVE_INVALID_ARGUMENT]: NativeInvalidArgumentError, + [ErrorCode$1.ERR_WEBEID_NATIVE_FATAL]: NativeFatalError, + [ErrorCode$1.ERR_WEBEID_NATIVE_UNAVAILABLE]: NativeUnavailableError, + [ErrorCode$1.ERR_WEBEID_USER_CANCELLED]: UserCancelledError, + [ErrorCode$1.ERR_WEBEID_USER_TIMEOUT]: UserTimeoutError, + [ErrorCode$1.ERR_WEBEID_VERSION_INVALID]: VersionInvalidError, + [ErrorCode$1.ERR_WEBEID_VERSION_MISMATCH]: VersionMismatchError, +}; +function deserializeError(errorObject) { + let error; + if (typeof errorObject.code == "string" && errorObject.code in errorCodeToErrorClass) { + const CustomError = errorCodeToErrorClass[errorObject.code]; + error = new CustomError(); + } + else { + error = new UnknownError(); + } + for (const [key, value] of Object.entries(errorObject)) { + error[key] = value; + } + return error; +} + +class WebExtensionService { + constructor() { + this.loggedWarnings = []; + this.queue = []; + window.addEventListener("message", (event) => this.receive(event)); + } + receive(event) { + var _a, _b, _c, _d, _e, _f; + if (!/^web-eid:/.test((_a = event.data) === null || _a === void 0 ? void 0 : _a.action)) + return; + const message = event.data; + const suffix = (_c = (_b = message.action) === null || _b === void 0 ? void 0 : _b.match(/success$|failure$|ack$/)) === null || _c === void 0 ? void 0 : _c[0]; + const initialAction = this.getInitialAction(message.action); + const pending = this.getPendingMessage(initialAction); + if (message.action === Action$1.WARNING) { + (_d = message.warnings) === null || _d === void 0 ? void 0 : _d.forEach((warning) => { + if (!this.loggedWarnings.includes(warning)) { + this.loggedWarnings.push(warning); + console.warn(warning); + } + }); + } + else if (pending) { + switch (suffix) { + case "ack": { + clearTimeout(pending.ackTimer); + break; + } + case "success": { + this.removeFromQueue(initialAction); + (_e = pending.resolve) === null || _e === void 0 ? void 0 : _e.call(pending, message); + break; + } + case "failure": { + const failureMessage = message; + this.removeFromQueue(initialAction); + (_f = pending.reject) === null || _f === void 0 ? void 0 : _f.call(pending, failureMessage.error ? deserializeError(failureMessage.error) : failureMessage); + break; + } + } + } + } + send(message, timeout) { + if (this.getPendingMessage(message.action)) { + return Promise.reject(new ActionPendingError()); + } + else if (!window.isSecureContext) { + return Promise.reject(new ContextInsecureError()); + } + else { + const pending = { message }; + this.queue.push(pending); + pending.promise = new Promise((resolve, reject) => { + pending.resolve = resolve; + pending.reject = reject; + }); + pending.ackTimer = window.setTimeout(() => this.onAckTimeout(pending), config.EXTENSION_HANDSHAKE_TIMEOUT); + pending.replyTimer = window.setTimeout(() => this.onReplyTimeout(pending), timeout); + window.postMessage(message, "*"); + return pending.promise; + } + } + onReplyTimeout(pending) { + var _a; + this.removeFromQueue(pending.message.action); + (_a = pending.reject) === null || _a === void 0 ? void 0 : _a.call(pending, new ActionTimeoutError()); + } + onAckTimeout(pending) { + var _a; + clearTimeout(pending.replyTimer); + this.removeFromQueue(pending.message.action); + (_a = pending.reject) === null || _a === void 0 ? void 0 : _a.call(pending, new ExtensionUnavailableError()); + } + getPendingMessage(action) { + return this.queue.find((pm) => { + return pm.message.action === action; + }); + } + getInitialAction(action) { + return action.replace(/-success$|-failure$|-ack$/, ""); + } + removeFromQueue(action) { + const pending = this.getPendingMessage(action); + clearTimeout(pending === null || pending === void 0 ? void 0 : pending.replyTimer); + this.queue = this.queue.filter((pending) => (pending.message.action !== action)); + } +} + +/** + * Sleeps for a specified time before resolving the returned promise. + * + * @param milliseconds Time in milliseconds until the promise is resolved + * + * @returns Empty promise + */ +function sleep(milliseconds) { + return new Promise((resolve) => { + setTimeout(() => resolve(), milliseconds); + }); +} + +const webExtensionService = new WebExtensionService(); +const initializationTime = +new Date(); +async function extensionLoadDelay() { + const now = +new Date(); + await sleep(initializationTime + config.MAX_EXTENSION_LOAD_DELAY - now); +} +async function status() { + await extensionLoadDelay(); + const timeout = config.EXTENSION_HANDSHAKE_TIMEOUT + config.NATIVE_APP_HANDSHAKE_TIMEOUT; + const message = { + action: Action$1.STATUS, + libraryVersion: config.VERSION, + }; + try { + const { library, extension, nativeApp, } = await webExtensionService.send(message, timeout); + return { + library, + extension, + nativeApp, + }; + } + catch (error) { + error.library = config.VERSION; + throw error; + } +} +async function authenticate(challengeNonce, options) { + await extensionLoadDelay(); + if (!challengeNonce) { + throw new MissingParameterError("authenticate function requires a challengeNonce"); + } + const timeout = (config.EXTENSION_HANDSHAKE_TIMEOUT + + config.NATIVE_APP_HANDSHAKE_TIMEOUT + + ((options === null || options === void 0 ? void 0 : options.userInteractionTimeout) || config.DEFAULT_USER_INTERACTION_TIMEOUT)); + const message = { + action: Action$1.AUTHENTICATE, + libraryVersion: config.VERSION, + challengeNonce, + options, + }; + const { unverifiedCertificate, algorithm, signature, format, appVersion, } = await webExtensionService.send(message, timeout); + return { + unverifiedCertificate, + algorithm, + signature, + format, + appVersion, + }; +} +async function getSigningCertificate(options) { + await extensionLoadDelay(); + const timeout = (config.EXTENSION_HANDSHAKE_TIMEOUT + + config.NATIVE_APP_HANDSHAKE_TIMEOUT + + ((options === null || options === void 0 ? void 0 : options.userInteractionTimeout) || config.DEFAULT_USER_INTERACTION_TIMEOUT) * 2); + const message = { + action: Action$1.GET_SIGNING_CERTIFICATE, + libraryVersion: config.VERSION, + options, + }; + const { certificate, supportedSignatureAlgorithms, } = await webExtensionService.send(message, timeout); + return { + certificate, + supportedSignatureAlgorithms, + }; +} +async function sign(certificate, hash, hashFunction, options) { + await extensionLoadDelay(); + if (!certificate) { + throw new MissingParameterError("sign function requires a certificate as parameter"); + } + if (!hash) { + throw new MissingParameterError("sign function requires a hash as parameter"); + } + if (!hashFunction) { + throw new MissingParameterError("sign function requires a hashFunction as parameter"); + } + const timeout = (config.EXTENSION_HANDSHAKE_TIMEOUT + + config.NATIVE_APP_HANDSHAKE_TIMEOUT + + ((options === null || options === void 0 ? void 0 : options.userInteractionTimeout) || config.DEFAULT_USER_INTERACTION_TIMEOUT) * 2); + const message = { + action: Action$1.SIGN, + libraryVersion: config.VERSION, + certificate, + hash, + hashFunction, + options, + }; + const { signature, signatureAlgorithm, } = await webExtensionService.send(message, timeout); + return { + signature, + signatureAlgorithm, + }; +} + +export { Action$1 as Action, ErrorCode$1 as ErrorCode, authenticate, config, getSigningCertificate, sign, status }; From c9532fc5ba526162863f62c8031b29fade45ae29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=20S=C3=B5mermaa?= Date: Fri, 20 May 2022 21:43:04 +0300 Subject: [PATCH 03/34] doc: update README.md, explain possible 32-bit/64-bit errors WE2-572 Signed-off-by: Mart Somermaa --- example/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/example/README.md b/example/README.md index 94e94e3..b25779f 100644 --- a/example/README.md +++ b/example/README.md @@ -42,7 +42,7 @@ Set up the `libdigidocpp` library as follows: 1. Install the _libdigidocpp-3.14.4.msi_ package or higher. The installation packages are available from [https://github.com/open-eid/libdigidocpp/releases](https://github.com/open-eid/libdigidocpp/releases). 2. Copy the C# source files from the `libdigidocpp` installation folder `include\digidocpp_csharp` to the `src\WebEid.AspNetCore.Example\DigiDoc` folder. -3. Copy all files from `libdigidocpp` installation folder `x64` subfolder to the example application build output folder `bin\...\net60` (after building, see next step). +3. Copy all files from either the `x64` subfolder of the `libdigidocpp` installation folder to the example application build output folder `bin\...\net60` (after building, see next step). When building custom applications, choose `x64` if your application is 64-bit and `x86` if it is 32-bit. 4. When running in the `Development` profile, create an empty file named `EE_T.xml` for TSL cache as described in the [_Using test TSL lists_](https://github.com/open-eid/libdigidocpp/wiki/Using-test-TSL-lists#preconditions) section of the `libdigidocpp` wiki. Further information is available in the [libdigidocpp example C# application](https://github.com/open-eid/libdigidocpp/tree/master/examples/DigiDocCSharp) and in the [`libdigidocpp` wiki](https://github.com/open-eid/libdigidocpp/wiki). @@ -101,3 +101,7 @@ See the [Web eID Java example application documentation](https://github.com/web- #### Why do I get the `System.ApplicationException: Failed to verify OCSP Responder certificate` error during signing? You are running in the `Development` profile, but you have not created an empty file named `EE_T.xml` for TSL cache. Creating the file is mandatory and is described in more detail in the [_Using test TSL lists_](https://github.com/open-eid/libdigidocpp/wiki/Using-test-TSL-lists#preconditions) section of the `libdigidocpp` wiki. + +#### Why do I get the `System.BadImageFormatException: An attempt was made to load a program with an incorrect format` error during signing? + +You are using `libdigidocpp` DLLs for the wrong architecture. Copy files from the `x64` subfolder of the `libdigidocpp` installation folder to right place as described in the section _3. Setup the `libdigidocpp` library for signing_ above. In case you get this error while developing a custom 32-bit application, copy files from the `x86` subfolder instead. From 5ab932dcd73bf5cf94e5fbb0cbfbeb1adf7657c7 Mon Sep 17 00:00:00 2001 From: Kristel Merilain Date: Thu, 2 Jun 2022 13:26:08 +0300 Subject: [PATCH 04/34] Update LICENSE --- example/LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/LICENSE b/example/LICENSE index 9783de8..58b9706 100644 --- a/example/LICENSE +++ b/example/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 eID on platform Web +Copyright (c) 2021-2022 Estonian Information System Authority Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c9d141dbc0c70f674cf59615c8ef24aeecafc4cd Mon Sep 17 00:00:00 2001 From: Kristel Merilain Date: Thu, 27 Apr 2023 09:10:21 +0300 Subject: [PATCH 05/34] Update copyright year (#4) * Update copyright year * Update copyright year --- example/LICENSE | 2 +- example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/LICENSE b/example/LICENSE index 58b9706..ebbc5e6 100644 --- a/example/LICENSE +++ b/example/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2022 Estonian Information System Authority +Copyright (c) 2021-2023 Estonian Information System Authority Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js b/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js index 7bc8c92..1665e6d 100644 --- a/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js +++ b/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 The Web eID Project + * Copyright (c) 2020-2023 Estonian Information System Authority * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From efac302e1fc419d4af9b3a739602cadd41b12aa7 Mon Sep 17 00:00:00 2001 From: Erkki Arus Date: Mon, 17 Jul 2023 16:33:41 +0300 Subject: [PATCH 06/34] Updates web-eid.js to use the latest version 2.0.1 Signed-off-by: Erkki Arus --- .../WebEid.AspNetCore.Example/wwwroot/js/web-eid.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/js/web-eid.js b/example/src/WebEid.AspNetCore.Example/wwwroot/js/web-eid.js index 803c911..ee9b770 100644 --- a/example/src/WebEid.AspNetCore.Example/wwwroot/js/web-eid.js +++ b/example/src/WebEid.AspNetCore.Example/wwwroot/js/web-eid.js @@ -1,7 +1,7 @@ /** * MIT License * - * Copyright (c) 2020-2021 Estonian Information System Authority + * Copyright (c) 2020-2022 Estonian Information System Authority * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -48,20 +48,12 @@ var ErrorCode; (function (ErrorCode) { ErrorCode["ERR_WEBEID_ACTION_TIMEOUT"] = "ERR_WEBEID_ACTION_TIMEOUT"; ErrorCode["ERR_WEBEID_USER_TIMEOUT"] = "ERR_WEBEID_USER_TIMEOUT"; - ErrorCode["ERR_WEBEID_SERVER_TIMEOUT"] = "ERR_WEBEID_SERVER_TIMEOUT"; ErrorCode["ERR_WEBEID_VERSION_MISMATCH"] = "ERR_WEBEID_VERSION_MISMATCH"; ErrorCode["ERR_WEBEID_VERSION_INVALID"] = "ERR_WEBEID_VERSION_INVALID"; ErrorCode["ERR_WEBEID_EXTENSION_UNAVAILABLE"] = "ERR_WEBEID_EXTENSION_UNAVAILABLE"; ErrorCode["ERR_WEBEID_NATIVE_UNAVAILABLE"] = "ERR_WEBEID_NATIVE_UNAVAILABLE"; ErrorCode["ERR_WEBEID_UNKNOWN_ERROR"] = "ERR_WEBEID_UNKNOWN_ERROR"; ErrorCode["ERR_WEBEID_CONTEXT_INSECURE"] = "ERR_WEBEID_CONTEXT_INSECURE"; - ErrorCode["ERR_WEBEID_PROTOCOL_INSECURE"] = "ERR_WEBEID_PROTOCOL_INSECURE"; - ErrorCode["ERR_WEBEID_TLS_CONNECTION_BROKEN"] = "ERR_WEBEID_TLS_CONNECTION_BROKEN"; - ErrorCode["ERR_WEBEID_TLS_CONNECTION_INSECURE"] = "ERR_WEBEID_TLS_CONNECTION_INSECURE"; - ErrorCode["ERR_WEBEID_TLS_CONNECTION_WEAK"] = "ERR_WEBEID_TLS_CONNECTION_WEAK"; - ErrorCode["ERR_WEBEID_CERTIFICATE_CHANGED"] = "ERR_WEBEID_CERTIFICATE_CHANGED"; - ErrorCode["ERR_WEBEID_ORIGIN_MISMATCH"] = "ERR_WEBEID_ORIGIN_MISMATCH"; - ErrorCode["ERR_WEBEID_SERVER_REJECTED"] = "ERR_WEBEID_SERVER_REJECTED"; ErrorCode["ERR_WEBEID_USER_CANCELLED"] = "ERR_WEBEID_USER_CANCELLED"; ErrorCode["ERR_WEBEID_NATIVE_INVALID_ARGUMENT"] = "ERR_WEBEID_NATIVE_INVALID_ARGUMENT"; ErrorCode["ERR_WEBEID_NATIVE_FATAL"] = "ERR_WEBEID_NATIVE_FATAL"; @@ -112,7 +104,7 @@ class ExtensionUnavailableError extends Error { } var config = Object.freeze({ - VERSION: "2.0.0", + VERSION: "2.0.1", EXTENSION_HANDSHAKE_TIMEOUT: 1000, NATIVE_APP_HANDSHAKE_TIMEOUT: 5 * 1000, DEFAULT_USER_INTERACTION_TIMEOUT: 2 * 60 * 1000, From 6195bbef18bb334c9af73dd63d8b362f55a26ae1 Mon Sep 17 00:00:00 2001 From: Mart Somermaa Date: Fri, 3 Nov 2023 23:00:31 +0200 Subject: [PATCH 07/34] Add Docker and TLS terminating proxy support WE2-835 Signed-off-by: Mart Somermaa --- example/.github/workflows/dotnet-build.yml | 6 +- example/.gitignore | 4 + example/README.md | 90 ++++++++++++++++++ example/src/.dockerignore | 4 +- example/src/Dockerfile | 22 +++++ .../Signing/SigningService.cs | 5 +- .../src/WebEid.AspNetCore.Example/Startup.cs | 14 ++- example/src/docker-compose.yml | 8 ++ example/src/ria_public_key.gpg | Bin 0 -> 2215 bytes 9 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 example/src/Dockerfile create mode 100644 example/src/docker-compose.yml create mode 100644 example/src/ria_public_key.gpg diff --git a/example/.github/workflows/dotnet-build.yml b/example/.github/workflows/dotnet-build.yml index 06af20e..debf937 100644 --- a/example/.github/workflows/dotnet-build.yml +++ b/example/.github/workflows/dotnet-build.yml @@ -34,7 +34,11 @@ jobs: args: unzip -qq digidoc.zip -d src/WebEid.AspNetCore.Example/DigiDoc - name: Build - run: dotnet build --configuration Release --no-restore src/WebEid.AspNetCore.Example.sln --verbosity normal + run: dotnet publish --configuration Release --no-restore src/WebEid.AspNetCore.Example.sln --verbosity normal - name: Test run: dotnet test --no-restore --verbosity normal src/WebEid.AspNetCore.Example.sln + + - name: Test building Docker image + working-directory: ./src + run: docker build -t web-eid-asp-dotnet-example . diff --git a/example/.gitignore b/example/.gitignore index a52c158..613f6dd 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1,3 +1,7 @@ +# Web eID specific +*.swp +/src/WebEid.AspNetCore.Example/DigiDoc/ + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## diff --git a/example/README.md b/example/README.md index b25779f..566c931 100644 --- a/example/README.md +++ b/example/README.md @@ -105,3 +105,93 @@ You are running in the `Development` profile, but you have not created an empty #### Why do I get the `System.BadImageFormatException: An attempt was made to load a program with an incorrect format` error during signing? You are using `libdigidocpp` DLLs for the wrong architecture. Copy files from the `x64` subfolder of the `libdigidocpp` installation folder to right place as described in the section _3. Setup the `libdigidocpp` library for signing_ above. In case you get this error while developing a custom 32-bit application, copy files from the `x86` subfolder instead. + +## Building and running with Docker on Ubuntu Linux + +This section covers the steps required to build the application on an Ubuntu Linux environment and run it using Docker. + +### Prerequisites + +Before you begin, ensure you have the following installed on your system: + +- .NET SDK 7.0 +- libdigidocpp-csharp + +You can install them using the following command: + +```sh +sudo apt install dotnet-sdk-7.0 libdigidocpp-csharp +``` + +### Building the application + +To build the application, follow these steps: + +1. Navigate to the `src` directory: + + ```sh + cd src + ``` + +2. Copy the necessary DigiDoc C# library files into your project: + + ```sh + cp /usr/include/digidocpp_csharp/* WebEid.AspNetCore.Example/DigiDoc/ + ``` + +3. Publish the application with the Release configuration: + + ```sh + dotnet publish --configuration Release WebEid.AspNetCore.Example.sln + ``` + +4. Update the `OriginUrl` in the `appsettings.json` to match your production environment: + + ```sh + sed -i 's#"OriginUrl": "https://localhost:44391"#"OriginUrl": "https://example.com"#' WebEid.AspNetCore.Example/bin/Release/net6.0/publish/appsettings.json + ``` + +### Building the Docker image + +After successfully building the application, you can create a Docker image: + +```sh +docker build -t web-eid-asp-dotnet-example . +``` + +This command builds a Docker image named `web-eid-asp-dotnet-example` using the `Dockerfile` in the current directory. + +## Running the Docker container with HTTPS support + +To enable HTTPS support for the .NET application, you have two primary options: + +1. Directly configure Kestrel to use HTTPS by setting up the necessary certificate information in the app's configuration files. This method is detailed in the [ASP.NET Core documentation](https://docs.microsoft.com/aspnet/core/security/enforcing-ssl). + +2. Employ a reverse proxy that manages TLS termination and forwards requests to the application over HTTP. This is a common pattern in production environments due to its flexibility. + +In this project, we assume the application is running behind a reverse proxy. + +First, the proxy server must pass the `Host:` line from the incoming request to the proxied application and set the `X-Forwarded-*` headers to inform the application that it runs behind a reverse proxy. Here is example configuration for the Apache web server: + + + ProxyPreserveHost On + ProxyPass http://localhost:8480/ + ProxyPassReverse http://localhost:8480/ + RequestHeader set X-Forwarded-Proto https + RequestHeader set X-Forwarded-Port 443 + + + +Next, the .NET application must be configured to recognize and honor the `X-Forwarded-*` headers. This can be done by configuring the Forwarded Headers middleware in `Startup.cs`: + +```csharp +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto +}); +``` + +By default, this middleware is already enabled in the application. + +A Docker Compose configuration file `docker-compose.yml` is available in the `src` directory for running the Docker image `web-eid-asp-dotnet-example` on port 8480 behind a reverse proxy. + diff --git a/example/src/.dockerignore b/example/src/.dockerignore index 3729ff0..19c9155 100644 --- a/example/src/.dockerignore +++ b/example/src/.dockerignore @@ -22,4 +22,6 @@ **/secrets.dev.yaml **/values.dev.yaml LICENSE -README.md \ No newline at end of file +README.md + +!WebEid.AspNetCore.Example/bin/Release/net6.0/publish/ diff --git a/example/src/Dockerfile b/example/src/Dockerfile new file mode 100644 index 0000000..8c8a127 --- /dev/null +++ b/example/src/Dockerfile @@ -0,0 +1,22 @@ +# We're using .NET 6 now, but when we migrate to .NET 8 in the future, we should use chiseled images. +FROM mcr.microsoft.com/dotnet/aspnet:6.0-jammy + +WORKDIR /app + +COPY ria_public_key.gpg /usr/share/keyrings/ria-repository.gpg + +# Add RIA repository to install the official libdigidocpp-csharp package. As each RUN commits the layer to image, +# need to chain commands and clean up in the end to keep the image small. +RUN echo "deb [signed-by=/usr/share/keyrings/ria-repository.gpg] https://installer.id.ee/media/ubuntu/ jammy main" > /etc/apt/sources.list.d/ria-repository.list && \ + apt-get update && \ + apt-get install -y --no-install-recommends libdigidocpp-csharp && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists + +COPY ./WebEid.AspNetCore.Example/bin/Release/net6.0/publish/ . + +ENV ASPNETCORE_ENVIRONMENT=Production + +EXPOSE 80 + +ENTRYPOINT ["dotnet", "WebEid.AspNetCore.Example.dll"] diff --git a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs index a6d0ac2..febfd4b 100644 --- a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs +++ b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; + using System.IO; using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using digidoc; @@ -12,7 +13,7 @@ public class SigningService { - private const string FileToSign = @"wwwroot\files\example-for-signing.txt"; + private static readonly string FileToSign = Path.Combine("wwwroot", "files", "example-for-signing.txt"); private readonly DigiDocConfiguration configuration; private readonly ILogger logger; @@ -40,7 +41,7 @@ public DigestDto PrepareContainer(CertificateDto data, ClaimsIdentity identity, container.addDataFile(FileToSign, "application/octet-stream"); logger?.LogInformation("Preparing container for signing for file '{0}'", tempContainerName); var signature = - container.prepareWebSignature(certificate.Export(X509ContentType.Cert), "BES/time-stamp"); + container.prepareWebSignature(certificate.Export(X509ContentType.Cert), "time-stamp"); container.save(); return new DigestDto { diff --git a/example/src/WebEid.AspNetCore.Example/Startup.cs b/example/src/WebEid.AspNetCore.Example/Startup.cs index fe6fedf..ba61fb7 100644 --- a/example/src/WebEid.AspNetCore.Example/Startup.cs +++ b/example/src/WebEid.AspNetCore.Example/Startup.cs @@ -4,6 +4,7 @@ namespace WebEid.AspNetCore.Example using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -102,6 +103,15 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddAntiforgery(); + + // Add support for running behind a TLS terminating proxy. + services.Configure(options => + { + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + // Only use this if you're behind a known proxy: + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + }); } private static Uri GetOriginUrl(IConfiguration configuration) @@ -121,14 +131,16 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); + app.UseHttpsRedirection(); } else { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); + // Add support for running behind a TLS terminating proxy. + app.UseForwardedHeaders(); } - app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); diff --git a/example/src/docker-compose.yml b/example/src/docker-compose.yml new file mode 100644 index 0000000..41cff12 --- /dev/null +++ b/example/src/docker-compose.yml @@ -0,0 +1,8 @@ +version: '2' +services: + web-eid-asp-dotnet-example: + image: registry.gitlab.com/web-eid/service/web-eid-authentication-token-validation-dotnet/web-eid-asp-dotnet-example + restart: always + ports: + - '127.0.0.1:8480:80' + diff --git a/example/src/ria_public_key.gpg b/example/src/ria_public_key.gpg new file mode 100644 index 0000000000000000000000000000000000000000..1630297842655e2444ee8e2ff4fbc4e848f67e01 GIT binary patch literal 2215 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 literal 0 HcmV?d00001 From 42e0f8f320eb6bd7b2d3ddd9c4cf6211af784100 Mon Sep 17 00:00:00 2001 From: Mart Somermaa Date: Fri, 1 Dec 2023 23:55:43 +0200 Subject: [PATCH 08/34] release: bump version to 1.1.0 WE2-838 Signed-off-by: Mart Somermaa --- .../WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj index cd939e9..c4b04c2 100644 --- a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj +++ b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj @@ -19,7 +19,7 @@ - + From 8452dcb1904d3f015c86da1f32fee0ace59519bc Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 5 Jan 2024 14:42:57 +0200 Subject: [PATCH 09/34] Use Set-Cookie Secure attribute WE2-852 Signed-off-by: Raul Metsma --- example/src/WebEid.AspNetCore.Example/Startup.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/example/src/WebEid.AspNetCore.Example/Startup.cs b/example/src/WebEid.AspNetCore.Example/Startup.cs index ba61fb7..ed143fd 100644 --- a/example/src/WebEid.AspNetCore.Example/Startup.cs +++ b/example/src/WebEid.AspNetCore.Example/Startup.cs @@ -102,7 +102,10 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - services.AddAntiforgery(); + services.AddAntiforgery(options => + { + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + }); // Add support for running behind a TLS terminating proxy. services.Configure(options => From 2cb12a2660a593b165edd1b24aa269893bcdd301 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 5 Jan 2024 14:34:59 +0200 Subject: [PATCH 10/34] Use Set-Cookie Secure attribute WE2-851 Signed-off-by: Raul Metsma --- example/src/WebEid.AspNetCore.Example/Startup.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/src/WebEid.AspNetCore.Example/Startup.cs b/example/src/WebEid.AspNetCore.Example/Startup.cs index ed143fd..022a80a 100644 --- a/example/src/WebEid.AspNetCore.Example/Startup.cs +++ b/example/src/WebEid.AspNetCore.Example/Startup.cs @@ -84,6 +84,8 @@ public void ConfigureServices(IServiceCollection services) services.AddSession(options => { options.Cookie.Name = "WebEid.AspNetCore.Example.Session"; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.SameSite = SameSiteMode.Strict; options.IdleTimeout = TimeSpan.FromSeconds(60); options.Cookie.IsEssential = true; }); From 0ab197f3a53ff95469cbafd46f553b68c177505e Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Tue, 13 Feb 2024 22:11:39 +0200 Subject: [PATCH 11/34] Fix GetSupportedHashAlgorithm function WE2-857 Signed-off-by: Raul Metsma --- .../Signing/SigningService.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs index febfd4b..6774fbb 100644 --- a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs +++ b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs @@ -42,11 +42,12 @@ public DigestDto PrepareContainer(CertificateDto data, ClaimsIdentity identity, logger?.LogInformation("Preparing container for signing for file '{0}'", tempContainerName); var signature = container.prepareWebSignature(certificate.Export(X509ContentType.Cert), "time-stamp"); + var hashFunction = GetSupportedHashAlgorithm(data.SupportedSignatureAlgorithms, signature.signatureMethod()); container.save(); return new DigestDto { Hash = Convert.ToBase64String(signature.dataToSign()), - HashFunction = GetSupportedHashAlgorithm(data.SupportedSignatureAlgorithms) + HashFunction = hashFunction }; } finally @@ -75,15 +76,32 @@ public void SignContainer(SignatureDto signatureDto, string tempContainerName) } } - private static string GetSupportedHashAlgorithm(IList supportedSignatureAlgorithms) + private static string GetSupportedHashAlgorithm(IList supportedSignatureAlgorithms, string signatureMethod) { - var algorithmNames = supportedSignatureAlgorithms.Select(a => a.HashFunction).ToArray(); - return algorithmNames switch + var framgment = new Uri(signatureMethod).Fragment; + if (supportedSignatureAlgorithms.FirstOrDefault(algo => FragmentEquals(framgment, algo), null) is SignatureAlgorithmDto algo) { - var a when a.Contains("SHA-384") => "SHA-384", - var a when a.Contains("SHA-256") => "SHA-256", - _ => throw new ArgumentException("SHA-384 or SHA-256 algorithm must be supported") - }; + return algo.HashFunction; + } + throw new ArgumentException("Supported signature algorithm not found"); } + + private static bool FragmentEquals(string framgent, SignatureAlgorithmDto signatureAlgorithm) => + signatureAlgorithm switch + { + { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-224" } => framgent == "#ecdsa-sha224", + { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-256" } => framgent == "#ecdsa-sha256", + { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-384" } => framgent == "#ecdsa-sha384", + { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-512" } => framgent == "#ecdsa-sha512", + { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-224" } => framgent == "#rsa-sha224", + { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-256" } => framgent == "#rsa-sha256", + { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-384" } => framgent == "#rsa-sha384", + { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-512" } => framgent == "#rsa-sha512", + { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-224" } => framgent == "#sha224-rsa-MGF1", + { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-256" } => framgent == "#sha256-rsa-MGF1", + { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-384" } => framgent == "#sha384-rsa-MGF1", + { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-512" } => framgent == "#sha512-rsa-MGF1", + _ => false + }; } } From 8ea7c0a8f91a9fbbb4ac4c786d187787fe27afc0 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 5 Jan 2024 14:44:39 +0200 Subject: [PATCH 12/34] Set __Host- prefix to auth cookie WE2-854 Signed-off-by: Raul Metsma --- example/src/WebEid.AspNetCore.Example/Startup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example/src/WebEid.AspNetCore.Example/Startup.cs b/example/src/WebEid.AspNetCore.Example/Startup.cs index 022a80a..e4d1707 100644 --- a/example/src/WebEid.AspNetCore.Example/Startup.cs +++ b/example/src/WebEid.AspNetCore.Example/Startup.cs @@ -67,7 +67,8 @@ public void ConfigureServices(IServiceCollection services) services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { - options.Cookie.Name = "WebEid.AspNeCore.Example.Auth"; + options.Cookie.Name = "__Host-WebEid.AspNetCore.Example.Auth"; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.Strict; options.Events.OnRedirectToLogin = context => { From d8b65f04424563d4694462701e77c3b57d732d5b Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 5 Jan 2024 14:38:29 +0200 Subject: [PATCH 13/34] Set __Host- prefix to session cookie WE2-854 Signed-off-by: Raul Metsma --- example/src/WebEid.AspNetCore.Example/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/WebEid.AspNetCore.Example/Startup.cs b/example/src/WebEid.AspNetCore.Example/Startup.cs index e4d1707..5db11a5 100644 --- a/example/src/WebEid.AspNetCore.Example/Startup.cs +++ b/example/src/WebEid.AspNetCore.Example/Startup.cs @@ -84,7 +84,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSession(options => { - options.Cookie.Name = "WebEid.AspNetCore.Example.Session"; + options.Cookie.Name = "__Host-WebEid.AspNetCore.Example.Session"; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.Strict; options.IdleTimeout = TimeSpan.FromSeconds(60); From dcc752b3c5f46e5eb90088cc1467ff29bf233951 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Fri, 5 Jan 2024 21:15:08 +0200 Subject: [PATCH 14/34] All ID-Card certificates are expired in EstEID 2015 WE2-839 Signed-off-by: Raul Metsma --- .../Certificates/Dev/TEST_of_ESTEID-SK_2015.cer | Bin 1671 -> 0 bytes .../Certificates/Prod/ESTEID-SK_2015.cer | Bin 1652 -> 0 bytes .../WebEid.AspNetCore.Example.csproj | 6 ------ 3 files changed, 6 deletions(-) delete mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_ESTEID-SK_2015.cer delete mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Prod/ESTEID-SK_2015.cer diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_ESTEID-SK_2015.cer b/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_ESTEID-SK_2015.cer deleted file mode 100644 index 7749286c895084bf2d7bacb98b742a01cd684122..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1671 zcmb7EX;4#F6wZ5ji4YT@LXe$^l>i-*`vL)3WD$Z;z=c%QO6{Uz|Nti5Ys|*EzrHLU@nPI%pFormh!Q(M}UdIaK zh5^P>8p=gE=t|&>IygG=jdVG{ z0pJd2jsw?)>%s>~|DWJeK-UnAqw1^;3*qtXQQ++83{VHu5}3bcZn(faG>jMEYabfO z-~f~h6tLCX4T4Anq5{?>;C>3MfFXkFHX*9@YOKZPhws_e%qlnOxqQNRwIF&n`~1KE|CGi>^}+HLlswpy~$=6&4-s8xoQu zPWZNaI-1?K_1m)=i%?y>INNxazUcC3=NFXg{Lh`Ad45@PS*LrIYTEetNUfHrv#Vuz z!^?qN&#WMG!<^jJDzbBy1YKXU#6!Pztk|T2UvZ`;T2i5xe=9g>AlSFJ&0{qDxBce} zGxzCtI8=KbySVY7swKF3L{S$NR�?itslb-bPA?nh!_1G3$MPD2FPu$I{g%r!&R2 z7Wd~~5Wl%Zb&m2WF~aUx^f0H!IT=uI@RRV8ox@z?+~Vy|8D@qT+J=qy&-q8w#2zEW z2o9}C(Yo!lu)kE2_u_8K=yLn6HRI5`n zsa6&tfKJg0aY#En=1H<22%?{K4E76 z{^(l5NYEX2cK46HW>pDOFcBg^RSKAfPlPtG#D~dzIf763E`#%ql=4$-y(^>3bedFk z*cwyfw%J$HX0C%6A_f7?lfEw4%`!hX+RE7Mx;}dwWi`;zyw`=GIRwF%l?)O!n1Kku zM*t;LJ|IH~)L}`4Ag~-PBWvN9L|OzxhV;#HxkTp5W~Zg4v0kka+h=_!puY74eg_fX zK?qo)$)m5ys|CpTg%39H*7z(CSFrN96F~4iumY4^Ey9}vta2Ii_J7@gsrh0B!T=WY zWQp1f7QA2~VE5j606|D5*a=q5V*n|QHPzw8C15GyH`NK_1Y)5~0zwwTV5Q%iuzz|W z#1%-!)-1L_oG9S&1n)=^jx>@a&`6TN$D5UaNm)Xv9QbHRTO#fNue{Q!(oxCzcXy?j zL@X1@#nN=PP?{`b%Q2Z;fMG(RR2YYUlKi%0;F?NQKoFtDWzSej1FN!l*U`=!Cn?sn zJBhYMN6%(Y5V8znN<+$f>|Emwh@a_k>IXiqZaCYfpj)CtH)li_+}E{7H<1W@=XS#^ zk5pEZ4tXR;>CsZo?QFTW^+PAsUsa#xxl6cvJp#_n^fag0JjuBf6Th2TRy19ex5>-q zfckldT}3N)JvnlfKYgd2=Q$TXbg#>X!ff?V+wV{=G&xpz=_SnV;64Ak!`EPhIYw8ESfeZIIag&l~psrr8d>b^~V diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/Prod/ESTEID-SK_2015.cer b/example/src/WebEid.AspNetCore.Example/Certificates/Prod/ESTEID-SK_2015.cer deleted file mode 100644 index b16695560fd7f7498f20dedd8ac67098f6eeee57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1652 zcmXqLVk7~D2aMlsHU>`aH^Y`xS}0|jwj zLsLT|LvuqTV+#}GC<%Te17ib_fRVAOrKx3# zz|hzbD#6Ud=NcU1>gl2z?5$vAU}$R4#H55AAdIXG%uP)E3_x)%rY0svhD(jVu3Y$- zdv)*Rlc$-Z+A{VrzWXzG;oX))^Dp^`Io@Z~RhZCS=H;?cUo-Y&lG_^QuoG;OyZ1TY z&s`VxQ>X7!_pTEYrSzX%f6IEM*H+bX4M&76M}F!Q1#6Zl*>x_bA4KU)e5SWd!23qP zUT&sj9i88z`6i*7+Fow_>y%EOnrrkR^xyWPr3-vOBNkXD#Lh-*2P4$x$Xx&sBFWoh{-1IZgvIBe5mKc1o zY3y7Ttz-CNoqCgT7GrdSOPh(Q>?3uxOCrJ_G$&MgZk60RkF{HF#Sb6H`f7&&@0dwf zPp34UYA}8ywRHub`R|7^otx`Uh?~ykHcRW;Q{?ZX?RMyhv0RXg$oZvl4p~)SI{Tb< z8$4b$`Ml@aQm!+eJDy&$6xg_)+!b zO7ANoGYw=x3iw#WSVT6j?$|T)YSNNcEsnEWWQ(kui`)YZ_(0P9jEw(TSb&+6&7d5_ zS70eH$TQ$zV`E|HuVQ2ZW=3`vhVC~GK&Am3r#2fS3*$L?Mn(f=14T9tAeWVuorzIQ zG^3=Xpx8=Zzr4I$51gg+odbdmxDh&-kU7lA92Ns1kj26vhj19M0V!ZgH*f@r$+1`& zSS&O%U;$L4RjSriQ9U@m3mgtM67EEa8CVDv>$|$fqm)KK9;h^m2bD(g&W?I1 zsYQCpMI{EdAZN<6m>HND7%ebZpx35@VPH{eL4I*&Nq$kKesWPxv3_c5a&l2}B2aq{ za(MwPK$)8u85v5#c#_QReYI~LUOw&9aV`Hi;jMq~KKb|XU2x?R9jl3!XIVo0ZYv(S z&p2Op@V?mgOcP?e^&Swgg`8Lk9Lwc8aMT*f_WD$n`_=Ts!uy z^C+wf{uaFH+$!mv8#xO1CVihh;a)`luN!XPYX3c|U8ml7TE^l~-|g$S%5Hpp!sL4X zx$eIe>(5o)ubH-n - - Always - Always - - Always - Always From 3b9a417f70df964ff679283f476722acdae438ef Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Tue, 19 Mar 2024 21:49:37 +0200 Subject: [PATCH 15/34] Update copyright year WE2-891 Signed-off-by: Raul Metsma --- example/LICENSE | 2 +- .../WebEid.AspNetCore.Example.Tests.csproj | 15 --------------- example/src/WebEid.AspNetCore.Example.sln | 6 ------ .../wwwroot/js/errors.js | 2 +- 4 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 example/src/WebEid.AspNetCore.Example.Tests/WebEid.AspNetCore.Example.Tests.csproj diff --git a/example/LICENSE b/example/LICENSE index ebbc5e6..29b2598 100644 --- a/example/LICENSE +++ b/example/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2023 Estonian Information System Authority +Copyright (c) 2021-2024 Estonian Information System Authority Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/example/src/WebEid.AspNetCore.Example.Tests/WebEid.AspNetCore.Example.Tests.csproj b/example/src/WebEid.AspNetCore.Example.Tests/WebEid.AspNetCore.Example.Tests.csproj deleted file mode 100644 index 8843685..0000000 --- a/example/src/WebEid.AspNetCore.Example.Tests/WebEid.AspNetCore.Example.Tests.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net6.0 - - false - - - - - - - - - diff --git a/example/src/WebEid.AspNetCore.Example.sln b/example/src/WebEid.AspNetCore.Example.sln index 86e294d..72f5596 100644 --- a/example/src/WebEid.AspNetCore.Example.sln +++ b/example/src/WebEid.AspNetCore.Example.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebEid.AspNetCore.Example", "WebEid.AspNetCore.Example\WebEid.AspNetCore.Example.csproj", "{573AD725-C52C-40DE-8E70-6DF4E4227120}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebEid.AspNetCore.Example.Tests", "WebEid.AspNetCore.Example.Tests\WebEid.AspNetCore.Example.Tests.csproj", "{E51FD3B1-1F87-457A-BA45-984001A5F1F4}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,10 +15,6 @@ Global {573AD725-C52C-40DE-8E70-6DF4E4227120}.Debug|Any CPU.Build.0 = Debug|Any CPU {573AD725-C52C-40DE-8E70-6DF4E4227120}.Release|Any CPU.ActiveCfg = Release|Any CPU {573AD725-C52C-40DE-8E70-6DF4E4227120}.Release|Any CPU.Build.0 = Release|Any CPU - {E51FD3B1-1F87-457A-BA45-984001A5F1F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E51FD3B1-1F87-457A-BA45-984001A5F1F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E51FD3B1-1F87-457A-BA45-984001A5F1F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E51FD3B1-1F87-457A-BA45-984001A5F1F4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js b/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js index 1665e6d..95220bb 100644 --- a/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js +++ b/example/src/WebEid.AspNetCore.Example/wwwroot/js/errors.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2023 Estonian Information System Authority + * Copyright (c) 2020-2024 Estonian Information System Authority * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From 7db258ef9493dc06ca77eeb9d740e70b5d1178db Mon Sep 17 00:00:00 2001 From: Mart Somermaa Date: Tue, 26 Mar 2024 17:13:19 +0200 Subject: [PATCH 16/34] Add licence to source files WE2-891 Signed-off-by: Mart Somermaa --- .../Certificates/CertificateLoader.cs | 19 +++++++++++++++++++ .../ClaimsIdentityExtensions.cs | 19 +++++++++++++++++++ .../Controllers/Api/AuthController.cs | 19 +++++++++++++++++++ .../Controllers/Api/BaseController.cs | 19 +++++++++++++++++++ .../Controllers/Api/ChallengeController.cs | 19 +++++++++++++++++++ .../Controllers/Api/SignController.cs | 19 +++++++++++++++++++ .../Controllers/WelcomeController.cs | 19 +++++++++++++++++++ .../Dto/AuthenticateRequestDto.cs | 19 +++++++++++++++++++ .../Dto/CertificateDto.cs | 19 +++++++++++++++++++ .../Dto/ChallengeDto.cs | 19 +++++++++++++++++++ .../Dto/DigestDto.cs | 19 +++++++++++++++++++ .../WebEid.AspNetCore.Example/Dto/FileDto.cs | 19 +++++++++++++++++++ .../Dto/SignatureAlgorithmDto.cs | 19 +++++++++++++++++++ .../Dto/SignatureDto.cs | 19 +++++++++++++++++++ .../LoggedInAuthorizationHandler.cs | 19 +++++++++++++++++++ .../Pages/Welcome.cshtml.cs | 19 +++++++++++++++++++ .../src/WebEid.AspNetCore.Example/Program.cs | 19 +++++++++++++++++++ .../SessionBackedChallengeNonceStore.cs | 19 +++++++++++++++++++ .../Signing/DigiDocConfiguration.cs | 19 +++++++++++++++++++ .../Signing/SigningService.cs | 19 +++++++++++++++++++ .../src/WebEid.AspNetCore.Example/Startup.cs | 19 +++++++++++++++++++ 21 files changed, 399 insertions(+) diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/CertificateLoader.cs b/example/src/WebEid.AspNetCore.Example/Certificates/CertificateLoader.cs index 57c4b65..186694c 100644 --- a/example/src/WebEid.AspNetCore.Example/Certificates/CertificateLoader.cs +++ b/example/src/WebEid.AspNetCore.Example/Certificates/CertificateLoader.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Certificates { using System.Collections.Generic; diff --git a/example/src/WebEid.AspNetCore.Example/ClaimsIdentityExtensions.cs b/example/src/WebEid.AspNetCore.Example/ClaimsIdentityExtensions.cs index c5ea8cc..ddc8983 100644 --- a/example/src/WebEid.AspNetCore.Example/ClaimsIdentityExtensions.cs +++ b/example/src/WebEid.AspNetCore.Example/ClaimsIdentityExtensions.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example { using System.Linq; diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs index 83a218f..1fa0004 100644 --- a/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Controllers.Api { using Microsoft.AspNetCore.Authentication; diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs index b3a6b16..acd42a8 100644 --- a/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Controllers.Api { using System.Security; diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/ChallengeController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/ChallengeController.cs index 13573cf..764a1bf 100644 --- a/example/src/WebEid.AspNetCore.Example/Controllers/Api/ChallengeController.cs +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/ChallengeController.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Controllers.Api { using Microsoft.AspNetCore.Mvc; diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs index 6c9ae04..f3b1177 100644 --- a/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Controllers.Api { using System; diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/WelcomeController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/WelcomeController.cs index 1a0d09b..c9743e4 100644 --- a/example/src/WebEid.AspNetCore.Example/Controllers/WelcomeController.cs +++ b/example/src/WebEid.AspNetCore.Example/Controllers/WelcomeController.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Controllers { using Microsoft.AspNetCore.Authorization; diff --git a/example/src/WebEid.AspNetCore.Example/Dto/AuthenticateRequestDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/AuthenticateRequestDto.cs index 19f1734..c36726f 100644 --- a/example/src/WebEid.AspNetCore.Example/Dto/AuthenticateRequestDto.cs +++ b/example/src/WebEid.AspNetCore.Example/Dto/AuthenticateRequestDto.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Dto { using System.Text.Json.Serialization; diff --git a/example/src/WebEid.AspNetCore.Example/Dto/CertificateDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/CertificateDto.cs index c2bc73d..5fabd62 100644 --- a/example/src/WebEid.AspNetCore.Example/Dto/CertificateDto.cs +++ b/example/src/WebEid.AspNetCore.Example/Dto/CertificateDto.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Dto { using System.Collections.Generic; diff --git a/example/src/WebEid.AspNetCore.Example/Dto/ChallengeDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/ChallengeDto.cs index f1022c1..63aa24c 100644 --- a/example/src/WebEid.AspNetCore.Example/Dto/ChallengeDto.cs +++ b/example/src/WebEid.AspNetCore.Example/Dto/ChallengeDto.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Dto { public class ChallengeDto diff --git a/example/src/WebEid.AspNetCore.Example/Dto/DigestDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/DigestDto.cs index d6b29d2..08b1d87 100644 --- a/example/src/WebEid.AspNetCore.Example/Dto/DigestDto.cs +++ b/example/src/WebEid.AspNetCore.Example/Dto/DigestDto.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Dto { public class DigestDto diff --git a/example/src/WebEid.AspNetCore.Example/Dto/FileDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/FileDto.cs index e6bc6d0..b29cd14 100644 --- a/example/src/WebEid.AspNetCore.Example/Dto/FileDto.cs +++ b/example/src/WebEid.AspNetCore.Example/Dto/FileDto.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Dto { public class FileDto diff --git a/example/src/WebEid.AspNetCore.Example/Dto/SignatureAlgorithmDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/SignatureAlgorithmDto.cs index 7561afd..3ac7527 100644 --- a/example/src/WebEid.AspNetCore.Example/Dto/SignatureAlgorithmDto.cs +++ b/example/src/WebEid.AspNetCore.Example/Dto/SignatureAlgorithmDto.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Dto { public class SignatureAlgorithmDto diff --git a/example/src/WebEid.AspNetCore.Example/Dto/SignatureDto.cs b/example/src/WebEid.AspNetCore.Example/Dto/SignatureDto.cs index 59479ad..ff5e9d8 100644 --- a/example/src/WebEid.AspNetCore.Example/Dto/SignatureDto.cs +++ b/example/src/WebEid.AspNetCore.Example/Dto/SignatureDto.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Dto { public class SignatureDto diff --git a/example/src/WebEid.AspNetCore.Example/LoggedInAuthorizationHandler.cs b/example/src/WebEid.AspNetCore.Example/LoggedInAuthorizationHandler.cs index bac3585..dc01f3e 100644 --- a/example/src/WebEid.AspNetCore.Example/LoggedInAuthorizationHandler.cs +++ b/example/src/WebEid.AspNetCore.Example/LoggedInAuthorizationHandler.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example { using Microsoft.AspNetCore.Authorization; diff --git a/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs b/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs index 5345f36..fcd2c03 100644 --- a/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs +++ b/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Pages { using System.Linq; diff --git a/example/src/WebEid.AspNetCore.Example/Program.cs b/example/src/WebEid.AspNetCore.Example/Program.cs index 1d41942..af5995f 100644 --- a/example/src/WebEid.AspNetCore.Example/Program.cs +++ b/example/src/WebEid.AspNetCore.Example/Program.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example { using Microsoft.AspNetCore.Hosting; diff --git a/example/src/WebEid.AspNetCore.Example/SessionBackedChallengeNonceStore.cs b/example/src/WebEid.AspNetCore.Example/SessionBackedChallengeNonceStore.cs index 2ae5059..e269f36 100644 --- a/example/src/WebEid.AspNetCore.Example/SessionBackedChallengeNonceStore.cs +++ b/example/src/WebEid.AspNetCore.Example/SessionBackedChallengeNonceStore.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + using Microsoft.AspNetCore.Http; using System.Text.Json; using WebEid.Security.Challenge; diff --git a/example/src/WebEid.AspNetCore.Example/Signing/DigiDocConfiguration.cs b/example/src/WebEid.AspNetCore.Example/Signing/DigiDocConfiguration.cs index 71f2fef..6e4854c 100644 --- a/example/src/WebEid.AspNetCore.Example/Signing/DigiDocConfiguration.cs +++ b/example/src/WebEid.AspNetCore.Example/Signing/DigiDocConfiguration.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Services { using digidoc; diff --git a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs index 6774fbb..6101e34 100644 --- a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs +++ b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example.Services { using System; diff --git a/example/src/WebEid.AspNetCore.Example/Startup.cs b/example/src/WebEid.AspNetCore.Example/Startup.cs index 5db11a5..bbabba4 100644 --- a/example/src/WebEid.AspNetCore.Example/Startup.cs +++ b/example/src/WebEid.AspNetCore.Example/Startup.cs @@ -1,3 +1,22 @@ +// Copyright (c) 2021-2024 Estonian Information System Authority +// +// 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. + namespace WebEid.AspNetCore.Example { using Certificates; From a13922056a23805a98abe2522ca3e81ef089bd00 Mon Sep 17 00:00:00 2001 From: realmerx Date: Mon, 19 Feb 2024 23:43:10 +0200 Subject: [PATCH 17/34] Fixed race condition with digidoc initialize/terminate In fixed version digidoc is initialized in constructor and terminated in Dispose. Problematic implementation initalized and terminated digidoc inside methods using digidoc which could cause race condition with multiple threads. --- .../Signing/SigningService.cs | 83 ++++++++++--------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs index 6101e34..e67d488 100644 --- a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs +++ b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs @@ -30,16 +30,23 @@ using Microsoft.Extensions.Logging; using WebEid.Security.Util; - public class SigningService + public class SigningService : IDisposable { private static readonly string FileToSign = Path.Combine("wwwroot", "files", "example-for-signing.txt"); private readonly DigiDocConfiguration configuration; private readonly ILogger logger; + private bool _disposedValue; public SigningService(DigiDocConfiguration configuration, ILogger logger) { this.configuration = configuration; this.logger = logger; + + // Current implementation of static digidoc assumes that SigningService is used as singleton + // in ASP.NET Core application we initialize it with AddSingleton method in Startup.cs + // digidoc is initialized in constructor and terminated in Dispose + configuration.Initialize(); + digidoc.initialize("WebEidExample"); } public DigestDto PrepareContainer(CertificateDto data, ClaimsIdentity identity, string tempContainerName) @@ -50,49 +57,30 @@ public DigestDto PrepareContainer(CertificateDto data, ClaimsIdentity identity, throw new ArgumentException( "Authenticated subject ID code differs from signing certificate subject ID code"); } - - configuration.Initialize(); - digidoc.initialize("WebEidExample"); - try - { - this.logger?.LogDebug("Creating container file: '{0}'", tempContainerName); - Container container = Container.create(tempContainerName); - container.addDataFile(FileToSign, "application/octet-stream"); - logger?.LogInformation("Preparing container for signing for file '{0}'", tempContainerName); - var signature = - container.prepareWebSignature(certificate.Export(X509ContentType.Cert), "time-stamp"); + + this.logger?.LogDebug("Creating container file: '{0}'", tempContainerName); + Container container = Container.create(tempContainerName); + container.addDataFile(FileToSign, "application/octet-stream"); + logger?.LogInformation("Preparing container for signing for file '{0}'", tempContainerName); + var signature = + container.prepareWebSignature(certificate.Export(X509ContentType.Cert), "time-stamp"); var hashFunction = GetSupportedHashAlgorithm(data.SupportedSignatureAlgorithms, signature.signatureMethod()); - container.save(); - return new DigestDto - { - Hash = Convert.ToBase64String(signature.dataToSign()), - HashFunction = hashFunction - }; - } - finally + container.save(); + return new DigestDto { - digidoc.terminate(); - } + Hash = Convert.ToBase64String(signature.dataToSign()), + HashFunction = hashFunction + }; } - public void SignContainer(SignatureDto signatureDto, string tempContainerName) { - configuration.Initialize(); - digidoc.initialize("WebEidExample"); - try - { - var container = Container.open(tempContainerName); - var signatureBytes = Convert.FromBase64String(signatureDto.Signature); - var signature = container.signatures().First(); // Container must have one signature as it was added in PrepareContainer - signature.setSignatureValue(signatureBytes); - signature.extendSignatureProfile("BES/time-stamp"); - container.save(); - } - finally - { - digidoc.terminate(); - } + var container = Container.open(tempContainerName); + var signatureBytes = Convert.FromBase64String(signatureDto.Signature); + var signature = container.signatures().First(); // Container must have one signature as it was added in PrepareContainer + signature.setSignatureValue(signatureBytes); + signature.extendSignatureProfile("BES/time-stamp"); + container.save(); } private static string GetSupportedHashAlgorithm(IList supportedSignatureAlgorithms, string signatureMethod) @@ -122,5 +110,24 @@ private static bool FragmentEquals(string framgent, SignatureAlgorithmDto signat { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-512" } => framgent == "#sha512-rsa-MGF1", _ => false }; + + ~SigningService() => Dispose(false); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) {} + + digidoc.terminate(); + _disposedValue = true; + } + } } } From c9e0153654a300585986debe07b4ae7729640edc Mon Sep 17 00:00:00 2001 From: realmerx Date: Sun, 3 Mar 2024 23:57:33 +0200 Subject: [PATCH 18/34] Improved comments --- .../Signing/SigningService.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs index e67d488..0d892c0 100644 --- a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs +++ b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs @@ -42,9 +42,9 @@ public SigningService(DigiDocConfiguration configuration, ILogger logger) this.configuration = configuration; this.logger = logger; - // Current implementation of static digidoc assumes that SigningService is used as singleton - // in ASP.NET Core application we initialize it with AddSingleton method in Startup.cs - // digidoc is initialized in constructor and terminated in Dispose + // The current implementation of the static DigiDoc library assumes that SigningService is used as a singleton. + // In the ASP.NET Core application, we initialize it using the AddSingleton method in Startup.cs. + // The DigiDoc library is initialized in the constructor and terminated in Dispose. configuration.Initialize(); digidoc.initialize("WebEidExample"); } @@ -123,7 +123,9 @@ private void Dispose(bool disposing) { if (!_disposedValue) { - if (disposing) {} + if (disposing) { + // You can release managed resources here if needed. + } digidoc.terminate(); _disposedValue = true; From cb8f8789abaea996dbdff03a8cc8abb3385aca23 Mon Sep 17 00:00:00 2001 From: realmerx Date: Tue, 5 Mar 2024 00:55:10 +0200 Subject: [PATCH 19/34] Fixed typo --- .../Signing/SigningService.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs index 0d892c0..6c6ae76 100644 --- a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs +++ b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs @@ -93,21 +93,21 @@ private static string GetSupportedHashAlgorithm(IList sup throw new ArgumentException("Supported signature algorithm not found"); } - private static bool FragmentEquals(string framgent, SignatureAlgorithmDto signatureAlgorithm) => + private static bool FragmentEquals(string fragment, SignatureAlgorithmDto signatureAlgorithm) => signatureAlgorithm switch { - { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-224" } => framgent == "#ecdsa-sha224", - { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-256" } => framgent == "#ecdsa-sha256", - { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-384" } => framgent == "#ecdsa-sha384", - { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-512" } => framgent == "#ecdsa-sha512", - { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-224" } => framgent == "#rsa-sha224", - { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-256" } => framgent == "#rsa-sha256", - { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-384" } => framgent == "#rsa-sha384", - { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-512" } => framgent == "#rsa-sha512", - { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-224" } => framgent == "#sha224-rsa-MGF1", - { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-256" } => framgent == "#sha256-rsa-MGF1", - { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-384" } => framgent == "#sha384-rsa-MGF1", - { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-512" } => framgent == "#sha512-rsa-MGF1", + { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-224" } => fragment == "#ecdsa-sha224", + { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-256" } => fragment == "#ecdsa-sha256", + { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-384" } => fragment == "#ecdsa-sha384", + { CryptoAlgorithm: "ECC", PaddingScheme: "NONE", HashFunction: "SHA-512" } => fragment == "#ecdsa-sha512", + { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-224" } => fragment == "#rsa-sha224", + { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-256" } => fragment == "#rsa-sha256", + { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-384" } => fragment == "#rsa-sha384", + { CryptoAlgorithm: "RSA", PaddingScheme: "PKCS1.5", HashFunction: "SHA-512" } => fragment == "#rsa-sha512", + { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-224" } => fragment == "#sha224-rsa-MGF1", + { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-256" } => fragment == "#sha256-rsa-MGF1", + { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-384" } => fragment == "#sha384-rsa-MGF1", + { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-512" } => fragment == "#sha512-rsa-MGF1", _ => false }; From 48353bf88788d495d5865e99fbe8508cb8c152ac Mon Sep 17 00:00:00 2001 From: realmerx Date: Tue, 5 Mar 2024 11:32:26 +0200 Subject: [PATCH 20/34] Fixed formatting --- .../Signing/SigningService.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs index 6c6ae76..9448c41 100644 --- a/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs +++ b/example/src/WebEid.AspNetCore.Example/Signing/SigningService.cs @@ -41,7 +41,7 @@ public SigningService(DigiDocConfiguration configuration, ILogger logger) { this.configuration = configuration; this.logger = logger; - + // The current implementation of the static DigiDoc library assumes that SigningService is used as a singleton. // In the ASP.NET Core application, we initialize it using the AddSingleton method in Startup.cs. // The DigiDoc library is initialized in the constructor and terminated in Dispose. @@ -57,14 +57,14 @@ public DigestDto PrepareContainer(CertificateDto data, ClaimsIdentity identity, throw new ArgumentException( "Authenticated subject ID code differs from signing certificate subject ID code"); } - + this.logger?.LogDebug("Creating container file: '{0}'", tempContainerName); Container container = Container.create(tempContainerName); container.addDataFile(FileToSign, "application/octet-stream"); logger?.LogInformation("Preparing container for signing for file '{0}'", tempContainerName); var signature = container.prepareWebSignature(certificate.Export(X509ContentType.Cert), "time-stamp"); - var hashFunction = GetSupportedHashAlgorithm(data.SupportedSignatureAlgorithms, signature.signatureMethod()); + var hashFunction = GetSupportedHashAlgorithm(data.SupportedSignatureAlgorithms, signature.signatureMethod()); container.save(); return new DigestDto { @@ -110,9 +110,9 @@ private static bool FragmentEquals(string fragment, SignatureAlgorithmDto signat { CryptoAlgorithm: "RSA", PaddingScheme: "PSS", HashFunction: "SHA-512" } => fragment == "#sha512-rsa-MGF1", _ => false }; - + ~SigningService() => Dispose(false); - + public void Dispose() { Dispose(true); @@ -123,7 +123,8 @@ private void Dispose(bool disposing) { if (!_disposedValue) { - if (disposing) { + if (disposing) + { // You can release managed resources here if needed. } From 7f344690b473f9a6d0b5652481f419f64e7c32fe Mon Sep 17 00:00:00 2001 From: realmerx Date: Thu, 11 Apr 2024 20:32:18 +0300 Subject: [PATCH 21/34] Fixed simultaneous signing from multiple browsers --- .../Controllers/Api/AuthController.cs | 4 ++++ .../Controllers/Api/BaseController.cs | 20 ++++++++++++++----- .../Controllers/Api/SignController.cs | 1 - 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs index 1fa0004..c4874d4 100644 --- a/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs @@ -66,6 +66,10 @@ await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); + + // Assign a unique ID within the session to enable the use of a unique temporary container name across successive requests. + // A unique temporary container name is required to facilitate simultaneous signing from multiple browsers. + SetUniqueIdInSession(); } [HttpGet] diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs index acd42a8..79831e3 100644 --- a/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/BaseController.cs @@ -19,22 +19,32 @@ namespace WebEid.AspNetCore.Example.Controllers.Api { - using System.Security; - using System.Security.Claims; + using System; + using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; public abstract class BaseController : ControllerBase { + const string uniqueIdKey = "UniqueId"; + protected void RemoveUserContainerFile() { System.IO.File.Delete(GetUserContainerName()); } + protected void SetUniqueIdInSession() + { + HttpContext.Session.SetString(uniqueIdKey, Guid.NewGuid().ToString()); + } + + private string GetUniqueIdFromSession() + { + return HttpContext.Session.GetString(uniqueIdKey); + } + protected string GetUserContainerName() { - var identity = (ClaimsIdentity)this.HttpContext.User?.Identity ?? - throw new SecurityException("User is not logged in"); - return identity.GetIdCode(); + return $"container_{GetUniqueIdFromSession()}"; } } } diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs index f3b1177..d4f6806 100644 --- a/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/SignController.cs @@ -22,7 +22,6 @@ using System; using System.Security.Claims; using System.Threading.Tasks; - using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Services; From 067037d7b28855eccd2437563022de7afa603dcb Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Wed, 1 May 2024 15:40:58 +0300 Subject: [PATCH 22/34] Add new TEST ORG certificate issuers WE2-924 Signed-off-by: Raul Metsma --- .../Certificates/Dev/TEST_ORG_2021E.cer | Bin 0 -> 932 bytes .../Certificates/Dev/TEST_ORG_2021R.cer | Bin 0 -> 1744 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_ORG_2021E.cer create mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_ORG_2021R.cer diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_ORG_2021E.cer b/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_ORG_2021E.cer new file mode 100644 index 0000000000000000000000000000000000000000..6246e0ce7ebf75ca55cc998c943558fe7aa19f33 GIT binary patch literal 932 zcmXqLVqRd-#LT>anTe5!NuXG3+KOpOU}k=R`7ID2+q$bEy>K!D^_p}HWW7y1xY6I@cD%Vxw`5a z8kn1y8yFfJY8t46B$#;=LR^DG6!Oy)&~*p-`-dpF8@d|EiSrr(^%)r%SeTd?nnj88 z8k-?;4f3hs9xVe6hD2$*e?3O()Q!mx&K6(3!<8b-!?@$Hj0?;6qh=*RHzwx(vYMfp zzjb<%;FKg zcmsK$OJ$W=Bn-qFL>|f9OTM%3M@t=}wVrpw#)tJ+KRz>%1u5WT5n~bYTrP7Vda8u6 z=s~$pGv+<7_U3ELG~feC^D{F3XJG+GH=BVFh%XG{a~QAzDJDh+gB&&vZ8k<$R(3{4 zmIQ-110@*WfU!+1qokz3N?$)EH8)?cI9o3@RX;zuxWK>yu7;^i2dX9+ssI$K@xk8l zpjeG}H;e~|q+UvDkzR69iGd}^1@bIL1_lOt3v?D}wP_HgJ-H~yAONIQfyL9n-N2QN z1;}JDur;uTxrB*POdn)aG0?f?<>f%fWTq6u5~yBkseWpneo<;cesN|=eo>{qb3m{@ za$09@>|!uzY-4ha)8k?)PhOyL{%^wBdEGY5hS&b^G@51BM?F1rEwE;#=lc1~6R)q= z(_o02arb$(Z`Om%`iTv8a(xdEt2&q0M$SIZ-7NspCGyb literal 0 HcmV?d00001 diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_ORG_2021R.cer b/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_ORG_2021R.cer new file mode 100644 index 0000000000000000000000000000000000000000..f2749c79c3e873ef1ff7dda93100fa1c8e39027c GIT binary patch literal 1744 zcmb`HdoM)q+hLpwW6AuN>?v}(mIQ8B;fuZJVs#cUpy=SjmOoU zNi+qfQXnB(@7q6tuqccWt&Ec1E1-pF2-QR&*v@|EdeAd_3%OH3d*i>t+9>x!LrJw& zTUG$);C&o(nARzOs@2n`>ZLrtg3>s0I$u5Co7B7k6O@(}I#qaPgm4CpcIrBR4d3FX zSGKTsXYQ`lxJmEz4+ofU8hWOWI=F~u-uDxF^UBpz!(JkbKAwp6JCZ@$C2zuq*VEKk!Ts*EX#ha@>R?Kbl- z_aqOO6nrxAjCo(Gw&v5}v+fDy+W0h*t$}k2s>%sdKe(prLZJ3*w@Ge6*x5bE?S)6> z^Bp=iWtS7|je0L>Zi&?y(5Osp7uC&_c3wPYQM@Vra}B9N&NwnB|F-RXcWm6?3w_79 z6z4;=I<2oW>jHU|=UjSL_7n6YR7UFFK5~kTkYnKu?(eAV7ZtRBZ(DkU^J>AcN~fA| z4mLL2-RkOW@lJj6o>G#5rxlc19(|j8G;sISn5w4 zP?c#lnU)7Q=_YMB5QdW{;UFwl4uW7m-~;r&Y5*lJ+ba-6+E6F~oNz9gA4=wMC|ou_ z5?KA6gHAO5lEeOzAoGAP;^Zsypf8i^`)A*i12{Y~nGEO z*}O2YO;$>T9RU+CaIo}<0=9tml>`kfqsX4(OL3#4qopvx0sODygv^Pea3Ux?P9&Ef zEa38DC=S~Y%C|<84kxJ*AyiUPJnpA$hLd_Tm)_|dh>!Y?-Zy7~ zSl%2LhaQ})QttPrBtG^Pd@>Rq0S zzehK+2=5dh3ek1SyH$U`?vit)g7>4hrhALd?&z7$yf}D*=fP`Tvh-klaiy*A>(-}l z2dlX`H%w>_Tg@`oa)?a>HPfY^ob?SdPEOo8Ud49F%(3ifMsD}*J$y5nkIlM!&P_6h z3Xc^OPnZLN{{W^FV6e41m@bz zzB@FvtHAWvJ3Sm@kuo>vfZ9waIM|hj-L#k&TQ+8!At4za4T-aEd`wb1U8nI@klvtS z6DnUs62rX3?X1;NT|31pCCZrfVRWmH8(@a{Q0aTp?#+^J#=Xib&dj`7ac#malk+cn zz1j8Y>D}u2+jCk~;G@pk-1QuBgZtgtd{I|b#PV0yHkZ#G_&8X+`(n!tRU!xWOYe}t z!UZjbq9>@#ko;5gh}lQ~XXZMHZkuscv5EO(gNMU3kL&rZ>p#_Cj-tJ1Mewcx$#@V=dx*q*Tq87_$>*8{E|5xceC9MAGI%bs)iK{LH; P?4i$f_J{Ib%30ALokzCI literal 0 HcmV?d00001 From 230173f52a5a254d13c7a203c6a5913282828a21 Mon Sep 17 00:00:00 2001 From: rabadashTheFool <57250329+rabadashTheFool@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:33:39 +0300 Subject: [PATCH 23/34] Add support for organization certificates (#21) Uses Common Name in case of an organization certificate instead of first and last name. WE2-745 Signed-off-by: Mihkel Kivisild Co-authored-by: Signed-off-by: Erkki Arus --- example/README.md | 35 ++++++++++++++++--- .../Controllers/Api/AuthController.cs | 28 ++++++++++----- .../Pages/Welcome.cshtml.cs | 12 ++++++- .../Properties/launchSettings.json | 9 +++++ 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/example/README.md b/example/README.md index 566c931..4c63f32 100644 --- a/example/README.md +++ b/example/README.md @@ -36,16 +36,42 @@ The algorithm, which performs the validation of the Web eID authentication token In case you need to provide your own CA certificates, add the `.cer` files to the `src/WebEid.AspNetCore.Example/Certificates/{Dev,Prod}` profile-specific directory. ### 3. Setup the `libdigidocpp` library for signing + `libdigidocpp` is a library for creating, signing and verifying digitally signed documents according to XAdES and XML-DSIG standards. It is a C++ library that has [SWIG](http://swig.org/) bindings for C#. Set up the `libdigidocpp` library as follows: -1. Install the _libdigidocpp-3.14.4.msi_ package or higher. The installation packages are available from [https://github.com/open-eid/libdigidocpp/releases](https://github.com/open-eid/libdigidocpp/releases). +#### For MS Windows + +1. Install the _libdigidocpp-3.17.1.msi_ package or higher. The installation packages are available from [https://github.com/open-eid/libdigidocpp/releases](https://github.com/open-eid/libdigidocpp/releases). 2. Copy the C# source files from the `libdigidocpp` installation folder `include\digidocpp_csharp` to the `src\WebEid.AspNetCore.Example\DigiDoc` folder. 3. Copy all files from either the `x64` subfolder of the `libdigidocpp` installation folder to the example application build output folder `bin\...\net60` (after building, see next step). When building custom applications, choose `x64` if your application is 64-bit and `x86` if it is 32-bit. 4. When running in the `Development` profile, create an empty file named `EE_T.xml` for TSL cache as described in the [_Using test TSL lists_](https://github.com/open-eid/libdigidocpp/wiki/Using-test-TSL-lists#preconditions) section of the `libdigidocpp` wiki. -Further information is available in the [libdigidocpp example C# application](https://github.com/open-eid/libdigidocpp/tree/master/examples/DigiDocCSharp) and in the [`libdigidocpp` wiki](https://github.com/open-eid/libdigidocpp/wiki). +#### For Ubuntu Linux + +1. Add RIA repository to install the official _libdigidocpp-csharp_ package: +```sh +wget https://github.com/web-eid/web-eid-asp-dotnet-example/raw/main/src/ria_public_key.gpg +cp ria_public_key.gpg /usr/share/keyrings/ria-repository.gpg +echo "deb [signed-by=/usr/share/keyrings/ria-repository.gpg] https://installer.id.ee/media/ubuntu/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/ria-repository.list +``` +2. Install the _libdigidocpp-csharp_ package: +```sh +apt update +apt install -y --no-install-recommends libdigidocpp-csharp +``` + +#### For macOS + +1. Install the _libdigidocpp-3.17.1.pkg_ package or higher. The installation packages are available from [https://github.com/open-eid/libdigidocpp/releases](https://github.com/open-eid/libdigidocpp/releases). +2. Copy the C# source files from `/Library/libdigidocpp/include/digidocpp_csharp` directory to `src/WebEid.AspNetCore.Example/DigiDoc` directory. +3. Go to `src/WebEid.AspNetCore.Example/bin/.../net60` directory and create symbolic link to `/Library/libdigidocpp/lib/libdigidoc_csharp.dylib` library: +```cmd +ln -s /Library/libdigidocpp/lib/libdigidoc_csharp.dylib +``` + +Further information is available in the [libdigidocpp example C# application source code](https://github.com/open-eid/libdigidocpp/tree/master/examples/DigiDocCSharp) and in the [`libdigidocpp` Wiki](https://github.com/open-eid/libdigidocpp/wiki). ### 4. Build the application @@ -60,7 +86,7 @@ dotnet build If you have a test eID card, use the `Development` profile. In this case access to paid services is not required, but you need to upload the authentication and signing certificates of the test card to the test OCSP responder database as described in section _[Using DigiDoc4j in test mode with the `dev` profile](https://github.com/web-eid/web-eid-spring-boot-example#using-digidoc4j-in-test-mode-with-the-dev-profile)_ of the Web eID Java example application documentation. The`Development` profile is activated by default. -If you only have a production eID card, use the `Production` profile. You can still test authentication without further configuration; however, for digital signing to work, you need access to a paid timestamping service as described in section [_Using DigiDoc4j in production mode with the `prod` profile_](https://github.com/web-eid/web-eid-spring-boot-example#using-digidoc4j-in-production-mode-with-the-prod-profile) of the Web eID Java example documentation. +If you only have a production eID card, i.e. an eID card issued to a real person or organization, use the `Production` profile. You can still test authentication without further configuration; however, for digital signing to work, you need access to a paid timestamping service as described in section [_Using DigiDoc4j in production mode with the `prod` profile_](https://github.com/web-eid/web-eid-spring-boot-example#using-digidoc4j-in-production-mode-with-the-prod-profile) of the Web eID Java example documentation. You can specify the profile as an environment variable `ASPNETCORE_ENVIRONMENT` when running the application. To set the profile for the current session before starting the app using [`dotnet run`](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-run), use the following command: ```cmd @@ -193,5 +219,4 @@ app.UseForwardedHeaders(new ForwardedHeadersOptions By default, this middleware is already enabled in the application. -A Docker Compose configuration file `docker-compose.yml` is available in the `src` directory for running the Docker image `web-eid-asp-dotnet-example` on port 8480 behind a reverse proxy. - +A Docker Compose configuration file `docker-compose.yml` is available in the `src` directory for running the Docker image `web-eid-asp-dotnet-example` on port 8480 behind a reverse proxy. \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs b/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs index c4874d4..b8873fd 100644 --- a/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs +++ b/example/src/WebEid.AspNetCore.Example/Controllers/Api/AuthController.cs @@ -17,7 +17,7 @@ // 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. -namespace WebEid.AspNetCore.Example.Controllers.Api +namespace WebEid.AspNetCore.Example.Controllers.Api { using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; @@ -29,6 +29,7 @@ using System.Threading.Tasks; using Security.Challenge; using WebEid.AspNetCore.Example.Dto; + using System; [Route("[controller]")] [ApiController] @@ -48,12 +49,13 @@ public AuthController(IAuthTokenValidator authTokenValidator, IChallengeNonceSto public async Task Login([FromBody] AuthenticateRequestDto authToken) { var certificate = await this.authTokenValidator.Validate(authToken.AuthToken, this.challengeNonceStore.GetAndRemove().Base64EncodedNonce); - var claims = new List - { - new Claim(ClaimTypes.GivenName, certificate.GetSubjectGivenName()), - new Claim(ClaimTypes.Surname, certificate.GetSubjectSurname()), - new Claim(ClaimTypes.NameIdentifier, certificate.GetSubjectIdCode()) - }; + + List claims = new(); + + AddNewClaimIfCertificateHasData(claims, ClaimTypes.GivenName, certificate.GetSubjectGivenName); + AddNewClaimIfCertificateHasData(claims, ClaimTypes.Surname, certificate.GetSubjectSurname); + AddNewClaimIfCertificateHasData(claims, ClaimTypes.NameIdentifier, certificate.GetSubjectIdCode); + AddNewClaimIfCertificateHasData(claims, ClaimTypes.Name, certificate.GetSubjectCn); var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); @@ -77,8 +79,16 @@ await HttpContext.SignInAsync( public async Task Logout() { RemoveUserContainerFile(); - await HttpContext.SignOutAsync( - CookieAuthenticationDefaults.AuthenticationScheme); + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + } + + private static void AddNewClaimIfCertificateHasData(List claims, string claimType, Func dataGetter) + { + var claimData = dataGetter(); + if (!string.IsNullOrEmpty(claimData)) + { + claims.Add(new Claim(claimType, claimData)); + } } } } diff --git a/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs b/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs index fcd2c03..7e19ee1 100644 --- a/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs +++ b/example/src/WebEid.AspNetCore.Example/Pages/Welcome.cshtml.cs @@ -35,7 +35,17 @@ private static string GetPrincipalName(ClaimsIdentity identity) var surname = identity.Claims.Where(claim => claim.Type == ClaimTypes.Surname) .Select(claim => claim.Value) .SingleOrDefault(); - return $"{givenName} {surname}"; + + if (!string.IsNullOrEmpty(givenName) && !string.IsNullOrEmpty(surname)) + { + return $"{givenName} {surname}"; + } + else + { + return identity.Claims.Where(claim => claim.Type == ClaimTypes.Name) + .Select(claim => claim.Value) + .SingleOrDefault(); + } } } } \ No newline at end of file diff --git a/example/src/WebEid.AspNetCore.Example/Properties/launchSettings.json b/example/src/WebEid.AspNetCore.Example/Properties/launchSettings.json index a2e5ff7..1532817 100644 --- a/example/src/WebEid.AspNetCore.Example/Properties/launchSettings.json +++ b/example/src/WebEid.AspNetCore.Example/Properties/launchSettings.json @@ -8,6 +8,15 @@ } }, "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:44391", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, From 216ad76d1ba4c948531f65fb754cc990cd97f7a3 Mon Sep 17 00:00:00 2001 From: rabadashTheFool <57250329+rabadashTheFool@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:44:17 +0300 Subject: [PATCH 24/34] Updated documentation in For Ubuntu Linux and Docker sections (#22) Updated documentation in For Ubuntu Linux and Docker sections WE2-572 Signed-off-by: Mihkel Kivisild --- example/README.md | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/example/README.md b/example/README.md index 4c63f32..1eeba5e 100644 --- a/example/README.md +++ b/example/README.md @@ -13,6 +13,9 @@ The ASP.NET web application makes use of the following technologies: - the Web eID JavaScript library [_web-eid.js_](https://github.com/web-eid/web-eid.js), - the digital signing library [_libdigidocpp_](https://github.com/open-eid/libdigidocpp/tree/master/examples/DigiDocCSharp). +Note that for including the Web eID authentication token validation library as a nuget package you need to have added a Package Source with the following address to the NuGet Package Manager: +https://gitlab.com/api/v4/projects/35362906/packages/nuget/index.json + ## Quickstart Complete the steps below to run the example application in order to test authentication and digital signing with Web eID. @@ -51,25 +54,35 @@ Set up the `libdigidocpp` library as follows: #### For Ubuntu Linux 1. Add RIA repository to install the official _libdigidocpp-csharp_ package: -```sh -wget https://github.com/web-eid/web-eid-asp-dotnet-example/raw/main/src/ria_public_key.gpg -cp ria_public_key.gpg /usr/share/keyrings/ria-repository.gpg -echo "deb [signed-by=/usr/share/keyrings/ria-repository.gpg] https://installer.id.ee/media/ubuntu/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/ria-repository.list -``` + ```sh + wget https://github.com/web-eid/web-eid-asp-dotnet-example/raw/main/src/ria_public_key.gpg + cp ria_public_key.gpg /usr/share/keyrings/ria-repository.gpg + echo "deb [signed-by=/usr/share/keyrings/ria-repository.gpg] https://installer.id.ee/media/ubuntu/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/ria-repository.list + ``` 2. Install the _libdigidocpp-csharp_ package: -```sh -apt update -apt install -y --no-install-recommends libdigidocpp-csharp -``` + ```sh + apt update + apt install -y --no-install-recommends libdigidocpp-csharp + ``` +3. Navigate to the `src` directory: + + ```sh + cd src + ``` +4. Copy the necessary DigiDoc C# library files into your project: + + ```sh + cp /usr/include/digidocpp_csharp/* WebEid.AspNetCore.Example/DigiDoc/ + ``` #### For macOS 1. Install the _libdigidocpp-3.17.1.pkg_ package or higher. The installation packages are available from [https://github.com/open-eid/libdigidocpp/releases](https://github.com/open-eid/libdigidocpp/releases). 2. Copy the C# source files from `/Library/libdigidocpp/include/digidocpp_csharp` directory to `src/WebEid.AspNetCore.Example/DigiDoc` directory. 3. Go to `src/WebEid.AspNetCore.Example/bin/.../net60` directory and create symbolic link to `/Library/libdigidocpp/lib/libdigidoc_csharp.dylib` library: -```cmd -ln -s /Library/libdigidocpp/lib/libdigidoc_csharp.dylib -``` + ```cmd + ln -s /Library/libdigidocpp/lib/libdigidoc_csharp.dylib + ``` Further information is available in the [libdigidocpp example C# application source code](https://github.com/open-eid/libdigidocpp/tree/master/examples/DigiDocCSharp) and in the [`libdigidocpp` Wiki](https://github.com/open-eid/libdigidocpp/wiki). @@ -149,6 +162,8 @@ You can install them using the following command: sudo apt install dotnet-sdk-7.0 libdigidocpp-csharp ``` +Note: Before installing `libdigidocpp-csharp` you have to have added the RIA repository as a package source. See [For Ubuntu Linux section](#for-ubuntu-linux) for information. + ### Building the application To build the application, follow these steps: @@ -219,4 +234,4 @@ app.UseForwardedHeaders(new ForwardedHeadersOptions By default, this middleware is already enabled in the application. -A Docker Compose configuration file `docker-compose.yml` is available in the `src` directory for running the Docker image `web-eid-asp-dotnet-example` on port 8480 behind a reverse proxy. \ No newline at end of file +A Docker Compose configuration file `docker-compose.yml` is available in the `src` directory for running the Docker image `web-eid-asp-dotnet-example` on port 8480 behind a reverse proxy. From 422a340790a297a633ea3cf208edc356d7bc1e88 Mon Sep 17 00:00:00 2001 From: Mihkel Kivisild Date: Mon, 17 Jun 2024 17:06:21 +0300 Subject: [PATCH 25/34] Updated example to .Net 8 and updated dependencies WE2-888 Signed-off-by: Mihkel Kivisild mihkel.kivisild@cgi.com --- example/.github/workflows/dotnet-build.yml | 8 ++++---- example/README.md | 4 ++-- example/src/.dockerignore | 2 +- example/src/Dockerfile | 6 +++--- .../WebEid.AspNetCore.Example.csproj | 18 +++++++++--------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/example/.github/workflows/dotnet-build.yml b/example/.github/workflows/dotnet-build.yml index debf937..2b32ece 100644 --- a/example/.github/workflows/dotnet-build.yml +++ b/example/.github/workflows/dotnet-build.yml @@ -7,15 +7,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup dotnet - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x # SDK Version to use. + dotnet-version: 8.0.x # SDK Version to use. - name: Cache Nuget packages - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.nuget/packages # Look to see if there is a cache hit for the corresponding requirements file diff --git a/example/README.md b/example/README.md index 1eeba5e..24cf3fe 100644 --- a/example/README.md +++ b/example/README.md @@ -48,7 +48,7 @@ Set up the `libdigidocpp` library as follows: 1. Install the _libdigidocpp-3.17.1.msi_ package or higher. The installation packages are available from [https://github.com/open-eid/libdigidocpp/releases](https://github.com/open-eid/libdigidocpp/releases). 2. Copy the C# source files from the `libdigidocpp` installation folder `include\digidocpp_csharp` to the `src\WebEid.AspNetCore.Example\DigiDoc` folder. -3. Copy all files from either the `x64` subfolder of the `libdigidocpp` installation folder to the example application build output folder `bin\...\net60` (after building, see next step). When building custom applications, choose `x64` if your application is 64-bit and `x86` if it is 32-bit. +3. Copy all files from either the `x64` subfolder of the `libdigidocpp` installation folder to the example application build output folder `bin\...\net8.0` (after building, see next step). When building custom applications, choose `x64` if your application is 64-bit and `x86` if it is 32-bit. 4. When running in the `Development` profile, create an empty file named `EE_T.xml` for TSL cache as described in the [_Using test TSL lists_](https://github.com/open-eid/libdigidocpp/wiki/Using-test-TSL-lists#preconditions) section of the `libdigidocpp` wiki. #### For Ubuntu Linux @@ -79,7 +79,7 @@ Set up the `libdigidocpp` library as follows: 1. Install the _libdigidocpp-3.17.1.pkg_ package or higher. The installation packages are available from [https://github.com/open-eid/libdigidocpp/releases](https://github.com/open-eid/libdigidocpp/releases). 2. Copy the C# source files from `/Library/libdigidocpp/include/digidocpp_csharp` directory to `src/WebEid.AspNetCore.Example/DigiDoc` directory. -3. Go to `src/WebEid.AspNetCore.Example/bin/.../net60` directory and create symbolic link to `/Library/libdigidocpp/lib/libdigidoc_csharp.dylib` library: +3. Go to `src/WebEid.AspNetCore.Example/bin/.../net8.0` directory and create symbolic link to `/Library/libdigidocpp/lib/libdigidoc_csharp.dylib` library: ```cmd ln -s /Library/libdigidocpp/lib/libdigidoc_csharp.dylib ``` diff --git a/example/src/.dockerignore b/example/src/.dockerignore index 19c9155..50e2413 100644 --- a/example/src/.dockerignore +++ b/example/src/.dockerignore @@ -24,4 +24,4 @@ LICENSE README.md -!WebEid.AspNetCore.Example/bin/Release/net6.0/publish/ +!WebEid.AspNetCore.Example/bin/Release/net8.0/publish/ diff --git a/example/src/Dockerfile b/example/src/Dockerfile index 8c8a127..33b8ad1 100644 --- a/example/src/Dockerfile +++ b/example/src/Dockerfile @@ -1,5 +1,5 @@ -# We're using .NET 6 now, but when we migrate to .NET 8 in the future, we should use chiseled images. -FROM mcr.microsoft.com/dotnet/aspnet:6.0-jammy +# In the future, we should use chiseled images. +FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy WORKDIR /app @@ -13,7 +13,7 @@ RUN echo "deb [signed-by=/usr/share/keyrings/ria-repository.gpg] https://install apt-get clean && \ rm -rf /var/lib/apt/lists -COPY ./WebEid.AspNetCore.Example/bin/Release/net6.0/publish/ . +COPY ./WebEid.AspNetCore.Example/bin/Release/net8.0/publish/ . ENV ASPNETCORE_ENVIRONMENT=Production diff --git a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj index ceaae77..fd43bc6 100644 --- a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj +++ b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 aspnet-web_eid_asp_dotnet_example-3FF31439-FDC0-4164-B154-03E005FE96CD WebEid.AspNetCore.Example false @@ -11,14 +11,14 @@ - - - - - - - - + + + + + + + + From 87dcb1df83368671125e651cb4b6d0ffeb8b79cf Mon Sep 17 00:00:00 2001 From: Mihkel Kivisild Date: Mon, 15 Jul 2024 10:00:20 +0300 Subject: [PATCH 26/34] Updated example application dependencies WE2-983 Signed-off-by: Mihkel Kivisild mihkel.kivisild@cgi.com --- .../WebEid.AspNetCore.Example.csproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj index fd43bc6..8c06c79 100644 --- a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj +++ b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj @@ -11,15 +11,15 @@ - - - - - - + + + + + + - - + + From c5640bdba50c8a0dd2daf61c55b792b61af2f865 Mon Sep 17 00:00:00 2001 From: Mihkel Kivisild Date: Tue, 22 Oct 2024 13:37:11 +0300 Subject: [PATCH 27/34] Updated example application dependencies Signed-off-by: Mihkel Kivisild --- .../WebEid.AspNetCore.Example.csproj | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj index 8c06c79..d131665 100644 --- a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj +++ b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj @@ -11,14 +11,14 @@ - - - - - + + + + + - - + + From 9a45a56b2cc12f41f7b208ab82e6f9c64a5a7609 Mon Sep 17 00:00:00 2001 From: Mihkel Kivisild Date: Tue, 1 Oct 2024 14:03:25 +0300 Subject: [PATCH 28/34] GitHub action using official libdigidocpp-csharp repository Signed-off-by: Mihkel Kivisild --- example/.github/workflows/dotnet-build.yml | 24 +++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/example/.github/workflows/dotnet-build.yml b/example/.github/workflows/dotnet-build.yml index 2b32ece..54ba2e7 100644 --- a/example/.github/workflows/dotnet-build.yml +++ b/example/.github/workflows/dotnet-build.yml @@ -25,13 +25,23 @@ jobs: - name: Install dependencies run: dotnet restore src/WebEid.AspNetCore.Example.sln --source "https://gitlab.com/api/v4/projects/35362906/packages/nuget/index.json" --source "https://api.nuget.org/v3/index.json" - - name: Download digidoc - run: wget https://gitlab.com/api/v4/projects/35362906/packages/generic/digidoc/1.0.0/digidoc.zip - - - name: Unzip digidoc - uses: montudor/action-zip@v1 - with: - args: unzip -qq digidoc.zip -d src/WebEid.AspNetCore.Example/DigiDoc + - name: Download RIA repository public key + run: wget https://github.com/web-eid/web-eid-asp-dotnet-example/raw/main/src/ria_public_key.gpg + + - name: Copy RIA repository key to keyrings + run: sudo cp ria_public_key.gpg /usr/share/keyrings/ria-repository.gpg + + - name: Add RIA repository to APT + run: | + echo "deb [signed-by=/usr/share/keyrings/ria-repository.gpg] https://installer.id.ee/media/ubuntu/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ria-repository.list + + - name: Update APT and install libdigidocpp-csharp + run: | + sudo apt update + sudo apt install -y --no-install-recommends libdigidocpp-csharp + + - name: Copy the necessary DigiDoc C# library files + run: sudo cp /usr/include/digidocpp_csharp/* src/WebEid.AspNetCore.Example/DigiDoc/ - name: Build run: dotnet publish --configuration Release --no-restore src/WebEid.AspNetCore.Example.sln --verbosity normal From a7c300f8f27c383a47e9f9e837b0b03a4548b71a Mon Sep 17 00:00:00 2001 From: Mart Somermaa Date: Thu, 25 Jul 2024 19:33:53 +0300 Subject: [PATCH 29/34] Add KLASS3-SK_2016 certificates and remove BES from signature timestamp profile WE2-745 Signed-off-by: Mart Somermaa --- .../Certificates/Dev/TEST_of_KLASS3-SK_2016.cer | Bin 0 -> 1741 bytes .../Signing/SigningService.cs | 2 +- .../WebEid.AspNetCore.Example.csproj | 10 ++++++++-- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_KLASS3-SK_2016.cer diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_KLASS3-SK_2016.cer b/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TEST_of_KLASS3-SK_2016.cer new file mode 100644 index 0000000000000000000000000000000000000000..190f6de7e83bbb980d0f5c932dff55022fbd8f47 GIT binary patch literal 1741 zcmb7EYfuwc6y8lX;T0ky5wL&+AAlg)yD>ae6w;WAqE!h}6>W^kMq_}4-9!+C5{ifg zVSLqE1QEqL9nk?BN_D_EMBWNIDx&pK5%B?3MFF4f21RWDbUHit?tb4r_wG68p7VhW zW+TX8lo!)!FpcIA!xATV`n4ZeE_PF_E#@Uq(fW#r_xw5(EKkI_3e~ z-^b=O5?jPVp+qv>UlPIP11u0^&?bJGLYM*3GiYNVinpa@&|s(;8CSYc9aCX_Uko>G zU--JNJ{ZZ1sBG;TxYe_XY`WLeu`Ssvq^zPJb*O8Uvho)=*gN+wVRVR|q=!93jHVf; z7YS8|_^W?)4GrkRUi%NtQSPYSn)cFpMP1!)}<#%6%m@c;Ytnw*%n<6R8zNRkv(@ld-=kKd-72e%h%{qNHrMo^}TtC*G zxGWBaQ};M_XA|B(hbQUh+<+es&<=Xms$2U~U+h2r=tjJ>CM?Opt|jx4psitEy*52X zG0iA0AKIJgzfsL|t(HpBb3CwQ!bGm2Ix$nZo@^va|fI{fN%CjAwkr-37F@JKAEOKUwuS0rdoIG}oN&m8; zu-y7AciEKmmW}#3MJ1@RUmMzP5}@1nCT%Vooadi0;hamz ztGLpNs&R`I>~E^g-ghJ0BQ8%;loUp<-8DYWya^gSS{pMfVCP=BZ|BWq-t9>%ANaT| zK&L>D(t53M^H0JVeg#8rSFRT3y{m|*sP;<0d=?p|78z)C7=jOF&@ieZ{J~hNnA{y< z_%Fm7FsT<+Ab@S*##lInYSB7i7_bmr#Lx!}kAdM^ri~vHM&Jt`R&=nDYDa4dG8k4g z*om`Vr_;_v(Zs|=-f;V%axDoWKU&bh7Zc_}LWqR)kd7K8ghSlnnV_%=Qc^<=$*4yM z@u(FMq=5)XJ3L!Bi}ieHF2n=YOy)WRi_#B(j=Dnw7IpY9cc`rhl%EX4l&n}GP)<)6 z^*KUXjgs`?PIG4?P6&tRntvKb1w+>XlC$dUvsF_%uMG}2vHK*1(vEbFQD9qGiaMO@ z_GH(X%kg=x^o{R;A$!Cb7)CaO_DYa_XIekxtS}nq_tgHLbpF)hX5rpT^P864 zIsKsVNxXGuo`8O`t(ClU1f6{mY$&OX4*&Xfd(oDO>pe6crMGs*w-C=biwc(JmT)=8 zv(GQe?=qC$>$G`_$+9j!_qGkaw13;t*62&)bM=$h?jin1H=PYz-`#e2#%s`PSas_7 zZPx>N{HoH+o>$qNra$~T%x^;7`f4-dOgB!fcz3Kk@>0%#xt<@MRsCZ84EN$=Y0sh7 v-An4f;d)=}dt;pn$K*2liKZ%-^RCr5+e2UK2e$SXUB6&gwTE64x6=7 - Always + PreserveNewest - Always + PreserveNewest + + + PreserveNewest + + + PreserveNewest From c87e1bbad24b323a627c29201a711ff6fc7ae133 Mon Sep 17 00:00:00 2001 From: Mihkel Kivisild Date: Tue, 29 Oct 2024 17:07:03 +0200 Subject: [PATCH 30/34] Add version number to .Net example WE2-945 Signed-off-by: Mihkel Kivisild --- .../WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj index 070eca4..9d5d3d1 100644 --- a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj +++ b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj @@ -8,6 +8,7 @@ Linux WebEid.AspNetCore.Example true + 1.2.0 From e513362736634d11738184d5bc718ce93b42dedb Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Tue, 29 Apr 2025 11:15:19 +0300 Subject: [PATCH 31/34] Minor versions upgrades to latest WE2-1085 Signed-off-by: Sven Mitt --- .../WebEid.AspNetCore.Example.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj index 9d5d3d1..1258b1a 100644 --- a/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj +++ b/example/src/WebEid.AspNetCore.Example/WebEid.AspNetCore.Example.csproj @@ -12,11 +12,11 @@ - - - - - + + + + + From 408df7520b1e55a159626063ce5d415ea7150c49 Mon Sep 17 00:00:00 2001 From: Mart Somermaa Date: Fri, 21 Mar 2025 16:06:13 +0200 Subject: [PATCH 32/34] Add Thales test ID card intermediate CA to trusted certificates in Dev profile WE2-1064 Signed-off-by: Mart Somermaa --- .../Certificates/Dev/TestESTEID2025.cer | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 example/src/WebEid.AspNetCore.Example/Certificates/Dev/TestESTEID2025.cer diff --git a/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TestESTEID2025.cer b/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TestESTEID2025.cer new file mode 100644 index 0000000..ca8933f --- /dev/null +++ b/example/src/WebEid.AspNetCore.Example/Certificates/Dev/TestESTEID2025.cer @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMTCCAregAwIBAgIUNtXxgsJYFy9r5Opm2j2LcsnZYtkwCgYIKoZIzj0EAwMw +XTEZMBcGA1UEAwwQVGVzdCBFRUdvdkNBMjAyNTEXMBUGA1UEYQwOTlRSRUUtMTcw +NjYwNDkxGjAYBgNVBAoMEVpldGVzIEVzdG9uaWEgT8OcMQswCQYDVQQGEwJFRTAe +Fw0yNDExMDQxMjU5NTVaFw0zOTExMDMxMjU5NTRaMFwxGDAWBgNVBAMMD1Rlc3Qg +RVNURUlEMjAyNTEXMBUGA1UEYQwOTlRSRUUtMTcwNjYwNDkxGjAYBgNVBAoMEVpl +dGVzIEVzdG9uaWEgT8OcMQswCQYDVQQGEwJFRTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABC8Uc5s70j1iWMZNbQyVYpDmwp4Ad5HlQmFB9noY2yBeDKL2KHKQG31SDTbo +KlBz7JUWsmaxF1Vj6ZkKAwcltO2cBnEU1B5H8hWgk5Un61GZxhX2wPkwJLm7vjyi +dKmftqOCATcwggEzMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAU4Vbf +rsSXORfv3goMbOVys4vVchAwSQYIKwYBBQUHAQEEPTA7MDkGCCsGAQUFBzAChi1o +dHRwOi8vY3J0LXRlc3QuZWlkcGtpLmVlL3Rlc3RFRUdvdkNBMjAyNS5jcnQwQgYD +VR0gBDswOTA3BgRVHSAAMC8wLQYIKwYBBQUHAgEWIWh0dHBzOi8vcmVwb3NpdG9y +eS10ZXN0LmVpZHBraS5lZTA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vY3JsLXRl +c3QuZWlkcGtpLmVlL3Rlc3RFRUdvdkNBMjAyNS5jcmwwHQYDVR0OBBYEFO7ylT+M +svxRnoTm5l6EEX5CuiA2MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBl +AjEA3qECw4GIfbeoC5cFhtiPJRfFlzsjRGVBtQTH6DNbZsm+EF6Gc28/iZFX1H6n +UTRlAjAiwooqEyVbxA1KqT6PwVl1BXNbF59j6MaiNR43dYeJxrdOnxleR50EVdIC +DJFEm2E= +-----END CERTIFICATE----- \ No newline at end of file From 0e9878595fffbf60257c4ef3fb6232094ec7d63e Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Wed, 30 Jul 2025 11:25:47 +0300 Subject: [PATCH 33/34] Move workflow scripts from example/.github/workflows to .github/workflows WE2-933 Signed-off-by: Sven Mitt --- .../workflows/dotnet-build-example.yml | 18 +++++++++++++++--- .github/workflows/dotnet-build-linux.yml | 10 +++++++++- .github/workflows/dotnet-build-windows.yml | 10 +++++++++- .github/workflows/sonarcloud-analysis.yml | 6 +++++- 4 files changed, 38 insertions(+), 6 deletions(-) rename example/.github/workflows/dotnet-build.yml => .github/workflows/dotnet-build-example.yml (87%) diff --git a/example/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build-example.yml similarity index 87% rename from example/.github/workflows/dotnet-build.yml rename to .github/workflows/dotnet-build-example.yml index 54ba2e7..6f9f2e7 100644 --- a/example/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build-example.yml @@ -1,6 +1,18 @@ -name: Dotnet build +name: Dotnet example build -on: [ push, pull_request ] +on: + push: + paths: + - 'example/**' + - '.github/workflows/*example*' + pull_request: + paths: + - 'example/**' + - '.github/workflows/*example*' + +defaults: + run: + working-directory: ./example jobs: build: @@ -50,5 +62,5 @@ jobs: run: dotnet test --no-restore --verbosity normal src/WebEid.AspNetCore.Example.sln - name: Test building Docker image - working-directory: ./src + working-directory: ./example/src run: docker build -t web-eid-asp-dotnet-example . diff --git a/.github/workflows/dotnet-build-linux.yml b/.github/workflows/dotnet-build-linux.yml index d39cd8d..b31c92b 100644 --- a/.github/workflows/dotnet-build-linux.yml +++ b/.github/workflows/dotnet-build-linux.yml @@ -1,6 +1,14 @@ name: Dotnet Linux build -on: [ push, pull_request ] +on: + push: + paths-ignore: + - 'example/**' + - '.github/workflows/*example*' + pull_request: + paths-ignore: + - 'example/**' + - '.github/workflows/*example*' jobs: build: diff --git a/.github/workflows/dotnet-build-windows.yml b/.github/workflows/dotnet-build-windows.yml index 3fb7f48..f487a55 100644 --- a/.github/workflows/dotnet-build-windows.yml +++ b/.github/workflows/dotnet-build-windows.yml @@ -1,6 +1,14 @@ name: Dotnet Windows build -on: [ push, pull_request ] +on: + push: + paths-ignore: + - 'example/**' + - '.github/workflows/*example*' + pull_request: + paths-ignore: + - 'example/**' + - '.github/workflows/*example*' jobs: build: diff --git a/.github/workflows/sonarcloud-analysis.yml b/.github/workflows/sonarcloud-analysis.yml index d4d12d3..2172ed9 100644 --- a/.github/workflows/sonarcloud-analysis.yml +++ b/.github/workflows/sonarcloud-analysis.yml @@ -1,6 +1,10 @@ name: SonarCloud code analysis -on: [push] +on: + push: + paths-ignore: + - 'example/**' + - '.github/workflows/*example*' jobs: analyze: From 11052e11cbae0525cdee6b9dab71fd0d51bfa850 Mon Sep 17 00:00:00 2001 From: Sven Mitt Date: Wed, 30 Jul 2025 15:36:08 +0300 Subject: [PATCH 34/34] Use sonar.token instead of deprecated sonar.login WE2-1104 Signed-off-by: Sven Mitt --- .github/workflows/sonarcloud-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud-analysis.yml b/.github/workflows/sonarcloud-analysis.yml index 2172ed9..85ef4fb 100644 --- a/.github/workflows/sonarcloud-analysis.yml +++ b/.github/workflows/sonarcloud-analysis.yml @@ -67,7 +67,7 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} shell: powershell run: | - .\.sonar\scanner\dotnet-sonarscanner begin /k:"web-eid_web-eid-authtoken-validation-dotnet" /o:"web-eid" /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" -d:sonar.cs.vstest.reportsPaths="**/TestResults/*.trx" /d:sonar.verbose=true /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" + .\.sonar\scanner\dotnet-sonarscanner begin /k:"web-eid_web-eid-authtoken-validation-dotnet" /o:"web-eid" /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" -d:sonar.cs.vstest.reportsPaths="**/TestResults/*.trx" /d:sonar.verbose=true /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" dotnet build --configuration Release --no-restore src/WebEid.Security.sln dotnet test src/WebEid.Security.sln --logger trx --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover --results-directory "TestResults" .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"