diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8c23067 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..abbc343 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,76 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '42 11 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp', 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/Snappass.NET.UnitTest/Snappass.NET.UnitTest.csproj b/Snappass.NET.UnitTest/Snappass.NET.UnitTest.csproj index 2020075..7b42af1 100644 --- a/Snappass.NET.UnitTest/Snappass.NET.UnitTest.csproj +++ b/Snappass.NET.UnitTest/Snappass.NET.UnitTest.csproj @@ -1,18 +1,24 @@ - netcoreapp3.0 + net7.0 false - - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Snappass.NET.sln b/Snappass.NET.sln index 6b656f1..6122ed5 100644 --- a/Snappass.NET.sln +++ b/Snappass.NET.sln @@ -1,11 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29411.108 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snappass.NET", "Snappass.NET\Snappass.NET.csproj", "{9D5B2889-491B-4E44-B00D-4DA803197031}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snappass.NET.UnitTest", "Snappass.NET.UnitTest\Snappass.NET.UnitTest.csproj", "{2B880861-D49F-4C86-AB15-67BC84D5933A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snappass.NET.UnitTest", "Snappass.NET.UnitTest\Snappass.NET.UnitTest.csproj", "{2B880861-D49F-4C86-AB15-67BC84D5933A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Snappass.NET/Controllers/PasswordController.cs b/Snappass.NET/Controllers/PasswordController.cs index d508bf7..238afa5 100644 --- a/Snappass.NET/Controllers/PasswordController.cs +++ b/Snappass.NET/Controllers/PasswordController.cs @@ -16,16 +16,23 @@ public PasswordController(IMemoryStore memoryStore, ILogger } [HttpGet] + [HttpPost] public IActionResult Preview(string key) { - if (!_memoryStore.Has(key)) + (string storageKey, string encryptionKey) = Encryption.ParseToken(key); + if (!_memoryStore.Has(storageKey)) { - _logger.LogWarning($@"password with key {key} requested, but not found"); + _logger.LogWarning($@"password with key {storageKey} requested, but not found"); return NotFound(); } - string encryptedPassword = _memoryStore.Retrieve(key); - string decrypted = Encryption.Decrypt(encryptedPassword, key); - return View("Preview", new PreviewModel { Key = decrypted }); + if (HttpContext.Request.Method == "POST") + { + string encryptedPassword = _memoryStore.Retrieve(storageKey); + string decrypted = Encryption.Decrypt(encryptedPassword, encryptionKey); + return View("Password", new PreviewModel { Key = decrypted }); + } + + return View("Preview"); } } } diff --git a/Snappass.NET/Controllers/ShareController.cs b/Snappass.NET/Controllers/ShareController.cs index acb0593..b1e2540 100644 --- a/Snappass.NET/Controllers/ShareController.cs +++ b/Snappass.NET/Controllers/ShareController.cs @@ -35,12 +35,15 @@ public IActionResult Share(string password, string ttl) "week" => TimeToLive.Week, "day" => TimeToLive.Day, "hour" => TimeToLive.Hour, - _ => throw new ArgumentException("Expected week, day or hour"), + "twoweeks" => TimeToLive.TwoWeeks, + _ => throw new ArgumentException("Expected twoweeks, week, day or hour"), }; TimeToLive timeToLive = Parse(ttl); - (string encryptedPassword, string key) = Encryption.Encrypt(password); - _memoryStore.Store(encryptedPassword, key, timeToLive); - var model = new GeneratedPassword { Key = key, BaseUri = GetBaseUrl() }; + string storageKey = Guid.NewGuid().ToString("N").ToUpper(); + (string encryptedPassword, string encryptionKey) = Encryption.Encrypt(password); + _memoryStore.Store(encryptedPassword, storageKey, timeToLive); + string token = Encryption.CreateToken(storageKey, encryptionKey); + var model = new GeneratedPassword { Token = token, BaseUri = GetBaseUrl() }; return View("Shared", model); } } diff --git a/Snappass.NET/Dockerfile b/Snappass.NET/Dockerfile new file mode 100644 index 0000000..404f160 --- /dev/null +++ b/Snappass.NET/Dockerfile @@ -0,0 +1,25 @@ +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. + +#Depending on the operating system of the host machines(s) that will build or run the containers, the image specified in the FROM statement may need to be changed. +#For more information, please see https://aka.ms/containercompat + +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["Snappass.NET/Snappass.NET.csproj", "Snappass.NET/"] +RUN dotnet restore "Snappass.NET/Snappass.NET.csproj" +COPY . . +WORKDIR "/src/Snappass.NET" +RUN dotnet build "Snappass.NET.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Snappass.NET.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Snappass.NET.dll"] \ No newline at end of file diff --git a/Snappass.NET/Encryption.cs b/Snappass.NET/Encryption.cs index 712ea2c..89bd152 100644 --- a/Snappass.NET/Encryption.cs +++ b/Snappass.NET/Encryption.cs @@ -1,25 +1,47 @@ using System; +using System.Net; using Fernet; namespace Snappass { public class Encryption { - public static (string encryptedPassword, string key) Encrypt(string password) + private static readonly char tokenSeparator = '~'; + + public static (string encryptedPassword, string encryptionKey) Encrypt(string password) { - byte[] keyBytes = SimpleFernet.GenerateKey().UrlSafe64Decode(); + byte[] EncryptionKeyBytes = SimpleFernet.GenerateKey().UrlSafe64Decode(); var passwordBytes = System.Text.Encoding.Unicode.GetBytes(password); - string encrypted = SimpleFernet.Encrypt(keyBytes, passwordBytes); - string key = keyBytes.UrlSafe64Encode(); - return (encrypted, key); + string encryptedPassword = SimpleFernet.Encrypt(EncryptionKeyBytes, passwordBytes); + string encryptionKey = EncryptionKeyBytes.UrlSafe64Encode(); + return (encryptedPassword, encryptionKey); } - public static string Decrypt(string encrypted, string key) + public static string Decrypt(string encryptedPassword, string encryptionKey) { - var keyBytes = key.UrlSafe64Decode(); - var decryptedBytes = SimpleFernet.Decrypt(keyBytes, encrypted, out DateTime _); + var encryptionKeyBytes = encryptionKey.UrlSafe64Decode(); + var decryptedBytes = SimpleFernet.Decrypt(encryptionKeyBytes, encryptedPassword, out DateTime _); var decrypted = decryptedBytes.UrlSafe64Encode().FromBase64String().Replace("\0", ""); return decrypted; } + + public static (string storageKey, string decryptionKey) ParseToken(string token) + { + var tokenFragments = token.Split(tokenSeparator, 2); + var storageKey = tokenFragments[0]; + var decryptionKey = string.Empty; + + if (tokenFragments.Length > 1) + decryptionKey = WebUtility.UrlDecode(tokenFragments[1]); + + return (storageKey, decryptionKey); + } + public static string CreateToken(string storageKey, string encryptionKey) + { + var token = string.Join(tokenSeparator, storageKey, WebUtility.UrlEncode(encryptionKey)); + + return token; + + } } } diff --git a/Snappass.NET/MemoryStore.cs b/Snappass.NET/MemoryStore.cs index a8146db..3f0ae8e 100644 --- a/Snappass.NET/MemoryStore.cs +++ b/Snappass.NET/MemoryStore.cs @@ -71,18 +71,22 @@ public string Retrieve(string key) TimeToLive.Day => item.StoredDateTime.AddDays(1), TimeToLive.Week => item.StoredDateTime.AddDays(7), TimeToLive.Hour => item.StoredDateTime.AddHours(1), + TimeToLive.TwoWeeks => item.StoredDateTime.AddDays(14), + _ => item.StoredDateTime.AddHours(1), }; DateTime atTheLatest = GetAtTheLatest(item.TimeToLive); if (_dateTimeProvider.Now > atTheLatest) { static string ToString(TimeToLive ttl) => ttl switch { - TimeToLive.Week => "week", - TimeToLive.Day => "day", - TimeToLive.Hour => "hour", + TimeToLive.Week => "1 week", + TimeToLive.Day => "1 day", + TimeToLive.Hour => "1 hour", + TimeToLive.TwoWeeks => "2 weeks", + _ => "1 hour", }; var ttlString = ToString(item.TimeToLive); - _logger.Log(LogLevel.Warning, $@"Tried to retrieve password for key [{key}] after date is expired. Key set at [{item.StoredDateTime}] for 1 [{ttlString}]"); + _logger.Log(LogLevel.Warning, $@"Tried to retrieve password for key [{key}] after date is expired. Key set at [{item.StoredDateTime}] for [{ttlString}]"); _items.Remove(key); // ensure "read-once" is implemented return null; } diff --git a/Snappass.NET/Models/GeneratedPassword.cs b/Snappass.NET/Models/GeneratedPassword.cs index c5c5161..cf0916c 100644 --- a/Snappass.NET/Models/GeneratedPassword.cs +++ b/Snappass.NET/Models/GeneratedPassword.cs @@ -2,8 +2,8 @@ { public class GeneratedPassword { - public string Key { get; set; } + public string Token { get; set; } public string BaseUri { get; set; } - public string Uri => $@"{BaseUri}/Password/{Key}"; + public string Uri => $@"{BaseUri}/Password/{Token}"; } } diff --git a/Snappass.NET/Properties/serviceDependencies.json b/Snappass.NET/Properties/serviceDependencies.json new file mode 100644 index 0000000..33703d5 --- /dev/null +++ b/Snappass.NET/Properties/serviceDependencies.json @@ -0,0 +1,3 @@ +{ + "dependencies": {} +} \ No newline at end of file diff --git a/Snappass.NET/Properties/serviceDependencies.local.json b/Snappass.NET/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..33703d5 --- /dev/null +++ b/Snappass.NET/Properties/serviceDependencies.local.json @@ -0,0 +1,3 @@ +{ + "dependencies": {} +} \ No newline at end of file diff --git a/Snappass.NET/Snappass.NET.csproj b/Snappass.NET/Snappass.NET.csproj index b53859d..0dcf7f7 100644 --- a/Snappass.NET/Snappass.NET.csproj +++ b/Snappass.NET/Snappass.NET.csproj @@ -1,22 +1,24 @@ - + - netcoreapp3.0 + net7.0 Snappass.NET Snappass + Exe + - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + diff --git a/Snappass.NET/SqliteStore.cs b/Snappass.NET/SqliteStore.cs index 80096b9..c303722 100644 --- a/Snappass.NET/SqliteStore.cs +++ b/Snappass.NET/SqliteStore.cs @@ -46,6 +46,7 @@ public override TimeToLive Parse(object value) 0 => TimeToLive.Hour, 1 => TimeToLive.Day, 2 => TimeToLive.Week, + 3 => TimeToLive.TwoWeeks, _ => TimeToLive.Hour, }; } @@ -103,6 +104,7 @@ FROM SECRET TimeToLive.Day => dateTime.AddDays(1), TimeToLive.Week => dateTime.AddDays(7), TimeToLive.Hour => dateTime.AddHours(1), + TimeToLive.TwoWeeks => dateTime.AddDays(14), _ => dateTime.AddHours(1) }; DateTime atTheLatest = GetAtTheLatest(secret.TimeToLive, secret.StoredDateTime); @@ -110,13 +112,14 @@ FROM SECRET { static string ToString(TimeToLive ttl) => ttl switch { - TimeToLive.Week => "week", - TimeToLive.Day => "day", - TimeToLive.Hour => "hour", + TimeToLive.Week => "1 week", + TimeToLive.Day => "1 day", + TimeToLive.Hour => "1 hour", + TimeToLive.TwoWeeks => "2 weeks", _ => "hour" }; var ttlString = ToString(secret.TimeToLive); - _logger.Log(LogLevel.Warning, $@"Tried to retrieve password for key [{key}] after date is expired. Key set at [{secret.StoredDateTime}] for 1 [{ttlString}]"); + _logger.Log(LogLevel.Warning, $@"Tried to retrieve password for key [{key}] after date is expired. Key set at [{secret.StoredDateTime}] for [{ttlString}]"); Remove(key); return null; } diff --git a/Snappass.NET/TimeToLive.cs b/Snappass.NET/TimeToLive.cs index e5fe43b..82a223e 100644 --- a/Snappass.NET/TimeToLive.cs +++ b/Snappass.NET/TimeToLive.cs @@ -2,6 +2,6 @@ { public enum TimeToLive { - Week, Day, Hour + Week, Day, Hour, TwoWeeks } } diff --git a/Snappass.NET/Views/Password/Password.cshtml b/Snappass.NET/Views/Password/Password.cshtml new file mode 100644 index 0000000..30aa282 --- /dev/null +++ b/Snappass.NET/Views/Password/Password.cshtml @@ -0,0 +1,25 @@ +@{ + ViewData["Title"] = "ViewPassword"; + Layout = "~/Views/_Layout.cshtml"; +} +
+
+ +

