From e95f9e4433a8f1fef7dfe2355ad05df49e07a1a7 Mon Sep 17 00:00:00 2001 From: James Eversole Date: Tue, 2 May 2023 18:35:04 -0500 Subject: [PATCH 1/9] Flakeify repository and remove Stack --- .gitignore | 1 + build-docker | 13 --------- flake.lock | 60 ++++++++++++++++++++++++++++++++++++++++ flake.nix | 58 ++++++++++++++++++++++++++++++++++++++ Purr.cabal => purr.cabal | 18 +++++------- stack.yaml | 27 ------------------ stack.yaml.lock | 20 -------------- 7 files changed, 126 insertions(+), 71 deletions(-) delete mode 100755 build-docker create mode 100644 flake.lock create mode 100644 flake.nix rename Purr.cabal => purr.cabal (85%) delete mode 100644 stack.yaml delete mode 100644 stack.yaml.lock diff --git a/.gitignore b/.gitignore index 73533db..6200a1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ data/ bin/ +/result /config.dhall /Dockerfile /docker-stack.yml diff --git a/build-docker b/build-docker deleted file mode 100755 index 17b267e..0000000 --- a/build-docker +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e -# Date: 12/27/2022 -# Author: James Eversole -# ISC License -# This script completes a stack build and then builds a docker image -# containing Purr. The image name is the first argument to the script. - -IMAGE_NAME=${1:-"purr"} - -stack setup -stack build --copy-bins -docker build . -t $IMAGE_NAME diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..f3ca2a9 --- /dev/null +++ b/flake.lock @@ -0,0 +1,60 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1683067191, + "narHash": "sha256-41nOQDiYytmT6YVfI+X/Qo+rfIJOSuQ7OTAANAFp82o=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a002cddf22e5f6c59c5cdeece1285c7ae9236d7d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..278836e --- /dev/null +++ b/flake.nix @@ -0,0 +1,58 @@ +{ + description = "purr - a web application for generating and sharing secrets "; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + haskellPackages = pkgs.haskellPackages; + + jailbreakUnbreak = pkg: + pkgs.haskell.lib.doJailbreak (pkg.overrideAttrs (_: { meta = { }; })); + + packageName = "purr"; + in { + packages.${packageName} = + haskellPackages.callCabal2nix packageName self rec { + crypto-simple = + pkgs.haskell.lib.dontCheck haskellPackages.crypto-simple; + }; + + packages.default = self.packages.${system}.${packageName}; + defaultPackage = self.packages.${system}.default; + + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + ghcid + cabal-install + ]; + inputsFrom = map (__getAttr "env") (__attrValues self.packages.${system}); + }; + devShell = self.devShells.${system}.default; + packages.purrImage = pkgs.dockerTools.buildImage { + name = "purr"; + copyToRoot = pkgs.buildEnv { + name = "image-root"; + paths = [ self.packages.${system}.default ]; + pathsToLink = [ "/bin" ]; + }; + tag = "latest"; + config = { + Cmd = [ + "/bin/Purr-musl" + ]; + ExposedPorts = { + "3000/tcp" = {}; + }; + extraCommands = '' + ''; + }; + }; + }); +} diff --git a/Purr.cabal b/purr.cabal similarity index 85% rename from Purr.cabal rename to purr.cabal index 5a84633..421a78d 100644 --- a/Purr.cabal +++ b/purr.cabal @@ -1,12 +1,8 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.4. --- --- see: https://github.com/sol/hpack - -name: Purr +name: purr version: 0.3.0 -description: https://git.eversole.co/James/Purr +description: https://git.eversole.co/purr author: James Eversole maintainer: james@eversole.co copyright: 2022 James Eversole @@ -52,13 +48,13 @@ library , bytestring >=0.10.12.1 , containers >=0.6.4.1 , crypto-simple >=0.1.0.0 - , dhall >=1.40 && <1.41.2 + , dhall >=1.40 , file-embed ==0.0.15.0 , http-types >=0.12.3 , iso8601-time >=0.1.5 , mtl >=2.2.2 , random >=1.2 - , scotty ==0.12 + , scotty >=0.12 , shakespeare >=2.0.20 , split >=0.2.3.4 , sqlite-simple >=0.4.18.0 @@ -83,7 +79,7 @@ executable Purr-musl GeneralizedNewtypeDeriving OverloadedStrings ScopedTypeVariables - ghc-options: -threaded -rtsopts -with-rtsopts=-N -static -optl-static -optl-pthread -fPIC + ghc-options: -threaded -rtsopts -with-rtsopts=-N -optl-pthread -fPIC build-depends: Purr , base >=4.7 @@ -92,13 +88,13 @@ executable Purr-musl , bytestring >=0.10.12.1 , containers >=0.6.4.1 , crypto-simple >=0.1.0.0 - , dhall >=1.40 && <1.41.2 + , dhall >=1.40 , file-embed ==0.0.15.0 , http-types >=0.12.3 , iso8601-time >=0.1.5 , mtl >=2.2.2 , random >=1.2 - , scotty ==0.12 + , scotty >=0.12 , shakespeare >=2.0.20 , split >=0.2.3.4 , sqlite-simple >=0.4.18.0 diff --git a/stack.yaml b/stack.yaml deleted file mode 100644 index 7c12f6a..0000000 --- a/stack.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# This file was automatically generated by 'stack init' -# -resolver: - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/13.yaml - -# User packages to be built. -packages: -- . -# -extra-deps: -- crypto-simple-0.1.0.0@sha256:5c0e1e04a814d903743d7543245951a91a46817230fdf478fadca57116805fc1,1502 - -docker: - enable: true - image: "utdemir/ghc-musl:v24-ghc902" - -local-bin-path: - ./bin -#ghc-options: - -# Require a specific version of stack, using version ranges -# require-stack-version: -any # Default -# require-stack-version: ">=2.7" -# -# Override the architecture used by stack, especially useful on Windows -# arch: i386 -# arch: x86_64 diff --git a/stack.yaml.lock b/stack.yaml.lock deleted file mode 100644 index 4f86da1..0000000 --- a/stack.yaml.lock +++ /dev/null @@ -1,20 +0,0 @@ -# This file was autogenerated by Stack. -# You should not edit this file by hand. -# For more information, please see the documentation at: -# https://docs.haskellstack.org/en/stable/lock_files - -packages: -- completed: - hackage: crypto-simple-0.1.0.0@sha256:5c0e1e04a814d903743d7543245951a91a46817230fdf478fadca57116805fc1,1502 - pantry-tree: - size: 472 - sha256: 66c4ac2c2ddb74d31370026799a44fa78dc3b64d82cec0a1bc87b30e816195a4 - original: - hackage: crypto-simple-0.1.0.0@sha256:5c0e1e04a814d903743d7543245951a91a46817230fdf478fadca57116805fc1,1502 -snapshots: -- completed: - size: 618740 - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/13.yaml - sha256: ef98d70e4018bf01feb00ccdcd33ab26d056dbb71b38057c78fdd0d1ec671c85 - original: - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/13.yaml From a04b129e37414e2eb134f4c524bcbd75aa1d511a Mon Sep 17 00:00:00 2001 From: James Eversole Date: Tue, 2 May 2023 18:39:25 -0500 Subject: [PATCH 2/9] Update README for Nix; add TODO --- README | 20 ++++++-------------- TODO | 2 ++ 2 files changed, 8 insertions(+), 14 deletions(-) create mode 100644 TODO diff --git a/README b/README index a46dd3a..1ae3037 100644 --- a/README +++ b/README @@ -24,21 +24,13 @@ You shouldn't. This is free and open-source software which you can run on your o DEPLOYMENT -purr is intended to run in a docker container. -This repo's Stack project is configured to use a musl-based docker container for builds. -Assuming your working directory is inside of this repository: +Use Nix with flakes enabled. -1. Copy "examples/config.dhall" to ./config.dhall - configure this file appropriately. - - Use `openssl rand -hex 10` to generate an encryption key for "dbKey" -2. Copy "examples/Dockerfile" to ./Dockerfile -3. If using default database file location, run: `mkdir ./data; touch ./data/Purr.sqlite` -4. Run `chmod +x build-docker` -5. Run `./build-docker $IMAGE_NAME` to complete the initial Stack build and create the container -6. Orchestrate the container as desired - - docker run -d -v "$(pwd -P)/data/Purr.sqlite:/app/data/Purr.sqlite" \ - -v "$(pwd -P)/config.dhall:/app/config.dhall" \ - -p 5195:3000 purr - |- An example docker-stack.yml is provided: `docker stack deploy -c docker-stack.yml purr` +Build binary and run natively: +nix build && ./result/bin/Purr-musl + +Build and add Docker image to local registry: +nix build .#purrImage --impure && docker load < result DEVELOPMENT & SUPPORT diff --git a/TODO b/TODO new file mode 100644 index 0000000..dbe7c70 --- /dev/null +++ b/TODO @@ -0,0 +1,2 @@ +- Replace crypto-simple dependency +- Make the Docker images much smaller again From b4bbf6e5a796d6dfc44ac0a052ec4949d2394927 Mon Sep 17 00:00:00 2001 From: James Eversole Date: Tue, 2 May 2023 18:41:00 -0500 Subject: [PATCH 3/9] Add --impure flag for native build; add disclaimer that I'm working on dropping the impure requirement --- README | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README b/README index 1ae3037..3685ab8 100644 --- a/README +++ b/README @@ -27,11 +27,13 @@ DEPLOYMENT Use Nix with flakes enabled. Build binary and run natively: -nix build && ./result/bin/Purr-musl +nix build --impure && ./result/bin/Purr-musl Build and add Docker image to local registry: nix build .#purrImage --impure && docker load < result +I'll get rid of the "--impure" requirement ASAP. + DEVELOPMENT & SUPPORT Please send me an email for support or to provide patches. From 9efdc01828af6d56755ea381cb2708f3f8b288b6 Mon Sep 17 00:00:00 2001 From: James Eversole Date: Tue, 2 May 2023 21:26:40 -0500 Subject: [PATCH 4/9] Pure nix flake; replace crypto-simple with Saltine; update README and TODO --- purr.cabal => Purr.cabal | 4 +-- README | 6 ++--- TODO | 3 +-- flake.nix | 2 -- package.yaml | 12 ++++----- src/Feature/Sharing/SQLite.hs | 46 ++++++++++++++++++++--------------- src/Lib.hs | 2 ++ 7 files changed, 40 insertions(+), 35 deletions(-) rename purr.cabal => Purr.cabal (97%) diff --git a/purr.cabal b/Purr.cabal similarity index 97% rename from purr.cabal rename to Purr.cabal index 421a78d..38f3688 100644 --- a/purr.cabal +++ b/Purr.cabal @@ -47,13 +47,13 @@ library , blaze-html >=0.9.1.0 , bytestring >=0.10.12.1 , containers >=0.6.4.1 - , crypto-simple >=0.1.0.0 , dhall >=1.40 , file-embed ==0.0.15.0 , http-types >=0.12.3 , iso8601-time >=0.1.5 , mtl >=2.2.2 , random >=1.2 + , saltine >=0.2.0.0 , scotty >=0.12 , shakespeare >=2.0.20 , split >=0.2.3.4 @@ -87,13 +87,13 @@ executable Purr-musl , blaze-html >=0.9.1.0 , bytestring >=0.10.12.1 , containers >=0.6.4.1 - , crypto-simple >=0.1.0.0 , dhall >=1.40 , file-embed ==0.0.15.0 , http-types >=0.12.3 , iso8601-time >=0.1.5 , mtl >=2.2.2 , random >=1.2 + , saltine >=0.2.0.0 , scotty >=0.12 , shakespeare >=2.0.20 , split >=0.2.3.4 diff --git a/README b/README index 3685ab8..654ce70 100644 --- a/README +++ b/README @@ -27,12 +27,10 @@ DEPLOYMENT Use Nix with flakes enabled. Build binary and run natively: -nix build --impure && ./result/bin/Purr-musl +nix build && ./result/bin/Purr-musl Build and add Docker image to local registry: -nix build .#purrImage --impure && docker load < result - -I'll get rid of the "--impure" requirement ASAP. +nix build .#purrImage && docker load < result DEVELOPMENT & SUPPORT diff --git a/TODO b/TODO index dbe7c70..cfc4bda 100644 --- a/TODO +++ b/TODO @@ -1,2 +1 @@ -- Replace crypto-simple dependency -- Make the Docker images much smaller again +- Make the Docker images much smaller diff --git a/flake.nix b/flake.nix index 278836e..e76b8e2 100644 --- a/flake.nix +++ b/flake.nix @@ -20,8 +20,6 @@ in { packages.${packageName} = haskellPackages.callCabal2nix packageName self rec { - crypto-simple = - pkgs.haskell.lib.dontCheck haskellPackages.crypto-simple; }; packages.default = self.packages.${system}.${packageName}; diff --git a/package.yaml b/package.yaml index 2a04bd1..e34e824 100644 --- a/package.yaml +++ b/package.yaml @@ -33,14 +33,14 @@ dependencies: - blaze-html >= 0.9.1.0 - bytestring >= 0.10.12.1 - containers >= 0.6.4.1 -- crypto-simple >= 0.1.0.0 -- dhall >= 1.40 && < 1.41.2 +- dhall >= 1.40 - file-embed == 0.0.15.0 - http-types >= 0.12.3 - iso8601-time >= 0.1.5 - mtl >= 2.2.2 - random >= 1.2 -- scotty == 0.12 +- saltine >= 0.2.0.0 +- scotty >= 0.12 - shakespeare >= 2.0.20 - sqlite-simple >= 0.4.18.0 - split >= 0.2.3.4 @@ -61,9 +61,9 @@ executables: - -threaded - -rtsopts - -with-rtsopts=-N - - -static - - -optl-static - - -optl-pthread + #- -static + #- -optl-static + #- -optl-pthread - -fPIC dependencies: - Purr diff --git a/src/Feature/Sharing/SQLite.hs b/src/Feature/Sharing/SQLite.hs index af206ab..5702c76 100644 --- a/src/Feature/Sharing/SQLite.hs +++ b/src/Feature/Sharing/SQLite.hs @@ -4,18 +4,19 @@ import Core.SQLite import Core.Types import Feature.Generation.Passwords (Password) -import Control.Monad.Reader (ask, lift, liftIO) -import Crypto.Simple.CBC (decrypt, encrypt) -import Data.List.Split (splitOn) -import Data.Maybe (listToMaybe) -import Data.Time.Clock.POSIX (getPOSIXTime) +import Control.Monad.Reader (ask, lift, liftIO) +import Data.List.Split (splitOn) +import Data.Maybe (listToMaybe, fromMaybe) +import Data.Time.Clock.POSIX (getPOSIXTime) import Database.SQLite.Simple -import qualified Data.ByteString.Base64 as B64 -import qualified Data.ByteString.Char8 as B -import qualified Data.Text as T -import qualified Data.Text.Encoding as ET -import qualified Data.Text.Lazy as LT +import qualified Crypto.Saltine.Core.SecretBox as Box +import qualified Crypto.Saltine.Internal.SecretBox as IBox +import qualified Data.ByteString.Base64 as B64 +import qualified Data.ByteString.Char8 as B +import qualified Data.Text as T +import qualified Data.Text.Encoding as ET +import qualified Data.Text.Lazy as LT findByLink :: String -> PurrAction (Maybe T.Text) findByLink link = do @@ -30,7 +31,8 @@ insertNewSecret :: T.Text -> Integer -> T.Text -> Integer -> PurrAction () insertNewSecret sec life link maxViews = do db <- dbPath key <- encKey - encSec <- liftIO $ encryptSecret key sec + nonce <- liftIO $ Box.newNonce + let encSec = encryptSecret key sec nonce conn <- liftIO $ open db time <- liftIO $ epochTime liftIO $ execute conn @@ -41,12 +43,10 @@ insertNewSecret sec life link maxViews = do readEncryptedSecret :: String -> [SecretEntry] -> PurrAction (Maybe T.Text) readEncryptedSecret key sec = do db <- dbPath + nonce <- liftIO $ Box.newNonce liftIO $ incViews sec db delete <- liftIO $ deleteExpiredSecret sec db - decKey <- liftIO ( sequence - $ decryptSecret key - <$> decodeSecret - <$> listToMaybe sec ) + let decKey = decryptSecret key nonce $ decodeSecret $ safeHead failedSecret sec if (delete) then return Nothing else return (ET.decodeLatin1 <$> decKey) @@ -83,11 +83,19 @@ encodeSecret b = ET.decodeUtf8 $ B64.encode b decodeSecret :: SecretEntry -> B.ByteString decodeSecret s = B64.decodeLenient $ ET.encodeUtf8 (secret s) -encryptSecret :: String -> T.Text -> IO B.ByteString -encryptSecret k s = encrypt (B.pack k) (ET.encodeUtf8 s) +encryptSecret :: String -> T.Text -> Box.Nonce -> B.ByteString +encryptSecret k s n = do + Box.secretbox (IBox.Key $ B.pack k) n (ET.encodeUtf8 s) -decryptSecret :: String -> B.ByteString -> IO B.ByteString -decryptSecret k b = decrypt (B.pack k) b +decryptSecret :: String -> Box.Nonce -> B.ByteString -> Maybe B.ByteString +decryptSecret k n b = Box.secretboxOpen (IBox.Key $ B.pack k) n b epochTime :: IO Integer epochTime = fmap round getPOSIXTime + +failedSecret :: SecretEntry +failedSecret = SecretEntry "fail" "fail" 0 0 0 0 + +safeHead :: a -> [a] -> a +safeHead x [] = x +safeHead x l = head l diff --git a/src/Lib.hs b/src/Lib.hs index cf0d20e..aca3f90 100644 --- a/src/Lib.hs +++ b/src/Lib.hs @@ -6,12 +6,14 @@ import qualified Core.SQLite as DB import Core.Types import Control.Monad.Reader (lift, liftIO, runReaderT) +import Crypto.Saltine (sodiumInit) import GHC.Natural (popCountNatural) import Prelude hiding (id) import Web.Scotty.Trans (scottyT) main :: IO () main = do + sodiumInit dhallConf <- liftIO Configuration.main DB.main (dbFile dhallConf) scottyT (applicationPort dhallConf) (flip runApp dhallConf) HTTP.app where From a05d227138d584a78062ef126cf53a3e283cb0da Mon Sep 17 00:00:00 2001 From: James Eversole Date: Thu, 4 May 2023 07:45:02 -0500 Subject: [PATCH 5/9] Resolve cached naming issue; statically link Haskell dependencies --- Purr.cabal | 6 +++--- README | 2 +- flake.lock | 6 +++--- flake.nix | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Purr.cabal b/Purr.cabal index 38f3688..f9df5c0 100644 --- a/Purr.cabal +++ b/Purr.cabal @@ -1,8 +1,8 @@ cabal-version: 1.12 -name: purr +name: Purr version: 0.3.0 -description: https://git.eversole.co/purr +description: https://git.eversole.co/Purr author: James Eversole maintainer: james@eversole.co copyright: 2022 James Eversole @@ -65,7 +65,7 @@ library , wai-middleware-static >=0.5 default-language: Haskell2010 -executable Purr-musl +executable Purr main-is: Main.hs other-modules: Paths_Purr diff --git a/README b/README index 654ce70..7f1d222 100644 --- a/README +++ b/README @@ -30,7 +30,7 @@ Build binary and run natively: nix build && ./result/bin/Purr-musl Build and add Docker image to local registry: -nix build .#purrImage && docker load < result +nix build .#purr-docker && docker load < result DEVELOPMENT & SUPPORT diff --git a/flake.lock b/flake.lock index f3ca2a9..c890336 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1683067191, - "narHash": "sha256-41nOQDiYytmT6YVfI+X/Qo+rfIJOSuQ7OTAANAFp82o=", + "lastModified": 1683159243, + "narHash": "sha256-Fh41KQcZTswb4NyYfSsbNEhDS/Im0/Id6m3k7qZ6/Xw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a002cddf22e5f6c59c5cdeece1285c7ae9236d7d", + "rev": "3a227d4f883aa6b39b1772041494f38a9a427595", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index e76b8e2..96fa37b 100644 --- a/flake.nix +++ b/flake.nix @@ -9,14 +9,13 @@ outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let - pkgs = nixpkgs.legacyPackages.${system}; + pkgs = nixpkgs.legacyPackages.${system}; + packageName = "purr"; + dockerPackageName = "${packageName}-docker"; haskellPackages = pkgs.haskellPackages; - jailbreakUnbreak = pkg: - pkgs.haskell.lib.doJailbreak (pkg.overrideAttrs (_: { meta = { }; })); - - packageName = "purr"; + enableSharedExecutables = false; in { packages.${packageName} = haskellPackages.callCabal2nix packageName self rec { @@ -32,8 +31,9 @@ ]; inputsFrom = map (__getAttr "env") (__attrValues self.packages.${system}); }; + devShell = self.devShells.${system}.default; - packages.purrImage = pkgs.dockerTools.buildImage { + packages.${dockerPackageName} = pkgs.dockerTools.buildImage { name = "purr"; copyToRoot = pkgs.buildEnv { name = "image-root"; From a9c08415926e0511f0ba901bdebd3c37a1f4f5de Mon Sep 17 00:00:00 2001 From: James Eversole Date: Fri, 5 May 2023 19:38:59 -0500 Subject: [PATCH 6/9] Stop using a Cabal library to achieve reasonable Docker image sizes --- Purr.cabal | 73 +++++++++++++----------------------------------------- flake.lock | 52 +++++++++++++++++++++++++++++++++++++- flake.nix | 19 ++++++++++---- 3 files changed, 82 insertions(+), 62 deletions(-) diff --git a/Purr.cabal b/Purr.cabal index f9df5c0..2ad0e4b 100644 --- a/Purr.cabal +++ b/Purr.cabal @@ -13,64 +13,11 @@ extra-source-files: README ChangeLog.md -library - exposed-modules: - Core.Configuration - Core.HTTP - Core.SQLite - Core.Templates - Core.Types - Feature.Generation.HTTP - Feature.Generation.Links - Feature.Generation.Passwords - Feature.Generation.Shared - Feature.Generation.Templates - Feature.Sharing.HTTP - Feature.Sharing.SQLite - Feature.Sharing.Templates - Lib - other-modules: - Paths_Purr - hs-source-dirs: - src - default-extensions: - ConstraintKinds - DeriveGeneric - FlexibleContexts - FlexibleInstances - GeneralizedNewtypeDeriving - OverloadedStrings - ScopedTypeVariables - build-depends: - base >=4.7 - , base64-bytestring >=1.2.0.0 - , blaze-html >=0.9.1.0 - , bytestring >=0.10.12.1 - , containers >=0.6.4.1 - , dhall >=1.40 - , file-embed ==0.0.15.0 - , http-types >=0.12.3 - , iso8601-time >=0.1.5 - , mtl >=2.2.2 - , random >=1.2 - , saltine >=0.2.0.0 - , scotty >=0.12 - , shakespeare >=2.0.20 - , split >=0.2.3.4 - , sqlite-simple >=0.4.18.0 - , text >=1.2.5.0 - , time >=1.9 - , utf8-string ==1.0.2 - , wai-extra >=3.1.12.1 - , wai-middleware-static >=0.5 - default-language: Haskell2010 - executable Purr main-is: Main.hs - other-modules: - Paths_Purr hs-source-dirs: app + , src default-extensions: ConstraintKinds DeriveGeneric @@ -81,8 +28,7 @@ executable Purr ScopedTypeVariables ghc-options: -threaded -rtsopts -with-rtsopts=-N -optl-pthread -fPIC build-depends: - Purr - , base >=4.7 + base >=4.7 , base64-bytestring >=1.2.0.0 , blaze-html >=0.9.1.0 , bytestring >=0.10.12.1 @@ -103,4 +49,19 @@ executable Purr , utf8-string ==1.0.2 , wai-extra >=3.1.12.1 , wai-middleware-static >=0.5 + other-modules: + Core.Configuration + Core.HTTP + Core.SQLite + Core.Templates + Core.Types + Feature.Generation.HTTP + Feature.Generation.Links + Feature.Generation.Passwords + Feature.Generation.Shared + Feature.Generation.Templates + Feature.Sharing.HTTP + Feature.Sharing.SQLite + Feature.Sharing.Templates + Lib default-language: Haskell2010 diff --git a/flake.lock b/flake.lock index c890336..a8c529a 100644 --- a/flake.lock +++ b/flake.lock @@ -18,7 +18,56 @@ "type": "github" } }, + "flake-utils_2": { + "locked": { + "lastModified": 1653893745, + "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nix2container": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1677791117, + "narHash": "sha256-zL4Fc5133KMqX7zCzcPODaBPEblUfgQt2UccNBm34Pc=", + "owner": "nlewo", + "repo": "nix2container", + "rev": "85670cab354f7df69dd4af097c27cf9bc5826cb2", + "type": "github" + }, + "original": { + "owner": "nlewo", + "repo": "nix2container", + "type": "github" + } + }, "nixpkgs": { + "locked": { + "lastModified": 1677612629, + "narHash": "sha256-yC+9LfhfwOd5sXFW8TLnDmqVMNYiHXYPGy9BbdpRqfU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "111ca8e0378e88d9decaa1c6dd7597f35d8bc67f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { "locked": { "lastModified": 1683159243, "narHash": "sha256-Fh41KQcZTswb4NyYfSsbNEhDS/Im0/Id6m3k7qZ6/Xw=", @@ -36,7 +85,8 @@ "root": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" + "nix2container": "nix2container", + "nixpkgs": "nixpkgs_2" } }, "systems": { diff --git a/flake.nix b/flake.nix index 96fa37b..5c4704d 100644 --- a/flake.nix +++ b/flake.nix @@ -4,22 +4,29 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; flake-utils.url = "github:numtide/flake-utils"; + nix2container.url = "github:nlewo/nix2container"; }; - outputs = { self, nixpkgs, flake-utils }: + outputs = { self, nixpkgs, flake-utils, nix2container }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; packageName = "purr"; dockerPackageName = "${packageName}-docker"; + nix2containerPkgs = nix2container.packages.x86_64-linux; + haskellPackages = pkgs.haskellPackages; enableSharedExecutables = false; + enableSharedLibraries = false; + + purr = pkgs.haskell.lib.justStaticExecutables self.packages.${system}.default; + in { + packages.${packageName} = - haskellPackages.callCabal2nix packageName self rec { - }; + haskellPackages.callCabal2nix packageName self rec {}; packages.default = self.packages.${system}.${packageName}; defaultPackage = self.packages.${system}.default; @@ -33,17 +40,19 @@ }; devShell = self.devShells.${system}.default; + packages.${dockerPackageName} = pkgs.dockerTools.buildImage { name = "purr"; + copyToRoot = pkgs.buildEnv { name = "image-root"; - paths = [ self.packages.${system}.default ]; + paths = [ purr ]; pathsToLink = [ "/bin" ]; }; tag = "latest"; config = { Cmd = [ - "/bin/Purr-musl" + "/bin/Purr" ]; ExposedPorts = { "3000/tcp" = {}; From 1ad7ce3440210afffd390c342cfe636ae1d97fef Mon Sep 17 00:00:00 2001 From: James Eversole Date: Sun, 7 May 2023 13:08:47 -0500 Subject: [PATCH 7/9] Remove nix2container from flake, update dev shell config --- .gitignore | 2 ++ TODO | 1 - flake.lock | 52 +--------------------------------------------------- flake.nix | 10 +++------- 4 files changed, 6 insertions(+), 59 deletions(-) diff --git a/.gitignore b/.gitignore index 6200a1e..c81ff34 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ bin/ /Dockerfile /docker-stack.yml .stack-work/ +*.swp +dist* *~ diff --git a/TODO b/TODO index cfc4bda..e69de29 100644 --- a/TODO +++ b/TODO @@ -1 +0,0 @@ -- Make the Docker images much smaller diff --git a/flake.lock b/flake.lock index a8c529a..c890336 100644 --- a/flake.lock +++ b/flake.lock @@ -18,56 +18,7 @@ "type": "github" } }, - "flake-utils_2": { - "locked": { - "lastModified": 1653893745, - "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nix2container": { - "inputs": { - "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1677791117, - "narHash": "sha256-zL4Fc5133KMqX7zCzcPODaBPEblUfgQt2UccNBm34Pc=", - "owner": "nlewo", - "repo": "nix2container", - "rev": "85670cab354f7df69dd4af097c27cf9bc5826cb2", - "type": "github" - }, - "original": { - "owner": "nlewo", - "repo": "nix2container", - "type": "github" - } - }, "nixpkgs": { - "locked": { - "lastModified": 1677612629, - "narHash": "sha256-yC+9LfhfwOd5sXFW8TLnDmqVMNYiHXYPGy9BbdpRqfU=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "111ca8e0378e88d9decaa1c6dd7597f35d8bc67f", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { "locked": { "lastModified": 1683159243, "narHash": "sha256-Fh41KQcZTswb4NyYfSsbNEhDS/Im0/Id6m3k7qZ6/Xw=", @@ -85,8 +36,7 @@ "root": { "inputs": { "flake-utils": "flake-utils", - "nix2container": "nix2container", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs" } }, "systems": { diff --git a/flake.nix b/flake.nix index 5c4704d..480447c 100644 --- a/flake.nix +++ b/flake.nix @@ -4,25 +4,21 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs"; flake-utils.url = "github:numtide/flake-utils"; - nix2container.url = "github:nlewo/nix2container"; }; - outputs = { self, nixpkgs, flake-utils, nix2container }: + outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; packageName = "purr"; dockerPackageName = "${packageName}-docker"; - nix2containerPkgs = nix2container.packages.x86_64-linux; - haskellPackages = pkgs.haskellPackages; enableSharedExecutables = false; enableSharedLibraries = false; purr = pkgs.haskell.lib.justStaticExecutables self.packages.${system}.default; - in { packages.${packageName} = @@ -35,10 +31,10 @@ buildInputs = with pkgs; [ ghcid cabal-install + ghc ]; - inputsFrom = map (__getAttr "env") (__attrValues self.packages.${system}); + inputsFrom = builtins.attrValues self.packages.${system}; }; - devShell = self.devShells.${system}.default; packages.${dockerPackageName} = pkgs.dockerTools.buildImage { From f9c3a40c991a3f489f44e3d96e2aaf793801540a Mon Sep 17 00:00:00 2001 From: James Eversole Date: Sun, 7 May 2023 14:04:25 -0500 Subject: [PATCH 8/9] Add README warning that project is currently broken due to a change in crypto libraries --- README | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/README b/README index 7f1d222..0773853 100644 --- a/README +++ b/README @@ -1,8 +1,17 @@ purr ----- +STATUS: BROKEN +DETAILS: Currently unable to decrypt/unencode secrets written to the database. +This broke when converting to Nix because it was learned that the previous +crypto-simple library was out of date and needed to be replaced. Use commit +b4bbf6e5a796d6dfc44ac0a052ec4949d2394927 if you want to build a +working project. + + https://purr.eversole.co -a work-in-progress web application offering customizable password generation and time-limited sharing of secrets. +a work-in-progress web application offering customizable password generation +and time-limited sharing of secrets. TECH STACK @@ -12,15 +21,23 @@ TECH STACK GOALS -- Generate sufficiently memorable but secure passwords for use with accounts that don't offer better authentication methods. -- Share text secrets with others without disclosing the secret in the message itself. +- Generate sufficiently memorable but secure passwords for use with accounts +that don't offer better authentication methods. + +- Share text secrets with others without disclosing the secret in the +message itself. + - Be really cute compared to the competition. + - Provide a minimal and clean interface for generating and sharing passwords. -- Maintain a clean and organized codebase that can be extended to include more utilities than originally anticipated. + +- Maintain a clean and organized codebase that can be extended to include more +utilities than originally anticipated. WHY TRUST YOU? -You shouldn't. This is free and open-source software which you can run on your own hardware. +You shouldn't. This is free and open-source software which you can run on your +own hardware. DEPLOYMENT From 9ad3d1ee7acce4f294267090ab5a017b3801bb65 Mon Sep 17 00:00:00 2001 From: James Eversole Date: Fri, 16 Feb 2024 18:30:06 -0600 Subject: [PATCH 9/9] Working saltine encryption and decryption --- src/Core/SQLite.hs | 6 +++-- src/Core/Types.hs | 4 ++-- src/Feature/Sharing/SQLite.hs | 42 +++++++++++++++++++++-------------- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/Core/SQLite.hs b/src/Core/SQLite.hs index f4f3dc8..2b1cd9e 100644 --- a/src/Core/SQLite.hs +++ b/src/Core/SQLite.hs @@ -3,6 +3,7 @@ module Core.SQLite where import Core.Types import Control.Monad.Reader (ask, lift, liftIO) +import Data.ByteString as B import Database.SQLite.Simple import Database.SQLite.Simple.FromRow @@ -15,6 +16,7 @@ main db = do "CREATE TABLE IF NOT EXISTS pws\ \ (link TEXT PRIMARY KEY,\ \ secret TEXT,\ + \ nonce TEXT,\ \ date DATETIME DEFAULT CURRENT_TIMESTAMP,\ \ life INT,\ \ views INT,\ @@ -24,8 +26,8 @@ main db = do dbPath :: PurrAction String dbPath = lift ask >>= (\a -> return $ dbFile a) -encKey :: PurrAction String -encKey = lift ask >>= (\a -> return $ dbKey a) +encKey :: IO ByteString +encKey = B.readFile "./data/key" confLinkLength :: PurrAction Int confLinkLength = lift ask >>= (\a -> return $ linkLength a) diff --git a/src/Core/Types.hs b/src/Core/Types.hs index 77252b2..f47d272 100644 --- a/src/Core/Types.hs +++ b/src/Core/Types.hs @@ -2,7 +2,7 @@ module Core.Types where import qualified Data.Text as T import qualified Data.Text.Lazy as LT - +import Data.ByteString as B import Control.Monad.Reader (MonadIO, MonadReader, ReaderT) import Data.Text import Database.SQLite.Simple (ToRow) @@ -34,7 +34,6 @@ data DhallConfig = DhallConfig , applicationHost :: String , applicationPort :: Int , dbFile :: String - , dbKey :: String , linkLength :: Int , adminEmail :: String } deriving (Generic, Show) @@ -42,6 +41,7 @@ data DhallConfig = DhallConfig data SecretEntry = SecretEntry { link :: T.Text , secret :: T.Text + , nonce :: B.ByteString , date :: Integer , life :: Integer , views :: Integer diff --git a/src/Feature/Sharing/SQLite.hs b/src/Feature/Sharing/SQLite.hs index 5702c76..3ea8eaa 100644 --- a/src/Feature/Sharing/SQLite.hs +++ b/src/Feature/Sharing/SQLite.hs @@ -6,14 +6,15 @@ import Feature.Generation.Passwords (Password) import Control.Monad.Reader (ask, lift, liftIO) import Data.List.Split (splitOn) -import Data.Maybe (listToMaybe, fromMaybe) +import Data.Maybe (listToMaybe, fromMaybe, Maybe(Just)) import Data.Time.Clock.POSIX (getPOSIXTime) import Database.SQLite.Simple import qualified Crypto.Saltine.Core.SecretBox as Box -import qualified Crypto.Saltine.Internal.SecretBox as IBox +import qualified Crypto.Saltine.Class as CL import qualified Data.ByteString.Base64 as B64 -import qualified Data.ByteString.Char8 as B +import qualified Data.ByteString.Char8 as BSC8 +import qualified Data.ByteString as B import qualified Data.Text as T import qualified Data.Text.Encoding as ET import qualified Data.Text.Lazy as LT @@ -21,7 +22,7 @@ import qualified Data.Text.Lazy as LT findByLink :: String -> PurrAction (Maybe T.Text) findByLink link = do db <- dbPath - key <- encKey + key <- liftIO encKey conn <- liftIO $ open db res <- liftIO $ query conn "SELECT * from pws WHERE link = ?" (Only (last $ splitOn "/" link)) liftIO $ close conn @@ -30,26 +31,26 @@ findByLink link = do insertNewSecret :: T.Text -> Integer -> T.Text -> Integer -> PurrAction () insertNewSecret sec life link maxViews = do db <- dbPath - key <- encKey + key <- liftIO encKey nonce <- liftIO $ Box.newNonce let encSec = encryptSecret key sec nonce conn <- liftIO $ open db time <- liftIO $ epochTime liftIO $ execute conn - "INSERT INTO pws (link, secret, date, life, views, maxViews) VALUES (?, ?, ?, ?, ?, ?)" - (SecretEntry link (encodeSecret encSec) time life 0 maxViews) + "INSERT INTO pws (link, secret, nonce, date, life, views, maxViews) VALUES (?, ?, ?, ?, ?, ?, ?)" + (SecretEntry link (encodeSecret encSec) (CL.encode nonce) time life 0 maxViews) liftIO $ close conn -readEncryptedSecret :: String -> [SecretEntry] -> PurrAction (Maybe T.Text) +readEncryptedSecret :: B.ByteString -> [SecretEntry] -> PurrAction (Maybe T.Text) readEncryptedSecret key sec = do db <- dbPath - nonce <- liftIO $ Box.newNonce - liftIO $ incViews sec db + let secNonce = nonce $ safeHead failedSecret sec + liftIO $ incViews sec db delete <- liftIO $ deleteExpiredSecret sec db - let decKey = decryptSecret key nonce $ decodeSecret $ safeHead failedSecret sec + let decSec = decryptSecret key secNonce $ decodeSecret $ safeHead failedSecret sec if (delete) then return Nothing - else return (ET.decodeLatin1 <$> decKey) + else return (ET.decodeLatin1 <$> decSec) where incViews :: [SecretEntry] -> String -> IO () incViews [] _ = return () @@ -83,18 +84,25 @@ encodeSecret b = ET.decodeUtf8 $ B64.encode b decodeSecret :: SecretEntry -> B.ByteString decodeSecret s = B64.decodeLenient $ ET.encodeUtf8 (secret s) -encryptSecret :: String -> T.Text -> Box.Nonce -> B.ByteString +encryptSecret :: B.ByteString -> T.Text -> Box.Nonce -> B.ByteString encryptSecret k s n = do - Box.secretbox (IBox.Key $ B.pack k) n (ET.encodeUtf8 s) + case (CL.decode k) of + (Just key) -> Box.secretbox key n (ET.encodeUtf8 s) + Nothing -> error "fail" -decryptSecret :: String -> Box.Nonce -> B.ByteString -> Maybe B.ByteString -decryptSecret k n b = Box.secretboxOpen (IBox.Key $ B.pack k) n b +decryptSecret :: B.ByteString -> B.ByteString -> B.ByteString -> Maybe B.ByteString +decryptSecret k n b = do + case (CL.decode k) of + (Just key) -> case (CL.decode n) of + (Just nonce) -> Box.secretboxOpen key nonce b + Nothing -> error "Failed to decode nonce" + Nothing -> error "Failed to decode secret key" epochTime :: IO Integer epochTime = fmap round getPOSIXTime failedSecret :: SecretEntry -failedSecret = SecretEntry "fail" "fail" 0 0 0 0 +failedSecret = SecretEntry "fail" "fail" (BSC8.pack "fail") 0 0 0 0 safeHead :: a -> [a] -> a safeHead x [] = x