Save the following secret to a secure location.

+
+
+ +
+ +
+ +
+
+

The secret has now been permanently deleted from the system, and the URL will no longer work. Refresh this page to verify.

+
+
+ diff --git a/Snappass.NET/Views/Password/Preview.cshtml b/Snappass.NET/Views/Password/Preview.cshtml index f52ac7d..1b28aea 100644 --- a/Snappass.NET/Views/Password/Preview.cshtml +++ b/Snappass.NET/Views/Password/Preview.cshtml @@ -1,5 +1,5 @@ @{ - ViewData["Title"] = "ViewPassword"; + ViewData["Title"] = "PreviewPassword"; Layout = "~/Views/_Layout.cshtml"; }
@@ -14,18 +14,6 @@
-
-

Save the following secret to a secure location.

-
- - -
-

The secret has now been permanently deleted from the system, and the URL will no longer work. Refresh this page to verify.

-
- + diff --git a/Snappass.NET/Views/Share/Share.cshtml b/Snappass.NET/Views/Share/Share.cshtml index 660073c..f274b78 100644 --- a/Snappass.NET/Views/Share/Share.cshtml +++ b/Snappass.NET/Views/Share/Share.cshtml @@ -16,9 +16,10 @@
diff --git a/Snappass.NET/database.sqlite b/Snappass.NET/database.sqlite index 4f70088..6efe9ab 100644 Binary files a/Snappass.NET/database.sqlite and b/Snappass.NET/database.sqlite differ diff --git a/Snappass.NET/libman.json b/Snappass.NET/libman.json new file mode 100644 index 0000000..ceee271 --- /dev/null +++ b/Snappass.NET/libman.json @@ -0,0 +1,5 @@ +{ + "version": "1.0", + "defaultProvider": "cdnjs", + "libraries": [] +} \ No newline at end of file diff --git a/Snappass.NET/wwwroot/js/preview.js b/Snappass.NET/wwwroot/js/preview.js index eaa7999..fafca51 100644 --- a/Snappass.NET/wwwroot/js/preview.js +++ b/Snappass.NET/wwwroot/js/preview.js @@ -1,6 +1,9 @@ (function () { $('#revealSecret').click(function (e) { - $('#revealed').show(); - $('#revealSecret').prop("disabled", true); + var form = $('
') + .attr('id', 'revealSecretForm') + .attr('method', 'post'); + form.appendTo($('body')); + form.submit(); }); })(); \ No newline at end of file